Use the club's legal name and home address on the dues invoice header
Same convention as the referee payment form (official_name, home Location) -- a dues invoice previously just showed the everyday name with no address at all. Also gave the referee form's PDF preview a sample external game ID so that part of the template shows up there too. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
@@ -17,6 +17,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from club.models import ClubMembership, DuesInvoice
|
||||
from club.services.fees import remaining_balance
|
||||
from events.models import Location
|
||||
|
||||
|
||||
class DuesInvoicePDFError(Exception):
|
||||
@@ -158,5 +159,10 @@ def render_pdf(html: str) -> bytes:
|
||||
|
||||
|
||||
def invoice_pdf(invoice: DuesInvoice) -> bytes:
|
||||
html = render_to_string("club/dues_invoice_pdf.html", {"club": invoice.club, "invoice": invoice, "membership": invoice.membership, "member": invoice.membership.member})
|
||||
# Same header convention as management/event_referee_form_pdf.html: the club's
|
||||
# legal name (official_name falls back to the everyday name when unset) and its
|
||||
# home location -- never an event-specific location, since a dues invoice isn't
|
||||
# tied to any one event.
|
||||
home_location = Location.objects.filter(club=invoice.club, is_home=True).first()
|
||||
html = render_to_string("club/dues_invoice_pdf.html", {"club": invoice.club, "invoice": invoice, "membership": invoice.membership, "member": invoice.membership.member, "home_location": home_location})
|
||||
return render_pdf(html)
|
||||
|
||||
@@ -42,7 +42,11 @@
|
||||
<body>
|
||||
<div class="header">
|
||||
<div>
|
||||
<h1>{{ club.name }}</h1>
|
||||
<h1>{{ club.official_name }}</h1>
|
||||
{% if home_location %}
|
||||
<div class="muted">{{ home_location.address }}</div>
|
||||
<div class="muted">{{ home_location.zip_code }} {{ home_location.city }}</div>
|
||||
{% endif %}
|
||||
{% if club.contact_email %}<div class="muted">{{ club.contact_email }}</div>{% endif %}
|
||||
</div>
|
||||
<div class="right">
|
||||
|
||||
@@ -3,6 +3,7 @@ import uuid
|
||||
from contextlib import contextmanager
|
||||
from decimal import Decimal
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from allauth.mfa.models import Authenticator
|
||||
from dateutil.relativedelta import relativedelta
|
||||
@@ -17,7 +18,7 @@ from django.test import RequestFactory, TestCase, override_settings
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from events.models import Event
|
||||
from events.models import Event, Location
|
||||
from members.models import Family, FamilyMembership, Member
|
||||
from teams.models import Position, StaffAssignment, Team, TeamMembership
|
||||
from teams.services import eligible_roster_members
|
||||
@@ -39,7 +40,7 @@ from .services.access import (
|
||||
teams_staffed_by,
|
||||
)
|
||||
from .services.fees import mark_as_paid, record_payment, remaining_balance
|
||||
from .services.invoicing import create_or_resend_invoice, invoices_due_for_reminder, recipient_for
|
||||
from .services.invoicing import create_or_resend_invoice, invoice_pdf, invoices_due_for_reminder, recipient_for
|
||||
from .services.onboarding import (
|
||||
annotate_onboarding_status,
|
||||
approve_all_clean,
|
||||
@@ -1643,6 +1644,46 @@ class CreateOrResendInvoiceTests(TestCase):
|
||||
self.assertEqual(DuesInvoice.objects.filter(membership=self.membership).count(), 1)
|
||||
|
||||
|
||||
class InvoicePdfTests(TestCase):
|
||||
"""club.services.invoicing.invoice_pdf -- same header convention as
|
||||
management/event_referee_form_pdf.html: legal name, and the club's home
|
||||
location, never any other one."""
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.club = Club.objects.create(name="Ajax United", slug="ajax-united", legal_name="Ajax United VZW")
|
||||
cls.season = make_season(cls.club)
|
||||
cls.member = Member.objects.create(first_name="Jane", last_name="Doe", email="jane@example.com")
|
||||
cls.membership = ClubMembership.objects.create(club=cls.club, member=cls.member, season=cls.season, status=ClubMembership.StatusChoices.PENDING, fee_amount=Decimal("150.00"))
|
||||
cls.invoice = create_or_resend_invoice(cls.membership, due_in_days=14, recipient_email="jane@example.com", sent_to_guardian=False)
|
||||
|
||||
def render(self):
|
||||
with mock.patch("club.services.invoicing.render_pdf", side_effect=lambda html: html) as renderer:
|
||||
invoice_pdf(self.invoice)
|
||||
return renderer.call_args[0][0]
|
||||
|
||||
def test_the_header_uses_the_legal_name(self):
|
||||
html = self.render()
|
||||
|
||||
self.assertIn("Ajax United VZW", html)
|
||||
|
||||
def test_the_header_uses_the_clubs_home_location(self):
|
||||
Location.objects.create(club=self.club, name="Sports Hall", address="Sportlaan 1", zip_code="1000", city="Brussels", is_home=True)
|
||||
Location.objects.create(club=self.club, name="Away ground", address="Elsewhere 2", zip_code="2000", city="Antwerp", is_home=False)
|
||||
|
||||
html = self.render()
|
||||
|
||||
self.assertIn("Sportlaan 1", html)
|
||||
self.assertNotIn("Elsewhere 2", html)
|
||||
|
||||
def test_renders_fine_with_no_home_location_set(self):
|
||||
self.assertEqual(Location.objects.filter(club=self.club, is_home=True).count(), 0)
|
||||
|
||||
html = self.render()
|
||||
|
||||
self.assertIn(self.invoice.number, html)
|
||||
|
||||
|
||||
class InvoicesDueForReminderTests(TestCase):
|
||||
"""club.services.invoicing.invoices_due_for_reminder -- sent, unpaid, past
|
||||
their own due date; never paid, waived, or not yet due."""
|
||||
|
||||
@@ -56,7 +56,7 @@ def _referee_form_pdf_context(club, request):
|
||||
start=timezone.now() + datetime.timedelta(days=7),
|
||||
teams=SimpleNamespace(all=lambda: [SimpleNamespace(short_name="U16")]),
|
||||
opponent="Leuven",
|
||||
external_game_id="",
|
||||
external_game_id="BE-2026-00417",
|
||||
)
|
||||
home_location = SimpleNamespace(address="Sportlaan 1", zip_code="1000", city="Brussels")
|
||||
grand_total = sum((referee.total_payable for referee in referees), Decimal("0"))
|
||||
|
||||
Reference in New Issue
Block a user