Functional Weave
Code in Rust

finance.invoice.calculate@1.0.1

impl/python.py

2,420 bytes · the Python implementation · view raw

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,
    )