"""Geldbeträge werden intern immer als ganze Cent (int) geführt – keine Float-Rundungsfehler.""" from __future__ import annotations from decimal import ROUND_HALF_UP, Decimal, InvalidOperation def parse_cent(value) -> int: """Wandelt '1.234,56', '-8', '30', 12.5, '12.50 €' usw. in Cent um.""" if value is None: return 0 if isinstance(value, bool): raise ValueError("Ungültiger Betrag") if isinstance(value, int): return value * 100 if isinstance(value, float): return int(Decimal(str(value)).scaleb(2).quantize(Decimal(1), ROUND_HALF_UP)) s = str(value).strip().replace("€", "").replace("EUR", "").replace(" ", "").replace("\xa0", "") if not s: return 0 neg = False if s.endswith("-"): # SAP-Stil: '30,00-' neg, s = True, s[:-1] if "," in s and "." in s: if s.rfind(",") > s.rfind("."): s = s.replace(".", "").replace(",", ".") # 1.234,56 else: s = s.replace(",", "") # 1,234.56 elif "," in s: s = s.replace(",", ".") try: d = Decimal(s) except InvalidOperation as exc: raise ValueError(f"Ungültiger Betrag: {value!r}") from exc cent = int(d.scaleb(2).quantize(Decimal(1), ROUND_HALF_UP)) return -cent if neg else cent def apply_percent(cent: int, prozent: float) -> int: d = Decimal(cent) * Decimal(str(prozent)) / Decimal(100) return int(d.quantize(Decimal(1), ROUND_HALF_UP)) def fmt(cent: int | None, symbol: bool = True) -> str: if cent is None: return "" neg = cent < 0 cent = abs(cent) euro, rest = divmod(cent, 100) s = f"{euro:,}".replace(",", ".") + f",{rest:02d}" if neg: s = "-" + s return s + (" €" if symbol else "") def to_euro(cent: int) -> float: return float(Decimal(cent) / Decimal(100)) def fmt_percent(p: float | None) -> str: if p is None: return "" s = f"{p:.2f}".rstrip("0").rstrip(".") return s.replace(".", ",") + " %" def parse_percent(text: str) -> float: s = str(text).strip().replace("%", "").replace(" ", "").replace(",", ".") p = float(s) if p < 0 or p > 1000: raise ValueError("Prozentwert außerhalb des gültigen Bereichs") return p