dodil/erp-brick-inventory

DODIL ERP brick: inventory

src/brick_inventory/domain/units.py

"""Showing a quantity in the packs people handle: 5 PK24 and 6 C62 (94.5 LTR), the warehouse unit first.

A unit's factor is the base-unit quantity of ONE of it (numerator / denominator). Everything is exact `Fraction`; a remainder
that no listed unit divides is shown in the base unit.
"""
from decimal import Decimal
from fractions import Fraction

from brick_inventory.domain.values import fraction, round_half_up


def factor(row: dict) -> Fraction:
    return Fraction(int(row["numerator"]), int(row["denominator"]))


def breakdown(quantity_base: Decimal, base_unit: str, units: list) -> list:
    """[(quantity, unit_code)] greedy over `units` (rows with unit_code, numerator, denominator, quantity_decimals), largest
    first as given; whole counts only for a unit that takes no decimals; the rest in the base unit."""
    left = fraction(quantity_base)
    out = []
    for u in units:
        f = factor(u)
        if f <= 0 or left <= 0 or f == 1 and u["unit_code"] == base_unit:
            continue
        whole = int(left // f)
        if whole >= 1:
            out.append((Decimal(whole), u["unit_code"]))
            left -= whole * f
    if left > 0 or not out:
        out.append((round_half_up(left, 4).normalize() if left else Decimal(0), base_unit))
    return out


def express(quantity_base: Decimal, row: dict) -> dict:
    """The quantity in one unit, exact: {quantity (to the unit's decimals), exact "n/d", unit_code}."""
    q = fraction(quantity_base) / factor(row)
    places = int(row.get("quantity_decimals") if row.get("quantity_decimals") is not None else 4)
    return {"quantity": str(round_half_up(q, places)), "exact": f"{q.numerator}/{q.denominator}", "unit_code": row["unit_code"]}