Engineering Case Study Aseria RMS · Restaurant Management Platform

Rebuilding the backbone of a live restaurant platform, one flow at a time

Nine months as lead engineer on a production POS, kitchen-display, e-kiosk and queue system running across ~20 restaurant deployments — hardening the money-handling paths, the device fleet, and the platform underneath them without ever breaking a live workflow.

Role Lead Full-Stack Engineer Stack PHP 8.3 · Laravel 10 · MySQL · Vue · Next.js Window Nov 2025 → Jul 2026
Contribution — end of period totalsgit · author ledger
3,615
commits authored
782K
lines added
1,220
features shipped
550
bugs fixed
230
DB migrations
812
PHP classes owned
18
deploy environments
588
distinct feature areas
Status key Shipped & live Built · staged off-by-default Designed · blueprint
01

Payments & the order lifecycle

The rules that decide when a customer pays, when the kitchen starts cooking, and how an order is labelled are the most dangerous code in the system. I extended them for real restaurant workflows without moving a single existing money-path.

Pay Later for dine-in

Shipped
ProblemPayment was required before an order could exist or reach the kitchen — blocking the normal dine-in flow of "order now, pay after the meal."
ActionAdded an is_pay_later path through CreateSaleAction that dispatches unpaid orders to the kitchen, resolves the customer by phone, and settles later via MarkSalePaidAction. Guarded by a runtime kill-switch and a broadcast rule that lets only unpaid POS pay-later orders through.
ResultFull pay-later lifecycle across POS, kitchen, queue and admin — with regression suites — while Z-report totals stay correct because money only moves at payment time.

Scheduled kitchen release

Shipped
ProblemPay-later orders for a future pickup hit the kitchen immediately, so food was cooked too early and sat waiting.
ActionComputed kitchen_release_at = pickup − lead_minutes; a per-minute cron promotes orders to the kitchen exactly on time, with an idempotency stamp preventing double dispatch.
ResultPrep aligns with pickup time. Fully backward-compatible — partial or legacy requests fall back to the unchanged immediate dispatch.

POS ↔ Kiosk source alignment

Shipped
ProblemA kiosk order carried to a POS terminal was re-tagged as "kiosk" too early, breaking POS receipt numbering and hiding the receipt from staff.
ActionKept the order on the full POS receipt path at creation and re-tagged the source to kiosk only at final payment; switched admin receipt-visibility from channel to a true kiosk-origin key.
ResultConverted orders keep their receipt number and stay visible to staff; genuine kiosk cash orders keep their hide behaviour; reporting still classifies correctly.

Kitchen-dispatch decision matrix

Shipped
ProblemAcross kiosk/POS × cash/card × paid/pay-later × with/without kitchen items, dispatch and realtime events fired at the wrong moment — double-dispatching or leaking unpaid orders to the kitchen.
ActionCodified one documented, testable contract; made dispatch idempotent via updateOrCreate and moved every broadcast to fire only after the DB transaction commits.
ResultA single source of truth for when the kitchen sees an order — eliminating the race conditions between database state and realtime events.
02

Shift integrity & reliability

Shifts (Z-reports) are the unit that reconciles a day's cash. I closed a class of "charged but not recorded" bugs, made shift-end propagate across every device, and built the harness to prove it holds under load.

Auto-open shift — the orphaned-payment fix

Shipped
ProblemPlacing an order required an open shift. If one terminal closed the shift while a kiosk or second POS held stale state, the customer could be charged at the device but sale creation was rejected — a charged-but-not-recorded money bug, the worst class of POS error.
ActionWhen no active shift exists, a new AutoOpenShiftAction opens one — inferring register/location from history, concurrency-safe under lockForUpdate so parallel orders share one shift, stamped with an auto_opened audit flag and a kill-switch. If a location has no shift history at all, the original error is preserved.
ResultPayments taken at a device can no longer become orphaned sales — shifts self-heal on order creation, remain fully auditable, and manual open/close/reporting is untouched.

Device shift-end logout

Staged
ProblemClosing a shift on one terminal left every other device on the same register logged in — but the closing terminal must not kick itself out of its own receipt view.
ActionA device-scoped PosShiftEnded broadcast fans out one event per device, carrying the initiator's ID so the closing terminal ignores its own event. Runs on a retrying queue, wrapped so a broadcast outage never rolls back the close.
ResultAll terminals on a register log out cleanly and simultaneously; the closing terminal keeps its receipt — coordinated across web, Capacitor, Electron and native Android.

Midnight-crossing report bucketing

Shipped
ProblemA shift opened 11pm and closed 1am could land in the wrong reporting day, corrupting daily and monthly Z-report aggregation.
ActionEnforced one canonical rule everywhere: a shift is bucketed by its opening date; the close time never influences the bucket.
ResultDeterministic shift-to-day assignment — midnight-crossing shifts never split or double-count.

Shift stress-test harness

Shipped
ProblemThe full open/close lifecycle — with broadcasting, DB locking and listeners — needed proof it was safe under concurrent, high-volume load.
ActionA shifts:stress-test command auto-discovers every active register and runs N open/close cycles through the real production actions, reporting per-device and aggregate metrics — creating only new reports, never mutating live data.
ResultRepeatable multi-device load testing (~0.35–0.40s/cycle) exercising the exact production code path and broadcasting stack, usable in CI.
03

