diff --git a/README.md b/README.md index f474a9c..faad5d7 100644 --- a/README.md +++ b/README.md @@ -80,12 +80,15 @@ Placeholders use `{{group.field}}`. Available values include: `member.mandate_signed_at`, `member.mandate_revoked_at`, `member.mandate_active` - Claim: `claim.id`, `claim.title`, `claim.due_date`, `claim.total`, - `claim.paid`, `claim.balance`, `claim.status`, `claim.items` + `claim.created_date`, `claim.created_at`, `claim.paid`, `claim.balance`, + `claim.status`, `claim.items` - Reminder: `reminder.id`, `reminder.level`, `reminder.name`, `reminder.status`, `reminder.created_at`, `reminder.sent_at`, `reminder.payment_deadline`, `reminder.payment_deadline_days`, `reminder.fee`, `reminder.detail`, `reminder.channel` -- Document: `document.date`, `document.datetime` +- Document: `document.created_date`, `document.created_at`; compatibility + aliases: `document.date`, `document.datetime`, `current_date`, + `current_datetime` - Organization: `organization.name`, `organization.street`, `organization.postal_code`, `organization.city`, `organization.country`, `organization.address_line`, `organization.email`, `organization.phone`, diff --git a/src/ccma/assets/CHANGELOG.json b/src/ccma/assets/CHANGELOG.json index 437fe1a..3613dc6 100644 --- a/src/ccma/assets/CHANGELOG.json +++ b/src/ccma/assets/CHANGELOG.json @@ -28,6 +28,7 @@ "OpenDocument-Templates mit Platzhaltern, lokaler PDF-Erzeugung, Audit-Verknüpfung und Dokumentöffnung aus der Mitgliederakte ergänzt.", "Zentrale Vereins- und Absenderdaten sowie getrennte Mitgliedsbereiche für Anschrift, Telefon und validierte Bank-/SEPA-Daten ergänzt.", "Wiederholbare OpenDocument-Tabellenzeilen für beliebig viele Forderungspositionen eingeführt.", + "Eindeutige Templatefelder für Dokument-, aktuelle und Forderungs-Erstellungszeit ergänzt.", "Dropdowns zeigen deutsche Begriffe bei weiterhin englischen Speicher-Keys; der Hausmeisterstatus liegt einheitlich in housekeeper.json.", "Mehrstufiger Mahnworkflow mit Hausmeister-Regel, Entwurf, Versandbestätigung, Zahlungsfrist, optionaler Gebühr und Mahnsperre ergänzt.", "Splash-Screen auf das eingebettete CCMA-Hintergrundmotiv umgestellt und redundante Titeltexte entfernt.", diff --git a/src/ccma/assets/templates/Forderung.fodt b/src/ccma/assets/templates/Forderung.fodt index 26d817c..672cfc7 100644 --- a/src/ccma/assets/templates/Forderung.fodt +++ b/src/ccma/assets/templates/Forderung.fodt @@ -8,7 +8,7 @@ {{organization.name}} {{organization.address_line}} · {{organization.email}} - Erstellt am {{document.date}} + Erstellt am {{document.created_date}} Forderung: {{claim.title}} Mitglied: {{member.full_name}} ({{member.number}}) E-Mail: {{member.email}} diff --git a/src/ccma/assets/templates/Mahnung.fodt b/src/ccma/assets/templates/Mahnung.fodt index 87afdb1..5656d94 100644 --- a/src/ccma/assets/templates/Mahnung.fodt +++ b/src/ccma/assets/templates/Mahnung.fodt @@ -8,7 +8,7 @@ {{organization.name}} {{organization.address_line}} · {{organization.email}} - {{document.date}} + {{document.created_date}} An {{member.full_name}} ({{member.number}}) {{member.address_line}} {{reminder.name}} diff --git a/src/ccma/assets/templates/Mitglied.fodt b/src/ccma/assets/templates/Mitglied.fodt index e06374b..759eb44 100644 --- a/src/ccma/assets/templates/Mitglied.fodt +++ b/src/ccma/assets/templates/Mitglied.fodt @@ -7,7 +7,7 @@ {{organization.name}} - Erstellt am {{document.date}} + Erstellt am {{document.created_date}} Mitgliedsdaten Name: {{member.full_name}} Mitgliedsnummer: {{member.number}} diff --git a/src/ccma/services/documents.py b/src/ccma/services/documents.py index 5b7b9a8..388c081 100644 --- a/src/ccma/services/documents.py +++ b/src/ccma/services/documents.py @@ -182,6 +182,9 @@ def _template_values( organization: dict | None = None, ) -> tuple[dict[str, str], dict[str, list[dict[str, str]]]]: organization = organization or {} + created_at = datetime.now().astimezone() + created_date = format_date_for_display(created_at.date().isoformat()) + created_timestamp = created_at.strftime("%d.%m.%Y %H:%M") organization_address = " ".join( part for part in ( @@ -192,8 +195,12 @@ def _template_values( if part ) values = { - "document.date": format_date_for_display(date.today().isoformat()), - "document.datetime": datetime.now().astimezone().strftime("%d.%m.%Y %H:%M"), + "document.date": created_date, + "document.datetime": created_timestamp, + "document.created_date": created_date, + "document.created_at": created_timestamp, + "current_date": created_date, + "current_datetime": created_timestamp, "member.id": member.member_id, "member.number": member.member_number, "member.first_name": member.first_name, @@ -246,6 +253,10 @@ def _template_values( "claim.id": claim_id, "claim.title": str(claim.get("title", "")), "claim.due_date": format_date_for_display(str(claim.get("due_date", ""))), + "claim.created_date": _display_date_from_timestamp( + str(claim.get("created_at", "")) + ), + "claim.created_at": _display_timestamp(str(claim.get("created_at", ""))), "claim.total": f"{money_text(claim_total(claim))} EUR", "claim.paid": f"{money_text(allocated_total(data, claim_id))} EUR", "claim.balance": f"{money_text(claim_balance(data, claim))} EUR", @@ -516,5 +527,14 @@ def _display_timestamp(value: str) -> str: return value[:16] +def _display_date_from_timestamp(value: str) -> str: + if not value: + return "" + try: + return format_date_for_display(datetime.fromisoformat(value).date().isoformat()) + except ValueError: + return value[:10] + + def _local_name(tag: str) -> str: return tag.rsplit("}", 1)[-1] diff --git a/tests/test_documents.py b/tests/test_documents.py index 2bdc8fb..4eb7219 100644 --- a/tests/test_documents.py +++ b/tests/test_documents.py @@ -6,7 +6,13 @@ from xml.etree import ElementTree import pytest import ccma.services.documents as document_module -from ccma.services.documents import DocumentError, DocumentService, _replace_xml_placeholders +from ccma.domain.models import ContributionData, Member +from ccma.services.documents import ( + DocumentError, + DocumentService, + _replace_xml_placeholders, + _template_values, +) from ccma.services.housekeeper import Housekeeper from ccma.storage.repository import MemberRepository, RepositoryError @@ -32,6 +38,24 @@ def test_unknown_placeholder_is_reported() -> None: _replace_xml_placeholders(source, {}) +def test_document_and_claim_creation_time_placeholders() -> None: + member = Member("member-1", "CCMA-1", "Ada", "Lovelace") + claim = { + "claim_id": "claim-1", + "title": "Test", + "amount": "10.00", + "created_at": "2026-06-21T14:35:00+02:00", + } + data = ContributionData(claims=[claim]) + + values, _repeats = _template_values(member, data=data, claim=claim) + + assert values["document.created_date"] == values["document.date"] == values["current_date"] + assert values["document.created_at"] == values["document.datetime"] == values["current_datetime"] + assert values["claim.created_date"] in {"21.06.2026", "2026-06-21"} + assert values["claim.created_at"] == "21.06.2026 14:35" + + def test_claim_item_loop_clones_formatted_table_row() -> None: source = b"""