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>
7.4 KiB
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 butpayroll_dashboardalready speaks query params (?status=), so a param is more idiomatic here and easier to strip cleanly withhistory.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:
{# === 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):
// === 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.replaceStatestrips the param so F5 / browser-Back does not silently re-pop the modal (classic deep-link footgun).try/catchguarantees 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 withhref="…?action=pay-salary"; and GET/payroll/?action=pay-salaryreturns 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)
- From
/as admin: the Pay Salary tile appears in Quick Actions (next to Run Payroll),fa-user-tieicon. - Click it → lands on
/payroll/with the Pay Salary modal already open, type = Salary, picker auto-scoped to managers only, no workers pre-ticked. - The browser URL shows a clean
/payroll/(the?action=pay-salaryparam has been stripped). - Refresh the page → the modal does NOT re-open (param gone).
- The existing on-page Pay Salary button still works exactly as before.
- Log in as a non-admin supervisor →
/does not show the Pay Salary tile (admin-only branch). - 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.