Skip to content

Tenant deliveryPrice + Google road delivery via /availability

Scope: Continue PR #6127 with TOP-4905 follow-up: source perKmRate from tenant-level setting and surface accurate Google-road delivery to BP search/create-offer responses.

Goal: BP search list and create-offer auto-quote display offer-generator-aligned delivery price (Google road distance × tenant settings.deliveryPrice), with SQL haversine × 1.3 used only for sort/filter and as fallback.

Decisions (from /gsd-do dialog 2026-04-27):

  1. Per-km rate lives on EnabledTenant.deliveryPrice (not Vehicle.perKmRate).
  2. SQL compute_vehicle_price uses 1.3 × haversine for sort/filter; FE-displayed delivery comes from PR /availability (Google road, cached in Fauna distances).
  3. Endpoint sources stay as-is (BP Vehicle.lat/lng, OG Fauna Address.id).
  4. PR settings UI label “per 1 mile” stays (no conversion in code).

Verified Current State

  • EnabledTenant already mirrors many tenant settings (commissionPercent, tarifficationVariant, hiddenInsurance*, standardBufferTime, …) but has no deliveryPrice column.
  • Vehicle.perKmRate is uniformly 2.00 for all 5 rows (set by migration; not real per-vehicle).
  • compute_vehicle_price SQL takes p_per_km_rate from Vehicle.perKmRate (CTE column c.per_km_rate).
  • /api/public/broker/availability handler today: queries vehicle for price, tariffs, extraKmPrice, baseKmDay, unlimitedKilometers, seasonsTariffsMatrix. No currentLocation. Returns deliveryPrice nowhere.
  • tenantRegistrationService.registerTenant(config) is the only writer to EnabledTenant — no auto-sync from PR; settings are seeded explicitly.
  • Existing distance resolver: server/graphql/resolvers/query/distance.resolver.js exposes getMinimalDistance({start, finish, ids}) with Fauna distances cache + Google Distance Matrix. Accepts Place IDs (ids=true) or address strings (ids=false). lat,lng strings work with Google.

Files

A. BP schema + SQL

  • Modify: apps/broker-portal/prisma/schema.prisma (add EnabledTenant.deliveryPrice Decimal @default(1))
  • Create: apps/broker-portal/prisma/migrations/<n>_enabled_tenant_delivery_price/migration.sql (column add + backfill UPDATE "EnabledTenant" SET "deliveryPrice" = 1)
  • Create: apps/broker-portal/prisma/migrations/manual-43-amend-compute-vehicle-price-tenant-rate.sql (new SQL function version: takes tenant rate, applies 1.3 × haversine)
  • Modify: apps/broker-portal/src/api/services/vehicle-search-service.ts (CTE selects t."deliveryPrice"; pass to compute_vehicle_price)
  • Modify: apps/broker-portal/src/api/services/tenant-registration-service.ts (TenantConfig.deliveryPrice?: number)

B. PR /availability extension

  • Modify: pages/api/public/broker/availability.ts (accept pickup/return coords; fetch vehicle.currentLocation; compute delivery via getMinimalDistance × tenant settings.deliveryPrice; return deliveryPrice on each vehicle row)
  • Modify: server/api-schemas/broker/availability.schema.ts (add optional pickupLat, pickupLng, returnLat, returnLng)
  • Modify: packages/broker-types/src/index.ts (VehicleAvailability.deliveryPrice?: number; CheckAvailabilityParams adds the 4 coord fields)

C. BP wiring

  • Modify: apps/broker-portal/src/lib/power-rent-client.http.ts (checkAvailability forwards pickupLat/Lng/returnLat/Lng)
  • Modify: apps/broker-portal/src/api/services/vehicle-search-service.ts (forward params; replace SQL delivery_price with API deliveryPrice in displayed SearchResultVehicle.rentalTotal & deliveryPrice)

D. Tests

  • Modify: _tests_/pages/api/public/broker/availability.test.ts
  • Modify: apps/broker-portal/src/api/services/__tests__/vehicle-search-service.test.ts
  • Modify: apps/broker-portal/src/api/__tests__/vehicle-routes.test.ts

Task A — BP schema + SQL: tenant deliveryPrice + 1.3× haversine

A.1 Add EnabledTenant.deliveryPrice (Prisma)

  • Edit schema.prisma:
    model EnabledTenant {
    ...
    deliveryPrice Decimal @default(1) @db.Decimal(10, 2)
    }
  • Generate migration: pnpm prisma migrate dev --name enabled_tenant_delivery_price --create-only
  • Hand-edit migration to keep it idempotent and add backfill for existing rows.

⚠️ Migration application is gated. Do NOT apply in this session — broker-portal .env is PROD Neon (memory feedback_prod_db_manual_changes.md). Just produce the migration file. User applies when ready.

