Add self-serve session handling and lane selection normalization
This commit is contained in:
@@ -7,6 +7,11 @@ export const WASH_STEPS = {
|
||||
COMPLETED: 5,
|
||||
};
|
||||
|
||||
const normalizeLaneId = (value) => {
|
||||
const normalized = parseInt(String(value ?? ""), 10);
|
||||
return Number.isNaN(normalized) || normalized <= 0 ? null : normalized;
|
||||
};
|
||||
|
||||
export function useWashFlowState(options) {
|
||||
const {
|
||||
currentStep,
|
||||
@@ -35,6 +40,17 @@ export function useWashFlowState(options) {
|
||||
activeTasks.value.length > 0 ? steps.TASKS : steps.WASH_IN_PROGRESS
|
||||
);
|
||||
|
||||
const getSelectedLaneId = () => normalizeLaneId(radioLaneOption.value);
|
||||
|
||||
const getSelectedLane = () => {
|
||||
const selectedLaneId = getSelectedLaneId();
|
||||
if (selectedLaneId === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return nearestDepartment.value?.lanes.find((lane) => normalizeLaneId(lane.id) === selectedLaneId) || null;
|
||||
};
|
||||
|
||||
const clickableSteps = {
|
||||
[steps.VEHICLE]: () => {
|
||||
if (washInProgress.value) {
|
||||
@@ -106,7 +122,7 @@ export function useWashFlowState(options) {
|
||||
}
|
||||
|
||||
if (currentStep.value === steps.SELECT_LANE) {
|
||||
const selectedLane = nearestDepartment.value?.lanes.find((lane) => lane.id === radioLaneOption.value);
|
||||
const selectedLane = getSelectedLane();
|
||||
if (!selectedLane || !isLaneAvailable(selectedLane)) {
|
||||
return true;
|
||||
}
|
||||
@@ -136,9 +152,10 @@ export function useWashFlowState(options) {
|
||||
const handleConfirmNext = async () => {
|
||||
if (currentStep.value === steps.QUESTIONS) {
|
||||
editAnswers.value = false;
|
||||
if (radioLaneOption.value && radioLaneOption.value !== "Any") {
|
||||
const selectedLaneId = getSelectedLaneId();
|
||||
if (selectedLaneId !== null) {
|
||||
try {
|
||||
await updateLaneAllowedServices(radioLaneOption.value);
|
||||
await updateLaneAllowedServices(selectedLaneId);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
@@ -153,8 +170,9 @@ export function useWashFlowState(options) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedLaneId = getSelectedLaneId() ?? radioLaneOption.value;
|
||||
await onStartWash(
|
||||
radioLaneOption.value,
|
||||
selectedLaneId,
|
||||
licensePlateInput.value,
|
||||
customerNumberInput.value,
|
||||
targetStepForStart()
|
||||
|
||||
@@ -121,6 +121,7 @@ const {
|
||||
answers,
|
||||
completedTasks,
|
||||
allowedServices,
|
||||
session: selfServeSession,
|
||||
error: selfServeDataError,
|
||||
visibleQuestions,
|
||||
activeTasks,
|
||||
@@ -203,8 +204,33 @@ const isLaneSelfServeEnabled = (lane: any) =>
|
||||
const isCurrentActiveWashLane = (lane: any) =>
|
||||
washInProgress.value && normalizeLaneId(lane?.id) === normalizeLaneId(washLaneId.value);
|
||||
|
||||
const isCurrentAllowedSelfServeLane = (lane: any) =>
|
||||
selfServeAllowed.value === true && normalizeLaneId(lane?.id) === normalizeLaneId(selfServeLane.value?.id);
|
||||
const isAllowedSelfServeValue = (value: any) =>
|
||||
value === true ||
|
||||
value === 1 ||
|
||||
value === "1" ||
|
||||
String(value ?? "")
|
||||
.trim()
|
||||
.toLowerCase() === "true";
|
||||
|
||||
const isSelfServeSessionAllowed = () =>
|
||||
isAllowedSelfServeValue(selfServeAllowed.value) || isAllowedSelfServeValue(selfServeSession.value?.allowed);
|
||||
|
||||
const getAllowedSelfServeLaneIds = () =>
|
||||
new Set(
|
||||
[
|
||||
selfServeLane.value?.id,
|
||||
selfServeSession.value?.lane_id,
|
||||
selfServeSession.value?.lane?.id,
|
||||
selfServeSession.value?.metadata?.lane_id,
|
||||
]
|
||||
.map(normalizeLaneId)
|
||||
.filter((laneId): laneId is number => laneId !== null)
|
||||
);
|
||||
|
||||
const isCurrentAllowedSelfServeLane = (lane: any) => {
|
||||
const laneId = normalizeLaneId(lane?.id);
|
||||
return laneId !== null && isSelfServeSessionAllowed() && getAllowedSelfServeLaneIds().has(laneId);
|
||||
};
|
||||
|
||||
const isLaneAvailable = (lane: any) =>
|
||||
(isCurrentActiveWashLane(lane) || isCurrentAllowedSelfServeLane(lane) || lane.status === "AVAILABLE") &&
|
||||
@@ -219,7 +245,9 @@ const isMachineAvailable = (laneId: number | string | null) => {
|
||||
);
|
||||
}
|
||||
|
||||
const selectedLane = nearestDepartment.value?.lanes.find((entry: { id: number }) => entry.id === laneId);
|
||||
const selectedLane = nearestDepartment.value?.lanes.find(
|
||||
(entry: { id: number }) => normalizeLaneId(entry.id) === normalizeLaneId(laneId)
|
||||
);
|
||||
if (!selectedLane) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -52,11 +52,13 @@ describe("MyWashStart.vue production recovery contracts", () => {
|
||||
});
|
||||
|
||||
it("does not treat the customer's active occupied lane as unavailable", () => {
|
||||
expect(source).toContain("session: selfServeSession,");
|
||||
expect(source).toContain("const isCurrentActiveWashLane = (lane: any) =>");
|
||||
expect(source).toContain("const isSelfServeSessionAllowed = () =>");
|
||||
expect(source).toContain("const isCurrentAllowedSelfServeLane = (lane: any) =>");
|
||||
expect(source).toContain(
|
||||
"selfServeAllowed.value === true && normalizeLaneId(lane?.id) === normalizeLaneId(selfServeLane.value?.id)"
|
||||
);
|
||||
expect(source).toContain("isAllowedSelfServeValue(selfServeSession.value?.allowed)");
|
||||
expect(source).toContain("selfServeSession.value?.lane_id");
|
||||
expect(source).toContain("getAllowedSelfServeLaneIds().has(laneId)");
|
||||
expect(source).toContain(
|
||||
'(isCurrentActiveWashLane(lane) || isCurrentAllowedSelfServeLane(lane) || lane.status === "AVAILABLE")'
|
||||
);
|
||||
|
||||
@@ -26,6 +26,7 @@ const mocks = vi.hoisted(() => {
|
||||
const allowedServices = { value: ["MACHINE"] };
|
||||
const selfServeAllowed = { value: false };
|
||||
const selfServeLane = { value: { id: 7 } };
|
||||
const selfServeSession = { value: null };
|
||||
|
||||
return {
|
||||
nearestDepartment,
|
||||
@@ -40,6 +41,7 @@ const mocks = vi.hoisted(() => {
|
||||
allowedServices,
|
||||
selfServeAllowed,
|
||||
selfServeLane,
|
||||
selfServeSession,
|
||||
fetchDepartments: vi.fn(async () => guestDepartments.value),
|
||||
startAutoRefresh: vi.fn(),
|
||||
stopAutoRefresh: vi.fn(),
|
||||
@@ -176,6 +178,7 @@ vi.mock("@/composables/useSelfServeLogic", () => ({
|
||||
completedTasks: mocks.completedTasks,
|
||||
allowedServices: mocks.allowedServices,
|
||||
allowed: mocks.selfServeAllowed,
|
||||
session: mocks.selfServeSession,
|
||||
visibleQuestions: mocks.visibleQuestions,
|
||||
activeTasks: mocks.activeTasks,
|
||||
allVisibleQuestionsAnswered: { value: false },
|
||||
@@ -376,6 +379,7 @@ describe("MyWashStart", () => {
|
||||
mocks.allowedServices.value = ["MACHINE"];
|
||||
mocks.selfServeAllowed.value = false;
|
||||
mocks.selfServeLane.value = { id: 7 };
|
||||
mocks.selfServeSession.value = null;
|
||||
mocks.conditions.value = [];
|
||||
mocks.rules.value = [];
|
||||
mocks.sessionCustomerNumber.value = 12345679;
|
||||
@@ -517,6 +521,27 @@ describe("MyWashStart", () => {
|
||||
expect(wrapper.get('[data-testid="lane-7-available"]').text()).toBe("true");
|
||||
});
|
||||
|
||||
it("uses the allowed session lane when the lane payload is not populated yet", async () => {
|
||||
mocks.nearestDepartment.value = {
|
||||
...mocks.nearestDepartment.value,
|
||||
lanes: [{ id: 7, name: "7", status: "OCCUPIED", products: [2], machine_available: true }],
|
||||
};
|
||||
mocks.guestDepartments.value = [mocks.nearestDepartment.value];
|
||||
mocks.selfServeLane.value = null;
|
||||
mocks.selfServeAllowed.value = false;
|
||||
mocks.selfServeSession.value = { lane_id: 7, allowed: true };
|
||||
|
||||
const wrapper = mountWithApp(MyWashStart, {
|
||||
global: {
|
||||
stubs: stubComponents,
|
||||
},
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.get('[data-testid="lane-7-available"]').text()).toBe("true");
|
||||
});
|
||||
|
||||
it("hides machine tasks while manual wash is selected", async () => {
|
||||
mocks.activeTasks.value = [
|
||||
{ id: 31, task: "Machine checklist", services: ["MACHINE"] },
|
||||
|
||||
@@ -79,6 +79,17 @@ describe("useWashFlowState production transitions", () => {
|
||||
expect(flow.isNextButtonDisabled()).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps lane confirmation enabled when the selected lane id is a string", async () => {
|
||||
const { state, flow } = createFlow({ radioLaneOption: ref("7") });
|
||||
state.currentStep.value = WASH_STEPS.SELECT_LANE;
|
||||
|
||||
expect(flow.isNextButtonDisabled()).toBe(false);
|
||||
|
||||
await flow.handleConfirmNext();
|
||||
|
||||
expect(state.onStartWash).toHaveBeenCalledWith(7, "AB12345", "12345679", WASH_STEPS.WASH_IN_PROGRESS);
|
||||
});
|
||||
|
||||
it("stays on questions when lane allowed-service refresh fails", async () => {
|
||||
const { state, flow } = createFlow({
|
||||
updateLaneAllowedServices: vi.fn(async () => {
|
||||
|
||||
@@ -111,4 +111,21 @@ describe("useWashFlowState", () => {
|
||||
|
||||
expect(state.onStartWash).toHaveBeenCalledWith(7, "AB12345", "12345", WASH_STEPS.TASKS);
|
||||
});
|
||||
|
||||
it("matches string lane selections against numeric lane ids", async () => {
|
||||
const { flow, state } = createState({
|
||||
currentStep: ref(WASH_STEPS.QUESTIONS),
|
||||
radioLaneOption: ref("7"),
|
||||
});
|
||||
|
||||
await flow.handleConfirmNext();
|
||||
|
||||
expect(state.updateLaneAllowedServices).toHaveBeenCalledWith(7);
|
||||
expect(state.currentStep.value).toBe(WASH_STEPS.SELECT_LANE);
|
||||
expect(flow.isNextButtonDisabled()).toBe(false);
|
||||
|
||||
await flow.handleConfirmNext();
|
||||
|
||||
expect(state.onStartWash).toHaveBeenCalledWith(7, "AB12345", "12345", WASH_STEPS.WASH_IN_PROGRESS);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user