SalesOrders
Sales order header + items + timeline notes + order channels (SO-only org-configurable lookup). FG ledger entries (DEMAND / ALLOCATE / CONSUME) are written by FinishedGoodsInventory via L3. Document attachments live in Documents.
Tables owned
| Table | Purpose |
|---|---|
org.sales_orders | SO header (number, customer, channel, fulfillment location, costs, MABD, status enum, fulfillment status enum, QBO ID, notes). |
org.so_items | Line items (sku, quantity, unit price). |
org.so_timeline_notes | Append-only notes attached to an SO with status-at-time-of-note. |
org.order_channels | Org-configurable channel lookup (e.g., Direct, Distributor) with display color. |
Operations
Writes — Header
createSo(conn, input)→SalesOrder.input:{ number, customerId, channelId, fulfillmentLocationId, costs?, mabd?, status, fulfillmentStatus, qboId?, notes? }. Status enums:order_status,fulfillment_status.updateSo(conn, soId, patch)→SalesOrder. Partial update including status transitions.deleteSo(conn, soId)→void. Cascades to items, notes. Documents are detached by L3 (which callsDocuments.deletefor each).
Writes — Items
addItem(conn, soId, item)→SoItem.item:{ skuId, quantity, unitPrice }.updateItem(conn, itemId, patch)→SoItem. Edits quantity / unit price / SKU.removeItem(conn, itemId)→void.
Writes — Timeline notes
addTimelineNote(conn, soId, input)→SoTimelineNote.input:{ body, statusAtTime? }.statusAtTimeis captured by L3 from the SO's current status if not supplied.deleteTimelineNote(conn, noteId)→void.
Writes — Order channels
createChannel(conn, { name, color? })→OrderChannel.updateChannel(conn, channelId, patch)→OrderChannel. Rename / recolor.deleteChannel(conn, channelId)→void. L3 enforces "no SO uses it".
Reads — Header
listSos(conn, filters?)→SalesOrder[]. Filters:{ status?, fulfillmentStatus?, customerId?, channelId?, fulfillmentLocationId?, dateFrom?, dateTo? }.getSoById(conn, soId)→SalesOrder | null.getSosByIds(conn, soIds)→SalesOrder[].getSoByNumber(conn, number)→SalesOrder | null.
Reads — Items
listItemsBySo(conn, soId)→SoItem[].listItemsBySos(conn, soIds)→Map<soId, SoItem[]>.getItemById(conn, itemId)→SoItem | null.
Reads — Timeline notes
listTimelineNotesBySo(conn, soId)→SoTimelineNote[]. Ordered by created_at desc.
Reads — Order channels
listChannels(conn)→OrderChannel[].getChannelById(conn, channelId)→OrderChannel | null.getChannelsByIds(conn, channelIds)→OrderChannel[]. Batch hydration.isChannelInUse(conn, channelId)→{ inUse: boolean, soCount: number }. Gates deletion.
Notes
- Document attachments on an SO go through
Documents(entity_type ='sales_order'). L3 composes by callingSalesOrders.getSoById+Documents.listByEntity('sales_order', soId). - Computed fulfillment status on the detail view (from the FG ledger) is an L3 derivation. L2 returns the stored enum value only.
- "Bulk import SOs" is an L3 workflow.