Low-Level Design
This document captures the mechanics: the booking state machine, ID/number formats, GST and gems math, all 19 background jobs, request lifecycles, indexes, and security implementation. Money is always integer paise; status transitions are always logged in
booking_status_log.Booking State Machine
Walk-ins skip
pending — they go directly to confirmed. Every status transition is recorded in booking_status_log with timestamp, actor (user ID), and reason.ID & Number Formats
- Tab Title
- Tab Title
H= salon (hair/beauty)S= spa-Msuffix = membership session
GST Calculation
All prices are GST-inclusive (18%, SAC 999721). Back-calculate the taxable base:base + gst always reconstructs the original inclusive amount exactly.
Gems (Loyalty) Calculation
No-Show Policy Logic
Recovery: 3 consecutive completed bookings reset
no_show_count. Walk-in no-shows do NOT count.
Background Jobs (19 Total)
- Tab Title
- Tab Title
- Tab Title
QStash scheduled jobs — including the seven that were formerly pg_cron.
Realtime Channels (Ably)
Token Auth: clients receive a scoped Ably JWT from
POST /api/ably/token. The token is scoped to only the channels the user is allowed to subscribe to (based on their role and user ID).
Request Lifecycles
The four core write paths, step by step.- Tab Title
- Tab Title
- Tab Title
- Tab Title
Customer submits the booking form →
POST /api/bookings.1
Validate session (
requireSession).2
Zod-validate the request body.
3
Check slot availability (Redis cache → DB).
4
Validate all service IDs exist and are active.
5
Get the default staff for each service.
6
Generate the booking number (
BK-RS-YYMM-H/S-XXXXX).7
INSERT the
booking row (status: pending).8
INSERT
booking_service rows (price snapshot).9
Enqueue QStash job 17 (stale booking alert, +2h).
10
Invalidate the Redis slot cache.
11
Publish an Ably event to
admin:bookings:{branchId}.12
Return
{ success: true, data: { bookingId, bookingNumber } }.