diff --git a/openapi.yaml b/openapi.yaml index edbfb5c1..546c97d9 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -8704,6 +8704,116 @@ paths: schema: $ref: '#/components/schemas/SelfServeLaneStatus' + /modules/self-serve/lane/wash/my-active-wash: + get: + tags: + - Modules + summary: Get the authenticated customer's active self-serve wash + description: | + Returns the current authenticated customer's open self-serve wash session, + if one exists. Regular customers must only receive their own active wash + details from this endpoint. + operationId: getMyActiveSelfServeWash + responses: + '200': + description: Authenticated customer's active wash details resolved + content: + application/json: + schema: + type: object + properties: + lane_id: + type: integer + nullable: true + in_progress: + type: boolean + session: + type: object + nullable: true + properties: + id: + type: integer + lane_id: + type: integer + nullable: true + department_id: + type: integer + nullable: true + status: + type: string + reg: + type: string + customer_number: + type: integer + nullable: true + vehicle_id: + type: integer + nullable: true + vehicle_type_id: + type: integer + nullable: true + included_minutes: + type: integer + nullable: true + machine_type_id: + type: integer + nullable: true + machine_relay_enabled: + type: boolean + machine_relay_enabled_at: + type: string + nullable: true + machine_start_triggered: + type: boolean + machine_start_triggered_at: + type: string + nullable: true + wash_started_at: + type: string + nullable: true + created_at: + type: string + updated_at: + type: string + nullable: true + customer: + type: object + nullable: true + properties: + id: + type: integer + nullable: true + customer_number: + type: integer + nullable: true + display_name: + type: string + nullable: true + email: + type: string + nullable: true + phone_country_code: + type: integer + nullable: true + phone: + type: string + nullable: true + vehicle: + type: object + nullable: true + properties: + id: + type: integer + customer_id: + type: integer + type: + type: integer + reg: + type: string + reference: + type: string + nullable: true + /modules/self-serve/lane/wash/in-progress: get: tags: diff --git a/src/views/dashboards/userDashboard/wash/MyWashStart.vue b/src/views/dashboards/userDashboard/wash/MyWashStart.vue index c1fcf782..ae0272d4 100644 --- a/src/views/dashboards/userDashboard/wash/MyWashStart.vue +++ b/src/views/dashboards/userDashboard/wash/MyWashStart.vue @@ -654,18 +654,28 @@ const fetchSelfServeData = async () => { await fetchSelfServeDataInternal(departmentId, vehicleTypeSelect.value || null, laneId, normalizedReg); }; -const getServerActiveWashCandidates = () => { +const SERVER_ACTIVE_WASH_ENDPOINT = "/modules/self-serve/lane/wash/my-active-wash"; + +const findServerActiveWashCandidate = (laneId: number | string | null) => { + const normalizedLaneId = normalizeLaneId(laneId); + if (!normalizedLaneId) { + return null; + } + const departments = Array.isArray(guestDepartments.value) ? guestDepartments.value : []; - return departments.flatMap((department: any) => { + for (const department of departments) { const lanes = Array.isArray(department?.lanes) ? department.lanes : []; - return lanes - .map((lane: any) => ({ + const lane = lanes.find((candidateLane: any) => normalizeLaneId(candidateLane?.id) === normalizedLaneId); + if (lane) { + return { department, lane, - laneId: normalizeLaneId(lane?.id), - })) - .filter((candidate: any) => candidate.laneId); - }); + laneId: normalizedLaneId, + }; + } + } + + return { laneId: normalizedLaneId }; }; const isAuthenticatedCustomerActiveWash = (details: any, customerNumber: number) => { @@ -769,31 +779,24 @@ const fetchServerActiveWash = async () => { return null; } - const candidates = getServerActiveWashCandidates(); - if (candidates.length === 0) { + const response = await SessionUser.request(SERVER_ACTIVE_WASH_ENDPOINT, "GET"); + const details = unwrapApiData(response); + if (!isAuthenticatedCustomerActiveWash(details, customerNumber)) { return null; } - const responses = await Promise.allSettled( - candidates.map(async (candidate: any) => { - const response = await SessionUser.request("/modules/self-serve/lane/wash/in-progress", "GET", { - lane_id: candidate.laneId, - }); - return { - ...candidate, - details: unwrapApiData(response), - }; - }) - ); + const laneId = normalizeLaneId(details?.lane_id ?? details?.session?.lane_id); + const activeWashCandidate = findServerActiveWashCandidate(laneId); + const activeWash = { + ...(activeWashCandidate || {}), + details, + }; - const activeMatches = responses - .filter((entry): entry is PromiseFulfilledResult => entry.status === "fulfilled") - .map((entry) => entry.value) - .filter((entry) => isAuthenticatedCustomerActiveWash(entry.details, customerNumber)) - .filter((entry) => !isRecentlyCompletedActiveWash(entry)) - .sort((left, right) => activeWashTimestamp(right.details) - activeWashTimestamp(left.details)); + if (isRecentlyCompletedActiveWash(activeWash)) { + return null; + } - return activeMatches[0] || null; + return activeWash; }; const applyServerActiveWash = async (activeWash: any) => { diff --git a/tests/e2e/support/network.js b/tests/e2e/support/network.js index 9e074499..696ab1d7 100644 --- a/tests/e2e/support/network.js +++ b/tests/e2e/support/network.js @@ -6506,6 +6506,26 @@ export async function mockApi(page, options = {}) { return; } + if (pathname.endsWith("/modules/self-serve/lane/wash/my-active-wash") && method === "GET") { + const customerNumber = Number(options.sessionData?.customer_number || 0); + const details = Object.values(selfServe.inProgressByLaneId || {}).find((entry) => { + const entryCustomerNumber = Number(entry?.session?.customer_number ?? entry?.customer?.customer_number ?? 0); + return Boolean(entry?.in_progress) && customerNumber > 0 && entryCustomerNumber === customerNumber; + }); + await route.fulfill( + json({ + data: details || { + lane_id: null, + in_progress: false, + session: null, + customer: null, + vehicle: null, + }, + }) + ); + return; + } + if (pathname.endsWith("/modules/self-serve/lane/wash/in-progress") && method === "GET") { const laneId = parsedUrl.searchParams.get("lane_id"); const details = selfServe.inProgressByLaneId?.[String(laneId || "")] || null; diff --git a/tests/unit/my-wash-start.spec.js b/tests/unit/my-wash-start.spec.js index b50338a3..e5307b59 100644 --- a/tests/unit/my-wash-start.spec.js +++ b/tests/unit/my-wash-start.spec.js @@ -826,7 +826,7 @@ describe("MyWashStart", () => { it("retries server active wash restore when the authenticated customer number arrives after mount", async () => { mocks.sessionCustomerNumber.value = null; mocks.sessionRequest.mockImplementation(async (path, method, payload) => { - if (path === "/modules/self-serve/lane/wash/in-progress" && method === "GET" && payload?.lane_id === 7) { + if (path === "/modules/self-serve/lane/wash/my-active-wash" && method === "GET") { return { data: { data: { @@ -882,9 +882,7 @@ describe("MyWashStart", () => { await new Promise((resolve) => setTimeout(resolve, 0)); await flushPromises(); - expect(mocks.sessionRequest).toHaveBeenCalledWith("/modules/self-serve/lane/wash/in-progress", "GET", { - lane_id: 7, - }); + expect(mocks.sessionRequest).toHaveBeenCalledWith("/modules/self-serve/lane/wash/my-active-wash", "GET"); expect(mocks.fetchWashSummary).toHaveBeenCalledWith({ session_id: 805, vehicle_type: 2 }, false); expect(mocks.startElapsedTimer).toHaveBeenCalled(); expect( @@ -1155,7 +1153,10 @@ describe("MyWashStart", () => { }; mocks.guestDepartments.value = [mocks.nearestDepartment.value, odense]; mocks.sessionRequest.mockImplementation(async (path, method, payload) => { - if (path === "/modules/self-serve/lane/wash/in-progress" && method === "GET" && payload?.lane_id === 9) { + if ( + (path === "/modules/self-serve/lane/wash/my-active-wash" && method === "GET") || + (path === "/modules/self-serve/lane/wash/in-progress" && method === "GET" && payload?.lane_id === 9) + ) { return { data: { data: { @@ -1199,12 +1200,10 @@ describe("MyWashStart", () => { await new Promise((resolve) => setTimeout(resolve, 0)); await flushPromises(); - expect(mocks.sessionRequest).toHaveBeenCalledWith("/modules/self-serve/lane/wash/in-progress", "GET", { + expect(mocks.sessionRequest).toHaveBeenCalledWith("/modules/self-serve/lane/wash/my-active-wash", "GET"); + expect(mocks.sessionRequest).not.toHaveBeenCalledWith("/modules/self-serve/lane/wash/in-progress", "GET", { lane_id: 7, }); - expect(mocks.sessionRequest).toHaveBeenCalledWith("/modules/self-serve/lane/wash/in-progress", "GET", { - lane_id: 9, - }); expect(mocks.fetchWashSummary).toHaveBeenCalledWith({ session_id: 704, vehicle_type: 2 }, false); expect(mocks.startElapsedTimer).toHaveBeenCalled(); expect(mocks.saveProgress).toHaveBeenCalledWith("serverActiveWash"); @@ -1226,7 +1225,7 @@ describe("MyWashStart", () => { }) ); mocks.sessionRequest.mockImplementation(async (path, method, payload) => { - if (path === "/modules/self-serve/lane/wash/in-progress" && method === "GET" && payload?.lane_id === 7) { + if (path === "/modules/self-serve/lane/wash/my-active-wash" && method === "GET") { return { data: { data: { @@ -1270,9 +1269,7 @@ describe("MyWashStart", () => { await new Promise((resolve) => setTimeout(resolve, 0)); await flushPromises(); - expect(mocks.sessionRequest).toHaveBeenCalledWith("/modules/self-serve/lane/wash/in-progress", "GET", { - lane_id: 7, - }); + expect(mocks.sessionRequest).toHaveBeenCalledWith("/modules/self-serve/lane/wash/my-active-wash", "GET"); expect(mocks.fetchWashSummary).not.toHaveBeenCalled(); expect(mocks.startElapsedTimer).not.toHaveBeenCalled(); expect(mocks.saveProgress).not.toHaveBeenCalledWith("serverActiveWash");