Invoice with InvoiceLine rows (the #3039 flow: InvoiceEditor, invoiceForm.ts, db/invoices.ts). A line that bills something Soundcheck tracks elsewhere carries a source key in invoice_lines.source_key. The key is the only link between an invoice line and its origin. Do not add a join table from a feature table to invoices.
Key grammar
Addendum and trip ids are 1–64 of
A-Z a-z 0-9 _ -. Categories are 1–40 of a-z 0-9 _ -. A key is at most 160 characters.
invoiceSourceGroupOf(key) names the invoice a key is drafted onto: the balance and every addendum share balance, every category of a trip shares buyout:<trip id>, and the rest stand alone.
Guarantees
- One live invoice per source per gig. Creating or editing an invoice whose lines carry source keys takes the gig’s billing lock (
lockEventBilling) and refuses a key already on another non-void invoice with409 INVOICE_SOURCE_TAKEN. Voiding the invoice frees the key. - A key appears once per invoice. The parser rejects a second line with the same key.
- Unknown keys are rejected at the API boundary (
parseInvoiceFields) with 400. - Overtime keys must name a POSTED entry on the same gig.
- Sending, voiding, receipts and receivables are the ordinary invoice lifecycle. A source key changes none of them.
invoiceSource.ts.
API
Pure helpers,api-events/src/systems/invoices/invoiceSource.ts:
systems/billing-schedule/billingPlan.ts re-exports the shorthand builders addendumSourceKey, overtimeSourceKey, buyoutSourceKey, buyoutSourcePrefix and the DEPOSIT_1_SOURCE, DEPOSIT_2_SOURCE and BALANCE_SOURCE constants.
Reads, api-events/src/db/billingCharges.ts:
listInvoiceSourceLinks(eventId, { sourceKeyPrefix?, includeVoid? }, client?)returns one row per sourced line:source_key,invoice_line_id,invoice_id,invoice_number,invoice_status,currency,amount,ledger_item_id. Void invoices are left out unlessincludeVoidis set.listBillingCharges(eventId)returns the newest charge per key, preferring a non-void invoice, with itskindand due rule.GET /api/v1/events/:eventId/billingbuilds the schedule from it.
POST and PUT /api/v1/events/:eventId/invoices[/:invoiceId] accept lines[].source_key. Invoice reads return it on each line.
Trip buyouts
- Build the draft lines with
buildBuyoutInvoicePrefill(summary)(systems/billing-schedule/buyoutInvoiceAdapter.ts). It returnsreadywith one charge per priced category, each carryingbuyoutSourceKey(tripId, category), orincomplete/invalid. - Create the invoice on the Trip’s primary gig through the normal invoice create (
createInvoiceOperation, orPOST /api/v1/events/:eventId/invoices), withsource_keyon every line. A second live invoice for the same trip category gets409 INVOICE_SOURCE_TAKEN. - Find the trip’s invoices with
listInvoiceSourceLinks(primaryEventId, { sourceKeyPrefix: buyoutSourcePrefix(tripId) }). The trailing colon in the prefix keepstrip-1from matchingtrip-10.
amount with the trip’s current buyout for that category. The billing view has no trip summary, so it reports buyout charges with planned_cents: null and cannot flag the difference itself.