Skip to content

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

PropertyValue
Package@nx/sale
CodeSVC-00030-SALE
TypeMicroservice
RuntimeBun
Base ClassVerifierApplication
Locationpackages/sale
Base Path/v1/api/sale
Dev Port31030
Container Port3000 (external 31030)
Snowflake ID3
DB Schemasale (multi-table — see Domain Model)
Binding Namespace@nx/sale

2. Purpose & Scope

IncludedExcluded
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 callTax 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, servedRecipe / 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:

LibraryPurpose
@venizia/ignisIoC container, DI, BaseService, BaseController, ControllerFactory
@venizia/ignis-helpersLogger, KafkaProducerHelper, Redis helper
@platformatic/kafkaKafka client (sale uses producer-only with helper defaults — no explicit idempotent/lz4 config)
honoHTTP server (via IGNIS)
@hono/zod-openapiOpenAPI generator from Zod schemas
@scalar/hono-api-referenceOpenAPI explorer at /doc
drizzle-ormDB access via PostgresCoreDataSource
pgPostgreSQL driver
lodashUtilities

Internal:

PackagePurpose
@nx/coreSchemas, repositories (re-exported), VerifierApplication, KafkaTopics, SaleOrderStatuses, SaleOrderItemModes, SaleConstraints
@nx/pricingHTTP-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.json

5. 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):

ControllerBase pathNotes
SaleOrderController/sale-orders+ lifecycle (draft, items, checkout, revert, cancel, merge, split)
SaleOrderItemController/sale-order-itemsCRUD + override for quantity validation
SaleCheckController/sale-checks+ split, split-equal, merge, rollback
SaleOrderCheckController/sale-orders/:id/checksnested under order
KitchenStationController/kitchen-stationsCRUD
KitchenTicketController/kitchen-ticketsCRUD
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-usagesCRUD + batch complete/cancel
ReservationController/reservations+ create with allocation
ShiftController/pos/sessions+ open / close
CustomerController/customersCRUD
PointTransactionController/point-transactionsCRUD (mostly read)
SalesReportController/reports/salesDaily summary, by-product, by-category, purchase-summary
PaymentWebhookController/webhooks/paymentMQ-Pay event ingestion (no auth)

Async surface — full reference in API Events:

DirectionChannelCount
InboundHTTP webhook1 endpoint × 6 event types
OutboundKafka2 (PAYMENT_SUCCESS, KITCHEN_TICKET_ITEM_STATUS_CHANGED)
OutboundWebSocket7 topics × multiple room helpers (merchant/order/check/kitchen/allocation/reservation)

8. Components

ComponentFilePurpose
ApplicationKafkaComponentsrc/components/kafka/component.tsIdempotent producer; emits PAYMENT_SUCCESS + KITCHEN_TICKET_ITEM_STATUS_CHANGED (no consumer)
ApplicationWebSocketComponentsrc/components/websocket/component.tsWebSocket emitter via signal service; room fanout
Redis cache (optional)bound at BindingKeys.APPLICATION_REDIS_CACHEAuthorization permission cache

9. Services

ServiceFileOne-liner
SaleOrderServicesale.service.tsDRAFT order CRUD, add/clear items, cancel, archive
SaleOrderItemServicesale-order-item.service.tsBatch item updates with row-level locks + summary recalc
CheckoutServicecheckout.service.tsDRAFT → PROCESSING; pricing v1+v2; revert
SaleCheckServicesale-check.service.tsBill split: split, split-equal, merge, rollback
OrderMergeServiceorder-merge.service.tsMerge multiple draft orders; reversible
OrderSplitServiceorder-split.service.tsSplit one order into multiple; redistributes items + allocations + pricing
KitchenTicketServicekitchen-ticket.service.tsSend-to-kitchen, void, rush, mark ready/served
KitchenTicketItemServicekitchen-ticket-item.service.tsPer-line item status transitions + Kafka emit
AllocationUsageServiceallocation-usage.service.tsDINE_IN/TAKEAWAY/DELIVERY; reserved → completed/cancelled
ReservationServicereservation.service.tsBooking flow with allocation reservation
ShiftServiceshift.service.tsPOS session: getCurrent, list, open, close, validate
CustomerServicecustomer.service.tsCustomer CRUD
CustomerPointServicecustomer-point.service.tsAward loyalty points on payment success (idempotent)
PaymentWebhookServicepayment-webhook.service.tsWebhook event dispatcher (routes to SaleOrder vs SaleCheck handler)
SaleOrderPaymentWebhookServicesale-order-payment-webhook.service.tsHandle attempt + transaction events for SaleOrder; emits Kafka
SaleCheckPaymentWebhookServicesale-check-payment-webhook.service.tsSame flow for SaleCheck
PricingNetworkServicepricing-network.service.tsHTTP client to @nx/pricing for calculate() + calculateV2()
AllocationSnapshotServiceallocation-snapshot.service.tsSnapshot allocation unit/zone state on order events for audit
ProductVariantSnapshotServiceproduct-variant-snapshot.service.tsSnapshot product variant metadata at order-item add time
SalesReportServicesales-report.service.tsDaily / product / category / purchase summary queries

10. Repositories

RepositorySourceNotes
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/coreCross-package read
ConfigurationRepository@nx/coreRead encrypted payment configs
FinanceTransactionRepository, FinanceWalletRepository@nx/coreCross-package read for checkout finance metadata
PurchaseOrderRepository@nx/coreCross-package read for sales report
MetaLinkRepository, SettingRepository, DeviceRepository@nx/coreCross-package

11. Entry Points

FilePurpose
src/index.tsService entry → bootstrapApplication()
src/migrate.tsMigration entry → bootstrapMigration()
src/application.tsApplication extends VerifierApplication

12. Configuration

Env vars + seeded data: see Configuration.

13. Operations

Deployment + observability + security + runbook: see Operations.

Concepts — why/how:

Reference — lookup:

Features — deep dives:

Decisions:

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.