from .dates_add_days import add_days from .finance_early_payment_discount_types import EarlyPaymentDiscount from .finance_payment_terms_due_date import payment_due_date from .finance_payment_terms_due_date_types import PaymentTerms from .money_add import subtract_money from .money_amount import Money, money from .money_apply_rate import apply_rate def early_payment_discount( invoice_total: Money, invoice_date: str, terms: PaymentTerms, payment_date: str ) -> EarlyPaymentDiscount: """The settlement discount open on ``payment_date``, and what is left to pay. The window is inclusive and counted in calendar days from the invoice date, so under 2/10 the tenth day after the invoice still earns the discount. """ if invoice_total.minor < 0: raise ValueError("invoice total must not be negative") # Validates the terms, including a discount window that outlasts the due date. due_date = payment_due_date(invoice_date, terms) # Validates the payment date; ISO dates then compare as strings. add_days(payment_date, 0) offered = terms.discount_basis_points > 0 deadline = add_days(invoice_date, terms.discount_days) if offered else None available = deadline is not None and payment_date <= deadline discount = ( apply_rate(invoice_total, terms.discount_basis_points, "half-up") if available else money(0, invoice_total.currency) ) return EarlyPaymentDiscount( available=available, discount=discount, amount_to_pay=subtract_money(invoice_total, discount), discount_deadline=deadline, due_date=due_date, )