diff --git a/core/tests.py b/core/tests.py index 5ac8e9e..2969d86 100644 --- a/core/tests.py +++ b/core/tests.py @@ -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')) diff --git a/core/views.py b/core/views.py index 12b4cdd..c4b9209 100644 --- a/core/views.py +++ b/core/views.py @@ -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