All work

Aseria RMS

Multi-location restaurant POS, synced across every device

Run Every Kitchen. Serve Every Table. Own Every Number.

~20 restaurant locations · 18 deploy environments

Laravel 10PHP 8.3VueNext.jsMySQLRedissoketiMulti-tenant
Role
Senior Software Engineer
Company
Aseria SA
Period
November 2025 – Present
Location
Satigny, Switzerland
Domain
Restaurant POS

Overview

Aseria RMS is a multi-location restaurant management platform built around a point-of-sale core. A single restaurant group runs tills, kitchen screens, and self-service kiosks across several venues, and every one of those devices has to agree on the same orders, menu, and prices at the same moment — including when a device drops off the network mid-service.

I joined as the senior software engineer on the platform, which means I own the system end to end: the multi-tenant application architecture, the real-time synchronization layer, the payment and reporting flows, and the self-hosted infrastructure it all runs on, through to CI/CD and production monitoring.

The hard requirements here are correctness and uptime under service pressure. A restaurant during a dinner rush cannot tolerate a lost order, a double charge, or a printer that silently stops — so the design leans heavily on acknowledgement, retry, idempotency, and reconciliation rather than assuming the happy path.

The platform runs ~25 modules across 18 deploy environments and roughly 20 restaurant locations, linking the front counter, self-service kiosk, kitchen stations, queue screens, payment flow and owner reporting into one live operating loop. Because it was already live in production the entire time, every change had to be additive and reversible.

What I built

  • Real-time device sync with acknowledgement and auto-retry across all devices using sockets with soketi.
  • Redis-backed caching, queues, session management, and real-time event coordination for high-performance operations.
  • Full POS and payment flows with idempotency and reconciliation.
  • X/Z financial reporting with configurable breakdowns.
  • Kitchen Display System with auto-assign workflows and ESC/POS printing.
  • E-Kiosk with shift and cash-register management.
  • Menu Hub with a complete modifier-system overhaul.
  • APK and device version management with real-time rollout.
  • Multi-tenant architecture with a self-hosted WebSocket broadcasting server using soketi.
  • Dockerized self-hosted VPS infrastructure with fully automated deployment.
  • Prometheus and Grafana monitoring and observability dashboards.
  • CI/CD with lint-gated deployments and multi-language i18n.

Walkthrough · 6 screens

Screenshots and recordings from the running system, with a note on what each one implies about the design underneath. Open any of them for a closer look.

Engineering breakdown

The work that mattered, as problem, solution and result. Shipped is merged and live; Staged is built and tested but intentionally off by default pending cutover; Blueprint is designed and documented, awaiting sign-off.

01

Payments & order lifecycle

Pay Later for dine-in

shipped
Problem
Payment was required before an order could exist or reach the kitchen, which blocks the ordinary dine-in flow — people eat first and pay afterwards.
Solution
Added an is_pay_later path through CreateSaleAction so unpaid orders dispatch to the kitchen, the customer is resolved by phone, and the bill settles later via MarkSalePaidAction. A runtime kill-switch and a broadcast rule restrict unpaid POS orders.
Result
A full pay-later lifecycle across POS, kitchen, queue and admin, covered by regression suites, with Z-report totals still correct.

Scheduled kitchen release

shipped
Problem
Pay-later orders for a future pickup hit the kitchen immediately, so food was cooked early and then sat waiting.
Solution
Computed kitchen_release_at as pickup minus lead_minutes, with a per-minute cron promoting orders on time and an idempotency stamp preventing double dispatch.
Result
Prep aligns with pickup time, and the change is fully backward-compatible.

POS ↔ kiosk source alignment

shipped
Problem
A kiosk order carried to a POS terminal was re-tagged as 'kiosk' too early, which broke POS receipt numbering and hid the receipt from staff.
Solution
Kept the order on the full POS receipt path at creation and re-tagged the source to kiosk only at final payment, switching admin receipt-visibility from channel to a kiosk-origin key.
Result
Converted orders keep their receipt number and staff visibility, genuine kiosk cash orders hide correctly, and reporting classifies both properly.

Kitchen-dispatch decision matrix

shipped
Problem
Across kiosk/POS × cash/card × paid/pay-later × with/without kitchen items, dispatch and realtime events fired at the wrong moment — double-dispatching orders or leaking unpaid ones to the kitchen.
Solution
Codified one documented, testable contract, made dispatch idempotent via updateOrCreate, and moved every broadcast to fire only after the database transaction commits.
Result
A single source of truth for kitchen order visibility, eliminating race conditions between database state and realtime events.
02

Shift integrity & reliability

Auto-open shift — orphaned-payment fix

shipped
Problem
A customer could be charged at the device while sale creation was rejected — charged-but-not-recorded money, the worst class of POS error.
Solution
AutoOpenShiftAction opens a shift when none exists, inferring register and location from history, concurrency-safe under lockForUpdate so parallel orders share one shift. Every auto-open is stamped with an audit flag and sits behind a kill-switch.
Result
Payments can no longer become orphaned sales; shifts self-heal on order creation, remain fully auditable, and manual operations are untouched.

