3PL billing primer
The billing chain — charge items → rate sheets → merchant billing rules → statements — and the current gaps (manual quantities, no statement UI yet).
The billing chain#
Charge items → rate sheets → merchant billing rules → statement/invoice generation (wms_merchant_invoices + line items). All of it is desktop-only — there is no billing on the phone.
Charge items#
WMS → Billing → Charge items. Create: code (unique per organization) / name / category (8: storage, inbound, outbound, pick, pack, value_added, freight, other) / pricing_method (6: per_pallet_day, per_unit, per_order, per_weight, per_cubic_ft, flat) / unit_price / currency (default USD) / uom / is_active.
Rate sheets#
- WMS → Billing → Rate sheet: name + merchant_id (NULL = global, applies to all merchants) + version + effective_date/expiry_date + is_active. Multiple versions are supported.
- Detail lines each bind one charge item; unit_price_override empty = use the item’s original price. A line may also set its own effective/expiry (empty = follows the sheet).
Merchant billing rules#
WMS → Billing → Rules: one rule per merchant (unique per organization×merchant), pointing at a rate_sheet_id. Changing versions = changing the rate_sheet_id.
Generating a statement#
Call POST /api/wms/billing/invoices/generate — there is currently no desktop generation page, so statements are generated via API.
- Body: merchant_id, period_from, period_to (YYYY-MM-DD), optional rate_sheet_id, quantities (usage, passed in manually), notes.
- Rate sheet resolution: an explicitly passed one wins; otherwise the merchant’s active billing rule is used. No rule → 400.
- Duplicate protection: a statement for the same period already existing is rejected (unique on organization×merchant×period).
- Line-by-line math (recomputable): unit_price = override ?? item price; qty from quantities (flat pricing with no qty defaults to 1; zero-qty lines are not saved); amount = round(qty × unit_price, 2). Each line carries its formula (qty × price = amount) so it can be checked by hand.
- Numbering: MINV-000001 and up (atomic numbering); the header is saved with status=draft plus a rate-sheet snapshot (rate_sheet_id+name+version), then the lines.
- Known gaps in the current version: a generated statement stops at draft — the sent/paid transition API is not implemented; there is no desktop list/detail/generate page for statements (the GET list/detail APIs exist and can be called); statements have no link to accounting AR (acc_invoices) — the statement→AR posting/reconciliation chain is not implemented; currency is carried, not converted (Phase 1).