fix(dashboard): 'Paid This Month' actually uses calendar month

The dashboard card labeled 'Paid This Month' was summing the
last 60 days of PayrollRecords — identical to the payroll
dashboard's 'Paid (60D)' card. Misleading at best, wrong at
worst when explaining the dashboard to a non-developer.

Now filters by date__year + date__month (current calendar month
only). Added 3 regression tests: excludes 45-day-old payment,
includes 1st-of-month payment, returns 0 cleanly when nothing
paid yet this month.

Found during Konrad's 15 May audit of dashboard numbers.
This commit is contained in:
Konrad du Plessis 2026-05-15 01:46:47 +02:00
parent 9bd0e8541d
commit 18c75b2bce
2 changed files with 66 additions and 3 deletions

View File

@ -3008,3 +3008,60 @@ class WorkHistoryTeamFilterTests(TestCase):
resp = self.client.get(f'/history/?team={self.team_a.id}')
self.assertIn(f'team={self.team_a.id}', resp.context['filter_params'])
self.assertEqual(resp.context['selected_team'], str(self.team_a.id))
class DashboardPaidThisMonthTests(TestCase):
"""Regression: 'Paid This Month' on the admin dashboard must be the
CURRENT CALENDAR MONTH only not a rolling 60-day window. Previously
this card showed the same value as the payroll dashboard's 'Paid (60D)'
card, which was misleading when explaining the dashboard to a
non-developer."""
@classmethod
def setUpTestData(cls):
cls.admin = User.objects.create_user(
username='admin', password='pw', is_staff=True, is_superuser=True,
)
cls.worker = Worker.objects.create(
name='Pay W', id_number='PW1', monthly_salary=Decimal('6000'),
)
def setUp(self):
self.client.force_login(self.admin)
def test_paid_this_month_excludes_last_month(self):
"""A payment dated 45 days ago (almost certainly in the prior
calendar month) must NOT count toward 'Paid This Month'."""
today = datetime.date.today()
forty_five_days_ago = today - datetime.timedelta(days=45)
# In-month payment (counts)
PayrollRecord.objects.create(
worker=self.worker, amount_paid=Decimal('1000.00'), date=today,
)
# 45-days-old payment (does NOT count — almost always prior month)
PayrollRecord.objects.create(
worker=self.worker, amount_paid=Decimal('9999.00'),
date=forty_five_days_ago,
)
resp = self.client.get('/')
self.assertEqual(resp.context['paid_this_month'], Decimal('1000.00'))
def test_paid_this_month_includes_first_of_month(self):
"""A payment dated 1st of the current month counts (boundary case)."""
today = datetime.date.today()
first_of_month = today.replace(day=1)
PayrollRecord.objects.create(
worker=self.worker, amount_paid=Decimal('500.00'),
date=first_of_month,
)
resp = self.client.get('/')
self.assertEqual(resp.context['paid_this_month'], Decimal('500.00'))
def test_paid_this_month_zero_when_no_payments(self):
"""No payments in the current month → zero (not None)."""
resp = self.client.get('/')
self.assertEqual(resp.context['paid_this_month'], Decimal('0.00'))

View File

@ -372,10 +372,16 @@ def index(request):
pending_adjustments_sub = _o['pending_adj_sub']
outstanding_by_project = _o['outstanding_by_project']
# Sum total paid out in the last 60 days
sixty_days_ago = timezone.now().date() - timezone.timedelta(days=60)
# === PAID THIS MONTH (calendar month, 1st → today) ===
# The dashboard card is labeled "Paid This Month" and must reflect
# the CURRENT CALENDAR MONTH only — not a rolling 60-day window.
# The payroll dashboard has its own separate "Paid (60D)" card if
# the rolling-window view is wanted. Filtering by date__year +
# date__month is unambiguous and matches the label exactly.
_today_dt = timezone.now().date()
paid_this_month = PayrollRecord.objects.filter(
date__gte=sixty_days_ago
date__year=_today_dt.year,
date__month=_today_dt.month,
).aggregate(total=Sum('amount_paid'))['total'] or Decimal('0.00')
# Count and total balance of active loans