Device shift-end logout

staged
Problem
Closing a shift on one terminal left every other device logged in, and the closing terminal must not kick itself out of the receipt view.
Solution
A device-scoped PosShiftEnded broadcast fans out one event per device carrying the initiator's ID, so the closing terminal ignores its own event. It runs on a retrying queue, wrapped so a broadcast outage never rolls back the close.
Result
All terminals log out cleanly and simultaneously while the closing terminal keeps its receipt — coordinated across web, Capacitor, Electron and native Android.

Midnight-crossing report bucketing

shipped
Problem
A shift opened at 11pm and closed at 1am could land in the wrong reporting day, corrupting daily and monthly Z-report aggregation.
Solution
Enforced one canonical rule everywhere: a shift is bucketed by its opening date, and close time never influences the bucket.
Result
Deterministic shift-to-day assignment; midnight-crossing shifts never split or double-count.

Shift stress-test harness

shipped
Problem
The full open/close lifecycle needed proof of safety under concurrent, high-volume load.
Solution
A shifts:stress-test command auto-discovers active registers and runs N open/close cycles through the real production actions, reporting per-device and aggregate metrics at roughly 0.35–0.40s per cycle. It only creates new reports and never mutates live data.
Result
Repeatable multi-device load testing that exercises the exact production code path and broadcasting stack, usable in CI.
03

Managing the device fleet

APK versioning & distribution

shipped
Problem
There was no controlled way to ship device builds, risking an older APK being pushed over a newer one, or stale builds being assigned.
Solution
Upload auto-detects device type from the package name, stores a SHA-256 checksum, and rejects any version lower than the current one. Assignment permits only the latest build and pushes over WebSocket.
Result
Monotonic, checksum-verified rollout with auto-typed builds and latest-only assignment.

Per-device update acknowledgement

shipped
Problem
The system could assign an update but had no confirmation the device installed it — no way to spot stragglers left on old versions.
Solution
Four apk/acknowledge endpoints validate the reported version against the assignment and write an immutable audit row capturing device, version, IP and user-agent. Covered by 17 tests.
Result
Full per-device rollout tracking; admins can query acknowledgement rate and identify lagging devices.

Magic-link device pairing

shipped
Problem
Pairing meant typing a device ID plus an auth code by hand — slow and error-prone.
Solution
One auto-rotating short URL per device returns that device's full login payload, with a scheduled command rotating tokens every 10 minutes without invalidating live sessions.
Result
Operators pair a device by pasting one link, and leaked links self-expire within minutes while paired devices stay logged in.

Kiosk operating hours

shipped
Problem
The 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.
Solution
Added a weekly schedule with overnight rollover plus a server-anchored clock in the login response, so the device evaluates hours against server time and clock drift is neutralised.
Result
Per-device weekly hours with an 'out of hours' screen; fully additive, and older apps simply ignore it.
04

Realtime & resilience

Broadcast reliability layer

shipped
Problem
A transiently unreachable broadcast service could block a sale or order request, or silently drop a device update.
Solution
Moved all realtime events onto a dedicated queue (8 tries, staged backoff to 15 minutes) processed ahead of the default, plus a transport-agnostic pending_acks layer that re-fires until the device acknowledges.
Result
User-facing requests never wait on the broadcast service, and device state survives short outages instead of being lost.

Pusher → self-hosted soketi

staged
Problem
Around 20 deployments shared a single free Pusher app, hitting the connection and message ceiling with no affordable upgrade path.
Solution
Built a migration to a self-hosted soketi broadcasting server — Pusher-protocol compatible, so zero broadcast-code change — with supervisor and nginx configs and a runbook. It ships off by default, and rollback is one env var, one restaurant at a time.
Result
Removes the connection ceiling and the recurring cost, staged as a safe, reversible, per-deployment cutover rather than a big-bang migration.

Offline-first resilience blueprint

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

Menu & catalog system

Modifier groups & modifiers

shipped
Problem
'Choose size', 'pick sauce' and 'add extras' had to be reused across items, meals, deals and individual lines without duplicating data or breaking legacy flat-modifier clients.
Solution
One modifier catalog reused via a single scoped pivot, with group rules for required, multi-select and limits. Every sale snapshots its choices so historical receipts survive catalog renames and deletes; pricing uses per-unit max.
Result
One 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
Problem
Admins rebuilt the same Size / Sauces / Toppings groups inline on every product, under a hard constraint that device payloads must not change.
Solution
Chose copy-on-attach with traceability over live-linking, to keep the read path byte-identical: applying a template materialises normal rows that existing serializers read unchanged. Includes an explicit resync path.
Result
Define-once, reuse-everywhere with per-product override freedom and zero API-contract diff, proven by snapshot regression tests.

Menu Hub — unified editor

