Skip to content

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

RoleWhat 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 tabcomponents/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 via localStorage key locationCalendar.helpExpanded (TOP-5071).
  • Reservation creation, vehicle pickercomponents/VehiclesTable/VehicleRow/index.js
    • The Location column shows both CL (current) and PL (planned at the reservation dateFrom). Legend labels in components/LocationLegend/messages.js.
    • PL links to the vehicle’s Location Calendar tab via ?tab=location-calendar (TOP-5086).
  • Offer generator (Calculator)containers/Calculator/CalculatorVehicles.js
    • CalculatorVehicles fragment selects both currentLocation and plannedLocation(at: $dateFrom) for each vehicle (TOP-5070).
    • City filter respects plannedLocation as well as currentLocation (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.base when no entry matches.
  • Overlap resolution (narrowest-span-wins):
    1. Priority DESC — higher priority wins (today all entries default to 0; the field is reserved for future use, e.g. one-off overrides over a base layer).
    2. Span ASC — narrower date range wins. An entry with dateTo: null (indefinite) is treated as widest possible.
    3. updatedAt DESC — the most recently edited entry wins.
  • Date semantics — both dateFrom and dateTo are 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

  • currentLocation stays as-is. It’s the real-time post-checkout location, used in unrelated flows. Planned ≠ current.
  • vehicle.base stays as the fallback. The calendar overrides it for dates it covers; gaps and empty calendars use base.
  • 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 getMinimalDistance as 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 (plannedLocation resolver), TOP-5086 (reservation row PL/CL), TOP-5089 (broker portal).