Sale Service
@nx/sale owns the cart-and-order lifecycle: a single SaleOrder plays both roles (DRAFT cart + committed order). It receives MQ-Pay webhooks for payment events, coordinates kitchen ticket flow, manages dining allocations and reservations, and emits Kafka topics + WebSocket events to inventory, finance, and downstream UIs.
1. Quick Reference
| Property | Value |
|---|---|
| Package | @nx/sale |
| Code | SVC-00030-SALE |
| Type | Microservice |
| Runtime | Bun |
| Base Class | VerifierApplication |
| Location | packages/sale |
| Base Path | /v1/api/sale |
| Dev Port | 31030 |
| Container Port | 3000 (external 31030) |
| Snowflake ID | 3 |
| DB Schema | sale (multi-table — see Domain Model) |
| Binding Namespace | @nx/sale |
2. Purpose & Scope
| Included | Excluded |
|---|---|
Single SaleOrder entity for cart + order (DRAFT → … → COMPLETED / CANCELLED) | Stock mutation (delegated to @nx/inventory) |
Item modes: PRODUCT (auto-merge) and CUSTOM (always new line) | Payment provider integration (delegated to @nx/mq-pay) |
Checkout pricing via @nx/pricing HTTP call | Tax calculation engine |
| Payment webhook processing (MQ-Pay HTTP webhooks) | Receipt printing (frontend concern) |
Bill splitting: SaleCheck (independently-payable groups) | Loyalty rewards engine |
| Order merge / split (with reversibility) | Customer-facing menus |
| Kitchen ticket lifecycle (FOH/BOH) — void, rush, ready, served | Recipe / BOM data (owned by @nx/inventory) |
| Allocation usage (DINE_IN tables, zones, units) | |
| Reservations (booking-then-arrival) | |
| POS shift / session management | |
| Customer point award (loyalty) on payment success | |
| Sales reports (daily summary, by product/category, purchase summary) | |
| Real-time WebSocket events (multi-room fanout) |
3. Tech Stack
External:
| Library | Purpose |
|---|---|
@venizia/ignis | IoC container, DI, BaseService, BaseController, ControllerFactory |
@venizia/ignis-helpers | Logger, KafkaProducerHelper, Redis helper |
@platformatic/kafka | Kafka client (sale uses producer-only with helper defaults — no explicit idempotent/lz4 config) |
hono | HTTP server (via IGNIS) |
@hono/zod-openapi | OpenAPI generator from Zod schemas |
@scalar/hono-api-reference | OpenAPI explorer at /doc |
drizzle-orm | DB access via PostgresCoreDataSource |
pg | PostgreSQL driver |
lodash | Utilities |
Internal:
| Package | Purpose |
|---|---|
@nx/core | Schemas, repositories (re-exported), VerifierApplication, KafkaTopics, SaleOrderStatuses, SaleOrderItemModes, SaleConstraints |
@nx/pricing | HTTP-only — PricingNetworkService calls pricing service for dynamic prices on checkout |
4. Project Structure
packages/sale/
├── src/
│ ├── application.ts # VerifierApplication subclass
│ ├── index.ts # bootstrapApplication()
│ ├── migrate.ts # bootstrapMigration()
│ ├── common/
│ │ ├── constants.ts # SaleConstraints, ApplicationRoles
│ │ ├── keys.ts # BindingKeys
│ │ ├── rest-paths.ts # 13 RestPaths
│ │ ├── webhook-types.ts # PaymentWebhookEventTypes + zod schemas
│ │ ├── websocket.ts # 7 WebSocket topics + room helpers
│ │ ├── sale-check.constants.ts # check splitting helpers
│ │ ├── shift.types.ts # shift session types
│ │ └── response.helper.ts # response shaping
│ ├── components/
│ │ ├── kafka/ # ApplicationKafkaComponent (producer-only)
│ │ └── websocket/ # ApplicationWebSocketComponent
│ ├── controllers/ # 13 controller folders → 16 registered controller classes
│ ├── datasources/ # PostgresCoreDataSource
│ ├── migrations/processes/ # 2 migrations (permissions, pos-session-report)
│ ├── models/ # zod request/response schemas
│ ├── repositories/ # re-exports from @nx/core
│ └── services/ # 20 services
├── package.json
└── tsconfig.json5. Architecture
Detail: see Architecture.
6. Domain Snapshot
Full ERD + per-entity tables: see Domain Model.
7. Surface Summary
REST controllers — full reference rendered live from /v1/api/sale/doc/openapi.json (live spec — Scalar viewer at /doc, gateway portal):
| Controller | Base path | Notes |
|---|---|---|
SaleOrderController | /sale-orders | + lifecycle (draft, items, checkout, revert, cancel, merge, split) |
SaleOrderItemController | /sale-order-items | CRUD + override for quantity validation |
SaleCheckController | /sale-checks | + split, split-equal, merge, rollback |
SaleOrderCheckController | /sale-orders/:id/checks | nested under order |
KitchenStationController | /kitchen-stations | CRUD |
KitchenTicketController | /kitchen-tickets | CRUD |
KitchenTicketSaleOrderController | /sale-orders/:id/kitchen-tickets/... | sendToKitchen |
KitchenTicketActionController | /kitchen-tickets/:id/... | void / rush / mark-ready / mark-served |
KitchenTicketItemActionController | /kitchen-ticket-items/:id/... | start-cooking / mark-ready / mark-served / void |
AllocationUsageController | /allocation-usages | CRUD + batch complete/cancel |
ReservationController | /reservations | + create with allocation |
ShiftController | /pos/sessions | + open / close |
CustomerController | /customers | CRUD |
PointTransactionController | /point-transactions | CRUD (mostly read) |
SalesReportController | /reports/sales | Daily summary, by-product, by-category, purchase-summary |
PaymentWebhookController | /webhooks/payment | MQ-Pay event ingestion (no auth) |
Async surface — full reference in API Events:
| Direction | Channel | Count |
|---|---|---|
| Inbound | HTTP webhook | 1 endpoint × 6 event types |
| Outbound | Kafka | 2 (PAYMENT_SUCCESS, KITCHEN_TICKET_ITEM_STATUS_CHANGED) |
| Outbound | WebSocket | 7 topics × multiple room helpers (merchant/order/check/kitchen/allocation/reservation) |
8. Components
| Component | File | Purpose |
|---|---|---|
ApplicationKafkaComponent | src/components/kafka/component.ts | Idempotent producer; emits PAYMENT_SUCCESS + KITCHEN_TICKET_ITEM_STATUS_CHANGED (no consumer) |
ApplicationWebSocketComponent | src/components/websocket/component.ts | WebSocket emitter via signal service; room fanout |
| Redis cache (optional) | bound at BindingKeys.APPLICATION_REDIS_CACHE | Authorization permission cache |
9. Services
| Service | File | One-liner |
|---|---|---|
SaleOrderService | sale.service.ts | DRAFT order CRUD, add/clear items, cancel, archive |
SaleOrderItemService | sale-order-item.service.ts | Batch item updates with row-level locks + summary recalc |
CheckoutService | checkout.service.ts | DRAFT → PROCESSING; pricing v1+v2; revert |
SaleCheckService | sale-check.service.ts | Bill split: split, split-equal, merge, rollback |
OrderMergeService | order-merge.service.ts | Merge multiple draft orders; reversible |
OrderSplitService | order-split.service.ts | Split one order into multiple; redistributes items + allocations + pricing |
KitchenTicketService | kitchen-ticket.service.ts | Send-to-kitchen, void, rush, mark ready/served |
KitchenTicketItemService | kitchen-ticket-item.service.ts | Per-line item status transitions + Kafka emit |
AllocationUsageService | allocation-usage.service.ts | DINE_IN/TAKEAWAY/DELIVERY; reserved → completed/cancelled |
ReservationService | reservation.service.ts | Booking flow with allocation reservation |
ShiftService | shift.service.ts | POS session: getCurrent, list, open, close, validate |
CustomerService | customer.service.ts | Customer CRUD |
CustomerPointService | customer-point.service.ts | Award loyalty points on payment success (idempotent) |
PaymentWebhookService | payment-webhook.service.ts | Webhook event dispatcher (routes to SaleOrder vs SaleCheck handler) |
SaleOrderPaymentWebhookService | sale-order-payment-webhook.service.ts | Handle attempt + transaction events for SaleOrder; emits Kafka |
SaleCheckPaymentWebhookService | sale-check-payment-webhook.service.ts | Same flow for SaleCheck |
PricingNetworkService | pricing-network.service.ts | HTTP client to @nx/pricing for calculate() + calculateV2() |
AllocationSnapshotService | allocation-snapshot.service.ts | Snapshot allocation unit/zone state on order events for audit |
ProductVariantSnapshotService | product-variant-snapshot.service.ts | Snapshot product variant metadata at order-item add time |
SalesReportService | sales-report.service.ts | Daily / product / category / purchase summary queries |
10. Repositories
| Repository | Source | Notes |
|---|---|---|
SaleOrderRepository | @nx/core | + updateSummaryFromItems, getDailySummary, getProductSales, getCategorySales |
SaleOrderItemRepository | @nx/core | + updateAll, deleteAll |
SaleCheckRepository | @nx/core | + recalculateTotals |
SaleCheckItemRepository | @nx/core | + createAll |
KitchenTicketRepository | @nx/core | + findByIdempotencyKey, getNextSequence, evaluateTicketAutoProgression |
KitchenTicketItemRepository | @nx/core | + createAll |
KitchenStationRepository | @nx/core | — |
AllocationUsageRepository | @nx/core | + existsBySaleOrderId |
AllocationUnitRepository / AllocationZoneRepository | @nx/core | — |
ReservationRepository | @nx/core | — |
PosSessionRepository | @nx/core | + findOpenByDeviceId |
PosSessionReportRepository | @nx/core | — |
PointTransactionRepository | @nx/core | + existsBySaleOrderId |
CustomerRepository | @nx/core | — |
SaleChannelRepository | @nx/core | — |
ProductVariantRepository, ProductIdentifierRepository, ProductInfoRepository | @nx/core | Cross-package read |
ConfigurationRepository | @nx/core | Read encrypted payment configs |
FinanceTransactionRepository, FinanceWalletRepository | @nx/core | Cross-package read for checkout finance metadata |
PurchaseOrderRepository | @nx/core | Cross-package read for sales report |
MetaLinkRepository, SettingRepository, DeviceRepository | @nx/core | Cross-package |
11. Entry Points
| File | Purpose |
|---|---|
src/index.ts | Service entry → bootstrapApplication() |
src/migrate.ts | Migration entry → bootstrapMigration() |
src/application.ts | Application extends VerifierApplication |
12. Configuration
Env vars + seeded data: see Configuration.
13. Operations
Deployment + observability + security + runbook: see Operations.
14. Related Pages
Concepts — why/how:
- Architecture
- Domain Model
- Integration — MQ-Pay webhook + inventory/finance/commerce/identity contracts
Reference — lookup:
- API Events
- Configuration
- Operations
- REST endpoints — live OpenAPI at
/v1/api/sale/doc/openapi.json(live spec — Scalar viewer at/doc, gateway portal)
Features — deep dives:
- Sale Order
- Checkout
- Order Operations — merge / split
- Check Splitting
- Kitchen Orders
- Payment Webhooks
- Allocation Usage
- Reservation
- Shift / POS Session
- Sales Report
- Customer Points
Decisions: