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:
parent
9bd0e8541d
commit
18c75b2bce
@ -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'))
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user