All projects
  • Contract
  • Solo
  • POS
  • 2026

Operations and billing for a multi-outlet cafe chain

A back office and a table-service POS for a multi-outlet cafe chain in India. Two Next.js apps on one Postgres database, built alone, with every rule that touches money enforced in the database.

Live at six outlets with no downtime since launch. The chain plans to grow to 10 to 15 outlets on it within a year.

Timeline
September 2026 to now
Role
Solo, end to end
Status
Live in 6 outlets
Stack
Next.js, Postgres, Supabase

6 in 5 days

outlets live on the new POS

No downtime since

96%

fewer serverless requests

About 179K a day down to 5K to 7K

144

row-level security policies

Plus ~90 SQL functions and 18 triggers

₹60,000

a year in POS subscriptions replaced

₹10,000 per outlet, the client's figure

POS tables screen for the fictional demo brand, with free, busy and bill-due tables and takeaway orders
POS tables screen on a phone

Run a shift

Take an order, send it to the kitchen, print the bill, then close the day. Fictional demo brand, canned menu, under a minute.

Opening the till…

The problem

Each outlet had a manager, a few staff and a counter POS on a paid subscription. Head office had nowhere to see what was happening.

  • No nightly close at all. Sales, purchases and expenses came in through back-and-forth messages, and nobody counted the cash drawer against an expected figure.
  • Nobody could say whether an outlet made money. Some owners said theirs ran at a loss; nothing could confirm or disprove it.
  • Nothing was locked. A figure could change after the fact with no record of who changed it.
  • Salary was argued over: absences, leave, overtime and advances with no shared working.
  • The POS was an island. Its sales were re-typed into reports by hand, and its fee grew with every outlet.

What I built

One product, two apps, sharing sign-in, outlets and payment channels. Order takers only ever see billing, and the database itself stops them reading anything else.

Billing and POS, at the counter

Tables and orders
The outlet's tables at a glance: free, busy with a running total, or bill due. Unsent items survive a refresh on the device.
Kitchen tokens
Send to kitchen prints only the new items, numbered per outlet from 1 each day. Prices come from the database at send time, never from the phone.
GST bills
CGST and SGST per outlet, manager-only discounts capped at 15% with a reason, round-up to the rupee, one bill-number series with no gaps.
Silent printing
One computer per outlet prints every slip to an 80mm or 58mm thermal printer, pushed in real time. Everyone can see whether the printer is working.

Operations, for managers and head office

Nightly close
Sales per channel, purchases, expenses and a cash count. The drawer reconciles live, any difference needs a note, and sending locks the day.
Corrections
A sent day reopens only with head office approval, for 24 hours, with every change logged as old value, new value, who and when.
Salary with the working shown
Attendance feeds salary with every figure explained. A locked month stores its numbers so later rule changes never rewrite history.
Head office view
Who hasn't reported, cash differences past a threshold, live sales across outlets, profit and loss per outlet, and an accounting export.

More of the product

Real screens with a fictional demo brand and made-up data.

POS order screen adding items to a table's running tab
Operations app nightly close in progress, with sales per channel and the cash drawer reconciling
Head office home: sales so far today, what needs attention per outlet, and live billing figures
Bill dialog with customer details, discount options and the total before printing
POS tables screen on a phone
POS order screen on a phone, with the menu by category

Architecture

Tap any part to see the decision behind it and what it cost.

People

Two apps, on Vercel

One Supabase project

Postgres

Decision
Writes on money tables go through SQL functions, not table writes. Billing tables have no insert or update policies at all.
Why
Role, outlet, order state and price are checked in one transaction, whichever app calls.
Trade-off accepted
Two codebases depend on one schema, so every migration lives in one place and the generated types are shared.

At the counter

Key decisions and trade-offs

  • One database for two apps

    Why: Bills must flow into the nightly report, and one account works in both apps

    Cost: Both codebases depend on one schema

  • Business rules in Postgres

    Why: Access and money rules hold even if someone calls the API directly

    Cost: Harder to unit test, covered by a live security suite

  • Store computed salary, not just inputs

    Why: A locked month must read the same after rules change, and they did, twice

    Cost: Unlocking a month is head office only and recalculates from scratch

  • No ORM, validation, state or component library

    Why: Small surface, fast pages, one person maintains both apps

    Cost: More hand-written code

  • Push, not poll, for printing

    Why: Printer traffic scales with slips, not with time

    Cost: A realtime connection to keep healthy

