mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-07-01 03:04:52 +02:00
feat: add template creation timestamps
This commit is contained in:
@@ -80,12 +80,15 @@ Placeholders use `{{group.field}}`. Available values include:
|
|||||||
`member.mandate_signed_at`, `member.mandate_revoked_at`,
|
`member.mandate_signed_at`, `member.mandate_revoked_at`,
|
||||||
`member.mandate_active`
|
`member.mandate_active`
|
||||||
- Claim: `claim.id`, `claim.title`, `claim.due_date`, `claim.total`,
|
- 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: `reminder.id`, `reminder.level`, `reminder.name`,
|
||||||
`reminder.status`, `reminder.created_at`, `reminder.sent_at`,
|
`reminder.status`, `reminder.created_at`, `reminder.sent_at`,
|
||||||
`reminder.payment_deadline`, `reminder.payment_deadline_days`,
|
`reminder.payment_deadline`, `reminder.payment_deadline_days`,
|
||||||
`reminder.fee`, `reminder.detail`, `reminder.channel`
|
`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: `organization.name`, `organization.street`,
|
||||||
`organization.postal_code`, `organization.city`, `organization.country`,
|
`organization.postal_code`, `organization.city`, `organization.country`,
|
||||||
`organization.address_line`, `organization.email`, `organization.phone`,
|
`organization.address_line`, `organization.email`, `organization.phone`,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"OpenDocument-Templates mit Platzhaltern, lokaler PDF-Erzeugung, Audit-Verknüpfung und Dokumentöffnung aus der Mitgliederakte ergänzt.",
|
"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.",
|
"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.",
|
"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.",
|
"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.",
|
"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.",
|
"Splash-Screen auf das eingebettete CCMA-Hintergrundmotiv umgestellt und redundante Titeltexte entfernt.",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<office:text>
|
<office:text>
|
||||||
<text:p text:style-name="Title">{{organization.name}}</text:p>
|
<text:p text:style-name="Title">{{organization.name}}</text:p>
|
||||||
<text:p>{{organization.address_line}} · {{organization.email}}</text:p>
|
<text:p>{{organization.address_line}} · {{organization.email}}</text:p>
|
||||||
<text:p>Erstellt am {{document.date}}</text:p>
|
<text:p>Erstellt am {{document.created_date}}</text:p>
|
||||||
<text:p text:style-name="Heading">Forderung: {{claim.title}}</text:p>
|
<text:p text:style-name="Heading">Forderung: {{claim.title}}</text:p>
|
||||||
<text:p>Mitglied: {{member.full_name}} ({{member.number}})</text:p>
|
<text:p>Mitglied: {{member.full_name}} ({{member.number}})</text:p>
|
||||||
<text:p>E-Mail: {{member.email}}</text:p>
|
<text:p>E-Mail: {{member.email}}</text:p>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<office:text>
|
<office:text>
|
||||||
<text:p text:style-name="Title">{{organization.name}}</text:p>
|
<text:p text:style-name="Title">{{organization.name}}</text:p>
|
||||||
<text:p>{{organization.address_line}} · {{organization.email}}</text:p>
|
<text:p>{{organization.address_line}} · {{organization.email}}</text:p>
|
||||||
<text:p>{{document.date}}</text:p>
|
<text:p>{{document.created_date}}</text:p>
|
||||||
<text:p>An {{member.full_name}} ({{member.number}})</text:p>
|
<text:p>An {{member.full_name}} ({{member.number}})</text:p>
|
||||||
<text:p>{{member.address_line}}</text:p>
|
<text:p>{{member.address_line}}</text:p>
|
||||||
<text:p text:style-name="Heading">{{reminder.name}}</text:p>
|
<text:p text:style-name="Heading">{{reminder.name}}</text:p>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<office:body>
|
<office:body>
|
||||||
<office:text>
|
<office:text>
|
||||||
<text:p text:style-name="Title">{{organization.name}}</text:p>
|
<text:p text:style-name="Title">{{organization.name}}</text:p>
|
||||||
<text:p>Erstellt am {{document.date}}</text:p>
|
<text:p>Erstellt am {{document.created_date}}</text:p>
|
||||||
<text:p text:style-name="Heading">Mitgliedsdaten</text:p>
|
<text:p text:style-name="Heading">Mitgliedsdaten</text:p>
|
||||||
<text:p>Name: {{member.full_name}}</text:p>
|
<text:p>Name: {{member.full_name}}</text:p>
|
||||||
<text:p>Mitgliedsnummer: {{member.number}}</text:p>
|
<text:p>Mitgliedsnummer: {{member.number}}</text:p>
|
||||||
|
|||||||
@@ -182,6 +182,9 @@ def _template_values(
|
|||||||
organization: dict | None = None,
|
organization: dict | None = None,
|
||||||
) -> tuple[dict[str, str], dict[str, list[dict[str, str]]]]:
|
) -> tuple[dict[str, str], dict[str, list[dict[str, str]]]]:
|
||||||
organization = organization or {}
|
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(
|
organization_address = " ".join(
|
||||||
part
|
part
|
||||||
for part in (
|
for part in (
|
||||||
@@ -192,8 +195,12 @@ def _template_values(
|
|||||||
if part
|
if part
|
||||||
)
|
)
|
||||||
values = {
|
values = {
|
||||||
"document.date": format_date_for_display(date.today().isoformat()),
|
"document.date": created_date,
|
||||||
"document.datetime": datetime.now().astimezone().strftime("%d.%m.%Y %H:%M"),
|
"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.id": member.member_id,
|
||||||
"member.number": member.member_number,
|
"member.number": member.member_number,
|
||||||
"member.first_name": member.first_name,
|
"member.first_name": member.first_name,
|
||||||
@@ -246,6 +253,10 @@ def _template_values(
|
|||||||
"claim.id": claim_id,
|
"claim.id": claim_id,
|
||||||
"claim.title": str(claim.get("title", "")),
|
"claim.title": str(claim.get("title", "")),
|
||||||
"claim.due_date": format_date_for_display(str(claim.get("due_date", ""))),
|
"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.total": f"{money_text(claim_total(claim))} EUR",
|
||||||
"claim.paid": f"{money_text(allocated_total(data, claim_id))} EUR",
|
"claim.paid": f"{money_text(allocated_total(data, claim_id))} EUR",
|
||||||
"claim.balance": f"{money_text(claim_balance(data, claim))} EUR",
|
"claim.balance": f"{money_text(claim_balance(data, claim))} EUR",
|
||||||
@@ -516,5 +527,14 @@ def _display_timestamp(value: str) -> str:
|
|||||||
return value[:16]
|
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:
|
def _local_name(tag: str) -> str:
|
||||||
return tag.rsplit("}", 1)[-1]
|
return tag.rsplit("}", 1)[-1]
|
||||||
|
|||||||
+25
-1
@@ -6,7 +6,13 @@ from xml.etree import ElementTree
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import ccma.services.documents as document_module
|
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.services.housekeeper import Housekeeper
|
||||||
from ccma.storage.repository import MemberRepository, RepositoryError
|
from ccma.storage.repository import MemberRepository, RepositoryError
|
||||||
|
|
||||||
@@ -32,6 +38,24 @@ def test_unknown_placeholder_is_reported() -> None:
|
|||||||
_replace_xml_placeholders(source, {})
|
_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:
|
def test_claim_item_loop_clones_formatted_table_row() -> None:
|
||||||
source = b"""<office:document
|
source = b"""<office:document
|
||||||
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
|
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user