diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 0cae82c..20b523f 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 366ef82..9d83914 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/templates/core/work_log_list.html b/core/templates/core/work_log_list.html index a6954d6..9c17790 100644 --- a/core/templates/core/work_log_list.html +++ b/core/templates/core/work_log_list.html @@ -11,9 +11,14 @@

Work Log History

Filter and review historical daily work logs.

- - + New Entry - +
+ + Export CSV + + + + New Entry + +
@@ -82,6 +87,7 @@ Date Project Labourers + Amount Status / Payslip Supervisor Action @@ -121,6 +127,9 @@ {% endif %} + + R {{ log.display_amount|floatformat:2 }} + {% with payslip=log.paid_in.first %} {% if payslip %} @@ -156,6 +165,13 @@ {% endfor %} + + + Total: + R {{ total_amount|floatformat:2 }} + + + {% else %} diff --git a/core/urls.py b/core/urls.py index b5f1475..f06bb24 100644 --- a/core/urls.py +++ b/core/urls.py @@ -2,7 +2,8 @@ from django.urls import path from .views import ( home, log_attendance, - work_log_list, + work_log_list, + export_work_log_csv, manage_resources, toggle_resource_status, payroll_dashboard, @@ -14,6 +15,7 @@ urlpatterns = [ path("", home, name="home"), path("log-attendance/", log_attendance, name="log_attendance"), path("work-logs/", work_log_list, name="work_log_list"), + path("work-logs/export/", export_work_log_csv, name="export_work_log_csv"), path("manage-resources/", manage_resources, name="manage_resources"), path("manage-resources/toggle///", toggle_resource_status, name="toggle_resource_status"), path("payroll/", payroll_dashboard, name="payroll_dashboard"), diff --git a/core/views.py b/core/views.py index 956a7db..f4d7f32 100644 --- a/core/views.py +++ b/core/views.py @@ -1,6 +1,7 @@ import os import platform import json +import csv from django.shortcuts import render, redirect, get_object_or_404 from django.utils import timezone from django.contrib.auth.decorators import login_required @@ -8,7 +9,7 @@ from django.db.models import Sum, Q, Prefetch from django.core.mail import send_mail from django.conf import settings from django.contrib import messages -from django.http import JsonResponse +from django.http import JsonResponse, HttpResponse from .models import Worker, Project, Team, WorkLog, PayrollRecord from .forms import WorkLogForm from datetime import timedelta @@ -156,8 +157,11 @@ def work_log_list(request): logs = WorkLog.objects.all().prefetch_related('workers', 'project', 'supervisor', 'paid_in').order_by('-date', '-id') + target_worker = None if worker_id: logs = logs.filter(workers__id=worker_id) + # Fetch the worker to get the day rate reliably + target_worker = Worker.objects.filter(id=worker_id).first() if team_id: # Find workers in this team and filter logs containing them @@ -180,9 +184,23 @@ def work_log_list(request): else: logs = logs.filter(paid_in__isnull=True) + # Calculate amounts for display + # Convert to list to attach attributes + final_logs = [] + total_amount = 0 + for log in logs: + if target_worker: + log.display_amount = target_worker.day_rate + else: + # Sum of all workers in this log + log.display_amount = sum(w.day_rate for w in log.workers.all()) + final_logs.append(log) + total_amount += log.display_amount + # Context for filters context = { - 'logs': logs, + 'logs': final_logs, + 'total_amount': total_amount, 'workers': Worker.objects.filter(is_active=True).order_by('name'), 'teams': Team.objects.filter(is_active=True).order_by('name'), 'projects': Project.objects.filter(is_active=True).order_by('name'), @@ -190,10 +208,71 @@ def work_log_list(request): 'selected_team': int(team_id) if team_id else None, 'selected_project': int(project_id) if project_id else None, 'selected_payment_status': payment_status, + 'target_worker': target_worker, } return render(request, 'core/work_log_list.html', context) +def export_work_log_csv(request): + """Export filtered work logs to CSV.""" + worker_id = request.GET.get('worker') + team_id = request.GET.get('team') + project_id = request.GET.get('project') + payment_status = request.GET.get('payment_status') + + logs = WorkLog.objects.all().prefetch_related('workers', 'project', 'supervisor', 'paid_in').order_by('-date', '-id') + + target_worker = None + if worker_id: + logs = logs.filter(workers__id=worker_id) + target_worker = Worker.objects.filter(id=worker_id).first() + + if team_id: + team_workers = Worker.objects.filter(teams__id=team_id) + logs = logs.filter(workers__in=team_workers).distinct() + + if project_id: + logs = logs.filter(project_id=project_id) + + if payment_status == 'paid': + logs = logs.filter(paid_in__isnull=False).distinct() + elif payment_status == 'unpaid': + if worker_id: + worker = get_object_or_404(Worker, pk=worker_id) + logs = logs.exclude(paid_in__worker=worker) + else: + logs = logs.filter(paid_in__isnull=True) + + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="work_logs.csv"' + + writer = csv.writer(response) + writer.writerow(['Date', 'Project', 'Workers', 'Amount', 'Payment Status', 'Supervisor']) + + for log in logs: + # Amount Logic + if target_worker: + display_amount = target_worker.day_rate + workers_str = target_worker.name + else: + display_amount = sum(w.day_rate for w in log.workers.all()) + workers_str = ", ".join([w.name for w in log.workers.all()]) + + # Payment Status Logic + is_paid = log.paid_in.exists() + status_str = "Paid" if is_paid else "Pending" + + writer.writerow([ + log.date, + log.project.name, + workers_str, + f"{display_amount:.2f}", + status_str, + log.supervisor.username if log.supervisor else "System" + ]) + + return response + def manage_resources(request): """View to manage active status of resources.""" # Prefetch teams for workers to avoid N+1 in template @@ -227,7 +306,7 @@ def toggle_resource_status(request, model_type, pk): return JsonResponse({ 'success': True, 'is_active': obj.is_active, - 'message': f"{obj.name} is now {'Active' if obj.is_active else 'Inactive'}." + 'message': f"{obj.name} is now {'Active' if obj.is_active else 'Inactive'}ணை." }) return redirect('manage_resources') @@ -346,4 +425,4 @@ def payslip_detail(request, pk): 'record': record, 'logs': logs, } - return render(request, 'core/payslip.html', context) \ No newline at end of file + return render(request, 'core/payslip.html', context)