From 4aac2c1cf2e1a6226f35315d1e14dc16ff890b72 Mon Sep 17 00:00:00 2001 From: Konrad du Plessis Date: Sat, 16 May 2026 13:13:30 +0200 Subject: [PATCH] docs: design for Managers pay-type filter (Approach A, display-only) Konrad-approved design for a display-only ?pay_type= filter on /workers/ and a "Managers only" toggle on the Add-Adjustment modal picker. No model/migration/URL changes; rides with the paused Manager/Salaried feature's HARD STOP (nothing pushed until local verification). Co-Authored-By: Claude Opus 4.7 (1M context) --- ...26-05-16-managers-paytype-filter-design.md | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 docs/plans/2026-05-16-managers-paytype-filter-design.md diff --git a/docs/plans/2026-05-16-managers-paytype-filter-design.md b/docs/plans/2026-05-16-managers-paytype-filter-design.md new file mode 100644 index 0000000..3e9f966 --- /dev/null +++ b/docs/plans/2026-05-16-managers-paytype-filter-design.md @@ -0,0 +1,147 @@ +# Managers Pay-Type Filter — Design + +**Date:** 16 May 2026 +**Status:** Approved by Konrad on 16 May 2026; ready for implementation plan. +**Branch:** `ai-dev`, on top of the **paused, un-pushed** Manager/Salaried +commits (HEAD `4d06b83`). Same logical feature. +**NOT to be pushed/deployed until Konrad confirms it works locally** — +this rides with the paused Manager/Salaried feature's HARD STOP. + +## Goal (one sentence) + +Make `Worker(pay_type='fixed')` managers fast to find in the two places +that matter — the `/workers/` list and the Add-Adjustment modal's worker +picker — via a pure display-only `pay_type` filter that touches no model, +migration, URL, or money math. + +## Why + +Managers are `Worker(pay_type='fixed')` rows (Path-A — no separate model). +They deliberately stay mixed into the normal worker list and stay payable +in the Add-Adjustment modal (the documented critical invariant). But +there is currently no way to *narrow* either view to just managers, so +paying Fitz's salary means scrolling/searching the full roster. A +lightweight filter — mirroring the existing status/team filters — solves +this with the lowest possible risk. + +## Decision (from the brainstorm) + +Konrad chose **Approach A — `pay_type` filter** over a real "Managers +team" (B) or both. Rationale: A works regardless of whether team +membership is kept up to date, needs no new Team, and is the smallest +possible change. A Managers team can still be created manually later as +an organisational habit — nothing here precludes it. + +## § 1 — `/workers/` list filter + +- **`core/views.py::worker_list`** — add one GET param `?pay_type=`, + handled exactly like the existing `status`/`team` params: + - `pay_type=daily` → `workers = workers.filter(pay_type='daily')` + - `pay_type=fixed` → `workers = workers.filter(pay_type='fixed')` + - absent / anything else → no filter (existing behaviour unchanged) + - Add `pay_type_filter` to the template context. +- **`core/templates/core/workers/list.html`** — one more `` +and Select-All/Clear links in a filter row at ~line 1075. + +- **Worker rows (~line 1087)** — add `data-pay-type="{{ w.pay_type }}"` + to each `.form-check` wrapper. `all_workers` is a `Worker` queryset so + `w.pay_type` is already available — **no view change**. +- **Filter row (~line 1075)** — add a small + `` toggling row `display` + by a `data-*` attribute) — no new mechanism, no new failure modes. + +## § 3 — Tests (~4, in `core/tests.py`) + +1. `/workers/?pay_type=fixed` → context `workers` contains only the + manager, not the daily worker. +2. `/workers/?pay_type=daily` → context `workers` contains only the + daily worker, not the manager. +3. `/workers/` (no `pay_type` param) → both appear (regression — the + default behaviour must not change). +4. Add-Adjustment modal (payroll dashboard render) still includes a + manager in `all_workers` **and** the rendered manager row carries + `data-pay-type="fixed"` (regression on the must-stay-payable + invariant + the new attribute is present). + +Confirm any existing `/workers/` list test still passes unchanged +(the no-param path is deliberately untouched). + +## Files touched + +| File | Change | +|---|---| +| `core/views.py::worker_list` | +`pay_type` GET param branch, +context key | +| `core/templates/core/workers/list.html` | +1 filter ``, +1 small JS handler | +| `core/tests.py` | +4 tests | +| `docs/plans/parked-work.md` | Update the paused Manager/Salaried entry to note this filter rides with it | +| `CLAUDE.md` | One line under the Manager/Salaried section: `/workers/?pay_type=fixed` filter + modal toggle exist (display-only) | + +**No model / migration / URL / dependency changes.** ~80–120 LOC incl. +tests. + +## Out of scope (deliberately) + +- No "Managers team" creation (Approach B explicitly not chosen). +- No change to salary-payment logic, the `Salary` adjustment, or any + report/cost math. +- No server-side narrowing of the modal's `all_workers` (would break + the must-stay-payable invariant). +- No `pay_type` filter on `/history/`, `/absences/`, etc. — managers + can't reach those records anyway (they're excluded from attendance + and absence pickers). + +## Verification (manual, local — Konrad) + +1. `/workers/` → new "pay type" dropdown defaults to "All pay types"; + list looks exactly as before. +2. Select **Managers (Salaried)** → only managers (e.g. Fitz) shown; + URL is `/workers/?pay_type=fixed`. Combine with a team or status + filter → both narrow together. +3. Select **Daily workers** → managers disappear; daily workers remain. +4. Payroll dashboard → **Add Adjustment** modal → new pay-type + `