Sales
The Sales module is the customer-facing funnel: Leads → Quotations → Bookings → Customers. Everything a sales executive does before a customer becomes a paying traveller lives here.
Where it lives
All Sales pages sit under src/pages/sales/ and a few siblings:
src/pages/leads/Leads.tsx:65— Leads pipeline (routed at/sales/leads)src/pages/sales/Quotations.tsx— Quotations list + dialogsrc/pages/sales/Bookings.tsx:68— Bookings list + detail peeksrc/pages/sales/BookingDetail.tsx:177— Booking detail pagesrc/pages/sales/BookingWizard.tsx:4— New-booking wizard wrappersrc/pages/sales/Customers.tsx— Customers mastersrc/pages/sales/bookingsMath.ts,customersMath.ts,quotationsMath.ts— pure helpers (stats, filters, patch-merge)
Routes (all under /sales/*, see src/App.tsx:158):
| Route | Component | Permission |
|---|---|---|
/sales/customers |
Customers |
customers.view |
/sales/leads |
Leads |
leads.view |
/sales/quotations |
Quotations |
quotations.view |
/sales/bookings |
Bookings |
bookings.view |
/sales/bookings/new |
BookingWizard |
bookings.create |
/sales/bookings/:id |
BookingDetail |
bookings.view |
1. The sales pipeline
flowchart LR
Inquiry([Customer inquiry]) --> Lead[/Lead/]
Lead -->|Contact + qualify| Qual{Qualified?}
Qual -->|no| Closed[Close lead]
Qual -->|yes| Convert{Convert path}
Convert -->|Quote customer| Quote[/Quotation/]
Convert -->|Skip quote| Book[/Booking/]
Quote -->|Send to customer| Sent[sent]
Sent -->|Accept| Accepted
Sent -->|Expire / reject| Dead[rejected or expired]
Accepted --> Book
Book -->|Ops + Finance approve| Confirmed([Confirmed booking])
Closed --> End([End])
Dead --> End
Confirmed --> End
Each step is a distinct page with its own permission, its own API surface, and its own status machine.
2. Leads (/sales/leads)
src/pages/leads/Leads.tsx:65 is a lightweight CRM for inbound inquiries — customers who called, WhatsApp'd, filled a web form, or walked in.
Fields captured on create
defaultLeadForm (Leads.tsx:54):
fullName,phone,email,citytravelType—umrah,hajj,umrah_plus,custom(Leads.tsx:47)groupSizepreferredMonthmessage— freeform
Status pipeline
statusOptions (Leads.tsx:39):
| Status | Meaning |
|---|---|
new |
Just captured; no outbound contact yet |
contacted |
Rep has reached out |
qualified |
Budget / intent confirmed; ready to quote |
converted |
Became a quotation or booking |
closed |
Dead lead (no-intent / duplicate / stale) |
Actions
| Action | Permission | Path |
|---|---|---|
| View page | leads.view |
Route |
| Create lead | leads.edit |
Header button |
| Update status | leads.edit |
Detail dialog |
| Convert to quotation | quotations.create |
Detail dialog → calls convertLeadToQuotation |
| Convert to booking | bookings.create |
Detail dialog → calls convertLeadToBooking |
Services: src/services/leadService.ts — fetchLeads, createLead, updateLeadStatus, convertLeadToQuotation, convertLeadToBooking.
3. Quotations (/sales/quotations)
src/pages/sales/Quotations.tsx is the quote management workspace. A quotation is a non-binding price offer the customer accepts or rejects; accepted quotes convert to bookings.
Quotation form
Shape defined in QuotationFormData (Quotations.tsx:65):
customerId,groupId,agentId(if sold through a partner)packageDetails:flights[],hotels[],meals[]— inventory IDs + display labels (operators pick from dropdowns)visa,duration,roomType,requestedAmount,specialRemarkspassengers[]— same shape as booking passengers
totalAmount,discount,currencyvalidUntil— expiry date (ISO)status—draft/sent/accepted/rejected/expirednotes
Status lifecycle
stateDiagram-v2
[*] --> draft: Create
draft --> sent: Send to customer
sent --> accepted: Customer approves
sent --> rejected: Customer declines
sent --> expired: Past validUntil (auto)
accepted --> [*]: Convert to booking
rejected --> [*]
expired --> [*]
draft --> [*]: Delete
isQuotationExpired (src/pages/sales/quotationsMath.ts) is the client-side flag.
Actions
| Action | Permission | Source |
|---|---|---|
| View list | quotations.view |
Route |
| Create | quotations.create |
Header button |
| Edit | quotations.create (treat-as-edit) |
Row action |
| Send to customer | quotations.create |
sendQuotation (Quotations.tsx:54, line 645) |
| Convert to booking | bookings.create + quotations.create |
createBookingFromQuotation (Quotations.tsx:58, line 515) — also flips status to accepted if not already (Quotations.tsx:561) |
| Delete | quotations.create |
Row action |
Quotations permissions are under-split
Edit / send / delete all currently gate on quotations.create. docs/PERMISSIONS.md §6.3 marks this as a future split into quotations.edit / quotations.delete. Do not introduce new finer checks without also updating the matrix and seed migration.
Services: src/services/quotationService.ts — fetchQuotations, createQuotation, updateQuotation, updateQuotationStatus, sendQuotation.
4. Bookings (/sales/bookings)
Full coverage: Bookings.
In short: the paying-customer record, created by the Booking Wizard (see Booking Wizard) or converted from a lead / quotation. Gated by bookings.* permissions.
5. Customers (/sales/customers)
src/pages/sales/Customers.tsx is the master list of every individual person Al Huda has ever transacted with. A customer row is the durable identity that multiple bookings and payments reference.
Key facets
- Passport-based dedup — the primary natural key; the wizard and import dialog both use it to prevent duplicates.
- Profile fields — name, DOB, gender, title, nationality (default INDIAN), full Indian address block, phone, email.
- Hajj-specific fields — blood group, PAN, Aadhaar (populated from Hajj bookings, used on future bookings).
- Source —
DirectvsThrough Business Partner(links tosourceAgentId). - Documents — Drive-backed uploads (passport, visa, tickets) via
customers.edit.
Actions
| Action | Permission |
|---|---|
| View list + detail | customers.view |
| Create | customers.create |
| Edit | customers.edit |
| Delete | customers.delete |
| Upload / delete document | customers.edit |
| View ledger | customers.view |
| Export CSV | customers.view |
| Import CSV | customers.create |
Services: src/services/customerService.ts.
6. How the Sales pages talk to each other
flowchart LR
subgraph Leads page
A[Lead row] -- Convert to Quote --> Q[Quotation]
A -- Convert to Booking --> B[Booking]
end
subgraph Quotations page
Q -- Convert (accepted) --> B
end
subgraph Customers page
C[Customer master]
end
B --> C
Q --> C
A --> C
- Lead conversion calls
createQuotationorcreateBookingFromLead; both take the lead's captured fields and create aCustomerrow on the fly if the contact isn't already in the master. - Quotation → Booking uses
createBookingFromQuotation, which copies passengers, rates, and payer onto a freshBooking, then updates the quotation status toaccepted(Quotations.tsx:515). - Every booking update that changes a customer-relevant field (e.g. phone) propagates back to the
Customerrow so the next booking starts from fresh data.
7. Permissions summary
The canonical source is docs/PERMISSIONS.md §6.1 through §6.4. Condensed:
| Module | view |
create |
edit |
delete |
|---|---|---|---|---|
| Leads | leads.view |
— | leads.edit |
— |
| Quotations | quotations.view |
quotations.create |
quotations.create |
quotations.create |
| Bookings | bookings.view |
bookings.create |
bookings.edit |
bookings.delete |
| Customers | customers.view |
customers.create |
customers.edit |
customers.delete |
Role bundles that carry full sales-module access: CEO, GM, IT_ADMIN, ADMIN_HR, SALES_MANAGER, B2B_MANAGER. SALES_EXEC and B2B_EXEC lack customers.delete but hold everything else in the funnel (see docs/PERMISSIONS.md §5.2).
8. What the Sales module does NOT contain
For developer context, so you don't go hunting in the wrong folder:
- Finance (payments, vouchers, journals) lives under
/finance—src/pages/finance/Finance.tsx. Payments are triggered from the Bookings list via the "Add Payment" button (gated byfinance.create) but the posting handler lives in finance. - Approvals queue (for ops + finance to approve bookings) is under
/approvals—src/pages/approvals/Approvals.tsx. Sales routes them there via "Send Back" and "Resubmit". - Partners / agents master is under
/agents—src/pages/partners/Partners.tsx. Sales only selects an existing partner; creation / edit happens there. - Groups is a peer module, not a child of Sales —
/groups. See Groups.