It's wedding season. A family pays an advance at the desk. Before they leave, someone has to check the register to make sure the date is free, write a receipt for each of three businesses with the right number from each book, and remember what is still owed on the day of the event. Then do it again for the next family.
Shree Gowri Shankara Kalyana Mantapa is a traditional wedding hall in Hanur, Karnataka. Tavra Labs built the system that replaced its handwritten register: a bilingual public website that takes enquiries, and a staff dashboard that turns each enquiry into a booking, records every payment and prints numbered Kannada receipts. This is how it works, and why it's built the way it is.
The problem: why the register fails
A paper register is a good record of what happened. It is a poor guard against what shouldn't happen.
- A date can only be sold once, but the register can't warn you when you are about to sell it twice.
- One event, three businesses. The venue and two service partners bill each event together, and each needs its own receipt in its own number series.
- Money arrives in parts. An advance at booking, the balance on the event day, and that balance is often paid a little short.
- Numbers restart every April. Receipt serials run per Indian financial year.
- Kannada first. Staff work in Kannada, and receipts must be printed in Kannada.
- Prices change between seasons, but a booking must keep the price it was sold at.
Every one of these is a rule staff carried in their heads and enforced with a pen. The job was to move those rules into software, where they hold even on a busy day.
The constraints we accepted
- No server to run. A small business with no IT staff shouldn't own a backend. The browser talks to the database directly, and database rules are the access control.
- Kannada first. Dashboard, public site and printed receipts are in Kannada. Only Advance, Balance, Invoice and GST stay in English.
- Paper stays the reference. Receipts print on A4, and the old register can be back-entered.
- Two kinds of staff. Admins handle money. Executives check dates and work enquiries, nothing more.
- Money adds up to the rupee on every receipt, every time.
Architecture
One Next.js app on Vercel serves both the public site and the staff dashboard. There are no API routes and no server actions: every read and write goes from the browser to Cloud Firestore through the client SDK, and Firestore's security rules decide what each person may do. Staff sign in with Google; the public enquiry form writes without signing in, but only in a shape the rules accept.
The data model is small. A booking holds its dates, a frozen price snapshot and a running payment summary; payments and receipts sit underneath it. Enquiries, staff-blocked dates, configuration and one receipt counter per financial year complete it.
Key decisions and trade-offs
No backend over an API server
- Options
- Run an API server in front of the database, or let the browser talk to Firestore under security rules.
- Choice
- No backend. The rules are the access control, kept in the repo as one file.
- Why
- Nothing to patch, scale or pay for, and every permission is auditable in one place.
- Cost
- Every guarantee must be expressible as a rule or a client-side transaction. We built a Telegram notifier with server code, then removed it to keep the app server-free.
Status derived, not stored
- Options
- Store a status field and update it, or work it out from the facts each time.
- Choice
- One function derives the status from payments, balance and event date.
- Why
- "Balance Pending" can't go stale, and every screen agrees.
- Cost
- Queries can't filter on a derived status directly.
Freeze prices per booking, with a version
- Options
- Price receipts from current settings, or snapshot prices onto each booking.
- Choice
- A versioned price snapshot, saved when the booking is created.
- Why
- Changing prices never rewrites a receipt already issued.
- Cost
- Two versions of the receipt maths live side by side.
One write path for money
- Options
- Let each screen write payments, or route every payment through one function.
- Choice
- One function validates each payment and updates the booking's payment summary in the same transaction.
- Why
- Dashboards read one summary instead of adding up payments, and totals can't disagree.
- Cost
- Discipline: nothing may write a payment any other way.
The hard part: one payment, three receipts, one number series
When a family pays, the money belongs to three businesses. The system has to decide how much goes to each, print a receipt for each, and give each receipt the next number in this financial year's series, without ever printing a duplicate or skipping a number.
The split
The advance is divided by a fixed ratio between the three businesses, rounded to ₹500 steps so receipts carry round figures, and capped at what each business is actually owed. Whatever rounding leaves over goes to the last business, so the parts always add up to exactly what was paid. The balance works differently: the venue's electricity charge is paid first, then the rest is shared in proportion to what each business is still owed. If the family pays a little short, the shortfall is recorded as a discount and spread across the businesses, and the booking closes.
The numbers
Each receipt has a deterministic id built from the booking, the receipt type and the business. Issuing it runs in one Firestore transaction: check that the receipt doesn't already exist, take the next serial from this financial year's counter, and write both. If two phones press "Generate" at the same moment, one wins and the other finds the receipt already there. No duplicate is printed and no number is skipped or reused.
History that doesn't move
The third business joined after the system went live. A dated rule keeps bookings made before then on the split they were sold under, and the versioned price snapshot keeps their line items intact. Old receipts reprint exactly as they were first issued.
Enquiry to receipt: the lifecycle
Most bookings start on the public site. The enquiry form is Kannada by default, with an English toggle, and the database rules check its shape before accepting it: an Indian mobile number pattern, field sizes and a server timestamp.
From there, an enquiry moves through a short pipeline: new → called → marked for booking → converted, or not interested with a required reason. An executive can call back and hand an enquiry to an admin; only an admin creates the booking, which links back to the enquiry it came from.
A booking is saved either as a draft, with no advance, or confirmed, with the booking and its advance written in one atomic batch so a failed save can't leave a date held with no money recorded. Once the event date arrives with money still owed, it shows as Balance Pending. The balance payment makes it Completed; a cancellation, with a reason, releases the dates.
The code also refuses what shouldn't happen: an advance as large as the total, a balance above what's owed, and any payment or receipt on a cancelled booking.
Security, and what we'd do differently
With no server, the security rules carry all of the access control:
- Staff must be on an allow-list and have a verified email.
- Only admins can write money: payments, receipts and prices.
- An admin can't demote or delete themselves, so the venue can't lock itself out.
- The public enquiry form is schema-checked by the rules, not just by the form.
Two things we'd tell you about before you asked:
Double booking isn't atomic yet. The check for a taken date reads before it writes, and staff-blocked dates are enforced by the calendar rather than the database. With two admins this holds in practice. The fix, a per-date lock document created in the same transaction and made create-only in the rules, is designed and is the next step. It isn't live yet.
Tests are thin. Only the pricing and receipt maths has an automated check. Turning this into a platform means tests around payments and receipt numbering first.
Results
We haven't measured time saved or bookings handled, so we won't put numbers on it. What changed is easy to describe:
- Income-tax filing got easier. Every rupee has a numbered receipt per business, in one series per financial year, so year-end filing comes from the system instead of the register.
- One place for the record. Bookings, payments, receipts, enquiries and blocked dates live in one system instead of a register and loose receipt books, and the old register can be imported.
- Numbers the register couldn't give. The Command Center shows this month against this financial year (bookings made, booked value, money collected) and what is still outstanding.
- Roles that match the desk. Admins handle money; executives check dates and follow up enquiries.
From one venue to a platform
Most of what makes this work isn't specific to one hall. The single payment write path, race-safe receipt numbering, the capped splitter, price snapshots, derived status, the enquiry pipeline, the availability calendar and the role model all carry over.
What's specific today (the three businesses, the pricing formula, wedding fields, Kannada copy and branding) lives in code. The path to the Tavra venue booking platform is to move those into configuration first, then add resources and time slots, then run many venues on one system.
It fits wedding, banquet and community halls as it stands. Auditoriums need time slots; guest houses, studios and turfs need room or hourly inventory and online payment.
What this means for your venue
Money that always adds up, history that never changes, no server to babysit, and software in your staff's language. If you run a venue or any booking-led business, talk to us about running on the platform, or about a build of your own.