From e74f48f050618d0843e9abd364846bc5ff373ff6 Mon Sep 17 00:00:00 2001 From: Konrad du Plessis Date: Wed, 22 Apr 2026 22:20:14 +0200 Subject: [PATCH] Add _company_cost_velocity helper + 3 tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Computes company-wide avg daily and monthly labour cost for the executive report's hero KPI band (cards 3 and 4). Denominator is working days (distinct work-log dates), not calendar days — true cost-of-a-productive-day metric per design section 2. Monthly = daily * 30.44 (the 365.25/12 month-length approximation, which keeps annualised totals correct on average). Tests cover: empty DB returns zero, known values with assertAlmostEqual for the 30.44 multiplication, and that multiple workers on one date count as 1 working day (not N). Co-Authored-By: Claude Opus 4.7 (1M context) --- core/tests.py | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ core/views.py | 36 ++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/core/tests.py b/core/tests.py index 096e8f5..11da607 100644 --- a/core/tests.py +++ b/core/tests.py @@ -476,3 +476,61 @@ class SupervisorPickerQuerysetTests(TestCase): username='an_admin', password='pass', is_staff=True ) self.assertIn(admin, _supervisor_user_queryset()) + + +# ============================================================================= +# === TESTS FOR EXECUTIVE REPORT v2 === +# Covers the new helpers introduced in the report rebuild (Apr 2026): +# _company_cost_velocity, _current_outstanding_in_scope, _team_project_activity, +# and the multi-filter extension of _build_report_context. +# ============================================================================= + + +class CompanyCostVelocityTests(TestCase): + """Company-wide avg daily and monthly labour cost (hero KPI card 3 & 4).""" + + def test_empty_db_returns_zero(self): + from core.views import _company_cost_velocity + result = _company_cost_velocity() + self.assertEqual(result['avg_daily'], Decimal('0.00')) + self.assertEqual(result['avg_monthly'], Decimal('0.00')) + self.assertEqual(result['working_days'], 0) + + def test_known_values(self): + from core.views import _company_cost_velocity + # Setup: 2 workers (daily_rate = 4000/20 = R 200 each), each works 5 distinct dates. + # Lifetime cost = 2 workers * 5 days * R 200 = R 2000. Working days = 5. + # Avg daily = 2000 / 5 = R 400. + # Avg monthly = 400 * 30.44 = R 12,176. + admin = User.objects.create_user(username='admin-cv', is_staff=True) + project = Project.objects.create(name='P') + w1 = Worker.objects.create(name='W1', id_number='W1', monthly_salary=Decimal('4000')) + w2 = Worker.objects.create(name='W2', id_number='W2', monthly_salary=Decimal('4000')) + for d in range(1, 6): # 5 distinct dates + log = WorkLog.objects.create( + date=datetime.date(2026, 3, d), + project=project, supervisor=admin, + ) + log.workers.add(w1, w2) + + result = _company_cost_velocity() + self.assertEqual(result['working_days'], 5) + self.assertEqual(result['avg_daily'], Decimal('400.00')) + # Tolerance: ±1 cent for the 30.44 multiplication + self.assertAlmostEqual( + float(result['avg_monthly']), 12176.00, delta=0.01 + ) + + def test_duplicate_dates_not_double_counted(self): + """Two workers working the same date = 1 distinct date, not 2.""" + from core.views import _company_cost_velocity + admin = User.objects.create_user(username='admin-cv2', is_staff=True) + project = Project.objects.create(name='P2') + w1 = Worker.objects.create(name='X', id_number='X1', monthly_salary=Decimal('4000')) + w2 = Worker.objects.create(name='Y', id_number='Y1', monthly_salary=Decimal('4000')) + log = WorkLog.objects.create( + date=datetime.date(2026, 3, 1), project=project, supervisor=admin, + ) + log.workers.add(w1, w2) + result = _company_cost_velocity() + self.assertEqual(result['working_days'], 1) # not 2 diff --git a/core/views.py b/core/views.py index 9aa9fb7..064e9fc 100644 --- a/core/views.py +++ b/core/views.py @@ -216,6 +216,42 @@ def _compute_outstanding(project_ids=None, team_ids=None): } +# ============================================================================= +# === COMPANY COST VELOCITY === +# Lifetime "what does a typical FoxFitt working day cost us?" metric. +# Denominator = COUNT(DISTINCT work_log.date) — true working days, not +# calendar days (rain days, weekends, permit delays don't dilute the rate). +# Used by the hero KPI band on the payroll report. +# ============================================================================= + +def _company_cost_velocity(): + """Return company-wide avg daily and monthly labour cost (lifetime).""" + # Total lifetime labour cost: sum of (worker.daily_rate) over every + # (log, worker) pair that has ever been logged. + total_cost = Decimal('0.00') + for wl in WorkLog.objects.prefetch_related('workers').all(): + for worker in wl.workers.all(): + total_cost += worker.daily_rate + + # Distinct work-log dates = working days + working_days = WorkLog.objects.values('date').distinct().count() + + if working_days == 0: + avg_daily = Decimal('0.00') + else: + avg_daily = (total_cost / working_days).quantize(Decimal('0.01')) + + # 30.44 = 365.25 / 12 — standard month-length approximation. + # Keeps annualised totals correct on average. + avg_monthly = (avg_daily * Decimal('30.44')).quantize(Decimal('0.01')) + + return { + 'avg_daily': avg_daily, + 'avg_monthly': avg_monthly, + 'working_days': working_days, + } + + # === HOME DASHBOARD === # The main page users see after logging in. Shows different content # depending on whether the user is an admin or supervisor.