From 6d37d1ba9b4efd7c0299d0419dbfac8d02f96bb1 Mon Sep 17 00:00:00 2001 From: Konrad du Plessis Date: Wed, 22 Apr 2026 18:23:24 +0200 Subject: [PATCH] Task 10: add Task 3 full-payload test + mark design doc as shipped Adds a consolidated regression test to WorkLogPayrollAjaxTests that exercises: paid worker serialization shape, null team branch, OT flag in JSON, full_page_url value, and adjustment payslip-link serialization. Closes the 'Important' coverage gap flagged in Task 3's quality review. Also appends a 'Shipped' block to the design doc summarising QA status and capturing all five deferred nits (admin-gate consistency, template branch tests, |default:0 redundancy, admin-gate expression readability, background vs background-color) so they survive the merge into project history. All 19 tests pass. manage.py check clean. No migrations needed. Co-Authored-By: Claude Opus 4.7 (1M context) --- core/tests.py | 60 +++++++++++++++++++ ...04-22-work-log-payroll-crosslink-design.md | 22 +++++++ 2 files changed, 82 insertions(+) diff --git a/core/tests.py b/core/tests.py index c3619d1..d97d231 100644 --- a/core/tests.py +++ b/core/tests.py @@ -214,6 +214,66 @@ class WorkLogPayrollAjaxTests(TestCase): resp = self.client.get('/history/99999/payroll/ajax/') self.assertEqual(resp.status_code, 404) + def test_full_payload_with_paid_worker_null_team_and_ot(self): + """One scenario exercising: paid worker, null team, OT flag, full URL.""" + # Create a paid worker on a separate log with no team (null team edge case) + # and with overtime hours but no priced_workers (unpriced OT flag). + project = Project.objects.create(name='P-full') + paid_worker = Worker.objects.create( + name='Paula', id_number='P1', monthly_salary=Decimal('4000') + ) + log = WorkLog.objects.create( + date=datetime.date(2026, 4, 11), + project=project, + team=None, # null team + supervisor=self.admin, + overtime_amount=Decimal('1.50'), # > 0 and no priced_workers -> flag fires + ) + log.workers.add(paid_worker) + + # Link a PayrollRecord so Paula shows as Paid + record = PayrollRecord.objects.create( + worker=paid_worker, + amount_paid=Decimal('250.00'), + date=datetime.date(2026, 4, 16), + ) + record.work_logs.add(log) + + # Link an adjustment to the same log + adj = PayrollAdjustment.objects.create( + worker=paid_worker, + project=project, + type='Overtime', + amount=Decimal('75.00'), + date=datetime.date(2026, 4, 11), + description='OT pricing', + work_log=log, + payroll_record=record, + ) + + self.client.login(username='admin', password='pass') + resp = self.client.get(reverse('work_log_payroll_ajax', args=[log.id])) + self.assertEqual(resp.status_code, 200) + data = resp.json() + + # Null team branch + self.assertIsNone(data['team']) + # Project is still present + self.assertEqual(data['project']['name'], 'P-full') + # Paid worker serialization shape + paula_row = next(r for r in data['worker_rows'] if r['worker_name'] == 'Paula') + self.assertEqual(paula_row['status'], 'Paid') + self.assertEqual(paula_row['payroll_record_id'], record.pk) + self.assertEqual(paula_row['paid_date'], '2026-04-16') + # OT flag fires (overtime > 0, no priced_workers) + self.assertTrue(data['overtime_needs_pricing']) + # full_page_url is the reverse of work_log_payroll_detail + self.assertEqual(data['full_page_url'], f'/history/{log.id}/') + # Adjustment serialized with payslip link + self.assertEqual(len(data['adjustments']), 1) + self.assertEqual(data['adjustments'][0]['type'], 'Overtime') + self.assertEqual(data['adjustments'][0]['payroll_record_id'], record.pk) + # === TESTS FOR THE WORK LOG PAYROLL FULL-PAGE VIEW === # These cover the HTML page at /history// that shares the same context diff --git a/docs/plans/2026-04-22-work-log-payroll-crosslink-design.md b/docs/plans/2026-04-22-work-log-payroll-crosslink-design.md index 5b626af..96abecc 100644 --- a/docs/plans/2026-04-22-work-log-payroll-crosslink-design.md +++ b/docs/plans/2026-04-22-work-log-payroll-crosslink-design.md @@ -144,3 +144,25 @@ No third-party dependencies added. ## Next step Hand off to `superpowers:writing-plans` to produce a task-by-task implementation plan with review checkpoints. + +--- + +## Shipped — 22 Apr 2026 + +**Commits:** 1c00ba2 (design) through the Task 10 "shipped" commit (this one). +**Plan:** `docs/plans/2026-04-22-work-log-payroll-crosslink-plan.md`. + +**QA summary:** +- 19 tests pass (`python manage.py test core.tests`) +- `python manage.py check` — no new issues +- No model changes, no migrations, no pushed-to-prod artefacts +- Three entry points clickable for admins; supervisors unchanged + +**Deferred for future passes (non-blocking):** +- Admin-gate consistency: work_history uses `{% if is_admin %}`; teams/projects detail templates use `{% if user.is_staff or user.is_superuser %}`. Both semantically identical but stylistically inconsistent. +- Task 4 template branch tests: OT banner, adjustments table, Paid badge, Inactive worker — covered end-to-end via modal tests now, not via Django test client. +- Task 4 template: redundant `|default:0` on `log.overtime_amount` (harmless, renders "0" instead of "0.00"). +- Task 5 admin gate: `user.is_authenticated and user.is_staff or user.is_superuser` could be simplified to `user.is_staff or user.is_superuser`. +- `background` shorthand in Task 9 hover rule could be `background-color` for precision (pedantic). + +**"Pay these workers now" modal action** and **"Add adjustment pre-filled for this log" shortcut** — explicitly out-of-scope per the design; revisit if users ask.