Tenant deliveryPrice + Google road delivery via /availability
Scope: Continue PR #6127 with TOP-4905 follow-up: source
perKmRatefrom 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):
- Per-km rate lives on
EnabledTenant.deliveryPrice(notVehicle.perKmRate). - SQL
compute_vehicle_priceuses1.3 × haversinefor sort/filter; FE-displayed delivery comes from PR/availability(Google road, cached in Faunadistances). - Endpoint sources stay as-is (BP
Vehicle.lat/lng, OG FaunaAddress.id). - PR settings UI label “per 1 mile” stays (no conversion in code).
Verified Current State
EnabledTenantalready mirrors many tenant settings (commissionPercent,tarifficationVariant,hiddenInsurance*,standardBufferTime, …) but has nodeliveryPricecolumn.Vehicle.perKmRateis uniformly2.00for all 5 rows (set by migration; not real per-vehicle).compute_vehicle_priceSQL takesp_per_km_ratefromVehicle.perKmRate(CTE columnc.per_km_rate)./api/public/broker/availabilityhandler today: queries vehicle forprice, tariffs, extraKmPrice, baseKmDay, unlimitedKilometers, seasonsTariffsMatrix. NocurrentLocation. ReturnsdeliveryPricenowhere.tenantRegistrationService.registerTenant(config)is the only writer toEnabledTenant— no auto-sync from PR; settings are seeded explicitly.- Existing distance resolver:
server/graphql/resolvers/query/distance.resolver.jsexposesgetMinimalDistance({start, finish, ids})with Faunadistancescache + Google Distance Matrix. Accepts Place IDs (ids=true) or address strings (ids=false).lat,lngstrings work with Google.
Files
A. BP schema + SQL
- Modify:
apps/broker-portal/prisma/schema.prisma(addEnabledTenant.deliveryPrice Decimal @default(1)) - Create:
apps/broker-portal/prisma/migrations/<n>_enabled_tenant_delivery_price/migration.sql(column add + backfillUPDATE "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, applies1.3 × haversine) - Modify:
apps/broker-portal/src/api/services/vehicle-search-service.ts(CTE selectst."deliveryPrice"; pass tocompute_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 viagetMinimalDistance× tenant settings.deliveryPrice; returndeliveryPriceon each vehicle row) - Modify:
server/api-schemas/broker/availability.schema.ts(add optionalpickupLat,pickupLng,returnLat,returnLng) - Modify:
packages/broker-types/src/index.ts(VehicleAvailability.deliveryPrice?: number;CheckAvailabilityParamsadds the 4 coord fields)
C. BP wiring
- Modify:
apps/broker-portal/src/lib/power-rent-client.http.ts(checkAvailabilityforwards pickupLat/Lng/returnLat/Lng) - Modify:
apps/broker-portal/src/api/services/vehicle-search-service.ts(forward params; replace SQLdelivery_pricewith APIdeliveryPricein displayedSearchResultVehicle.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 numericarg semantics: now interpreted as “tenant’s per-km rate” (caller passes tenant value). - Inside the Haversine block: multiply both
v_dist_to_pickupandv_dist_from_returnby1.3(road approximation). - Keep formula otherwise:
delivery = (d1 + d2) × p_per_km_rate.
- Replace
- File mirrors structure of
manual-42-02-amend-compute-vehicle-price.sql.
A.3 Update vehicle-search-service CTE
- In
candidatesCTE: replacev."perKmRate" AS per_km_ratewitht."deliveryPrice" AS per_km_rate(or rename column to clarify). - Drop
Vehicle.perKmRatefrom the SELECT — it’s no longer the source.
A.4 Update TenantConfig type
- Add
deliveryPrice?: numbertotenantRegistrationService.TenantConfigso 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&pickupLngprovided AND vehicle hascurrentLocation.id(Place ID):dPickup = getMinimalDistance({ start: vehicle.currentLocation.id, finish: \${pickupLat},${pickupLng}`, ids: false })`- If
returnLat&returnLngprovided:dReturn = getMinimalDistance({ start: \${returnLat},${returnLng}`, finish: vehicle.currentLocation.id, ids: false }); elsedReturn = dPickup`. deliveryPrice = (dPickup + dReturn) × tenantSettings.deliveryPrice
- Else:
deliveryPrice = 0(caller must accept).
- If
- Pass
deliveryPriceintocalculateBrokerOfferGeneratorPrice({...})(already supported) sototalPriceincludes delivery. - Return
deliveryPriceon eachBrokerVehicleQuoteAvailablerow.
B.3 Update shared types
packages/broker-types/src/index.ts:CheckAvailabilityParams: add 4 optional coord fields.VehicleAvailability: adddeliveryPrice?: number.
Task C — BP wiring
C.1 power-rent-client.http.ts
- Forward new optional fields in
checkAvailabilitybody.
C.2 vehicle-search-service.ts
- Pass
pickupLat/Lng/returnLat/LngfromVehicleSearchParamsintoclient.checkAvailability. - In price snapshot map, additionally capture
deliveryPricefrom API response. - In row→
SearchResultVehiclemapping:- 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).
- When API delivery present:
Task D — Tests
D.1 PR availability.test.ts
- New test:
passes pickup coords through to delivery calc and returns deliveryPrice. MockgetMinimalDistanceto return e.g. 500 km; assertdeliveryPrice = 500 × 2 × tenantSettings.deliveryPricereturned on the row, andtotalPriceincludes 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
deliveryPricefield present in search response when API returns it.
Out of scope (deferred)
- Backfilling
EnabledTenant.deliveryPricefrom real PRsettings.deliveryPriceper tenant — produced as one-lineUPDATE … SET deliveryPrice = 1since user confirmed PR setting is1. - Applying migrations to PROD Neon (gated behind explicit user approval).
- Removing
Vehicle.perKmRatecolumn 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
deliveryPricefrom 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_priceuses tenantdeliveryPrice × 1.3 × haversine(d1+d2)for sort/filter only. - All targeted tests pass; root
yarn test, broker-portalpnpm test,yarn typecheck,yarn lintclean. - Migrations created but NOT applied in this session.
Unresolved Questions
- Backfill source: confirm one-time
UPDATE … SET "deliveryPrice" = 1for all tenants is correct, or pull per-tenant from PR Fauna? - Should
Vehicle.perKmRatecolumn be dropped now or in a follow-up? - For
/availabilitycalls without pickup coords (existing callers) — shoulddeliveryPrice = 0or omit field? (Suggest: omitdeliveryPricewhen coords missing; callers fall back to SQL.)