From a4e3984123c6b4280c2999e77af131b11bd32a43 Mon Sep 17 00:00:00 2001 From: OziinG Date: Mon, 5 Oct 2026 10:00:09 +0900 Subject: [PATCH] Stop maintaining unreachable product paths through their own tests Remove unused API and Route Ops helpers, dependent types, and their dedicated checks. Exercise active runtime factories, diagnostic parsers, token guards, and dispatch ownership instead; keep the shared push fake in test support. Constraint: Preserve exposed routes, tenant boundaries, and driver compatibility. Rejected: Delete every static candidate automatically | shorthand injection and disconnected UI intent require caller review. Confidence: high Scope-risk: moderate Tested: API lint/typecheck/build and 3020 tests; web lint/typecheck/build and 185 tests; 341 route registrations and 10 web asset hashes unchanged. Not-tested: Local disposable DB unavailable; PR CI must run the PostgreSQL profile. No production or authenticated device/browser smoke. --- apps/delivery-api/src/config/env.ts | 9 - .../customer-email/customer-email.service.ts | 8 - .../driver-runtime-diagnostics.contract.ts | 4 - .../modules/driver/driver-token-verifier.ts | 60 ---- ...sv-customer-account-invitations.service.ts | 5 - .../src/modules/dsv/dsv-driver-identity.ts | 21 -- .../src/modules/dsv/dsv-principal.ts | 21 -- .../src/modules/dsv/dsv-v1-read.dto.ts | 25 -- .../modules/geocoding/geocoding.service.ts | 6 - .../admin-notification.dependencies.ts | 6 - .../route-grouping/driver-push.provider.ts | 13 - .../route-grouping/route-grouping.geometry.ts | 16 - .../route-grouping/route-grouping.service.ts | 25 -- .../modules/route-ops/route-scope-config.ts | 8 - .../route-plans/route-execution-ownership.ts | 68 +---- .../route-tracking/route-tracking.geometry.ts | 29 -- .../route-tracking.road-match.ts | 11 - .../shopify/order-sync.dependencies.ts | 8 - .../modules/shopify/webhook.dependencies.ts | 6 - .../woocommerce-order-sync.service.ts | 21 -- .../src/routes/admin-ui-route-planning.ts | 155 ---------- ...n-active-route-reorder.integration.test.ts | 2 +- .../admin-notification.dependencies.test.ts | 6 +- .../admin-ui-route-planning.optimize.test.ts | 26 +- ...iver-event-contract-v2.integration.test.ts | 2 +- ...river-runtime-diagnostics.contract.test.ts | 29 +- .../tests/driver-token-verifier.test.ts | 107 ++----- ...dsv-assignment-command.integration.test.ts | 2 +- ...stomer-account-invitations.service.test.ts | 8 - .../tests/dsv-driver-identity.test.ts | 12 - apps/delivery-api/tests/dsv-principal.test.ts | 37 --- .../tests/dsv-v1-contract-floor.test.ts | 40 +-- .../tests/route-execution-ownership.test.ts | 50 ++-- .../route-group-copy.integration.test.ts | 2 +- .../route-grouping-save.integration.test.ts | 2 +- .../tests/route-grouping.service.test.ts | 36 +-- ...oute-membership-mutation-authority.test.ts | 9 - .../tests/route-tracking.road-match.test.ts | 27 -- .../shopify-webhook.dependencies.test.ts | 18 +- .../support/fake-driver-push-provider.ts | 18 ++ .../woocommerce-order-sync.service.test.ts | 20 -- apps/route-ops-web/src/api.ts | 171 ----------- .../src/components/PageShell.tsx | 16 - .../src/components/primitives.tsx | 10 - apps/route-ops-web/src/i18n.ts | 9 - apps/route-ops-web/src/maps/provider.ts | 74 +---- apps/route-ops-web/src/pages/OrdersPage.tsx | 232 --------------- apps/route-ops-web/src/routeScopeConfig.ts | 36 +-- apps/route-ops-web/src/state.ts | 276 ------------------ apps/route-ops-web/src/types.ts | 77 ----- .../tests/drivers-routes.test.tsx | 75 ----- apps/route-ops-web/tests/map-helpers.test.ts | 40 +-- .../route-ops-web/tests/orders-table.test.tsx | 146 +-------- apps/route-ops-web/tests/state.test.ts | 100 +------ 54 files changed, 122 insertions(+), 2118 deletions(-) create mode 100644 apps/delivery-api/tests/support/fake-driver-push-provider.ts delete mode 100644 apps/route-ops-web/src/components/PageShell.tsx diff --git a/apps/delivery-api/src/config/env.ts b/apps/delivery-api/src/config/env.ts index 8a2a93a1..42424357 100644 --- a/apps/delivery-api/src/config/env.ts +++ b/apps/delivery-api/src/config/env.ts @@ -1,6 +1,3 @@ -import type { TokenEncryptionKey } from '../modules/security/token-encryption.js'; -import { loadTokenEncryptionKey } from '../modules/security/token-encryption.js'; - export type AppEnv = { nodeEnv: string; port: number; @@ -17,12 +14,6 @@ export function loadEnv(input: NodeJS.ProcessEnv = process.env): AppEnv { }; } -export function loadShopifyTokenEncryptionKey( - input: NodeJS.ProcessEnv = process.env -): TokenEncryptionKey { - return loadTokenEncryptionKey(input.SHOPIFY_TOKEN_ENCRYPTION_KEY); -} - function parsePort(value: string | undefined): number { if (value === undefined || value.trim() === '') { return DEFAULT_PORT; diff --git a/apps/delivery-api/src/modules/customer-email/customer-email.service.ts b/apps/delivery-api/src/modules/customer-email/customer-email.service.ts index c589b8a3..9f509f4c 100644 --- a/apps/delivery-api/src/modules/customer-email/customer-email.service.ts +++ b/apps/delivery-api/src/modules/customer-email/customer-email.service.ts @@ -1527,14 +1527,6 @@ function automaticSettingsVersion(settings: CustomerEmailSettings): string { return `v3:g${settings.globalVersion}:t${templateVersions}`; } -export function readCustomerEmailSettingsPayload(value: unknown): CustomerEmailSettings | null { - try { - return validateCustomerEmailSettingsPayload(value); - } catch { - return null; - } -} - export function readCustomerEmailGlobalSettingsPayload(value: unknown): { branding: Partial; expectedVersion: number; diff --git a/apps/delivery-api/src/modules/driver/driver-runtime-diagnostics.contract.ts b/apps/delivery-api/src/modules/driver/driver-runtime-diagnostics.contract.ts index 1fd66efa..53b80d75 100644 --- a/apps/delivery-api/src/modules/driver/driver-runtime-diagnostics.contract.ts +++ b/apps/delivery-api/src/modules/driver/driver-runtime-diagnostics.contract.ts @@ -421,7 +421,3 @@ export function parseDriverDiagnosticEnvelopeDetailed(value: unknown): ParsedDri rejectedRecords }; } - -export function parseDriverDiagnosticEnvelope(value: unknown): DriverDiagnosticEnvelope | null { - return parseDriverDiagnosticEnvelopeDetailed(value)?.envelope ?? null; -} diff --git a/apps/delivery-api/src/modules/driver/driver-token-verifier.ts b/apps/delivery-api/src/modules/driver/driver-token-verifier.ts index 81455f18..5ec5692a 100644 --- a/apps/delivery-api/src/modules/driver/driver-token-verifier.ts +++ b/apps/delivery-api/src/modules/driver/driver-token-verifier.ts @@ -1,7 +1,5 @@ import { createHmac, timingSafeEqual } from 'node:crypto'; -import { normalizeDriverCommerceDomain } from './driver-commerce-domain.js'; -const DRIVER_AUDIENCE = 'clever-delivery-driver'; const DRIVER_ACCOUNT_AUDIENCE = 'clever-driver-account'; const DRIVER_ROUTE_AUDIENCE = 'clever-delivery-driver-route'; const MIN_DRIVER_JWT_SECRET_CHARACTERS = 32; @@ -22,14 +20,6 @@ export type VerifiedDriverAccountToken = { tokenVersion: number; }; -export type VerifiedDriverToken = { - driverId: string; - issuedAt: Date; - shopDomain: string; - subject: string; - tokenVersion: number; -}; - export type VerifiedDriverRouteToken = { accountId: string; issuedAt: Date; @@ -43,14 +33,6 @@ export type VerifyDriverTokenOptions = { secret: string; }; -export type SignDriverTokenInput = { - driverId: string; - expiresInSeconds: number; - shopDomain: string; - subject: string; - tokenVersion?: number; -}; - export type SignDriverAccountTokenInput = { accountId: string; expiresInSeconds: number; @@ -116,35 +98,6 @@ export function verifyDriverAccountToken( }; } -export function verifyDriverToken( - token: string, - options: VerifyDriverTokenOptions -): VerifiedDriverToken { - const claims = verifyTokenEnvelope(token, options.secret); - const nowSeconds = Math.floor((options.now ?? new Date()).getTime() / 1000); - const audience = requireStringClaim(claims.aud, 'aud'); - const driverId = requireStringClaim(claims.driverId, 'driverId'); - const expiresAt = requireNumberClaim(claims.exp, 'exp'); - const issuedAtSeconds = requireNumberClaim(claims.iat, 'iat'); - const shopDomain = normalizeDriverCommerceDomain(requireStringClaim(claims.shopDomain, 'shopDomain')); - const subject = requireStringClaim(claims.sub, 'sub'); - const tokenVersion = readTokenVersionClaim(claims.tokenVersion); - - if (audience !== DRIVER_AUDIENCE) { - throw new Error('Driver token audience mismatch'); - } - - verifyTokenTimes(claims, expiresAt, nowSeconds); - - return { - driverId, - issuedAt: new Date(issuedAtSeconds * 1000), - shopDomain, - subject, - tokenVersion - }; -} - export function verifyDriverRouteToken( token: string, options: VerifyDriverTokenOptions @@ -185,19 +138,6 @@ export function signDriverAccountToken( }, input.expiresInSeconds, options); } -export function signDriverToken( - input: SignDriverTokenInput, - options: VerifyDriverTokenOptions -): SignDriverTokenResult { - return signToken({ - aud: DRIVER_AUDIENCE, - driverId: requireStringClaim(input.driverId, 'driverId'), - shopDomain: normalizeDriverCommerceDomain(input.shopDomain), - sub: requireStringClaim(input.subject, 'sub'), - tokenVersion: readTokenVersionClaim(input.tokenVersion) - }, input.expiresInSeconds, options); -} - export function signDriverRouteToken( input: SignDriverRouteTokenInput, options: VerifyDriverTokenOptions diff --git a/apps/delivery-api/src/modules/dsv/dsv-customer-account-invitations.service.ts b/apps/delivery-api/src/modules/dsv/dsv-customer-account-invitations.service.ts index 0d2f924a..ec9b595d 100644 --- a/apps/delivery-api/src/modules/dsv/dsv-customer-account-invitations.service.ts +++ b/apps/delivery-api/src/modules/dsv/dsv-customer-account-invitations.service.ts @@ -5,7 +5,6 @@ import { appScopedShopWhere } from '../shopify/shopify-app-scope.js'; import type { AdminStoreSettings } from '../commerce/admin-store-settings.service.js'; import { normalizeDsvOperationalSettings } from './dsv-operational-settings.js'; import type { DsvManualEmailService } from './dsv-manual-email.service.js'; -import { loadDsvWebPublicOrigin } from './dsv-web-public-origin.js'; export type DsvCustomerAccountInvitePurpose = 'SIGNUP' | 'PASSWORD_RESET'; export type DsvCustomerAccountApiStatus = 'INVITED' | 'ACTIVE' | 'DISABLED' | 'EXPIRED'; @@ -528,10 +527,6 @@ export class DsvCustomerAccountServiceError extends Error { } } -export function loadDsvCustomerAccountWebPublicOrigin(value: string | undefined): string | undefined { - return loadDsvWebPublicOrigin(value); -} - export function createCustomerSessionSubject(input: { accountId: string; activeSessionId: string }): string { if (!uuidPattern.test(input.accountId) || !uuidPattern.test(input.activeSessionId)) { throw new Error('Invalid DSV customer session subject'); diff --git a/apps/delivery-api/src/modules/dsv/dsv-driver-identity.ts b/apps/delivery-api/src/modules/dsv/dsv-driver-identity.ts index 8796c653..fc453ce9 100644 --- a/apps/delivery-api/src/modules/dsv/dsv-driver-identity.ts +++ b/apps/delivery-api/src/modules/dsv/dsv-driver-identity.ts @@ -1,7 +1,3 @@ -import { createHmac } from 'node:crypto'; - -const RESIDENT_NUMBER_FRONT_PATTERN = /^\d{7}$/u; - export function normalizeDsvDriverLoginId(value: string): string { return value.trim().toLowerCase(); } @@ -9,20 +5,3 @@ export function normalizeDsvDriverLoginId(value: string): string { export function normalizeDsvDriverPhone(value: string): string { return value.replace(/\D/gu, ''); } - -export function fingerprintResidentNumberFront( - residentNumberFront: string, - secret: string, -): string { - if (!RESIDENT_NUMBER_FRONT_PATTERN.test(residentNumberFront)) { - throw new Error('residentNumberFront must contain exactly seven digits'); - } - if (secret.length < 32) { - throw new Error('DSV driver identity secret must contain at least 32 characters'); - } - - return createHmac('sha256', secret) - .update('clever-dsv-driver-resident-front-v1\0', 'utf8') - .update(residentNumberFront, 'utf8') - .digest('hex'); -} diff --git a/apps/delivery-api/src/modules/dsv/dsv-principal.ts b/apps/delivery-api/src/modules/dsv/dsv-principal.ts index 27502605..39c904d6 100644 --- a/apps/delivery-api/src/modules/dsv/dsv-principal.ts +++ b/apps/delivery-api/src/modules/dsv/dsv-principal.ts @@ -171,24 +171,3 @@ export function requireDsvScopes(principal: DsvPrincipal, requiredScopes: readon if (requiredScopes.every((scope) => granted.has(scope))) return; throw new DsvForbiddenError({ principal, requiredScopes }); } - -export function requireCustomerDeliveryPrincipal(input: { - customerId?: string; - destinationId?: string; - principal: DsvPrincipal; -}): DsvCustomerUserPrincipal { - requireDsvScopes(input.principal, ['dsv:customer-deliveries:read']); - if (input.principal.principalType !== 'CUSTOMER_USER' || input.principal.customerId === '') { - throw new DsvForbiddenError({ - principal: input.principal, - requiredScopes: ['dsv:customer-deliveries:read'], - }); - } - if (input.customerId !== undefined && input.customerId !== input.principal.customerId) { - throw new DsvForbiddenError({ - principal: input.principal, - requiredScopes: ['dsv:customer-deliveries:read'], - }); - } - return input.principal; -} diff --git a/apps/delivery-api/src/modules/dsv/dsv-v1-read.dto.ts b/apps/delivery-api/src/modules/dsv/dsv-v1-read.dto.ts index 2359fe7b..b2cb6c70 100644 --- a/apps/delivery-api/src/modules/dsv/dsv-v1-read.dto.ts +++ b/apps/delivery-api/src/modules/dsv/dsv-v1-read.dto.ts @@ -24,31 +24,6 @@ export const dsvV1ErrorCodes = [ ] as const; export type DsvV1ErrorCode = typeof dsvV1ErrorCodes[number]; -export const dsvV1CustomerDeliveryRequiredFields = [ - 'sellerOrderId', - 'sellerOrderKey', - 'destinationId', - 'destinationDisplayName', - 'shippedBoxes', - 'deliveryStatus', - 'etaStatus', - 'eventSummary', - 'proofStatus', -] as const; - -export const dsvV1SellerOrderSummaryRequiredFields = [ - 'sellerOrderId', - 'sellerOrderKey', - 'deliveryStopId', - 'customerId', - 'destinationId', - 'assignmentStatus', - 'etaStatus', - 'eventSummary', -] as const; - -export const dsvV1SessionRequiredFields = ['csrfToken', 'principalType', 'shopId', 'scopes'] as const; - export type DsvV1PrincipalType = 'DSV_ADMIN' | 'CUSTOMER_USER' | 'DRIVER' | 'IMPORT_WORKER' | 'DEVICE'; export type DsvV1SuccessEnvelope = { diff --git a/apps/delivery-api/src/modules/geocoding/geocoding.service.ts b/apps/delivery-api/src/modules/geocoding/geocoding.service.ts index 3de8d4a5..130b8449 100644 --- a/apps/delivery-api/src/modules/geocoding/geocoding.service.ts +++ b/apps/delivery-api/src/modules/geocoding/geocoding.service.ts @@ -368,12 +368,6 @@ export function normalizeAddress(address: GeocodingAddress): string | null { return normalizeAddressParts(addressParts(address)); } -export function normalizeAddressQueries(address: GeocodingAddress): string[] { - return buildGeocodingQueries(address).flatMap((query) => - query.kind === 'freeform' ? [query.q] : [], - ); -} - export function buildGeocodingQueries(address: GeocodingAddress): GeocodingQuery[] { const full = normalizeAddress(address); if (full === null) return []; diff --git a/apps/delivery-api/src/modules/notifications/admin-notification.dependencies.ts b/apps/delivery-api/src/modules/notifications/admin-notification.dependencies.ts index 6b25fbe7..8fc1acb7 100644 --- a/apps/delivery-api/src/modules/notifications/admin-notification.dependencies.ts +++ b/apps/delivery-api/src/modules/notifications/admin-notification.dependencies.ts @@ -41,9 +41,3 @@ export function createAdminNotificationRuntime(input: { start: () => bridge?.start() ?? Promise.resolve(), }; } - -export function createAdminNotificationService(input: { - prisma: PrismaClient; -}): AdminNotificationService { - return createAdminNotificationRuntime(input).service; -} diff --git a/apps/delivery-api/src/modules/route-grouping/driver-push.provider.ts b/apps/delivery-api/src/modules/route-grouping/driver-push.provider.ts index 631211e2..dac31d8c 100644 --- a/apps/delivery-api/src/modules/route-grouping/driver-push.provider.ts +++ b/apps/delivery-api/src/modules/route-grouping/driver-push.provider.ts @@ -23,19 +23,6 @@ export type DriverPushProvider = { sendRouteNotification(message: DriverRoutePushMessage): Promise; }; -export class FakeDriverPushProvider implements DriverPushProvider { - readonly providerName = 'fake'; - readonly sentMessages: DriverRoutePushMessage[] = []; - - sendRouteNotification(message: DriverRoutePushMessage): Promise { - this.sentMessages.push(message); - return Promise.resolve({ - providerMessageId: `fake:${message.routeGroupingId ?? 'standalone'}:${message.childVersion ?? message.publicationVersion ?? 'current'}:${message.routePlanId}`, - status: 'SENT' - }); - } -} - export class DisabledDriverPushProvider implements DriverPushProvider { readonly providerName = 'disabled'; diff --git a/apps/delivery-api/src/modules/route-grouping/route-grouping.geometry.ts b/apps/delivery-api/src/modules/route-grouping/route-grouping.geometry.ts index 22c3c056..9bf43f39 100644 --- a/apps/delivery-api/src/modules/route-grouping/route-grouping.geometry.ts +++ b/apps/delivery-api/src/modules/route-grouping/route-grouping.geometry.ts @@ -70,19 +70,3 @@ export function coordinatesFromGeoJsonPolygon(value: unknown): PolygonRing { }) .filter((entry): entry is Coordinate => entry !== null); } - -export function geoJsonPolygonFromCoordinates(vertices: PolygonRing): { type: 'Polygon'; coordinates: number[][][] } { - const ring = vertices.map((vertex) => [vertex.longitude, vertex.latitude]); - const first = ring[0]; - const last = ring[ring.length - 1]; - const closed = - first !== undefined && - last !== undefined && - first[0] === last[0] && - first[1] === last[1] - ? ring - : first === undefined - ? ring - : [...ring, first]; - return { type: 'Polygon', coordinates: [closed] }; -} diff --git a/apps/delivery-api/src/modules/route-grouping/route-grouping.service.ts b/apps/delivery-api/src/modules/route-grouping/route-grouping.service.ts index 54992d35..69551883 100644 --- a/apps/delivery-api/src/modules/route-grouping/route-grouping.service.ts +++ b/apps/delivery-api/src/modules/route-grouping/route-grouping.service.ts @@ -169,31 +169,6 @@ type ChildRouteGeometrySnapshot = { routeStopPoints: RoutePlanRouteStopPoint[]; }; -export function currentRouteBindingAuthorityState( - childVersionId: string, - snapshotOrderIds: string[], - assignments: Array<{ orderId: string; order: { currentRouteVersionId: string | null } }> -): 'EXACT' | 'LEGACY_UNBOUND' | 'MISMATCH' { - const snapshotOrderIdSet = new Set(snapshotOrderIds); - if (snapshotOrderIdSet.size !== snapshotOrderIds.length) return 'MISMATCH'; - - const snapshotAssignments = assignments.filter(({ orderId }) => snapshotOrderIdSet.has(orderId)); - if (snapshotAssignments.length !== snapshotOrderIds.length) return 'MISMATCH'; - - const boundOrderIds = assignments - .filter(({ order }) => order.currentRouteVersionId === childVersionId) - .map(({ orderId }) => orderId) - .sort(); - const sortedSnapshotOrderIds = [...snapshotOrderIds].sort(); - const exactBinding = sortedSnapshotOrderIds.length === boundOrderIds.length - && sortedSnapshotOrderIds.every((orderId, index) => orderId === boundOrderIds[index]); - if (exactBinding) return 'EXACT'; - - const entirelyUnbound = boundOrderIds.length === 0 - && snapshotAssignments.every(({ order }) => order.currentRouteVersionId === null); - return entirelyUnbound ? 'LEGACY_UNBOUND' : 'MISMATCH'; -} - type OptimizedDraftRoute = { assignments: LoadedAssignment[]; routeResult: RoutePlanRouteResult; diff --git a/apps/delivery-api/src/modules/route-ops/route-scope-config.ts b/apps/delivery-api/src/modules/route-ops/route-scope-config.ts index bebe1132..1f1c077e 100644 --- a/apps/delivery-api/src/modules/route-ops/route-scope-config.ts +++ b/apps/delivery-api/src/modules/route-ops/route-scope-config.ts @@ -165,14 +165,6 @@ export function isActiveDeliverySession(config: RouteScopeConfigDto, value: unkn return isActiveRouteScopeValue(config.deliverySessions, value); } -export function buildActiveServiceTypes(config: RouteScopeConfigDto): RouteScopeValueDto[] { - return config.serviceTypes.filter((item) => item.enabled); -} - -export function buildActiveDeliverySessions(config: RouteScopeConfigDto): RouteScopeValueDto[] { - return config.deliverySessions.filter((item) => item.enabled); -} - function normalizeRouteScopeValues(input: { builtIns: readonly RouteScopeValueDto[]; raw: unknown; diff --git a/apps/delivery-api/src/modules/route-plans/route-execution-ownership.ts b/apps/delivery-api/src/modules/route-plans/route-execution-ownership.ts index a5cf0425..84298175 100644 --- a/apps/delivery-api/src/modules/route-plans/route-execution-ownership.ts +++ b/apps/delivery-api/src/modules/route-plans/route-execution-ownership.ts @@ -2,20 +2,6 @@ import { Prisma } from '@prisma/client'; type RouteExecutionOwnershipTx = { $queryRaw(query: TemplateStringsArray | Prisma.Sql, ...values: unknown[]): Promise; - routePlanStop: { - findFirst(args: { - select: { deliveryStopId: true; routePlanId: true }; - where: { - deliveryStopId: { in: string[] }; - routePlanId: { not: string }; - routePlan: { shopId: string; status: 'IN_PROGRESS' }; - }; - }): Promise<{ deliveryStopId: string; routePlanId: string } | null>; - findMany(args: { - select: { deliveryStopId: true }; - where: { routePlanId: string }; - }): Promise>; - }; }; export class RouteExecutionConflictError extends Error { @@ -40,7 +26,7 @@ export type RouteDispatchConflict = { }; export async function lockRouteExecutionStops( - tx: Pick, + tx: RouteExecutionOwnershipTx, deliveryStopIds: string[] ): Promise { for (const stopId of [...new Set(deliveryStopIds)].sort()) { @@ -51,7 +37,7 @@ export async function lockRouteExecutionStops( // Planning membership is many-to-many. Dispatch reserves execution, separately // from the order's legacy primary-route projection. No stop outcome is reset. export async function assertRouteDispatchOwnership( - tx: Pick, + tx: RouteExecutionOwnershipTx, input: { deliveryStopIds: string[]; routePlanId: string; shopId: string } ): Promise { const stopIds = [...new Set(input.deliveryStopIds)].sort(); @@ -100,7 +86,7 @@ export function withoutDispatchReservation(value: unknown): Prisma.InputJsonObje } export async function claimRouteExecutionProjection( - tx: Pick, + tx: RouteExecutionOwnershipTx, input: { routePlanId: string; shopId: string } ): Promise { // This singular field remains a compatibility projection for order/DSV @@ -117,51 +103,3 @@ export async function claimRouteExecutionProjection( AND s."routePlanId" = ${input.routePlanId}::uuid AND o."shopId" = ${input.shopId}::uuid `); } - -export async function assertRouteExecutionOwnership( - tx: RouteExecutionOwnershipTx, - input: { - createConflictError?: (conflict: { deliveryStopId: string; routePlanId: string }) => Error; - deliveryStopIds: string[]; - routePlanId: string; - shopId: string; - } -): Promise { - const deliveryStopIds = [...new Set(input.deliveryStopIds)].sort((left, right) => left.localeCompare(right)); - if (deliveryStopIds.length === 0) return; - - await lockRouteExecutionStops(tx, deliveryStopIds); - - const conflict = await tx.routePlanStop.findFirst({ - select: { deliveryStopId: true, routePlanId: true }, - where: { - deliveryStopId: { in: deliveryStopIds }, - routePlanId: { not: input.routePlanId }, - routePlan: { - shopId: input.shopId, - status: 'IN_PROGRESS' - } - } - }); - if (conflict !== null) { - throw input.createConflictError?.(conflict) ?? new RouteExecutionConflictError(conflict.routePlanId, conflict.deliveryStopId); - } -} - -export async function assertRoutePlanExecutionOwnership( - tx: RouteExecutionOwnershipTx, - input: { - createConflictError?: (conflict: { deliveryStopId: string; routePlanId: string }) => Error; - routePlanId: string; - shopId: string; - } -): Promise { - const stops = await tx.routePlanStop.findMany({ - select: { deliveryStopId: true }, - where: { routePlanId: input.routePlanId } - }); - await assertRouteExecutionOwnership(tx, { - ...input, - deliveryStopIds: stops.map((stop) => stop.deliveryStopId) - }); -} diff --git a/apps/delivery-api/src/modules/route-tracking/route-tracking.geometry.ts b/apps/delivery-api/src/modules/route-tracking/route-tracking.geometry.ts index 935d5bb0..2f24fb93 100644 --- a/apps/delivery-api/src/modules/route-tracking/route-tracking.geometry.ts +++ b/apps/delivery-api/src/modules/route-tracking/route-tracking.geometry.ts @@ -129,35 +129,6 @@ export async function persistRouteTrackingGeometryPosition( return document; } -export async function rebuildRouteTrackingGeometryForRoute( - prisma: RouteTrackingGeometryPrismaClient, - routePlanId: string, - now = new Date() -): Promise { - await prisma.$queryRaw( - Prisma.sql`SELECT TRUE AS "locked" FROM pg_advisory_xact_lock(hashtextextended(${routePlanId}, 0))` - ); - const retentionCutoff = new Date( - now.getTime() - ROUTE_TRACKING_GEOMETRY_RETENTION_DAYS * 24 * 60 * 60 * 1000 - ); - const eventWindow = await loadRouteTrackingEventWindow(prisma, routePlanId); - const document = buildRouteTrackingGeometryDocument( - await loadRouteTrackingPositions(prisma, routePlanId, retentionCutoff, eventWindow) - ); - if (document.coordinates.length === 0) return document; - const write = createRouteTrackingGeometryWrite(routePlanId, document); - await prisma.routeTrackingGeometry.upsert({ create: write, update: write, where: { routePlanId } }); - const lastSample = document.samples.at(-1)!; - if (prisma.routeTrackingRoadMatchJob !== undefined) { - await enqueueRouteTrackingRoadMatch(prisma as RouteTrackingGeometryPrismaClient & { routeTrackingRoadMatchJob: Prisma.TransactionClient['routeTrackingRoadMatchJob'] }, { - lastInputOccurredAt: new Date(lastSample.occurredAt), - routePlanId, - sourcePointCount: document.sourcePointCount, - }); - } - return document; -} - export function buildRouteTrackingGeometryDocument( positions: RouteTrackingGeometryPositionInput[] ): RouteTrackingGeometryDocumentV1 { diff --git a/apps/delivery-api/src/modules/route-tracking/route-tracking.road-match.ts b/apps/delivery-api/src/modules/route-tracking/route-tracking.road-match.ts index 056ca85b..034fdd40 100644 --- a/apps/delivery-api/src/modules/route-tracking/route-tracking.road-match.ts +++ b/apps/delivery-api/src/modules/route-tracking/route-tracking.road-match.ts @@ -9,7 +9,6 @@ import { } from '../route-plans/route-engine-coverage.js'; import { ROUTE_TRACKING_V1_POLICY } from './route-tracking.policy.js'; import { - readRouteTrackingGeometryDocument, type RouteTrackingGeometryDocumentV1, type RouteTrackingGeometryPositionInput, type RouteTrackingGeometryRecord, @@ -497,16 +496,6 @@ export function buildRouteTrackingRoadMatchedPath( }; } -export function shouldRefreshRouteTrackingRoadMatchedPath(record: RouteTrackingGeometryRecord | null | undefined): boolean { - if (record === null || record === undefined) return false; - const document = readRouteTrackingGeometryDocument(record); - if (document.coordinates.length < 2) return false; - if (record.roadMatchedSchemaVersion !== ROUTE_TRACKING_ROAD_MATCH_CACHE_VERSION) return true; - if ((record.roadMatchedSourcePointCount ?? 0) < record.sourcePointCount) return true; - if (record.roadMatchedLastInputOccurredAt === null || record.roadMatchedLastInputOccurredAt === undefined) return true; - return false; -} - export function buildRouteTrackingRoadMatchCacheWrite(path: RouteTrackingRoadMatchedPathV1): { roadMatchedCoverage: RouteEngineCoverage; roadMatchedGeometry: Prisma.JsonObject | typeof Prisma.JsonNull; diff --git a/apps/delivery-api/src/modules/shopify/order-sync.dependencies.ts b/apps/delivery-api/src/modules/shopify/order-sync.dependencies.ts index 6b160278..acdeb267 100644 --- a/apps/delivery-api/src/modules/shopify/order-sync.dependencies.ts +++ b/apps/delivery-api/src/modules/shopify/order-sync.dependencies.ts @@ -31,14 +31,6 @@ export type AdminOrdersRuntimeEnv = ShopifyAppCredentialsEnv & Partial>; -export function loadAdminOrdersDependencies(input: { - adminNotificationService?: AdminNotificationServiceApi | undefined; - env: AdminOrdersRuntimeEnv; - prisma: PrismaClient; -}): AdminOrdersDependencies | undefined { - return loadAdminOrdersRuntime(input)?.dependencies; -} - export function loadAdminOrdersRuntime(input: { adminNotificationService?: AdminNotificationServiceApi | undefined; env: AdminOrdersRuntimeEnv; diff --git a/apps/delivery-api/src/modules/shopify/webhook.dependencies.ts b/apps/delivery-api/src/modules/shopify/webhook.dependencies.ts index e63f73df..12ccedc5 100644 --- a/apps/delivery-api/src/modules/shopify/webhook.dependencies.ts +++ b/apps/delivery-api/src/modules/shopify/webhook.dependencies.ts @@ -27,12 +27,6 @@ type LoadShopifyWebhookDependenciesInput = { prisma: PrismaClient; }; -export function loadShopifyWebhookDependencies( - input: LoadShopifyWebhookDependenciesInput -): ShopifyWebhookDependencies | undefined { - return loadShopifyWebhookRuntime(input)?.dependencies; -} - export function loadShopifyWebhookRuntime( input: LoadShopifyWebhookDependenciesInput & { logger?: Pick } ): { dependencies: ShopifyWebhookDependencies; worker?: ShopifyOrderWebhookWorker } | undefined { diff --git a/apps/delivery-api/src/modules/woocommerce/woocommerce-order-sync.service.ts b/apps/delivery-api/src/modules/woocommerce/woocommerce-order-sync.service.ts index 6002299a..3b18bb54 100644 --- a/apps/delivery-api/src/modules/woocommerce/woocommerce-order-sync.service.ts +++ b/apps/delivery-api/src/modules/woocommerce/woocommerce-order-sync.service.ts @@ -54,14 +54,6 @@ export type WooCommerceSyncSingleOrderInput = { sourceOrderId: number | string; }; -export type WooCommerceSyncTier = 'hot' | 'cold'; - -export type WooCommerceSyncTierClassification = { - reason: 'today_or_future_delivery' | 'past_delivery' | 'missing_delivery_date'; - review: boolean; - tier: WooCommerceSyncTier; -}; - type Repository = { findCanonicalOrderById?(input: { orderId: string; shopDomain: string; shopId?: string | undefined }): Promise; listCanonicalOrdersBySourceIdentity(input: ListCanonicalOrdersBySourceIdentityInput): Promise; @@ -403,19 +395,6 @@ export function applyWooModifiedAfterOverlap( return new Date(modifiedAfter.getTime() - Math.floor(overlapWindowMs)); } -export function classifyWooCommerceSyncTier(input: { - deliveryDate: string | null; - today: string; -}): WooCommerceSyncTierClassification { - if (input.deliveryDate === null) { - return { reason: 'missing_delivery_date', review: true, tier: 'cold' }; - } - if (input.deliveryDate >= input.today) { - return { reason: 'today_or_future_delivery', review: false, tier: 'hot' }; - } - return { reason: 'past_delivery', review: false, tier: 'cold' }; -} - function withUpdatedDeliveryFact( synced: UpsertOrderWithDeliveryStopInput['synced'], geocodeStatus: NonNullable['geocodeStatus'], diff --git a/apps/delivery-api/src/routes/admin-ui-route-planning.ts b/apps/delivery-api/src/routes/admin-ui-route-planning.ts index 6936df18..afa11995 100644 --- a/apps/delivery-api/src/routes/admin-ui-route-planning.ts +++ b/apps/delivery-api/src/routes/admin-ui-route-planning.ts @@ -40,11 +40,6 @@ import { type SaveRoutePlanPayload, } from "../modules/route-plans/route-plan.types.js"; import { RouteExecutionConflictError } from "../modules/route-plans/route-execution-ownership.js"; -import type { - RouteOptimizationResult, - RouteOptimizationService, - RouteOptimizationStopSequence, -} from "../modules/route-plans/route-optimization.types.js"; import { readRouteEngineRegistrySummary } from "../modules/route-plans/route-engine-coverage.js"; const ADMIN_UI_APP_ROUTE_PLANS_PATH = "/admin/ui/app/routes"; @@ -1619,156 +1614,6 @@ export function readStopOrderLines( }); } -export type OptimizedStopOrder = { - missingCoordinateStops: number; - source: "clever_v1" | RouteOptimizationResult["source"]; - stops: RouteOptimizationStopSequence[]; -}; - -export async function buildOptimizedStopOrder(input: { - detail: RoutePlanDetail; - routeOptimizationService?: RouteOptimizationService | undefined; - shopDomain: string; -}): Promise { - if (input.routeOptimizationService !== undefined) { - try { - const optimized = await input.routeOptimizationService.optimizeStopOrder({ - detail: input.detail, - shopDomain: input.shopDomain, - }); - if (optimized !== null) { - return optimized; - } - } catch { - // Keep the operator workflow available when the internal solver is degraded. - } - } - - return buildCleverV1OptimizedStopOrder(input.detail); -} - -export function buildCleverV1OptimizedStopOrder( - detail: RoutePlanDetail, -): OptimizedStopOrder { - const sortableStops = detail.stops - .map((stop) => ({ coordinates: readStopCoordinates(stop), stop })) - .filter( - ( - entry, - ): entry is { - coordinates: { latitude: number; longitude: number }; - stop: RoutePlanDetail["stops"][number]; - } => entry.coordinates !== null, - ) - .sort( - (left, right) => - left.stop.sequence - right.stop.sequence || - left.stop.shopifyOrderGid.localeCompare(right.stop.shopifyOrderGid), - ); - const missingStops = detail.stops - .filter((stop) => readStopCoordinates(stop) === null) - .sort( - (left, right) => - left.sequence - right.sequence || - left.shopifyOrderGid.localeCompare(right.shopifyOrderGid), - ); - - const depot = readDepotCoordinates(detail.routePlan); - let origin = depot ?? sortableStops[0]?.coordinates ?? null; - const ordered: RoutePlanDetail["stops"][number][] = []; - const remaining = [...sortableStops]; - - while (remaining.length > 0) { - if (origin === null) { - ordered.push(...remaining.map((entry) => entry.stop)); - remaining.length = 0; - break; - } - let nearestIndex = 0; - let nearestDistance = Number.POSITIVE_INFINITY; - for (let index = 0; index < remaining.length; index += 1) { - const candidate = remaining[index]; - if (candidate === undefined) continue; - const distance = haversineMeters(origin, candidate.coordinates); - if ( - distance < nearestDistance || - (distance === nearestDistance && - candidate.stop.shopifyOrderGid.localeCompare( - remaining[nearestIndex]?.stop.shopifyOrderGid ?? "", - ) < 0) - ) { - nearestIndex = index; - nearestDistance = distance; - } - } - const [next] = remaining.splice(nearestIndex, 1); - if (next === undefined) break; - ordered.push(next.stop); - origin = next.coordinates; - } - - const stops = [...ordered, ...missingStops].map((stop, index) => ({ - deliveryStopId: stop.deliveryStopId, - sequence: index + 1, - shopifyOrderGid: stop.shopifyOrderGid, - })); - - return { - missingCoordinateStops: missingStops.length, - source: "clever_v1", - stops, - }; -} - -export function buildRouteOptimizeNotice( - optimized: OptimizedStopOrder, -): string { - const sourceLabel = optimized.source === "vroom" ? "VROOM" : "CLEVER v1"; - return optimized.missingCoordinateStops === 0 - ? `${sourceLabel} optimized sequence saved.` - : `${sourceLabel} optimized sequence saved; ${optimized.missingCoordinateStops} stop(s) without coordinates stayed at the end.`; -} - -export function readDepotCoordinates( - routePlan: RoutePlanSummary, -): { latitude: number; longitude: number } | null { - const latitude = routePlan.depot.latitude; - const longitude = routePlan.depot.longitude; - if (latitude === null || longitude === null) return null; - return { latitude, longitude }; -} - -export function readStopCoordinates( - stop: RoutePlanDetail["stops"][number], -): { latitude: number; longitude: number } | null { - const latitude = stop.coordinates.latitude; - const longitude = stop.coordinates.longitude; - if (latitude === null || longitude === null) return null; - return { latitude, longitude }; -} - -function haversineMeters( - left: { latitude: number; longitude: number }, - right: { latitude: number; longitude: number }, -): number { - const earthRadiusMeters = 6_371_000; - const leftLatitude = toRadians(left.latitude); - const rightLatitude = toRadians(right.latitude); - const deltaLatitude = toRadians(right.latitude - left.latitude); - const deltaLongitude = toRadians(right.longitude - left.longitude); - const a = - Math.sin(deltaLatitude / 2) * Math.sin(deltaLatitude / 2) + - Math.cos(leftLatitude) * - Math.cos(rightLatitude) * - Math.sin(deltaLongitude / 2) * - Math.sin(deltaLongitude / 2); - return 2 * earthRadiusMeters * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); -} - -function toRadians(value: number): number { - return (value * Math.PI) / 180; -} - export function normalizeOptionalDate( value: string | null | undefined, ): string | null { diff --git a/apps/delivery-api/tests/admin-active-route-reorder.integration.test.ts b/apps/delivery-api/tests/admin-active-route-reorder.integration.test.ts index 3d6a766e..9c1027e2 100644 --- a/apps/delivery-api/tests/admin-active-route-reorder.integration.test.ts +++ b/apps/delivery-api/tests/admin-active-route-reorder.integration.test.ts @@ -3,7 +3,7 @@ import { PrismaClient } from '@prisma/client'; import { afterAll, describe, expect, test } from 'vitest'; import { PrismaDriverAssignedRouteRepository } from '../src/modules/driver/driver-assigned-route.repository.js'; -import { FakeDriverPushProvider } from '../src/modules/route-grouping/driver-push.provider.js'; +import { FakeDriverPushProvider } from './support/fake-driver-push-provider.js'; import { PrismaRouteGroupingService } from '../src/modules/route-grouping/route-grouping.service.js'; import { PrismaRoutePlanRepository } from '../src/modules/route-plans/route-plan.repository.js'; diff --git a/apps/delivery-api/tests/admin-notification.dependencies.test.ts b/apps/delivery-api/tests/admin-notification.dependencies.test.ts index 2a34b2ca..973bc995 100644 --- a/apps/delivery-api/tests/admin-notification.dependencies.test.ts +++ b/apps/delivery-api/tests/admin-notification.dependencies.test.ts @@ -1,7 +1,7 @@ import type { PrismaClient } from '@prisma/client'; import { describe, expect, test, vi } from 'vitest'; -import { loadAdminOrdersDependencies } from '../src/modules/shopify/order-sync.dependencies.js'; +import { loadAdminOrdersRuntime } from '../src/modules/shopify/order-sync.dependencies.js'; import { loadWooCommerceWebhookDependencies } from '../src/modules/woocommerce/woocommerce.dependencies.js'; import { loadWordPressPluginDependencies } from '../src/modules/wordpress-plugin/wordpress-plugin.dependencies.js'; import type { AdminNotificationServiceApi } from '../src/modules/notifications/admin-notification.service.js'; @@ -64,11 +64,11 @@ type WordPressSyncCarrier = { describe('admin notification dependency wiring', () => { test('threads the shared notification service into admin Shopify order sync', () => { const service = notificationService(); - const dependencies = loadAdminOrdersDependencies({ + const dependencies = loadAdminOrdersRuntime({ adminNotificationService: service, env: { SHOPIFY_API_KEY: 'api-key', SHOPIFY_API_SECRET: 'api-secret' }, prisma: prisma(), - }); + })?.dependencies; const carrier = dependencies?.orderSyncService as unknown as RepositoryCarrier; expect(carrier.options.repository.options?.notificationService).toBe(service); diff --git a/apps/delivery-api/tests/admin-ui-route-planning.optimize.test.ts b/apps/delivery-api/tests/admin-ui-route-planning.optimize.test.ts index 126ff6b8..d644d8e3 100644 --- a/apps/delivery-api/tests/admin-ui-route-planning.optimize.test.ts +++ b/apps/delivery-api/tests/admin-ui-route-planning.optimize.test.ts @@ -1,12 +1,9 @@ import { describe, expect, test } from 'vitest'; import type { CanonicalOrderRow } from '../src/modules/shopify/order-sync.mapper.js'; -import { - buildRouteOptimizeNotice, - toRouteOpsOrderDto, -} from '../src/routes/admin-ui-route-planning.js'; +import { toRouteOpsOrderDto } from '../src/routes/admin-ui-route-planning.js'; -describe('buildRouteOptimizeNotice', () => { +describe('route planning DTO', () => { test('exposes order total and currency to the Route Ops UI DTO', () => { const dto = toRouteOpsOrderDto(canonicalOrderRow({ currencyCode: 'CAD', @@ -16,25 +13,6 @@ describe('buildRouteOptimizeNotice', () => { expect(dto.currencyCode).toBe('CAD'); expect(dto.totalPriceAmount).toBe('42.50'); }); - - test('labels VROOM optimizer results distinctly from clever fallback', () => { - expect( - buildRouteOptimizeNotice({ - missingCoordinateStops: 0, - source: 'vroom', - stops: [], - }), - ).toBe('VROOM optimized sequence saved.'); - expect( - buildRouteOptimizeNotice({ - missingCoordinateStops: 2, - source: 'vroom', - stops: [], - }), - ).toBe( - 'VROOM optimized sequence saved; 2 stop(s) without coordinates stayed at the end.', - ); - }); }); function canonicalOrderRow( diff --git a/apps/delivery-api/tests/driver-event-contract-v2.integration.test.ts b/apps/delivery-api/tests/driver-event-contract-v2.integration.test.ts index e59e2911..58c50266 100644 --- a/apps/delivery-api/tests/driver-event-contract-v2.integration.test.ts +++ b/apps/delivery-api/tests/driver-event-contract-v2.integration.test.ts @@ -16,7 +16,7 @@ import { PrismaDriverEventReceiptRepository } from '../src/modules/driver/driver-event-receipt.repository.js'; import { PrismaRouteGroupingService, replaceCurrentRouteGroupingChildVersion } from '../src/modules/route-grouping/route-grouping.service.js'; -import { FakeDriverPushProvider } from '../src/modules/route-grouping/driver-push.provider.js'; +import { FakeDriverPushProvider } from './support/fake-driver-push-provider.js'; import { PrismaRoutePlanRepository } from '../src/modules/route-plans/route-plan.repository.js'; import { PrismaDriverRouteCompletionReviewRepository } from '../src/modules/driver/driver-route-completion-review.repository.js'; diff --git a/apps/delivery-api/tests/driver-runtime-diagnostics.contract.test.ts b/apps/delivery-api/tests/driver-runtime-diagnostics.contract.test.ts index 03ecb7d2..03e9610c 100644 --- a/apps/delivery-api/tests/driver-runtime-diagnostics.contract.test.ts +++ b/apps/delivery-api/tests/driver-runtime-diagnostics.contract.test.ts @@ -2,7 +2,6 @@ import { describe, expect, test } from 'vitest'; import { DRIVER_DIAGNOSTIC_MAX_BATCH_BYTES, - parseDriverDiagnosticEnvelope, parseDriverDiagnosticEnvelopeDetailed, parseDriverDiagnosticSnapshot, type DriverDiagnosticBlocker, @@ -134,30 +133,30 @@ describe('driver diagnostic contract parser', () => { }] }); - const parsed = parseDriverDiagnosticEnvelope(unsafe); + const parsed = parseDriverDiagnosticEnvelopeDetailed(unsafe)?.envelope ?? null; expect(parsed).not.toBeNull(); const serialized = JSON.stringify(parsed); expect(serialized).not.toMatch(/authorization|Bearer|latitude|longitude|phone|errorMessage|customer|address|token/iu); }); test('rejects an unknown live reason instead of silently projecting healthy', () => { - expect(parseDriverDiagnosticEnvelope(envelope({ + expect(parseDriverDiagnosticEnvelopeDetailed(envelope({ liveSnapshot: snapshot({ blockers: [{ ...blocker(), reason: 'FREE_TEXT_FAILURE' as never }] }) - }))).toBeNull(); + }))?.envelope ?? null).toBeNull(); }); test('accepts a user-initiated support report without retaining free text', () => { const record = envelope().records[0]!; - const parsed = parseDriverDiagnosticEnvelope(envelope({ + const parsed = parseDriverDiagnosticEnvelopeDetailed(envelope({ records: [{ ...record, kind: 'USER_REPORT', message: 'customer address, PIN, and token must not persist', rawError: 'private stack trace' }] - })); + }))?.envelope ?? null; expect(parsed?.records[0]).toMatchObject({ diagnosticId: DIAGNOSTIC_ID, kind: 'USER_REPORT' }); expect(JSON.stringify(parsed?.records[0])).not.toMatch(/message|rawError|customer address|PIN|token|stack trace/iu); @@ -185,9 +184,9 @@ describe('driver diagnostic contract parser', () => { }); test('enforces record count and serialized 64 KiB limits before sanitization', () => { - expect(parseDriverDiagnosticEnvelope(envelope({ records: Array.from({ length: 51 }, () => envelope().records[0]) }))) + expect(parseDriverDiagnosticEnvelopeDetailed(envelope({ records: Array.from({ length: 51 }, () => envelope().records[0]) }))?.envelope ?? null) .toBeNull(); - expect(parseDriverDiagnosticEnvelope(envelope({ ignored: 'x'.repeat(DRIVER_DIAGNOSTIC_MAX_BATCH_BYTES) }))) + expect(parseDriverDiagnosticEnvelopeDetailed(envelope({ ignored: 'x'.repeat(DRIVER_DIAGNOSTIC_MAX_BATCH_BYTES) }))?.envelope ?? null) .toBeNull(); }); @@ -202,13 +201,13 @@ describe('driver diagnostic contract parser', () => { const clientEventId = `completion-assistance-${operation}:${REQUEST_ID}`; const payload = envelope(); const record = payload.records[0]!; - const parsed = parseDriverDiagnosticEnvelope(envelope({ + const parsed = parseDriverDiagnosticEnvelopeDetailed(envelope({ records: [{ ...record, identifiers: { clientEventId }, snapshot: snapshot({ blockers: [blocker({ clientEventId, reason: 'STORAGE_READ_FAILED', stage: 'STORAGE' })] }) }] - })); + }))?.envelope ?? null; expect(parsed?.records[0]?.identifiers?.clientEventId).toBe(clientEventId); expect(parsed?.records[0]?.snapshot.blockers?.[0]?.clientEventId).toBe(clientEventId); }); @@ -216,9 +215,9 @@ describe('driver diagnostic contract parser', () => { test('accepts a strict version-7 UUID completion assistance identifier', () => { const clientEventId = 'completion-assistance-read:018f47a2-4b5c-7def-8abc-0123456789ab'; const record = envelope().records[0]!; - const parsed = parseDriverDiagnosticEnvelope(envelope({ + const parsed = parseDriverDiagnosticEnvelopeDetailed(envelope({ records: [{ ...record, identifiers: { clientEventId } }] - })); + }))?.envelope ?? null; expect(parsed?.records[0]?.identifiers?.clientEventId).toBe(clientEventId); }); @@ -547,14 +546,14 @@ describe('driver runtime diagnostic projection', () => { describe('database integer wire bounds', () => { test('accepts the PostgreSQL integer boundary and rejects envelope overflow', () => { - expect(parseDriverDiagnosticEnvelope(envelope({ discardedRecordCount: 2_147_483_647 }))).not.toBeNull(); - expect(parseDriverDiagnosticEnvelope(envelope({ discardedRecordCount: 2_147_483_648 }))).toBeNull(); + expect(parseDriverDiagnosticEnvelopeDetailed(envelope({ discardedRecordCount: 2_147_483_647 }))?.envelope ?? null).not.toBeNull(); + expect(parseDriverDiagnosticEnvelopeDetailed(envelope({ discardedRecordCount: 2_147_483_648 }))?.envelope ?? null).toBeNull(); }); test('rejects only an overflowing record sequence with an explicit record rejection', () => { const payload = envelope(); const first = payload.records[0]!; - expect(parseDriverDiagnosticEnvelope(envelope({ records: [{ ...first, sequence: 2_147_483_647 }] }))).not.toBeNull(); + expect(parseDriverDiagnosticEnvelopeDetailed(envelope({ records: [{ ...first, sequence: 2_147_483_647 }] }))?.envelope ?? null).not.toBeNull(); expect(parseDriverDiagnosticEnvelopeDetailed(envelope({ records: [{ ...first, sequence: 2_147_483_648 }] }))) .toMatchObject({ envelope: { records: [] }, rejectedRecords: [{ diagnosticId: DIAGNOSTIC_ID, code: 'INVALID_RECORD' }] }); }); diff --git a/apps/delivery-api/tests/driver-token-verifier.test.ts b/apps/delivery-api/tests/driver-token-verifier.test.ts index 60e6aab4..f39e2e84 100644 --- a/apps/delivery-api/tests/driver-token-verifier.test.ts +++ b/apps/delivery-api/tests/driver-token-verifier.test.ts @@ -4,9 +4,7 @@ import { describe, expect, test } from 'vitest'; import { readDriverJwtSecret, signDriverRouteToken, - signDriverToken, verifyDriverRouteToken, - verifyDriverToken } from '../src/modules/driver/driver-token-verifier.js'; const secret = 'driver-secret'; @@ -25,7 +23,7 @@ describe('readDriverJwtSecret', () => { }); }); -describe('verifyDriverToken', () => { +describe('verifyDriverRouteToken', () => { test('signs route access with only the global account and assigned route scope', () => { const result = signDriverRouteToken( { @@ -49,90 +47,21 @@ describe('verifyDriverToken', () => { expect(decodePayload(result.token)).not.toHaveProperty('shopDomain'); }); - test('signs a short-lived driver JWT that the verifier accepts', () => { - const result = signDriverToken( - { - driverId: 'driver-id', - expiresInSeconds: 900, - shopDomain: 'Example.myshopify.com', - subject: 'driver:driver-id' - }, - { now, secret } - ); - - expect(result.expiresAt).toBe('2026-05-07T06:25:00.000Z'); - expect(verifyDriverToken(result.token, { now, secret })).toEqual({ - driverId: 'driver-id', - issuedAt: new Date('2026-05-07T06:10:00.000Z'), - shopDomain: 'example.myshopify.com', - subject: 'driver:driver-id', - tokenVersion: 0 - }); - }); - - test('signs and verifies driver JWTs for Woo/customer domains', () => { - const result = signDriverToken( - { - driverId: 'driver-id', - expiresInSeconds: 900, - shopDomain: 'Dev1.TomatonoFood.com', - subject: 'driver:driver-id', - tokenVersion: 4 - }, - { now, secret } - ); - - expect(verifyDriverToken(result.token, { now, secret })).toEqual({ - driverId: 'driver-id', - issuedAt: new Date('2026-05-07T06:10:00.000Z'), - shopDomain: 'dev1.tomatonofood.com', - subject: 'driver:driver-id', - tokenVersion: 4 - }); - }); - - test('accepts a server-issued driver JWT and returns driver context', () => { - const token = legacySignDriverToken({ - aud: 'clever-delivery-driver', - driverId: 'driver-id', - exp: Math.floor(now.getTime() / 1000) + 60, - iat: Math.floor(now.getTime() / 1000), - shopDomain: 'example.myshopify.com', - sub: 'driver-auth-subject', - tokenVersion: 3 - }); - - expect(verifyDriverToken(token, { now, secret })).toEqual({ - driverId: 'driver-id', - issuedAt: new Date('2026-05-07T06:10:00.000Z'), - shopDomain: 'example.myshopify.com', - subject: 'driver-auth-subject', - tokenVersion: 3 - }); - }); - - test('rejects invalid commerce domains in server-issued tokens', () => { - expect(() => signDriverToken( - { - driverId: 'driver-id', - expiresInSeconds: 900, - shopDomain: 'localhost', - subject: 'driver:driver-id' - }, - { now, secret } - )).toThrow('Commerce domain is not a valid customer domain'); + test.each([ + [{ aud: 'clever-delivery-driver' }, 'audience mismatch'], + [{ aud: 'clever-driver-account' }, 'audience mismatch'], + [{ exp: Math.floor(now.getTime() / 1000) }, 'has expired'], + [{ nbf: Math.floor(now.getTime() / 1000) + 1 }, 'not active yet'], + ])('rejects incompatible scope or time claims %j', (claims, message) => { + expect(() => verifyDriverRouteToken(signClaims(claims), { now, secret })).toThrow(message); }); test('rejects tokens with invalid signatures', () => { - const token = `${legacySignDriverToken({ - aud: 'clever-delivery-driver', - driverId: 'driver-id', - exp: Math.floor(now.getTime() / 1000) + 60, - shopDomain: 'example.myshopify.com', - sub: 'driver-auth-subject' - }).slice(0, -1)}x`; + const token = signClaims({}); + const [header, payload, signature] = token.split('.'); + const invalidSignature = `${signature?.[0] === 'x' ? 'y' : 'x'}${signature?.slice(1)}`; - expect(() => verifyDriverToken(token, { now, secret })).toThrow('Invalid driver token signature'); + expect(() => verifyDriverRouteToken(`${header}.${payload}.${invalidSignature}`, { now, secret })).toThrow('Invalid driver token signature'); }); }); @@ -142,7 +71,17 @@ function decodePayload(token: string): Record { return JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf8')) as Record; } -function legacySignDriverToken(payload: Record): string { +function signClaims(overrides: Record): string { + const issuedAt = Math.floor(now.getTime() / 1000); + const payload = { + accountId: 'account-id', + aud: 'clever-delivery-driver-route', + exp: issuedAt + 900, + iat: issuedAt, + routePlanId: 'route-plan-id', + sub: 'driver-account:account-id', + ...overrides, + }; const header = { alg: 'HS256', typ: 'JWT' }; const encodedHeader = Buffer.from(JSON.stringify(header), 'utf8').toString('base64url'); const encodedPayload = Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url'); diff --git a/apps/delivery-api/tests/dsv-assignment-command.integration.test.ts b/apps/delivery-api/tests/dsv-assignment-command.integration.test.ts index 3c099d83..5ced20e2 100644 --- a/apps/delivery-api/tests/dsv-assignment-command.integration.test.ts +++ b/apps/delivery-api/tests/dsv-assignment-command.integration.test.ts @@ -13,7 +13,7 @@ import type { RouteGroupingService, } from '../src/modules/route-grouping/route-grouping.types.js'; import type { DsvAssignmentTransactionClient } from '../src/modules/dsv/dsv-assignment-transaction-port.js'; -import { FakeDriverPushProvider } from '../src/modules/route-grouping/driver-push.provider.js'; +import { FakeDriverPushProvider } from './support/fake-driver-push-provider.js'; import { PrismaRouteGroupingService } from '../src/modules/route-grouping/route-grouping.service.js'; const legacySafeTargetClass = 'safe-local-g004-temp-cluster'; diff --git a/apps/delivery-api/tests/dsv-customer-account-invitations.service.test.ts b/apps/delivery-api/tests/dsv-customer-account-invitations.service.test.ts index be477c5d..f4da50e1 100644 --- a/apps/delivery-api/tests/dsv-customer-account-invitations.service.test.ts +++ b/apps/delivery-api/tests/dsv-customer-account-invitations.service.test.ts @@ -3,7 +3,6 @@ import { describe, expect, test, vi } from 'vitest'; import { DsvCustomerAccountServiceError, - loadDsvCustomerAccountWebPublicOrigin, PrismaDsvCustomerAccountService, } from '../src/modules/dsv/dsv-customer-account-invitations.service.js'; import { defaultDsvOperationalSettings } from '../src/modules/dsv/dsv-operational-settings.js'; @@ -17,13 +16,6 @@ const activeSessionId = 'dddddddd-dddd-4ddd-8ddd-dddddddddddd'; const token = 'valid_token_value_12345678901234567890'; describe('PrismaDsvCustomerAccountService', () => { - test('validates http(s) DSV web public origins only', () => { - expect(loadDsvCustomerAccountWebPublicOrigin('https://dsv.example.com')).toBe('https://dsv.example.com'); - expect(loadDsvCustomerAccountWebPublicOrigin('')).toBeUndefined(); - expect(() => loadDsvCustomerAccountWebPublicOrigin('https://dsv.example.com/path')).toThrow(/origin/u); - expect(() => loadDsvCustomerAccountWebPublicOrigin('javascript:alert(1)')).toThrow(/origin/u); - }); - test('returns null for invalid, consumed, revoked, or cross-boundary invites and identifies expiration', async () => { const harness = createHarness(); const service = harness.service; diff --git a/apps/delivery-api/tests/dsv-driver-identity.test.ts b/apps/delivery-api/tests/dsv-driver-identity.test.ts index 9fcac696..52717b77 100644 --- a/apps/delivery-api/tests/dsv-driver-identity.test.ts +++ b/apps/delivery-api/tests/dsv-driver-identity.test.ts @@ -1,7 +1,6 @@ import { describe, expect, test } from 'vitest'; import { - fingerprintResidentNumberFront, normalizeDsvDriverLoginId, normalizeDsvDriverPhone, } from '../src/modules/dsv/dsv-driver-identity.js'; @@ -11,15 +10,4 @@ describe('DSV driver identity helpers', () => { expect(normalizeDsvDriverLoginId(' Driver.One ')).toBe('driver.one'); expect(normalizeDsvDriverPhone('010-9000-0001')).toBe('01090000001'); }); - - test('creates a deterministic keyed fingerprint without retaining the seven digits', () => { - const first = fingerprintResidentNumberFront('9001011', 'identity-secret-a-that-is-long-enough'); - const same = fingerprintResidentNumberFront('9001011', 'identity-secret-a-that-is-long-enough'); - const otherSecret = fingerprintResidentNumberFront('9001011', 'identity-secret-b-that-is-long-enough'); - - expect(first).toBe(same); - expect(first).not.toBe(otherSecret); - expect(first).toMatch(/^[a-f0-9]{64}$/u); - expect(first).not.toContain('9001011'); - }); }); diff --git a/apps/delivery-api/tests/dsv-principal.test.ts b/apps/delivery-api/tests/dsv-principal.test.ts index 1795d50e..339f57f4 100644 --- a/apps/delivery-api/tests/dsv-principal.test.ts +++ b/apps/delivery-api/tests/dsv-principal.test.ts @@ -4,7 +4,6 @@ import { DsvForbiddenError, createDsvAdminPrincipal, createDsvCustomerUserPrincipalFromAccount, - requireCustomerDeliveryPrincipal, requireDsvScopes, } from '../src/modules/dsv/dsv-principal.js'; import type { DsvPrincipal } from '../src/modules/dsv/dsv-principal.js'; @@ -98,16 +97,6 @@ describe('DSV principal authorization', () => { }); expect(() => requireDsvScopes(principal, ['dsv:session:read'])).not.toThrow(); expect(() => requireDsvScopes(principal, ['dsv:customer-deliveries:read'])).not.toThrow(); - expect(requireCustomerDeliveryPrincipal({ - customerId: 'customer-a', - destinationId: 'shared-destination', - principal, - })).toBe(principal); - expect(() => requireCustomerDeliveryPrincipal({ - customerId: 'customer-b', - destinationId: 'shared-destination', - principal, - })).toThrow(DsvForbiddenError); }); test('missing, null, inactive, or empty CustomerAccount status cannot grant CUSTOMER_USER scope', () => { @@ -139,30 +128,4 @@ describe('DSV principal authorization', () => { }, })).toThrow(DsvForbiddenError); }); - - test('destination ID alone cannot authorize customer reads', () => { - const customerPrincipal = createDsvCustomerUserPrincipalFromAccount({ - account: { - customerId: 'customer-a', - shopId: '99999999-9999-4999-8999-999999999999', - status: 'ACTIVE', - }, - }); - const adminPrincipal = createDsvAdminPrincipal({ shopId: '99999999-9999-4999-8999-999999999999' }); - - expect(requireCustomerDeliveryPrincipal({ - customerId: 'customer-a', - destinationId: 'shared-destination', - principal: customerPrincipal, - })).toBe(customerPrincipal); - expect(() => requireCustomerDeliveryPrincipal({ - destinationId: 'shared-destination', - principal: adminPrincipal, - })).toThrow(DsvForbiddenError); - expect(() => requireCustomerDeliveryPrincipal({ - customerId: 'customer-b', - destinationId: 'shared-destination', - principal: customerPrincipal, - })).toThrow(DsvForbiddenError); - }); }); diff --git a/apps/delivery-api/tests/dsv-v1-contract-floor.test.ts b/apps/delivery-api/tests/dsv-v1-contract-floor.test.ts index 2d05c0e9..0b0d44c8 100644 --- a/apps/delivery-api/tests/dsv-v1-contract-floor.test.ts +++ b/apps/delivery-api/tests/dsv-v1-contract-floor.test.ts @@ -2,13 +2,10 @@ import { describe, expect, test } from 'vitest'; import { dsvV1ApiVersion, - dsvV1CustomerDeliveryRequiredFields, dsvV1EmittedProofStatuses, dsvV1ErrorCodes, dsvV1EtaStatuses, dsvV1ProofStatuses, - dsvV1SellerOrderSummaryRequiredFields, - dsvV1SessionRequiredFields, mapDsvV1CustomerDeliveryInquiryItem, mapDsvV1SellerOrderSummary, mapDsvV1SessionPrincipal, @@ -50,7 +47,6 @@ describe('G005 DSV v1 OpenAPI contract floor', () => { }); test('freezes session, ETA, proof, and emitted proof enum floors', () => { - expect(dsvV1SessionRequiredFields).toEqual(['csrfToken', 'principalType', 'shopId', 'scopes']); expect(dsvV1EtaStatuses).toEqual(['NOT_REQUIRED', 'PENDING', 'READY', 'FAILED', 'STALE']); expect(dsvV1ProofStatuses).toEqual(['NONE', 'AVAILABLE', 'REDACTED', 'EXPIRED']); expect(dsvV1EmittedProofStatuses).toEqual(['NONE', 'AVAILABLE', 'EXPIRED']); @@ -61,9 +57,12 @@ describe('G005 DSV v1 OpenAPI contract floor', () => { shopId: 'shop-1', }, 'csrf-1'); - for (const key of dsvV1SessionRequiredFields) { - expect(dto).toHaveProperty(key); - } + expect(dto).toEqual({ + csrfToken: 'csrf-1', + principalType: 'DSV_ADMIN', + scopes: ['dsv:session:read'], + shopId: 'shop-1', + }); }); test('freezes seller order summary required shape and ETA serialization floor', () => { @@ -80,19 +79,6 @@ describe('G005 DSV v1 OpenAPI contract floor', () => { sellerOrderKey: 'SO-001', }); - expect(dsvV1SellerOrderSummaryRequiredFields).toEqual([ - 'sellerOrderId', - 'sellerOrderKey', - 'deliveryStopId', - 'customerId', - 'destinationId', - 'assignmentStatus', - 'etaStatus', - 'eventSummary', - ]); - for (const key of dsvV1SellerOrderSummaryRequiredFields) { - expect(dto).toHaveProperty(key); - } expect(dto).toEqual({ assignmentStatus: 'ASSIGNED', customerId: 'customer-1', @@ -127,20 +113,6 @@ describe('G005 DSV v1 OpenAPI contract floor', () => { shippedBoxes: 3, }); - expect(dsvV1CustomerDeliveryRequiredFields).toEqual([ - 'sellerOrderId', - 'sellerOrderKey', - 'destinationId', - 'destinationDisplayName', - 'shippedBoxes', - 'deliveryStatus', - 'etaStatus', - 'eventSummary', - 'proofStatus', - ]); - for (const key of dsvV1CustomerDeliveryRequiredFields) { - expect(dto).toHaveProperty(key); - } expect(dto).toEqual({ deliveryStatus: 'DELIVERED', destinationDisplayName: 'Dock A', diff --git a/apps/delivery-api/tests/route-execution-ownership.test.ts b/apps/delivery-api/tests/route-execution-ownership.test.ts index 0aa572ab..1cea2262 100644 --- a/apps/delivery-api/tests/route-execution-ownership.test.ts +++ b/apps/delivery-api/tests/route-execution-ownership.test.ts @@ -1,65 +1,57 @@ import { describe, expect, test, vi } from 'vitest'; import { - assertRouteExecutionOwnership, + assertRouteDispatchOwnership, RouteExecutionConflictError } from '../src/modules/route-plans/route-execution-ownership.js'; describe('route execution ownership', () => { - test('locks unique stop ids in deterministic order before checking active overlap', async () => { + test('locks unique stop ids in deterministic order before rejecting an active dispatch overlap', async () => { const lockSql: string[] = []; + const lockedStopIds: unknown[] = []; const tx = { - $queryRaw: vi.fn((query: TemplateStringsArray | { strings: readonly string[] }) => { + $queryRaw: vi.fn((query: TemplateStringsArray | { strings: readonly string[]; values: readonly unknown[] }) => { const strings = 'strings' in query ? query.strings : query; - lockSql.push(Array.from(strings).join('?')); - return Promise.resolve([{ locked: true }]); - }), - routePlanStop: { - findFirst: vi.fn(() => Promise.resolve({ + const sql = Array.from(strings).join('?'); + if (sql.includes('SELECT DISTINCT')) return Promise.resolve([{ deliveryStopId: 'stop-a', - routePlanId: 'other-route' - })), - findMany: vi.fn(() => Promise.resolve([])) - } + orderId: 'order-a', + orderName: '#1001', + routePlanId: 'other-route', + routeName: 'Other route' + }]); + lockSql.push(sql); + if ('strings' in query) lockedStopIds.push(query.values[0]); + return Promise.resolve([{ locked: true }]); + }) }; - await expect(assertRouteExecutionOwnership(tx, { + await expect(assertRouteDispatchOwnership(tx, { deliveryStopIds: ['stop-b', 'stop-a', 'stop-b'], routePlanId: 'route-current', shopId: 'shop-id' })).rejects.toBeInstanceOf(RouteExecutionConflictError); - expect(tx.$queryRaw).toHaveBeenCalledTimes(2); + expect(tx.$queryRaw).toHaveBeenCalledTimes(3); expect(lockSql).toEqual([ 'SELECT TRUE AS "locked" FROM pg_advisory_xact_lock(710027, hashtext(?))', 'SELECT TRUE AS "locked" FROM pg_advisory_xact_lock(710027, hashtext(?))' ]); - expect(tx.routePlanStop.findFirst).toHaveBeenCalledWith({ - select: { deliveryStopId: true, routePlanId: true }, - where: { - deliveryStopId: { in: ['stop-a', 'stop-b'] }, - routePlanId: { not: 'route-current' }, - routePlan: { shopId: 'shop-id', status: 'IN_PROGRESS' } - } - }); + expect(lockedStopIds).toEqual(['stop-a', 'stop-b']); }); test('fails closed when the transaction lock cannot be acquired', async () => { const lockError = new Error('database lock unavailable'); const tx = { - $queryRaw: vi.fn(() => Promise.reject(lockError)), - routePlanStop: { - findFirst: vi.fn(() => Promise.resolve(null)), - findMany: vi.fn(() => Promise.resolve([])) - } + $queryRaw: vi.fn(() => Promise.reject(lockError)) }; - await expect(assertRouteExecutionOwnership(tx, { + await expect(assertRouteDispatchOwnership(tx, { deliveryStopIds: ['stop-a'], routePlanId: 'route-current', shopId: 'shop-id' })).rejects.toBe(lockError); - expect(tx.routePlanStop.findFirst).not.toHaveBeenCalled(); + expect(tx.$queryRaw).toHaveBeenCalledOnce(); }); }); diff --git a/apps/delivery-api/tests/route-group-copy.integration.test.ts b/apps/delivery-api/tests/route-group-copy.integration.test.ts index cfdfb689..9615ce04 100644 --- a/apps/delivery-api/tests/route-group-copy.integration.test.ts +++ b/apps/delivery-api/tests/route-group-copy.integration.test.ts @@ -1,7 +1,7 @@ import { PrismaClient } from '@prisma/client'; import { afterAll, beforeAll, describe, expect, test } from 'vitest'; -import { FakeDriverPushProvider } from '../src/modules/route-grouping/driver-push.provider.js'; +import { FakeDriverPushProvider } from './support/fake-driver-push-provider.js'; import { PrismaRouteGroupingService } from '../src/modules/route-grouping/route-grouping.service.js'; import { PrismaRoutePlanRepository } from '../src/modules/route-plans/route-plan.repository.js'; diff --git a/apps/delivery-api/tests/route-grouping-save.integration.test.ts b/apps/delivery-api/tests/route-grouping-save.integration.test.ts index 3819ac13..c7e0cf3e 100644 --- a/apps/delivery-api/tests/route-grouping-save.integration.test.ts +++ b/apps/delivery-api/tests/route-grouping-save.integration.test.ts @@ -13,7 +13,7 @@ import { } from '../src/modules/driver/driver-event.repository.js'; import { PrismaDriverAssignedRouteRepository } from '../src/modules/driver/driver-assigned-route.repository.js'; import { PrismaDriverRouteAccessRepository } from '../src/modules/driver/driver-route-access.repository.js'; -import { FakeDriverPushProvider } from '../src/modules/route-grouping/driver-push.provider.js'; +import { FakeDriverPushProvider } from './support/fake-driver-push-provider.js'; import { PrismaRouteGroupingService, rebindCurrentOrdersToRouteVersion } from '../src/modules/route-grouping/route-grouping.service.js'; import { PrismaRoutePlanRepository } from '../src/modules/route-plans/route-plan.repository.js'; import { PrismaOrderQueryRepository } from '../src/modules/shopify/order-query.repository.js'; diff --git a/apps/delivery-api/tests/route-grouping.service.test.ts b/apps/delivery-api/tests/route-grouping.service.test.ts index cb55e573..853a66a1 100644 --- a/apps/delivery-api/tests/route-grouping.service.test.ts +++ b/apps/delivery-api/tests/route-grouping.service.test.ts @@ -1,12 +1,11 @@ import { describe, expect, test, vi } from 'vitest'; import { buildApp } from '../src/app.js'; import { classifyCoordinateInPolygons } from '../src/modules/route-grouping/route-grouping.geometry.js'; -import { FakeDriverPushProvider } from '../src/modules/route-grouping/driver-push.provider.js'; +import { FakeDriverPushProvider } from './support/fake-driver-push-provider.js'; import { computeRouteShapeSignatureFromParts } from '../src/modules/route-plans/route-plan-geometry-cache.js'; import { assertDraftSchedulePlanDates, assertLockedRoutePlanSuccessorPolicy, - currentRouteBindingAuthorityState, deriveGroupingDisplayStatus, PrismaRouteGroupingService, newChildRouteName, @@ -1136,39 +1135,6 @@ describe('route grouping contracts', () => { })).rejects.toMatchObject({ code: 'ROUTE_GROUPING_STALE_WRITE' }); }); - test('classifies exact, legacy-unbound, and mismatched route binding authority', () => { - const assignments = (bindings: Array) => bindings.map((currentRouteVersionId, index) => ({ - order: { currentRouteVersionId }, - orderId: `order-${index + 1}` - })); - - expect(currentRouteBindingAuthorityState( - 'child-current', - ['order-1', 'order-2'], - assignments([null, null]) - )).toBe('LEGACY_UNBOUND'); - expect(currentRouteBindingAuthorityState( - 'child-current', - ['order-1', 'order-2'], - assignments(['child-current', 'child-current']) - )).toBe('EXACT'); - expect(currentRouteBindingAuthorityState( - 'child-current', - ['order-1', 'order-2'], - assignments(['child-current', null]) - )).toBe('MISMATCH'); - expect(currentRouteBindingAuthorityState( - 'child-current', - ['order-1', 'order-2'], - assignments(['child-foreign', null]) - )).toBe('MISMATCH'); - expect(currentRouteBindingAuthorityState( - 'child-current', - ['order-1', 'order-2'], - [...assignments([null, null]), { order: { currentRouteVersionId: 'child-current' }, orderId: 'order-extra' }] - )).toBe('MISMATCH'); - }); - test('rebinds current order ownership across every child-version replacement path', () => { const source = readFileSync(join(process.cwd(), 'src/modules/route-grouping/route-grouping.service.ts'), 'utf8'); const calls = source.match(/await replaceCurrentRouteGroupingChildVersion\(tx,/gu) ?? []; diff --git a/apps/delivery-api/tests/route-membership-mutation-authority.test.ts b/apps/delivery-api/tests/route-membership-mutation-authority.test.ts index c03e3368..42e9eb4e 100644 --- a/apps/delivery-api/tests/route-membership-mutation-authority.test.ts +++ b/apps/delivery-api/tests/route-membership-mutation-authority.test.ts @@ -145,18 +145,9 @@ describe('route membership mutation authority', () => { expect(assignmentAuthority).toContain("throw new RouteGroupingValidationError(['current route membership snapshot is malformed'])"); expect(assignmentAuthority).toContain("throw new RouteGroupingValidationError(['current route membership snapshot tuple does not match grouping authority'])"); expect(assignmentAuthority).toContain("throw new RouteGroupingValidationError(['current route membership snapshot does not match bound route authority'])"); - expect(assignmentAuthority).not.toContain('currentRouteBindingAuthorityState(child.id, snapshotOrderIds, group.orders)'); expect(assignmentAuthority).toContain("return resolveChildSnapshotAssignments(group, child, 'CURRENT')"); expect(assignmentAuthority).toContain("return resolveChildSnapshotAssignments(group, child, 'CURRENT_READ')"); expect(assignmentAuthority).not.toContain('.filter((assignment)'); - const bindingAuthority = source.slice( - source.indexOf('export function currentRouteBindingAuthorityState('), - source.indexOf('type OptimizedDraftRoute =') - ); - expect(bindingAuthority).toContain('order.currentRouteVersionId === childVersionId'); - expect(bindingAuthority).toContain('boundOrderIds.length === 0'); - expect(bindingAuthority).toContain('order.currentRouteVersionId === null'); - expect(bindingAuthority).toContain("return entirelyUnbound ? 'LEGACY_UNBOUND' : 'MISMATCH'"); // Draft partition discovery also reads membership; actual child mutations still require CURRENT authority. expect(source.match(/readCurrentChildAssignments\(/gu)).toHaveLength(5); const copy = source.slice(source.indexOf('async copyGrouping('), source.indexOf('async copyStandaloneRoutePlan(')); diff --git a/apps/delivery-api/tests/route-tracking.road-match.test.ts b/apps/delivery-api/tests/route-tracking.road-match.test.ts index 4364f3b5..723aee7f 100644 --- a/apps/delivery-api/tests/route-tracking.road-match.test.ts +++ b/apps/delivery-api/tests/route-tracking.road-match.test.ts @@ -4,7 +4,6 @@ import { OsrmRouteTrackingRoadMatchProvider, buildRouteTrackingRoadMatchCacheWrite, buildRouteTrackingRoadMatchedPath, - shouldRefreshRouteTrackingRoadMatchedPath, } from '../src/modules/route-tracking/route-tracking.road-match.js'; import type { RouteTrackingGeometryDocumentV1, @@ -1498,32 +1497,6 @@ describe('route tracking road matching', () => { expect(String((fetch.mock.calls as unknown as Array<[string]>)[0]![0])).not.toContain('-79.4'); }); - test('does not refresh from cache when the watermark already covers the latest input', () => { - const record = trackingRecord({ - roadMatchedLastInputOccurredAt: new Date('2026-07-21T00:02:00.000Z'), - roadMatchedSchemaVersion: 'route_tracking_road_match.v5', - roadMatchedSourcePointCount: 3, - roadMatchedWatermark: 'route_tracking_road_match.v1:korea:3:2:2026-07-21T00:02:00.000Z:abc', - sourcePointCount: 3, - }); - - expect(shouldRefreshRouteTrackingRoadMatchedPath(record)).toBe(false); - expect(shouldRefreshRouteTrackingRoadMatchedPath(trackingRecord({ - roadMatchedLastInputOccurredAt: new Date('2026-07-21T00:01:00.000Z'), - roadMatchedSchemaVersion: 'route_tracking_road_match.v5', - roadMatchedSourcePointCount: 2, - roadMatchedWatermark: 'route_tracking_road_match.v1:korea:2:2:2026-07-21T00:01:00.000Z:abc', - sourcePointCount: 3, - }))).toBe(true); - expect(shouldRefreshRouteTrackingRoadMatchedPath(trackingRecord({ - roadMatchedLastInputOccurredAt: new Date('2026-07-21T00:02:00.000Z'), - roadMatchedSchemaVersion: 'route_tracking_road_match.v4', - roadMatchedSourcePointCount: 3, - roadMatchedWatermark: 'route_tracking_road_match.v1:korea:3:2:2026-07-21T00:02:00.000Z:abc', - sourcePointCount: 3, - }))).toBe(true); - }); - test('serializes cached road-matched geometry into the snapshot contract', () => { const path = buildRouteTrackingRoadMatchedPath(trackingRecord({ roadMatchedCoverage: 'korea', diff --git a/apps/delivery-api/tests/shopify-webhook.dependencies.test.ts b/apps/delivery-api/tests/shopify-webhook.dependencies.test.ts index 7a1dbbd1..37fbae96 100644 --- a/apps/delivery-api/tests/shopify-webhook.dependencies.test.ts +++ b/apps/delivery-api/tests/shopify-webhook.dependencies.test.ts @@ -1,9 +1,9 @@ import type { PrismaClient } from '@prisma/client'; import { describe, expect, test } from 'vitest'; -import { loadShopifyWebhookBodyLimitBytes, loadShopifyWebhookDependencies } from '../src/modules/shopify/webhook.dependencies.js'; +import { loadShopifyWebhookBodyLimitBytes, loadShopifyWebhookRuntime } from '../src/modules/shopify/webhook.dependencies.js'; -describe('loadShopifyWebhookDependencies', () => { +describe('loadShopifyWebhookRuntime', () => { test('uses a bounded Shopify-compatible webhook body limit', () => { expect(loadShopifyWebhookBodyLimitBytes(undefined)).toBe(5 * 1024 * 1024); expect(loadShopifyWebhookBodyLimitBytes(String(6 * 1024 * 1024))).toBe(6 * 1024 * 1024); @@ -11,14 +11,14 @@ describe('loadShopifyWebhookDependencies', () => { expect(() => loadShopifyWebhookBodyLimitBytes(String(10 * 1024 * 1024 + 1))).toThrow('between 1048576 and 10485760'); }); test('stays disabled when no Shopify webhook secret material is configured', () => { - expect(loadShopifyWebhookDependencies({ env: {}, prisma: prisma() })).toBeUndefined(); + expect(loadShopifyWebhookRuntime({ env: {}, prisma: prisma() })).toBeUndefined(); }); test('uses the legacy Shopify API secret as a default webhook-only fallback', () => { - const dependencies = loadShopifyWebhookDependencies({ + const dependencies = loadShopifyWebhookRuntime({ env: { SHOPIFY_API_SECRET: 'shared-secret' }, prisma: prisma() - }); + })?.dependencies; expect(dependencies?.appCredentials).toEqual([ { appId: 'clever', clientSecret: 'shared-secret' } @@ -26,13 +26,13 @@ describe('loadShopifyWebhookDependencies', () => { }); test('loads app-specific webhook secrets from Shopify app credentials', () => { - const dependencies = loadShopifyWebhookDependencies({ + const dependencies = loadShopifyWebhookRuntime({ env: { SHOPIFY_DEV_API_KEY: 'dev-client-id', SHOPIFY_DEV_API_SECRET: 'dev-secret' }, prisma: prisma() - }); + })?.dependencies; expect(dependencies?.appCredentials).toEqual([ { appId: 'clever-route-dev', clientSecret: 'dev-secret' } @@ -40,13 +40,13 @@ describe('loadShopifyWebhookDependencies', () => { }); test('wires order webhook processing when token encryption is configured', () => { - const dependencies = loadShopifyWebhookDependencies({ + const dependencies = loadShopifyWebhookRuntime({ env: { SHOPIFY_API_SECRET: 'shared-secret', SHOPIFY_TOKEN_ENCRYPTION_KEY: 'base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' }, prisma: prisma() - }); + })?.dependencies; expect(dependencies?.orderWebhookProcessor?.canProcessTopic('orders/updated')).toBe(true); }); diff --git a/apps/delivery-api/tests/support/fake-driver-push-provider.ts b/apps/delivery-api/tests/support/fake-driver-push-provider.ts new file mode 100644 index 00000000..268976f3 --- /dev/null +++ b/apps/delivery-api/tests/support/fake-driver-push-provider.ts @@ -0,0 +1,18 @@ +import type { + DriverPushProvider, + DriverRoutePushMessage, + DriverRoutePushResult, +} from '../../src/modules/route-grouping/driver-push.provider.js'; + +export class FakeDriverPushProvider implements DriverPushProvider { + readonly providerName = 'fake'; + readonly sentMessages: DriverRoutePushMessage[] = []; + + sendRouteNotification(message: DriverRoutePushMessage): Promise { + this.sentMessages.push(message); + return Promise.resolve({ + providerMessageId: `fake:${message.routeGroupingId ?? 'standalone'}:${message.childVersion ?? message.publicationVersion ?? 'current'}:${message.routePlanId}`, + status: 'SENT', + }); + } +} diff --git a/apps/delivery-api/tests/woocommerce-order-sync.service.test.ts b/apps/delivery-api/tests/woocommerce-order-sync.service.test.ts index 026e392c..11cea713 100644 --- a/apps/delivery-api/tests/woocommerce-order-sync.service.test.ts +++ b/apps/delivery-api/tests/woocommerce-order-sync.service.test.ts @@ -5,7 +5,6 @@ import type { UpsertOrderWithDeliveryStopResult } from '../src/modules/shopify/o import { GeocodingService } from '../src/modules/geocoding/geocoding.service.js'; import type { GeocodingQuery } from '../src/modules/geocoding/geocoding.types.js'; import { - classifyWooCommerceSyncTier, prePersistGeocodingAddress, WooCommerceOrderSyncService } from '../src/modules/woocommerce/woocommerce-order-sync.service.js'; @@ -76,25 +75,6 @@ describe('WooCommerceOrderSyncService', () => { ); }); - test('classifies hot and cold fallback tiers from CLEVER normalized schedule dates', () => { - expect(classifyWooCommerceSyncTier({ - deliveryDate: '2026-06-05', - today: '2026-06-05' - })).toEqual({ reason: 'today_or_future_delivery', review: false, tier: 'hot' }); - expect(classifyWooCommerceSyncTier({ - deliveryDate: '2026-06-06', - today: '2026-06-05' - })).toEqual({ reason: 'today_or_future_delivery', review: false, tier: 'hot' }); - expect(classifyWooCommerceSyncTier({ - deliveryDate: '2026-06-04', - today: '2026-06-05' - })).toEqual({ reason: 'past_delivery', review: false, tier: 'cold' }); - expect(classifyWooCommerceSyncTier({ - deliveryDate: null, - today: '2026-06-05' - })).toEqual({ reason: 'missing_delivery_date', review: true, tier: 'cold' }); - }); - test('fetches and syncs a single WooCommerce order through the shared mapper pipeline', async () => { const wooOrder = order(11432); const client = { diff --git a/apps/route-ops-web/src/api.ts b/apps/route-ops-web/src/api.ts index aa0efcb9..0cba741b 100644 --- a/apps/route-ops-web/src/api.ts +++ b/apps/route-ops-web/src/api.ts @@ -1,9 +1,7 @@ import type { ApiEnvelope, BootstrapPayload, - BulkGeocodeOrdersResponse, DriversResponse, - GeocodeOrderResponse, GeocodeSettingsResponse, NotificationMutationResponse, NotificationsResponse, @@ -14,7 +12,6 @@ import type { OperationalHealthResponse, OrdersResponse, RouteDeleteResponse, - RouteOptimizationJobResponse, RouteGroupingDetailDto, RoutePlanDetailDto, RouteOperationalStateResponse, @@ -197,64 +194,6 @@ export async function patchOrderMetadata(input: { ); } -export async function patchOrderCoordinates(input: { - csrfToken: string; - latitude: number; - longitude: number; - orderId: string; - source: "manual" | "map_click"; -}): Promise { - return apiMutation( - `/admin/ui/app/api/orders/${encodeURIComponent(input.orderId)}/coordinates`, - "PATCH", - input.csrfToken, - { - latitude: input.latitude, - longitude: input.longitude, - source: input.source, - }, - ); -} - -export async function geocodeOrder(input: { - address?: Record; - csrfToken: string; - orderId: string; - save: boolean; -}): Promise { - return apiMutation( - `/admin/ui/app/api/orders/${encodeURIComponent(input.orderId)}/geocode`, - "POST", - input.csrfToken, - { - ...(input.address === undefined ? {} : { address: input.address }), - save: input.save, - }, - ); -} - -export async function bulkGeocodeOrders(input: { - csrfToken: string; - query: string; -}): Promise { - return apiMutation( - input.query === "" - ? "/admin/ui/app/api/orders/geocode" - : `/admin/ui/app/api/orders/geocode?${input.query}`, - "POST", - input.csrfToken, - {}, - ); -} - -export async function getBulkGeocodeJob( - jobId: string, -): Promise { - return apiGet( - `/admin/ui/app/api/orders/geocode/${encodeURIComponent(jobId)}`, - ); -} - export async function requestWooOrderSync(input: { csrfToken: string; pageSize?: number; @@ -277,10 +216,6 @@ export async function getWooOrderSyncRun( ); } -export async function getLatestWooOrderSync(): Promise { - return apiGet("/admin/ui/app/api/orders/sync/latest"); -} - export async function geocodeSettings(input: { csrfToken: string; defaultDepotAddress: string; @@ -363,19 +298,6 @@ export async function saveRouteGroupingPolygons(input: { ); } -export async function resolveRouteGroupingAssignments(input: { - assignments: Array<{ assignedDriverId: string; orderId: string }>; - csrfToken: string; - routeGroupId: string; -}): Promise<{ routeGroup: RouteGroupingDetailDto }> { - return apiMutation<{ routeGroup: RouteGroupingDetailDto }>( - `/admin/ui/app/api/route-groups/${encodeURIComponent(input.routeGroupId)}/assignments`, - "PATCH", - input.csrfToken, - { assignments: input.assignments }, - ); -} - export async function generateRouteGroupingChildRoutes(input: { confirmRisk?: boolean; csrfToken: string; @@ -398,24 +320,6 @@ export async function getRouteDetail( ); } -export async function createRoute(input: { - csrfToken: string; - depotAddress: string | null; - depotLatitude: number | null; - depotLongitude: number | null; - orderIds: string[]; - planDate: string; - routeName: string; - scope?: "history" | "planning"; -}): Promise<{ routePlan: { id: string } }> { - return apiMutation<{ routePlan: { id: string } }>( - appendRouteOpsScope("/admin/ui/app/api/routes", input.scope), - "POST", - input.csrfToken, - input, - ); -} - export async function deleteRoute( routePlanId: string, csrfToken: string, @@ -471,81 +375,6 @@ export async function publishRoute( ); } -export async function saveStopSequence( - routePlanId: string, - csrfToken: string, - stops: Array<{ deliveryStopId: string; sourceOrderId: string }>, -): Promise { - return apiMutation( - `/admin/ui/app/api/routes/${encodeURIComponent(routePlanId)}/stops`, - "PATCH", - csrfToken, - { stops }, - ); -} - -export async function saveRouteOptions( - routePlanId: string, - csrfToken: string, - routeEndMode: RoutePlanDetailDto["routePlan"]["routeEndMode"], -): Promise { - return apiMutation( - `/admin/ui/app/api/routes/${encodeURIComponent(routePlanId)}/options`, - "PATCH", - csrfToken, - { routeEndMode }, - ); -} - -export async function createRouteOptimizationJob( - routePlanId: string, - csrfToken: string, -): Promise { - return apiMutation( - `/admin/ui/app/api/routes/${encodeURIComponent(routePlanId)}/optimize-jobs`, - "POST", - csrfToken, - {}, - ); -} - -export async function getLatestRouteOptimizationJob( - routePlanId: string, -): Promise { - return apiGet( - `/admin/ui/app/api/routes/${encodeURIComponent(routePlanId)}/optimize-jobs/latest`, - ); -} - -export async function getRouteOptimizationJob( - routePlanId: string, - jobId: string, -): Promise { - return apiGet( - `/admin/ui/app/api/routes/${encodeURIComponent(routePlanId)}/optimize-jobs/${encodeURIComponent(jobId)}`, - ); -} - -export async function optimizeRoute( - routePlanId: string, - csrfToken: string, -): Promise { - return createRouteOptimizationJob(routePlanId, csrfToken); -} - -export async function assignDriver( - routePlanId: string, - csrfToken: string, - driverId: string | null, -): Promise { - return apiMutation( - `/admin/ui/app/api/routes/${encodeURIComponent(routePlanId)}/driver`, - "PATCH", - csrfToken, - { driverId }, - ); -} - export async function getDrivers(): Promise { return apiGet("/admin/ui/app/api/drivers"); } diff --git a/apps/route-ops-web/src/components/PageShell.tsx b/apps/route-ops-web/src/components/PageShell.tsx deleted file mode 100644 index 7ad5fd70..00000000 --- a/apps/route-ops-web/src/components/PageShell.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { ReactElement, ReactNode } from 'react'; - -export function PageShell({ actions, children, eyebrow, title }: { actions?: ReactNode; children: ReactNode; eyebrow?: string; title: string }): ReactElement { - return ( -
-
-
- {eyebrow === undefined ? null : {eyebrow}} -

{title}

-
- {actions === undefined ? null :
{actions}
} -
- {children} -
- ); -} diff --git a/apps/route-ops-web/src/components/primitives.tsx b/apps/route-ops-web/src/components/primitives.tsx index f2aaacc6..fa17356b 100644 --- a/apps/route-ops-web/src/components/primitives.tsx +++ b/apps/route-ops-web/src/components/primitives.tsx @@ -1,6 +1,5 @@ import type { ReactElement, ReactNode } from 'react'; -import type { CanonicalOrderDto } from '../types'; import type { OperationalPillModel, OperationalPillTone } from '../operationalStatus'; export function Kpi({ label, value }: { label: string; value: number }): ReactElement { @@ -32,12 +31,3 @@ export function OperationalPillGroup({ }): ReactElement { return
{pills.map((pill) => {pill.label})}
; } - -export function BlockerList({ blockers }: { blockers: string[] }): ReactElement { - return
Resolve before routing
    {blockers.map((blocker) =>
  • {blocker}
  • )}
; -} - -export function MiniOrderList({ empty, onSelect, orders }: { empty: string; onSelect?(order: CanonicalOrderDto): void; orders: CanonicalOrderDto[] }): ReactElement { - if (orders.length === 0) return

{empty}

; - return
    {orders.map((order) =>
  • {order.orderName}{order.blockerReasons.join(', ') || order.deliveryDate}{onSelect === undefined ? null : }
  • )}
; -} diff --git a/apps/route-ops-web/src/i18n.ts b/apps/route-ops-web/src/i18n.ts index cb89ede9..89fd1742 100644 --- a/apps/route-ops-web/src/i18n.ts +++ b/apps/route-ops-web/src/i18n.ts @@ -1329,9 +1329,6 @@ export const orderDetailLabelsByLocale = { } } as const; -export const orderDetailLabels = orderDetailLabelsByLocale['en-CA']; -export const orderBlockerLabels = orderDetailLabels.blockerReasons; - export const orderFieldLabelsByLocale = { 'en-CA': { ...orderDetailLabelsByLocale['en-CA'].diagnosticPaths, @@ -1365,8 +1362,6 @@ export const orderFieldLabelsByLocale = { } } as const; -export const orderFieldLabels = orderFieldLabelsByLocale['en-CA']; - export function getAppCopy(locale: string | null | undefined): (typeof appCopy)[AppLocale] { return appCopy[resolveLocale(locale)]; } @@ -1402,7 +1397,3 @@ export function getOrderDetailLabels(locale: string | null | undefined): (typeof export function getOrderFieldLabels(locale: string | null | undefined): (typeof orderFieldLabelsByLocale)[AppLocale] { return orderFieldLabelsByLocale[resolveLocale(locale)]; } - -export function getOrderBlockerLabels(locale: string | null | undefined): (typeof orderDetailLabelsByLocale)[AppLocale]['blockerReasons'] { - return getOrderDetailLabels(locale).blockerReasons; -} diff --git a/apps/route-ops-web/src/maps/provider.ts b/apps/route-ops-web/src/maps/provider.ts index 4d18d0fa..61bf651a 100644 --- a/apps/route-ops-web/src/maps/provider.ts +++ b/apps/route-ops-web/src/maps/provider.ts @@ -1,5 +1,4 @@ -import type { BootstrapPayload, MapProviderMode, MapProviderStatus } from '../types'; -import { settingsCopy, resolveLocale } from '../i18n'; +import type { MapProviderStatus } from '../types'; export type MapReadiness = 'interactive_map' | 'provider_not_configured'; @@ -7,74 +6,3 @@ export function mapReadiness(input: { mapStatus: MapProviderStatus }): MapReadin if (input.mapStatus === 'not_configured') return 'provider_not_configured'; return 'interactive_map'; } - -export function providerModeLabel(mode: MapProviderMode, locale: string | null | undefined = 'en-CA'): string { - if (resolveLocale(locale) === 'ko-KR') { - const t = settingsCopy['ko-KR']; - if (mode === 'self_hosted') return t.selfHosted; - if (mode === 'public_allowlisted') return t.publicAllowlisted; - return t.notConfigured; - } - if (mode === 'self_hosted') return 'Self-hosted map'; - if (mode === 'public_allowlisted') return 'Public map provider allowlisted'; - return 'Map provider not configured'; -} - -export function providerStatusLabel(mapConfig: BootstrapPayload['mapConfig'], locale: string | null | undefined = 'en-CA'): string { - if (mapConfig.status === 'not_configured') return mapConfig.disabledReason ?? 'not_configured'; - return providerModeLabel(mapConfig.providerMode, locale); -} - -export function extractStyleEndpointUrls(manifest: unknown): string[] { - if (manifest === null || typeof manifest !== 'object' || Array.isArray(manifest)) return []; - const record = manifest as Record; - const endpoints: string[] = []; - collectStringEndpoint(record.sprite, endpoints); - collectStringEndpoint(record.glyphs, endpoints); - const sources = record.sources; - if (sources !== null && typeof sources === 'object' && !Array.isArray(sources)) { - for (const source of Object.values(sources as Record)) { - if (source === null || typeof source !== 'object' || Array.isArray(source)) continue; - const sourceRecord = source as Record; - collectStringEndpoint(sourceRecord.url, endpoints); - const tiles = sourceRecord.tiles; - if (Array.isArray(tiles)) { - for (const tile of tiles) collectStringEndpoint(tile, endpoints); - } - } - } - return [...new Set(endpoints)]; -} - -export function auditStyleEndpoints(endpoints: string[], allowedHosts: readonly string[]): { - externalHosts: string[]; - isAllowed: boolean; - providerMode: 'public_allowlisted' | 'self_hosted'; -} { - const externalHosts = [...new Set(endpoints.map(endpointHost).filter((host): host is string => host !== null))].sort(); - return { - externalHosts, - isAllowed: externalHosts.every((host) => allowedHosts.includes(host)), - providerMode: externalHosts.length === 0 ? 'self_hosted' : 'public_allowlisted' - }; -} - -function collectStringEndpoint(value: unknown, endpoints: string[]): void { - if (typeof value !== 'string' || value.trim() === '') return; - const endpoint = value.trim(); - endpoints.push(endpoint); - if (endpoint.startsWith('pmtiles://')) { - const nested = endpoint.slice('pmtiles://'.length); - if (nested !== '') endpoints.push(nested); - } -} - -function endpointHost(endpoint: string): string | null { - const normalized = endpoint.startsWith('pmtiles://') ? endpoint.slice('pmtiles://'.length) : endpoint; - if (!/^https?:\/\//iu.test(normalized)) return null; - try { - return new URL(normalized).host.toLowerCase(); - } catch { - return null; - } -} diff --git a/apps/route-ops-web/src/pages/OrdersPage.tsx b/apps/route-ops-web/src/pages/OrdersPage.tsx index f1476e51..a6c6f8bb 100644 --- a/apps/route-ops-web/src/pages/OrdersPage.tsx +++ b/apps/route-ops-web/src/pages/OrdersPage.tsx @@ -26,7 +26,6 @@ import { formatOrderItemName, formatOrderItemOptions, getOrderItemDisplayKey, - getOrderItemSemanticDisplayKey, getOrderItems, } from "../orderItems"; import { @@ -810,59 +809,6 @@ export function RoutePlanPanel(input: { ); } -export type OrderInventoryRow = { - orderRefs: string[]; - product: string; - quantity: number; -}; - -export function buildOrderInventoryRows( - orders: CanonicalOrderDto[], -): OrderInventoryRow[] { - const rows = new Map< - string, - { orderQuantities: Map; product: string; quantity: number } - >(); - for (const order of orders) { - const orderRef = formatInventoryOrderRef(order); - for (const item of getOrderItems(order.items)) { - const key = getOrderItemSemanticDisplayKey(item); - const options = formatOrderItemOptions(item); - const product = - options.length === 0 - ? formatOrderItemName(item) - : `${formatOrderItemName(item)} (${options})`; - const row = rows.get(key) ?? { - orderQuantities: new Map(), - product, - quantity: 0, - }; - row.quantity += item.quantity; - row.orderQuantities.set( - orderRef, - (row.orderQuantities.get(orderRef) ?? 0) + item.quantity, - ); - rows.set(key, row); - } - } - return [...rows.values()] - .map((row) => ({ - orderRefs: [...row.orderQuantities.entries()] - .sort(([first], [second]) => - first.localeCompare(second, undefined, { numeric: true }), - ) - .map(([orderRef, quantity]) => `${quantity}x ${orderRef}`), - product: row.product, - quantity: row.quantity, - })) - .sort((first, second) => first.product.localeCompare(second.product)); -} - -function formatInventoryOrderRef(order: CanonicalOrderDto): string { - const source = order.sourceOrderNumber ?? order.orderName; - return source.startsWith('#') ? source : `#${source}`; -} - function FilterBar({ filterOptions, filters, @@ -1878,16 +1824,6 @@ function statusMeaningForOrder( return t.metadataReview; } -export function formatOrderReceivedLabel( - order: CanonicalOrderDto, - locale: string | null | undefined = "en-CA", -): string { - const label = formatOrderReceivedLabelParts(order, locale); - return label.updated === null - ? label.created - : `${label.created}\n${label.updated}`; -} - export function formatOrderReceivedLabelParts( order: CanonicalOrderDto, locale: string | null | undefined = "en-CA", @@ -1975,88 +1911,6 @@ function formatRouteLabel( return humanizeToken(order.planningStatus, locale); } -export function getRouteRepairPrompt( - order: CanonicalOrderDto, - locale: string | null | undefined = "en-CA", -): { - canGeocode: boolean; - routeDetail: string; - statusDetail: string | null; - statusLabel: string; -} { - const t = getOrdersCopy(locale); - if (order.routePlanId !== null || order.planningStatus !== "UNPLANNED") { - return { - canGeocode: false, - routeDetail: t.statusLabels.alreadyPlanned, - statusDetail: - order.routePlanName ?? humanizeToken(order.planningStatus, locale), - statusLabel: t.statusLabels.planned, - }; - } - if (isRoutePlanEligible(order)) { - return { - canGeocode: false, - routeDetail: t.statusLabels.routeEligible, - statusDetail: null, - statusLabel: t.statusLabels.ready, - }; - } - if (isAddressReviewRequired(order)) { - return { - canGeocode: false, - routeDetail: t.statusLabels.addressReview, - statusDetail: t.statusDetails.verifyAddress, - statusLabel: t.statusLabels.addressReview, - }; - } - if (isDeliveryDateReviewRequired(order)) { - return { - canGeocode: false, - routeDetail: t.statusLabels.deliveryDateReview, - statusDetail: t.statusDetails.verifyDeliveryDate, - statusLabel: t.statusLabels.deliveryDateReview, - }; - } - if (order.deliveryDate === null) { - return { - canGeocode: false, - routeDetail: t.statusLabels.missingDeliveryDate, - statusDetail: t.statusDetails.enterDeliveryDate, - statusLabel: t.statusLabels.missingDeliveryDate, - }; - } - if (order.metadataResolved !== true) { - return { - canGeocode: false, - routeDetail: t.statusLabels.needsMetadata, - statusDetail: geocodeDetail(order, locale), - statusLabel: t.statusLabels.metadataReview, - }; - } - if (!hasResolvedCoordinates(order)) { - const canGeocode = hasGeocodableAddress(order); - return { - canGeocode, - routeDetail: canGeocode - ? t.statusLabels.needCoordinates - : t.statusLabels.needAddress, - statusDetail: canGeocode - ? t.statusDetails.useBulkGeocode - : t.statusDetails.enterAddressOrCoordinates, - statusLabel: canGeocode - ? t.statusLabels.needCoordinates - : t.statusLabels.missingAddress, - }; - } - return { - canGeocode: false, - routeDetail: t.statusLabels.notRouteEligible, - statusDetail: t.statusDetails.reviewRouteConstraints, - statusLabel: t.statusLabels.notRouteEligible, - }; -} - function geocodeDetail( order: CanonicalOrderDto, locale: string | null | undefined = "en-CA", @@ -3013,54 +2867,10 @@ function formatAddressSummary( }; } -function formatCoordinateSummary( - order: CanonicalOrderDto, - locale: string | null | undefined = "en-CA", -): { - primary: string; - secondary: string | null; -} { - const t = getOrdersCopy(locale); - if (hasResolvedCoordinates(order)) { - return { - primary: `${order.coordinates.latitude?.toFixed(6)}, ${order.coordinates.longitude?.toFixed(6)}`, - secondary: t.coordinatesReady, - }; - } - if (isAddressReviewRequired(order)) { - return { - primary: t.statusLabels.addressReview, - secondary: t.statusDetails.verifyAddress, - }; - } - if (hasGeocodableAddress(order)) { - return { - primary: t.coordinatesNeeded, - secondary: t.useBulkGeocodeFromList, - }; - } - return { - primary: t.addressRequired, - secondary: t.enterAddressBeforeGeocoding, - }; -} - function sanitizeId(value: string): string { return value.replace(/[^a-zA-Z0-9_-]+/gu, "-"); } -export function filterOrdersByRoutePlan( - orders: CanonicalOrderDto[], - routePlanId: string | null, -): CanonicalOrderDto[] | null { - if (routePlanId === null) return null; - return orders.filter((order) => order.routePlanId === routePlanId); -} - -export function buildRouteDetailPath(routePlanId: string): string { - return `/admin/ui/app/routes/${encodeURIComponent(routePlanId)}`; -} - function orderSelectedOrdersByDraft( selectedOrders: CanonicalOrderDto[], selectedOrderIds: ReadonlySet, @@ -3208,29 +3018,6 @@ export function buildVisibleSelectedOrderIds( .map((order) => order.orderId); } -export function buildRouteDraftSelection( - orders: CanonicalOrderDto[], - requestedSelectedOrderIds: ReadonlySet, - locale: string | null | undefined = "en-CA", -): { - deliveryDate: string | null; - orderIds: string[]; - routeType: OrderRouteTypeFilter | null; - warning: string | null; -} { - const ordersById = new Map(orders.map((order) => [order.orderId, order])); - const requestedOrders = [...requestedSelectedOrderIds] - .map((orderId) => ordersById.get(orderId)) - .filter((order): order is CanonicalOrderDto => order !== undefined); - - return { - deliveryDate: getRouteDraftSingleDeliveryDate(requestedOrders), - orderIds: requestedOrders.map((order) => order.orderId), - routeType: getRouteDraftSingleType(requestedOrders), - warning: null, - }; -} - export function getRouteDraftCreateReasons( selectedOrders: CanonicalOrderDto[], locale: string | null | undefined = "en-CA", @@ -3255,13 +3042,6 @@ export function getRouteDraftCreateReasons( return reasons; } -export function getRouteDraftFirstCreateReason( - selectedOrders: CanonicalOrderDto[], - locale: string | null | undefined = "en-CA", -): string | null { - return getRouteDraftCreateReasons(selectedOrders, locale)[0] ?? null; -} - function getRouteDraftSingleDeliveryDate( orders: CanonicalOrderDto[], ): string | null { @@ -3314,15 +3094,3 @@ function formatRoutePlanningType( ): string { return humanizeToken(value, locale); } - -function isRoutePlanEligible(order: CanonicalOrderDto): boolean { - if (order.routePlanId !== null || order.planningStatus !== "UNPLANNED") { - return false; - } - return ( - order.routeEligible === true || - (order.routeEligible !== false && - order.blockerReasons.length === 0 && - order.planningStatus === "UNPLANNED") - ); -} diff --git a/apps/route-ops-web/src/routeScopeConfig.ts b/apps/route-ops-web/src/routeScopeConfig.ts index 041a90e9..c9156d55 100644 --- a/apps/route-ops-web/src/routeScopeConfig.ts +++ b/apps/route-ops-web/src/routeScopeConfig.ts @@ -1,4 +1,4 @@ -import type { RouteScopeConfigDto, RouteScopeValueDto } from './types'; +import type { RouteScopeConfigDto } from './types'; export function defaultRouteScopeConfig(): RouteScopeConfigDto { return { @@ -62,37 +62,3 @@ export function defaultRouteScopeConfig(): RouteScopeConfigDto { version: 1 }; } - -export function normalizeRouteScopeConfig(config: RouteScopeConfigDto | null | undefined): RouteScopeConfigDto { - if (config === null || config === undefined) return defaultRouteScopeConfig(); - return { - deliverySessions: normalizeValues(config.deliverySessions, defaultRouteScopeConfig().deliverySessions), - serviceTypes: normalizeValues(config.serviceTypes, defaultRouteScopeConfig().serviceTypes), - timeWindow: { - endExample: config.timeWindow?.endExample ?? '21:00', - helpText: config.timeWindow?.helpText ?? 'Use 24-hour HH:mm format.', - startExample: config.timeWindow?.startExample ?? '17:00' - }, - version: 1 - }; -} - -export function activeRouteScopeValues(values: RouteScopeValueDto[]): RouteScopeValueDto[] { - return values.filter((value) => value.enabled); -} - -export function routeScopeValueSummary(values: RouteScopeValueDto[]): string { - return activeRouteScopeValues(values) - .map((item) => item.value) - .join(', '); -} - -function normalizeValues(values: RouteScopeValueDto[] | undefined, defaults: RouteScopeValueDto[]): RouteScopeValueDto[] { - const byValue = new Map((values ?? []).map((item) => [item.value, item])); - const output = defaults.map((fallback) => ({ ...fallback, ...byValue.get(fallback.value), builtIn: true, enabled: true })); - for (const item of values ?? []) { - if (output.some((existing) => existing.value === item.value)) continue; - output.push({ ...item, builtIn: false }); - } - return output; -} diff --git a/apps/route-ops-web/src/state.ts b/apps/route-ops-web/src/state.ts index 61012b7e..fca5c699 100644 --- a/apps/route-ops-web/src/state.ts +++ b/apps/route-ops-web/src/state.ts @@ -1,8 +1,6 @@ import type { BootstrapPayload, CanonicalOrderDto, - MapProviderStatus, - RoutePlanDetailDto, RouteStopDto, StoreSettingsDto } from './types'; @@ -48,50 +46,6 @@ export type OrderWorksetContext = { today?: string; }; -export type OrderWorksetUnavailableReason = - | 'already_planned' - | 'address_review' - | 'completed_or_cancelled' - | 'delivery_date_review' - | 'different_delivery_date' - | 'different_route_scope' - | 'missing_address' - | 'missing_coordinates' - | 'missing_delivery_date' - | 'missing_route_scope' - | 'needs_review'; - -export type OrderWorksetReason = { - code: OrderWorksetUnavailableReason; - label: string; -}; - -export type OrderWorksetSummary = { - reasonLabels: string[]; - reasonsByCode: Record; - selectableCount: number; - selectableOrderIds: string[]; - selectedCount: number; - unavailableCount: number; -}; - -export const ORDER_WORKSET_REASON_LABELS: Record< - OrderWorksetUnavailableReason, - string -> = { - already_planned: 'Already planned', - address_review: 'Address Review', - completed_or_cancelled: 'Completed/cancelled', - delivery_date_review: 'Delivery date review', - different_delivery_date: 'Different delivery date', - different_route_scope: 'Different delivery session', - missing_address: 'Missing address', - missing_coordinates: 'Missing coordinates', - missing_delivery_date: 'Missing delivery date', - missing_route_scope: 'Missing route scope', - needs_review: 'Other metadata review' -}; - export function createDefaultOrderFilters(): OrderFilterState { return { deliveryArea: '', @@ -140,10 +94,6 @@ export function buildOrderFetchQuery(filters: OrderFilters): string { return buildOrderQuery(serverFilters); } -export function buildAreaOptionSourceFilters(filters: OrderFilters): OrderFilters { - return { ...filters, deliveryArea: '' }; -} - export function deriveAreaFilterOptions(orders: CanonicalOrderDto[]): string[] { return Array.from( new Set( @@ -644,125 +594,6 @@ export function matchesOrderTab( return !isPlannedOrder(order) && isRoutePlanEligibleForWorkset(order); } -export function getOrderWorksetUnavailableReasons( - order: CanonicalOrderDto, - context: OrderWorksetContext = {} -): OrderWorksetReason[] { - const reasons = new Set(); - if (isCompletedOrCancelledOrder(order)) reasons.add('completed_or_cancelled'); - if (isPlannedOrder(order)) reasons.add('already_planned'); - if (isDeliveryDateReviewRequired(order)) { - reasons.add('delivery_date_review'); - } else if (order.deliveryDate === null) { - reasons.add('missing_delivery_date'); - } - if (!hasRouteScope(order)) reasons.add('missing_route_scope'); - if (!hasResolvedCoordinates(order)) { - reasons.add(readCoordinateUnavailableReason(order)); - } - if (hasUnrepresentedNeedsReviewReason(order, reasons)) { - reasons.add('needs_review'); - } - if ( - context.routeDate !== undefined && - context.routeDate !== null && - order.deliveryDate !== null && - order.deliveryDate !== context.routeDate - ) { - reasons.add('different_delivery_date'); - } - const routeScopeKey = getRouteScopeKey(order); - if ( - context.routeScopeKey !== undefined && - context.routeScopeKey !== null && - routeScopeKey !== null && - routeScopeKey !== context.routeScopeKey - ) { - reasons.add('different_route_scope'); - } - return [...reasons].map((code) => ({ - code, - label: ORDER_WORKSET_REASON_LABELS[code] - })); -} - -export function formatOrderWorksetUnavailableReasons( - reasons: OrderWorksetReason[], - locale: string | null | undefined = 'en-CA' -): OrderWorksetReason[] { - const labels = getStateCopy(locale).worksetReasons; - return reasons.map((reason) => ({ ...reason, label: labels[reason.code] })); -} - -export function isOrderWorksetEligible( - order: CanonicalOrderDto, - context: OrderWorksetContext = {} -): boolean { - return ( - getOrderWorksetUnavailableReasons(order, context).length === 0 && - isRoutePlanEligibleForWorkset(order) - ); -} - -export function summarizeOrderWorkset( - orders: CanonicalOrderDto[], - selectedOrderIds: ReadonlySet, - context: OrderWorksetContext = {}, - locale: string | null | undefined = 'en-CA' -): OrderWorksetSummary { - const labels = getStateCopy(locale).worksetReasons; - const reasonsByCode = Object.fromEntries( - Object.keys(ORDER_WORKSET_REASON_LABELS).map((code) => [code, 0]) - ) as Record; - const selectableOrderIds: string[] = []; - for (const order of orders) { - const reasons = getOrderWorksetUnavailableReasons(order, context); - if (reasons.length === 0 && isRoutePlanEligibleForWorkset(order)) { - selectableOrderIds.push(order.orderId); - continue; - } - for (const reason of reasons) reasonsByCode[reason.code] += 1; - } - const reasonLabels = ( - Object.entries(reasonsByCode) as Array< - [OrderWorksetUnavailableReason, number] - > - ) - .filter(([, count]) => count > 0) - .map(([code, count]) => `${labels[code]} ${count}`); - return { - reasonLabels, - reasonsByCode, - selectableCount: selectableOrderIds.length, - selectableOrderIds, - selectedCount: selectableOrderIds.filter((orderId) => - selectedOrderIds.has(orderId) - ).length, - unavailableCount: orders.length - selectableOrderIds.length - }; -} - -export function summarizeSelection( - orders: CanonicalOrderDto[], - selectedOrderIds: ReadonlySet -): { - blockers: string[]; - readySelected: CanonicalOrderDto[]; -} { - const selected = orders.filter((order) => selectedOrderIds.has(order.orderId)); - const blockers = selected.flatMap((order) => - order.blockerReasons.map((reason) => `${order.orderName}: ${reason}`) - ); - return { - blockers, - readySelected: selected.filter( - (order) => - order.blockerReasons.length === 0 && - order.planningStatus === 'UNPLANNED' - ) - }; -} - function isRoutePlanEligibleForWorkset(order: CanonicalOrderDto): boolean { if (isPlannedOrder(order)) return false; return ( @@ -792,51 +623,6 @@ function isNeedsReviewOrder(order: CanonicalOrderDto): boolean { ); } -const BLOCKER_WORKSET_REASON_MAP: Partial< - Record -> = { - ambiguous_delivery_day: 'delivery_date_review', - delivery_date_weekday_mismatch: 'delivery_date_review', - delivery_date_weekday_unverified: 'delivery_date_review', - delivery_day_unparsed: 'delivery_date_review', - missing_address: 'missing_address', - missing_coordinates: 'missing_coordinates', - missing_delivery_date: 'missing_delivery_date', - missing_route_scope: 'missing_route_scope' -}; - -function hasUnrepresentedNeedsReviewReason( - order: CanonicalOrderDto, - reasons: ReadonlySet -): boolean { - const hasSpecificMetadataReason = - reasons.has('address_review') || - reasons.has('delivery_date_review') || - reasons.has('missing_address') || - reasons.has('missing_coordinates') || - reasons.has('missing_delivery_date') || - reasons.has('missing_route_scope'); - const hasUnrepresentedBlocker = order.blockerReasons.some((blocker) => { - if (blocker === 'missing_coordinates' && reasons.has('address_review')) { - return false; - } - if (blocker === 'missing_delivery_date' && reasons.has('delivery_date_review')) { - return false; - } - const mappedReason = BLOCKER_WORKSET_REASON_MAP[blocker]; - return mappedReason === undefined || !reasons.has(mappedReason); - }); - if (hasUnrepresentedBlocker) return true; - if ( - order.health === 'needs_review' || - order.metadataResolved === false || - order.routeEligible === false - ) { - return !hasSpecificMetadataReason; - } - return false; -} - function isCompletedOrCancelledOrder(order: CanonicalOrderDto): boolean { return ( order.deliveryStatus === 'completed' || @@ -849,17 +635,6 @@ function hasRouteScope(order: CanonicalOrderDto): boolean { return isPresent(order.serviceType) && isPresent(order.deliverySession); } -function getRouteScopeKey(order: CanonicalOrderDto): string | null { - if (!hasRouteScope(order) || order.deliveryDate === null) return null; - return [ - order.deliveryDate, - order.serviceType, - order.deliverySession, - order.timeWindowStart ?? '', - order.timeWindowEnd ?? '' - ].join('|'); -} - function hasResolvedCoordinates(order: CanonicalOrderDto): boolean { return ( typeof order.coordinates.latitude === 'number' && @@ -869,13 +644,6 @@ function hasResolvedCoordinates(order: CanonicalOrderDto): boolean { ); } -function readCoordinateUnavailableReason( - order: CanonicalOrderDto -): OrderWorksetUnavailableReason { - if (!hasGeocodableAddress(order)) return 'missing_address'; - return isAddressReviewRequired(order) ? 'address_review' : 'missing_coordinates'; -} - export function isAddressReviewRequired(order: CanonicalOrderDto): boolean { if (hasResolvedCoordinates(order)) return false; if (!hasGeocodableAddress(order)) return false; @@ -942,10 +710,6 @@ export function moveStop(stops: RouteStopDto[], deliveryStopId: string, directio } -export function moveStopBefore(stops: RouteStopDto[], draggedStopId: string, targetStopId: string): RouteStopDto[] { - return moveStopToDropPosition(stops, draggedStopId, targetStopId, 'before'); -} - export type StopDropPosition = 'before' | 'after'; export function moveStopToDropPosition( @@ -986,46 +750,6 @@ export function resequenceStops(stops: RouteStopDto[]): RouteStopDto[] { return stops.map((stop, index) => ({ ...stop, sequence: index + 1 })); } -export function deriveRouteStats(detail: RoutePlanDetailDto | null): { - attempted: number; - completed: number; - missingCoordinates: number; - stops: number; -} { - if (detail === null) return { attempted: 0, completed: 0, missingCoordinates: 0, stops: 0 }; - return detail.stops.reduce( - (stats, stop) => ({ - attempted: stats.attempted + (stop.status.toUpperCase() === 'ATTEMPTED' ? 1 : 0), - completed: stats.completed + (stop.status.toUpperCase() === 'COMPLETED' ? 1 : 0), - missingCoordinates: stats.missingCoordinates + (stop.coordinates.latitude === null || stop.coordinates.longitude === null ? 1 : 0), - stops: stats.stops + 1 - }), - { attempted: 0, completed: 0, missingCoordinates: 0, stops: 0 } - ); -} - -export function geometryLabel(detail: RoutePlanDetailDto | null, routerStatus: MapProviderStatus, locale: string | null | undefined = 'en-CA'): string { - const t = getStateCopy(locale).geometry; - if (detail === null) return t.noRouteSelected; - if (detail.routeGeometry !== null) return t.roadGeometry; - if (countRoutableRoutePoints(detail) < 2) return t.noCoordinates; - if (routerStatus === 'not_configured') return t.routerNotConfigured; - return t.roadGeometryUnavailable; -} - -function countRoutableRoutePoints(detail: RoutePlanDetailDto): number { - const depot = detail.routePlan.depot; - const points = [ - depot === null ? null : { latitude: depot.latitude, longitude: depot.longitude }, - ...detail.stops.map((stop) => stop.coordinates) - ]; - return points.filter((point): point is { latitude: number; longitude: number } => - point !== null && - Number.isFinite(point.latitude) && - Number.isFinite(point.longitude) - ).length; -} - export function hideSetupActions(bootstrap: BootstrapPayload): boolean { return bootstrap.mode === 'plugin'; } diff --git a/apps/route-ops-web/src/types.ts b/apps/route-ops-web/src/types.ts index 125e8224..9afe4e42 100644 --- a/apps/route-ops-web/src/types.ts +++ b/apps/route-ops-web/src/types.ts @@ -463,45 +463,6 @@ export type RouteSaveResponse = RoutePlanDetailDto & { saveOperations?: RouteSaveOperationDto[]; }; -export type RouteOptimizationJobStatus = - | "APPLIED" - | "CANCELLED" - | "FAILED" - | "QUEUED" - | "RUNNING" - | "TIMEOUT"; - -export type RouteOptimizationJobStep = - | "APPLYING_RESULT" - | "CALLING_ENGINE" - | "COMPLETED" - | "QUEUED"; - -export type RouteOptimizationJobDto = { - appliedAt: string | null; - createdAt: string; - createdBy: string | null; - currentStep: RouteOptimizationJobStep; - elapsedMs: number | null; - engineResultSequence: unknown; - errorCode: string | null; - errorMessage: string | null; - finishedAt: string | null; - id: string; - invalidatedReason: string | null; - routePlanId: string; - shopId: string; - startedAt: string | null; - status: RouteOptimizationJobStatus; - timeoutBudgetMs: number; - traceId: string; - updatedAt: string; -}; - -export type RouteOptimizationJobResponse = { - job: RouteOptimizationJobDto | null; -}; - export type DriverDto = { appLinked: boolean; authStatus: string; @@ -575,44 +536,6 @@ export type RouteScopeConfigDto = { version: 1; }; -export type GeocodeOrderResponse = { - geocode: { - cached: boolean; - ok: true; - result: { - addressLabel: string; - latitude: number; - longitude: number; - provider: string; - providerPlaceId: string | null; - rawLabel?: string | null; - }; - }; - order?: CanonicalOrderDto; -}; - -export type BulkGeocodeJobDto = { - completedAt: string | null; - counts: { - alreadyHasCoordinates: number; - attempted: number; - failed: number; - matched: number; - noAddress: number; - succeeded: number; - }; - createdAt: string; - error: string | null; - jobId: string; - results: Array>; - status: "accepted" | "completed" | "failed" | "running"; - updatedAt: string; -}; - -export type BulkGeocodeOrdersResponse = { - geocode: BulkGeocodeJobDto; -}; - export type WooSyncRunDto = { acceptedAt: string; completedAt: string | null; diff --git a/apps/route-ops-web/tests/drivers-routes.test.tsx b/apps/route-ops-web/tests/drivers-routes.test.tsx index 82f3be6b..b6b5b45b 100644 --- a/apps/route-ops-web/tests/drivers-routes.test.tsx +++ b/apps/route-ops-web/tests/drivers-routes.test.tsx @@ -5,14 +5,11 @@ import { afterEach, describe, expect, test, vi } from 'vitest'; import { acknowledgeNotification, ApiError, - createRouteOptimizationJob, deleteDriver, generateRouteGroupingChildRoutes, - getLatestRouteOptimizationJob, getNotifications, getOperationalHealth, getRouteOperationalState, - getRouteOptimizationJob, openNotificationChangeStream, markNotificationRead, publishRoute, @@ -52,7 +49,6 @@ import type { BootstrapPayload, DriverDto, RouteGroupingSummaryDto, - RouteOptimizationJobDto, RoutePlanDetailDto, RoutePlanSummaryDto, StoreSettingsDto, @@ -1052,53 +1048,6 @@ describe('Route Ops driver invite and route assignment UI helpers', () => { expect(html).not.toContain('route-optimize-button'); }); - test('route optimization job API helpers use the protected Route Ops endpoints', async () => { - const fetchMock = vi.fn((url: string) => - Promise.resolve( - new Response( - JSON.stringify({ data: { job: routeOptimizationJobFixture({ id: url.includes('latest') ? 'latest-job-id' : 'job-id' }) }, error: null }), - { - headers: { 'Content-Type': 'application/json' }, - status: 200, - }, - ), - ), - ); - vi.stubGlobal('fetch', fetchMock); - vi.stubGlobal('window', { location: { search: '?shopDomain=tenant-a.example.test' } }); - - await createRouteOptimizationJob('route/id', 'csrf-token'); - await getLatestRouteOptimizationJob('route/id'); - await getRouteOptimizationJob('route/id', 'job/id'); - - expect(fetchMock).toHaveBeenNthCalledWith( - 1, - '/admin/ui/app/api/routes/route%2Fid/optimize-jobs?shopDomain=tenant-a.example.test', - expect.objectContaining({ - body: '{}', - credentials: 'same-origin', - headers: expect.objectContaining({ 'X-CSRF-Token': 'csrf-token' }), - method: 'POST', - }), - ); - expect(fetchMock).toHaveBeenNthCalledWith( - 2, - '/admin/ui/app/api/routes/route%2Fid/optimize-jobs/latest?shopDomain=tenant-a.example.test', - expect.objectContaining({ - credentials: 'same-origin', - headers: { Accept: 'application/json' }, - }), - ); - expect(fetchMock).toHaveBeenNthCalledWith( - 3, - '/admin/ui/app/api/routes/route%2Fid/optimize-jobs/job%2Fid?shopDomain=tenant-a.example.test', - expect.objectContaining({ - credentials: 'same-origin', - headers: { Accept: 'application/json' }, - }), - ); - }); - test('route grouping child generation posts to the protected Route Ops API with CSRF', async () => { const fetchMock = vi.fn(() => Promise.resolve( @@ -1588,30 +1537,6 @@ function bootstrap(overrides: Partial = {}): BootstrapPayload }; } -function routeOptimizationJobFixture(overrides: Partial = {}): RouteOptimizationJobDto { - return { - appliedAt: null, - createdAt: '2026-06-10T07:00:00.000Z', - createdBy: 'web-operator', - currentStep: 'QUEUED', - elapsedMs: null, - engineResultSequence: null, - errorCode: null, - errorMessage: null, - finishedAt: null, - id: 'job-id', - invalidatedReason: null, - routePlanId: 'route-plan-id', - shopId: 'shop-id', - startedAt: null, - status: 'QUEUED', - timeoutBudgetMs: 180000, - traceId: 'route-opt:route-plan-id:test', - updatedAt: '2026-06-10T07:00:00.000Z', - ...overrides, - }; -} - function routePlanDetailFixture(overrides: Partial = {}): RoutePlanDetailDto { return { routeGeometry: null, diff --git a/apps/route-ops-web/tests/map-helpers.test.ts b/apps/route-ops-web/tests/map-helpers.test.ts index 12bd001c..1d965f54 100644 --- a/apps/route-ops-web/tests/map-helpers.test.ts +++ b/apps/route-ops-web/tests/map-helpers.test.ts @@ -3,9 +3,9 @@ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { buildOrdersMapFeatureCollection, buildRouteDropoffPointFeatureCollection, buildRouteGeometryFeature, buildRouteStopMarkerFeatureCollection, fitBoundsForPoints, getRouteDropoffPoints, getRouteFitPoints, getRouteMapPoints } from '../src/maps/geojson'; -import { auditStyleEndpoints, extractStyleEndpointUrls, mapReadiness, providerStatusLabel } from '../src/maps/provider'; +import { mapReadiness } from '../src/maps/provider'; import { installPmtilesProtocol } from '../src/maps/pmtiles'; -import type { BootstrapPayload, CanonicalOrderDto, RoutePlanDetailDto } from '../src/types'; +import type { CanonicalOrderDto, RoutePlanDetailDto } from '../src/types'; describe('route ops map helpers', () => { test('builds order GeoJSON with candidate/review pin classification and filters invalid coordinates', () => { @@ -259,25 +259,6 @@ describe('route ops map helpers', () => { ]); }); - test('extracts style manifest endpoints and classifies public vs self-hosted hosts', () => { - const endpoints = extractStyleEndpointUrls({ - glyphs: 'https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf', - sources: { - local: { tiles: ['/admin/ui/app/vendor/tiles/{z}/{x}/{y}.pbf'] }, - pmtiles: { url: 'pmtiles://https://example.test/world.pmtiles' } - }, - sprite: '/admin/ui/app/vendor/sprites/clever' - }); - expect(endpoints).toContain('https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf'); - expect(endpoints).toContain('https://example.test/world.pmtiles'); - expect(auditStyleEndpoints(endpoints, ['tiles.openfreemap.org']).isAllowed).toBe(false); - expect(auditStyleEndpoints(endpoints, ['tiles.openfreemap.org', 'example.test'])).toEqual({ - externalHosts: ['example.test', 'tiles.openfreemap.org'], - isAllowed: true, - providerMode: 'public_allowlisted' - }); - }); - test('does not double-install PMTiles protocol in one browser session', () => { const originalWindow = (globalThis as { window?: Window }).window; (globalThis as { window?: Partial }).window = { __cleverRouteOpsPmtilesProtocolInstalled: false }; @@ -292,11 +273,6 @@ describe('route ops map helpers', () => { } }); - test('labels configured and fallback provider states', () => { - expect(providerStatusLabel(bootstrap({ providerMode: 'public_allowlisted', status: 'configured' }))).toBe('Public map provider allowlisted'); - expect(providerStatusLabel(bootstrap({ disabledReason: 'public_provider_mode_not_enabled', providerMode: null, status: 'not_configured' }))).toBe('public_provider_mode_not_enabled'); - }); - test('keeps configured providers interactive before markers exist', () => { expect(mapReadiness({ mapStatus: 'not_configured' })).toBe('provider_not_configured'); expect(mapReadiness({ mapStatus: 'configured' })).toBe('interactive_map'); @@ -363,15 +339,3 @@ function routeDetail(): RoutePlanDetailDto { ] }; } - -function bootstrap(mapConfig: Partial): BootstrapPayload['mapConfig'] { - return { - allowedHosts: [], - attribution: null, - providerMode: null, - status: 'not_configured', - styleAudit: null, - styleUrl: null, - ...mapConfig - }; -} diff --git a/apps/route-ops-web/tests/orders-table.test.tsx b/apps/route-ops-web/tests/orders-table.test.tsx index e6d10a93..d280ea70 100644 --- a/apps/route-ops-web/tests/orders-table.test.tsx +++ b/apps/route-ops-web/tests/orders-table.test.tsx @@ -2,15 +2,10 @@ import { renderToStaticMarkup } from "react-dom/server"; import { describe, expect, test } from "vitest"; import { - buildRouteDetailPath, buildEditableMetadataFields, buildNextPlanSelection, - buildOrderInventoryRows, buildOrderMapMarkerStates, - buildRouteDraftSelection, buildVisibleSelectedOrderIds, - filterOrdersByRoutePlan, - getRouteDraftFirstCreateReason, getRouteDraftCreateReasons, normalizeOrderMetadataPatchForFields, OrderDetailChoiceDropdown, @@ -19,21 +14,17 @@ import { formatBlockerReason, formatMethodLabel, formatMethodStatusLabel, - formatOrderReceivedLabel, formatOperationalStatus, formatTableReadinessStatus, formatPaymentStatusLabel, - getRouteRepairPrompt, ORDERS_TABLE_COLUMN_COUNT, RoutePlanPanel, OrderTable, type OrderMetadataPatch, } from "../src/pages/OrdersPage"; import { - getOrderBlockerLabels, + getOrderDetailLabels, getOrderFieldLabels, - orderBlockerLabels, - orderFieldLabels, } from "../src/i18n"; import { createDefaultOrderFilters } from "../src/state"; import type { @@ -103,27 +94,6 @@ describe("Orders compact operations table", () => { expect(html).not.toContain("Route eligible"); }); - test("formats source-created order labels with localized update markers", () => { - expect(formatOrderReceivedLabel(orderFixture(), "en-CA")).toBe( - "2026-06-04 THU\nupdated 2026-06-05 FRI", - ); - expect(formatOrderReceivedLabel(orderFixture(), "ko-KR")).toBe( - "2026-06-04 목요일\n수정 2026-06-05 금요일", - ); - expect( - formatOrderReceivedLabel( - orderFixture({ sourceUpdatedDate: "2026-06-04" }), - "en-CA", - ), - ).toBe("2026-06-04 THU"); - expect( - formatOrderReceivedLabel( - orderFixture({ sourceCreatedDate: null, sourceUpdatedDate: null }), - "en-CA", - ), - ).toBe("—"); - }); - test("keeps the order table mounted during filter refreshes to avoid scroll jumps", () => { const html = renderOrderTable([orderFixture()], { loading: true, @@ -201,37 +171,6 @@ describe("Orders compact operations table", () => { }); - test("builds a compact printable inventory table from filtered order items", () => { - const rows = buildOrderInventoryRows([ - orderFixture({ - orderName: "#1001", - sourceOrderNumber: "1001", - items: [ - { name: "Soup", options: [], productId: 1, quantity: 1, sku: null, variationId: 0 }, - { name: "Kimchi", options: [{ key: "size", value: "Large" }], productId: 2, quantity: 2, sku: "K", variationId: 0 }, - ], - }), - orderFixture({ - orderName: "#1002", - sourceOrderNumber: "1002", - items: [ - { name: "Soup", options: [], productId: 1, quantity: 3, sku: null, variationId: 0 }, - ], - }), - ]); - - expect(rows).toContainEqual({ - orderRefs: ["1x #1001", "3x #1002"], - product: "Soup", - quantity: 4, - }); - expect(rows).toContainEqual({ - orderRefs: ["2x #1001"], - product: "Kimchi (size: Large)", - quantity: 2, - }); - }); - test("renders reliable order total in order detail only", () => { const html = renderOrderTable( [orderFixture({ currencyCode: "CAD", totalPriceAmount: "42.50" })], @@ -413,12 +352,6 @@ describe("Orders compact operations table", () => { expect(html).not.toContain("Geocode & add"); expect(html).not.toContain("Geocode and add order"); expect(html).not.toContain("Use bulk geocode"); - expect(getRouteRepairPrompt(missingCoordinates)).toEqual({ - canGeocode: true, - routeDetail: "Need coordinates", - statusDetail: "use bulk geocode", - statusLabel: "Need coordinates", - }); expect(formatOperationalStatus(missingCoordinates).label).toBe( "Need coordinates", ); @@ -452,12 +385,6 @@ describe("Orders compact operations table", () => { expect(html).toContain('name="address1"'); expect(html).not.toContain("use bulk geocode"); expect(html).not.toContain("Use Bulk geocode from the order list."); - expect(getRouteRepairPrompt(addressReview)).toEqual({ - canGeocode: false, - routeDetail: "Address Review", - statusDetail: "Verify address", - statusLabel: "Address Review", - }); expect(formatOperationalStatus(addressReview)).toEqual( expect.objectContaining({ detail: "Verify address", @@ -508,12 +435,6 @@ describe("Orders compact operations table", () => { expect(formatOperationalStatus(dateReview).meaning).toContain( "A delivery date hint exists", ); - expect(getRouteRepairPrompt(dateReview)).toEqual({ - canGeocode: false, - routeDetail: "Delivery date review", - statusDetail: "Verify delivery date", - statusLabel: "Delivery date review", - }); expect(html).not.toContain("Delivery date review"); expect(html).toContain("Verify delivery date"); expect(html).not.toContain("Warning meaning: A delivery date hint exists"); @@ -911,6 +832,8 @@ describe("Orders compact operations table", () => { }); test("centralizes order i18n labels and avoids raw fallback labels", () => { + const orderFieldLabels = getOrderFieldLabels("en-CA"); + const orderBlockerLabels = getOrderDetailLabels("en-CA").blockerReasons; expect(orderFieldLabels["meta_data._tomatono_delivery_day"]).toBe( "Delivery day", ); @@ -923,7 +846,7 @@ describe("Orders compact operations table", () => { ); expect(orderBlockerLabels.missing_coordinates).toBe("Need coordinates"); expect(getOrderFieldLabels("ko-KR").address1).toBe("도로명 주소"); - expect(getOrderBlockerLabels("ko-KR").missing_coordinates).toBe( + expect(getOrderDetailLabels("ko-KR").blockerReasons.missing_coordinates).toBe( "좌표 필요", ); expect(formatDiagnosticPathLabel("unknown.path")).toBe("Order metadata"); @@ -1054,7 +977,7 @@ describe("Orders compact operations table", () => { expect(html).not.toContain("Missing delivery date"); }); - test("route draft selection keeps every selected order and validates create separately", () => { + test("route draft validation reports mismatched delivery dates and types", () => { const first = orderFixture({ orderId: "first" }); const sameScope = orderFixture({ orderId: "same-scope", @@ -1074,21 +997,8 @@ describe("Orders compact operations table", () => { timeWindowStart: null, }); - const draft = buildRouteDraftSelection( - [first, sameScope, otherDate, otherSession], - new Set(["first", "same-scope", "other-date", "other-session"]), - ); - - expect(draft.deliveryDate).toBeNull(); - expect(draft.orderIds).toEqual([ - "first", - "same-scope", - "other-date", - "other-session", - ]); - expect(draft.warning).toBeNull(); - expect(getRouteDraftFirstCreateReason([first, sameScope])).toBeNull(); - expect(getRouteDraftFirstCreateReason([first])).toBeNull(); + expect(getRouteDraftCreateReasons([first, sameScope])).toEqual([]); + expect(getRouteDraftCreateReasons([first])).toEqual([]); expect( getRouteDraftCreateReasons([first, otherDate, otherSession]), ).toEqual([ @@ -1134,17 +1044,12 @@ describe("Orders compact operations table", () => { const third = orderFixture({ orderId: "third", orderName: "#11455" }); const filters = createDefaultOrderFilters(); - const clickedDraft = buildRouteDraftSelection( - [first, second, third], - new Set(["third", "first", "second"]), - ); - - expect(clickedDraft.orderIds).toEqual(["third", "first", "second"]); + const selectedOrderIds = new Set(["third", "first", "second"]); const clickedMarkers = buildOrderMapMarkerStates({ filters, orders: [first, second, third], - selectedOrderIds: new Set(clickedDraft.orderIds), + selectedOrderIds, worksetContext: { scope: "planning" }, }); @@ -1163,39 +1068,6 @@ describe("Orders compact operations table", () => { new Set(["delivery", "pickup"]), ), ).toEqual(["delivery"]); - expect( - buildRouteDraftSelection( - [delivery, pickup], - new Set(buildVisibleSelectedOrderIds([delivery], new Set(["delivery", "pickup"]))), - ), - ).toMatchObject({ - orderIds: ["delivery"], - routeType: "delivery", - }); - }); - - test("planned route helpers filter orders by route and build the route detail URL", () => { - const routeOrder = orderFixture({ - orderId: "route-order", - orderName: "#11460", - planningStatus: "PLANNED", - routePlanId: "route/id", - routePlanName: "Route 1", - }); - const otherOrder = orderFixture({ - orderId: "other-order", - orderName: "#11461", - }); - - expect( - filterOrdersByRoutePlan([routeOrder, otherOrder], "route/id")?.map( - (order) => order.orderId, - ), - ).toEqual(["route-order"]); - expect(filterOrdersByRoutePlan([routeOrder, otherOrder], null)).toBeNull(); - expect(buildRouteDetailPath("route/id")).toBe( - "/admin/ui/app/routes/route%2Fid", - ); }); test("planned route markers stay blue and unfaded even when subfilters differ", () => { diff --git a/apps/route-ops-web/tests/state.test.ts b/apps/route-ops-web/tests/state.test.ts index 1338cc69..daf0643a 100644 --- a/apps/route-ops-web/tests/state.test.ts +++ b/apps/route-ops-web/tests/state.test.ts @@ -11,15 +11,12 @@ import { } from '../src/settingsUi'; import { applyClientOrderFilters, - buildAreaOptionSourceFilters, + applyClientOrderFiltersExcept, buildOrderFetchQuery, buildOrderQuery, createDefaultOrderFilters, deriveAreaFilterOptions, deriveOrderFilterOptions, - deriveRouteStats, - geometryLabel, - getOrderWorksetUnavailableReasons, hasStopSequenceChanged, hideSetupActions, isAddressReviewRequired, @@ -28,7 +25,6 @@ import { matchesPlanningScope, mergeOrderListsById, moveStop, - moveStopBefore, moveStopToDropPosition, moveStopToSequence, pruneOrderFilters, @@ -37,11 +33,9 @@ import { selectOrdersForClientFilters, shouldLoadHistoryOrders, storeSettingsToDepotPoint, - summarizeOrderWorkset, - summarizeSelection, toggleWeekdayDeliveryDates } from '../src/state'; -import type { BootstrapPayload, CanonicalOrderDto, RoutePlanDetailDto, RouteStopDto } from '../src/types'; +import type { BootstrapPayload, CanonicalOrderDto, RouteStopDto } from '../src/types'; describe('route ops web state helpers', () => { test('manages route ops UI reminder and template helpers without rendering real order values', () => { @@ -250,7 +244,6 @@ describe('route ops web state helpers', () => { deliveryArea: 'Toronto West', deliveryDate: '2026-05-27' }; - const sourceFilters = buildAreaOptionSourceFilters(filters); const orders = [ order({ deliveryArea: 'Toronto West', deliveryDate: '2026-05-27', orderId: 'west' }), order({ deliveryArea: 'Toronto East', deliveryDate: '2026-05-27', orderId: 'east' }), @@ -259,8 +252,7 @@ describe('route ops web state helpers', () => { order({ deliveryArea: null, deliveryDate: '2026-05-27', orderId: 'missing-area' }), ]; - expect(sourceFilters).toEqual(expect.objectContaining({ deliveryArea: '', deliveryDate: '2026-05-27' })); - const sourceOrders = applyClientOrderFilters(orders, sourceFilters); + const sourceOrders = applyClientOrderFiltersExcept(orders, filters, 'deliveryArea'); expect(sourceOrders.map((item) => item.orderId)).toEqual(['west', 'east', 'blank', 'missing-area']); expect(deriveAreaFilterOptions(sourceOrders)).toEqual(['Toronto East', 'Toronto West']); @@ -280,11 +272,10 @@ describe('route ops web state helpers', () => { ); }); - test('classifies planning scope tabs and workset availability reasons', () => { + test('classifies planning scope tabs', () => { const ready = order({ deliveryDate: '2026-05-29', orderId: 'ready' }); const planned = order({ deliveryDate: '2026-05-29', orderId: 'planned', planningStatus: 'PLANNED', routePlanId: 'route-1' }); const missingDate = order({ blockerReasons: ['missing_delivery_date'], deliveryDate: null, metadataResolved: false, orderId: 'missing-date', routeEligible: false }); - const metadataReview = order({ blockerReasons: ['missing_delivery_area'], metadataResolved: false, orderId: 'metadata-review', routeEligible: false }); const completed = order({ deliveryDate: '2026-05-29', deliveryStatus: 'completed', orderId: 'completed' }); expect(matchesPlanningScope(ready, '2026-05-29')).toBe(true); @@ -292,20 +283,6 @@ describe('route ops web state helpers', () => { expect(matchesOrderTab(ready, 'unplanned', '2026-05-29')).toBe(true); expect(matchesOrderTab(planned, 'planned', '2026-05-29')).toBe(true); expect(matchesOrderTab(missingDate, 'needs_review', '2026-05-29')).toBe(true); - - expect(getOrderWorksetUnavailableReasons(planned, { scope: 'planning' }).map((reason) => reason.code)).toContain('already_planned'); - expect(getOrderWorksetUnavailableReasons(ready, { scope: 'history' })).toEqual([]); - expect(getOrderWorksetUnavailableReasons(missingDate, { scope: 'history' }).map((reason) => reason.code)).toEqual(['missing_delivery_date']); - expect(getOrderWorksetUnavailableReasons(metadataReview, { scope: 'history' }).map((reason) => reason.code)).toContain('needs_review'); - const summary = summarizeOrderWorkset([ready, planned, missingDate, metadataReview], new Set(['ready']), { scope: 'planning' }); - expect(summary).toEqual(expect.objectContaining({ selectableCount: 1, selectedCount: 1, unavailableCount: 3 })); - expect(summary.reasonLabels.join(' ')).toContain('Already planned'); - expect(summary.reasonLabels.join(' ')).toContain('Missing delivery date'); - expect(summary.reasonLabels.join(' ')).toContain('Other metadata review'); - const koreanSummary = summarizeOrderWorkset([ready, planned, missingDate, metadataReview], new Set(['ready']), { scope: 'planning' }, 'ko-KR'); - expect(koreanSummary.reasonLabels.join(' ')).toContain('이미 배정됨'); - expect(koreanSummary.reasonLabels.join(' ')).toContain('배송 날짜 누락'); - expect(koreanSummary.reasonLabels.join(' ')).toContain('기타 메타데이터 검토'); }); test('separates exhausted bulk geocode failures as address review', () => { @@ -328,13 +305,6 @@ describe('route ops web state helpers', () => { }); expect(isAddressReviewRequired(addressReview)).toBe(true); - expect( - getOrderWorksetUnavailableReasons(addressReview).map((reason) => reason.code) - ).toEqual(['address_review']); - const summary = summarizeOrderWorkset([addressReview], new Set()); - expect(summary.reasonLabels).toEqual(['Address Review 1']); - expect(summary.reasonsByCode.address_review).toBe(1); - const pendingCoordinates = order({ blockerReasons: ['missing_coordinates'], coordinates: { latitude: null, longitude: null }, @@ -352,9 +322,6 @@ describe('route ops web state helpers', () => { } }); expect(isAddressReviewRequired(pendingCoordinates)).toBe(false); - expect( - getOrderWorksetUnavailableReasons(pendingCoordinates).map((reason) => reason.code) - ).toEqual(['missing_coordinates']); }); test('separates missing delivery dates from delivery date review blockers', () => { @@ -375,20 +342,7 @@ describe('route ops web state helpers', () => { expect(isDeliveryDateReviewRequired(actuallyMissing)).toBe(false); expect(isDeliveryDateReviewRequired(reviewNeeded)).toBe(true); - expect( - getOrderWorksetUnavailableReasons(actuallyMissing).map((reason) => reason.code) - ).toEqual(['missing_delivery_date']); - expect( - getOrderWorksetUnavailableReasons(reviewNeeded).map((reason) => reason.code) - ).toEqual(['delivery_date_review']); - const summary = summarizeOrderWorkset([actuallyMissing, reviewNeeded], new Set()); - expect(summary.reasonLabels).toEqual([ - 'Delivery date review 1', - 'Missing delivery date 1' - ]); - expect(summary.reasonsByCode.delivery_date_review).toBe(1); - expect(summary.reasonsByCode.missing_delivery_date).toBe(1); }); @@ -403,13 +357,6 @@ describe('route ops web state helpers', () => { expect(withWorkspaceQuery('/admin/ui/app/api/bootstrap', '')).toBe('/admin/ui/app/api/bootstrap'); }); - test('summarizes selected ready orders and blockers', () => { - const orders = [order({ orderId: 'ready' }), order({ blockerReasons: ['missing_coordinates'], orderId: 'blocked', planningStatus: 'UNPLANNED' })]; - const result = summarizeSelection(orders, new Set(['ready', 'blocked'])); - expect(result.readySelected.map((item) => item.orderId)).toEqual(['ready']); - expect(result.blockers).toEqual(['#1001: missing_coordinates']); - }); - test('moves stops using keyboard-compatible reorder helper', () => { const stops = [stop('a', 1), stop('b', 2), stop('c', 3)]; expect(moveStop(stops, 'b', -1).map((item) => `${item.deliveryStopId}:${item.sequence}`)).toEqual(['b:1', 'a:2', 'c:3']); @@ -426,13 +373,6 @@ describe('route ops web state helpers', () => { - test('supports drag/drop stop insertion before a target stop', () => { - const stops = [stop('a', 1), stop('b', 2), stop('c', 3), stop('d', 4)]; - expect(moveStopBefore(stops, 'd', 'b').map((item) => `${item.deliveryStopId}:${item.sequence}`)).toEqual(['a:1', 'd:2', 'b:3', 'c:4']); - expect(moveStopBefore(stops, 'b', 'd').map((item) => `${item.deliveryStopId}:${item.sequence}`)).toEqual(['a:1', 'c:2', 'b:3', 'd:4']); - expect(moveStopBefore(stops, 'x', 'd')).toBe(stops); - }); - test('supports drag/drop stop insertion before or after a preview target', () => { const stops = [stop('a', 1), stop('b', 2), stop('c', 3), stop('d', 4)]; @@ -453,38 +393,6 @@ describe('route ops web state helpers', () => { expect(moveStopToSequence(stops, 'b', 5)).toBe(stops); }); - test('derives route stats and geometry labels honestly', () => { - const detail: RoutePlanDetailDto = { - routeGeometry: null, - routePlan: { - createdAt: '', - deliveryAreas: [], - deliveryDate: '2026-05-27', - depot: { latitude: 43.7, longitude: -79.4 }, - driverId: null, - id: 'route-1', - missingCoordinates: 1, - name: 'Route 1', - planDate: '2026-05-27', - routeEndMode: 'END_AT_LAST_STOP', - status: 'DRAFT', - stopsCount: 2, - updatedAt: '' - }, - routeStopPoints: [], - stops: [stop('a', 1, 'COMPLETED'), stop('b', 2, 'ATTEMPTED', null, null)] - }; - expect(deriveRouteStats(detail)).toEqual({ attempted: 1, completed: 1, missingCoordinates: 1, stops: 2 }); - expect(geometryLabel(null, 'configured')).toBe('No route selected'); - expect(geometryLabel(detail, 'not_configured')).toBe('Router not configured for ready stops'); - expect(geometryLabel(detail, 'not_configured', 'ko-KR')).toBe('표시된 정류지의 라우터가 설정되지 않음'); - expect(geometryLabel({ ...detail, routeGeometry: { coordinates: [[-79, 43], [-79.1, 43.1]], type: 'LineString' } }, 'configured')).toBe('Road path ready'); - expect(geometryLabel({ ...detail, routeGeometry: { coordinates: [[-79, 43], [-79.1, 43.1]], type: 'LineString' } }, 'configured', 'ko-KR')).toBe('도로 경로'); - expect(geometryLabel(detail, 'configured')).toBe('Road path not generated for ready stops'); - expect(geometryLabel({ ...detail, routePlan: { ...detail.routePlan, depot: { latitude: null, longitude: null } }, stops: [stop('a', 1, 'PENDING', null, null)] }, 'configured')).toBe('Need coordinates for road path'); - expect(geometryLabel({ ...detail, routePlan: { ...detail.routePlan, depot: { latitude: null, longitude: null } }, stops: [stop('a', 1, 'PENDING', null, null)] }, 'configured', 'ko-KR')).toBe('경로 선에 필요한 좌표 부족'); - }); - test('keeps map/provider states explicit and plugin mode hides setup actions', () => { expect(hideSetupActions(bootstrap('plugin'))).toBe(true); expect(hideSetupActions(bootstrap('internal-admin'))).toBe(false);