Product · Booking · Pricing
Resort booking
Availability that counts units, not overlaps
The booking half of a resort site: per-night availability across room types, seasonal and length-of-stay pricing, and refusals that name the night that blocked the stay rather than shrugging.
- TypeScript
- React
- Vitest
In short
- Availability is a per-night unit count, so one booking does not close a room type with four units
- Half-open stays — a guest checking out on the 5th frees the room for one checking in on the 5th
- Season and weekend multipliers compound; tax applies after the discount, not before
- Every amount is an integer number of centavos, so the total always equals the sum of its parts
The overlap question is the wrong question
The obvious implementation asks whether the requested stay overlaps an existing booking, and refuses if it does. It is wrong twice. A property with four identical villas has one of them taken and the whole room type disappears from the search. And when a seven-night request fails, nobody — not the guest, not the person on the phone — can say which night was the problem.
The right question is per night: on the busiest night of the requested range, how many units are already occupied? If that count is below the inventory on every night, the stay can be sold. It costs a fold over the nights and it buys both a correct answer and an explainable one.
The changeover day
A stay is half-open. The guest occupies the check-in date and every night after it, but not the check-out date — they are gone by mid-morning and the room is sold again that afternoon.
Model the range as closed and the system refuses a booking the hotel can absolutely honour, on every changeover day, on every room, silently. It is not a dramatic bug: nothing errors, no log line appears, the room simply looks full and the revenue never arrives. There is a test named for exactly that case, and it is the most valuable one in the file.
Money is not a decimal
Every amount is an integer number of centavos. Currency in a floating-point number is a bug waiting for a big enough invoice — seven nights, a seasonal multiplier, a percentage discount and 12% tax is more than enough arithmetic to leave a total a centavo away from the sum of its lines.
Rounding happens in exactly one place, and the tests assert the identity that matters: subtotal minus discount equals taxable, taxable plus tax equals total, across a range of stays. An invoice that does not add up is the sort of thing a guest photographs.
What a refusal should say
A refusal returns the constraining night, and the interface says it. "Not available" is a dead end; "the 14th is the only night we are full" lets a guest shift by a day and book anyway.
That is a product decision more than a technical one, but it is only available because the availability check was built to know the answer. An engine that returns a boolean cannot be asked a follow-up question later without being rewritten.
Source
- src/lib/booking/availability.ts
- src/lib/booking/pricing.ts
- src/lib/booking/booking.test.ts