Managing the device fleet

Every location runs a mix of POS terminals, kitchen displays, kiosks and queue screens. I built the tooling to version them, prove they updated, pair them without friction, and keep them on schedule.

APK versioning & distribution

Shipped
ProblemNo controlled way to ship device builds — risk of pushing an older APK over a newer one, or assigning stale builds.
ActionUpload auto-detects device type from the package name, stores a SHA-256 checksum, and rejects any version lower than current. Assignment permits only the latest build and pushes it to the device over WebSocket.
ResultMonotonic, checksum-verified rollout with auto-typed builds and latest-only assignment.

Per-device update acknowledgement

Shipped
ProblemThe system could assign an update but had no confirmation a device actually installed it — no way to spot stragglers stuck on old versions.
ActionFour apk/acknowledge endpoints validate the reported version against the assignment and write an immutable audit row (device, version, IP, user-agent). Covered by 17 tests.
ResultFull per-device rollout tracking — admins can query acknowledgement rate and every device lagging behind.

Magic-link device pairing

Shipped
ProblemPairing a device meant typing a device ID plus an auth code by hand — slow and error-prone.
ActionOne auto-rotating short URL per device returns that device's full login payload; a scheduled command rotates tokens every 10 min without invalidating live sessions.
ResultOperators pair a device by pasting one link; leaked links self-expire in minutes while paired devices stay logged in.

Kiosk operating hours

Shipped
ProblemA kiosk was a single on/off flag, so an "on" device ran 24/7 — taking ghost orders before opening and after closing, causing refunds and complaints.
ActionAdded a weekly schedule (with overnight rollover) plus a server-anchored clock in the login response, so the device evaluates hours against server time — neutralising device clock drift.
ResultPer-device weekly hours with an "out of hours" screen; fully additive so older apps simply ignore it.
04

Realtime & resilience

Kitchen tickets, queue screens and POS state all depend on realtime events. I made those events survive outages, scoped them per-tenant, and drew the roadmap for locations that keep running when the internet doesn't.

Broadcast reliability layer

Shipped
ProblemA transiently unreachable broadcast service could block the originating sale/order request or silently drop a device update.
ActionMoved all realtime events onto a dedicated queue (8 tries, staged backoff to 15 min) processed ahead of default, plus a transport-agnostic pending_acks layer that re-fires events until the device acknowledges.
ResultThe user-facing request never waits on the broadcast service; device state survives short outages instead of being lost.

Pusher → self-hosted Reverb

Staged
Problem~20 deployments shared a single free Pusher app, collectively hitting its connection/message ceiling with no affordable upgrade tier.
ActionBuilt a migration to self-hosted Laravel Reverb (Pusher-protocol compatible, so zero broadcast-code change) with supervisor/nginx configs and a runbook. Ships off-by-default; rollback is one env var, one restaurant at a time.
ResultRemoves the connection ceiling and recurring cost, staged as a safe, reversible, per-deployment cutover rather than a big-bang switch.

Offline-first resilience blueprint

Blueprint
ProblemTwo production incidents (disk-full, then a cleanup-induced second outage) took down POS, kiosk, kitchen and queue at every location simultaneously for ~15 minutes each. Leadership asked for locations that keep working when the central server or internet is down.
ActionAuthored a phased "local-first, eventually-consistent" architecture: per-location edge cluster, native SQLite-backed apps, a sync engine with mandatory idempotency keys and collision-free per-location order numbering, offline-verifiable auth, and three-tier realtime — each phase shippable on its own.
ResultA Toast/Square-class blueprint that narrows the gap to invisible outages phase by phase, awaiting a buy-vs-build sign-off on the sync engine.
05

The menu & catalog system

Modifiers, meals and their pricing feed POS, kiosk, kitchen and every receipt. I rebuilt the catalog into one reusable system with an audit-safe history — and hard API-contract stability as the design driver, since native devices consume it.

Modifier groups & modifiers

Shipped
Problem"Choose size", "pick sauce", "add extras" had to be reused across items, meals, deals and individual meal lines — without duplicating data, and without breaking legacy flat-modifier clients.
ActionOne modifier catalog reused via a single scoped pivot, with group rules (required, multi-select, limits). Every sale snapshots its choices so historical receipts survive catalog renames or deletes; pricing uses a per-unit max to prevent double-billing.
ResultOne reusable catalog with per-product overrides, grouped-or-flat output that degrades gracefully for old clients, and audit-safe sale snapshots.

Reusable modifier-group library

Shipped
ProblemAdmins rebuilt the same "Size / Sauces / Toppings" groups inline on every product. Hard constraint: device payloads must not change.
ActionChose copy-on-attach with traceability over live-linking, specifically to keep the read path byte-identical — applying a template materialises normal rows the existing serializers read unchanged. Includes an explicit resync path.
ResultDefine-once/reuse-everywhere with per-product override freedom and zero API-contract diff, proven by snapshot regression tests.

Menu Hub — unified editor

