Functional Weave
Code in Python

finance.aged-debt@1.0.0

impl/typescript.ts

2,211 bytes · the TypeScript 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.

import { daysBetween } from "./dates_days_between.ts";  ← from dates.days-between ^1.0.0 · built alongside by fune
import { addMoney } from "./money_add.ts";  ← from money.add ^1.0.0 · built alongside by fune
import { money } from "./money_amount.ts";  ← from money.amount ^1.0.0 · built alongside by fune
import { type AgedDebt, type AgedDebtBucket, type OpenInvoice } from "./finance_aged_debt_types.ts";

/**
 * 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.
 */
export function agedDebt(
  invoices: readonly OpenInvoice[],
  asAt: string,
  bucketDays: readonly number[],
  currency: string,
): AgedDebt {
  if (bucketDays.length === 0) {
    throw new RangeError("bucketDays needs at least one boundary");
  }
  let previous = 0;
  for (const bound of bucketDays) {
    if (!Number.isInteger(bound) || bound <= previous) {
      throw new RangeError(`bucketDays must be whole days, ascending, from 1: received ${bound} after ${previous}`);
    }
    previous = bound;
  }

  const zero = money(0, currency);
  const buckets: { label: string; fromDays: number | null; toDays: number | null; total: typeof zero; references: string[] }[] = [
    { label: "current", fromDays: null, toDays: 0, total: zero, references: [] },
  ];
  let from = 1;
  for (const bound of bucketDays) {
    buckets.push({ label: `${from}-${bound}`, fromDays: from, toDays: bound, total: zero, references: [] });
    from = bound + 1;
  }
  buckets.push({ label: `${from - 1}+`, fromDays: from, toDays: null, total: zero, references: [] });

  let total = zero;
  for (const invoice of invoices) {
    const overdue = daysBetween(invoice.dueDate, asAt);
    let index = 0;
    if (overdue > 0) {
      index = bucketDays.length + 1;
      for (let i = 0; i < bucketDays.length; i += 1) {
        if (overdue <= bucketDays[i]) {
          index = i + 1;
          break;
        }
      }
    }
    const bucket = buckets[index];
    bucket.total = addMoney(bucket.total, invoice.outstanding);
    bucket.references.push(invoice.reference);
    total = addMoney(total, invoice.outstanding);
  }

  const result: AgedDebtBucket[] = buckets.map((b) => ({ ...b }));
  return { asAt, buckets: result, total };
}