dodil/erp-brick-inventory
DODIL ERP brick: inventory
src/brick_inventory/domain/measures.py
"""Weights and volumes of a document, summed exactly through the units' conversions (no float anywhere)."""
from decimal import Decimal
from fractions import Fraction
from typing import Optional
from brick_inventory.domain.errors import RuleBroken
from brick_inventory.domain.values import fraction, round_half_up
# size in the reference unit of each dimension (KGM; MTQ). unit_conversion (GRM, LBR, ...) is exact.
WEIGHT = {"KGM": Fraction(1), "GRM": Fraction(1, 1000), "TNE": Fraction(1000), "LBR": Fraction(45359237, 100000000),
"ONZ": Fraction(28349523125, 1000000000000)}
VOLUME = {"MTQ": Fraction(1), "LTR": Fraction(1, 1000), "MLT": Fraction(1, 1000000), "CMQ": Fraction(1, 1000000)}
def to_kgm(value: Decimal, unit_code: str) -> Fraction:
if unit_code not in WEIGHT:
raise RuleBroken(f"cannot convert a weight in {unit_code}: use one of {', '.join(WEIGHT)}")
return fraction(value) * WEIGHT[unit_code]
def to_mtq(value: Decimal, unit_code: str) -> Fraction:
if unit_code not in VOLUME:
raise RuleBroken(f"cannot convert a volume in {unit_code}: use one of {', '.join(VOLUME)}")
return fraction(value) * VOLUME[unit_code]
def shown(value: Fraction, places: int = 3) -> str:
d = round_half_up(value, places)
s = format(d, "f")
return s.rstrip("0").rstrip(".") if "." in s else s
def shipping(lines: list) -> dict:
"""lines: [{quantity_done, gross_weight, net_weight, weight_unit_code, volume, volume_unit_code}] per one of the line's unit.
A line with an unknown measure is named, never counted as 0."""
gross = net = vol = Fraction(0)
unknown_w, unknown_v = [], []
for i, l in enumerate(lines, start=1):
qty = fraction(l["quantity_done"] if l["quantity_done"] is not None else 0)
if l.get("gross_weight") is None and l.get("net_weight") is None:
unknown_w.append(l.get("label") or i)
else:
if l.get("gross_weight") is not None:
gross += qty * to_kgm(l["gross_weight"], l["weight_unit_code"] or "KGM")
if l.get("net_weight") is not None:
net += qty * to_kgm(l["net_weight"], l["weight_unit_code"] or "KGM")
if l.get("volume") is None:
unknown_v.append(l.get("label") or i)
else:
vol += qty * to_mtq(l["volume"], l["volume_unit_code"] or "MTQ")
n = len(lines)
return {"gross_weight": shown(gross), "net_weight": shown(net), "weight_unit_code": "KGM", "volume": shown(vol),
"volume_unit_code": "MTQ",
"weight_unknown": f"weight unknown for {len(unknown_w)} of {n} lines" if unknown_w else None,
"volume_unknown": f"volume unknown for {len(unknown_v)} of {n} lines" if unknown_v else None,
"unknown_weight_lines": unknown_w, "unknown_volume_lines": unknown_v}
def kgm_to_unit(value: Fraction, unit_code: str) -> Optional[Fraction]:
return value / WEIGHT[unit_code] if unit_code in WEIGHT else None