Profiled /payroll/ under Django Debug Toolbar and confirmed heavy N+1
patterns in the shared payroll_dashboard() code path (shared by all four
tabs). Main wins:
1. outstanding_project_costs loop + project_chart_data loop previously
fired one PayrollAdjustment SELECT per project (outstanding) and per
(project x 6 months) (chart) — ~42+7 = 49 round-trips on a 7-project
dataset. Replaced with 4 GROUP BY aggregate queries keyed by
project_id / (project_id, month), merged in Python.
2. Per-worker Loan.exists() and get_worker_active_team() checks inside
the workers_data loop — pre-computed into a set + dict once, up-front.
3. team_workers_map loop used `team.workers.filter(active=True)` which
bypasses the prefetch cache; switched to a Prefetch(to_attr=) that
returns already-filtered active workers, dropping 6 duplicate SELECTs.
4. Adjustments tab: reused `paginator.count` for the "Total" stat card
(was firing a second identical COUNT(*)) and reused existing
all_workers / all_teams querysets instead of re-querying for the
filter popovers.
5. Hoisted shared lookups (all_workers, active_projects_list, chart
date-window) so duplicate ordering-identical SELECTs from multiple
call sites collapse into a single evaluated queryset.
===== Quick-Wins Pass A - before/after query counts =====
/ 15q, no duplicates (healthy, no fix)
/payroll/?status=pending 157q (before) -> 26q (after), 0 dupes
/payroll/?status=history 157q -> 26q, 0 dupes
/payroll/?status=loans 158q -> 27q, 0 dupes
/payroll/?status=adjustments 168q -> 34q, 0 dupes
CSS cache-bust token (0c42cde) is still expected to be the biggest
user-felt improvement of this pass — custom.css now holds at
Cloudflare's edge for its full 4h TTL instead of being re-fetched
from the VM on every page load. The payroll-dashboard query-count
cut (~131 SQL round-trips trimmed per render) is a meaningful
admin-UX latency win on top of that, especially under MySQL over
the Flatlogic network.
WeasyPrint confirmed still lazy-imported.
Test suite: 68/68.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>