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 Dict, List, Sequence, Tuple
from .finance_tax_add_vat import VatBreakdown, add_vat ← from finance.tax.add-vat ^1.0.0 · built alongside by fune
from .finance_tax_remove_vat import remove_vat ← from finance.tax.remove-vat ^1.0.0 · built alongside by fune
from .money_allocate import allocate ← from money.allocate ^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_sum import sum_money ← from money.sum ^1.0.0 · built alongside by fune
from .retail_promotion_apply import PromoLine, Promotion, apply_promotions ← from retail.promotion-apply ^1.0.0 · built alongside by fune
from .retail_basket_total_types import BasketLine, BasketLineTotal, BasketVatGroup, Basket
def basket_total(
lines: Sequence[BasketLine],
promotions: Sequence[Promotion],
basket_discount: Money,
delivery: Money,
delivery_tax_category: str,
prices_include_vat: bool,
jurisdiction: str,
on_date: str,
) -> Basket:
"""The checkout: promotions, then the basket discount shared across the
lines, then VAT per line on what the line really costs, then delivery.
No arithmetic lives here; the order of operations is the whole function.
"""
priced = apply_promotions(
[PromoLine(sku=l.sku, unit_price=l.unit_price, quantity=l.quantity) for l in lines],
promotions,
)
currency = priced.subtotal.currency
for name, amount in (("basketDiscount", basket_discount), ("delivery", delivery)):
if amount.currency != currency:
raise ValueError("currency mismatch: %s and %s" % (currency, amount.currency))
if amount.minor < 0:
raise ValueError("%s must not be negative, received %d" % (name, amount.minor))
if basket_discount.minor > priced.total.minor:
raise ValueError(
"basketDiscount %d is more than the basket's %d after promotions"
% (basket_discount.minor, priced.total.minor)
)
after_promotions = [l.net.minor for l in priced.lines]
if basket_discount.minor == 0:
shares = [money(0, currency) for _ in after_promotions]
else:
shares = allocate(basket_discount, after_promotions)
def vat(amount: Money, category: str) -> VatBreakdown:
if prices_include_vat:
return remove_vat(amount, jurisdiction, category, on_date)
return add_vat(amount, jurisdiction, category, on_date)
rates: List[int] = []
totals: List[BasketLineTotal] = []
for i, line in enumerate(lines):
v = vat(money(after_promotions[i] - shares[i].minor, currency), line.tax_category)
rates.append(v.basis_points)
totals.append(
BasketLineTotal(
sku=line.sku,
quantity=line.quantity,
gross=priced.lines[i].gross,
promotion_discount=priced.lines[i].discount,
basket_discount=shares[i],
net=v.net,
tax=v.tax,
total=v.gross,
)
)
delivery_vat = vat(delivery, delivery_tax_category)
# Grouped by rate, lowest first, so the receipt's VAT summary is stable.
groups: Dict[int, Tuple[int, int]] = {}
def add(bp: int, net: Money, tax: Money) -> None:
n, t = groups.get(bp, (0, 0))
groups[bp] = (n + net.minor, t + tax.minor)
for i, t in enumerate(totals):
add(rates[i], t.net, t.tax)
if delivery.minor != 0:
add(delivery_vat.basis_points, delivery_vat.net, delivery_vat.tax)
vat_breakdown = [
BasketVatGroup(basis_points=bp, net=money(n, currency), tax=money(t, currency))
for bp, (n, t) in sorted(groups.items())
]
return Basket(
lines=totals,
promotions=priced.applied,
delivery=delivery_vat,
vat_breakdown=vat_breakdown,
subtotal=priced.subtotal,
promotion_discount=priced.discount,
basket_discount=basket_discount,
net=sum_money([t.net for t in totals] + [delivery_vat.net], currency),
tax=sum_money([t.tax for t in totals] + [delivery_vat.tax], currency),
total=sum_money([t.total for t in totals] + [delivery_vat.gross], currency),
)