A.2 Update compute_vehicle_price SQL function

  • Create manual-43-amend-compute-vehicle-price-tenant-rate.sql:
    • Replace p_per_km_rate numeric arg semantics: now interpreted as “tenant’s per-km rate” (caller passes tenant value).
    • Inside the Haversine block: multiply both v_dist_to_pickup and v_dist_from_return by 1.3 (road approximation).
    • Keep formula otherwise: delivery = (d1 + d2) × p_per_km_rate.
  • File mirrors structure of manual-42-02-amend-compute-vehicle-price.sql.

A.3 Update vehicle-search-service CTE

  • In candidates CTE: replace v."perKmRate" AS per_km_rate with t."deliveryPrice" AS per_km_rate (or rename column to clarify).
  • Drop Vehicle.perKmRate from the SELECT — it’s no longer the source.

A.4 Update TenantConfig type

  • Add deliveryPrice?: number to tenantRegistrationService.TenantConfig so future tenant registration accepts the rate.

Task B — PR /availability: accept pickup coords, return delivery

B.1 Extend request schema

  • availability.schema.ts: add four optional numeric fields: pickupLat, pickupLng, returnLat, returnLng. Validate as finite numbers in [-90,90] / [-180,180].

B.2 Extend handler

  • Fetch currentLocation { id, coordinates } on each vehicle in the Fauna FQL query.
  • After settings + vehicle data resolved, for each available vehicle:
    • If pickupLat & pickupLng provided AND vehicle has currentLocation.id (Place ID):
      • dPickup = getMinimalDistance({ start: vehicle.currentLocation.id, finish: \${pickupLat},${pickupLng}`, ids: false })`
      • If returnLat & returnLng provided: dReturn = getMinimalDistance({ start: \${returnLat},${returnLng}`, finish: vehicle.currentLocation.id, ids: false }); else dReturn = dPickup`.
      • deliveryPrice = (dPickup + dReturn) × tenantSettings.deliveryPrice
    • Else: deliveryPrice = 0 (caller must accept).
  • Pass deliveryPrice into calculateBrokerOfferGeneratorPrice({...}) (already supported) so totalPrice includes delivery.
  • Return deliveryPrice on each BrokerVehicleQuoteAvailable row.

B.3 Update shared types

  • packages/broker-types/src/index.ts:
    • CheckAvailabilityParams: add 4 optional coord fields.
    • VehicleAvailability: add deliveryPrice?: number.

Task C — BP wiring

C.1 power-rent-client.http.ts

  • Forward new optional fields in checkAvailability body.

C.2 vehicle-search-service.ts

  • Pass pickupLat/Lng/returnLat/Lng from VehicleSearchParams into client.checkAvailability.
  • In price snapshot map, additionally capture deliveryPrice from API response.
  • In row→SearchResultVehicle mapping:
    • When API delivery present: rentalTotal = api.totalPrice (already includes delivery via /availability), deliveryPrice = api.deliveryPrice.
    • When API succeeded but no delivery: rentalTotal = api.totalPrice + sqlDelivery, deliveryPrice = sqlDelivery (current behavior).
    • When API failed: SQL fallback (current behavior).

Task D — Tests

D.1 PR availability.test.ts

  • New test: passes pickup coords through to delivery calc and returns deliveryPrice. Mock getMinimalDistance to return e.g. 500 km; assert deliveryPrice = 500 × 2 × tenantSettings.deliveryPrice returned on the row, and totalPrice includes it.
  • Update existing happy-path test to mock getMinimalDistance (now an additional dependency).

D.2 vehicle-search-service.test.ts

  • New test: forwards pickup/return coords to checkAvailability.
  • New test: uses API deliveryPrice as displayed delivery + omits SQL delivery_price addition when API supplies it.
  • Keep fallback tests: SQL delivery still used when API missing/failed.

D.3 vehicle-routes.test.ts

  • Assert deliveryPrice field present in search response when API returns it.

Out of scope (deferred)

  • Backfilling EnabledTenant.deliveryPrice from real PR settings.deliveryPrice per tenant — produced as one-line UPDATE … SET deliveryPrice = 1 since user confirmed PR setting is 1.
  • Applying migrations to PROD Neon (gated behind explicit user approval).
  • Removing Vehicle.perKmRate column entirely (kept for now to avoid drop migration; just unused).
  • TOP-4906 OG delivery investigation (still blocked on address-level Place ID inspection).

Acceptance Criteria

  • BP search response shows deliveryPrice from Google road distance (PR /availability) when pickup coords supplied.
  • BP create-offer-sheet auto-fills delivery from search response (already wired); should now match OG.
  • SQL compute_vehicle_price uses tenant deliveryPrice × 1.3 × haversine(d1+d2) for sort/filter only.
  • All targeted tests pass; root yarn test, broker-portal pnpm test, yarn typecheck, yarn lint clean.
  • Migrations created but NOT applied in this session.

Unresolved Questions

  • Backfill source: confirm one-time UPDATE … SET "deliveryPrice" = 1 for all tenants is correct, or pull per-tenant from PR Fauna?
  • Should Vehicle.perKmRate column be dropped now or in a follow-up?
  • For /availability calls without pickup coords (existing callers) — should deliveryPrice = 0 or omit field? (Suggest: omit deliveryPrice when coords missing; callers fall back to SQL.)