Hardest problems

  1. Keeping order takers out of the back office

    Problem
    Billing added a new role that shares outlet membership with managers. Almost every existing policy trusted "is this user linked to this outlet", so an order taker would have inherited access to cash, salary and expenses.
    Approach
    Audited every policy and storage bucket before the billing schema went in. Rewrote the shared membership helper so it ignores order takers, which closed every policy built on it at once, and gave billing its own helper.
    Result
    A live test signs in as an order taker and gets zero rows from every back-office table and bucket, and every write is refused. It also closed an older gap: a deactivated manager with a live session could read through the API.
  2. Bill numbers with no gaps, under concurrent taps

    Problem
    One bill-number series across all outlets, restarting each April, never reused, never skipping. Several phones and a counter PC sending at once on unreliable connections.
    Approach
    A counter row incremented and returned in one statement, so its lock queues callers, and called inside the same transaction that writes the bill, so a failure rolls the number back too. Idempotency keys handle the retries.
    Result
    20 concurrent sends give 20 distinct, consecutive token numbers. Two simultaneous bills with one key return one bill.
  3. Silent printing on ordinary Windows PCs

    Problem
    No native agent, non-technical staff, any printer model. On the first real counter PC a dialog flashed, nothing printed, and the Windows queue stayed empty.
    Approach
    Found that Chrome's kiosk printing uses the last printer chosen in that profile, defaulting to Save as PDF. The app now generates a launcher that writes the Windows default printer into Chrome's settings before starting, refuses PDF and virtual printers, and keeps a minimised window printing.
    Result
    The one rule for staff became "the bill printer is the Windows default". Slips match the outlets' old printed layouts at 80mm and 58mm.
  4. 96% fewer requests by pushing instead of polling

    Problem
    Printer computers polled every few seconds. Five printers made about 179K serverless requests a day for about 200 slips.
    Approach
    Print jobs are pushed in real time. The printer checks in every 10 seconds only while someone else at the outlet is online, every 5 minutes when alone. Links prefetch only on hover or touch.
    Result
    About 179K requests a day down to 5K to 7K, while the outlets print about 150 slips a day.
  5. Money that changes after the day is closed

    Problem
    Bills get cancelled, restored or paid later after the nightly report that counted them was sent and locked. Managers were also typing pay-later bills in by hand, so money was counted twice.
    Approach
    A change to one bill moves a sent report by exactly that bill's amounts. Hand-typed dues are linked to the matching bills when the customer and the total agree. Each migration was dry-run in a rolled-back transaction first.
    Result
    One ledger of who owes what across both apps. The linking matched 12 hand-typed dues to 26 bills in existing data.

Outcomes

In use

  • Six outlets billing live: the first two on 29 Oct 2026, all six by 2 Nov. No downtime since.
  • Outlets close their day in the app: a locked report, a reconciled drawer, and every difference explained in writing.
  • The chain can now see that outlets reported as loss-making are not (client).
  • Built to grow: the chain plans 10 to 15 outlets on it within a year, and a new outlet needs no licence and no deployment.

Under the hood

  • 42 migrations, about 50 tables, about 90 SQL functions, 144 row-level security policies and 18 triggers.
  • 490+ unit tests and a live security suite. Every migration is checked object by object against production.
  • Serverless traffic cut by 96%, so printing scales with slips, not with time.

Not used yet, and I'd rather say so

Built and tested but not used yet: outlet audits, correction requests, the accounting export (no ledgers set up yet) and order-taker accounts. Managers run the counter themselves for now.

Stack

App
Next.js 16, React 19, TypeScript, Tailwind 4, server actions
Database
Postgres, row-level security, PL/pgSQL functions and triggers, Supabase Auth, Storage, Realtime
Around it
thermal printing via Chrome kiosk, Excel and accounting exports, SMTP mail
Testing
320 + 171 unit tests, live security suite, in-memory Postgres schema check

My role

Solo, on contract. I built both apps end to end.

  • Requirements with the owner and head office, written up as specs with every client decision dated and every open question marked rather than guessed.
  • Database design, security model, migrations and the tooling to verify them.
  • Every screen in both apps, for phones, tablets and laptops: 73 in all.
  • Thermal printing on real counter PCs, deployment, loading the real menus, and staff onboarding with one recorded walkthrough.

Timeline

  1. 28 Sep 2026

    Operations specs written

  2. 2 Oct

    Operations app started: nightly close, corrections, attendance and salary, ordering

  3. 15 Oct

    Customer dues, Excel reports, head office dashboard

  4. 18 Oct

    Billing specs signed off, POS app started

  5. 28 Oct

    Silent printing fixed on the first real counter PC

  6. 29 Oct

    First two outlets billing live

  7. 2 Nov

    All six outlets billing

  8. 3 Nov

    Realtime push to printers replaces polling

  9. 12 Nov

    Dues paid back at the counter, who-owes-what per customer

Want the longer version, or the parts I couldn't put on a page?

Ask me about it

More work

  • Solo
  • AI
  • Launched in 2 days
  • 2026

Three AI hackathon judges who read your repo before the real ones do. Every README claim is traced to the file that would implement it, you answer three questions against a 30-second clock, and your odds move live. The model never sets a number.

0numbers set by the model

$0.006per full judging session

  • Co-founder
  • Full stack
  • AI search
  • 2026

Answers from a team's Slack, Google Drive and Notion. Every source is cited, nobody sees a document they couldn't already open, and each answer says whether it's still true.

0permission leaks found

90%of questions find the right source in the top 8, up from 80%

All projects

Got something worth building?