dodil/erp-brick-fixed-assets
DODIL ERP brick: fixed assets
src/brick_fixed_assets/domain/settlement.py
"""Settling an asset under construction (FA-09): how one line item (a bill, own labour) is distributed to the finished
assets. A rule is by percent (exactly 100, of a line nothing of which is settled yet) or by amount (never above what is
unsettled). Cumulative rounding makes the shares add up to the line to the cent."""
from decimal import Decimal
from fractions import Fraction
from brick_fixed_assets.domain.errors import RuleBroken
from brick_fixed_assets.domain.values import ZERO, frac, money
def distribute(line_amount: Decimal, unsettled: Decimal, receivers: list) -> list:
"""`receivers`: [(asset_id, percent | None, amount | None)] -> [(asset_id, amount, percent | None)]."""
if not receivers:
raise RuleBroken("a line item needs at least one receiver (fully_distributed)")
by_pct = [r[1] is not None for r in receivers]
by_amt = [r[2] is not None for r in receivers]
if all(by_pct) and not any(by_amt):
if unsettled != line_amount:
raise RuleBroken("part of this line item is already settled: settle the rest by amount (not_settled_twice)")
total = sum((frac(r[1]) for r in receivers), Fraction(0))
if total != 100:
raise RuleBroken(f"the receivers' shares add up to {float(total):g}%, not 100% (fully_distributed): complete the rule")
out, cum, done = [], Fraction(0), ZERO
for asset_id, pct, _ in receivers:
cum += frac(pct)
upto = money(frac(line_amount) * cum / 100)
out.append((asset_id, upto - done, pct))
done = upto
return out
if all(by_amt) and not any(by_pct):
total = sum((r[2] for r in receivers), ZERO)
if any(r[2] <= 0 for r in receivers):
raise RuleBroken("an amount to settle is more than zero")
if total > unsettled:
raise RuleBroken(f"{total} is more than the {unsettled} still unsettled on this line item (fully_distributed)")
return [(r[0], r[2], None) for r in receivers]
raise RuleBroken("settle a line item by percent or by amount for every receiver, not a mix")