Point of Sale
POS configuration, orders, covers, and slots inside the Sales plugin, plus the sync API the standalone POS client uses.
Two services are involved: sales_api (this module, port 3305) stores POS records and orders, and posclient_api (port 3312) serves the offline-capable storefront app. For the storefront itself, see POS Client.
Module layout
Backend code lives under backend/plugins/sales_api/src/modules/pos/:
| Area | Path | Responsibility |
|---|---|---|
| Models | db/definitions/{pos,orders,covers}.ts, db/models | Pos, PosOrders, PosCovers, PosSlots, ProductGroups |
| GraphQL | graphql/schemas, graphql/resolvers | POS config, order, cover, product-group operations |
| HTTP routes | routes.ts (mounted from src/routes.ts) | /pos-init, /pos-sync-config, /get-pos-token |
| tRPC | trpc/pos.ts | pos.* and orders.* routers |
| Meta | meta/automations, meta/segments, meta/export | POS-order event trigger, sales:pos.orders segment type, POS-items export |
POS settings screens live in frontend/plugins/sales_ui/src/modules/pos/ under settings/sales/*.
Data model
Pos: one document per terminal/store:name,token,adminIds/cashierIds,isOnline/onServer,branchId/departmentId,allowBranchIds,paymentIds,paymentTypes,productDetails,catProdMappings,initialCategoryIds,deliveryConfig,kioskMachine/kitchenScreen/waitingScreen,permissionConfig(e.g.cashiers.seeReport),isCheckRemainder/saveRemainder,beginNumber/skipNumber.PosOrders: synced orders:number,status,paidDate,customerId/customerType,posToken,items,paidAmounts,cashAmount/mobileAmount,totalAmount,deliveryInfo(may carrydealId),subscriptionInfo,registerNumber,convertDealId.PosCovers: shift/cash covers with anote.PosSlotsandProductGroups: table slots and the per-POS category groupings (withcategoryIds,excludedCategoryIds,excludedProductIds) that shape the storefront catalog.
HTTP routes
Mounted on the plugin's Express app (modules/pos/routes.ts), reached through the gateway as /pl:sales/<path>:
| Route | Handler | Purpose |
|---|---|---|
GET /pos-init | posInit | Full bootstrap for a POS token (header pos-token): config + users + product groups + slots |
POST /pos-sync-config | posSyncConfig | Partial resync by type: config, products, slots, productsConfigs |
GET /get-pos-token | getPosToken | Lists { name, token } for every POS; requires ?GET_CP_TOKEN= matching the GET_CP_TOKEN env var |
posInit/posSyncConfig assemble data across services: admins and cashiers come from Core users.find, and product categories/products from Core productCategories.find/products.find, all over tRPC. They also pull tax-receipt settings and pricing discounts from Enterprise Edition plugins.
Protect GET_CP_TOKEN
GET /get-pos-token returns POS tokens for the whole tenant. Keep GET_CP_TOKEN secret; it is meant for the client-portal flow, not general integrations.
Queries and mutations
Schemas live under modules/pos/graphql/schemas.
| Operation | Kind | Purpose |
|---|---|---|
posList / posDetail(_id) / posEnv | query | POS configs; posEnv exposes ALLOW_OFFLINE_POS |
posAdd / posEdit / posRemove | mutation | POS lifecycle |
productGroups / productGroupsAdd / productGroupsBulkInsert | query/mutation | Catalog grouping per POS |
posSlots / posSlotBulkUpdate | query/mutation | Table slots |
posProducts(…) | query | Catalog fetch against a POS scope |
posOrders / posOrdersList / posOrderDetail / posOrderLink | query | Order lists and detail |
posOrdersSummary / posOrdersGroupSummary / posOrdersTotalCount / posOrderRecords / posOrderRecordsCount | query | Aggregates and item records |
posOrderCustomers(+TotalCount) / posOrderBySubscriptions(+TotalCount) / checkSubscription | query | Customer and subscription views |
posOrderChangePayments(_id, …) | mutation | Adjust payments on an order |
posCovers / posCoversCount / posCoverDetail / posCoversEdit / posCoversRemove | query/mutation | Covers |
ecommerceGetBranches(posToken) | query | Branch list for a POS token |
Sync with posclient_api
The storefront backend pulls config and pushes orders:
- Config pull:
posConfigsFetch(token)onposclientcallsGET /pl:sales/pos-initand stores the result in itsConfigscollection;syncConfig(type)re-pullsconfig/products/slots/productsConfigsthroughPOST /pl:sales/pos-sync-config. - Order push:
syncOrdersonposclientbatches up to 100 unsynced paid orders (with items and receipt responses) and callspos.createOrUpdateOrdersManyover tRPC;pos.createOrUpdateOrdershandles single-orderstatusToDoneand payment sync. - Hourly remainder sync:
posclient_api's BullMQ schedulerposclient-sync-remainder(0 * * * *UTC) runssyncRemainders/syncDiscountsper tenant for every config withsaveRemainder. - Payment callbacks:
meta.payments.callbackinsales_apiforwards paidsales:pos.orderstransactions toposclient.paymentCallbackClientover tRPC.
tRPC
trpc/pos.ts exposes pos and orders sub-routers: pos.findOne/find/create/confirmCover/ecommerceGetBranches/ordersDeliveryInfo/createOrUpdateOrders/createOrUpdateOrdersMany, and orders.findOne/find/updateOne.
Automations and segments
modules/pos/meta/automations registers a custom trigger "POS order event" (sales:pos.orders) with event types created, paid, returned, statusChanged, paymentChanged, deliveryCompleted, and an action that creates POS orders. The sales:pos.orders segment content type is declared under meta/segments/ with field filters, member listing, and a customer.orders relation; it needs an index on pos_orders.customerId for relation measures.
Permissions
The pos permission module registers posRead, posOrderRead, posCoversRead (all always), plus posAdd/posEdit/posRemove, posOrderChangePayments, posCoversEdit/posCoversRemove, posSlotBulkUpdate, productGroupsBulkInsert, and posItemsExportManage (the sales:pos.posItems export type).
Troubleshooting
pos-initreturns "Not found POS by token": thepos-tokenheader is missing or doesn't match aPosdocument'stoken.pos-sync-configreturns "wrong type":typemust be one ofconfig,products,slots,productsConfigs.- Remainders never sync:
saveRemaindermust be set on the POS config, and theposclientworker must be running. - New POS is forced online: without
ALLOW_OFFLINE_POS,pos.createsetsonServer, which triggerssyncPosToClienton save.