Functional Weave
Code in TypeScript

finance.credit-note.calculate@1.0.0

impl/typescript.ts

2,790 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 { calculateInvoice } from "./finance_invoice_calculate.ts";  ← from finance.invoice.calculate ^1.0.0 · built alongside by fune
import { type Invoice, type InvoiceLine } from "./finance_invoice_calculate_types.ts";
import { lineTotal } from "./finance_invoice_line_total.ts";  ← from finance.invoice.line-total ^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 CreditRequest } from "./finance_credit_note_calculate_types.ts";

/**
 * A credit note against an invoice, with VAT at the original tax point.
 *
 * Every credit becomes a negative invoice line and the whole note is
 * calculated by finance.invoice.calculate at the original date, so the VAT
 * rates, rounding and grouping are the invoice's own.
 */
export function calculateCreditNote(
  originalLines: readonly InvoiceLine[],
  credits: readonly CreditRequest[],
  jurisdiction: string,
  originalTaxPoint: string,
): Invoice {
  if (credits.length === 0) {
    throw new RangeError("a credit note needs at least one line");
  }
  const credited = originalLines.map(() => 0);
  const lines: InvoiceLine[] = credits.map((credit) => {
    const n = credit.line;
    if (!Number.isInteger(n) || n < 1 || n > originalLines.length) {
      throw new RangeError(`credit refers to line ${n}, but the invoice has ${originalLines.length} lines`);
    }
    const original = originalLines[n - 1];
    if (original.quantity <= 0) {
      throw new RangeError(`line ${n} of the invoice is not a sale that can be credited`);
    }
    if ((credit.quantity === null) === (credit.net === null)) {
      throw new RangeError(`credit for line ${n} needs exactly one of quantity and net`);
    }
    let line: InvoiceLine;
    if (credit.quantity !== null) {
      if (!Number.isInteger(credit.quantity) || credit.quantity <= 0) {
        throw new RangeError(`credit quantity for line ${n} must be greater than zero`);
      }
      line = { ...original, quantity: -credit.quantity };
    } else {
      const net = credit.net!;
      if (net.currency !== original.unitPrice.currency) {
        throw new RangeError(`credit for line ${n} must be in ${original.unitPrice.currency}`);
      }
      if (net.minor <= 0) {
        throw new RangeError(`credit amount for line ${n} must be greater than zero`);
      }
      line = {
        description: original.description,
        unitPrice: money(-net.minor, net.currency),
        quantity: 1,
        discountBasisPoints: 0,
        taxCategory: original.taxCategory,
      };
    }
    credited[n - 1] -= lineTotal(line.unitPrice, line.quantity, line.discountBasisPoints).minor;
    if (credited[n - 1] > lineTotal(original.unitPrice, original.quantity, original.discountBasisPoints).minor) {
      throw new RangeError(`credits against line ${n} exceed what it was invoiced for`);
    }
    return line;
  });
  return calculateInvoice(lines, jurisdiction, originalTaxPoint);
}