API (api-v2)
POS & shifts
/pos/v1/* is the till's whole API. It covers tills, catalogue reads, sales, the cash drawer lifecycle, till sections and POS customer lookups. pos is an orchestrator; shift and till-section own their tables.
Catalogue reads
| Route | Guard |
|---|---|
get-tills | authenticated. Returns (shop, location) pairs where the user holds pos.operate, with currency, taxRate, taxesIncluded, canManageSections. |
get-catalog, lookup-barcode, get-categories | pos.operate |
get-modifier-groups | pos.operate |
- Reads span every branch, so a cashier can check another branch's stock.
- Prices are converted from USD to the shop currency.
get-catalogsorts in-stock items first (PR #85).
Sales
| Route | Guard |
|---|---|
create-order | shop access + at pos.operate for the till's location |
verify-discount-pin | pos.operate |
- Tenders:
cash,card,wallet. The UI callswallet"Whish" (POS ADR 0002, API PR #77). - Split tender is allowed. The till sends the cash part with an amount and the other part without an amount, and the server fills the remainder (
pos.payments.ts). - Change is computed on the server. "Covers the total" means rounded half-up to the cent, mirrored on the till.
- Card and wallet payments post to 1030 Card money in transit.
- Every sale is stamped with the open shift (API ADR 0001), and a cash sale requires an open drawer (ADR 0002). The till goes further and requires a drawer for any sale.
- Idempotent: the till generates one
crypto.randomUUID()per basket, not per attempt. A replay returnsidempotentReplay: true. - An optional
customerIdattaches a customer. Since PR #83 the order shows that customer.
Cash drawer (shifts)
| Route | Guard |
|---|---|
open-shift | at shifts.open |
get-open-shift | pos.operate |
get-expense-categories, record-cash-movement | cash.move |
close-shift, get-my-shifts, get-shift, get-shift-sales | shifts.open or shifts.manage |
/merchant/shift/v1/get-shifts | shifts.manage |
Lifecycle:
- Open with a float (zero allowed). A user has at most one open drawer. Opening a second returns 409.
- Cash movements:
drop,pay_inandpay_out, each with a required reason.- A
pay_outalso names an expense category and posts to the ledger as an expense (PR #71).
- Close with a counted amount and an optional note:
- Expected cash is frozen at close, and variance = counted − expected (
shift.math.ts). - Cash refunds made with
paidFromDrawerare included in expected cash.
- Expected cash is frozen at close, and variance = counted − expected (
POS ADR 0001 rules that the cashier never sees the running expected cash. get-open-shift is shaped so the till doesn't need it.
Till sections
Tables: till_section, till_section_product
| Route | Guard |
|---|---|
get-till-sections | pos.operate |
create, update, delete, reorder, move, add/remove-till-section-products | pos.operate + wide pos.sections.manage |
- Sections nest. A section holds products or child sections, never both.
- Deleting a section that still has children is refused.
- Sections hold products, so adding one variant adds the whole product.
- Colours: one of 9 named tints, or none.
POS customers
| Route | Guard |
|---|---|
search-customers, find-customers-by-phone | pos.operate + customers.view |
create-customer, update-customer | pos.operate + wide customers.write |
find-customers-by-phone powers the till's live duplicate-phone warning. It warns but doesn't block, because families share phones.
In flight
| PR | What |
|---|---|
#78 feat/pos-catalogue-sync | Serve a till its own copy of the catalogue (syncCatalog, getLiveFigures) |
#82 feat/offline-first-till | Accept and record sales a till made offline |
See Offline till.