Functional Weave
Code in Rust

professional.wip-valuation@1.0.0

impl/python.py

1,912 bytes · the Python implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

from typing import Sequence

from .math_round_div import round_div  ← from math.round-div ^1.0.0 · built alongside by fune
from .money_amount import Money, money  ← from money.amount ^1.0.0 · built alongside by fune
from .money_apply_rate import apply_rate  ← from money.apply-rate ^1.0.0 · built alongside by fune
from .professional_wip_valuation_types import WipBasis, WipEntry, WipValuation


def wip_valuation(entries: Sequence[WipEntry], basis: WipBasis, write_down_basis_points: int, currency: str) -> WipValuation:
    """Work in progress at cost or charge-out rates, less a write-down."""
    if basis not in ("cost", "charge-out"):
        raise ValueError('unknown WIP basis "%s": expected cost or charge-out' % (basis,))
    if (
        isinstance(write_down_basis_points, bool)
        or not isinstance(write_down_basis_points, int)
        or write_down_basis_points < 0
        or write_down_basis_points > 10000
    ):
        raise ValueError("writeDownBasisPoints must be between 0 and 10000, received %s" % (write_down_basis_points,))
    minutes = 0
    gross = 0
    for entry in entries:
        if isinstance(entry.minutes, bool) or not isinstance(entry.minutes, int) or entry.minutes < 0:
            raise ValueError("minutes must be a non-negative integer, received %s" % (entry.minutes,))
        rate: Money = entry.cost_rate if basis == "cost" else entry.charge_rate
        if rate.currency != currency:
            raise ValueError("currency mismatch: %s and %s" % (currency, rate.currency))
        if rate.minor < 0:
            raise ValueError("hourly rates must not be negative, received %s" % (rate.minor,))
        minutes += entry.minutes
        # Per entry, as the time would be billed, so WIP reconciles with the bill.
        gross += round_div(entry.minutes * rate.minor, 60, "half-up")
    gross_money = money(gross, currency)
    write_down = apply_rate(gross_money, write_down_basis_points, "half-up")
    return WipValuation(minutes=minutes, gross=gross_money, write_down=write_down, net=money(gross - write_down.minor, currency))