Functional Weave
Code in Rust

finance.aged-debt@1.0.0

impl/python.py

2,252 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, Optional, Sequence

from .dates_days_between import days_between  ← from dates.days-between ^1.0.0 · built alongside by fune
from .finance_aged_debt_types import AgedDebt, AgedDebtBucket, OpenInvoice
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


def aged_debt(invoices: Sequence[OpenInvoice], as_at: str, bucket_days: Sequence[int], currency: str) -> AgedDebt:
    """Bucket open invoices by how many days past their due date they are.

    Age runs from the due date, not the invoice date: an invoice due today is
    current. Every bucket is returned, empty or not, so a report's columns are
    fixed.
    """
    if len(bucket_days) == 0:
        raise ValueError("bucketDays needs at least one boundary")
    previous = 0
    for bound in bucket_days:
        if isinstance(bound, bool) or not isinstance(bound, int) or bound <= previous:
            raise ValueError(
                "bucketDays must be whole days, ascending, from 1: received %s after %s" % (bound, previous)
            )
        previous = bound

    zero = money(0, currency)
    labels: List[str] = ["current"]
    froms: List[Optional[int]] = [None]
    tos: List[Optional[int]] = [0]
    start = 1
    for bound in bucket_days:
        labels.append("%d-%d" % (start, bound))
        froms.append(start)
        tos.append(bound)
        start = bound + 1
    labels.append("%d+" % (start - 1,))
    froms.append(start)
    tos.append(None)

    totals: List[Money] = [zero for _ in labels]
    refs: List[List[str]] = [[] for _ in labels]
    total = zero
    for invoice in invoices:
        overdue = days_between(invoice.due_date, as_at)
        index = 0
        if overdue > 0:
            index = len(bucket_days) + 1
            for i, bound in enumerate(bucket_days):
                if overdue <= bound:
                    index = i + 1
                    break
        totals[index] = add_money(totals[index], invoice.outstanding)
        refs[index].append(invoice.reference)
        total = add_money(total, invoice.outstanding)

    buckets = [
        AgedDebtBucket(label=labels[i], from_days=froms[i], to_days=tos[i], total=totals[i], references=refs[i])
        for i in range(len(labels))
    ]
    return AgedDebt(as_at=as_at, buckets=buckets, total=total)