Skip to content

Data Model

Reference for the core entities that run the ERP — what they represent, the primary key / main foreign keys, life-cycle states, and the permissions that gate reads and writes. Source of truth is supabase/migrations/*.sql; the TypeScript projection lives in src/integrations/supabase/types.ts (generated) and the domain types in src/types/index.ts.

Naming conventions

  • Tables use PascalCase, quoted: public."Booking".
  • Columns are camelCase, also quoted in SQL: "bookingNo", "totalAmount".
  • Primary keys are TEXT with app-generated UUIDs (except a few newer tables that default gen_random_uuid()::text).
  • Timestamps are timestamp(3) without time zone on legacy tables, TIMESTAMPTZ on newer ones.

Entity map (top ~10 entities)

erDiagram
    Customer ||--o{ Booking : "places"
    Agent ||--o{ Booking : "sells (optional B2B)"
    TravelGroup ||--o{ Booking : "groups together"
    Booking ||--o{ BookingPassenger : "has passengers"
    Booking ||--o{ Payment : "receives"
    Booking ||--o{ Invoice : "bills"
    Booking ||--o{ VisaCase : "triggers visa"
    Booking ||--o{ TicketRecord : "triggers tickets"
    TravelGroup ||--o{ GroupInvoice : "issues to payers"
    TravelGroup ||--o{ GroupHotelAssignment : "assigns hotels"
    Supplier ||--o{ SupplierTransaction : "booked against"
    Account ||--o{ JournalLine : "debited/credited"
    JournalEntry ||--|{ JournalLine : "has 2+ balanced lines"
    Payment }o--|| Account : "lands in"
    AccountingPeriod ||--o{ JournalEntry : "contains (by date)"
    BankStatementImport ||--o{ BankStatementLine : "has lines"
    BankStatementLine }o--o| JournalEntry : "matched to"
    TDSRate ||--o{ TDSDeduction : "applied via"
    TDSDeduction }o--|| Payment : "deducted on"
    HotelInventory ||--o{ GroupHotelAssignment : "allocated to groups"

Core entities

Booking

Represents: One customer's (or partner's) purchase of travel services, attached to a travel group.

Aspect Detail
Table public."Booking"supabase/migrations/20260113130121_remote_schema.sql:91
PK id TEXT
FKs customerIdCustomer, groupIdTravelGroup, agentIdAgent (nullable), quotationIdQuotation, payerIdCustomer
Status fields statuspending | pending_finance | confirmed | cancelled | needs_correction, plus opsApprovalStatus and financeApprovalStatus (both pending | approved | rejected | cancelled | needs_correction)
Lifecycle Draft → PendingOpsReview → PendingFinanceApproval → Approved, with branches to OnHold / Cancelled. See docs/ALHUDA_ERP_STATE_MACHINES.md §Booking.
Read bookings.view
Write bookings.create, bookings.edit, bookings.delete (all RLS-enforced via auth_user_has_permission())

Customer

Represents: An end-traveller — whether they booked directly or through a B2B partner. Stores passport + KYC fields, GSTIN, Aadhaar, PAN, plus optional agent linkage.

Aspect Detail
Table public."Customer"supabase/migrations/20260113130121_remote_schema.sql:157
PK id TEXT
FKs agentIdAgent (optional — which partner introduced them), sourceAgentId (original source)
Lifecycle No explicit states — soft-deleted via deletedAt. Status is implicit through related bookings.
Read customers.view
Write customers.create, customers.edit, customers.delete

Supplier

Represents: A third-party vendor (airline, hotel, visa consultant, food caterer, transport, general). Has its own ledger (SupplierTransaction) for AP aging.

Aspect Detail
Table public."Supplier"supabase/migrations/20260330110000_suppliers.sql:2
PK id TEXT
Categories ticketing | visa | hotel | food | transport | general (src/types/index.ts:102)
Child SupplierTransaction (debit/credit/refund/adjustment)
Read inventory.view (see Sidebar drift note in docs/PERMISSIONS.md §7 — intended target is suppliers.view)
Write suppliers.create, suppliers.edit, suppliers.delete

Partner (Agent)

Represents: A B2B partner — a travel sub-agent who books on behalf of their own customers. Distinct from the staff User who logs in — a partner typically has one User account whose userId is linked here.

Aspect Detail
Table public."Agent"supabase/migrations/20260113130121_remote_schema.sql:34
PK id TEXT
FKs userIdUser (optional — the portal login)
Status active | inactive | suspended | pending
Fields commissionRate, creditLimit, totalBookings, totalRevenue
Read agents.view
Write agents.create, partners.edit, partners.delete (legacy names retained for RLS; see docs/PERMISSIONS.md §8.1)

TravelGroup (Group)

Represents: A scheduled group departure — the aggregate that flights, hotels, ground transfers, and passengers attach to. A booking is always for a group.

Aspect Detail
Table public."TravelGroup"supabase/migrations/20260113130121_remote_schema.sql:593
PK id TEXT
FKs payerCustomerIdCustomer (the customer who funds the whole group, e.g. a VIP family); many child tables (GroupHotelAssignment, GroupFlightOption, GroupInvoice, etc.)
Group types UMRAH | ZIYARAT | UMRAH_ZIYARAT | HAJ | HAJ_ZIYARAT | GENERAL_DOMESTIC | GENERAL_INTERNATIONAL (src/types/index.ts:252)
Status planning | open | full | departed | completed | closed | cancelled, with an override field (statusOverride) for manual intervention
Read groups.view
Write groups.create, groups.edit, groups.delete, group_pricing.edit (rate sheet), group_invoices.* (invoice lifecycle)

HotelInventory (Hotel)

Represents: A hotel room-night inventory record — typically leased from a supplier for a date range and a city (Makkah / Madinah / elsewhere).

Aspect Detail
Table public."HotelInventory"supabase/migrations/20260113130121_remote_schema.sql:285 (plus hotel-specific migrations 20260331100000_hotel_inventory_supplier_link.sql, 20260331130000_hotel_inventory_lease_dates.sql, 20260403000000_hotel_assignment_dates.sql)
PK id TEXT
FKs supplierIdSupplier, accountIdAccount (GL cost account)
Child GroupHotelAssignment — which group is placed in which hotel for which date range
Read hotels.view
Write hotels.create, hotels.edit, hotels.delete, hotels.export

Voucher (numbered JournalEntry)

Represents: A finance voucher — receipt, payment, contra, or manual journal. In this schema there is no separate Voucher table; a voucher is a JournalEntry with voucherNo populated.

Aspect Detail
Table public."JournalEntry" — base at supabase/migrations/20260113130121_remote_schema.sql:323; voucherNo added by supabase/migrations/20260330170000_account_hierarchy_and_vouchers.sql
PK id TEXT
Key fields voucherNo, memo, referenceType, referenceId, approvalStatus, currency, fxRate, originalAmount
Idempotency memo may be tagged [idem:<key>] — see src/lib/api.ts:2128-2155
Read finance.view
Write finance.create, finance.edit; approval / reversal gated by finance.journals.approve (default) and the break-glass finance.journals.approve_own / finance.journals.reverse_own — see maker-checker in supabase/migrations/20260418150000_journal_maker_checker_overrides.sql

JournalEntry

Represents: The header of a double-entry ledger posting. Paired with 2+ JournalLine rows that sum to zero.

Aspect Detail
Table public."JournalEntry"supabase/migrations/20260113130121_remote_schema.sql:323
PK id TEXT
Extensions currency + fxRate (20260412050000_journal_entry_currency.sql, 20260418060325_journal_fx_rate.sql), originalAmount (20260412060000), approvalStatus (20260412070000, default pending per 20260418110000), reversedEntryId, reversalReason (20260330180000_journal_reversal_tracking.sql)
Period lock Enforced by trigger — supabase/migrations/20260418150000_journal_period_lock_trigger.sql
Maker-checker Creator cannot approve / reverse own entries without override permission

JournalLine

Represents: One debit-or-credit line on a journal entry. Enforces double-entry via the sum-to-zero invariant.

Aspect Detail
Table public."JournalLine"supabase/migrations/20260113130121_remote_schema.sql:334
PK id TEXT
FKs entryIdJournalEntry, accountIdAccount
Extensions originalDebit / originalCredit for multi-currency (20260418150000_journal_line_original_amounts.sql)

Account

Represents: A chart-of-accounts ledger account. Hierarchical (group vs. ledger) with parentId self-reference. Canonical chart seeded by supabase/migrations/20260403040000_seed_chart_of_accounts.sql + canonicalised by 20260412020000_chart_of_accounts_canonical.sql.

Aspect Detail
Table public."Account" — base at supabase/migrations/20260113130121_remote_schema.sql:22, extended with parentId, isGroup, groupType, openingBalance, openingBalanceSide, sortOrder, etc.
PK id TEXT
Unique code
Types ASSET | LIABILITY | EQUITY | INCOME | EXPENSE (see AccountType enum)
Read finance.view
Write finance.edit (RLS — supabase/migrations/20260330150000_account_rls_policies.sql)

FinancialAccount vs Account

There is a separate FinancialAccount table (supabase/migrations/20260401100000_accounts.sql) representing payment instruments (bank account, card, cash, digital wallet). It is linked to a GL Account via glAccountId. Think of FinancialAccount as "the physical bank account where money actually sits" and Account as "the GL line that tracks it".

AccountingPeriod

Represents: A calendar window (usually a month) that can be open, locked, or closed for posting. Journal entries are assigned to a period by date.

Aspect Detail
Table public."AccountingPeriod"supabase/migrations/20260330190000_accounting_periods.sql:2
PK id TEXT
Status open | locked | closed
Trigger supabase/migrations/20260418150000_journal_period_lock_trigger.sql rejects posts into locked/closed periods
Read finance.view
Write Coarse finance.edit today; granular finance.periods.create | .lock | .unlock | .close and finance.years.create | .close are seeded but not all wired in code yet — see docs/PERMISSIONS.md §4.4 and §8.10

Invoice

Represents: A customer-facing bill for a booking. Installment-capable (installmentNo).

Aspect Detail
Table public."Invoice"supabase/migrations/20260113130121_remote_schema.sql:305
PK id TEXT
FKs bookingIdBooking
Status InvoiceStatus enum — OPEN | PAID | PARTIAL | VOID (see migration 20260411070000 for B2B extensions)
Sibling tables GroupInvoice (for group-level billing to a payer, supabase/migrations/20260418000000_group_pricing_and_invoices.sql:25), AgentInvoice (B2B partner invoices, supabase/migrations/20260409140000_agent_portal_v2.sql:7)
Read / Write Bookings → bookings.view; Group invoices → group_invoices.view | .create | .edit | .issue | .cancel; Agent invoices follow partner portal ownership checks

Payment

Represents: A cash / bank / card / UPI / cheque receipt against a booking.

Aspect Detail
Table public."Payment"supabase/migrations/20260113130121_remote_schema.sql:444
PK id TEXT
FKs bookingIdBooking, invoiceIdInvoice (optional), sourceAccountId (20260418123000_payment_source_account.sql)
Method cash | bank_transfer | card | cheque | upi | wallet
Status pending | verified | rejected | refunded
Razorpay razorpayPaymentId UNIQUE (dedup key for webhook — supabase/migrations/20260418114458_razorpay_payment_fields.sql)
Read finance.view
Write finance.create (record), finance.payments.verify | .reject | .refund (workflow — many still coarse-gated today; see docs/PERMISSIONS.md §8.10)

BankReconciliation

Represents: A bank statement import and its parsed lines, each of which can be matched to a JournalEntry / Payment / LedgerEntry.

Aspect Detail
Tables public."BankStatementImport", public."BankStatementLine"supabase/migrations/20260418150000_bank_reconciliation.sql:15-68
PK id TEXT on each
FKs Import.financialAccountIdAccount, Line.importIdBankStatementImport, Line.matchedEntryId → (journal_entry | payment | ledger_entry) discriminated by matchedEntryType
Status Import: imported | reconciled | partial; Line: unmatched | matched | ignored
Read finance.view (RLS — supabase/migrations/20260418150000_bank_reconciliation.sql:93)
Write finance.edit

TdsChallan

Represents: TDS (Tax Deducted at Source) infrastructure — rate master, per-deduction rows, and the plumbing for 26Q quarterly filing. Per-challan header is part of TDSDeduction.

Aspect Detail
Tables public."TDSRate", public."TDSDeduction" (+ a couple of columns on Supplier for defaults) — supabase/migrations/20260418150000_tds_scaffolding.sql
PK id TEXT (uuid default) on each
TDSRate keys section (e.g. 194C, 194J, 194Q), deducteeType (individual | huf | company | any), effectiveFrom — combined unique
TDSDeduction links to the Payment / SupplierTransaction that was deducted on, plus Form 281 challan + Form 16A certificate refs
GL 2401 TDS Payable under Duties & Taxes
Read finance.tds.view
Write finance.tds.deduct; export 26Q via finance.tds.export (permissions migration 20260418150100_tds_permissions.sql)

GstReturn

Represents: GST (India) compliance surface. As of 2026-04-18 the schema provides primitives — GSTIN on Customer / Supplier, hsnCode / sacCode on line-item-like tables, GST amount + rate on Booking, and per-line GST on group expenses. A dedicated GstReturn table is not yet present; returns (GSTR-1 / GSTR-3B) are computed on demand from the journals via src/lib/gstReports.ts. Filing to the GSTN portal is a stub (src/lib/gstnApiClient.ts).

Aspect Detail
Primitive columns Customer.gstNumber, Supplier.gstNumber, QuotationLineItem.hsnCode / sacCode, SupplierTransaction.hsnCode / sacCode, Invoice.hsnCode / sacCodesupabase/migrations/20260418150000_gst_primitives.sql
Other GST migrations 20260415090000_booking_gst_enabled.sql, 20260415100000_gst_input_credit_and_supplier_gst.sql, 20260415110000_group_expense_gst_and_journal.sql
Read finance.reports.gst_summary.view (seeded, per docs/PERMISSIONS.md §4.4)
Write Coarse finance.create / finance.edit today; dedicated return-filing permissions are future work

Supporting entities (quick reference)

Entity Table Purpose Read Write
Quotation public."Quotation" + QuotationLineItem Price quote that can become a booking quotations.view quotations.create
Lead public."Lead" + LeadAttachment Inbound enquiry leads.view leads.edit
VisaCase public."VisaCase" + VisaDocument + VisaStatusHistory Per-passenger visa processing visa.view visa.create, visa.edit
TicketRecord public."TicketRecord" + TicketDocument + TicketIssueApproval Air ticket issuance tickets.view tickets.edit, tickets.approve
BookingPassenger public."BookingPassenger" + BookingPassengerFlight + BookingPassengerHotel Passenger manifest per booking bookings.view bookings.edit
CustomerRequest public."CustomerRequest" + attachments + notes Service requests (customer / ops) requests.view requests.create, requests.edit
FinancialAccount public."FinancialAccount" + FinancialAccountTransaction Physical bank / wallet / card instruments finance.view finance.edit
LedgerEntry public."LedgerEntry" Per-booking running ledger (mirrors journal but booking-scoped) finance.view via finance.create / .edit postings
GroupInvoice public."GroupInvoice" Payer-level invoices at group level group_invoices.view group_invoices.create / .edit / .issue / .cancel
CommunicationsLog public."CommunicationsLog" Any outbound message (WhatsApp, SMS, email) Bookings / customer views module-specific

State machines (reference)

Formal state-machine rules live in docs/ALHUDA_ERP_STATE_MACHINES.md:

  • BookingDraft → PendingOpsReview → PendingFinanceApproval → Approved, with OnHold and Cancelled branches.
  • VisaNotStarted → Applied → UnderProcess → Issued | Rejected, plus SentToEmbassy detour.
  • TicketNotReady → NameUpdatePending → OnHold | FinanceCleared → Issued; issuance blocked unless finance-cleared or a GM exception proof exists.
  • GroupInvoiceDRAFT → ISSUED → PAID, with CANCELLED terminal (credit note posts reversal). See src/types/index.ts:369.

Respect the server-side validators

canTransitionBooking() / canTransitionVisa() / canTransitionTicket() and the policy validators in docs/ALHUDA_ERP_STATE_MACHINES.md §Server-Side Validators are not advisory — the API handlers call them before permitting a state change, and every transition writes to the *StatusHistory table for audit.

Drift notes

  • Legacy permission names like partners.create | .edit | .delete are retained for RLS compatibility (docs/PERMISSIONS.md §8.1); new code should prefer the agents.* family.
  • A number of granular permissions are seeded but not wired yet (finance report exports, workflow splits, admin splits) — docs/PERMISSIONS.md §4.4 and §8.10 list them. Until wired, coarse permissions (finance.create, finance.edit, admin.view, admin.edit) still gate those actions.
  • AgentWallet / wallet payment methods were removed in supabase/migrations/20260411060000_remove_wallet_system.sql; the wallet enum value on Payment.method remains for historical rows.

Further reading