+32
-10
@@ -33,6 +33,8 @@ Swagger UI: `http://localhost:3001/api/docs`
|
||||
### Devices
|
||||
`POST /devices/register` - Pair a terminal; every pairing adds one
|
||||
`POST /devices/:deviceId/sync` - Sync device state (device auth)
|
||||
`POST /devices/:deviceId/images` - Upload a picture taken on the terminal (device auth, multipart field `file`):
|
||||
`{ imageId }`. Processed like a dashboard upload (see Images); the terminal links it with a `product_updated` event
|
||||
`POST /devices/:deviceId/commands/:commandId/ack` - Acknowledge command (device auth)
|
||||
`GET /devices/generation-code` - Generate registration code (admin)
|
||||
|
||||
@@ -49,6 +51,12 @@ and `docs/server-stock-source-of-truth.md`.
|
||||
- `settled`: `amountInCents`, `paymentMethod` (`cash`, `payconiq`, `payment_pending` or `other`), optional
|
||||
`commandId` and `items` (`[{ productId?, productName, quantity, unitPriceInCents }]`)
|
||||
- `stock_set`: `productId`, `quantity` (a count ≥ 0, or `null` to stop tracking); no `tabId`
|
||||
- `product_created` / `product_updated` / `product_archived`: `productId` (the terminal makes the id of a new
|
||||
product) and `product` with the fields that changed: any of `name`, `category`, `priceInCents`,
|
||||
`lowStockThreshold`, `archived`, `imageId`. `product_created` needs `name` and `priceInCents`; no `tabId`.
|
||||
Every field keeps the time it last changed, and an event only changes a field it changed later (by
|
||||
`occurredAt`), so an offline rename and a dashboard price change both survive. An `imageId` the server doesn't
|
||||
have is ignored; an update of a product the venue doesn't have is acknowledged and dropped
|
||||
|
||||
Each id is applied once (tab state and stock in the same transaction). `acknowledgedEventIds` lists the applied
|
||||
ones, including repeats; `rejectedEvents` (`[{ id, reason }]`) were invalid and never applied. The terminal drops
|
||||
@@ -62,10 +70,12 @@ and `docs/server-stock-source-of-truth.md`.
|
||||
first, at most 500; the response's `settlementsCursor` is sent back next time.
|
||||
- `openTabs` is only used once per venue, on the first sync after the server started holding the tabs: the terminal's
|
||||
own tabs (and catalog stock) are taken over and the events of that request are stored without applying them again.
|
||||
- The dashboard manages the products. `products` in the request (at most 2000) is only used on that same first sync,
|
||||
to take over the terminal's catalog with its stock; after that it is ignored. The response's `catalog` lists every
|
||||
product of the venue: `[{ id, name, category, priceInCents, lowStockThreshold, archived }]`. The terminal adds and
|
||||
updates its products from it and hides archived products and products the catalog doesn't list.
|
||||
- Products are changed on the dashboard and, through the product events above, on terminals. `products` in the
|
||||
request (at most 2000) is only used on that same first sync, to take over the terminal's catalog with its stock;
|
||||
after that it is ignored. The response's `catalog` lists every product of the venue:
|
||||
`[{ id, name, category, priceInCents, lowStockThreshold, archived, imageId }]`. The terminal adds and updates its
|
||||
products from it (replaying its unsent product events on top), hides archived products and products the catalog
|
||||
doesn't list, and downloads pictures it doesn't have yet from `GET /images/:imageId`.
|
||||
- Planned customers are opened as tabs by the server, once; `plannedTabNames` is always empty.
|
||||
- Dashboard stock adjustments are applied by the server right away; `stockAdjustments` is always empty.
|
||||
`stockAdjustmentResults` from older app versions are still recorded and acknowledged.
|
||||
@@ -95,15 +105,27 @@ Names match case-insensitively and keep their existing spelling; `replace` also
|
||||
`GET /venues/:venueId/stock` - The venue's products (archived ones too, with `archivedAt`) with stock, low stock
|
||||
level and pending adjustments
|
||||
`GET /venues/:venueId/stock/adjustments?productId=&limit=&offset=` - Stock adjustment history, newest first
|
||||
`POST /venues/:venueId/stock/adjustments` - Request stock changes (admins and viewers): `{ adjustments: [{ productId, type: 'add' | 'set', quantity, note? }] }`.
|
||||
`POST /venues/:venueId/stock/adjustments` - Request stock changes (admins and managers): `{ adjustments: [{ productId, type: 'add' | 'set', quantity, note? }] }`.
|
||||
`add` takes a non-zero quantity (negative for breakage) and needs a tracked product; `set` takes 0 or more.
|
||||
`POST /venues/:venueId/stock/adjustments/:id/cancel` - Cancel an adjustment the terminal hasn't received yet (admins and viewers)
|
||||
`POST /venues/:venueId/stock/products` - Add a product (admin): `{ name, category?, priceInCents, stock?, lowStockThreshold? }`.
|
||||
`stock` is the starting count (recorded as an applied `set` adjustment); null or left out: not tracked.
|
||||
`product_name_exists` when an active product has the same name (ignoring case).
|
||||
`PATCH /venues/:venueId/stock/products/:productId` - Change a product (admin): any of `{ name, category, priceInCents,
|
||||
`POST /venues/:venueId/stock/adjustments/:id/cancel` - Cancel an adjustment the terminal hasn't received yet (admins and managers)
|
||||
`POST /venues/:venueId/stock/products` - Add a product (admins and managers): `{ name, category?, priceInCents, stock?,
|
||||
lowStockThreshold? }`. `stock` is the starting count (recorded as an applied `set` adjustment); null or left out:
|
||||
not tracked. `product_name_exists` when an active product has the same name (ignoring case). Terminals may still
|
||||
create products with the same name; the products page marks those as duplicates.
|
||||
`PATCH /venues/:venueId/stock/products/:productId` - Change a product (admins and managers): any of `{ name, category, priceInCents,
|
||||
lowStockThreshold, archived }`. `archived: true` deletes it for the terminals (kept for tabs and history),
|
||||
`archived: false` brings it back. The stock itself changes through adjustments.
|
||||
`POST /venues/:venueId/stock/products/:productId/image` - Set a product's picture (admins and managers; multipart
|
||||
field `file`, see Images): the product with its new `imageId`
|
||||
`DELETE /venues/:venueId/stock/products/:productId/image` - Remove a product's picture (admins and managers)
|
||||
|
||||
### Images
|
||||
`GET /images/:imageId` - A product picture (public, `image/webp`, cached for a year: an id never changes content).
|
||||
Uploads (JPEG, PNG, WebP, GIF or AVIF, at most 10 MB) are turned upright and stored as a 512×512 WebP without
|
||||
metadata. A product on a plain background (all four edges one colour, or transparent) is trimmed and centred
|
||||
with a 40 px margin on that background. Any other photo is cropped around its centre when roughly square, or
|
||||
fitted whole on a transparent background when not (bottles, cans). The id is the SHA-256 of the stored file.
|
||||
`invalid_image` when the upload can't be read.
|
||||
|
||||
### Commands
|
||||
`POST /venues/:venueId/commands` - Settle a tab: `{ tabId, type: "close_tab", expectedVersion, paymentMethod }`. The
|
||||
|
||||
+4
-2
@@ -19,8 +19,10 @@
|
||||
## Authorization
|
||||
|
||||
### Role-Based Access
|
||||
- `admin`: Full access, including users, device pairing, planned customers and sending reminders
|
||||
- `viewer` (shown as staff): views tabs and commands and can close tabs
|
||||
- `admin`: Full access, including users and pairing or unpairing terminals
|
||||
- `manager` (the people running the venue): products and their pictures, stock, tabs, planned customers and
|
||||
reminders. Accounts and tokens with the former role `viewer` are treated as managers
|
||||
- Product pictures are public by id (`GET /images/:imageId`); the id is a SHA-256 hash, so it can't be guessed
|
||||
|
||||
### Venue Isolation
|
||||
- Every user and device is scoped to exactly one venue
|
||||
|
||||
Reference in New Issue
Block a user