443 lines
14 KiB
JavaScript
443 lines
14 KiB
JavaScript
// @vitest-environment jsdom
|
|
import { defineComponent } from "vue";
|
|
import { flushPromises } from "@vue/test-utils";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { mountWithApp } from "./helpers/mountWithApp.js";
|
|
|
|
function buildContext(overrides = {}) {
|
|
return {
|
|
reg1: "",
|
|
reg2: "",
|
|
bookingMatches: [],
|
|
selectedBookingId: null,
|
|
skippedBooking: false,
|
|
requiresBookingSelection: false,
|
|
bookingResolution: "none",
|
|
committed: false,
|
|
source: "initial",
|
|
...overrides,
|
|
bookingMatches: Array.isArray(overrides.bookingMatches)
|
|
? overrides.bookingMatches.map((booking) => ({ ...booking }))
|
|
: [],
|
|
};
|
|
}
|
|
|
|
const posProcessState = vi.hoisted(() => ({
|
|
customerName: null,
|
|
orderId: null,
|
|
customerId: null,
|
|
step: null,
|
|
departmentId: null,
|
|
currentStep: null,
|
|
nextStep: vi.fn(),
|
|
clearCache: vi.fn(),
|
|
copyLastWashItemsToCurrentOrder: vi.fn(),
|
|
preflightHandler: null,
|
|
}));
|
|
|
|
const sessionState = vi.hoisted(() => ({
|
|
request: vi.fn(),
|
|
redirectToDepartment: vi.fn(),
|
|
}));
|
|
|
|
const selectVehicleFormState = vi.hoisted(() => ({
|
|
commitContext: null,
|
|
finalizedContext: null,
|
|
bookingSelectionContext: null,
|
|
skipBookingContext: null,
|
|
refreshContext: null,
|
|
finalizeDesktopStep1Context: vi.fn(),
|
|
applyBookingChoice: vi.fn(),
|
|
skipBookingChoice: vi.fn(),
|
|
refreshOrderBookingMatches: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@/components/shop/POSDepartmentProcess.vue", async () => {
|
|
const { ref } = await import("vue");
|
|
|
|
posProcessState.customerName = ref("");
|
|
posProcessState.orderId = ref(null);
|
|
posProcessState.customerId = ref(null);
|
|
posProcessState.step = ref(1);
|
|
posProcessState.departmentId = ref(12);
|
|
posProcessState.currentStep = ref(1);
|
|
|
|
return {
|
|
customer_name: posProcessState.customerName,
|
|
isCustomerSelected: () => Boolean(posProcessState.customerName.value),
|
|
department_id: posProcessState.departmentId,
|
|
order_id: posProcessState.orderId,
|
|
customer_id: posProcessState.customerId,
|
|
step: posProcessState.step,
|
|
getDepartment: () => 12,
|
|
getCurrentStep: () => posProcessState.currentStep.value,
|
|
clearCache: posProcessState.clearCache,
|
|
nextStep: posProcessState.nextStep,
|
|
copyLastWashItemsToCurrentOrder: posProcessState.copyLastWashItemsToCurrentOrder,
|
|
setDesktopStep1PreflightHandler: (handler = null) => {
|
|
posProcessState.preflightHandler = handler;
|
|
},
|
|
clearDesktopStep1PreflightHandler: (handler = null) => {
|
|
if (handler === null || posProcessState.preflightHandler === handler) {
|
|
posProcessState.preflightHandler = null;
|
|
}
|
|
},
|
|
};
|
|
});
|
|
|
|
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
|
SessionUser: {
|
|
request: sessionState.request,
|
|
functions: {
|
|
redirectTo: {
|
|
department: sessionState.redirectToDepartment,
|
|
},
|
|
},
|
|
objects: {
|
|
orders: {
|
|
meta: {
|
|
endpoint: "/orders",
|
|
},
|
|
columns: {
|
|
reg_1: {
|
|
label: "Number plate",
|
|
},
|
|
reference: {
|
|
label: "Reference",
|
|
},
|
|
},
|
|
},
|
|
global: {
|
|
language: {
|
|
possible_duplicates: "Possible duplicates",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
vi.mock("@/views/dashboards/departmentDashboard/modules/Pos/posRouteState.js", () => ({
|
|
parsePosRouteSearch: vi.fn(() => ({
|
|
orderId: null,
|
|
customerId: null,
|
|
step: null,
|
|
})),
|
|
}));
|
|
|
|
vi.mock("@/components/displays/department/pos/utils/orderBookingDisplay.js", () => ({
|
|
getOrderBookingReferenceValue: vi.fn((booking) => booking?.reference || booking?.reference_number || ""),
|
|
getOrderBookingServiceText: vi.fn(() => "Service"),
|
|
}));
|
|
|
|
import PosDepartmentStep1 from "@/components/displays/department/pos/steps/PosDepartmentStep1.vue";
|
|
|
|
const SlotStub = defineComponent({
|
|
template: "<div><slot /></div>",
|
|
});
|
|
|
|
const ElementTabsBoxStub = defineComponent({
|
|
template: `
|
|
<div>
|
|
<slot />
|
|
<slot name="customer" />
|
|
<slot name="license_plates" />
|
|
</div>
|
|
`,
|
|
});
|
|
|
|
const NextStepErrorStub = defineComponent({
|
|
template: '<div data-testid="pos-next-step-error-stub"></div>',
|
|
});
|
|
|
|
const SelectVehicleFormPOSStub = defineComponent({
|
|
emits: ["update:desktopStep1Context", "commit:desktopStep1"],
|
|
setup(_, { emit, expose }) {
|
|
const finalizeDesktopStep1Context = (...args) => selectVehicleFormState.finalizeDesktopStep1Context(...args);
|
|
const applyBookingChoice = (...args) => selectVehicleFormState.applyBookingChoice(...args);
|
|
const skipBookingChoice = (...args) => selectVehicleFormState.skipBookingChoice(...args);
|
|
const refreshOrderBookingMatches = (...args) => selectVehicleFormState.refreshOrderBookingMatches(...args);
|
|
|
|
expose({
|
|
finalizeDesktopStep1Context,
|
|
applyBookingChoice,
|
|
skipBookingChoice,
|
|
refreshOrderBookingMatches,
|
|
});
|
|
|
|
return {
|
|
emitCommit() {
|
|
emit("commit:desktopStep1", buildContext(selectVehicleFormState.commitContext));
|
|
},
|
|
};
|
|
},
|
|
template: `
|
|
<div>
|
|
<button type="button" data-testid="emit-step1-commit" @click="emitCommit">Commit</button>
|
|
</div>
|
|
`,
|
|
});
|
|
|
|
const PosDesktopOrderBookingSelectorModalStub = defineComponent({
|
|
props: {
|
|
isActive: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
bookings: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
},
|
|
emits: ["select", "skip", "refresh-bookings"],
|
|
methods: {
|
|
emitSelect() {
|
|
this.$emit("select", this.bookings[0] || null);
|
|
},
|
|
},
|
|
template: `
|
|
<div v-if="isActive" data-testid="pos-desktop-order-booking-modal">
|
|
<button type="button" data-testid="pos-desktop-order-booking-use-first" @click="emitSelect">Select</button>
|
|
</div>
|
|
`,
|
|
});
|
|
|
|
function mountStepOne() {
|
|
return mountWithApp(PosDepartmentStep1, {
|
|
messages: {
|
|
en: {
|
|
common: {
|
|
order: "Order",
|
|
},
|
|
admin: {
|
|
pos: {
|
|
license_plates: "License plates",
|
|
customer: "Customer",
|
|
not_found: "Not found",
|
|
order: "Order",
|
|
show_details: "Show details",
|
|
show_order_details: "Show order details",
|
|
hide_order_details: "Hide order details",
|
|
duplicate_order_warning: "Potential duplicates found.",
|
|
warning: "Warning",
|
|
order_booking_selector: {
|
|
title: "Select booking",
|
|
help_text: "Choose booking",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
global: {
|
|
stubs: {
|
|
WhiteBox: SlotStub,
|
|
ButtonsBox: SlotStub,
|
|
ElementTabsBox: ElementTabsBoxStub,
|
|
PosSelectedCustomer: SlotStub,
|
|
PosLastScannedLicensePlatesV2: SlotStub,
|
|
NextStepError: NextStepErrorStub,
|
|
NextStep: defineComponent({
|
|
template: '<button type="button" data-testid="pos-next-step-stub">Next</button>',
|
|
}),
|
|
Cancel: defineComponent({
|
|
template: '<button type="button" data-testid="pos-cancel-stub">Cancel</button>',
|
|
}),
|
|
SelectVehicleFormPOS: SelectVehicleFormPOSStub,
|
|
PosDesktopOrderBookingSelectorModal: PosDesktopOrderBookingSelectorModalStub,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
describe("PosDepartmentStep1 duplicate warning", () => {
|
|
beforeEach(() => {
|
|
posProcessState.customerName.value = "";
|
|
posProcessState.orderId.value = null;
|
|
posProcessState.customerId.value = null;
|
|
posProcessState.step.value = 1;
|
|
posProcessState.departmentId.value = 12;
|
|
posProcessState.currentStep.value = 1;
|
|
posProcessState.nextStep.mockReset();
|
|
posProcessState.clearCache.mockReset();
|
|
posProcessState.copyLastWashItemsToCurrentOrder.mockReset();
|
|
posProcessState.preflightHandler = null;
|
|
|
|
sessionState.request.mockReset();
|
|
sessionState.redirectToDepartment.mockReset();
|
|
|
|
selectVehicleFormState.commitContext = buildContext({
|
|
reg1: "AB12345",
|
|
committed: true,
|
|
source: "commit",
|
|
});
|
|
selectVehicleFormState.finalizedContext = buildContext({
|
|
reg1: "AB12345",
|
|
committed: true,
|
|
source: "commit",
|
|
});
|
|
selectVehicleFormState.bookingSelectionContext = buildContext({
|
|
reg1: "AB12345",
|
|
committed: true,
|
|
selectedBookingId: 8130,
|
|
bookingResolution: "selected",
|
|
source: "booking_selection",
|
|
});
|
|
selectVehicleFormState.skipBookingContext = buildContext({
|
|
reg1: "AB12345",
|
|
committed: true,
|
|
skippedBooking: true,
|
|
bookingResolution: "skipped",
|
|
source: "booking_skip",
|
|
});
|
|
selectVehicleFormState.refreshContext = buildContext({
|
|
reg1: "AB12345",
|
|
committed: false,
|
|
source: "booking_refresh",
|
|
});
|
|
|
|
selectVehicleFormState.finalizeDesktopStep1Context.mockReset();
|
|
selectVehicleFormState.finalizeDesktopStep1Context.mockImplementation(async () =>
|
|
buildContext(selectVehicleFormState.finalizedContext)
|
|
);
|
|
selectVehicleFormState.applyBookingChoice.mockReset();
|
|
selectVehicleFormState.applyBookingChoice.mockImplementation(async () =>
|
|
buildContext(selectVehicleFormState.bookingSelectionContext)
|
|
);
|
|
selectVehicleFormState.skipBookingChoice.mockReset();
|
|
selectVehicleFormState.skipBookingChoice.mockImplementation(async () =>
|
|
buildContext(selectVehicleFormState.skipBookingContext)
|
|
);
|
|
selectVehicleFormState.refreshOrderBookingMatches.mockReset();
|
|
selectVehicleFormState.refreshOrderBookingMatches.mockImplementation(async () =>
|
|
buildContext(selectVehicleFormState.refreshContext)
|
|
);
|
|
});
|
|
|
|
it("renders the inline warning and expands duplicate details after a committed duplicate match", async () => {
|
|
sessionState.request.mockResolvedValue({
|
|
data: {
|
|
data: [
|
|
{
|
|
id: 9206,
|
|
reg_1: "AB12345",
|
|
reference: "DUP-DETAILS",
|
|
customer_id: 12345679,
|
|
created_at: "2026-04-14T10:00:00.000Z",
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
const wrapper = mountStepOne();
|
|
|
|
await wrapper.get('[data-testid="emit-step1-commit"]').trigger("click");
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('[data-testid="pos-desktop-duplicate-warning-inline"]').text()).toContain(
|
|
"Potential duplicates found."
|
|
);
|
|
expect(wrapper.find('[data-testid="pos-desktop-duplicate-warning-details-inline"]').exists()).toBe(false);
|
|
|
|
await wrapper.get('[data-testid="pos-desktop-duplicate-warning-toggle-details"]').trigger("click");
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('[data-testid="pos-desktop-duplicate-warning-details-inline"]').text()).toContain("Order #9206");
|
|
|
|
await wrapper.get('[data-testid="pos-desktop-duplicate-order-open-9206"]').trigger("click");
|
|
expect(sessionState.redirectToDepartment).toHaveBeenCalledWith(12, "/modules/pos/orders/9206");
|
|
});
|
|
|
|
it("keeps booking selection ahead of the inline duplicate warning until booking resolution completes", async () => {
|
|
sessionState.request.mockResolvedValue({
|
|
data: {
|
|
data: [
|
|
{
|
|
id: 9207,
|
|
reg_1: "AB12345",
|
|
reference: "DUP-BOOKING",
|
|
customer_id: 12345679,
|
|
created_at: "2026-04-14T10:00:00.000Z",
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
const booking = {
|
|
id: 8130,
|
|
customer_name: "Pleno Logistics",
|
|
reg_1: "AB12345",
|
|
reference: "BOOKING-REF",
|
|
};
|
|
|
|
selectVehicleFormState.commitContext = buildContext({
|
|
reg1: "AB12345",
|
|
committed: true,
|
|
requiresBookingSelection: true,
|
|
bookingResolution: "selection_required",
|
|
bookingMatches: [booking],
|
|
source: "commit",
|
|
});
|
|
selectVehicleFormState.finalizedContext = buildContext(selectVehicleFormState.commitContext);
|
|
selectVehicleFormState.bookingSelectionContext = buildContext({
|
|
reg1: "AB12345",
|
|
committed: true,
|
|
selectedBookingId: booking.id,
|
|
bookingResolution: "selected",
|
|
bookingMatches: [booking],
|
|
source: "booking_selection",
|
|
});
|
|
selectVehicleFormState.finalizeDesktopStep1Context.mockReset();
|
|
selectVehicleFormState.finalizeDesktopStep1Context
|
|
.mockResolvedValueOnce(buildContext(selectVehicleFormState.commitContext))
|
|
.mockResolvedValue(buildContext(selectVehicleFormState.bookingSelectionContext));
|
|
|
|
const wrapper = mountStepOne();
|
|
|
|
await wrapper.get('[data-testid="emit-step1-commit"]').trigger("click");
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('[data-testid="pos-desktop-order-booking-modal"]').exists()).toBe(true);
|
|
expect(wrapper.find('[data-testid="pos-desktop-duplicate-warning-inline"]').exists()).toBe(false);
|
|
|
|
await wrapper.get('[data-testid="pos-desktop-order-booking-use-first"]').trigger("click");
|
|
await flushPromises();
|
|
|
|
expect(wrapper.find('[data-testid="pos-desktop-order-booking-modal"]').exists()).toBe(false);
|
|
expect(wrapper.get('[data-testid="pos-desktop-duplicate-warning-inline"]').exists()).toBe(true);
|
|
expect(selectVehicleFormState.applyBookingChoice).toHaveBeenCalledWith(booking, {
|
|
source: "booking_selection",
|
|
committed: true,
|
|
});
|
|
});
|
|
|
|
it("does not block desktop preflight when duplicates are found", async () => {
|
|
sessionState.request.mockResolvedValue({
|
|
data: {
|
|
data: [
|
|
{
|
|
id: 9208,
|
|
reg_1: "AB12345",
|
|
reference: "DUP-PREFLIGHT",
|
|
customer_id: 12345679,
|
|
created_at: "2026-04-14T10:00:00.000Z",
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
selectVehicleFormState.finalizedContext = buildContext({
|
|
reg1: "AB12345",
|
|
committed: true,
|
|
source: "next",
|
|
});
|
|
|
|
const wrapper = mountStepOne();
|
|
|
|
await flushPromises();
|
|
|
|
await expect(posProcessState.preflightHandler({ reason: "next" })).resolves.toBe(true);
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('[data-testid="pos-desktop-duplicate-warning-inline"]').exists()).toBe(true);
|
|
});
|
|
});
|