Add a Payments & dues section to the Me page, matching the design's M5 row

Home already had the M1 dues card; Me's own "Payments & dues" row was
explicitly stubbed out with nowhere to lead. Give it a real destination:
a Payments screen (open balances for everyone managed, reusing Home's
dues-card layout via a shared _dues_row.html partial) and a "N OPEN"
pill on the Me row itself, only shown once something is actually owed.
club.services.fees.open_dues_rows is factored out so Home and Payments
can never drift apart on what counts as "still open".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
2026-08-21 21:48:50 +02:00
parent 4b5a4a81ec
commit 0ce1593af1
9 changed files with 282 additions and 38 deletions

View File

@@ -16,6 +16,23 @@ def remaining_balance(membership):
return max(membership.fee_amount - membership.amount_paid, Decimal("0.00"))
def open_dues_rows(club, people, season):
"""Every season-dues row still owed by ``people`` in ``season`` -- shared by
mobile's Home dues card and its Payments & dues screen so the two never
drift out of sync on what counts as "still open". WAIVED memberships and
fully-paid balances are excluded."""
if season is None or not people:
return []
memberships = ClubMembership.objects.filter(club=club, member__in=people, season=season).exclude(fee_status=ClubMembership.FeeStatus.WAIVED).select_related("dues_invoice", "member")
rows = []
for membership in memberships:
balance = remaining_balance(membership)
if balance > 0:
rows.append({"membership": membership, "balance": balance, "invoice": getattr(membership, "dues_invoice", None)})
return rows
def record_payment(membership, *, amount, method=FeePayment.Method.BANK_TRANSFER, reference="", note="", recorded_by=None):
"""Record money received against one membership's fee. Several payments may
land on one membership -- a family paying in two installments must not read as