Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import re
from .money_amount import Money, money ← from money.amount ^1.0.0 · built alongside by fune
from .units_convert import convert_units ← from units.convert ^1.0.0 · built alongside by fune
from .retail_price_per_unit_types import UnitPriceBasis
# Each basis is a units.convert symbol and how many of it the price is for.
_BASES = {
"kg": ("kg", 1),
"100g": ("g", 100),
"10g": ("g", 10),
"L": ("L", 1),
"100mL": ("mL", 100),
"10mL": ("mL", 10),
"75cL": ("mL", 750),
"m": ("m", 1),
"m2": ("m2", 1),
"m3": ("m3", 1),
"item": ("item", 1),
}
_DECIMAL = re.compile(r"[0-9]+(\.[0-9]+)?")
def unit_price(price: Money, quantity: str, unit: str, per: UnitPriceBasis) -> Money:
"""The price of one ``per`` of the product, rounded half up to the minor unit.
The quantity is converted exactly (units.convert, 12 decimal places), then
price x multiplier / quantity is done in integers, so the one rounding step
is the last one.
"""
if price.minor < 0:
raise ValueError("price must not be negative, received %d" % price.minor)
if per not in _BASES:
raise ValueError('unknown unit price basis "%s"' % (per,))
target, multiplier = _BASES[per]
if target == "item" or unit == "item":
if target != "item":
raise ValueError('a quantity in items can only be priced per "item"')
if unit != "item":
raise ValueError('per "item" needs the quantity in "item"')
if not isinstance(quantity, str) or not _DECIMAL.fullmatch(quantity):
raise ValueError('quantity must be more than 0, received "%s"' % (quantity,))
converted = quantity
else:
converted = convert_units(quantity, unit, target, 12)
if converted.startswith("-") or not re.search(r"[1-9]", converted):
raise ValueError('quantity must be more than 0, received "%s"' % (quantity,))
# converted = digits / 10^places, so price / converted = price x 10^places / digits.
whole, _, fraction = converted.partition(".")
digits = int(whole + fraction)
numerator = price.minor * multiplier * 10 ** len(fraction)
quotient, remainder = divmod(numerator, digits)
if remainder * 2 >= digits:
quotient += 1
return money(quotient, price.currency)