polish: Salary multi-adjustment payslip-layout guard test; tighten list test; a11y badge contrast

This commit is contained in:
Konrad du Plessis 2026-05-15 21:34:12 +02:00
parent 862766f9b5
commit 268a050397
2 changed files with 61 additions and 2 deletions

View File

@ -3743,10 +3743,20 @@ class ManagerSalariedPayUITests(TestCase):
Worker.objects.create(
name='UI Mgr', id_number='MSU-M', monthly_salary=Decimal('40000'),
pay_type='fixed')
# A plain daily worker (default pay_type) alongside the manager so
# we can prove BOTH the salaried and the daily indicator render.
Worker.objects.create(
name='UI Daily', id_number='MSU-D', monthly_salary=Decimal('6000'))
# worker_list status filter param confirmed in CLAUDE.md routes table.
resp = self.client.get(reverse('worker_list') + '?status=all')
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, 'Manager')
# Assert the FULL salaried label (not just the 'Manager' substring)
# and the daily worker's exact 'Daily' chip markup. 'Daily' alone is
# too generic to assert on a whole page, so we target the closing
# markup of the daily badge span (list.html line ~101) which only
# the daily branch emits.
self.assertContains(resp, 'Manager / Salaried')
self.assertContains(resp, '>Daily</span>')
def test_salary_immediate_payslip_has_no_zero_days_line(self):
# Absorbed Task-5 review #2: an immediate Salary payment's payslip
@ -3807,3 +3817,52 @@ class ManagerSalariedPayUITests(TestCase):
html = render_to_string('core/pdf/payslip_pdf.html', ctx)
self.assertNotIn('days worked', html) # generic Base Pay line absent
self.assertIn('Salary', html)
def test_salary_with_other_adjustment_uses_normal_payslip_layout(self):
# MONEY-DOCUMENT PRESENTATION GUARD (negative path).
# The clean single-line "Salary Details" payslip layout must ONLY
# fire for a standalone single-Salary payment (the immediate
# add_adjustment path). When a Salary adjustment is paid ALONGSIDE
# another adjustment (here a Deduction) they net into ONE
# PayrollRecord with TWO linked adjustments — len(adjs_list)==1 is
# False, so payslip_detail's is_salary guard must stay False and
# the NORMAL multi-item layout (work-log table + adjustments table
# + Net Payable) must render. This locks the shared guard so it
# can't later be refactored into misfiring on a multi-adjustment
# record (which would hide the deduction on a money document).
proj = Project.objects.create(name='NormLayout Proj')
mgr = Worker.objects.create(
name='NormLayout Mgr', id_number='MSU-NL',
monthly_salary=Decimal('40000'), pay_type='fixed')
sal = PayrollAdjustment.objects.create(
worker=mgr, type='Salary', amount=Decimal('40000.00'),
date=_date(2026, 5, 25), project=proj) # unpaid
ded = PayrollAdjustment.objects.create(
worker=mgr, type='Deduction', amount=Decimal('1000.00'),
date=_date(2026, 5, 25), project=proj) # unpaid
# Pay flow nets both into a single PayrollRecord (40000 - 1000).
self.client.post(reverse('process_payment', args=[mgr.id]))
sal.refresh_from_db()
ded.refresh_from_db()
self.assertIsNotNone(sal.payroll_record)
self.assertEqual(sal.payroll_record, ded.payroll_record)
pr = sal.payroll_record
self.assertEqual(pr.amount_paid, Decimal('39000.00'))
self.assertEqual(pr.adjustments.count(), 2) # NOT 1
resp = self.client.get(reverse('payslip_detail', args=[pr.id]))
self.assertEqual(resp.status_code, 200)
body = resp.content.decode()
# NORMAL/generic branch markup (payslip.html lines ~168/197/254)
# MUST be present — these strings exist ONLY in the {% else %}
# generic branch, never in the clean is_salary branch.
self.assertIn('Work Log Details (Attendance)', body)
self.assertIn('Base Pay Subtotal', body)
self.assertIn('Net Payable:', body)
# Clean single-Salary branch headings (payslip.html lines ~134/159)
# MUST be ABSENT — if the is_salary guard wrongly fired for this
# 2-adjustment record, "Salary Details" / "Salary Amount:" would
# appear and this assertion would fail (the whole point of the
# guard test).
self.assertNotIn('Salary Details', body)
self.assertNotIn('Salary Amount:', body)

View File

@ -92,7 +92,7 @@
/* Salary (manager / fixed monthly pay) a distinct teal, not used by
any other type; reads as "regular salary/pay", separate from the
forest-green Bonus and the slate-blue Advance. */
--badge-salary-bg: #1f7a70; --badge-salary-fg: #d6f1ec;
--badge-salary-bg: #1a6b62; --badge-salary-fg: #e3f5f1;
/* === PAYROLL DASHBOARD action-button tokens (dark theme) ===
Soft-fill pastels for the 4 action buttons at the top of /payroll/