Space runs a café inside the building. Guests order from their phone by scanning a QR code at their seat; the counter rings up walk-ins on a POS; the bar works from its own display; members can run a tab and settle it later.
Who does what
| Screen | Route | Roles |
|---|---|---|
| Menu (QR target) | /{lang}/cafe?spot=<code> | Guests |
| Checkout | /{lang}/cafe/checkout | Guests |
| Order tracking | /{lang}/cafe/order/<token> | Guests |
| Counter queue | /{lang}/dashboard/cafe | Admin, Cashier |
| POS (new order) | /{lang}/dashboard/cafe/new | Admin, Cashier |
| Bar display | /{lang}/dashboard/cafe/bar | Everyone on staff |
| Member tabs | /{lang}/dashboard/cafe/tabs | Admin, Cashier |
| Menu, options, seats & QR, settings | /{lang}/dashboard/cafe/menu … | Admin |
| Sales | /{lang}/dashboard/cafe/sales | Admin |
An order's life
Placed
A QR order starts NEW and waits for the counter to confirm it — that confirmation is the check against pranks. A POS order starts CONFIRMED.
Made
The bar moves it CONFIRMED → PREPARING → READY. Guests watch the change on their tracking page (a 10-second poll; staff screens poll every 5 seconds and chime on new orders).
Handed over
READY → COMPLETED when it is picked up at the counter or brought to the seat. Any open order can be CANCELLED with a reason.
The state machine and who may move what live in src/lib/cafe/status.ts. Every write is conditional on the status the staff member saw, so two taps at once never skip a step.
Money
- Amounts are whole Sudanese pounds (
Int, SDG) — never floats. - Prices come only from the database. The cart shows totals with the same
computeLinethe server uses (src/lib/cafe/pricing.ts). - Orders snapshot item names (Arabic and English), options and prices. Editing the menu never rewrites history.
- A retried checkout returns the same order: each attempt carries an idempotency key.
Payment
Payment status is separate from order status — completing an order doesn't mark it paid.
| Method | How it works |
|---|---|
| Cash | Paid at the counter; the cashier marks it paid. |
| Bankak | The guest transfers to the café's Bankak account (shown at checkout and on tracking) and may type the transaction number; the cashier checks it and marks it paid. |
| Member tab | Charged to the open tab registered to the guest's phone. It stays unpaid until the tab is settled in one cash or Bankak payment. |
The menu
The seeded menu is a placeholder: typical Port Sudan café drinks and snacks, all unpublished at price 0. Nothing reaches guests until someone sets a price and publishes the item in Admin → Café menu. Items have two switches: published (on the menu at all) and available (in stock right now — the bar can flip it during the day).
pnpm cafe:seed # create-only; safe to re-run, never overwrites editsSeats and QR codes
Each seat or area (coworking, meeting room, training room…) is a CafeSpot with a short code. Its QR code opens /ar/cafe?spot=<code>, so "bring it to my seat" orders know where to go. Admin → Café menu → Seats & QR prints cut-out cards.