40. Location Calendar
Status: Shipped (M1, M2). Broker portal integration in flight (TOP-5089). | Last updated: 2026-05-20
1. Overview
The Location Calendar lets a tenant operator declare where each vehicle is planned to be at any future date. The most common shape is a seasonal base schedule: “this car is in Rome Oct–Mar and in Nice Apr–Sep”. The calendar is then used downstream to:
- show the operator the planned location next to the current one in vehicle pickers (reservation creation, offer generator);
- drive search visibility and delivery price calculation on the Billion marketplace (synced as events to Billion’s own geo store).
Before this feature, every consumer that needed a vehicle’s location read the static vehicle.base field. That was wrong for any company that physically moves cars across the year — Billion would hide their cars from the right cities and price deliveries from the wrong base.
The calendar is planned-only. It does not record where a car actually was; that is currentLocation (updated on order check-out) and is a separate concern.
2. Users and primary flows
| Role | What they do with the calendar |
|---|---|
| Operator (tenant) | Adds, edits, removes entries on a vehicle’s “Location Calendar” tab. Reads the planned location (“PL”) in reservation creation and the offer generator to know where a car will be on a given rental day. |
| Customer (Billion) | Implicit. Sees the car appear in search results for the right city/season and gets a delivery price computed from the right base. No UI changes from the customer side. |
| Customer (Broker portal) | Same — TOP-5089 brings parity once it lands. |
The operator’s mental model is “this car is in city X from this date to this date”. Overlaps are allowed (the resolution rules make narrower ranges win); see §4.
3. Surface in the product
- Vehicle edit page → Location Calendar tab —
components/pages/ViewVehicle/LocationCalendar/*- Upcoming and past entries split into two tables.
- Add/edit/delete dialog (
LocationCalendarDialog.js) with date pickers and a Google Places address picker. - Soft overlap indicator (
OverlapIndicator.js+overlapUtils.js) — overlapping ranges are flagged in the UI but accepted. - Collapsible in-product help (
LocationCalendarHelp.js) explaining the resolution rules; remembered vialocalStoragekeylocationCalendar.helpExpanded(TOP-5071).
- Reservation creation, vehicle picker —
components/VehiclesTable/VehicleRow/index.js- The Location column shows both CL (current) and PL (planned at the reservation
dateFrom). Legend labels incomponents/LocationLegend/messages.js. - PL links to the vehicle’s Location Calendar tab via
?tab=location-calendar(TOP-5086).
- The Location column shows both CL (current) and PL (planned at the reservation
- Offer generator (Calculator) —
containers/Calculator/CalculatorVehicles.jsCalculatorVehiclesfragment selects bothcurrentLocationandplannedLocation(at: $dateFrom)for each vehicle (TOP-5070).- City filter respects
plannedLocationas well ascurrentLocation(TOP-5081).
4. Key concepts
- Entry — a row on a vehicle’s calendar:
(locationData, dateFrom, dateTo?, priority).dateTo: null= indefinite. - Planned location at a date — the entry “winning” the resolution rules for that date. Falls back to
vehicle.basewhen no entry matches. - Overlap resolution (narrowest-span-wins):
- Priority DESC — higher
prioritywins (today all entries default to0; the field is reserved for future use, e.g. one-off overrides over a base layer). - Span ASC — narrower date range wins. An entry with
dateTo: null(indefinite) is treated as widest possible. updatedAtDESC — the most recently edited entry wins.
- Priority DESC — higher
- Date semantics — both
dateFromanddateToare inclusive. An entry from Jan 1 to Mar 2 covers every day including Mar 2. Date-only comparisons; no time zones. - Fallback — empty calendar, or a gap between entries, resolves to
vehicle.base. Zero behavioural change for vehicles whose calendar is empty (safety net for the Billion sync path).
5. Relationship to other features
currentLocationstays as-is. It’s the real-time post-checkout location, used in unrelated flows. Planned ≠ current.vehicle.basestays as the fallback. The calendar overrides it for dates it covers; gaps and empty calendars usebase.- Billion marketplace integration — every calendar mutation publishes a QStash event so Billion can rebuild the car’s geo footprint. Detailed contract in ADR-0016.
- Seasons tariffs matrix — independent feature with similar UX shape (vehicle-scoped, date-bounded, percentage-based). The two do not interact; pricing math runs on the seasons matrix, location math runs on the calendar.
6. Out of scope
- Recurrence / yearly patterns. The calendar stores concrete dates only. “Clone previous year” (TOP-4840) was deferred and ultimately cancelled.
- Auto-derivation from orders. The calendar is planned-only; it does not learn from checkouts.
- Post-booking reconciliation. If the operator planned a move but the car never went, there’s no automatic correction.
- One-way fee. Office-to-office, not vehicle location dependent.
- Public distance API. The design doc proposed exposing
getMinimalDistanceas a public endpoint; the integration with Billion uses QStash + Billion’s own PostGIS distance instead, so this was not built. - Operator user manual. Covered by the in-product help section (
LocationCalendarHelp.js). - Broker portal. Tracked separately in TOP-5089.
7. Architecture
- TRA-side architecture (Prisma schema, Resolver → Service → Repository layout, GraphQL surface, resolution algorithm) — ADR-0015.
- Billion sync (QStash events, payload shape, trigger points, what Billion does on receipt) — ADR-0016.
8. References
- Project: Location Calendar.
- Original design and estimation doc (historical, partially superseded — see ADR-0016 for the divergences): Location Calendar for Cars — Design & Estimation (v3).
- Notable tickets: TOP-4830 (Billion data-shape spike that changed the sync design), TOP-4832 (Prisma + GraphQL CRUD), TOP-4836 (calendar dialog), TOP-4837 (Billion sync), TOP-4936 (full-vehicle sync on first publish), TOP-5068 (
plannedLocationresolver), TOP-5086 (reservation row PL/CL), TOP-5089 (broker portal).