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)
| tool | args | result |
|---|---|---|
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.