Three service modules with no Odoo dependencies: - date_periods: fiscal year/month/quarter bounds + comparison derivation - account_hierarchy: parent-child tree walker with type filtering - totaling: move-line aggregation primitives 18 unit tests covering edge cases (December rollover, Feb 29, fiscal- year-before-start, balance check tolerance). Made-with: Cursor
50 lines
1.7 KiB
Python
50 lines
1.7 KiB
Python
"""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
|