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

POS App Development Case Study: How We Built a Role-Based System for a Multi-Location Salon Group

A multi-location salon group replaced paper books, a standalone till, and a spreadsheet with one Flutter POS app — roles enforced at the data layer, offline-first, and every claim on this page verifiable.

Abstract illustration of the app’s layoutTwo stylised phone outlines with blocked-out shapes standing in for a summary card, a bar chart, stat tiles and a list of records.
Industry
POS & Retail
Engagement
End-to-end build
Platforms
iOS & Android (Flutter)
Our role
Product, build, data model, release
Scale
Multi-site operator
Overview

Replacing Disconnected Systems with One Unified Platform

This POS app development case study covers a multi-location salon group that was running three disconnected systems: appointments in a paper book, sales through a standalone till, and staff hours in a spreadsheet — a separate copy of each, per location. We replaced all three with a single Flutter-built retail app where an owner, a supervisor, and a cashier see three genuinely different products, with permissions enforced at the data layer rather than the interface, and transactions that keep working through a network outage.

The Brief

Three Systems That Were Never Designed to Talk to Each Other

Each salon kept its own paper appointment book, its own till, and its own weekly staff-hours spreadsheet. That setup holds up fine until someone needs an answer that spans locations — which services are actually profitable, which staff member is busiest and where, whether this week is ahead of last week. Every one of those questions meant manually reconciling three separate sources of truth, so in practice they went unasked. The client wanted one mobile app for the whole operation — the real brief was in what "one app" needed to mean.

The Problem

Same App, Three Different Operating Realities

An owner needs visibility across every location at once. A supervisor needs full control of their own salon and zero visibility into any other. A cashier needs to move through a sale quickly and must never be able to reach payroll data.

The common shortcut — one interface with controls hidden per role — looks fine in a demo and fails in production: the data still reaches the device, the permission logic lives in the UI layer where a future refactor can quietly break it, and every new feature is a fresh chance to leak one location's numbers onto another's screen.

Two more constraints shaped the build: bookings made through the group's website had to reach the app with no manual re-entry, and the app had to keep taking payments through a network outage — a POS 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

Four Load-Bearing Decisions

1

Role-based architecture with per-salon isolation

Three access levels — owner, supervisor, cashier — enforced through role-scoped Firestore rules, not UI logic. A cashier's queries physically cannot return another location's records. The isolation lives in the backend, so it can't be undone by a future UI change.

2

A QR bridge between web bookings and the till

Customers book online; the app generates and scans a QR code that resolves a booking straight to the right service and staff member at checkout. No manual lookup, no re-typing, no transcription errors during rush hour.

3

Owner-readable reporting, no portal required

Sales graphs update live as transactions close. Owners and supervisors export revenue by service, staff, or period as a PDF or Excel file generated directly on the device.

4

Offline-first by design, not as a patch

Data is cached locally; transactions queue during outages and sync automatically on reconnect. This was an architecture decision from day one, not a resilience feature added later.

The Decision

Enforce Roles in the Data Layer, Not the Interface

The default approach

Build one screen set, hide controls per role. Fast, common, and looks correct in a demo.

Why it fails here

Hiding a control doesn't stop the data reaching the device. In a multi-location business, that's not theoretical — one salon's takings could be pulled from a device sitting on another salon's counter. It also decays over time, since permission logic sitting in the UI means every future screen is a fresh chance to reintroduce the gap, silently.

What we did instead

Designed the data model — roles, scopes, isolation — before building a single screen, and enforced it as backend rules. The UI just reflects what the query returns.

What that bought

A new screen can't introduce a permission bug, because screens no longer hold permission logic. The security property belongs to the data, and holds regardless of who builds the next feature.

Under the Hood

Tech Stack & Architectural Choices

Flutter
One codebase, both app 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 updating as transactions land
flutter_barcode
Bridges web bookings into the till without manual re-entry
pdf / excel
Owner-readable reports generated on-device
Provider
State management sized to the app — no unnecessary ceremony
The Outcome

Build Facts & Delivery Results

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.

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 automatically when the network drops

Build facts, verified against the delivered app. No outcome numbers are published unless they were actually measured.

Evidence

What You Can Check 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
Shipped to both app stores under client's developer account
From delivery record
Revenue or transaction-volume change post-rollout
Not recorded

What's not shown, and why

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

  • The interface itself (client's product, illustrations only).
  • The client's name (by their preference).
  • Revenue figures (theirs to share, not ours).
Why It Matters

Architecting Systems That Stay Correct Over Time

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 never in the screens. It's in deciding where permission logic 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 as other people extend it, and can be handed to a new developer without becoming a security review. This is the same approach we bring to hospitality, retail, field services, or any multi-location build.

FAQ

Frequently Asked Questions

How long does it take to build a role-based POS app like this with Flutter?

Depends on scope, but a multi-role, offline-first POS with backend permission enforcement typically runs several months for a full end-to-end build, from data modeling through app store release.

What does a multi-location retail app with role-based access cost?

Cost is driven mainly by the number of access levels, integrations (like QR/booking bridges), and offline-sync requirements — not by screen count. Get a scoped estimate based on your actual role structure rather than a flat industry number.

Why enforce permissions in the database instead of the app's UI?

Because UI-level permission hiding doesn't stop data from reaching the device — it only hides it visually. Data-layer enforcement means a permission bug can't be introduced by a future screen or refactor.

Does the app work if the internet goes down mid-sale?

Yes — transactions queue locally and sync automatically once connectivity returns, so a network outage doesn't stop sales.

Building something where roles actually matter?

Bring the awkward parts of your requirements; those are the useful ones.