diff --git a/core/templates/core/report.html b/core/templates/core/report.html index 177f233..b3d0538 100644 --- a/core/templates/core/report.html +++ b/core/templates/core/report.html @@ -199,33 +199,43 @@ {# === HERO KPI BAND === #} +{# Sub-labels intentionally call out current-pay-rate basis (Finding 2) + and the active filter scope (Finding 5) so the KPIs aren't read as + apples-to-apples when filters are on. #}
| Project | Worker-Days | Total Cost |
|---|---|---|
| Project | +Worker-Days | ++ Day-Rate Cost + | +
| {{ item.project }} | {{ item.worker_days }} | R {{ item.total|money }} |
| Team | Worker-Days | Total Cost |
|---|---|---|
| Team | +Worker-Days | ++ Day-Rate Cost + | +
| {{ item.team }} | {{ item.worker_days }} | R {{ item.total|money }} |
+ + Days reflect work logged in this period. + Total Paid reflects payments received in this period — + they may not match if a worker was paid in this period for previous-period work. +
{% else %}No worker payment data for this period.
{% endif %} diff --git a/core/views.py b/core/views.py index 04de15c..cae1f7d 100644 --- a/core/views.py +++ b/core/views.py @@ -3308,18 +3308,25 @@ def payroll_dashboard(request): }) # === CHART DATA: Per-Worker Monthly Breakdown === - # Pre-compute payment breakdown for each active worker over the last 6 months. + # Pre-compute payment breakdown for each worker over the last 6 months. # This powers the "By Worker" toggle on the Monthly Payroll Totals chart. # Only ~14 workers x 6 months = tiny dataset, so we embed it all as JSON # and switching between workers is instant (no server round-trips). # + # SCOPE: no `worker__active=True` filter — historical payments belong + # to the worker they were made to, even if that worker has since been + # deactivated. This matches `recent_payments_total` above (which also + # has no active-filter). The outer loop later iterates `active_workers` + # only, so deactivated-worker rows in the lookup dicts are simply + # ignored — but the SQL stays consistent across all payroll-dashboard + # stats (Finding 18, May 2026). + # # `six_months_ago_date` is already defined above (hoisted next to the # date-window setup) and reused here. # Query 1: Total amount paid per worker per month. # Uses database-level grouping — one query for ALL workers at once. worker_monthly_paid_qs = PayrollRecord.objects.filter( - worker__active=True, date__gte=six_months_ago_date, ).values( 'worker_id', @@ -3338,7 +3345,6 @@ def payroll_dashboard(request): # so it lines up with when the payment actually happened. worker_monthly_adj_qs = PayrollAdjustment.objects.filter( payroll_record__isnull=False, - worker__active=True, payroll_record__date__gte=six_months_ago_date, ).values( 'worker_id',