# What we learned from Ordit / Atis (2026-11-11) Base path: `/mnt/nfs/homes/bayi/Projektek/Webandmore/Ordit/`. Ideas only — the code is old (PHP 7.4 / Symfony 4.4 / Vue 2) and the repos contain committed secrets. Never copy config. ## Repos at a glance | Repo | Stack | Role | |---|---|---| | `ordit-backend` | Symfony 2/3-style monolith | checkout, payments, order send-out to restaurant software | | `ordit-core-api` | Symfony 4.4 + API Platform 2.5, MySQL 5.7, Messenger, Mercure | admin/restaurant/customer API, tablets, notifications, delivery | | `ordit-frontend` | Vue 2 PWA | customer app (no admin, no kiosk) | | `ordit-admin-frontend` | Vue 2 + Vuetify | back office (B2B heavy) | | `ordit-tablet` | Kotlin, Sunmi | restaurant tablet: receive/accept orders, print, sold-out toggles | | `atis-frontend` | **Nuxt 3 + Pinia + vue-query + PrimeVue + Tailwind + Capacitor 7** | web + app + **kiosk** from one codebase (`MODE_KIOSK` build) | | `atis-admin` | Vue 2 + Vuetify + Vite | Atis back office | | `atis-api` | Symfony (Ordit fork) | + KioskRegistration, Five terminal, Piggy loyalty, Deliverect | Ordit backend + core-api share one MySQL DB and talk through a DB `TaskQueue` table. **Don't repeat this.** ## Domain ideas worth keeping ### Restaurant (→ our `Location`) Slug, geo, timezone, accepted payment methods, approve emails (store notification addresses), pre-order rules (day-before / border time), `inactivatedUntil` (temp close), prep time, pickup information text, opening hours as `OpeningDay → Interval(open, close)` + special closures, cached computed opening hours. Order display-id prefix + per-store counter (`OrderDisplayIdCounter`). ### Menu - Two STI hierarchies: `MealAndOptionBase → Meal | MealOption` and `MealCategoryAndOptionGroupBase → MealCategory | MealOptionGroup`. - Option group rules: `min/maxQuantity` (total), `min/maxDifferent` (distinct options), `min/maxEachQuantity` (per option). Groups attach to meals **or options** → nested modifiers. - Item fields: code, price, discountPrice, VAT (separate delivery VAT), allergens, suitableFor, kcal, ingredients, isPopular, externalId (POS), `inactivatedUntil`, ordered `groupsOrder`. - Availability chain + "inactivate for today" (tablet sold-out button). ### Orders - Status: `init-payment → new → approved → cancelled | cancelled-after-approval`, plus flags `preparationConfirmed`, `readyToPickup`, `isPickedUp`, `cancelableUntil`. `OrderHistory` trail. - `OrderingItem` is a **self-referencing tree** (item → option group → option) storing snapshots of name/price/VAT/code/allergens. Good — we keep this. - Public share token for tracking; Mercure / Pusher for realtime. ### Dispatch to restaurant - `RestaurantSoftwareSetting` per restaurant (software type + credentials + delays + limits). - `OrderingSoftwareSending` log: isSent, sendAt, tryingCount, responses. Email as fallback channel. - `RestaurantEmailNotification` + `EmailSentLog`; Slack alert on offline tablets. ### Kiosk (atis) - **Device registration**: admin creates `KioskRegistration` → 1h activation code as QR → kiosk scans it, posts `deviceId` → gets JWT + locked restaurant + terminal ids. Kiosk runs as an auto-created kiosk user. - Flow: attract "Touch to order" → optional loyalty identify / guest → menu → pay on terminal (blocking call with START/IN_PROGRESS/SUCCESS/FAILED, retry/cancel, `illegal_state` = already paid) → order number screen → claim loyalty points / email receipt → reset. - Terminal: **Five (five.id)**, idempotency key per order, long-polling through 504s. - Missing in Atis (we should do better): idle reset, eat-in vs takeaway choice, receipt printing, per-location kiosk settings. ### Permissions & audit - Atis: `AclRole → AclUnit → AclUnitRule` matrix (role → sub-roles). Ordit core-api: role-suffixed serialization groups (`@restaurant`, `@company`) + Doctrine audit bundle; admin has an `/audit` page. - Neither is location-scoped in a clean way → our RBAC is scoped by design. ### Loyalty - Atis integrates **Piggy** (tiers, claim points from kiosk). Eat Activ currently uses Storekit loyalty. ## Dispatch driver pattern (ordit-backend) - `Spirit/OrderingSoftware/CoreBundle/Connection/AbstractConnection.php`: `isConnectionSupported`, `manualConnect(auth, OrderingPresentation)` for push, plus pull-style callbacks. - Neutral order DTO `Presentation/Ordering.php` + `OrderingItem.php` (nested, net/gross/VAT), built by `Order/PresentationBuilder.php`. - `OrderSoftwareManager::queueOrderForSending` → one `OrderingSoftwareSending` row per channel with `sendAt`; cron sends due rows. `RestaurantSoftwareLimit` = send N minutes before required time. - Email channel (`OrditConnection`) mails the store an approve link → `/order-software-confirm/{id}` → APPROVED. - Callback channel POSTs `{"id"}` with an api-key header; receiver pulls the order. - Inbound webhooks `POST /hook/{integration}/{action}` → `WebhookHandlerInterface`. - Email: Twig templates, SendGrid dynamic templates or SMTP, `EmailSentLog`. - Weak spots: no order state machine (setStatus everywhere, ready/picked-up are booleans/json), no pickup slots/capacity, no time-based menu availability. Audit = doctrine-audit-bundle `*_audit` tables + `OrderHistory`. ## Stream **Not found in any repo** (ordit-backend, core-api, atis-*, ordit-tablet). Built from scratch on the driver pattern above. The Deliverect integration shape is the template: `createOrder`/`cancelOrder` + inbound HMAC webhooks (`channelStatus`, `busyMode`, `menuUpdate`, `snooze`, `updatePrepTime`, `orderStatus`).