Functional Weave
Code in Rust

property.completion-statement@1.0.1

impl/python.py

3,218 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 .dates_days_between import days_between  ← from dates.days-between ^1.0.0 · built alongside by fune
from .finance_proration import prorate  ← from finance.proration ^1.0.0 · built alongside by fune
from .money_amount import Money, money  ← from money.amount ^1.0.0 · built alongside by fune
from .property_completion_statement_types import ApportionmentLine, CompletionStatement, FeeLine, PeriodCharge


def completion_statement(
    price: Money,
    deposit_paid: Money,
    completion_date: str,
    apportionments: Sequence[PeriodCharge],
    fees: Sequence[FeeLine],
) -> CompletionStatement:
    """A buyer's completion statement. Each outgoing is split at the end of
    the completion day (the seller owns the property until then) by
    finance.proration, so the two shares always add back to the charge."""
    currency = price.currency

    def same(m: Money, what: str) -> None:
        if m.currency != currency:
            raise ValueError("currency mismatch: %s is %s, the price is %s" % (what, m.currency, currency))

    same(deposit_paid, "depositPaid")
    if price.minor < 0:
        raise ValueError("price must not be negative, received %r" % (price.minor,))
    if deposit_paid.minor < 0 or deposit_paid.minor > price.minor:
        raise ValueError("depositPaid must be between 0 and the price, received %r" % (deposit_paid.minor,))
    balance = price.minor - deposit_paid.minor
    lines: List[ApportionmentLine] = []
    for charge in apportionments:
        same(charge.amount, '"%s"' % (charge.label,))
        if charge.amount.minor < 0:
            raise ValueError('"%s" must not be negative, received %r' % (charge.label, charge.amount.minor))
        total_days = days_between(charge.period_start, charge.period_end) + 1
        if total_days < 1:
            raise ValueError(
                '"%s": periodEnd %s is before periodStart %s' % (charge.label, charge.period_end, charge.period_start)
            )
        # The seller owns the property to the end of the completion day.
        up_to_completion = days_between(charge.period_start, completion_date) + 1
        seller_days = min(total_days, max(0, up_to_completion))
        split = prorate(charge.amount, total_days, seller_days)
        adjustment = split.unused.minor if charge.paid_by_seller else -split.used.minor
        balance += adjustment
        lines.append(
            ApportionmentLine(
                label=charge.label,
                seller_days=seller_days,
                buyer_days=total_days - seller_days,
                adjustment=money(adjustment, currency),
            )
        )
    fees_total = 0
    fee_lines: List[FeeLine] = []
    for fee in fees:
        same(fee.amount, '"%s"' % (fee.label,))
        if fee.amount.minor < 0:
            raise ValueError('"%s" must not be negative, received %r' % (fee.label, fee.amount.minor))
        fees_total += fee.amount.minor
        fee_lines.append(FeeLine(label=fee.label, amount=money(fee.amount.minor, currency)))
    return CompletionStatement(
        price=money(price.minor, currency),
        deposit_paid=money(deposit_paid.minor, currency),
        apportionments=lines,
        balance_to_seller=money(balance, currency),
        fees=fee_lines,
        fees_total=money(fees_total, currency),
        total_due=money(balance + fees_total, currency),
    )