From 56c10ab938acc3419fe273fb76be4fd377ddd9db Mon Sep 17 00:00:00 2001 From: Konrad du Plessis Date: Sat, 16 May 2026 22:54:30 +0200 Subject: [PATCH] docs: TDD plan for Pay Salary quick action (2 tasks, HARD STOP) Task 1: tile + deep-link hook + render test (TDD on the Django-render part; auto-click is JS/manual-checklist). Task 2: docs. Suite 207->208. Nothing pushed until Konrad's local verification. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...2026-05-16-pay-salary-quick-action-plan.md | 260 ++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 docs/plans/2026-05-16-pay-salary-quick-action-plan.md diff --git a/docs/plans/2026-05-16-pay-salary-quick-action-plan.md b/docs/plans/2026-05-16-pay-salary-quick-action-plan.md new file mode 100644 index 0000000..7f9ca3f --- /dev/null +++ b/docs/plans/2026-05-16-pay-salary-quick-action-plan.md @@ -0,0 +1,260 @@ +# Pay Salary Quick Action — Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task (in-session, fresh subagent + 2-stage review per task). Each subagent uses superpowers:test-driven-development for the Django-render portion. + +**Goal:** Add a "Pay Salary" tile to the home dashboard's admin Quick Actions row that deep-links to `/payroll/?action=pay-salary`; the payroll page auto-clicks the existing Pay Salary button (clean slate → type=Salary → managers-only scope → modal open), then strips the param. + +**Architecture:** A plain `` tile (admin-only branch) + a ~10-line client-side deep-link hook that triggers the existing `paySalaryBtn`. No duplicated behaviour, no view/model/URL change; `?action=` is inert server-side. + +**Tech Stack:** Django 5.2.7 templates + vanilla JS; SQLite local (`USE_SQLITE=true`); Bootstrap 5. + +**Design doc:** `docs/plans/2026-05-16-pay-salary-quick-action-design.md` (commit `fb19655`). + +**Branch / baseline:** `ai-dev`, HEAD `fb19655`, **207/207 tests passing**. On top of the paused, un-pushed Manager/Salaried + pay-type-filter + Salary-auto-scope commits. + +**Test command (Git Bash, per CLAUDE.md):** +```bash +USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 2 +``` + +> **TDD note.** Task 1's Django-render behaviour (tile present, param +> inert) **is** unit-testable — do real TDD there (failing test first). +> The auto-click itself is pure browser JS with no server surface, so — +> per the approved precedent (Task-3 toggle, Salary auto-scope) — it is +> verified by Konrad's manual checklist, NOT a Django test. Do not add a +> JS-source-sniffing test. + +> ⛔ **HARD STOP after Task 2.** Do NOT `git push`, do NOT deploy. Hand +> back to Konrad for the manual checklist. Ships bundled with the rest +> of the paused work in ONE push, on his explicit say-so only. + +--- + +### Task 1: Tile + deep-link hook + render test + +**Files:** +- Modify: `core/templates/core/index.html` — admin Quick Actions card (the "Run Payroll" tile is currently lines 208–211) +- Modify: `core/templates/core/payroll_dashboard.html` — after the `if (paySalaryBtn) { … }` block (currently lines 2101–2117; insert before line 2119) +- Test: `core/tests.py` — new class `PaySalaryQuickActionTests`, inserted immediately before `class WorkHistoryTeamFilterTests` + +**Step 1: Write the failing test** + +Insert this class immediately before `class WorkHistoryTeamFilterTests(TestCase):` in `core/tests.py` (reuse module-level `User`/`TestCase`/`reverse` already imported by neighbouring classes — do not add imports if present): + +```python +class PaySalaryQuickActionTests(TestCase): + """Home dashboard 'Pay Salary' Quick Action: an admin sees a tile + that deep-links /payroll/?action=pay-salary; the param is inert + server-side (no view change). The auto-open click is client-side + JS, verified by manual checklist (not asserted here).""" + + @classmethod + def setUpTestData(cls): + cls.admin = User.objects.create_user( + username='psqa_admin', password='pw', + is_staff=True, is_superuser=True, + ) + + def setUp(self): + self.client.force_login(self.admin) + + def test_home_has_pay_salary_quick_action(self): + resp = self.client.get('/') + self.assertEqual(resp.status_code, 200) + # The tile links to the payroll dashboard with the deep-link param. + self.assertContains(resp, '?action=pay-salary') + # And is labelled "Pay Salary". + self.assertContains(resp, 'Pay Salary') + + def test_payroll_dashboard_ignores_action_param(self): + # The param is purely a client-side signal; the view must not + # care about it — same 200 as a plain /payroll/ load. + resp = self.client.get('/payroll/?action=pay-salary') + self.assertEqual(resp.status_code, 200) +``` + +**Step 2: Run the tests, verify they FAIL** + +```bash +USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests.PaySalaryQuickActionTests -v 2 +``` +Expected: `test_home_has_pay_salary_quick_action` FAILS (no `?action=pay-salary` in `/`). `test_payroll_dashboard_ignores_action_param` will likely already PASS (param already inert) — that's fine; it's a guard that must stay green. + +**Step 3: Add the home dashboard tile** + +In `core/templates/core/index.html`, the "Run Payroll" tile currently is (lines 208–211): + +```html + + + Run Payroll + +``` + +Insert the new tile IMMEDIATELY AFTER that closing `` (line 211) and before the "View History" tile (``): + +```html + {# === PAY SALARY — quick path: opens the Pay-Salary modal on /payroll/ === #} + {# Same fa-user-tie icon as the payroll dashboard's Pay Salary button so #} + {# users have one mental model for "salary = fa-user-tie". #} + + + Pay Salary + +``` + +Match the file's existing indentation (16 spaces for the ``). Each +`{# #}` comment is a single self-contained line — do NOT let any `{#` +span lines. + +**Step 4: Add the deep-link hook** + +In `core/templates/core/payroll_dashboard.html`, the `paySalaryBtn` +wiring ends at line 2117 (` }` closing `if (paySalaryBtn) {`), +followed by a blank line 2118 and the header-button comment at line +2119. Insert this block on the blank line AFTER line 2117 and BEFORE +the `// When the modal is opened from the HEADER button …` comment: + +```javascript + // === Quick-action deep-link: /payroll/?action=pay-salary === + // The home dashboard "Pay Salary" Quick Action links here with this + // param. Auto-click the existing Pay Salary button (which does the + // clean-slate + type=Salary + managers-only scoping + modal open), + // then strip the param so a manual refresh or Back doesn't re-pop + // the modal. Best-effort — never let a deep-link quirk block the page. + try { + var _qsAction = new URLSearchParams(window.location.search).get('action'); + if (_qsAction === 'pay-salary' && paySalaryBtn) { + paySalaryBtn.click(); + var _u = new URL(window.location.href); + _u.searchParams.delete('action'); + window.history.replaceState({}, '', _u.pathname + _u.search + _u.hash); + } + } catch (e) { /* deep-link is best-effort; never block the page */ } +``` + +Notes: `paySalaryBtn` is the `var` declared at line 2100 — in scope +here. It must run AFTER its `addEventListener` (line 2102) so the click +has a handler — this placement (after line 2117) guarantees that. Do +NOT modify the `paySalaryBtn` click handler or any modal-reset logic. + +**Step 5: Guard greps** + +```bash +grep -rn "^\s*{#" core/templates/core/index.html | awk -F: '$0 !~ /#}/ {print}' +grep -rn "^\s*{#" core/templates/core/payroll_dashboard.html | awk -F: '$0 !~ /#}/ {print}' +grep -c 'id="paySalaryBtn"' core/templates/core/payroll_dashboard.html +grep -c 'action=pay-salary' core/templates/core/index.html +``` +Expected: first two → no output; third → `1` (button id still unique, untouched); fourth → `1` (one new tile). + +**Step 6: Run tests, verify PASS** + +```bash +USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests.PaySalaryQuickActionTests -v 2 +``` +Expected: 2 tests OK. + +Then full suite: +```bash +USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 2 +``` +Expected: **208 tests OK** (207 baseline + 1 new render test; the param-inert test was already-green and stays green). If anything unrelated fails, STOP and report. + +**Step 7: Commit (local only, NO push, NEW commit)** + +```bash +git add core/templates/core/index.html core/templates/core/payroll_dashboard.html core/tests.py +git commit -m "feat: Pay Salary quick action on home dashboard (deep-link to modal) + +Admin Quick Actions tile → /payroll/?action=pay-salary; the payroll +page auto-clicks the existing paySalaryBtn then strips the param. +Reuses all existing Pay-Salary machinery; param inert server-side. + +Co-Authored-By: Claude Opus 4.7 (1M context) " +``` + +**Self-review:** `git show HEAD` — exactly 3 files; the tile is inside +the admin Quick Actions card (between Run Payroll and View History); the +hook is after the `if (paySalaryBtn){}` block (not inside it); no +change to the paySalaryBtn handler or modal resets; greps clean; suite +208. + +--- + +### Task 2: Docs + final regression + HARD STOP + +**Files:** +- Modify: `docs/plans/parked-work.md` +- Modify: `CLAUDE.md` + +**Step 1: Full regression (anchor)** + +```bash +USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 2 +``` +Expected: **208 OK**. If not, STOP. + +**Step 2: Update `docs/plans/parked-work.md`** + +Read the file; find the paused Manager/Salaried entry (under "⏸ Paused +… awaiting Konrad's verification"; already carries the pay-type-filter +and Salary-auto-scope notes). Append, matching the file's prose style: + +> Also now: a home-dashboard admin Quick Actions tile **"Pay Salary"** +> deep-links `/payroll/?action=pay-salary` and auto-opens the existing +> Pay Salary modal (param stripped after). Design +> `docs/plans/2026-05-16-pay-salary-quick-action-design.md`, plan +> `docs/plans/2026-05-16-pay-salary-quick-action-plan.md`. Same HARD +> STOP — bundled into the one push on Konrad's say-so. + +(If no such entry exists, add a short one under "⏸ Paused" matching the +others' format. Report which case.) + +**Step 3: Update `CLAUDE.md`** + +In the "## Manager / Salaried pay (May 2026)" section, after the +existing **"Salary picker safety:"** line, append one line in the same +terse style: + +> **Pay Salary quick action:** the home dashboard's admin Quick Actions +> row has a "Pay Salary" tile linking `/payroll/?action=pay-salary`; +> `payroll_dashboard.html` JS auto-clicks the existing `paySalaryBtn` +> on load when that param is present, then strips it via +> `history.replaceState` (no re-pop on refresh). `?action=` is inert +> server-side — no view/URL change. + +**Step 4: Verify docs** + +```bash +grep -n "action=pay-salary\|Pay Salary quick action\|Pay Salary" docs/plans/parked-work.md CLAUDE.md +``` +Expected: new lines present in both files; Markdown intact. + +**Step 5: Commit (local only, NO push, NEW commit)** + +```bash +git add docs/plans/parked-work.md CLAUDE.md +git commit -m "docs: note Pay Salary quick action (rides paused bundle) + +Co-Authored-By: Claude Opus 4.7 (1M context) " +``` + +**Self-review:** `git show --stat HEAD` — only the 2 doc files, additions only. + +--- + +## ⛔ HARD STOP — hand back to Konrad + +After Task 2's commit: +1. `git status` clean; `git log --oneline -3` shows the 2 new commits on `ai-dev` on top of `fb19655`. +2. Full suite once more → **208/208 OK**. +3. **Do NOT `git push`. Do NOT deploy.** Report the commit list + test count and point Konrad at the **Manual verification** section of `docs/plans/2026-05-16-pay-salary-quick-action-design.md` (7 steps). +4. Push/deploy only on Konrad's explicit approval, bundled with the rest of the paused Manager/Salaried + pay-type-filter + Salary-auto-scope work in ONE push (github + gitea; deploy order: pull → migrate → collectstatic → restart last). + +## Notes + +- **DRY:** the tile just triggers the existing `paySalaryBtn`; all real behaviour stays in one place. +- **YAGNI:** no view/URL/server handling of `?action=`; no generic deep-link framework; no supervisor entry. +- **Why the hook sits after `if (paySalaryBtn){}`:** the click handler must be attached before we synthesise a click; this placement guarantees ordering and keeps `paySalaryBtn` in scope. +- **No migration / view / URL change**; only one new Django render test by design (the auto-click is JS, manual-checklist verified — documented precedent).