বুকিং স্টেট ট্রানজিশন: DRAFT → HELD → PENDING_PAYMENT/PENDING_APPROVAL → ISSUED → PARTIALLY_USED → COMPLETED। ব্যাতিক্রমী পথ: CANCELLED_BEFORE_ISSUE, CANCELLED_AFTER_ISSUE, EXPIRED। প্রতিটি স্টেট পরিবর্তন ব্যালেন্সড JE তৈরি করে।
Chapter 4.1 — Booking Lifecycle
1. Purpose
The booking is the atomic unit of operational state in travoBooks. Every ticket issued, every hotel night reserved, every transfer scheduled, every invoice line that ultimately feeds the GL begins life as a booking. This chapter defines the full lifecycle — from search through hold, confirmation, issuance, in-life servicing, completion, and archival — and the precise state transitions, ledger impacts, validation rules, and module interactions at each step.
2. Why it matters in modern travel accounting
The booking is the bridge between the operational world (PNRs, segments, traveller names) and the financial world (revenue, AP/AR, deferred revenue, commission). A correctly modelled booking lifecycle: - preserves an auditable chain from search → offer → issuance → settlement - supports same-transaction ledger posting (no operational "ghost" state without matching financial state) - handles the messy reality of supplier-side state drift (PNR cancelled airline-side without notice) - supports refunds, voids, reissues, and partial cancellations without losing history
3. Industry relevance
The travoBooks booking model accommodates the full spectrum of travel inventory: - Air — GDS-issued (Amadeus, Sabre), LCC API, NDC direct, BSP-settled or non-BSP - Hotel — prepaid wholesaler, pay-at-property, direct contract - Ground — transfers, car rental - Insurance — per-policy - Tour / Package — multi-component - Ancillary — baggage, seat, meal, lounge
Each product type slots into the same lifecycle skeleton with type-specific behaviour at the issuance and servicing steps.
4. Compliance considerations
- IFRS 15 — revenue recognition timing is driven by the booking's service-date field; air revenue recognised on flown-date, hotel on check-in-or-stay basis, packages on performance milestones.
- IATA Resolution 850m — accuracy of ticket data, agent code on every transaction.
- PCI DSS — no card data stored in booking records; only gateway tokens.
- GDPR — traveller PII has lawful basis and retention schedule.
- BSP rules — issuance must occur within the supplier's permitted timeframe; void window strictly enforced.
5. Business logic
5.1 Booking states
| State | Description | Reversible? | Ledger impact |
|---|---|---|---|
DRAFT |
Search results selected, customer assigned, not yet held | Yes (auto-expire 30m) | None |
HELD |
Supplier hold (PNR with TimeLimit; hotel option date) | Yes (until timelimit) | None |
PENDING_PAYMENT |
Awaiting customer payment (gateway/cash) | Yes | None |
PENDING_APPROVAL |
Above-threshold; awaiting approver | Yes | None |
ISSUED |
Supplier confirmed; ticket / voucher created | No (forward only) | JE posted |
PARTIALLY_USED |
Some segments flown / nights stayed | No | Revenue recognition progresses |
COMPLETED |
All performance obligations satisfied | No | Final revenue recognition complete |
CANCELLED_BEFORE_ISSUE |
Cancelled in DRAFT/HELD/PENDING | N/A | None |
CANCELLED_AFTER_ISSUE |
Void or full refund | No | Reversing JE posted |
PARTIALLY_REFUNDED |
Partial refund processed | No | Refund JE posted |
EXPIRED |
Held but timelimit passed without action | N/A | None |
ARCHIVED |
Completed / cancelled and older than retention threshold | No | None |
5.2 Booking record structure
| Field | Type | Notes |
|---|---|---|
booking_id |
BIGINT PK | Internal stable ID |
partner_id |
BIGINT FK | Tenant |
booking_reference |
VARCHAR(16) | Partner-facing, human-readable (TVB-2026-000123) |
customer_id |
BIGINT FK | Buyer |
branch_id |
BIGINT FK NULL | Selling branch |
agent_user_id |
BIGINT FK | Who created |
cost_centre_id |
BIGINT FK NULL | Customer cost centre tag |
product_type |
ENUM(AIR, HOTEL, GROUND, INSURANCE, TOUR, ANCILLARY) |
|
state |
ENUM | Above |
transaction_currency |
CHAR(3) | Selling currency |
gross_amount |
DECIMAL(18,2) | Customer pays |
net_supplier_amount |
DECIMAL(18,2) | Supplier cost |
markup_amount |
DECIMAL(18,2) | Partner margin |
commission_amount |
DECIMAL(18,2) | Commission expected |
tax_amount |
DECIMAL(18,2) | Customer-facing tax |
service_fee_amount |
DECIMAL(18,2) | Partner service fee |
service_date_start |
DATE | First service day (flight depart / check-in) |
service_date_end |
DATE | Last service day (last segment / checkout) |
payment_status |
ENUM(UNPAID, PARTIAL, PAID, REFUNDED) |
Computed |
issued_at |
DATETIME NULL | Set on transition to ISSUED |
cancelled_at |
DATETIME NULL | |
completed_at |
DATETIME NULL | |
external_pnr |
VARCHAR(16) NULL | Supplier locator |
external_record_locator |
VARCHAR(64) NULL | Hotel confirmation / NDC orderId |
metadata |
JSON | Type-specific payload |
| Audit columns |
Child tables: booking_segments, booking_passengers, booking_tickets, booking_rooms, booking_services, booking_ancillaries, booking_history, booking_documents.
5.3 Booking → Ledger contract
The state transition PENDING → ISSUED is the single point of ledger entry for a new booking. In the same database transaction:
- Update
bookings.state = 'ISSUED', setissued_at. - Insert child
booking_tickets/booking_rooms/ etc. - Compute JE lines using supplier
principal_or_agent, suppliersettlement_mode, customer payment terms, tax configuration. - Insert balanced
journal_entriesheader +journal_entry_lines. - Update
customer_balances(debit AR) andsupplier_balances/ BSP-payable (credit). - Insert
audit_logsevent. - Enqueue
booking.issuedwebhook.
If any step fails, the entire transaction rolls back — no orphan operational state, no orphan ledger state. This is the architectural invariant of travoBooks.
5.4 Holds & timelimits
GDS PNRs and hotel options have supplier-side timelimits. travoBooks tracks hold_expires_at on the booking. A scheduled job sweeps:
- 30 minutes before expiry: notify selling agent.
- At expiry without action: cancel supplier-side, transition booking to EXPIRED.
This avoids "ADM for unticketed PNR" liability.
5.5 Payment-before-issuance
By default, travoBooks supports issue-then-collect (credit customers) and collect-then-issue (cash customers) per customer.payment_terms_days:
terms_days = 0andcustomer_type = WALKIN→ cash flow: PENDING_PAYMENT → payment captured → ISSUED.terms_days > 0and credit limit available → credit flow: PENDING_APPROVAL (if needed) → ISSUED → invoice → payment per terms.
5.6 Approval workflow
If gross_amount > partner.booking_approval_threshold or customer.credit_exceeded_without_override, booking enters PENDING_APPROVAL. Approver (with bookings.approve.partner) reviews, approves or rejects. Approval action transitions to ISSUED (if all else green) or back to DRAFT (rejected with reason).
5.7 In-life servicing
After ISSUED, several events can occur:
- Flown segment — passive GDS event; updates booking_segments.status = 'FLOWN'; triggers partial revenue recognition (Ch 5.7).
- No-show — supplier marks unused; affects refund eligibility, may trigger fee.
- Reissue — exchange ticket; new JE for fare difference + reissue fee; preserves original FK chain.
- Void — only within void window (typically same calendar day for BSP); reverses original JE in full.
- Refund — full or partial; see Ch 4.3.
- Schedule change / SKCHG — supplier-initiated; may force re-confirmation; usually no financial impact unless customer cancels in response.
5.8 Booking → Invoice relationship
Bookings do not directly create customer invoices. Invoice generation is a separate event governed by customer.invoice_policy:
- per_booking_auto_issue — invoice issued immediately on booking issuance.
- consolidated_weekly / consolidated_monthly — bookings accumulate; invoice generated on schedule.
- on_demand — accountant runs invoice generation manually.
Until invoiced, the AR debit is recorded against 1102 — Unbilled AR (a sub-ledger of trade AR). On invoice creation, JE reclassifies from unbilled to billed.
This separation accommodates corporate clients who require one monthly invoice covering hundreds of bookings.
6. Inputs → processing → outputs
Create booking (search → issue)
Input flow:
1. Search request → offers returned from GDS/NDC/LCC/hotel cache.
2. Selection → POST /bookings with offer payload, customer_id, travellers.
3. Hold → call supplier Air_SellFromRecommendation or equivalent.
4. Price-verify (re-fetch fare) → check against quoted; if delta > tolerance, surface to agent.
5. Payment captured (if collect-first) OR credit available (if credit).
6. Issue → call supplier issuance API (Air_TicketIssue, Hotel_BookingConfirm).
7. Persist tickets / confirmations.
8. Post JE.
9. Generate documents (e-ticket, voucher).
10. Notify customer.
Output: booking_id, booking_reference, attached documents, JE references.
7. Module dependencies
| Direction | Module |
|---|---|
| Depends on | Customers, Suppliers, Tax Config, Supplier Integrations (GDS/NDC/LCC/Hotel), Payment Gateway (if collect-first), JE Engine, Document Generator, Notifications |
| Depended on by | Invoicing, Reporting, Reconciliation, Webhooks, Refund, Reissue, ADM/ACM, Deferred Revenue, Commission Accounting |
8. Security & permissions
| Permission | Allows |
|---|---|
bookings.read.partner / .branch:{id} / .own |
Read |
bookings.create.partner / .branch:{id} / .own |
Create |
bookings.update.partner (limited fields) |
Edit non-financial fields |
bookings.issue.partner |
Trigger ticketing |
bookings.void.partner |
Same-day void |
bookings.refund.partner |
Refund |
bookings.reissue.partner |
Exchange ticket |
bookings.approve.partner |
Approve PENDING_APPROVAL |
bookings.override_credit.partner |
Issue past credit limit |
bookings.cancel_after_issue.partner |
Post-issue cancellation |
PCI scope is bounded — card data flows through gateway iframe / hosted page; only the gateway token persists.
9. Validation rules
| Code | Condition |
|---|---|
BOOKING_CUSTOMER_REQUIRED |
No customer assigned |
BOOKING_SUPPLIER_INACTIVE |
Selected supplier inactive |
BOOKING_OFFER_EXPIRED |
Offer TTL expired (price-quote no longer valid) |
BOOKING_PRICE_CHANGED |
Price-verify shows delta beyond tolerance |
BOOKING_CREDIT_EXCEEDED |
Customer credit limit breached |
BOOKING_CREDIT_HOLD |
Customer on credit hold |
BOOKING_APPROVAL_REQUIRED |
Above approval threshold |
BOOKING_HOLD_EXPIRED |
Acted on after timelimit |
BOOKING_PAYMENT_REQUIRED |
Tries to issue without payment for cash customer |
BOOKING_DUPLICATE_TRAVELLER |
Same traveller twice in same booking |
BOOKING_PNR_NAME_MISMATCH |
Traveller name vs PNR mismatch |
BOOKING_TICKET_TIMELIMIT_PAST |
Ticketing deadline missed |
BOOKING_VOID_WINDOW_CLOSED |
Void attempted past supplier window |
BOOKING_PERIOD_CLOSED |
Edit attempted on booking whose period is closed |
10. Error handling
Errors at the supplier integration layer are mapped to canonical travoBooks codes (BOOKING_SUPPLIER_REJECTED) with the raw supplier error preserved in booking_history.supplier_response for support diagnosis. Customer-facing messages are friendly; agent-facing messages include the raw code.
Idempotency: every booking create / issue / refund call carries an Idempotency-Key header. Replays return the original result rather than creating duplicates — critical for resilience against network retries.
11. Real-world examples
Example A — Cash retail air booking
Walk-in customer buys a Biman DAC-CGP ticket for BDT 8,500.
- DRAFT → HELD (PNR created)
- Customer pays cash BDT 8,500 → PENDING_PAYMENT → ISSUED
- Same transaction posts:
| Account | 🅓 Debit | 🅒 Credit |
|---|---|---|
| 1001 Cash on Hand | 8,500 | |
| 2011 BSP Payable BD | 8,000 | |
| 4031 Service Fee Revenue | 500 |
(Service fee illustrative; real ticket fare structure varies.)
Example B — Corporate credit air booking
Beta Corp issues an Emirates DAC-DXB ticket for USD 600. - terms_days = 30, credit available, no approval needed. - DRAFT → HELD → ISSUED (single transition; no PENDING_PAYMENT). - Tax / commission / supplier breakdown per Chapter 5.1 worked example. - Customer balance increases by USD 600 (functional translation in JE). - Invoice generated on month-end consolidated cycle.
Example C — Same-day void
Agent ticketed BDT 12,000 EK-DAC-DXB at 10:00. Customer cancels at 14:00 same day.
- Agent triggers void → supplier Air_CancelTST and BSP same-day void.
- Original JE reversed in full (new JE with opposite sign, reverses_je_id set).
- Booking transitions to CANCELLED_AFTER_ISSUE with reason VOIDED_SAME_DAY.
Example D — Schedule change → partial refund
EK reschedules customer's outbound flight by 6 hours; customer refuses. Partial refund per fare rules.
- Agent processes refund → supplier Air_RefundQuote → confirmed amount.
- Booking transitions to PARTIALLY_REFUNDED.
- Refund JE: debit Supplier-Payable / credit AR (or Cash for customer refund); refund fees handled per fare rules; commission recall if applicable.
12. Step-by-step workflow
13. Database tables touched
| Table | Role |
|---|---|
bookings |
Main record |
booking_segments |
Air segments (DEP/ARR/flight number/cabin/fare basis) |
booking_passengers |
Travellers (subset of customers / standalone) |
booking_tickets |
Ticket numbers (e-ticket) |
booking_rooms |
Hotel rooms |
booking_services |
Generic service rows (transfer, insurance, tour) |
booking_ancillaries |
Bag, seat, meal |
booking_history |
State transitions, every change |
booking_documents |
Refs to S3 documents (e-ticket PDF, voucher) |
journal_entries / journal_entry_lines |
Financial impact |
customer_balances / supplier_balances |
Materialised |
14. Future scalability
- Booking import — bulk-import from external GDS mid-office systems during onboarding.
- Group booking module — sub-passenger management for large groups (Phase 2).
- Continuous booking enrichment — pull post-issuance updates from GDS PSS (refund waivers, change records).
- Predictive risk scoring — flag bookings likely to refund / ADM at issuance.
- Multi-currency wallet — partner-held customer credit balance per currency.
15. Common pitfalls
- ⚠️ Never persist operational ISSUED state without the matching JE. Single transaction or nothing — this invariant is sacred.
- ⚠️ Holds expire silently if you don't sweep. Implement the timelimit job before launching air booking.
- ⚠️ Don't change customer/supplier on an issued booking. Cancel and re-issue with audit trail.
- 🔒 Card data does not belong on the booking. Token only.
- ⚠️ Service-date drives revenue timing. If service-date is wrong, revenue lands in the wrong period — common BSP-import bug.
- ⚠️ Idempotency keys must be respected. A retried POST that creates a duplicate booking is a double-billing event.