Skip to main content

Data Model

Royal Glow runs on Neon PostgreSQL 16 with Drizzle ORM (pure TypeScript, edge-native). The schema lives in packages/db/src/schema/ as 15 files defining 38 tables that cover auth, profiles, services, scheduling, bookings, billing, memberships, offers, CRM, leads, loyalty, notifications, branches, and system operations.
Every monetary value is an integer in paise (₹1 = 100 paise) — never a float. Every timestamp is timestamptz (stored UTC, displayed IST). Prices are GST-inclusive at 18%.

Conventions

These rules are mandatory across every table.

Schema files & table groupings

The 15 schema files in packages/db/src/schema/ map to 38 tables. The tree below lists the actual pgTable names defined in each file.
The auth.ts tables (user, session, account, verification) are owned by Better Auth’s Drizzle adapter. Don’t add custom columns to them — extend via customer_profile / staff_profile instead. Enums are defined separately in enums.ts (not counted as a table). Totals: 15 files, 38 tables.

Key enums

Defined natively in PostgreSQL via enums.ts.

ID & number formats

H = salon (hair/beauty), S = spa. The -M suffix marks a membership session booking.

GST (18% inclusive)

Customer-facing prices already include GST. The invoice back-calculates the base and tax from the inclusive amount.

Loyalty (gems) rules

  • Earn: 1 gem per ₹100 invoiced (floor), on invoice_type = 'service' only.
  • No gems on membership_purchase or membership_session invoices.
  • Expiry: 365 days from earn date, auto-deducted by the QStash gems-auto-expire job.
  • Redemption: against specific catalogue services — not a ₹ discount.
  • Cannot combine with an offer on the same booking.

Booking lifecycle

Walk-ins skip pending and go straight to confirmed. Status transitions are recorded in booking_status_log.

Business rules enforced at the DB level

Last modified on June 29, 2026