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 .finance_invoice_calculate_types import InvoiceLine, CalculatedLine, TaxGroup, Invoice
from .finance_invoice_line_total import line_total ← from finance.invoice.line-total ^1.0.0 · built alongside by fune
from .finance_tax_add_vat import add_vat ← from finance.tax.add-vat ^1.0.0 · built alongside by fune
from .money_add import add_money ← from money.add ^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
def calculate_invoice(lines: Sequence[InvoiceLine], jurisdiction: str, invoice_date: str) -> Invoice:
"""Calculate a complete invoice.
Note what this function does not contain: no multiplication, no
percentages, no rounding, no rate table. Each of those is a capability that
is already pinned by its own vectors. What is left here is the part that is
genuinely about invoices - the order of operations, and how tax is grouped.
VAT is calculated per line and then grouped by rate, which keeps each
line's tax reconcilable against that line.
"""
if len(lines) == 0:
raise ValueError("an invoice needs at least one line")
currency = lines[0].unit_price.currency
calculated: List[CalculatedLine] = []
for line in lines:
net = line_total(line.unit_price, line.quantity, line.discount_basis_points)
vat = add_vat(net, jurisdiction, line.tax_category, invoice_date)
calculated.append(
CalculatedLine(
description=line.description,
net=vat.net,
tax=vat.tax,
gross=vat.gross,
basis_points=vat.basis_points,
)
)
subtotal = sum_money([line.net for line in calculated], currency)
tax_total = sum_money([line.tax for line in calculated], currency)
# Grouped by rate and ordered by rate, so the summary block on a printed
# invoice is stable between runs and between languages.
groups = {}
for line in calculated:
taxable, tax = groups.get(line.basis_points, (money(0, currency), money(0, currency)))
groups[line.basis_points] = (add_money(taxable, line.net), add_money(tax, line.tax))
tax_breakdown = [
TaxGroup(basis_points=basis_points, taxable=taxable, tax=tax)
for basis_points, (taxable, tax) in sorted(groups.items())
]
return Invoice(
currency=currency,
lines=calculated,
subtotal=subtotal,
tax_total=tax_total,
total=add_money(subtotal, tax_total),
tax_breakdown=tax_breakdown,
)