Files
2026-08-31 13:00:49 +05:30

4.7 KiB

11 — Route & Sales Visit Management

Status: Partially Implemented Depends On: 00 — Existing Gap Fixes


Goal

Route CRUD already exists (Features/Route). This step covers the sales-visit half of mainPlan.md §3 — the part that is about a SalesRep's daily visit schedule, not the ride/dispatch route from 03_ride_lifecycle.md's planDeliveryRoute.


Background / Context

Today a Store has a single FK_RouteId (bulk-assign not yet supported via API — see gap #2 in 00_existing_gaps.md), and route access is only granted via UserRouteScope (a many-to-many grant), not single ownership. There is no visit check-in/out data anywhere.


Data Model Changes

Route Ownership

RouteId, OwnerUserId — single-owner assignment, distinct from the broader UserRouteScope access grant which may cover multiple users (e.g. a Supervisor overseeing several reps' routes).

Route Visit Log

RouteId, StoreId, UserId, CheckInAt, CheckInGps, CheckOutAt — one row per planned visit.


API Endpoints

Function reference Endpoint intent
assignOutletsToRoute(routeId, outletIds[]) POST /api/routes/{id}/stores — bulk version of the existing single Store.RouteId field
assignRouteToSalesRep(routeId, salesRepId) PUT /api/routes/{id}/owner
optimizeRouteSequence(routeId) POST /api/routes/{id}/optimize
recordRouteCheckIn(routeId, outletId, gpsCoords, timestamp) POST /api/routes/{id}/visits/check-in
recordRouteCheckOut(routeId, outletId, timestamp) POST /api/routes/{id}/visits/check-out
getRouteAdherence(routeId, date) GET /api/routes/{id}/adherence?date=...
reassignRoute(routeId, newSalesRepId) PUT /api/routes/{id}/owner (same as assign, different actor)
getRoutePerformance(routeId, period) GET /api/routes/{id}/performance?period=...

Business Rules

  • Check-in gpsCoords feeds sendRouteDeviationAlert in 09_fraud_controls.md — a check-in materially outside the store's registered location should flag, not silently accept.
  • getStoreVisitHistory (§4) reads directly from Route Visit Log — no separate storage.

Open Questions

  • Is route sequence optimization (optimizeRouteSequence) a manual reorder endpoint, or does it need actual distance/priority-based computation (external mapping API)? — Deferred. The endpoint is stubbed (501 Not Implemented) until this is decided.

Implementation Notes

Implemented: assignOutletsToRoute, assignRouteToSalesRep/reassignRoute (same endpoint), recordRouteCheckIn/recordRouteCheckOut, and getStoreVisitHistory (§4, now reads from RouteVisitLog). See BACKEND_API_REFERENCE.md §6.6 for full request/response shapes.

Deliberately deferred, not oversights:

  • optimizeRouteSequence — stubbed as 501 Not Implemented; no distance/priority computation exists (see Open Questions above).
  • getRouteAdherence / getRoutePerformance — not built; the underlying metrics (adherence %, strike rate, missed-visit count) were never defined, so there was nothing concrete to implement against.
  • sendRouteDeviationAlert — check-in computes and stores a basic IsDeviationFlagged boolean (Haversine distance > 500m from the store's registered Latitude/Longitude) but does not call any alert/notification pipeline, since 09_fraud_controls.md itself is not implemented yet.

Data model differs slightly from the original sketch above: RouteId/OwnerUserId became a nullable FK_OwnerUserId directly on Route (no separate ownership table needed for a single-owner field), and Route Visit Log's CheckInGps became two decimal columns (CheckInLatitude/CheckInLongitude) to match the existing Store.Latitude/Longitude shape and make the distance calculation straightforward.

Authorization for the new endpoints follows the RegionService.CanManageRegionAsync pattern (scope check via IScopeAccessService, no dedicated routes.* permission code): bulk store assignment and owner assignment require IScopeAccessService.HasRouteScopeAsync plus role level ≥ 20 (Supervisor+); check-in/check-out require route scope only, no role floor, since any rep with access to the route should be able to log their own visits.

Frontend: implemented in distribution_fe — an admin "Manage" dialog on the Routes page (owner + bulk store assignment), a read-only visit-history dialog on the Stores page, and a new rep-facing /sales-visits page for GPS check-in/check-out (see distribution_fe/app/(dashboard)/sales-visits/).