Invoices
The billing document for an order. It is what turns expected revenue into realised revenue, and it carries the shipping and import-duty costs that rules book against.
An invoice bills a sales order. It arrives from NetSuite after the order does, and carries posting revenue used by the standard GL rules. Other realised impacts can already exist, such as payment fees, before the invoice arrives. Expected margin prices a separate synthetic invoice without waiting for the real one.
Where it comes from
app/jobs/netsuite/invoices.php consumes the ns_invoices batch type, one batch per date, and writes the header and line tables. Orders placed before ingest.modernera were loaded from verkoopdump instead: that import writes invoices as well as the orders and fulfillments beside them.
An invoice points back at its order through createdfrom, so classification waits for the order to exist. classify/invoices.php calls getblocked(array('ns_salesorders')), which ends the run while any sales order batch is still queued, rather than classifying an invoice whose order has not arrived.
Tables
| Table | Holds |
|---|---|
invoices | The header |
invoicelines | Item lines |
invoicelinesnoninv | Non-inventory lines: shipping, engraving, discounts, fees |
invoicediscount, invoicetax | Discounts and taxes |
invoiceclassifications | Header flags |
invoicelineclassifications | Line flags |
What classification records
The header classification carries the order it bills, its status and sales channel, the Paazl shipping option, the in-VAT and ex-VAT amount buckets, and the flags that describe the shape of the invoice: multiline, linesinvonly, linesnoshipping, privatelabel, engraving, engravingsimple, aftikker, discountorder, plus daysaftersalesorder and daysafterpromised.
Bundles are unpacked at classification time. A PRODUCTBUNDLE non-inventory line carries the bundle price while the SKU lines beside it carry the products. The classifier spreads the bundle amount across those SKU lines in proportion to their cost estimate and marks the line manipulated, so the same invoice is never spread twice. See classifications.
What it contributes to margin
More calculation rules target the invoice than any other transaction type. Rule filters read the invoice header and its lines, and the impacts land in the account group the rule's account belongs to. Shipping cost per shipper and country is seeded from shipping.csv; import duty per sales channel is one of the rules in rules.json.
calculate/invoices.php rebuilds a whole day at a time: it deletes that date's invoice impacts from all four contribution groups, recalculates them, and re-queues each touched order into cm_impact_salesorders. Recalculating a day is therefore a clean rebuild of that day for invoices, not an addition on top of what was there.
Where to look, and where to change
| What | Where |
|---|---|
| The invoices themselves | /transactions/invoices |
| One invoice in the context of its order | /transactions/salesorderdetail, under related records |
| The impacts rules booked on it | /contributionmargin/impact |
| The rules that book them | /contributionmargin/calculationrules |
Nothing about an invoice is editable in CCC. It is upstream data: correct it in NetSuite and let the import bring the correction through.
Related
Evidence
Import and blocking from app/jobs/netsuite/invoices.php and app/jobs/classify/invoices.php. Bundle spreading from the same classify job and app/models/classify.php. Table family from the CREATE TABLE statements in app/jobs/install/install.php. Day rebuild and re-queueing from app/jobs/calculate/invoices.php. Columns from templates/main/transactions/invoices.tpl. Rule seeds from config/seed/cm_calculationrules/. Basis: code-checked against the current implementation.
- Type
object - Status
active - Updated
2026-09-14 - Created
2026-09-13 - Covers
transactions/invoices