ver 7.7
This commit is contained in:
parent
fc8c69d0d5
commit
fa07cb69a8
Binary file not shown.
Binary file not shown.
@ -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 %}
|
||||||
|
|||||||
@ -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"),
|
||||||
|
|||||||
@ -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')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user