Konrad-approved: a home-dashboard admin Quick Actions tile that deep-links /payroll/?action=pay-salary and auto-clicks the existing paySalaryBtn (then strips the param). Reuses all existing machinery; no view/model/URL change. Rides the same paused-bundle HARD STOP. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
169 lines
7.4 KiB
Markdown
169 lines
7.4 KiB
Markdown
# Pay Salary Quick Action — 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
|
||
+ pay-type-filter + Salary-auto-scope commits (HEAD `b397cdf`). Same
|
||
logical feature line.
|
||
**NOT to be pushed/deployed until Konrad confirms it works locally** —
|
||
rides the same HARD STOP; ships in the one bundled push, Konrad's call.
|
||
|
||
## Goal (one sentence)
|
||
|
||
Add a "Pay Salary" tile to the home dashboard's admin Quick Actions row
|
||
that deep-links to `/payroll/` and auto-opens the existing Pay Salary
|
||
modal, so paying a manager is one click from the dashboard.
|
||
|
||
## Why
|
||
|
||
Managers are paid via the `Salary` adjustment through the Pay Salary
|
||
button on `/payroll/`. From the home dashboard that's currently a
|
||
multi-step path (go to payroll → find the button). The Quick Actions
|
||
row already has one-click shortcuts (Log Work, Log Absence, Run Payroll,
|
||
…); a "Pay Salary" shortcut completes the set for the manager-pay
|
||
workflow and mirrors the Log-Absence shortcut pattern added earlier.
|
||
|
||
## Decision (from the brainstorm)
|
||
|
||
Konrad chose **"Open the Pay Salary modal directly"** over a plain link
|
||
to `/payroll/`. The whole point of a Quick Action is to jump straight
|
||
into the task (matching how Log Absence jumps to its form), so the tile
|
||
must auto-open the modal, not just land on the payroll page.
|
||
|
||
## Approach (chosen)
|
||
|
||
Reuse the existing `paySalaryBtn` machinery entirely — the tile is a
|
||
plain link carrying a query param; a small JS hook on the payroll page
|
||
*triggers the existing button*. No behaviour is duplicated.
|
||
|
||
Considered + rejected:
|
||
- **Plain link to `/payroll/`** — 2-click, not a real "quick" action
|
||
(rejected by Konrad).
|
||
- **URL hash `#pay-salary`** — works but `payroll_dashboard` already
|
||
speaks query params (`?status=`), so a param is more idiomatic here
|
||
and easier to strip cleanly with `history.replaceState`.
|
||
|
||
## § 1 — Home dashboard tile
|
||
|
||
`core/templates/core/index.html`, inside the **admin** Quick Actions
|
||
card (the card is within the `{% if is_admin %}` branch, so the tile is
|
||
automatically admin-only — no new gating). Place it immediately AFTER
|
||
the existing "Run Payroll" tile so payroll actions stay grouped:
|
||
|
||
```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>
|
||
```
|
||
|
||
(Each `{# #}` comment is single-line — CLAUDE.md multi-line-comment
|
||
gotcha respected.)
|
||
|
||
## § 2 — Auto-open hook
|
||
|
||
`core/templates/core/payroll_dashboard.html`, JS, placed immediately
|
||
AFTER the existing `if (paySalaryBtn) { … }` block (so `paySalaryBtn`
|
||
is declared and its click listener attached before we trigger it):
|
||
|
||
```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 */ }
|
||
```
|
||
|
||
Why this is safe:
|
||
- `paySalaryBtn.click()` runs the **single source of truth** for Pay
|
||
Salary (clean worker slate, type=Salary, `toggleProjectField()` →
|
||
managers-only auto-scope, `modal.show()`). Zero duplicated logic; the
|
||
deep-link can never drift from the on-page button.
|
||
- `payroll_dashboard` (the view) never reads `?action=` — the param is
|
||
inert server-side. No view change, no security surface.
|
||
- `history.replaceState` strips the param so F5 / browser-Back does not
|
||
silently re-pop the modal (classic deep-link footgun).
|
||
- `try/catch` guarantees a malformed URL can never break page JS.
|
||
|
||
## § 3 — Permissions
|
||
|
||
No new gating. The home tile lives in the `{% if is_admin %}` branch;
|
||
`/payroll/` is admin-only server-side regardless. Supervisors get the
|
||
`{% else %}` home branch and never see the tile.
|
||
|
||
## § 4 — Testing
|
||
|
||
- **One Django render test** (`core/tests.py`): as an admin, GET `/`
|
||
contains the Pay Salary quick-action link with
|
||
`href="…?action=pay-salary"`; and GET `/payroll/?action=pay-salary`
|
||
returns HTTP 200 (proves the param is inert server-side). Optionally
|
||
assert a non-admin's `/` does NOT contain the tile (it's in the admin
|
||
branch) if a cheap non-admin fixture is already available.
|
||
- **The auto-click itself is client-side JS** with no server surface →
|
||
verified by Konrad's manual checklist, not a Django test (same
|
||
approved precedent as the Task-3 toggle and the Salary auto-scope).
|
||
- **Regression gate:** full suite goes 207 → **208 OK** (the one new
|
||
render test); nothing else changes.
|
||
|
||
## Files touched
|
||
|
||
| File | Change |
|
||
|---|---|
|
||
| `core/templates/core/index.html` | +1 `quick-action` tile (admin Quick Actions card, after "Run Payroll") |
|
||
| `core/templates/core/payroll_dashboard.html` | +~10 lines JS deep-link hook after the `paySalaryBtn` block |
|
||
| `core/tests.py` | +1 render test (tile present + param inert) |
|
||
| `docs/plans/parked-work.md` | One line: paused entry also covers the Pay Salary quick action |
|
||
| `CLAUDE.md` | One line under "Manager / Salaried pay": the `?action=pay-salary` deep-link |
|
||
|
||
**No model / migration / view / URL / dependency change.** ~25–35 LOC
|
||
incl. test.
|
||
|
||
## Out of scope (deliberately)
|
||
|
||
- No new view, URL route, or server-side handling of `?action=`
|
||
(kept inert — YAGNI).
|
||
- No change to the Pay Salary button, the Salary adjustment logic, or
|
||
the manager auto-scope (all reused as-is).
|
||
- No supervisor-facing Pay Salary entry (admin-only by design).
|
||
- No generic `?action=` framework for other modals (YAGNI — one tile).
|
||
|
||
## Manual verification (local — Konrad)
|
||
|
||
1. From `/` as admin: the **Pay Salary** tile appears in Quick Actions
|
||
(next to Run Payroll), `fa-user-tie` icon.
|
||
2. Click it → lands on `/payroll/` with the Pay Salary modal **already
|
||
open**, type = Salary, picker auto-scoped to managers only, no
|
||
workers pre-ticked.
|
||
3. The browser URL shows a clean `/payroll/` (the `?action=pay-salary`
|
||
param has been stripped).
|
||
4. Refresh the page → the modal does **NOT** re-open (param gone).
|
||
5. The existing on-page **Pay Salary** button still works exactly as
|
||
before.
|
||
6. Log in as a non-admin supervisor → `/` does **not** show the Pay
|
||
Salary tile (admin-only branch).
|
||
7. Full suite green (`USE_SQLITE=true … manage.py test core.tests`) —
|
||
expect 208 OK.
|
||
|
||
Then — and only then — Konrad decides whether to push (bundled with the
|
||
rest of the paused Manager/Salaried + pay-type-filter + Salary-auto-scope
|
||
work, in one push, his call).
|
||
|
||
## Branch / deploy
|
||
|
||
Build on `ai-dev` on top of `b397cdf`. **Do NOT push or deploy** until
|
||
Konrad has run the local verification and explicitly approves. Ships in
|
||
one bundled push with the rest of the paused work.
|