from typing import Sequence from .hospitality_service_charge_types import BillLine, ServiceCharge from .math_round_div import RoundingMode from .money_add import add_money from .money_apply_rate import apply_rate from .money_sum import sum_money def service_charge(lines: Sequence[BillLine], basis_points: int, mode: RoundingMode) -> ServiceCharge: """A discretionary service charge on the eligible lines of a bill. The rate is applied once, to the eligible total, and rounded once. Charging each line and adding the pennies up drifts: three 3.33 lines at 12.5% are 0.42 each, 1.26 in all, where 12.5% of 9.99 is 1.25. """ if len(lines) == 0: raise ValueError("a bill needs at least one line") if isinstance(basis_points, bool) or not isinstance(basis_points, int) or basis_points < 0 or basis_points > 10000: raise ValueError("basisPoints must be a whole number from 0 to 10000, received %s" % (basis_points,)) currency = lines[0].amount.currency subtotal = sum_money([line.amount for line in lines], currency) eligible_total = sum_money([line.amount for line in lines if line.eligible], currency) if eligible_total.minor < 0: raise ValueError( "the eligible lines total %d, and a service charge cannot be negative" % (eligible_total.minor,) ) charge = apply_rate(eligible_total, basis_points, mode) return ServiceCharge( eligible_total=eligible_total, basis_points=basis_points, charge=charge, subtotal=subtotal, total=add_money(subtotal, charge), )