Skip to content

Bookings

The Bookings module is the operational heart of the ERP. Every paying customer that travels with Al Huda — whether directly or through a B2B partner — is attached to a Booking row. A booking ties together a customer (the primary traveller / contact), a travel group (the package they joined), one or more passengers, a rate breakdown, and a payment lifecycle that is tracked independently for Ops and Finance.

Where it lives

  • List view: src/pages/sales/Bookings.tsx:68
  • Detail view: src/pages/sales/BookingDetail.tsx:177
  • Create wizard page: src/pages/sales/BookingWizard.tsx:4
  • Import dialog: src/components/bookings/BookingImportDialog.tsx
  • List math + filter helpers: src/pages/sales/bookingsMath.ts
  • State machine (canonical): docs/ALHUDA_ERP_STATE_MACHINES.md:17

1. What a booking is

A booking represents a paid or in-progress sale for a single departure. It owns:

Slot Where Purpose
customerId primary field Who the booking is for (always a Customer row — wizard creates one if the passport is new)
payerId optional Different billing party (e.g. a family head paying for multiple relatives)
groupId required for grouped sales Which travel group this booking joined
agentId + source='agent' partner bookings Invoiced on account against the partner ledger
passengers[] Booking Passenger rows Actual travellers (≥ 1, usually equal to passengerCount)
adultRate / childRate / infantRate money per tier Rate per head for the three age tiers
ticketRate / visaRate / hotelRate / mealsRate / groundRate breakdown Component split of the adult rate (for reporting, invoice printing, and P&L)
totalAmount, paidAmount, balanceAmount, gstAmount, grossAmount ledger-backed Money state; mirrored into the General Ledger via finance vouchers
status lifecycle Ops-facing status (draft, confirmed, on_hold, cancelled, needs_correction)
financeApprovalStatus / opsApprovalStatus twin-track Two independent approval tracks — see the state machine below

2. List view (/sales/bookings)

src/pages/sales/Bookings.tsx:68 renders the full bookings queue. It is a virtualized table (VirtualTable, src/pages/sales/Bookings.tsx:1777) with per-column filters and a React-Query cache keyed by ['bookings'] (src/pages/sales/Bookings.tsx:65).

Columns

Column Source Notes
Booking # b.bookingNo or fallback #<last-6-of-id> Mono, tracking-wide (Bookings.tsx:1788)
Customer formatPersonName(b.customer) Name + email
Group b.group?.name Shows Assigned or Unassigned pill
Amount grossAmount + balance Colour-coded: green when fully paid, amber when due
Pax b.passengerCount Center-aligned
Approvals finance + ops dots See approval statuses below
Status b.status Pill: Confirmed / Cancelled / On Hold / Draft (Bookings.tsx:1876)

Filter bar

Located at the top of the page. Filters are client-side predicates through buildBookingFilterPredicate (src/pages/sales/bookingsMath.ts:79):

  • Search — matches booking number, customer name, email, phone
  • Status — all / confirmed / cancelled / on hold / draft / needs correction
  • Finance — payment state
  • Group — filter to one group
  • Trip Type — Umrah / Haj / Ziyarat / Umrah & Ziyarat / Haj & Ziyarat / Domestic / International (Bookings.tsx:1714)

Statuses

The canonical state machine lives at docs/ALHUDA_ERP_STATE_MACHINES.md:17. Summary:

status Meaning Set by
draft Wizard just created it; not yet routed Wizard submit
confirmed / approved Finance + Ops have both approved Approvals workflow
on_hold Deliberately paused (e.g. waiting on passport) Approvals
cancelled Soft-cancelled; journals reversed Approvals / delete
needs_correction Sent back for a fix by Ops or Finance Send Back dialog (Bookings.tsx:2181)

Approvals are twin-tracked: financeApprovalStatus and opsApprovalStatus are each pending / approved / rejected / needs_correction. Both must end up approved before the booking is live. The list column renders two coloured dots summarising both tracks (Bookings.tsx:1849).

3. Detail view (/sales/bookings/:id)

src/pages/sales/BookingDetail.tsx:177 is the full record screen. It pulls three datasets in parallel: the booking itself, the Journal Entries referencing it, and the Audit log of every write against it.

Tabs (via Tabs from @/components/ui/tabs):

  • Overview — customer card, rate breakdown, KPI cards (gross / paid / balance)
  • Passengers — list of BookingPassenger rows with expandable flight + hotel panels (PassengerFlightsPanel, PassengerHotelsPanel)
  • Finance — linked journal entries, with drill-down to the voucher (VoucherDetailDialog)
  • Audit — every mutation, with a UserChip popover showing actor + role (BookingDetail.tsx:85)

Drill-down helpers:

  • openCustomerLedger(booking.customer) opens the party ledger dialog.
  • PartyLedgerDialog reuses fetchCustomerLedger from src/services/ledgerService.ts.

