Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18ab007f87 |
@@ -297,6 +297,30 @@ export function useSelfServeLogic() {
|
||||
const summaryVisibleQuestionIds = ref([]);
|
||||
const summaryQuestionOrder = ref({});
|
||||
const latestFetchRequestId = ref(0);
|
||||
const latestSuccessfulFetchKey = ref(null);
|
||||
const inFlightFetchKey = ref(null);
|
||||
|
||||
const createFetchKey = (departmentId, vehicleTypeId, laneId, reg) => {
|
||||
const normalizedDepartmentId = parseInt(departmentId);
|
||||
const normalizedLaneId = parseInt(laneId);
|
||||
const normalizedReg = String(reg || "").trim().toUpperCase();
|
||||
const normalizedVehicleTypeId = parseInt(vehicleTypeId);
|
||||
const vehicleTypeKey = !Number.isNaN(normalizedVehicleTypeId) && normalizedVehicleTypeId > 0
|
||||
? normalizedVehicleTypeId
|
||||
: "";
|
||||
|
||||
if (
|
||||
Number.isNaN(normalizedDepartmentId)
|
||||
|| normalizedDepartmentId <= 0
|
||||
|| Number.isNaN(normalizedLaneId)
|
||||
|| normalizedLaneId <= 0
|
||||
|| normalizedReg.length < 2
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [normalizedDepartmentId, normalizedLaneId, normalizedReg, vehicleTypeKey].join("|");
|
||||
};
|
||||
|
||||
const beginFetchRequest = () => {
|
||||
latestFetchRequestId.value += 1;
|
||||
@@ -575,10 +599,17 @@ export function useSelfServeLogic() {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchSelfServeData = async (_departmentId, _vehicleTypeId, laneId = null, reg = null) => {
|
||||
const fetchSelfServeData = async (_departmentId, _vehicleTypeId, laneId = null, reg = null, options = {}) => {
|
||||
const fetchKey = createFetchKey(_departmentId, _vehicleTypeId, laneId, reg);
|
||||
if (!options.force && fetchKey && (fetchKey === latestSuccessfulFetchKey.value || fetchKey === inFlightFetchKey.value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const requestId = beginFetchRequest();
|
||||
|
||||
if (!laneId || !reg || reg.trim().length < 2) {
|
||||
latestSuccessfulFetchKey.value = null;
|
||||
inFlightFetchKey.value = null;
|
||||
preview.value = null;
|
||||
summary.value = null;
|
||||
session.value = null;
|
||||
@@ -606,6 +637,7 @@ export function useSelfServeLogic() {
|
||||
|
||||
loading.value = true;
|
||||
requestError.value = null;
|
||||
inFlightFetchKey.value = fetchKey;
|
||||
try {
|
||||
const normalizedReg = reg.trim().toUpperCase();
|
||||
const normalizedVehicleTypeId = parseInt(_vehicleTypeId);
|
||||
@@ -687,6 +719,10 @@ export function useSelfServeLogic() {
|
||||
setSummaryVisibleQuestions(questions.value);
|
||||
}
|
||||
|
||||
if (isFetchRequestActive(requestId)) {
|
||||
latestSuccessfulFetchKey.value = fetchKey;
|
||||
}
|
||||
|
||||
return previewData;
|
||||
} catch (error) {
|
||||
console.error("Error fetching self-serve preview:", error);
|
||||
@@ -695,6 +731,9 @@ export function useSelfServeLogic() {
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
if (inFlightFetchKey.value === fetchKey) {
|
||||
inFlightFetchKey.value = null;
|
||||
}
|
||||
if (isFetchRequestActive(requestId)) {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -749,7 +788,7 @@ export function useSelfServeLogic() {
|
||||
updateResolvedVehicleTypeId(responseSummary, responseSummary?.session);
|
||||
}
|
||||
|
||||
await fetchSelfServeData(departmentId, refreshVehicleTypeId, laneId, normalizedReg);
|
||||
await fetchSelfServeData(departmentId, refreshVehicleTypeId, laneId, normalizedReg, { force: true });
|
||||
answers.value = {
|
||||
...answers.value,
|
||||
[parseInt(questionId)]: value,
|
||||
@@ -814,7 +853,7 @@ export function useSelfServeLogic() {
|
||||
? normalizedRefreshVehicleTypeId
|
||||
: null;
|
||||
|
||||
await fetchSelfServeData(departmentId, refreshVehicleTypeId, parseInt(laneId), normalizedReg);
|
||||
await fetchSelfServeData(departmentId, refreshVehicleTypeId, parseInt(laneId), normalizedReg, { force: true });
|
||||
|
||||
return { deletedCount: conditionIdsToDelete.length };
|
||||
} catch (error) {
|
||||
|
||||
@@ -60,6 +60,7 @@ const RECENT_COMPLETED_WASH_KEY = "mywash_recent_completed_v1";
|
||||
const RECENT_COMPLETED_WASH_SUPPRESSION_MS = 10 * 60 * 1000;
|
||||
const ACTIVE_WASH_REFRESH_MS = 5 * 1000;
|
||||
const WASH_START_SERVER_SYNC_GRACE_MS = 10 * 1000;
|
||||
const SELF_SERVE_FETCH_DEBOUNCE_MS = 300;
|
||||
|
||||
const normalizePositiveInteger = (value: any) => {
|
||||
const parsed = parseInt(String(value ?? ""), 10);
|
||||
@@ -637,24 +638,129 @@ const fetchVehicleTypes = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchSelfServeData = async () => {
|
||||
if (isRestoring.value || !nearestDepartment.value) {
|
||||
return;
|
||||
type SelfServeFetchRequest = {
|
||||
key: string;
|
||||
departmentId: number;
|
||||
laneId: number;
|
||||
registration: string;
|
||||
vehicleTypeId: number | null;
|
||||
};
|
||||
|
||||
let selfServeFetchDebounceTimer: ReturnType<typeof window.setTimeout> | null = null;
|
||||
let selfServeFetchResolvers: Array<(value: any) => void> = [];
|
||||
let latestSuccessfulSelfServeFetchKey: string | null = null;
|
||||
let inFlightSelfServeFetchKey: string | null = null;
|
||||
let selfServeFetchSequence = 0;
|
||||
let isSelfServeFetchUnmounted = false;
|
||||
|
||||
const resolvePendingSelfServeFetches = (value: any = null) => {
|
||||
const resolvers = selfServeFetchResolvers;
|
||||
selfServeFetchResolvers = [];
|
||||
resolvers.forEach((resolve) => resolve(value));
|
||||
};
|
||||
|
||||
const clearScheduledSelfServeFetch = (resolveValue: any = null) => {
|
||||
if (selfServeFetchDebounceTimer) {
|
||||
window.clearTimeout(selfServeFetchDebounceTimer);
|
||||
selfServeFetchDebounceTimer = null;
|
||||
}
|
||||
|
||||
const normalizedReg = normalizeLicensePlate(licensePlateInput.value);
|
||||
if (normalizedReg.length < 2) {
|
||||
return;
|
||||
if (selfServeFetchResolvers.length > 0) {
|
||||
resolvePendingSelfServeFetches(resolveValue);
|
||||
}
|
||||
};
|
||||
|
||||
const createSelfServeFetchRequest = (): SelfServeFetchRequest | null => {
|
||||
if (isRestoring.value || !nearestDepartment.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const departmentId = normalizePositiveInteger(nearestDepartment.value.id);
|
||||
if (!departmentId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const registration = normalizeLicensePlate(licensePlateInput.value);
|
||||
if (registration.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const laneId = ensureEffectiveLaneSelection();
|
||||
if (!laneId) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
const departmentId = nearestDepartment.value.id;
|
||||
const vehicleTypeId = normalizePositiveInteger(vehicleTypeSelect.value);
|
||||
const key = [departmentId, laneId, registration, vehicleTypeId ?? ""].join("|");
|
||||
|
||||
await fetchSelfServeDataInternal(departmentId, vehicleTypeSelect.value || null, laneId, normalizedReg);
|
||||
return {
|
||||
key,
|
||||
departmentId,
|
||||
laneId,
|
||||
registration,
|
||||
vehicleTypeId,
|
||||
};
|
||||
};
|
||||
|
||||
const executeSelfServeFetch = async (request: SelfServeFetchRequest | null = createSelfServeFetchRequest()) => {
|
||||
if (!request || isSelfServeFetchUnmounted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (request.key === latestSuccessfulSelfServeFetchKey || request.key === inFlightSelfServeFetchKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const requestSequence = ++selfServeFetchSequence;
|
||||
inFlightSelfServeFetchKey = request.key;
|
||||
|
||||
try {
|
||||
const result = await fetchSelfServeDataInternal(
|
||||
request.departmentId,
|
||||
request.vehicleTypeId,
|
||||
request.laneId,
|
||||
request.registration
|
||||
);
|
||||
|
||||
if (!isSelfServeFetchUnmounted && requestSequence === selfServeFetchSequence) {
|
||||
latestSuccessfulSelfServeFetchKey = request.key;
|
||||
}
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
if (inFlightSelfServeFetchKey === request.key) {
|
||||
inFlightSelfServeFetchKey = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const fetchSelfServeData = async (options: { immediate?: boolean } = {}) => {
|
||||
const request = createSelfServeFetchRequest();
|
||||
|
||||
if (options.immediate) {
|
||||
clearScheduledSelfServeFetch(null);
|
||||
return executeSelfServeFetch(request);
|
||||
}
|
||||
|
||||
if (!request || isSelfServeFetchUnmounted) {
|
||||
clearScheduledSelfServeFetch(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
selfServeFetchResolvers.push(resolve);
|
||||
|
||||
if (selfServeFetchDebounceTimer) {
|
||||
window.clearTimeout(selfServeFetchDebounceTimer);
|
||||
}
|
||||
|
||||
selfServeFetchDebounceTimer = window.setTimeout(async () => {
|
||||
selfServeFetchDebounceTimer = null;
|
||||
const latestRequest = createSelfServeFetchRequest();
|
||||
const result = await executeSelfServeFetch(latestRequest);
|
||||
resolvePendingSelfServeFetches(result);
|
||||
}, SELF_SERVE_FETCH_DEBOUNCE_MS);
|
||||
});
|
||||
};
|
||||
|
||||
const SERVER_ACTIVE_WASH_ENDPOINT = "/modules/self-serve/lane/wash/my-active-wash";
|
||||
@@ -1023,7 +1129,7 @@ const retrySelfServeData = async () => {
|
||||
|
||||
isSelfServeRetrying.value = true;
|
||||
try {
|
||||
await fetchSelfServeData();
|
||||
await fetchSelfServeData({ immediate: true });
|
||||
} finally {
|
||||
isSelfServeRetrying.value = false;
|
||||
}
|
||||
@@ -1093,7 +1199,7 @@ const onVehicleStepNext = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
await fetchSelfServeData();
|
||||
await fetchSelfServeData({ immediate: true });
|
||||
await nextTick();
|
||||
markQuestionReviewRequired();
|
||||
currentStep.value = visibleQuestions.value.length > 0 ? steps.QUESTIONS : steps.SELECT_LANE;
|
||||
@@ -1216,6 +1322,9 @@ onUnmounted(() => {
|
||||
unregisterBeforeUnload.value();
|
||||
}
|
||||
|
||||
isSelfServeFetchUnmounted = true;
|
||||
clearScheduledSelfServeFetch(null);
|
||||
selfServeFetchSequence += 1;
|
||||
stopAutoRefresh();
|
||||
stopActiveWashRefresh();
|
||||
markDestroying();
|
||||
|
||||
@@ -419,6 +419,8 @@ describe("MyWashStart", () => {
|
||||
});
|
||||
|
||||
it("wires child updates back into the self-serve runtime", async () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const wrapper = mountWithApp(MyWashStart, {
|
||||
global: {
|
||||
stubs: stubComponents,
|
||||
@@ -430,6 +432,7 @@ describe("MyWashStart", () => {
|
||||
await wrapper.get('[data-testid="emit-registration"]').trigger("click");
|
||||
await wrapper.get('[data-testid="emit-vehicle-type"]').trigger("click");
|
||||
await nextTick();
|
||||
await vi.advanceTimersByTimeAsync(300);
|
||||
await flushPromises();
|
||||
|
||||
expect(mocks.fetchSelfServeDataInternal).toHaveBeenCalledWith(6, 2, 7, "AB12345");
|
||||
@@ -449,6 +452,34 @@ describe("MyWashStart", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("debounces registration, lane, and vehicle type updates into one self-serve fetch", async () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const wrapper = mountWithApp(MyWashStart, {
|
||||
global: {
|
||||
stubs: stubComponents,
|
||||
},
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
mocks.fetchSelfServeDataInternal.mockClear();
|
||||
|
||||
await wrapper.get('[data-testid="emit-registration"]').trigger("click");
|
||||
await wrapper.get('[data-testid="emit-lane"]').trigger("click");
|
||||
await wrapper.get('[data-testid="emit-vehicle-type"]').trigger("click");
|
||||
await nextTick();
|
||||
|
||||
await vi.advanceTimersByTimeAsync(299);
|
||||
await flushPromises();
|
||||
expect(mocks.fetchSelfServeDataInternal).not.toHaveBeenCalled();
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1);
|
||||
await flushPromises();
|
||||
|
||||
expect(mocks.fetchSelfServeDataInternal).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.fetchSelfServeDataInternal).toHaveBeenCalledWith(6, 2, 7, "AB12345");
|
||||
});
|
||||
|
||||
it("hides machine tasks while manual wash is selected", async () => {
|
||||
mocks.activeTasks.value = [
|
||||
{ id: 31, task: "Machine checklist", services: ["MACHINE"] },
|
||||
@@ -597,6 +628,8 @@ describe("MyWashStart", () => {
|
||||
});
|
||||
|
||||
it("falls back to available lane when restored lane is stale for the selected department", async () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
mocks.restoredProgressPayload = {
|
||||
washInProgress: false,
|
||||
washLaneId: null,
|
||||
@@ -619,6 +652,8 @@ describe("MyWashStart", () => {
|
||||
},
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
await vi.advanceTimersByTimeAsync(300);
|
||||
await flushPromises();
|
||||
|
||||
expect(mocks.fetchSelfServeDataInternal).toHaveBeenCalledWith(6, 2, 7, "AB12345");
|
||||
|
||||
Reference in New Issue
Block a user