Enhance POS booking flow logic and tests:

- Added support for trailers (`reg_2`) in booking selection and vehicle matching logic.
- Implemented logic to prioritize bookings from registered vehicles and trailers.
- Enhanced booking dropdown with date labels and validation for booked vehicles.
- Improved e2e tests to cover booking date, trailer registration, and vehicle-search scenarios.
- Refactored Vue components for consistency in booking handling and dropdown interaction.
This commit is contained in:
Jeppe Bundgaard
2026-04-13 17:56:19 +02:00
parent 8f3ec2cf09
commit ce049d9196
5 changed files with 476 additions and 175 deletions
+148
View File
@@ -331,6 +331,18 @@ async function mockPosApi(page, fixture) {
return;
}
if (pathname.endsWith("/vehicles/search") && method === "GET") {
const search = (parsedUrl.searchParams.get("search") || "").toUpperCase();
const matches = fixture.vehicles.filter((vehicle) => vehicle.reg.includes(search));
await route.fulfill(
json({
success: true,
data: matches,
})
);
return;
}
if (pathname.endsWith("/vehicles") && method === "GET") {
const search = (parsedUrl.searchParams.get("search") || "").toUpperCase();
const matches = fixture.vehicles.filter((vehicle) => vehicle.reg.includes(search));
@@ -805,6 +817,22 @@ test.describe("POS flow", () => {
expect(consoleProblems.join("\n")).not.toContain("reg_1");
});
test("desktop hides the Sidste vask section when the selected vehicle has no last_order_id", async ({
page,
}, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop", "Desktop POS step 1 is validated on chromium-desktop.");
const fixture = createPosFixture();
await setupDesktopPosPage(page, fixture, { token: "pos-no-last-wash-token" });
await page.locator("#reg_1").fill("AB12345");
await expect(page.getByTestId("pos-step-1").getByText("Pleno Logistics").first()).toBeVisible({ timeout: 10_000 });
await expect(page.getByText(/Sidste vask/i)).toHaveCount(0);
await expect(page.getByRole("button", { name: /Kopier sidste vask/i })).toHaveCount(0);
});
test("desktop flow creates transaction, renders cart, and reaches completion step", async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop", "Desktop POS flow is validated on chromium-desktop.");
@@ -889,6 +917,50 @@ test.describe("POS flow", () => {
expect(consoleProblems.join("\n")).not.toContain("Extraneous non-props attributes");
});
test("desktop marks booked vehicles in the dropdown when the vehicle payload already carries a booking", async ({
page,
}, testInfo) => {
test.skip(
testInfo.project.name !== "chromium-desktop",
"Desktop POS booking flow is validated on chromium-desktop."
);
const fixture = createPosFixture();
fixture.vehicles = [
{
...fixture.vehicles[0],
reg: "EC21235",
booking_id: 8991,
status: "booked",
},
{
...fixture.vehicles[0],
id: 7002,
reg: "EC21234",
booking_id: 8992,
status: "booked",
},
];
fixture.orderBookings = [
buildOrderBooking(8991, {
reg_1: "EC21235",
datetime: "2026-01-03T07:00:00.000Z",
}),
buildOrderBooking(8992, {
reg_1: "EC21234",
datetime: "2026-01-04T09:00:00.000Z",
}),
];
await setupDesktopPosPage(page, fixture, { token: "pos-desktop-booked-dropdown" });
await page.locator("#reg_1").fill("EC212");
await expect(page.getByTestId("desktop-booked-icon-7001")).toBeVisible({ timeout: 10_000 });
await expect(page.getByTestId("desktop-booked-icon-7002")).toBeVisible({ timeout: 10_000 });
await expect(page.getByTestId("desktop-booked-date-7001")).toContainText(/\d/, { timeout: 10_000 });
await expect(page.getByTestId("desktop-booked-date-7002")).toContainText(/\d/, { timeout: 10_000 });
});
test("desktop auto-applies a single matching order booking and completes that booking", async ({
page,
}, testInfo) => {
@@ -902,6 +974,7 @@ test.describe("POS flow", () => {
buildOrderBooking(8101, {
reference: "SINGLE-BOOKING-REF",
reference_number: "SINGLE-BOOKING-REF",
reg_2: "TRAILER-1",
notes: "Single desktop booking",
note: "Single desktop booking",
items: [
@@ -920,6 +993,7 @@ test.describe("POS flow", () => {
const activeBookingSelector = page.locator('[data-testid="default-object-selector"].is-active');
await page.locator("#reg_1").fill("AB12345");
await expect(activeBookingSelector).toHaveCount(0);
await expect(page.locator("#reg_2")).toHaveValue("TRAILER-1");
await page.locator('[data-testid="pos-next-step"]:visible').click();
await expect(page.getByTestId("pos-step-2")).toBeVisible({ timeout: 10_000 });
@@ -927,6 +1001,7 @@ test.describe("POS flow", () => {
await expect
.poll(() => fixture.ordersById[9300]?.reference || null, { timeout: 10_000 })
.toBe("SINGLE-BOOKING-REF");
await expect.poll(() => fixture.ordersById[9300]?.reg_2 || null, { timeout: 10_000 }).toBe("TRAILER-1");
await expect
.poll(() => (fixture.orderItemsByOrderId[9300] || []).map((item) => Number(item.product_id)), { timeout: 10_000 })
.toEqual([53, 63]);
@@ -960,6 +1035,7 @@ test.describe("POS flow", () => {
datetime: "2026-01-01T07:00:00.000Z",
reference: "BOOKING-A-REF",
reference_number: "BOOKING-A-REF",
reg_2: "TRAILER-A",
items: [
{ id: 53, name: "Tankvogn med hænger", price: 599, quantity: 1 },
{ id: 63, name: "Dolly", price: 275, quantity: 1 },
@@ -973,6 +1049,7 @@ test.describe("POS flow", () => {
datetime: "2026-01-01T09:00:00.000Z",
reference: "BOOKING-B-REF",
reference_number: "BOOKING-B-REF",
reg_2: "TRAILER-B",
items: [{ id: 63, name: "Dolly", price: 275, quantity: 1 }],
parsed_services: {
string: "Dolly",
@@ -995,10 +1072,12 @@ test.describe("POS flow", () => {
await activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8102").click();
await expect(page.locator("#reference")).toHaveValue("BOOKING-A-REF");
await expect(page.locator("#reg_2")).toHaveValue("TRAILER-A");
await page.locator('[data-testid="pos-next-step"]:visible').click();
await expect(page.getByTestId("pos-step-2")).toBeVisible({ timeout: 10_000 });
await expect.poll(() => fixture.ordersById[9300]?.reg_2 || null, { timeout: 10_000 }).toBe("TRAILER-A");
await expect
.poll(() => (fixture.orderItemsByOrderId[9300] || []).map((item) => Number(item.product_id)), { timeout: 10_000 })
.toEqual([53, 63]);
@@ -1018,6 +1097,75 @@ test.describe("POS flow", () => {
expect(fixture.completedBookingIds).not.toContain(8103);
});
test("desktop opens the booking chooser for a booked plate even without a vehicle match", async ({
page,
}, testInfo) => {
test.skip(
testInfo.project.name !== "chromium-desktop",
"Desktop POS booking flow is validated on chromium-desktop."
);
const fixture = createPosFixture();
fixture.orderBookings = [
buildOrderBooking(8111, {
reg_1: "BOOKONLY1",
datetime: "2026-01-01T07:00:00.000Z",
reference: "BOOK-ONLY-A",
reference_number: "BOOK-ONLY-A",
items: [
{ id: 53, name: "Tankvogn med hænger", price: 599, quantity: 1 },
{ id: 63, name: "Dolly", price: 275, quantity: 1 },
],
parsed_services: {
string: "Tankvogn med hænger, Dolly",
array: ["Tankvogn med hænger", "Dolly"],
},
}),
buildOrderBooking(8112, {
reg_1: "BOOKONLY1",
datetime: "2026-01-01T09:00:00.000Z",
reference: "BOOK-ONLY-B",
reference_number: "BOOK-ONLY-B",
items: [{ id: 63, name: "Dolly", price: 275, quantity: 1 }],
parsed_services: {
string: "Dolly",
array: ["Dolly"],
},
}),
];
await setupDesktopPosPage(page, fixture, { token: "pos-desktop-booking-only" });
const activeBookingSelector = page.locator('[data-testid="default-object-selector"].is-active');
await page.locator("#reg_1").fill("BOOKONLY1");
await expect(activeBookingSelector).toBeVisible({ timeout: 10_000 });
await activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8111").click();
await expect(page.locator("#reference")).toHaveValue("BOOK-ONLY-A");
await page.locator('[data-testid="pos-next-step"]:visible').click();
await expect(page.getByTestId("pos-step-2")).toBeVisible({ timeout: 10_000 });
await expect
.poll(() => (fixture.orderItemsByOrderId[9300] || []).map((item) => Number(item.product_id)), { timeout: 10_000 })
.toEqual([53, 63]);
await page.locator('[data-testid="pos-next-step"]:visible').click();
await expect(page.getByTestId("pos-step-3")).toBeVisible({ timeout: 10_000 });
await page.locator('[data-testid="pos-next-step"]:visible').click();
await expect.poll(() => fixture.ordersById[9300]?.booking_id || null, { timeout: 10_000 }).toBe(8111);
await expect
.poll(
() =>
fixture.bookingOrderAssignments.some(
(entry) => Number(entry?.id) === 8111 && Number(entry?.order_id) === 9300
),
{ timeout: 10_000 }
)
.toBe(true);
await expect.poll(() => fixture.completedBookingIds, { timeout: 10_000 }).toContain(8111);
expect(fixture.completedBookingIds).not.toContain(8112);
});
test("desktop continue without booking skips hydration and completion requests", async ({ page }, testInfo) => {
test.skip(
testInfo.project.name !== "chromium-desktop",