4. Key actions

All actions are gated by <PermissionGate>. Source references below.

Action UI entry Permission Path
Create booking "New Booking" button bookings.create Bookings.tsx:1753/sales/bookings/new
Import bookings "Import" button (CSV/XLSX) bookings.create Bookings.tsx:1755BookingImportDialog
Edit booking Pencil / detail view bookings.edit Bookings.tsx:2072
Add passengers "Add Passengers" bookings.edit Bookings.tsx:2080AddPassengersDialog
Send message "Send Message" bookings.view Bookings.tsx:2092SendMessageDialog
WhatsApp Green WhatsApp button bookings.view Bookings.tsx:2103sendWhatsAppText
Print invoice Printer icon bookings.view Bookings.tsx:2125GET /sales/bookings/:id/invoice + printInvoice
Add payment "Add Payment" finance.create Bookings.tsx:2141POST /finance/payments
Request visa case "Request Visa" visa.create Bookings.tsx:2154POST /visa
Send back "Send Back" (to Ops) approvals.approve Bookings.tsx:2181
Resubmit to Finance visible when status==='needs_correction' bookings.edit Bookings.tsx:2187
Delete booking Trash button bookings.delete Bookings.tsx:2192
Customer ledger "Customer Ledger" button customers.view Bookings.tsx:1942

Deletion is soft by default

DELETE /sales/bookings/:id soft-deletes the row and reverses any posted journals. A hard delete is only possible when the booking has zero vouchers attached.

5. Wizard entry point

Clicking New Booking navigates to /sales/bookings/new (src/App.tsx:161), which renders BookingWizardWizardShell.

See Booking Wizard for the full walkthrough.

6. Rate breakdown

The booking carries a component breakdown of the adult rate so that finance can report revenue per service:

adultRate = ticketRate + visaRate + hotelRate + mealsRate + groundRate

The wizard auto-recomputes adultRate whenever any of the five components change (src/components/bookings/wizard/WizardShell.tsx:83). The edit dialog on the detail page follows the same invariant.

childRate and infantRate are independent overrides — if left blank they inherit the adult rate.

GST is a separate line: gstEnabled, gstRate, gstAmount. When enabled, the booking is printed as a tax invoice with CGST/SGST or IGST as configured globally (admin.currency/admin.config.edit). Per-booking override is allowed; empty values fall back to the server-side default (WizardShell.tsx:383).

7. Payment tracking

Payments are recorded separately in the Payment table and posted to the General Ledger via finance vouchers. The booking itself carries only the running totals:

  • paidAmount — rollup of verified receipts allocated to this booking
  • balanceAmountgrossAmount - paidAmount
  • gstAmount, grossAmount — only populated when gstEnabled

The "Add Payment" button (Bookings.tsx:2141) opens a dialog that posts POST /finance/payments with { bookingId, amount, method, reference, status: 'verified' }. Once verified, the booking's paidAmount increments (via applyBookingPaymentIncrementbookingsMath.ts). The wizard's first payment uses the same endpoint (WizardShell.tsx:394).

Partner bookings are billed on account

When source === 'agent', paymentPolicy is forced to on_account (WizardShell.tsx:380). The partner's ledger carries the receivable; the customer never sees an invoice directly. No "first payment" is taken at booking creation.

8. Permissions

Single source of truth: docs/PERMISSIONS.md §6.4.

Action Permission
View list + detail bookings.view
Create (wizard, import) bookings.create
Edit, add passengers, resubmit bookings.edit
Delete bookings.delete
Print invoice, send message, WhatsApp bookings.view
Add payment finance.create
Request visa visa.create
Send back approvals.approve
Customer ledger customers.view

Roles that hold bookings.* in full: CEO, GM, IT_ADMIN, ADMIN_HR, SALES_MANAGER, SALES_EXEC, B2B_MANAGER, B2B_EXEC (see docs/PERMISSIONS.md §5). OPS_MANAGER, OPS_EXEC, FINANCE_MANAGER, ACCOUNTANT, CASHIER, TICKET_MANAGER, VISA_OFFICER, AUDITOR hold bookings.view only.

9. Lifecycle diagram

stateDiagram-v2
  [*] --> draft: Wizard submits
  draft --> pending_ops: Routed to Ops
  pending_ops --> pending_finance: Ops approves
  pending_ops --> needs_correction: Sent back by Ops
  pending_finance --> confirmed: Finance approves
  pending_finance --> needs_correction: Sent back by Finance
  needs_correction --> pending_ops: Resubmitted
  confirmed --> on_hold: Paused (e.g. visa wait)
  on_hold --> pending_ops: Resume review
  confirmed --> cancelled: Cancel (reverses journals)
  draft --> cancelled: Cancel before routing
  cancelled --> [*]