diff --git a/club/services/invoicing.py b/club/services/invoicing.py index 87a5fe1..a29b145 100644 --- a/club/services/invoicing.py +++ b/club/services/invoicing.py @@ -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) diff --git a/club/templates/club/dues_invoice_pdf.html b/club/templates/club/dues_invoice_pdf.html index e845522..4f14988 100644 --- a/club/templates/club/dues_invoice_pdf.html +++ b/club/templates/club/dues_invoice_pdf.html @@ -42,7 +42,11 @@
-

{{ club.name }}

+

{{ club.official_name }}

+ {% if home_location %} +
{{ home_location.address }}
+
{{ home_location.zip_code }} {{ home_location.city }}
+ {% endif %} {% if club.contact_email %}
{{ club.contact_email }}
{% endif %}
diff --git a/club/tests.py b/club/tests.py index 8872e55..0e18f14 100644 --- a/club/tests.py +++ b/club/tests.py @@ -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.""" diff --git a/management/pdf_previews.py b/management/pdf_previews.py index f6a26ef..959e033 100644 --- a/management/pdf_previews.py +++ b/management/pdf_previews.py @@ -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"))