From 269d86259a4efee83f2259442991c03aaf1d3792 Mon Sep 17 00:00:00 2001 From: Konrad du Plessis Date: Thu, 23 Apr 2026 19:26:46 +0200 Subject: [PATCH] docs(adjustments): Shipped block on design doc + CLAUDE.md URL routes Captures the 11-task implementation, 5 deviations (biggest: the CP1 pivot from Choices.js chip-multiselect to popover-checkbox filter UX after Konrad flagged the chip pattern as intrusive), 14 new adjustments-tab tests, and total code churn (~+1400 lines). CLAUDE.md URL Routes table gains two rows so future sessions surface /payroll/?status=adjustments and the bulk-delete endpoint. Feature ready for final whole-feature code review + batched push. --- CLAUDE.md | 2 + .../2026-04-23-adjustments-tab-design.md | 112 ++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index e27a773..bb2cb42 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -179,6 +179,8 @@ USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 2 | `/projects/report/csv/` | `project_batch_report_csv` | Admin: project batch report as CSV download | | `/toggle///` | `toggle_active` | Admin: AJAX toggle active status | | `/payroll/` | `payroll_dashboard` | Admin: pending payments, loans, charts | +| `/payroll/?status=adjustments` | `payroll_dashboard` | Admin: browse ALL payroll adjustments (filter by type, worker, team, status, date; group-by type/worker; bulk-delete unpaid; row actions open existing modals) | +| `/payroll/adjustments/bulk-delete/` | `bulk_delete_adjustments` | Admin: POST-only; delete multiple unpaid adjustments in one shot via fetch() with X-CSRFToken cookie | | `/payroll/pay//` | `process_payment` | Admin: process payment (atomic) | | `/payroll/price-overtime/` | `price_overtime` | Admin: AJAX price unpriced OT entries | | `/payroll/adjustment/add/` | `add_adjustment` | Admin: create adjustment | diff --git a/docs/plans/2026-04-23-adjustments-tab-design.md b/docs/plans/2026-04-23-adjustments-tab-design.md index d78e597..88c6bf2 100644 --- a/docs/plans/2026-04-23-adjustments-tab-design.md +++ b/docs/plans/2026-04-23-adjustments-tab-design.md @@ -555,3 +555,115 @@ Hand off to `superpowers:writing-plans`. Two design docs exist today: - `docs/plans/2026-04-23-adjustments-tab-design.md` (this doc — Feature 2) Recommended sequence: **Feature 1 first** (smaller, ~5-6 tasks; Choices.js patterns learned here can lift into Feature 2). Ship Feature 1, validate on production, then Feature 2's plan + implementation. Both design docs stay local until their respective implementations ship; then push everything together. + +--- + +## 19. Shipped — 2026-04-23 + +Implementation complete. 11 tasks + 1 hard-pause checkpoint + 1 round of +Konrad feedback fixes. 65 tests passing (up from 47 pre-feature). + +### Commit map + +| Task | Commits | Scope | +|------|---------|-------| +| 1 | `97d8a69` | `type_slug` template filter (+ tests) | +| 2 | `a20a025` | CSS badge palette + foundational styles | +| 3 | `10d381e`, `89f109a` | Backend filter branch + stats; strengthened subquery test | +| 4 | `b450bd3`, `06b3315` | Tab markup + filter bar + flat table; pagination / a11y / N+1 fixes | +| 4* | `e088192`, `4c1cdb6` | Two multi-line `{# #}` comment hotfixes — see Deviations #2 | +| CP1 A | `b59eb31` | Row actions → modals + project link → History tab | +| CP1 B | `4f15e4b` | **Replaced Choices.js chip-multiselect with popover-checkbox filter UX** — see Deviations #1 | +| 5 | `0862805`, `e5d06f9` | Group-by type/worker + toggle + colour-accented headers; chevron + ordering polish | +| 6 | `03f177e`, `5f2e6d8`, `4c3e90f` | Bulk-delete endpoint; id-collision fix; **cascade logic fix** — see Deviations #3 | +| 7 | `6905703` | Team → Workers cross-filter | +| 8 | `c851b49` | Date picker single/range toggle + preset buttons | +| 9 | `7b71048` | Sortable column headers with URL state | +| 10 | `9bb9ede` | Empty-state card with recovery CTAs | + +### Deviations from the original design + +1. **Choices.js chip-multiselect → popover-checkbox filters.** The original + design (§3) specified Choices.js for Type/Workers/Teams multi-selects — + the same pattern used in the report page's retired modal. At Checkpoint 1 + Konrad flagged that the chip-style rendering was intrusive once multiple + options were selected, dominating the filter bar. We replaced the + Choices.js widgets with pill-buttons that open popovers containing a + scrollable checkbox list + search + Select All / Invert / Clear. Reuses + Feature 1's `.filter-pill` / `.filter-popover` CSS vocabulary. + Implemented in `4f15e4b`. + +2. **Multi-line `{# ... #}` comment bug, twice.** Django's `{# #}` comment + syntax is single-line only — multi-line blocks need + `{% comment %}...{% endcomment %}`. We shipped the bug in the Task 4 + row partial (`e088192` fixed it) and then AGAIN in the Fix-A worker + cell (`4c1cdb6` fixed it). Both shipped into production-looking + renders, not caught by automated tests. Lesson: add a repo-wide + grep guard or a Django linter for this class of template bug. + +3. **Bulk-delete cascade gap.** The original Task 6 spec's reference + implementation (`PayrollAdjustment.objects.filter(...).delete()`) + silently orphaned linked `Loan` rows and `priced_workers` M2M entries + when bulk-deleting adjustments of type "New Loan", "Advance Payment", + or "Overtime". The single-row `delete_adjustment` view had 30+ lines + of cascade logic the bulk view didn't use. Code review caught it. + Fix: extracted `_delete_adjustment_with_cascade(adj)` helper and + delegated both views to it — ensuring bulk and single-row have + identical semantics. Also added a 'has_paid_repayments' skip reason + in the JSON response so the UI can indicate why some rows were kept. + Implemented in `4c3e90f`. + +4. **Row actions → modals (CP1 Fix A).** The original design §4 said row + actions "match the rest of the dashboard — NO expandable rows". We + interpreted this as table-to-page navigation (Worker name → `/workers//`, + View Payslip → `/payroll/payslip//`). At CP1 Konrad clarified he + wanted in-place MODALS matching the Pending tab: worker name opens + `#workerLookupModal`, paid-row eye icon opens `#previewPayslipModal`, + project name goes to `/projects//#history` (History tab active). + Implemented in `b59eb31`; tiny tab-activation helper in + `projects/detail.html` picks up the URL hash. + +5. **id collision.** Task 4 added `id="adjSelectAll"` to the table + header checkbox, but the Add Adjustment modal already used that id + for its Select-All anchor. `document.getElementById` returns only + the first match, so the modal's handler silently bound to the table + checkbox. Renamed the table's to `#adjTableSelectAll` in `5f2e6d8`. + +### Tests + +Added 14 tests in `AdjustmentsTabTests`: + +- `test_admin_sees_adjustments_tab` — 200 + active_tab set +- `test_supervisor_forbidden` — non-admin redirected +- `test_type_multi_filter` — union on multi-value param (uses adj_total_count) +- `test_worker_multi_filter` — worker filter +- `test_team_filter_uses_subquery_no_inflation` — proves the subquery + pattern with 2 teams × 2 workers × 3 adjustments (naive would return 6) +- `test_status_filter_unpaid` — payroll_record__isnull filter +- `test_date_range_filter` — date__gte/lte +- `test_stats_scoped_to_filtered_set` — counts + sums respect filter +- `test_group_by_type` — buckets + net_sum + descending-magnitude ordering +- `test_group_by_worker` — buckets by worker_id +- `test_bulk_delete_only_affects_unpaid` — paid row survives +- `test_bulk_delete_requires_admin` — 403 for supervisors +- `test_bulk_delete_cascades_new_loan` — Loan + unpaid repayments gone too +- `test_bulk_delete_skips_loan_with_paid_repayments` — refuses, reports reason +- `test_team_worker_pairs_json_context_key` — raw Python list shape (not double-encoded) + +Also extended existing tests: +- `test_group_by_type` gained a descending-magnitude ordering assertion +- `TypeSlugFilterTests` has 3 tests for the new template filter + +### Net code churn + +- `core/views.py`: ~+200 lines (filter branch + 2 helpers + bulk-delete view) +- `core/templates/core/payroll_dashboard.html`: ~+450 lines (tab + filter bar + popover markup + table + JS modules) +- `core/templates/core/_adjustment_row.html`: new file, ~120 lines +- `core/templatetags/format_tags.py`: ~+35 lines (`type_slug`, `money_abs`, `url_replace`) +- `static/css/custom.css`: ~+220 lines (badge palette + layout skeleton + popover extensions + colour-accented group headers + chevron rotation) +- `core/tests.py`: ~+380 lines (14 new adjustments tests + 3 type_slug tests) +- `core/urls.py`: +1 route +- Total: ~+1,400 lines added, ~-100 replaced/removed. + +(Original estimate: ~960 lines. Actual: +44% — mostly from the popover- +checkbox filter rewrite, the bulk-delete cascade, and the cross-filter JS.)