Subscription billing thinks in schedules: phases, billing day, renewal, seat minimums, usage true-ups at period end. Freight billing thinks in movements: a file arrives, rows rate against a carrier contract, invoices group by shipper and window.
Conflating the two is how finance teams end up with a "monthly freight schedule" that nobody on operations recognises — and invoices that do not match the carrier export.
What a shipment org optimises for
| Concept | Subscription mode | Shipment mode |
|---|---|---|
| Bill-to party | Customer catalog | Shipper catalog |
| Hauler | N/A | Carrier on the contract |
| Billing unit | Schedule period | Individual shipment (or grouped window) |
| Price source | List price / phases | Rated charges + rate indexes |
| Recognition | Term, usage, or point-in-time per product | Shipment date (net of rate lines) |
| Collections | Customer + schedule context | Shipper; no schedule tab |
There is no billing period on a shipment invoice in the subscription sense. Nothing accumulates across a month unless your contract frequency groups daily/weekly/monthly windows — and even then each haul is rated on its own attributes first, then rolled into a draft.
The shape of the process, end to end
The chain runs from catalog to cash: shippers and carriers exist as their own records before a single shipment is billed; a carrier contract carries the rated charges and the file-mapping rules for one hauler relationship; a carrier file comes in and gets rated and matched to those shippers; matched rows become draft shipments; and drafts get grouped and invoiced according to the contract's frequency. Each of those stages is a real, separate state a shipment sits in — draft, then created, then invoiced — and once a batch of shipments has been invoiced, that batch is closed. The next file starts its own run rather than reopening the last one.
Invoice grouping and document numbers
Billing frequency on the contract controls grouping:
- None — one invoice per shipment.
- Daily / weekly / monthly — group by contract + shipper + period window; invoice date is period end.
Document numbers: Sonic created grouped format SSS-CCC-ddmmyy-ddmmyy, or a mapped invoice-number column when exactly one shipment sits on the invoice.
Issued invoices are never rewritten. A sent window invoice does not gain a late haul line — the next haul needs its own draft or window.
Why this is not "subscription with extra steps"
Schedules carry phase logic, payment gates, seat proration, and amendment history tied to a single customer relationship over years. Shipments carry rate formulas tied to distance, fuel indexes, accessorials, and shipper identity on each row.
Sonic keeps Invoice.billing_engine as subscription | shipment. Branch on that everywhere — UI, collections, bank rec payer match, journal filters. Do not model freight as a special schedule with one fake product called "shipment."
Revenue and collections follow the haul
| Surface | Freight behaviour |
|---|---|
| Watchtower | Ages sent unpaid freight invoices; shipper is the account |
| Bank rec | Matches paid_by to shipper name and shipper contacts |
| Journal | Recognises on haul date; billing posts on invoice date; same-day merges when dates align |
Watch out for
- Unlinked shipper is not FAILED. Rows show SHIPPER NOT SELECTED until you confirm in Shippers — do not persist them.
- Deductions exceeding charges block invoice create. That is a data problem, not a credit-note workaround.
- Re-upload does not delete missing rows. Shipments absent from a new file stay as they were.
- Rate index gaps skip fuel — no silent Default fallback when the carrier is on a branch series.
- Contracts nav is carrier deals, not the subscription PDF contracts page.