Shipped
ProblemFood-item and meal management lived on separate legacy pages needing full reloads and many chained AJAX calls.
ActionBuilt a single Vue editing surface with tabbed slide-over editing; added only 3 wrapper endpoints to collapse N round-trips into one transactional save. Legacy pages stay live.
ResultOne fast editing experience with inline validation and one-shot saves — no schema or POS-rendering impact.

Kiosk line-item materialization

Shipped
ProblemKiosk cash orders only created detailed line items at payment time, so unpaid orders showed dashes for prices and missing modifiers in admin.
ActionMaterialised full sale_items at creation for every kiosk order (idempotent, wrapped so a failure can't break the order); paired with a cleanup action that removes the original kiosk record once carried to POS and paid.
ResultEvery kiosk order renders POS-equivalent detail immediately, with no orphaned records after a POS hand-off.
06

Reporting & end-of-day

Z-reports and X-reports are how managers and accountants close the books. I widened delivery options and precisely diagnosed a client-side reporting bug without touching sound backend logic.

Z-report PDF export & email

Shipped
ProblemReports could only be downloaded as PNG — accountants need PDF, and managers wanted them emailed rather than downloaded and re-attached by hand.
ActionAdded PNG/PDF choice and a one-click email action across single, bulk and combined flows via a single notification class that reuses existing SMTP/queue plumbing — purely additive, so stale bundles never break.
ResultAccountant-friendly PDFs and one-click email delivery, with no new permission and the original buttons unchanged.

X-report failure — root-caused

Diagnosed
Problem"Print X report" always failed with a 422 — the interim shift summary was completely non-functional from the app.
ActionTraced it to a client contract bug: one API call omitted the register ID that every sibling call sent. Confirmed the backend was sound and scoped a precise 2-line client fix, flagging two identical latent bugs nearby.
ResultA low-risk fix isolated to the client with zero backend or contract changes — and two future failures caught before they shipped.
07

Platform & foundations

Beneath the features, I reshaped how the whole system is multiplied across tenants, deployed, documented, kept fast, and safely modified.

Single-codebase multi-tenancy

Staged
ProblemEach tenant needed its own deployed copy of the codebase — onboarding one meant provisioning a whole new server and pipeline.
ActionBuilt a shared-codebase, database-per-tenant architecture: hostname → database switching, a landlord control-plane, async provisioning, and an email-OTP super-admin console. Ships off-by-default behind a flag, with a full test suite.
ResultNew tenants can stand up on one shared deployment with per-tenant DB isolation — without touching existing single-tenant installs.

Deployment automation · 18 envs

Shipped
ProblemMany restaurant servers needed consistent SSH deploys; stale OPcache workers threw "undefined function" errors and route-cache races appeared post-deploy.
ActionStandardised test → build → deploy workflows across 18 environments with a passing-test gate, OPcache reset, full cache warming, and a fixed route-cache race — deploys gated on lint/test success.
ResultRepeatable, cache-optimised, zero-manual-step deploys where broken code cannot ship.

Runtime & query performance

Shipped
ProblemHigh-traffic admin and device-polling endpoints were slow under production data — the dashboard-states view took ~20 seconds.
ActionA sustained performance program: composite indexes on sales, sargable + cached dashboard queries, a durable Z-report print cache (also fixing blank PDFs), and telemetry tuning.
ResultMajor latency cuts on the hottest paths — the 20s dashboard chief among them — with no behaviour or contract change.

API docs & engineering guardrails

Shipped
ProblemHeterogeneous clients (POS, kitchen, kiosk, Android, Electron, Next.js) consumed the API with no authoritative contract, and risky flows needed protection from careless edits.
ActionCurated a hand-maintained OpenAPI spec behind a self-hosted Scalar UI, and authored a full operating guide plus a 24-file domain skills library encoding the system's non-negotiable stability rules.
ResultA single accurate API reference kept in lockstep with the contract, and codified guardrails so any engineer loads the right context before touching a live flow.

The through-line

Do no harm

Every change was additive and reversible — nullable columns, off-by-default flags, snapshot tests — because the platform was live in ~20 restaurants throughout. No existing workflow, contract or money-path was broken to ship a feature.

Guard the money

The highest-leverage work closed money-integrity gaps: orphaned payments, orders leaking to the kitchen unpaid, mis-bucketed shifts. Correctness on the cash path was treated as non-negotiable.

Build the platform

Beyond features, the work multiplied the whole system — tenancy, self-hosted realtime, 18-environment deploys, a documented API and a codified engineering framework the team now runs on.

About the engineer
Hayatunnabi Nabil

Hayatunnabi Nabil

Lead Full-Stack Engineer · Aseria RMS

Full-stack engineer with a backend-heavy discipline — PHP 8.3 / Laravel, MySQL, realtime device fleets, and the money-handling paths of a live restaurant platform. I build additively and reversibly, treat correctness on the cash path as non-negotiable, and care about polished, operationally efficient UI on every device.

Phone · WhatsApp +880 1878 005537
Location House 2 (Shyamoli Housing), Road 5/A, Sector 5, Uttara, Dhaka 1230, Bangladesh