ERP Systems

One room, sold once an hour, with the money accounted for.

Booking, roster, invoicing and staff commission for a one-room recording studio, with the rules that matter enforced in the database rather than the interface.

Client
Native Jamstudio
Practice
ERP Systems
Published
14 August 2026
31 daysempty repo to a studio running its own bookings, invoices and payouts
0overlapping bookings the system can accept, by database constraint
324silent underpayments found and fixed before a rupee was paid
The problem

One room. Every hour can only be sold once.

Native Jamstudio is a jam and recording studio in Churachandpur with one room in it. That single fact decides the shape of the whole business. The studio does not sell a product it can restock; it sells an hour, and an hour that has been promised twice cannot be fixed later by apologising. Two groups arrive, both have paid something, and one of them goes home. The cost of that mistake is not the refund. It is the reputation of a venue that people book on a friend's recommendation.

The money has the same shape. A session is quoted at one figure, an advance is collected before the room is held, and the balance is taken in person on the day, frequently by whoever happens to be on shift rather than by the owner. Once staff are paid a percentage of what they collect, the person recording the payment and the person benefiting from the number are the same person. That is not a suspicion about anyone. It is a structure, and a structure has to be answered structurally.

  • 01One room, so a scheduling clash is a lost customer, not a delay
  • 02Advance collected before the hour is held, balance taken in person
  • 03Staff on shift record money they are later paid a percentage of
  • 04Three kinds of user, with genuinely different reach, on one screen
  • 05No staging environment and no rehearsal: it takes real payments from day one
The approach

Put the rules where they cannot be bypassed.

The ordinary way to stop a double booking is to check for one in the booking screen. That works until something reaches the data another way: a second tab, a retried request, two people tapping confirm in the same second, or any future screen written by anyone who forgets the rule. An interface check is a request. We wanted a refusal.

So the rules that would be expensive to get wrong live in the database itself. An overlapping hour is rejected by the table, not by the form. A staff member's reach is decided by a role-gated function, written as a list of who is allowed rather than a list of who is barred, so that adding a fourth role next year fails closed instead of quietly granting it everything. The interface is where the studio works. It is not the arbiter.

Customer, onlineOwner panelAssistant, on shiftRole-gated functionsallowlist, never denylistTHE TABLE ITSELFExclusion constraintsan overlapping hour: refuseda double roster shift: refused
Three kinds of user, one write path, and the refusal at the bottom rather than the top. The booking form, the owner's panel and an assistant's phone all reach the same role-gated functions, and the table itself is what rejects an overlapping hour. Nothing can route around it, because there is no other way in.

The same principle decided how the studio handles a booking it wants off the books. Nothing deletes. A cancelled or mistaken session is archived, keeping the row and the audit trail, because a hard delete would orphan an invoice that had already been issued and would hand the person who records the money the ability to erase the record of it. Deleting is easy to build and impossible to undo, which is the wrong trade for a business whose books are the thing being protected.

On top of that foundation the studio got what it actually runs the day on: a public site and booking flow with a real calendar and a UPI advance, and an owner panel that installs to the phone's home screen — the day's bookings, history and takings over any date range, invoices, the rate card, the site's own copy, staff and their roster, and commission and payouts. Three roles, each with a different and deliberately limited reach: the owner, a co-admin who handles bookings and invoices, and an assistant who can see the whole day but can only act inside the hours he is actually rostered for.

The outcome

Measured, not estimated.

Thirty-one days from an empty repository to a studio running its own bookings, invoices, roster and staff payouts, live on real payments. Six hundred and twenty-five automated checks run green on the current code. An overlapping booking is not merely discouraged, it is refused by the table, so the count of double-bookings the system can accept is zero and stays zero no matter what is built on top of it later.

The result we would put first is one nobody would have noticed if we had not gone looking.

The staff commission formula was implemented exactly as the approved specification wrote it. Rather than trust that, we ran it against the live database across every plausible combination of hours, rate and amount: 324 wrong answers in 314,600, each one a rupee short, and every single one in the assistant's disfavour. The cause was a rounding step in the wrong position. All four worked examples in the specification happened to divide evenly, so building precisely what was asked for and testing precisely what was specified would have shipped it, and the people underpaid would have been the people least able to audit it. It was corrected before a single payout was made.

That is what we mean by measuring instead of estimating, and it is worth being just as plain about the limits. At the time of writing, the roster and commission system is built, applied and reviewed, but no assistant has yet been invited on the live studio, so every commission figure on the panel currently reads zero and is correct in doing so. The next step is not more building. It is ten minutes of the owner's time putting a real person through it. We would rather say that than let a number imply a proof it has not earned.

Built with
  • Supabase
  • TypeScript
  • Resend
  • Vercel
  • Claude