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