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) <noreply@anthropic.com>
This commit is contained in:
Konrad du Plessis 2026-05-16 22:54:30 +02:00
parent fb19655a1d
commit 56c10ab938

View File

@ -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 `<a>` 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 208211)
- Modify: `core/templates/core/payroll_dashboard.html` — after the `if (paySalaryBtn) { … }` block (currently lines 21012117; 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 208211):
```html
<a href="{% url 'payroll_dashboard' %}" class="quick-action">
<i class="fas fa-money-check-alt"></i>
<span>Run Payroll</span>
</a>
```
Insert the new tile IMMEDIATELY AFTER that closing `</a>` (line 211) and before the "View History" tile (`<a href="{% url 'work_history' %}" …>`):
```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". #}
<a href="{% url 'payroll_dashboard' %}?action=pay-salary" class="quick-action">
<i class="fas fa-user-tie"></i>
<span>Pay Salary</span>
</a>
```
Match the file's existing indentation (16 spaces for the `<a>`). 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) <noreply@anthropic.com>"
```
**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) <noreply@anthropic.com>"
```
**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).