Add _company_cost_velocity helper + 3 tests
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) <noreply@anthropic.com>
This commit is contained in:
parent
6be6a09056
commit
e74f48f050
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user