from typing import List, Sequence from .dates_days_between import days_between from .finance_proration import prorate from .money_amount import Money, money 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), )