From 9ab0c68243dbaede2514268a3ec27c3656c98131 Mon Sep 17 00:00:00 2001 From: Konrad du Plessis Date: Sat, 16 May 2026 22:58:10 +0200 Subject: [PATCH] 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) --- core/templates/core/index.html | 7 +++++ core/templates/core/payroll_dashboard.html | 16 +++++++++++ core/tests.py | 31 ++++++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/core/templates/core/index.html b/core/templates/core/index.html index d752c6b..750b174 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -209,6 +209,13 @@ Run Payroll + {# === 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". #} + + + Pay Salary + View History diff --git a/core/templates/core/payroll_dashboard.html b/core/templates/core/payroll_dashboard.html index 25aa0bd..f1fb24a 100644 --- a/core/templates/core/payroll_dashboard.html +++ b/core/templates/core/payroll_dashboard.html @@ -2116,6 +2116,22 @@ document.addEventListener('DOMContentLoaded', function() { }); } + // === 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 */ } + // When the modal is opened from the HEADER button (not quick-adjust, // not Pay-Salary), clear any pre-selected workers/project and reset // the type to Bonus. diff --git a/core/tests.py b/core/tests.py index 86d5b07..8ae64a1 100644 --- a/core/tests.py +++ b/core/tests.py @@ -3307,6 +3307,37 @@ class WorkerListPayTypeFilterTests(TestCase): ) +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) + + class WorkHistoryTeamFilterTests(TestCase): """The /history/ page accepts ?team= to narrow to logs tagged with that team, ?team=none for logs with no team set, and empty