shipped
Problem
Food-item and meal management lived on separate legacy pages that needed full reloads and many chained AJAX calls.
Solution
Built a single Vue editing surface with tabbed slide-over editing, adding only 3 wrapper endpoints that collapse N round-trips into one transactional save. Legacy pages stay live.
Result
A fast editing experience with inline validation and one-shot saves, with no schema or POS-rendering impact.

Kiosk line-item materialization

shipped
Problem
Kiosk cash orders only created detailed line items at payment time, so unpaid orders showed dashes for prices and missing modifiers in admin.
Solution
Materialised full sale_items at creation for every kiosk order — idempotent, and wrapped so a failure cannot break the order — paired with a cleanup action removing the original kiosk record once carried to POS and paid.
Result
Every kiosk order renders POS-equivalent detail immediately, with no orphaned records after POS hand-off.
06

Reporting & end-of-day

Z-report PDF export & email

shipped
Problem
Reports could only download as PNG, but accountants need PDF, and managers wanted email delivery rather than downloading and re-attaching by hand.
Solution
Added a PNG/PDF choice and a one-click email action across single, bulk and combined flows via a single notification class reusing the existing SMTP and queue plumbing — purely additive.
Result
Accountant-friendly PDFs and one-click email delivery, with the original buttons unchanged.

X-report failure — root-caused

diagnosed
Problem
'Print X report' always failed with a 422, leaving the interim shift summary completely non-functional.
Solution
Traced it to a client contract bug: one API call omitted the register ID that every sibling call sent. Confirmed the backend was sound, scoped a precise 2-line client fix, and flagged two identical latent bugs nearby.
Result
A low-risk fix isolated to the client with zero backend or contract changes, and two future failures caught pre-ship.
07

Platform & foundations

Single-codebase multi-tenancy

staged
Problem
Each tenant needed its own deployed codebase, so onboarding meant provisioning a whole new server and pipeline.
Solution
Built a shared-codebase, database-per-tenant architecture: hostname-to-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.
Result
New tenants stand up on one shared deployment with per-tenant database isolation, without touching existing single-tenant installs.

Deployment automation across 18 environments

shipped
Problem
Many restaurant servers needed consistent SSH deploys; stale OPcache workers threw errors and route-cache races appeared post-deploy.
Solution
Standardised test → build → deploy workflows across 18 environments with a passing-test gate, OPcache reset, full cache warming, and a fix for the route-cache race. Deploys are gated on lint and test success.
Result
Repeatable, cache-optimised, zero-manual-step deploys where broken code cannot ship.

Runtime & query performance

shipped
Problem
High-traffic admin and device-polling endpoints were slow under production data — the dashboard-states view took around 20 seconds.
Solution
A sustained performance program: composite indexes on sales, sargable and cached dashboard queries, a durable Z-report print cache that also fixed blank PDFs, and telemetry tuning.
Result
Major latency cuts on the hottest paths without any behaviour or contract change.

API docs & engineering guardrails

shipped
Problem
Heterogeneous clients — POS, kitchen, kiosk, Android, Electron, Next.js — consumed the API with no authoritative contract, and risky flows needed protection.
Solution
A curated, hand-maintained OpenAPI spec behind a self-hosted Scalar UI, plus a full operating guide and a 24-file domain skills library encoding the system's non-negotiable stability rules.
Result
A 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.

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 around 20 restaurants while I worked on it.

Guard the money

The highest-leverage work closed money-integrity gaps: orphaned payments, unpaid orders leaking to the kitchen, mis-bucketed shifts. Cash-path correctness is non-negotiable.

Build the platform

Work that multiplied across the whole system: multi-tenancy, self-hosted realtime, 18-environment deploys, a documented API, and a codified engineering framework.

The product · 25 modules

  • Dashboard
  • Order Management
  • Queue Management
  • E-Kiosk
  • Kitchen Management
  • POS System
  • Cash Register
  • User Management
  • User Roles
  • Customers
  • Overall Reports
  • Tax Reports
  • Z-Reports
  • Stock Alerts
  • Service Tables
  • Payment Methods
  • Settings
  • Report Downloads
  • Scheduled Email Reports
  • Monthly & Yearly Analytics
  • GitHub Issue Tracking
  • Daily Issue Email Reports
  • Advanced Reporting Engine
  • Daily Server Backups
  • Backup Management

Stack

Backend

  • PHP 8.3
  • Laravel 10
  • MySQL
  • REST APIs

Frontend

  • Vue
  • Next.js

Real-time

  • WebSockets
  • Pusher (legacy)
  • soketi (self-hosted)

Data & caching

  • Redis
  • Queues
  • Caching
  • Sessions
  • OPcache

Infrastructure

  • Docker
  • Self-hosted VPS
  • Nginx
  • Supervisor
  • CI/CD

Observability

  • Prometheus
  • Grafana

Docs & quality

  • OpenAPI
  • Scalar
  • PHPUnit / Pest