import re from dataclasses import dataclass from datetime import datetime from decimal import ROUND_HALF_UP, Decimal from typing import Optional _PLACEHOLDER_RE = re.compile(r"\{\{\s*(\w+)\s*\}\}") def render_template(text: str, variables: dict[str, str]) -> str: return _PLACEHOLDER_RE.sub(lambda m: str(variables.get(m.group(1), m.group(0))), text) def format_datetime_da(dt: datetime) -> str: return dt.strftime("%d-%m-%Y kl. %H:%M") @dataclass class CatalogRow: category_name: str wine_name: str unit_price_eur: Decimal ordered_quantity: Optional[int] # None = ikke bestilt, vises tomt def render_order_receipt_html(rows: list[CatalogRow]) -> str: """rows skal være for-sorteret efter (kategori.sort_order, vin-id) — denne funktion grupperer blot konsekutive ens kategorinavne under én overskrift, ligesom den gamle Kvitering-mail.""" html_parts = [''] current_category = None for row in rows: if row.category_name != current_category: html_parts.append( f'' ) current_category = row.category_name qty_display = str(row.ordered_quantity) if row.ordered_quantity else "" html_parts.append( f'' f'' f'' ) html_parts.append("
{row.category_name}
{qty_display}{row.wine_name}{row.unit_price_eur:.2f}
") return "".join(html_parts) def compute_order_total_eur(rows: list[CatalogRow]) -> Decimal: """Kun de faktisk bestilte linjer tæller med i totalen — resten af kataloget er kun til visning. Ingen afrunding her (fuld præcision bevares til evt. DKK-omregning).""" return sum( (r.unit_price_eur * r.ordered_quantity for r in rows if r.ordered_quantity), Decimal("0"), ) def format_currency(amount: Decimal) -> str: """Eneste sted afrunding sker — til visning, normal matematisk afrunding (ikke Decimal's ROUND_HALF_EVEN-standard).""" return str(amount.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))