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

View File

@ -3,6 +3,7 @@ from .views import (
home, home,
log_attendance, log_attendance,
work_log_list, work_log_list,
export_work_log_csv,
manage_resources, manage_resources,
toggle_resource_status, toggle_resource_status,
payroll_dashboard, payroll_dashboard,
@ -14,6 +15,7 @@ urlpatterns = [
path("", home, name="home"), path("", home, name="home"),
path("log-attendance/", log_attendance, name="log_attendance"), path("log-attendance/", log_attendance, name="log_attendance"),
path("work-logs/", work_log_list, name="work_log_list"), 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/", manage_resources, name="manage_resources"),
path("manage-resources/toggle/<str:model_type>/<int:pk>/", toggle_resource_status, name="toggle_resource_status"), path("manage-resources/toggle/<str:model_type>/<int:pk>/", toggle_resource_status, name="toggle_resource_status"),
path("payroll/", payroll_dashboard, name="payroll_dashboard"), path("payroll/", payroll_dashboard, name="payroll_dashboard"),

View File

@ -1,6 +1,7 @@
import os import os
import platform import platform
import json import json
import csv
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.utils import timezone from django.utils import timezone
from django.contrib.auth.decorators import login_required 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.core.mail import send_mail
from django.conf import settings from django.conf import settings
from django.contrib import messages 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 .models import Worker, Project, Team, WorkLog, PayrollRecord
from .forms import WorkLogForm from .forms import WorkLogForm
from datetime import timedelta 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') logs = WorkLog.objects.all().prefetch_related('workers', 'project', 'supervisor', 'paid_in').order_by('-date', '-id')
target_worker = None
if worker_id: if worker_id:
logs = logs.filter(workers__id=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: if team_id:
# Find workers in this team and filter logs containing them # Find workers in this team and filter logs containing them
@ -180,9 +184,23 @@ def work_log_list(request):
else: else:
logs = logs.filter(paid_in__isnull=True) 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 for filters
context = { context = {
'logs': logs, 'logs': final_logs,
'total_amount': total_amount,
'workers': Worker.objects.filter(is_active=True).order_by('name'), 'workers': Worker.objects.filter(is_active=True).order_by('name'),
'teams': Team.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'), '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_team': int(team_id) if team_id else None,
'selected_project': int(project_id) if project_id else None, 'selected_project': int(project_id) if project_id else None,
'selected_payment_status': payment_status, 'selected_payment_status': payment_status,
'target_worker': target_worker,
} }
return render(request, 'core/work_log_list.html', context) 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): def manage_resources(request):
"""View to manage active status of resources.""" """View to manage active status of resources."""
# Prefetch teams for workers to avoid N+1 in template # Prefetch teams for workers to avoid N+1 in template
@ -227,7 +306,7 @@ def toggle_resource_status(request, model_type, pk):
return JsonResponse({ return JsonResponse({
'success': True, 'success': True,
'is_active': obj.is_active, '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') return redirect('manage_resources')