diff --git a/core/tests.py b/core/tests.py index 0c384ad..a469c6c 100644 --- a/core/tests.py +++ b/core/tests.py @@ -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') 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) diff --git a/static/css/custom.css b/static/css/custom.css index ad6b6c6..9e5fae7 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -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/