Orders
The order aggregate is the OMS header: lines, status history, notes, tags, payments on the order, and imports. It lives in commerce, not in the warehouse app.
Responsibilities
- Persist an order and its lines for a tenant and channel.
- Enforce
VALID_ORDER_TRANSITIONSon status changes. - Write
OrderStatusHistory. - Enqueue outbox events in the same transaction as the write.
- Leave fulfillment creation and wallet debit to the worker.
Architecture
The HTTP response returns after the transaction commits, including the outbox insert. Fulfillment and the wallet debit are asynchronous.
Code map
| Piece | Path |
|---|---|
| State machine used by command | order/services/order-state-machine.ts |
| Duplicate state machine | order-status/domain/order-state-machine.ts |
| Fulfillment listener | order-fulfillment.listener.ts |
| Outbound sync listener | outbound-order-sync.listener.ts |
| Schema | 41-order.prisma |
Controllers: order and order-tag under the order feature. Notes are a separate feature (/commerce/order-note). History is /commerce/order-status-history. There is no separate cancel subsystem. Cancellation is a transition to CANCELLED.
Data model
Order, OrderItem, OrderStatusHistory, OrderNote. Status is OrderStatus. The database enum limits stored values. The TypeScript map limits transitions.
Channel.defaultFulfillmentMode defaults to OMS_MANAGED. The schema comment says Amazon AFN versus MFN is per order, and the channel default is a fallback. Whether AFN skips pick and pack was not found as a branch in this pass.
Business rules
See Business rules and State machine. Short version: the map is the spec, the file header comment is incomplete, and isValidOrderTransition(null, to) returns true.
API surface
Orders are under /v1/commerce/orders. Field-level contracts are OpenAPI. This page does not copy DTOs. Permission strings are whatever @RequirePermissions is on each handler. A handler without that decorator is allowed for any authenticated user.
Events and queues
| Event | When |
|---|---|
order.created | Create paths, including ingestion when items exist and the outbox writer is present |
order.confirmed | Confirm paths |
order.location.changed | Location changes |
order.status.changed | Status service and return command |
order.address.resolution_triggered | Order command |
stock.sync.requested | Some order-command paths |
Address validation can also enqueue validate-pending-order on address_validation_queue after ingestion.
Integrations
Marketplace pull creates orders through ingestion. Status push uses OutboundOrderSyncListener when the channel handler implements outbound and manageState is on. The listener’s exact guard was not line-read. Schema comment: manageState is the flag for pushing order status.
WhatsApp checkout can also create orders. That path is under automation, not MarketplaceRegistry.
Workflows
- Create order
- Update, hold, and cancel
- Bulk status
- What status does to fulfillment
- Partial payment
- Move warehouse
- Address resolution
- Status change
- Marketplace ingestion
Failure scenarios
- Throw inside the command transaction: order and outbox roll back together.
- Wallet listener throws after confirm: order stays committed, outbox retries the debit. See Wallet debit.
- Create idempotency key: not established. A client retry can create a second order unless a unique key is enforced in the service. That key was not confirmed.
Development
Change transitions in both state-machine files and the state machine page. Keep side effects on OutboxWriterService.enqueue(tx, ...).
Troubleshooting
If an order exists and no fulfillment order appears, check that the worker is running and that order.created is not DEAD. If the channel did not receive a status push, check manageState and whether the connector registered an outbound order service.