Show a paid club's cover end date in the health table

For a club on a plan whose dues are settled, the Dues cell now reads "until
<date> · paid" — the end of the current paid period, which is the day the grace
period would start if nothing renews. It is exactly the "when does this lapse?"
question the paid badge alone could not answer.

Driven by a new paid_until annotation: the furthest-out PAID period end, null when
the club owes or was never billed (so a fully-paid free tier shows just "paid",
and an owing club shows the amount, unchanged). It rides the SAME single query —
the assertNumQueries(1) test still holds — and the date is whitespace-nowrap so it
does not wrap in the narrow cell.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 08:41:20 +02:00
parent 65a2f741f6
commit 5283262e6b
4 changed files with 23 additions and 1 deletions

View File

@@ -84,6 +84,9 @@ def clubs_with_health(queryset=None, today=None, now=None):
dues_owed=_subquery(Due.objects.filter(status__in=Due.OWING), Sum(F("amount") - F("amount_paid")), DecimalField(max_digits=10, decimal_places=2)),
dues_grace_until=Subquery(Due.objects.filter(club=OuterRef("pk"), status__in=Due.OWING).order_by("grace_until").values("grace_until")[:1]),
dues_period_end=Subquery(Due.objects.filter(club=OuterRef("pk"), status__in=Due.OWING).order_by("period_end").values("period_end")[:1]),
# How far a fully-paid club is covered: the furthest-out PAID period end — the day
# grace would start if nothing is renewed. Null when the club owes, or was never billed.
paid_until=Subquery(Due.objects.filter(club=OuterRef("pk"), status=Due.Status.PAID).order_by("-period_end").values("period_end")[:1]),
)
.annotate(teams_without_coach=F("team_count") - F("teams_managed"))
.order_by("name")

View File

@@ -87,6 +87,10 @@
<div class="flex flex-row gap-2 items-center justify-end">
{% if not club.dues_owed %}
{% if club.tier_name %}
{% comment %} paid_until is the current period's end — the day grace would start if nothing renews. {% endcomment %}
{% if club.paid_until %}
<span class="whitespace-nowrap text-xs opacity-60">until {{ club.paid_until|date:"j M Y" }}</span>
{% endif %}
<span class="badge badge-success">paid</span>
{% else %}
<span class="badge badge-ghost badge-outline">n/a</span>

View File

@@ -1131,6 +1131,21 @@ class PlatformDuesMetricTests(TestCase):
self.assertEqual(club.tier_name, "Standard")
self.assertEqual(club.dues_owed, Decimal("500.00"))
def test_a_fully_paid_club_shows_when_its_cover_ends(self):
# The end of the current paid period is the day grace would start if nothing renews.
subscribe(self.club, self.tier)
due = self.club.dues.first()
record_payment(due, due.amount)
club = clubs_with_health().get(pk=self.club.pk)
self.assertEqual(club.paid_until, due.period_end)
def test_a_club_that_owes_has_no_paid_until(self):
subscribe(self.club, self.tier) # unpaid
self.assertIsNone(clubs_with_health().get(pk=self.club.pk).paid_until)
def test_the_health_table_still_costs_one_query_with_billing_on_it(self):
subscribe(self.club, self.tier)
subscribe(Club.objects.create(name="Feyenoord"), self.tier)

File diff suppressed because one or more lines are too long