Docs
/
adapter system

invoice

Invoice capability pack: build a full invoice object from line items with correct per-line and global tax/discount math, compute just the totals, roll invoice numbers ('INV-0006' → 'INV-0007'), apply...

invoice (adapter system)

Billing capability pack — Studio-aligned, monetizable CRM tools. Pure JS, zero deps. The money-true core for quoting and billing: build invoices from line items, compute totals, roll invoice numbers, apply late fees, and render a printable invoice.

Careful money math: all arithmetic runs in integer cents internally, then converts back to 2-decimal dollars on output — so totals never drift (0.1 + 0.2 ≠ 0.3 can't bite here) even across many line items. Per-line math (qty × unit → line discount → line tax) happens first; global tax/discount then fill in for lines that didn't specify their own, and a global discount is distributed proportionally so per-line tax stays fair.

Tools (adapters)

toolargsresult
build{ lineItems:[{description,quantity,unitPrice,taxRate?,discount?}], currency='USD', taxRate?, discount?, notes?, from?, to?, invoiceNumber? }full invoice { invoiceNumber, currency, lineItems:[{...,lineTotal}], subtotal, discountTotal, taxTotal, total, ... }
nextInvoiceNumber{ last, prefix?, pad=4 }{ next, number, prefix } — e.g. 'INV-0006''INV-0007'
totals{ lineItems, taxRate?, discount? }{ subtotal, discountTotal, taxTotal, total }
applyLateFee{ total, daysLate, dailyRate }{ originalTotal, lateFee, total }
toHtml{ invoice }{ html } — a clean printable HTML invoice string
summary{ invoice }{ summary } — one-line text summary

Discount forms: a bare number < 1 is a percent fraction (0.1 = 10%); a number ≥ 1 is a flat cash amount; or pass { type: 'percent'|'flat', value } explicitly. taxRate is a decimal (0.08 = 8%). A per-line taxRate/discount overrides the global one for that line.

Usage

const m = await import('./index.js');
const invoice = m.default.adapters;

const inv = invoice.build({
  lineItems: [
    { description: 'Design', quantity: 10, unitPrice: 100 },
    { description: 'Hosting', quantity: 1, unitPrice: 50 },
  ],
  taxRate: 0.1,
});
// inv.subtotal = 1050, inv.taxTotal = 105, inv.total = 1155

invoice.nextInvoiceNumber({ last: 'INV-0006' }); // → { next: 'INV-0007', ... }
invoice.toHtml({ invoice: inv });                // → { html: '<div class="invoice">…' }

Rendering deferred to a-templates

toHtml is a pure, dependency-free template literal — good enough to print or email. For richer, branded, themeable rendering (logos, custom layouts, PDF pipelines) defer to the a-templates system rather than growing this pack. invoice stays the money-true data core; a-templates owns the presentation layer.

CRM fit

build/totals are the billing core behind quoting and invoicing surfaces in Studio/Web; nextInvoiceNumber and applyLateFee handle the lifecycle. Gate/meter via @leumas/auth like any other adapter.

DRY boundary

finance owns generic money math (loans, TVM, currency formatting, ROI). invoice owns document-shaped billing (line items, totals, numbering, HTML). No overlap.

Source shared/engines/adapters/domain/invoice/README.md (no-git)markdownjson
Generated from the Leumas repository. Every page cites the file it came from.leumas.techllms.txt