Allotment
Allotment is the per-date inventory a supplier grants Volāre on each service rate (supplier_service_rates.allotment). AllotmentService resolves which rates an offer/booking consumes, checks remaining capacity, and consumes/releases units as bookings are finalized or cancelled.
When to Use
Section titled “When to Use”- Deciding whether an offer is bookable on the web (
getOfferAvailability/ bulkgetOffersAvailability) - Booking finalization —
consumeAllotment()is called insideBookingFinalizationService’s transaction - Admin offer preview — the availability breakdown shown per service (
OfferPreviewData)
Not for date-range blackouts — that is Stop Sales (see below).
How Rates Are Resolved
Section titled “How Rates Are Resolved”resolveServiceRates(Offer, array $session) maps an offer (plus any checkout upsell overrides in the session) to the SupplierServiceRate rows it consumes, each with the list of calendar service_dates:
- Hotels emit one consumed date per stay night — each consecutive itinerary day at the hotel becomes
departure_date + (day − 1). Checkout hotel upgrades replace the base hotel for the covered night range. - Activities and transfers emit one date per itinerary day they sit on; upsell selections on a day replace that day’s included/base entries. A trip-level luxury transfer maps to day 1 (the departure date).
- Package services (when the tour has them) emit a single date equal to the offer’s departure date.
The rate selected for each service is the one whose window covers departure_date (SupplierService::getRateForDate()). Multi-night stays do NOT re-resolve per night, even if the stay spills into a neighbouring rate window — this deliberately matches the pricing assumption.
Availability
Section titled “Availability”Consumption is tracked as one AllotmentConsumption row per (booking, rate, service_date). Remaining capacity for an offer is:
MIN over every (rate × service_date) of: rate.allotment − consumed_countgetOfferAvailability(Offer) returns an AllotmentAvailabilityResult DTO: available (the MIN), isAvailable, the bottleneck service name, and a per-service breakdown. Offers with no supplier tour rate or no constrained services are unlimited().
getOffersAvailability(Collection) is the bulk variant for product pages (all offers must share a tour; base services only, no upsells) — a few bulk queries instead of N per offer. Used by ProductByMarketController.
Consumption & Release
Section titled “Consumption & Release”consumeAllotment(Booking, array $session) — must run inside a DB transaction (it does, in BookingFinalizationService):
- Locks all involved rate rows with
lockForUpdate(), ordered by id (deterministic — no deadlocks), preventing concurrent overbooking - Pre-checks every
(rate, service_date)tuple and throwsInsufficientAllotmentExceptionbefore writing anything if one is already full - Inserts the consumption rows (
firstOrCreate, so re-finalization is idempotent) - When this booking takes the last unit of any
(rate, date), anAllotmentExhaustedNotificationemail goes to the Volāre entity address after commit (plus configured copies — see Email Copies)
releaseAllotment(Booking) deletes the booking’s consumption rows. It fires automatically from the Booking model’s updated hook whenever a booking’s status changes to cancelled (backend/app/Models/Booking.php).
Allotment vs Stop Sales
Section titled “Allotment vs Stop Sales”| Allotment | Stop sale | |
|---|---|---|
| Granularity | Per service rate × calendar date | Whole tour × date window |
| Semantics | Counted capacity, consumed booking by booking | Binary blackout, ignores capacity |
| Recovers | When bookings are cancelled (release) | When the window expires or is deleted |
| Managed as | allotment integer on SupplierServiceRate |
StopSale rows on the tour |
Both gates apply independently — an offer must have remaining allotment AND no active stop sale to be bookable.
Business Rules
Section titled “Business Rules”- Availability is always the worst
(rate, date)tuple — one exhausted hotel night blocks the whole offer - The departure-date rate governs the whole stay; nights never split across rate windows
- Overbooking is prevented with pessimistic locks at finalization, not just the earlier availability check
- Cancelling a booking returns its units automatically
Related
Section titled “Related”- Stop Sales — tour-level date blackouts (the other bookability gate)
- Booking Lifecycle — where consumption happens (finalization)
- Suppliers — services and rates data model
- Source:
backend/app/Services/Allotment/AllotmentService.php - Source:
backend/app/Services/Allotment/AllotmentAvailabilityResult.php - Source:
backend/app/Models/SupplierServiceRate.php