from typing import Sequence from .math_round_div import round_div from .money_amount import Money, money from .money_apply_rate import apply_rate 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))