"""Währungsumrechnung EUR<->CHF über die lokal gecachten EZB-Referenzkurse. Kurse werden als ``rate_per_eur`` gespeichert: wie viele Einheiten der Währung ein Euro wert war (EUR ist implizit 1.0). Umgerechnet wird immer mit dem Kurs zum Buchungsdatum; fehlt ein Kurs für genau diesen Tag (Wochenende/Feiertag), wird der letzte verfügbare Börsentag davor genommen. """ from datetime import date from decimal import Decimal, ROUND_HALF_UP from sqlalchemy.orm import Session from ..models import ExchangeRate def rate_on(db: Session, currency: str, on: date) -> Decimal | None: """Kurs (Währungseinheiten je 1 EUR) am Tag ``on`` oder dem letzten davor.""" if currency == "EUR": return Decimal(1) row = ( db.query(ExchangeRate) .filter(ExchangeRate.currency == currency, ExchangeRate.date <= on) .order_by(ExchangeRate.date.desc()) .first() ) return Decimal(str(row.rate_per_eur)) if row is not None else None def _round_cents(value: Decimal) -> int: return int(value.quantize(Decimal(1), rounding=ROUND_HALF_UP)) def to_eur_cents( db: Session, amount_cents: int, currency: str, on: date, fx_rate: float | None = None ) -> int | None: """Rechnet einen Betrag in Kontowährung in EUR-Cent um. ``fx_rate`` (Kontowährung je 1 EUR) hat Vorrang vor dem EZB-Kurs. Gibt None zurück, wenn kein Kurs verfügbar ist (Aufrufer entscheidet über Fallback).""" if currency == "EUR": return amount_cents rate = Decimal(str(fx_rate)) if fx_rate else rate_on(db, currency, on) if not rate or rate == 0: return None return _round_cents(Decimal(amount_cents) / rate) def from_eur_cents(db: Session, eur_cents: int, currency: str, on: date) -> int | None: """Rechnet EUR-Cent in die Zielwährung um (Kurs zum Tag ``on``).""" if currency == "EUR": return eur_cents rate = rate_on(db, currency, on) if rate is None: return None return _round_cents(Decimal(eur_cents) * rate) def convert( db: Session, amount_cents: int, from_ccy: str, to_ccy: str, on: date, fx_rate: float | None = None, ) -> int | None: """Rechnet zwischen zwei Währungen um (über EUR als Pivot).""" if from_ccy == to_ccy: return amount_cents eur = to_eur_cents(db, amount_cents, from_ccy, on, fx_rate) if eur is None: return None return from_eur_cents(db, eur, to_ccy, on) def compute_amount_eur_cents( db: Session, amount_cents: int, currency: str, on: date, fx_rate: float | None = None ) -> int: """Eingefrorener EUR-Wert für eine Transaktion. Fällt notfalls (kein Kurs verfügbar) auf 1:1 zurück, damit das Speichern nie scheitert.""" eur = to_eur_cents(db, amount_cents, currency, on, fx_rate) return eur if eur is not None else amount_cents