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:
parent
fb19655a1d
commit
56c10ab938
260
docs/plans/2026-05-16-pay-salary-quick-action-plan.md
Normal file
260
docs/plans/2026-05-16-pay-salary-quick-action-plan.md
Normal 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 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
|
||||
<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).
|
||||
Loading…
x
Reference in New Issue
Block a user