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")