Fix scoped active wash restoration

This commit is contained in:
Jeppe B
2026-06-01 22:06:14 +02:00
parent a76944b00c
commit 4463a5326a
4 changed files with 171 additions and 41 deletions
+110
View File
@@ -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:
@@ -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<any> => 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) => {
+20
View File
@@ -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;
+10 -13
View File
@@ -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");