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) <noreply@anthropic.com>
This commit is contained in:
parent
4d06b83e30
commit
4aac2c1cf2
147
docs/plans/2026-05-16-managers-paytype-filter-design.md
Normal file
147
docs/plans/2026-05-16-managers-paytype-filter-design.md
Normal file
@ -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 `<select>` in
|
||||
the existing filter row, styled identically to the status/team
|
||||
dropdowns: **All pay types / Daily workers / Managers (Salaried)**.
|
||||
Values are the DB strings (`""` / `daily` / `fixed`) per the Path-A
|
||||
convention (UI label "Managers (Salaried)", DB value `fixed`).
|
||||
|
||||
## § 2 — Add-Adjustment modal "Managers only" toggle
|
||||
|
||||
The picker (in `core/templates/core/payroll_dashboard.html`, ~line 1086)
|
||||
is a scrollable list of `.form-check` rows built from the `all_workers`
|
||||
context queryset, with an existing "Quick select by team…" `<select>`
|
||||
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
|
||||
`<select id="addAdjPayTypeFilter" class="form-select form-select-sm">`
|
||||
next to the team quick-select: **All / Daily only / Managers only**.
|
||||
- **One small JS handler** — on `change`, iterate `.add-adj-worker`
|
||||
rows and toggle `display` on the wrapping `.form-check` by
|
||||
`data-pay-type`. "All" clears the filter (shows every row).
|
||||
|
||||
### Why this is safe
|
||||
|
||||
- `all_workers` is **not** narrowed server-side, so managers remain
|
||||
payable — the critical Manager/Salaried invariant is untouched. The
|
||||
toggle only hides rows client-side and is fully reversible to "All".
|
||||
- The checkbox `name="workers"` value remains the **source of truth**
|
||||
for what is submitted; `data-pay-type` is presentational only (it
|
||||
drives visibility, not selection state) — consistent with the
|
||||
CLAUDE.md "read state from the input value, not data attributes"
|
||||
gotcha (that gotcha is about *state*; visibility is fine on a
|
||||
data-attr).
|
||||
- Reuses the exact pattern of the existing batch-pay / pending-table
|
||||
client-side team+loan filters (a `<select>` 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 `<select>` |
|
||||
| `core/templates/core/payroll_dashboard.html` | +`data-pay-type` on worker rows, +1 filter `<select>`, +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
|
||||
`<select>` defaults to "All"; full list shown.
|
||||
5. Choose **Managers only** → picker shows only managers; pick Fitz,
|
||||
type = Salary, complete the payment as before (logic unchanged).
|
||||
6. Switch back to **All** → every worker visible again; selections you
|
||||
already ticked are preserved.
|
||||
7. Full suite green (`USE_SQLITE=true … manage.py test core.tests`).
|
||||
|
||||
Then — and only then — Konrad decides whether to push to `ai-dev` +
|
||||
deploy (together with the rest of the paused Manager/Salaried feature).
|
||||
|
||||
## Branch / deploy
|
||||
|
||||
Build on `ai-dev` on top of `4d06b83`. **Do NOT push to origin or
|
||||
deploy** until Konrad has run the local verification above and
|
||||
explicitly approves. This is part of the daily-use payroll path —
|
||||
hands-on local check before production, same as the rest of the
|
||||
Manager/Salaried feature it ships with.
|
||||
Loading…
x
Reference in New Issue
Block a user