Inventory API
Hotels, airline quota blocks, FIT inventory, B2B flight offers, food, ground transfers, visa cases, and tickets.
Hotels
Handler: handleHotels at src/lib/api.ts:15385.
| Route | Method | Permission | Purpose |
|---|---|---|---|
/hotels |
GET | (read-only) | List hotels joined to active Supplier rows |
/hotels |
POST | hotels.create |
Create hotel contract; posts hotel_purchase journal (DR Stock-in-Hand, CR Supplier Payable); ensures supplier record |
/hotels/:id |
PATCH | hotels.edit |
Update hotel fields |
/hotels/:id |
DELETE | hotels.delete |
Remove hotel |
POST /hotels — inventory journal
Cite: src/lib/api.ts:15394.
Input — { supplierId?, city, starRating?, distanceFromHaram?, roomType, totalRooms,
pricePerNight, currency, exchangeRate?, leaseFromDate, leaseToDate, gstEnabled, gstRate,
gstAmount, ... }.
Work:
ensureHotelSupplierRecordauto-creates aSupplierifsupplierIdisn't passed.- Compute
leaseDays = (leaseToDate - leaseFromDate). totalCostOriginal = pricePerNight × totalRooms × leaseDays.- If non-INR with
exchangeRate, convert to INR for the GL journal. - Insert
SupplierTransactionrow recording the purchase. - Post perpetual-inventory journal (idempotent — skipped if a prior journal for this hotel already exists):
- DR Stock-in-Hand (1310, ASSET) — base cost in INR
- DR GST Input Credit (1400, ASSET) — if
gstAmount > 0 - CR Supplier Payable sub-ledger — gross amount in INR
Parity with airline blocks
Hotel inventory is treated as stock-at-cost until rooms are consumed by
GroupHotelAssignment. The expense fires when the assignment is made, not when the
contract is signed. Matches createQuotaBlockFinanceEntries (api.ts:17135).
Hotel assignments (allocate to groups)
Handler: handleHotelAssignmentsRoute at src/lib/api.ts:15916.
| Route | Method | Permission | Purpose |
|---|---|---|---|
/hotels/assignments |
GET | (read-only) | List all assignments |
/hotels/assignments |
POST | hotels.edit |
Assign rooms from a hotel to a group |
/hotels/assignments/:id |
PATCH | hotels.edit |
Update assignment |
/hotels/assignments/:id |
DELETE | Remove assignment |
Hotel low-stock thresholds
Handler: handleHotelThresholds at src/lib/api.ts:15869.
| Route | Method | Purpose |
|---|---|---|
/hotels/thresholds/hotels |
GET, POST | Set global low-room threshold |
/hotels/thresholds/food |
GET, POST | Set global low-stock threshold for food items |
Food inventory
Handler: handleFoodInventory at src/lib/api.ts:27531.
| Route | Method | Permission | Purpose |
|---|---|---|---|
/hotels/food |
GET | (read-only) | List food items |
/hotels/food |
POST | hotels.create |
Create food inventory item |
/hotels/food/:id |
PATCH | hotels.edit |
Update item |
/hotels/food/:id |
DELETE | hotels.delete |
Remove item |
/hotels/food/import |
POST | hotels.create |
Bulk import food items (JSON) |
/hotels/food/import-file |
POST | hotels.create |
Bulk import from uploaded file |
Food assignments
Handler: handleFoodAssignments at src/lib/api.ts:26279.
| Route | Method | Permission | Purpose |
|---|---|---|---|
/hotels/food/assignments |
GET | (read-only) | List meal assignments |
/hotels/food/assignments |
POST | hotels.edit |
Assign meals to a group |
/hotels/food/assignments/:id |
DELETE | hotels.edit |
Remove assignment |
Ground transfers
Handlers: handleGroundTransferInventory at src/lib/api.ts:16348,
handleGroundTransferAssignments at src/lib/api.ts:16460.
| Route | Method | Permission | Purpose |
|---|---|---|---|
/inventory/ground-transfers |
GET | (read-only) | List |
/inventory/ground-transfers |
POST | inventory.create |
Create transfer offering |
/inventory/ground-transfers/:id |
PATCH, DELETE | inventory.edit |
CRUD |
/inventory/ground-transfers/:id/assign |
POST | inventory.edit |
Assign to a group |
/ground-transfers/assignments |
GET | (read-only) | List assignments |
/ground-transfers/assignments |
POST | inventory.edit |
Create assignment |
/ground-transfers/assignments/:id |
PATCH, DELETE | inventory.edit |
Update / remove |
Airlines
Handler: handleInventoryAirlines at src/lib/api.ts:18049.
| Route | Method | Permission | Purpose |
|---|---|---|---|
/inventory/airlines |
GET | (read-only) | List airlines |
/inventory/airlines |
POST | inventory.create |
Create airline (code, name, flightNumbers[]) |
/inventory/airlines/:id |
PATCH | inventory.edit |
Update |
/inventory/airlines/:id |
DELETE | inventory.edit |
Hard-delete |
Airline quota blocks
Handler: handleInventoryQuotaBlocks at src/lib/api.ts:18103.
| Route | Method | Permission | Purpose |
|---|---|---|---|
/inventory/quota-blocks |
GET | (read-only) | List quota blocks with live seat math |
/inventory/quota-blocks |
POST | inventory.create |
Create block; posts DR Stock-in-Hand / CR Supplier Payable |
/inventory/quota-blocks/:id |
PATCH | inventory.edit |
Update |
/inventory/quota-blocks/:id |
DELETE | inventory.edit |
Delete (reverses journals) |
/inventory/quota-blocks/:id/manifest |
GET | (read-only) | Passenger manifest |
/inventory/quota-blocks/:id/b2b-passengers |
PATCH | inventory.edit |
Attach B2B passengers sold through partners |
/inventory/quota-blocks/:id/finance-events |
GET | finance.create ( ) |
Finance event history |
Validation rules
Cite: src/lib/api.ts:18103-18520. Cross-leg chronology is
validated: arrival time cannot precede departure, return departure cannot precede outbound
arrival. Invalid sequences throw 400 with a specific error message.
FIT inventory (free individual traveller)
Handler: handleInventoryFIT at src/lib/api.ts:19346.
| Route | Method | Permission | Purpose |
|---|---|---|---|
/inventory/fit |
GET | (read-only) | List FIT rows |
/inventory/fit |
POST | inventory.create |
Create FIT inventory; posts financial journal |
/inventory/fit/:id |
PATCH | inventory.edit |
Update |
/inventory/fit/:id |
DELETE | inventory.edit |
Delete |
/inventory/fit/:id/payments |
GET | (read-only) | Linked payments |
/inventory/fit/:id/finance-events |
GET | finance.create ( ) |
Finance events |
B2B flight offers (partner-sold seats)
Handler: handleInventoryB2BFlights at src/lib/api.ts:26930.
| Route | Method | Permission | Purpose |
|---|---|---|---|
/inventory/b2b-flights |
GET | (read-only) | List offers open for partner sale |
/inventory/b2b-flights |
POST | inventory.create |
Create offer (seatsTotal, seatsAvailable, pricePerSeat) |
/inventory/b2b-flights/:id |
PATCH | inventory.edit |
Update |
/inventory/b2b-flights/:id |
DELETE | Delete |
Partner bookings reduce seatsAvailable atomically (matched-by-version UPDATE in
handleSalesBookings POST) — see Bookings.
Inventory reports
| Route | Method | Handler | Purpose |
|---|---|---|---|
/inventory/pnl |
GET | handleInventoryPnl |
Per-block/hotel P&L |
/inventory/utilization |
GET | handleInventoryUtilization |
Seat / room utilization |
/inventory/status |
GET | handleInventoryStatus |
Overall inventory status |
All read-only.
POST /inventory/third-party-sale
Handler: handleThirdPartySale at src/lib/api.ts:26584.
Permission: finance.create. Records a sale of inventory to an external party (outside the
normal booking flow); posts revenue journal.
Visa
Handler: handleVisaRoute at src/lib/api.ts:13975.
| Route | Method | Permission | Purpose |
|---|---|---|---|
/visa |
GET | (read-only) | List visa cases |
/visa |
POST | visa.create |
Create visa case for a booking passenger |
/visa/sync |
POST | visa.edit |
Reconcile per-passenger cases with booking manifests; clean up legacy per-booking cases |
/visa/:id |
GET | (read-only) | One case + documents + status history + enriched passenger data |
/visa/:id/status |
PATCH | visa.edit |
Change status (NOT_STARTED, APPLIED, UNDER_PROCESS, SENT_TO_EMBASSY, ISSUED, COLLECTED, REJECTED); emails customer + agent |
/visa/:id/upload |
POST | tickets.edit ( ) |
Upload a visa document |
/visa/:id/documents |
GET | (read-only) | All visa + customer documents merged |
/visa/:id/documents/:docId |
DELETE | tickets.edit ( ) |
Remove a document |
Auto-created on ops-approve
When POST /operations/bookings/:id/ops-approve fires, the handler auto-creates a
VisaCase for every passenger with needsVisa=true and emits a
visa_status_change email. See Bookings.
Tickets
Handlers: handleTickets at src/lib/api.ts:15215,
handleTicketById at src/lib/api.ts:14920.
| Route | Method | Permission | Purpose |
|---|---|---|---|
/tickets |
GET | (read-only) | List all ticket records with booking/customer/group flight snapshot |
/tickets |
POST | tickets.edit |
Sync ticket records ({bookingId}, {groupId}, or {syncAll:true}) |
/tickets/:id |
GET | (read-only) | One ticket with full context |
/tickets/:id |
PATCH | tickets.edit |
Generic field update |
/tickets/:id/name-update |
PATCH | tickets.edit |
Finalise passenger name; status transitions based on finance clearance |
/tickets/:id/issue |
POST | tickets.edit |
Issue ticket; PNR required; → ISSUED |
/tickets/:id/finance-clear |
POST | tickets.edit |
Grant finance clearance; → FINANCE_CLEARED |
/tickets/:id/request-exception |
POST | tickets.edit |
Request finance exception with emailed proof; → ON_HOLD |
/tickets/:id/approve-exception |
POST | tickets.edit |
Approve finance exception; → FINANCE_CLEARED |
/tickets/:id/documents |
POST | tickets.edit |
Attach TicketDocument |
Ticket status machine
NOT_READY → FINANCE_CLEARED / ON_HOLD → ISSUED.
- Finance clearance is live-recomputed from
Booking.balanceAmount <= 0on each read. - A ticket can re-enter
NOT_READYif the passenger name changes after clearance. - Status history is tracked in
TicketStatusHistory(best-effort — may be missing if the migration hasn't run).
Auto-generation on finance-approve
Tickets are auto-created by handleFinanceBookings when finance approves a booking
(src/lib/api.ts:13899 → generateTicketRecordsForBooking). Manual
POST /tickets is the resync path (e.g. after booking-passenger changes).