Case Study · POS / Retail

Built for a multi-location salon group. Client named on request, under NDA.

  • POS / Retail
  • Multi-site operator
  • End-to-end build

Three roles, one app, and a Saturday with no internet

Appointments lived in a paper book, sales in a till, and staff hours in a spreadsheet — a separate set of each, per salon. We replaced all of it with one Flutter app where an owner, a supervisor and a cashier see three genuinely different products, and where losing the internet mid-shift doesn’t stop a sale.

Abstract illustration of the app’s layout Two stylised phone outlines with blocked-out shapes standing in for a summary card, a bar chart, stat tiles and a list of records. These are abstract illustrations in the Nautilus brand colours, not screenshots — no real interface, no real data and no client branding is shown.
Sector
POS / Retail
Engagement
End-to-end build
Platforms
iOS & Android (Flutter)
Our role
Product, build, data model, release
Scale
Multi-site operator
The Brief

Three systems that had never been designed to talk to each other

A multi-location salon group was running its business on three separate systems. Appointments lived in a paper book at the front desk. Sales went through a standalone till. Staff hours sat in a spreadsheet someone updated at the end of the week. Each salon kept its own copy of all three.

That arrangement holds together until you try to answer a question that spans locations. Which services actually make money? Which staff member is busiest, and at which site? Is this Tuesday better or worse than last Tuesday? Every one of those questions required somebody to sit down with three sources of truth and reconcile them by hand — which meant, in practice, that they went unasked.

They wanted one mobile app for the whole operation. The interesting part of the brief was what “one app” had to mean.

The Problem

The same app. Three different operating realities.

An owner needs to see everything, everywhere, at once. A supervisor needs full control of their own salon and no visibility into any other. A cashier needs to move through a transaction quickly and accurately, and must not be able to reach the payroll summary at all.

That is the part teams get wrong. The tempting approach is to build one interface and hide the controls each role shouldn’t see. It demos beautifully and it is structurally unsound: the data is still on the device, the permission logic lives in the widget tree where any refactor can quietly undo it, and every new feature is a fresh chance to leak one location’s numbers onto another’s screen.

Two further constraints shaped the build. Appointments booked through the group’s web presence had to arrive in the mobile app without anyone retyping them. And the app had to keep taking payments through a network outage — a salon that stops selling on a busy Saturday because the router rebooted has not been given a solution, it has been given a liability.

What We Built

One codebase, four load-bearing decisions

1

Role-based architecture with per-salon isolation

Three access levels — owner, supervisor, cashier — enforced through role-scoped Firestore rules rather than in the UI. A cashier signing in at one salon cannot address another site’s records at all, because the query never returns them. The isolation is a property of the backend, so a future feature cannot accidentally opt out of it.

2

A QR bridge between the web booking and the till

Customers book online; the app generates and reads a QR code that resolves a booking to the right service and staff member at check-in. The cashier scans, and the transaction opens pre-filled. No lookup, no re-entry, no transcription error at the busiest moment of the day.

3

Reports a non-technical owner can actually use

Sales graphs update as transactions complete. At any point an owner or supervisor can export a full report — revenue by service, by staff member, or by period — as a formatted PDF or Excel file, generated on the device and shareable straight from it. No web portal to log into, no export queue.

4

Offline-first, with sync on reconnect

Critical data is cached locally and transactions queue when connectivity drops, syncing automatically when it returns. This was treated as a hard requirement from the first architecture conversation rather than a resilience feature bolted on near the end — which is why it works.

The Decision

Enforce roles in the data layer, not the interface

The default

Build one screen set, then show and hide controls per role. It is fast, it is what most multi-role apps do, and it looks correct in a demo.

Why it fails here

Hiding a control does not stop the data reaching the device. In a multi-tenant business that is not a theoretical concern — it means one salon’s takings are retrievable from a device sitting on another salon’s counter. It also degrades over time: the permission rules live in the widget tree, so every refactor and every new screen is another chance to reintroduce a gap, and nothing fails loudly when someone does.

What we did

We designed the data model first — roles, scopes and isolation rules established before a single screen existed — and expressed them as role-scoped rules on the backend. The UI then reflects what the query returns, rather than deciding what the user deserves to see.

What it bought

Adding a screen cannot introduce a permission bug, because screens no longer hold permissions. The security property is a property of the data, and it holds whoever writes the next feature.

Under the Hood

What we used, and what each choice bought

Flutter
One codebase, both stores, one team
Firebase Firestore
Role-scoped rules enforce salon isolation at the data layer
Firebase Auth
Identity tied to the role claim, not a client-side flag
syncfusion_charts
Live sales graphs that update as transactions land
flutter_barcode
Bridges a web booking into the till without manual re-entry
pdf / excel
Owner-readable reports generated on-device
Provider
State management proportionate to the app — no ceremony
The Outcome

What shipped

The app shipped to both the App Store and Google Play. Owners see cross-location performance on one screen, supervisors run their own site independently, and cashiers complete a transaction in fewer steps with fewer opportunities to get it wrong. The reporting that previously required someone to reconcile three systems by hand is now an export button.

3
roles enforced at the data layer, not the UI
0
cross-salon data paths between locations
2
app stores served from one codebase
100%
of transactions queue and sync when the network drops

Build facts, verified against the delivered app. We don’t publish outcome numbers we didn’t measure.

Evidence

What you can check for yourself

How each claim on this page can be verified

5.0 / 5 across 5 verified client reviews
Publicly verifiable
100% Job Success over 49 completed jobs
Publicly verifiable
Upwork profile badge
Shipped to both app stores under the client’s developer account
From our delivery record
Revenue or transaction-volume change after rollout
Not recorded

What we can’t show you here

Some of this engagement stays private. Naming what’s missing seems fairer than quietly leaving gaps.

  • The screens. The interface is the client’s product; the illustrations on this page are abstract by design.
  • The client’s name, at their preference.
  • Revenue figures — theirs to share, not ours.
Why It Matters

If different users need different views of the same data

Multi-tenant apps — one product serving users who need genuinely different views of the same data — are harder than they look, and the difficulty is almost never in the screens. It is in deciding where permission lives. Put it in the interface and you have a demo. Put it in the data layer and you have a system that stays correct while other people extend it.

We design the data model first: roles, scopes and isolation rules before any screen exists. If you’re building for hospitality, retail, field services, or multi-location anything, that’s the approach we bring — and it’s the reason this app can be handed to a new developer without becoming a security review.

Building something where roles actually matter?

We’ll walk you through this build in detail on a call — screens included, under NDA. Bring the awkward parts of your requirements; those are the useful ones.

Talk to us Book a free call

More proof: all case studies · the engineering blog · what clients say