This commit is contained in:
Flatlogic Bot 2026-02-03 23:08:10 +00:00
parent fc8c69d0d5
commit fa07cb69a8
5 changed files with 105 additions and 8 deletions

View File

@ -11,9 +11,14 @@
<h1 class="display-5 mb-2">Work Log History</h1>
<p class="lead opacity-75">Filter and review historical daily work logs.</p>
</div>
<a href="{% url 'log_attendance' %}" class="btn btn-accent shadow-sm">
+ New Entry
</a>
<div class="d-flex gap-2">
<a href="{% url 'export_work_log_csv' %}?{{ request.GET.urlencode }}" class="btn btn-outline-secondary shadow-sm border-0 bg-white text-dark">
<i class="bi bi-download me-1"></i> Export CSV
</a>
<a href="{% url 'log_attendance' %}" class="btn btn-accent shadow-sm">
+ New Entry
</a>
</div>
</div>
<!-- Filter Card -->
@ -82,6 +87,7 @@
<th class="ps-4">Date</th>
<th>Project</th>
<th>Labourers</th>
<th>Amount</th>
<th>Status / Payslip</th>
<th>Supervisor</th>
<th class="pe-4 text-end">Action</th>
@ -121,6 +127,9 @@
</div>
{% endif %}
</td>
<td>
<span class="fw-bold font-monospace text-dark">R {{ log.display_amount|floatformat:2 }}</span>
</td>
<td>
{% with payslip=log.paid_in.first %}
{% if payslip %}
@ -156,6 +165,13 @@
</tr>
{% endfor %}
</tbody>
<tfoot class="table-light border-top-2">
<tr>
<td colspan="3" class="text-end fw-bold ps-4">Total:</td>
<td class="fw-bold font-monospace text-dark bg-warning bg-opacity-10">R {{ total_amount|floatformat:2 }}</td>
<td colspan="3"></td>
</tr>
</tfoot>
</table>
</div>
{% else %}

View File

@ -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/<str:model_type>/<int:pk>/", toggle_resource_status, name="toggle_resource_status"),
path("payroll/", payroll_dashboard, name="payroll_dashboard"),

View File

@ -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)
return render(request, 'core/payslip.html', context)