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 List, Sequence
from .math_round_div import round_div ← from math.round-div ^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_refund_calculate_types import SaleLine, ReturnLine, RefundLine, Refund
def calculate_refund(lines: Sequence[SaleLine], basket_discount: Money, returns: Sequence[ReturnLine]) -> Refund:
"""Refund returned units at what they were actually paid: the basket
discount is shared across every sale line first, then each line refunds by
the difference of cumulative shares, so a line fully returned in several
visits refunds exactly what it cost.
"""
if len(lines) == 0:
raise ValueError("a sale needs at least one line")
currency = lines[0].net.currency
for line in lines:
if line.net.currency != currency:
raise ValueError("currency mismatch: %s and %s" % (currency, line.net.currency))
if line.quantity < 1:
raise ValueError("quantity must be 1 or more, received %d" % line.quantity)
if line.net.minor < 0:
raise ValueError("net must not be negative, received %d" % line.net.minor)
if line.returned_before < 0 or line.returned_before > line.quantity:
raise ValueError(
"returnedBefore must be from 0 to %d, received %d" % (line.quantity, line.returned_before)
)
if basket_discount.currency != currency:
raise ValueError("currency mismatch: %s and %s" % (currency, basket_discount.currency))
basket = sum_money([line.net for line in lines], currency)
if basket_discount.minor < 0:
raise ValueError("basketDiscount must not be negative, received %d" % basket_discount.minor)
if basket_discount.minor > basket.minor:
raise ValueError(
"basketDiscount %d is more than the lines' total %d" % (basket_discount.minor, basket.minor)
)
if basket_discount.minor == 0:
shares = [money(0, currency) for _ in lines]
else:
shares = allocate(basket_discount, [line.net.minor for line in lines])
paid = [line.net.minor - share.minor for line, share in zip(lines, shares)]
seen = set()
refunded: List[RefundLine] = []
for r in returns:
if isinstance(r.line, bool) or not isinstance(r.line, int) or r.line < 0 or r.line >= len(lines):
raise ValueError("no line %s in the sale" % (r.line,))
if r.line in seen:
raise ValueError("line %d is returned twice in one refund" % r.line)
seen.add(r.line)
if isinstance(r.quantity, bool) or not isinstance(r.quantity, int) or r.quantity < 1:
raise ValueError("return quantity must be 1 or more, received %r" % (r.quantity,))
line = lines[r.line]
if line.returned_before + r.quantity > line.quantity:
raise ValueError(
"cannot return %d of line %d: %d bought, %d already returned"
% (r.quantity, r.line, line.quantity, line.returned_before)
)
before = round_div(paid[r.line] * line.returned_before, line.quantity, "half-up")
after = round_div(paid[r.line] * (line.returned_before + r.quantity), line.quantity, "half-up")
refunded.append(
RefundLine(
line=r.line,
sku=line.sku,
quantity=r.quantity,
paid=money(paid[r.line], currency),
amount=money(after - before, currency),
)
)
return Refund(lines=refunded, total=sum_money([r.amount for r in refunded], currency))