from .inventory_stock_turnover_types import StockTurnover from .math_round_div import round_div from .money_amount import Money, assert_same_currency, money def _not_negative(name: str, amount: Money) -> None: if amount.minor < 0: raise ValueError("%s must not be negative, received %d" % (name, amount.minor)) def stock_turnover(opening_stock: Money, closing_stock: Money, cost_of_sales: Money, period_days: int) -> StockTurnover: """Turns, days inventory outstanding and days of cover. Each is one exact division rounded half-up, then made a float, so every language agrees. """ assert_same_currency(opening_stock, closing_stock) assert_same_currency(opening_stock, cost_of_sales) _not_negative("openingStock", opening_stock) _not_negative("closingStock", closing_stock) _not_negative("costOfSales", cost_of_sales) if isinstance(period_days, bool) or not isinstance(period_days, int) or period_days < 1: raise ValueError("periodDays must be a whole number of days, at least 1, received %r" % (period_days,)) both = opening_stock.minor + closing_stock.minor cogs = cost_of_sales.minor return StockTurnover( average_stock=money(round_div(both, 2, "half-up"), opening_stock.currency), turns=None if both == 0 else round_div(cogs * 200, both, "half-up") / 100, days_inventory_outstanding=None if cogs == 0 else round_div(both * period_days * 10, cogs * 2, "half-up") / 10, days_of_cover=None if cogs == 0 else round_div(closing_stock.minor * period_days * 10, cogs, "half-up") / 10, )