Add _current_outstanding_in_scope helper + 3 tests

Hero KPI card 2 needs 'Outstanding NOW' scoped to the report's selected
projects/teams. This helper wraps _compute_outstanding, reshapes the
by_project dict into a sorted list, and exposes the net total for direct
rendering.

Tests cover unfiltered total, project-scoped total, and team-scoped
total (including the worker__teams subquery path for adjustments).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Konrad du Plessis 2026-04-22 22:29:41 +02:00
parent e74f48f050
commit 82594faad7
2 changed files with 81 additions and 0 deletions

View File

@ -534,3 +534,58 @@ class CompanyCostVelocityTests(TestCase):
log.workers.add(w1, w2)
result = _company_cost_velocity()
self.assertEqual(result['working_days'], 1) # not 2
class CurrentOutstandingInScopeTests(TestCase):
"""Hero card 2 — 'Outstanding NOW' with optional filter scope."""
def setUp(self):
self.admin = User.objects.create_user(username='a-out', is_staff=True)
self.p1 = Project.objects.create(name='ProjA')
self.p2 = Project.objects.create(name='ProjB')
self.t1 = Team.objects.create(name='TeamA', supervisor=self.admin)
self.w = Worker.objects.create(
name='Wkr', id_number='W1', monthly_salary=Decimal('4000')
)
self.t1.workers.add(self.w)
# Unpaid log on project 1
log1 = WorkLog.objects.create(
date=datetime.date(2026, 3, 1),
project=self.p1, team=self.t1, supervisor=self.admin,
)
log1.workers.add(self.w)
# Unpaid log on project 2
log2 = WorkLog.objects.create(
date=datetime.date(2026, 3, 2),
project=self.p2, team=self.t1, supervisor=self.admin,
)
log2.workers.add(self.w)
def test_no_filters_includes_all_projects(self):
from core.views import _current_outstanding_in_scope
result = _current_outstanding_in_scope()
# daily_rate = 4000/20 = 200; 2 unpaid logs * 200 = 400
self.assertEqual(result['total'], Decimal('400.00'))
self.assertEqual(len(result['by_project']), 2)
def test_project_filter_scopes_total(self):
from core.views import _current_outstanding_in_scope
result = _current_outstanding_in_scope(project_ids=[self.p1.id])
self.assertEqual(result['total'], Decimal('200.00'))
self.assertEqual(len(result['by_project']), 1)
self.assertEqual(result['by_project'][0]['name'], 'ProjA')
def test_team_filter_scopes_total(self):
"""Team filter on work logs + worker__teams on adjustments."""
from core.views import _current_outstanding_in_scope
# Adjustment on a worker not in t1
other_worker = Worker.objects.create(
name='Other', id_number='O1', monthly_salary=Decimal('4000')
)
PayrollAdjustment.objects.create(
worker=other_worker, project=self.p1, type='Bonus',
amount=Decimal('500.00'), date=datetime.date(2026, 3, 3),
)
# With team filter, only self.w's logs appear — R 400 total
result = _current_outstanding_in_scope(team_ids=[self.t1.id])
self.assertEqual(result['total'], Decimal('400.00'))

View File

@ -252,6 +252,32 @@ def _company_cost_velocity():
}
# =============================================================================
# === CURRENT OUTSTANDING — SCOPED FOR THE REPORT ===
# Thin wrapper around _compute_outstanding that shapes the output for
# the executive report's hero card 2. Includes a 'by_project' list
# sorted by amount desc, ready for direct template rendering.
# =============================================================================
def _current_outstanding_in_scope(project_ids=None, team_ids=None):
"""Return current outstanding payments, optionally scoped by project/team.
Calls _compute_outstanding and reshapes the by_project dict into a
list sorted by amount descending (for display). The 'total' field
is the net outstanding (unpaid wages + additive adjustments minus
deductive adjustments), matching the home dashboard card.
"""
raw = _compute_outstanding(project_ids=project_ids, team_ids=team_ids)
by_project_list = sorted(
[{'name': name, 'amount': amt} for name, amt in raw['outstanding_by_project'].items()],
key=lambda r: -r['amount'],
)
return {
'total': raw['outstanding_payments'],
'by_project': by_project_list,
}
# === HOME DASHBOARD ===
# The main page users see after logging in. Shows different content
# depending on whether the user is an admin or supervisor.