"""Move-line aggregation primitives for report totaling. Pure-Python helpers - callers pass dicts with debit/credit/balance/currency keys, no Odoo recordsets needed. Keeps the math testable without an ORM.""" from dataclasses import dataclass @dataclass class TotalLine: debit: float = 0.0 credit: float = 0.0 balance: float = 0.0 debit_currency: float = 0.0 credit_currency: float = 0.0 balance_currency: float = 0.0 line_count: int = 0 def aggregate(move_lines: list[dict]) -> TotalLine: """Aggregate a list of move-line dicts into a TotalLine. Each dict must have: debit, credit, balance (signed). Optional: debit_currency, credit_currency, balance_currency.""" out = TotalLine() for ml in move_lines: out.debit += ml.get('debit', 0.0) out.credit += ml.get('credit', 0.0) out.balance += ml.get('balance', 0.0) out.debit_currency += ml.get('debit_currency', 0.0) out.credit_currency += ml.get('credit_currency', 0.0) out.balance_currency += ml.get('balance_currency', 0.0) out.line_count += 1 return out def aggregate_per_account(move_lines: list[dict]) -> dict[int, TotalLine]: """Group + aggregate by account_id. Returns {account_id: TotalLine}.""" grouped: dict[int, list[dict]] = {} for ml in move_lines: acct = ml['account_id'] grouped.setdefault(acct, []).append(ml) return {acct: aggregate(lines) for acct, lines in grouped.items()} def is_balanced(move_lines: list[dict], *, tolerance: float = 0.005) -> bool: """True if total debits == total credits (within tolerance for rounding).""" agg = aggregate(move_lines) return abs(agg.debit - agg.credit) <= tolerance