docs: design for Pay Salary dashboard quick action
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>
This commit is contained in:
parent
b397cdf46c
commit
fb19655a1d
168
docs/plans/2026-05-16-pay-salary-quick-action-design.md
Normal file
168
docs/plans/2026-05-16-pay-salary-quick-action-design.md
Normal file
@ -0,0 +1,168 @@
|
||||
# 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.
|
||||
Loading…
x
Reference in New Issue
Block a user