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