From 985571af87ae4ec5d9ec8c6d16771fc5babd000f Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Thu, 4 Jun 2026 01:55:35 +0200 Subject: [PATCH] Treat unoccupied stop invoice errors as completed washes --- src/composables/useWashSessionActions.js | 5 ++- ...se-wash-session-actions-production.spec.js | 21 +++++++++ tests/unit/use-wash-session-actions.spec.js | 45 +++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/composables/useWashSessionActions.js b/src/composables/useWashSessionActions.js index 446bc216..ec62d8c3 100644 --- a/src/composables/useWashSessionActions.js +++ b/src/composables/useWashSessionActions.js @@ -70,7 +70,10 @@ export function useWashSessionActions(options) { const isAlreadyStoppedStopError = (message) => { const normalized = String(message || "").toLowerCase(); - return normalized.includes("cannot stop lane") && normalized.includes("not occupied"); + return ( + normalized.includes("not occupied") && + (normalized.includes("cannot stop lane") || normalized.includes("cannot invoice")) + ); }; const clearActiveWashState = () => { diff --git a/tests/unit/use-wash-session-actions-production.spec.js b/tests/unit/use-wash-session-actions-production.spec.js index 427f847c..297d466f 100644 --- a/tests/unit/use-wash-session-actions-production.spec.js +++ b/tests/unit/use-wash-session-actions-production.spec.js @@ -126,4 +126,25 @@ describe("useWashSessionActions production commands", () => { expect(state.stopElapsedTimer).toHaveBeenCalled(); expect(state.clearProgress).toHaveBeenCalled(); }); + + it("recovers local state when STOP says an unoccupied lane cannot invoice", async () => { + const request = vi.fn(async () => ({ + data: { success: false, message: "Failed to execute command: Lane ID 7 is not occupied; cannot invoice." }, + })); + const { state, actions } = createActions({ + request, + washInProgress: ref(true), + washLaneId: ref(7), + washStartTime: ref(1_000), + now: ref(6_000), + }); + + await expect(actions.onStopWash(7)).resolves.toBe(true); + + expect(state.washInProgress.value).toBe(false); + expect(state.washLaneId.value).toBeNull(); + expect(state.completedDurationMs.value).toBe(5_000); + expect(state.stopElapsedTimer).toHaveBeenCalled(); + expect(state.clearProgress).toHaveBeenCalled(); + }); }); diff --git a/tests/unit/use-wash-session-actions.spec.js b/tests/unit/use-wash-session-actions.spec.js index 997810be..43a3fcee 100644 --- a/tests/unit/use-wash-session-actions.spec.js +++ b/tests/unit/use-wash-session-actions.spec.js @@ -446,6 +446,51 @@ describe("useWashSessionActions property gate commands", () => { expect(clearProgress).toHaveBeenCalledTimes(1); }); + it("clears active wash state when stop reports the lane cannot invoice because it is unoccupied", async () => { + const washInProgress = ref(true); + const washLaneId = ref(7); + const washStartTime = ref(2_000); + const completedDurationMs = ref(null); + const now = ref(47_000); + const clearProgress = vi.fn(); + const stopElapsedTimer = vi.fn(); + const request = vi.fn(async (_url, _method, body) => { + if (body.command === "STOP") { + return { + data: { + success: false, + data: { + message: "Failed to execute command: Lane ID 7 is not occupied; cannot invoice.", + }, + }, + }; + } + + return { data: { success: true } }; + }); + + const { actions, alertFn } = createActions({ + request, + washInProgress, + washLaneId, + washStartTime, + completedDurationMs, + now, + clearProgress, + stopElapsedTimer, + }); + + const result = await actions.onStopWash(7); + + expect(result).toBe(true); + expect(alertFn).not.toHaveBeenCalled(); + expect(completedDurationMs.value).toBe(45_000); + expect(washInProgress.value).toBe(false); + expect(washLaneId.value).toBeNull(); + expect(stopElapsedTimer).toHaveBeenCalledTimes(1); + expect(clearProgress).toHaveBeenCalledTimes(1); + }); + it("clears active wash state only after the stop command succeeds", async () => { const washInProgress = ref(true); const washLaneId = ref(7);