import { expect, test } from "@playwright/test"; import { mockApi, primeMockSession } from "./support/network.js"; const API_HOST = /https?:\/\/(?:api\.truckwash\.io(?::\d+)?\/.*|localhost(?::\d+)?\/api\/.*|127\.0\.0\.1(?::\d+)?\/api\/.*)/i; function json(body, status = 200) { return { status, contentType: "application/json", body: JSON.stringify(body), }; } function toPositiveInteger(value) { const parsed = Number.parseInt(String(value ?? ""), 10); return Number.isInteger(parsed) && parsed > 0 ? parsed : null; } function toOrderBookingListEntry(booking, stripDetails = false) { if (!stripDetails || !booking || typeof booking !== "object") { return booking; } const summaryBooking = { ...booking }; delete summaryBooking.items; delete summaryBooking.parsed_services; return summaryBooking; } function suppressVueDevtoolsOverlay(page) { return page.addInitScript(() => { const style = document.createElement("style"); style.textContent = "#__vue-devtools-container__, .vue-devtools__anchor-btn, .vue-devtools__panel-content { display: none !important; visibility: hidden !important; pointer-events: none !important; }"; document.documentElement.appendChild(style); }); } async function primeSession(page, { token, permissions, sessionData = {} }) { await primeMockSession(page, { token }); } async function expectDesktopOrderItemsTable(scope) { await expect(scope.getByTestId("pos-order-panel-cart")).toBeVisible(); await expect(scope.getByTestId("pos-order-detail-items-table")).toBeVisible(); await expect(scope.getByTestId("pos-order-metadata-grid")).toBeVisible(); await expect(scope.getByTestId("pos-order-total")).toBeVisible(); await expect(scope.locator('[data-auto-excel-export-button="1"]')).toHaveCount(0); } function createPosFixture(overrides = {}) { const customer = { id: 1, customerNumber: 12345, name: "Pleno Logistics", address: "Demo Street 1", zip: "2630", city: "Taastrup", mobilePhone: "12345678", email: "pos-e2e@example.com", corporateIdentificationNumber: "12345678", barred: false, }; const products = [ { id: 53, name: "Tankvogn med hænger", description: "Material product used for completion flow", price: 599, subscription_allowed: true, category: 4, piktogram: "truck", apply_category_discount: true, requires_note: false, is_wash: true, display_in_booking_form: true, order_priority: 1, addons: [], }, { id: 63, name: "Dolly", description: "Support product", price: 275, subscription_allowed: true, category: 4, piktogram: "truck", apply_category_discount: true, requires_note: false, is_wash: true, display_in_booking_form: true, order_priority: 2, addons: [], }, ]; const ordersById = { 9201: { id: 9201, customer_id: customer.customerNumber, department_id: 1, reference: "REF-9201", notes: "", po: "", reg_1: "AB12345", reg_2: "", reg_3: "", invoice_collection_id: null, booking_id: null, completed_at: null, created_at: "2026-01-01T10:00:00.000Z", }, }; const orderItemsByOrderId = { 9201: [], }; const baseFixture = { customer, products, departmentCategories: [ { id: 11, department_id: 1, category: { id: 4, name: "Udvendig", meta: { products: [53, 63], }, }, }, ], vehicles: [ { id: 7001, reg: "AB12345", customer_id: customer.customerNumber, customer_name: customer.name, type: 53, status: "verified", barred: false, wash_subscription: false, addons: { enabled: 0, available: 0, list: [], }, reference: "REF-AB12345", last_order_id: null, }, ], customerSuggestionsByReg: {}, unknownVehicles: [], orderBookings: [], orderBookingsDelayMs: 0, duplicateOrders: [], ordersById, orderItemsByOrderId, markCompletedOrderIds: [], completedBookingIds: [], bookingOrderAssignments: [], readers: [ { id: "reader_online_1", label: "Mobile Reader", status: "online", action: null, }, ], readersGet: 0, nextOrderId: 9300, nextOrderItemId: 9800, }; return { ...baseFixture, ...overrides, customer: overrides.customer || baseFixture.customer, products: overrides.products || baseFixture.products, departmentCategories: overrides.departmentCategories || baseFixture.departmentCategories, vehicles: overrides.vehicles || baseFixture.vehicles, customerSuggestionsByReg: { ...baseFixture.customerSuggestionsByReg, ...(overrides.customerSuggestionsByReg || {}), }, ordersById: { ...baseFixture.ordersById, ...(overrides.ordersById || {}), }, orderItemsByOrderId: { ...baseFixture.orderItemsByOrderId, ...(overrides.orderItemsByOrderId || {}), }, }; } function buildOrderItem(product, body, id) { const quantity = Number(body.quantity || 1); return { id, order_id: Number(body.order_id), product_id: product.id, product, quantity, notes: body.notes || "", reference: body.reference || "", related_item_id: body.related_item_id ?? null, price: Number(body.price ?? product.price ?? 0), }; } function buildOrderBooking(id, overrides = {}) { return { id, customer_number: 12345, customer_name: "Pleno Logistics", department: 1, datetime: "2026-01-01T08:00:00.000Z", reg_1: "AB12345", reg_2: "", reg_3: "", reference: `BOOKING-${id}`, reference_number: `BOOKING-${id}`, notes: `Booking note ${id}`, note: `Booking note ${id}`, po: `PO-${id}`, status: "pending", order_id: null, wash_type: "Tankvogn med hænger", parsed_services: { string: "Tankvogn med hænger", array: ["Tankvogn med hænger"], }, items: [ { id: 53, name: "Tankvogn med hænger", price: 599, quantity: 1, }, ], ...overrides, }; } function buildTodayTimestamp(time = "10:00:00.000Z") { const today = new Date().toISOString().split("T")[0]; return `${today}T${time}`; } async function mockPosApi(page, fixture) { await page.route(API_HOST, async (route) => { const request = route.request(); const url = request.url(); const parsedUrl = new URL(url); const pathname = parsedUrl.pathname; const method = request.method(); if (pathname.endsWith("/departments") && method === "GET") { await route.fulfill( json({ success: true, data: [ { id: 1, name: "Taastrup", }, ], }) ); return; } if (pathname.endsWith("/departments/categories") && method === "GET") { await route.fulfill( json({ success: true, data: fixture.departmentCategories, }) ); return; } if (pathname.endsWith("/bookings") && method === "GET") { await route.fulfill( json({ success: true, data: [], }) ); return; } if (pathname.endsWith("/order-bookings") && method === "GET") { const bookingId = Number(parsedUrl.searchParams.get("id") || 0); const filters = String(parsedUrl.searchParams.get("filters") || ""); let bookings = Array.isArray(fixture.orderBookings) ? [...fixture.orderBookings] : []; if (Number(fixture.orderBookingsDelayMs || 0) > 0) { await new Promise((resolve) => setTimeout(resolve, Number(fixture.orderBookingsDelayMs))); } if (filters.includes("order_id:null") || filters.includes("order_id:is null")) { bookings = bookings.filter((booking) => booking.order_id === null || booking.order_id === undefined); } if (filters.includes("department:")) { const departmentFilter = Number(filters.split("department:")[1]?.split(",")[0] || 0); if (departmentFilter > 0) { bookings = bookings.filter((booking) => Number(booking.department) === departmentFilter); } } if (filters.includes("reg_1:")) { const reg1Filter = String(filters.split("reg_1:")[1]?.split(",")[0] || "").toUpperCase(); if (reg1Filter) { bookings = bookings.filter((booking) => String(booking.reg_1 || "").toUpperCase() === reg1Filter); } } if (filters.includes("reg_2:")) { const reg2Filter = String(filters.split("reg_2:")[1]?.split(",")[0] || "").toUpperCase(); if (reg2Filter) { bookings = bookings.filter((booking) => String(booking.reg_2 || "").toUpperCase() === reg2Filter); } } if (filters.includes("order_id:null") || filters.includes("order_id:is null")) { bookings = bookings.filter((booking) => String(booking.status || "pending").toLowerCase() !== "completed"); } if (bookingId > 0) { await route.fulfill( json({ success: true, data: bookings.find((booking) => Number(booking.id) === bookingId) || null, }) ); return; } await route.fulfill( json({ success: true, data: bookings.map((booking) => toOrderBookingListEntry(booking, fixture.orderBookingListStripsDetails === true) ), }) ); return; } if (/\/order-bookings\/\d+$/.test(pathname) && method === "GET") { const bookingId = Number(pathname.split("/").pop()); await route.fulfill( json({ success: true, data: (fixture.orderBookings || []).find((booking) => Number(booking.id) === bookingId) || null, }) ); return; } if (pathname.endsWith("/order-bookings") && method === "PUT") { const body = request.postDataJSON?.() || {}; const bookingId = Number(body.id); const bookingIndex = (fixture.orderBookings || []).findIndex((booking) => Number(booking.id) === bookingId); if (bookingIndex >= 0) { fixture.orderBookings[bookingIndex] = { ...fixture.orderBookings[bookingIndex], order_id: body.order_id ?? body.value ?? null, }; fixture.bookingOrderAssignments.push({ id: bookingId, order_id: fixture.orderBookings[bookingIndex].order_id, }); } await route.fulfill( json({ success: true, data: bookingIndex >= 0 ? fixture.orderBookings[bookingIndex] : null, }) ); return; } if (pathname.endsWith("/order-bookings/complete") && method === "POST") { const body = request.postDataJSON?.() || {}; const bookingId = Number(body.id); const bookingIndex = (fixture.orderBookings || []).findIndex((booking) => Number(booking.id) === bookingId); if (bookingIndex >= 0) { fixture.orderBookings[bookingIndex] = { ...fixture.orderBookings[bookingIndex], status: "completed", }; fixture.completedBookingIds.push(bookingId); if (typeof fixture.onCompleteOrderBooking === "function") { fixture.onCompleteOrderBooking(bookingId, fixture.orderBookings[bookingIndex], fixture); } } await route.fulfill( json({ success: true, data: bookingIndex >= 0 ? fixture.orderBookings[bookingIndex] : { id: bookingId, status: "completed" }, }) ); 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)); await route.fulfill( json({ success: true, data: matches, }) ); return; } if (pathname.endsWith("/department/vehicles/unknown-customer") && method === "GET") { await route.fulfill( json({ success: true, data: fixture.unknownVehicles, }) ); return; } if (pathname.endsWith("/department/vehicle/customer-suggestions") && method === "GET") { const reg1 = String(parsedUrl.searchParams.get("reg_1") || "").toUpperCase(); const suggestions = (fixture.customerSuggestionsByReg && Array.isArray(fixture.customerSuggestionsByReg[reg1]) ? fixture.customerSuggestionsByReg[reg1] : []) || []; await route.fulfill( json({ success: true, data: suggestions, }) ); return; } if (pathname.endsWith("/users/customer") && method === "GET") { await route.fulfill( json({ success: true, data: { customer_name: fixture.customer.name, economic_customer: fixture.customer, }, }) ); return; } if (pathname.endsWith("/customers") && method === "GET") { await route.fulfill( json({ success: true, data: [fixture.customer], meta: { pagination: { page: 1, limit: 10, total: 1, }, }, }) ); return; } if (pathname.endsWith("/customer/notes") && method === "GET") { await route.fulfill( json({ success: true, data: [], }) ); return; } if (pathname.endsWith("/customer/attributes") && method === "GET") { await route.fulfill( json({ success: true, data: [], }) ); return; } if (pathname.endsWith("/superuser/user/discounts") && method === "GET") { await route.fulfill( json({ success: true, data: [], }) ); return; } if (pathname.endsWith("/products") && method === "GET") { const id = parsedUrl.searchParams.get("id"); const category = parsedUrl.searchParams.get("category"); if (id) { const product = fixture.products.find((entry) => Number(entry.id) === Number(id)); await route.fulfill( json({ success: true, data: product || null, }) ); return; } const list = category ? fixture.products.filter((entry) => Number(entry.category) === Number(category)) : fixture.products; await route.fulfill( json({ success: true, data: list, }) ); return; } if (pathname.endsWith("/orders") && method === "GET") { const filters = String(parsedUrl.searchParams.get("filters") || ""); const regFilter = filters.includes("reg_1:") ? filters.split("reg_1:")[1]?.split(",")[0] || "" : ""; const departmentFilter = filters.includes("department_id:") ? Number(filters.split("department_id:")[1]?.split(",")[0] || 0) : 0; const createdFrom = filters.includes("created_at-date_from:") ? filters.split("created_at-date_from:")[1]?.split(",")[0] || null : null; const createdTo = filters.includes("created_at-date_to:") ? filters.split("created_at-date_to:")[1]?.split(",")[0] || null : null; let duplicateOrders = Array.isArray(fixture.duplicateOrders) ? [...fixture.duplicateOrders] : []; if (regFilter) { duplicateOrders = duplicateOrders.filter( (order) => String(order?.reg_1 || "").toUpperCase() === String(regFilter).toUpperCase() ); } if (departmentFilter > 0) { duplicateOrders = duplicateOrders.filter((order) => Number(order?.department_id) === departmentFilter); } if (createdFrom) { duplicateOrders = duplicateOrders.filter((order) => String(order?.created_at || "") >= createdFrom); } if (createdTo) { duplicateOrders = duplicateOrders.filter( (order) => String(order?.created_at || "") <= `${createdTo}T23:59:59.999Z` ); } await route.fulfill( json({ success: true, data: duplicateOrders, }) ); return; } if (pathname.endsWith("/orders") && method === "POST") { const body = request.postDataJSON?.() || {}; const orderId = fixture.nextOrderId++; fixture.ordersById[orderId] = { id: orderId, customer_id: Number(body.customer_id), department_id: Number(body.department_id), reference: body.reference || "", notes: body.notes || "", po: body.po || "", reg_1: body.reg_1 || "", reg_2: body.reg_2 || "", reg_3: body.reg_3 || "", invoice_collection_id: null, booking_id: toPositiveInteger(body.booking_id), completed_at: null, created_at: new Date().toISOString(), }; fixture.orderItemsByOrderId[orderId] = []; await route.fulfill( json({ success: true, data: { id: orderId, }, }) ); return; } if (pathname.endsWith("/orders") && method === "PUT") { const body = request.postDataJSON?.() || {}; const orderId = Number(body.id); if (fixture.ordersById[orderId]) { fixture.ordersById[orderId] = { ...fixture.ordersById[orderId], ...body, }; } await route.fulfill( json({ success: true, data: fixture.ordersById[orderId] || null, }) ); return; } if (pathname.endsWith("/order") && method === "GET") { const id = Number(parsedUrl.searchParams.get("id")); await route.fulfill( json({ success: true, data: fixture.ordersById[id] || null, }) ); return; } if (pathname.endsWith("/order") && method === "PUT") { const body = request.postDataJSON?.() || {}; const orderId = Number(body.id); if (fixture.ordersById[orderId]) { if (body.field) { fixture.ordersById[orderId][body.field] = body.value; } else { fixture.ordersById[orderId] = { ...fixture.ordersById[orderId], ...body, }; } } await route.fulfill( json({ success: true, data: fixture.ordersById[orderId] || null, }) ); return; } if (pathname.endsWith("/order/items") && method === "GET") { const orderId = Number(parsedUrl.searchParams.get("order_id")); await route.fulfill( json({ success: true, data: fixture.orderItemsByOrderId[orderId] || [], }) ); return; } if (pathname.endsWith("/order/items") && method === "POST") { const body = request.postDataJSON?.() || {}; const orderId = Number(body.order_id); const productId = Number(body.product_id); const product = fixture.products.find((entry) => Number(entry.id) === productId); if (!product || !fixture.ordersById[orderId]) { await route.fulfill( json( { success: false, data: { message: "Order or product not found", }, }, 422 ) ); return; } const item = buildOrderItem(product, body, fixture.nextOrderItemId++); if (!Array.isArray(fixture.orderItemsByOrderId[orderId])) { fixture.orderItemsByOrderId[orderId] = []; } fixture.orderItemsByOrderId[orderId].push(item); await route.fulfill( json({ success: true, data: item, }) ); return; } if (pathname.endsWith("/order/items") && method === "PUT") { const body = request.postDataJSON?.() || {}; const orderItemId = Number(body.id); Object.keys(fixture.orderItemsByOrderId).forEach((orderIdKey) => { fixture.orderItemsByOrderId[orderIdKey] = (fixture.orderItemsByOrderId[orderIdKey] || []).map((item) => { if (item.id !== orderItemId) { return item; } return { ...item, price: Number(body.price ?? item.price), notes: body.notes ?? item.notes, reference: body.reference ?? item.reference, quantity: Number(body.quantity ?? item.quantity), }; }); }); await route.fulfill( json({ success: true, data: { id: orderItemId, }, }) ); return; } if (pathname.endsWith("/order/items") && method === "DELETE") { const orderItemId = Number(parsedUrl.searchParams.get("id")); Object.keys(fixture.orderItemsByOrderId).forEach((orderIdKey) => { fixture.orderItemsByOrderId[orderIdKey] = (fixture.orderItemsByOrderId[orderIdKey] || []).filter( (item) => item.id !== orderItemId ); }); await route.fulfill( json({ success: true, data: true, }) ); return; } if (pathname.endsWith("/departments/order/recommended") && method === "GET") { await route.fulfill( json({ success: true, data: { reg_1: { motorapi: [53], order_history: { 2: [], 3: [], 4: [], 5: [], }, }, }, }) ); return; } if (pathname.endsWith("/orders/mark_as_completed") && method === "POST") { const body = request.postDataJSON?.() || {}; const orderId = Number(body.id); fixture.markCompletedOrderIds.push(orderId); if (fixture.ordersById[orderId]) { fixture.ordersById[orderId].completed_at = new Date().toISOString(); } await route.fulfill( json({ success: true, data: { id: orderId, }, }) ); return; } if (pathname.endsWith("/modules/stripe/department/terminal/readers") && method === "GET") { fixture.readersGet = Number(fixture.readersGet || 0) + 1; await route.fulfill( json({ success: true, data: { data: fixture.readers || [], }, }) ); return; } await route.fallback(); }); } function seedMobilePosState(page) { const state = { vehicles: { vehicle_1: { reg: "AB12345", customer_id: 12345, type: 53, status: "verified", barred: false, booking_id: null, addons: [], }, vehicle_2: null, vehicle_3: null, activeVehicleIndex: 1, }, views: { manualInput: false, vehicleSelection: false, additionalItemSelection: false, transactionHistoryView: false, }, transactionItems: { primaryItem: { id: 53, name: "Tankvogn med hænger", description: "Material product used for completion flow", price: 599, category: 4, is_wash: true, addons: [], }, additionalItems: [], }, categories: { list: [], selected: null, }, productList: { list: [], }, metadata: { customerId: 12345, notes: "", reference: "REF-9201", washId: null, bookingId: null, laneId: null, }, attachments: { files: [], base64: [], wash_certificate: false, }, transactionHistory: [], lastVehicleOrders: { vehicle_1: null, vehicle_2: null, vehicle_3: null, }, timestamp: Date.now(), }; return page.addInitScript((payload) => { window.localStorage.setItem("pos", JSON.stringify(payload)); }, state); } async function setupDesktopPosPage(page, fixture, { token = "pos-desktop-token", sessionData = {} } = {}) { const permissions = ["admin", "department_access_1"]; await mockApi(page, { authenticated: true, permissions, sessionData: { display_name: "POS Desktop", ...sessionData, }, }); await mockPosApi(page, fixture); await primeSession(page, { token, permissions, }); await page.goto("/admin/1/modules/pos"); await expect(page.getByTestId("pos-step-1")).toBeVisible({ timeout: 10_000 }); } function getActiveDesktopModal(page) { return page.locator('[data-testid="pos-desktop-order-booking-modal"].is-active'); } function getInlineDuplicateWarning(page) { return page.locator('[data-testid="pos-desktop-duplicate-warning-inline"]:visible').first(); } async function commitDesktopReg1ByBlur(page, value) { const reg1Input = page.locator("#reg_1"); await reg1Input.fill(value); await reg1Input.evaluate((element) => { element.blur(); }); } async function openDesktopOrderBookingSettings(modal, bookingId) { const settings = modal.getByTestId(`pos-desktop-order-booking-settings-${bookingId}`); await expect(settings).toBeVisible({ timeout: 10_000 }); await settings.locator(".dropdown-trigger button").click(); const dropdown = settings.locator(".dropdown-content"); await expect(dropdown).toBeVisible({ timeout: 10_000 }); return dropdown; } async function completeDesktopOrderBookingFromSettings(page, modal, bookingId) { const dropdown = await openDesktopOrderBookingSettings(modal, bookingId); await dropdown.locator("button.dropdown-item-action").first().click(); const popup = page.locator(".swal2-popup"); await expect(popup).toBeVisible({ timeout: 10_000 }); await popup.locator(".swal2-deny").click(); await expect(popup).toBeHidden({ timeout: 10_000 }); } test.describe("POS flow", () => { test.beforeEach(async ({ page }) => { await suppressVueDevtoolsOverlay(page); }); test("desktop bootstrap ignores customer_id-only query for order loading", async ({ page }, testInfo) => { test.skip(testInfo.project.name !== "chromium-desktop", "Desktop POS bootstrap is validated on chromium-desktop."); const fixture = createPosFixture(); const permissions = ["admin", "department_access_1"]; const invalidOrderRequests = []; const consoleProblems = []; page.on("request", (request) => { const requestUrl = request.url(); if ( request.method() === "GET" && (requestUrl.includes("/order/items?order_id=null") || requestUrl.includes("/order/items?order_id=undefined") || requestUrl.includes("/order/items?order_id=NaN") || requestUrl.includes("/order?id=null") || requestUrl.includes("/order?id=undefined") || requestUrl.includes("/order?id=NaN")) ) { invalidOrderRequests.push(requestUrl); } }); page.on("console", (message) => { if (message.type() === "warning" || message.type() === "error") { consoleProblems.push(message.text()); } }); await mockApi(page, { authenticated: true, permissions, sessionData: { display_name: "POS Bootstrap", }, }); await mockPosApi(page, fixture); await primeSession(page, { token: "pos-bootstrap-token", permissions, }); await page.goto("/admin/1/modules/pos?customer_id=12345&step=1"); await expect(page.getByTestId("pos-step-1")).toBeVisible({ timeout: 10_000 }); await expect(page.getByTestId("pos-step-1").getByText("Nummerplader").first()).toBeVisible({ timeout: 10_000 }); expect(invalidOrderRequests).toEqual([]); expect(consoleProblems.join("\n")).not.toContain("Extraneous non-props attributes"); 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 keeps customer selection mounted only once when an unlinked vehicle card is expanded", async ({ page, }, testInfo) => { test.skip(testInfo.project.name !== "chromium-desktop", "Desktop POS step 1 is validated on chromium-desktop."); const fixture = createPosFixture(); fixture.vehicles = [ { ...fixture.vehicles[0], reg: "AB12345", customer_id: null, customer_name: "", last_order_id: null, }, ]; await setupDesktopPosPage(page, fixture, { token: "pos-unlinked-vehicle-expanded-customer-selector" }); await page.locator("#reg_1").fill("AB12345"); const customerSearchInputs = page.locator("#pos_select_customer_input"); const visibleCustomerSearchInputs = page.locator("#pos_select_customer_input:visible"); const cardPaymentButtons = page.locator("button.customer-quick-action--card"); const visibleCardPaymentButtons = page.locator("button.customer-quick-action--card:visible"); await expect(customerSearchInputs).toHaveCount(1); await expect(visibleCustomerSearchInputs).toHaveCount(1); await expect(cardPaymentButtons).toHaveCount(1); await expect(visibleCardPaymentButtons).toHaveCount(1); await page.locator(".pos-vehicle-form__expandable .has-text-centered").click(); await expect(customerSearchInputs).toHaveCount(1); await expect(visibleCustomerSearchInputs).toHaveCount(1); await expect(cardPaymentButtons).toHaveCount(1); await expect(visibleCardPaymentButtons).toHaveCount(1); }); test("desktop defaults the inline customer picker to customer invoice and keeps the selected action clear", async ({ page, }, testInfo) => { test.skip(testInfo.project.name !== "chromium-desktop", "Desktop POS step 1 is validated on chromium-desktop."); const fixture = createPosFixture(); fixture.vehicles = [ { ...fixture.vehicles[0], reg: "AB12345", customer_id: null, customer_name: "", last_order_id: null, }, ]; await setupDesktopPosPage(page, fixture, { token: "pos-inline-customer-picker-actions", sessionData: { runtime_config: { economic: { transaction_draft_customer_number: 6001, }, }, }, }); await page.locator("#reg_1").fill("AB12345"); const invoiceButton = page.getByTestId("pos-customer-invoice-inline-action"); const draftButton = page.getByTestId("pos-draft-customer-inline-action"); const cardButton = page.getByTestId("pos-card-payment-inline-action"); const customerSearchInput = page.locator("#pos_select_customer_input:visible"); const clearButton = page.getByRole("button", { name: "Ryd" }); await expect(invoiceButton).toBeVisible(); await expect(draftButton).toBeVisible(); await expect(cardButton).toBeVisible(); await expect(invoiceButton).toHaveClass(/is-selected/); await expect(draftButton).not.toHaveClass(/is-selected/); await expect(cardButton).not.toHaveClass(/is-selected/); await expect(customerSearchInput).toHaveCount(1); const isCustomerSearchBelowActions = await page.evaluate(() => { const invoiceButtonElement = document.querySelector('[data-testid="pos-customer-invoice-inline-action"]'); const customerInputElement = document.querySelector("#pos_select_customer_input"); if (!invoiceButtonElement || !customerInputElement) { return null; } const invoiceRect = invoiceButtonElement.getBoundingClientRect(); const customerInputRect = customerInputElement.getBoundingClientRect(); return customerInputRect.top > invoiceRect.bottom; }); expect(isCustomerSearchBelowActions).toBe(true); await cardButton.click(); await expect(cardButton).toHaveClass(/is-selected/); await expect(invoiceButton).not.toHaveClass(/is-selected/); await expect(customerSearchInput).toHaveCount(0); await expect(clearButton).toBeVisible(); await clearButton.click(); await expect(invoiceButton).toHaveClass(/is-selected/); await expect(cardButton).not.toHaveClass(/is-selected/); await expect(page.locator("#pos_select_customer_input:visible")).toHaveCount(1); }); test("desktop disables the card quick action while all stripe readers are offline and re-enables it after polling", async ({ page, }, testInfo) => { test.skip(testInfo.project.name !== "chromium-desktop", "Desktop POS step 1 is validated on chromium-desktop."); const fixture = createPosFixture({ readers: [ { id: "reader_offline_1", label: "Offline reader", status: "offline", action: null, }, ], }); fixture.vehicles = [ { ...fixture.vehicles[0], reg: "AB12345", customer_id: null, customer_name: "", last_order_id: null, }, ]; await setupDesktopPosPage(page, fixture, { token: "pos-inline-card-readers-offline", }); await page.locator("#reg_1").fill("AB12345"); const cardButton = page.getByTestId("pos-card-payment-inline-action"); await expect(cardButton).toBeVisible(); await expect .poll(() => fixture.readersGet, { timeout: 10_000 }) .toBeGreaterThan(0); await expect(cardButton).toBeDisabled(); fixture.readers = [ { id: "reader_online_1", label: "Recovered reader", status: "online", action: null, }, ]; await expect .poll(async () => { return await cardButton.isEnabled(); }, { timeout: 12_000 }) .toBe(true); }); test("desktop auto-applies the only previous customer suggestion while selection source is none", async ({ page, }, testInfo) => { test.skip(testInfo.project.name !== "chromium-desktop", "Desktop POS step 1 is validated on chromium-desktop."); const fixture = createPosFixture(); fixture.vehicles = [ { ...fixture.vehicles[0], reg: "EC21233", customer_id: null, customer_name: "", status: "known", reference: "", last_order_id: null, }, ]; fixture.customerSuggestionsByReg = { EC21233: [ { id: 81, customer_number: fixture.customer.customerNumber, customer_name: fixture.customer.name, barred: false, }, ], }; await setupDesktopPosPage(page, fixture, { token: "pos-single-customer-suggestion-auto-apply" }); await page.locator("#reg_1").fill("EC21233"); await expect(page.getByRole("button", { name: "Ryd" })).toBeVisible({ timeout: 10_000, }); await expect(page.locator("#pos_select_customer_input:visible")).toHaveCount(0); }); test("desktop auto-applies the only previous customer suggestion when another department has the booking", async ({ page, }, testInfo) => { test.skip(testInfo.project.name !== "chromium-desktop", "Desktop POS step 1 is validated on chromium-desktop."); const fixture = createPosFixture(); fixture.vehicles = [ { ...fixture.vehicles[0], id: 7005, reg: "EC21244", customer_id: null, customer_name: "", status: "booked", booking_id: 8221, booking_datetime: "2026-01-06T08:00:00.000Z", reference: "", last_order_id: null, booking_matches: [ buildOrderBooking(8221, { department: 2, reg_1: "EC21244", datetime: "2026-01-06T08:00:00.000Z", }), ], }, ]; fixture.customerSuggestionsByReg = { EC21244: [ { id: 82, customer_number: fixture.customer.customerNumber, customer_name: fixture.customer.name, barred: false, }, ], }; fixture.orderBookings = [ buildOrderBooking(8221, { department: 2, reg_1: "EC21244", datetime: "2026-01-06T08:00:00.000Z", }), ]; await setupDesktopPosPage(page, fixture, { token: "pos-single-customer-suggestion-other-department-booking" }); const activeBookingSelector = getActiveDesktopModal(page); await page.locator("#reg_1").fill("EC21244"); await expect(page.getByRole("button", { name: "Ryd" })).toBeVisible({ timeout: 10_000, }); await expect(page.locator("#pos_select_customer_input:visible")).toHaveCount(0); await expect(page.getByTestId("desktop-booked-icon-7005")).toHaveCount(0); await expect(activeBookingSelector).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."); const fixture = createPosFixture(); const permissions = ["admin", "department_access_1"]; const consoleProblems = []; page.on("console", (message) => { if (message.type() === "warning" || message.type() === "error") { consoleProblems.push(message.text()); } }); await mockApi(page, { authenticated: true, permissions, sessionData: { display_name: "POS Desktop", }, }); await mockPosApi(page, fixture); await primeSession(page, { token: "pos-desktop-token", permissions, }); await page.goto("/admin/1/modules/pos"); await expect(page.getByTestId("pos-step-1")).toBeVisible({ timeout: 10_000 }); 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.getByTestId("pos-step-1").locator(".pos-card-tabs > .tabs li.is-active")).toContainText("Kunde", { timeout: 10_000, }); await page.locator('[data-testid="pos-next-step"]:visible').click(); await expect(page.getByTestId("pos-step-2")).toBeVisible({ timeout: 10_000 }); await expect(page).toHaveURL(/step=2/); await expectDesktopOrderItemsTable(page.getByTestId("pos-step-2")); await page.getByTestId("pos-product-card-53").click(); await expect(page.getByTestId("pos-add-to-cart-53")).toBeVisible({ timeout: 10_000 }); const addItemRequest = page.waitForRequest((request) => { return request.method() === "POST" && request.url().includes("/order/items"); }); await page.getByTestId("pos-add-to-cart-53").click(); await addItemRequest; await expect(page.getByTestId("pos-step-2")).toContainText("Tankvogn med hænger"); await page.locator('[data-testid="pos-next-step"]:visible').click(); await expect(page.getByTestId("pos-step-3")).toBeVisible({ timeout: 10_000 }); await expect(page).toHaveURL(/step=3/); await expectDesktopOrderItemsTable(page.getByTestId("pos-step-3")); await page.locator('[data-testid="pos-next-step"]:visible').click(); let reachedStep4 = false; await expect .poll( async () => { if (await page.getByTestId("pos-step-4").isVisible()) { reachedStep4 = true; return "step4"; } if (/\/admin\/1\/modules\/pos(?:\?|$)/.test(page.url()) && !page.url().includes("step=3")) { return "reset"; } return "pending"; }, { timeout: 10_000 } ) .not.toBe("pending"); if (reachedStep4) { await expectDesktopOrderItemsTable(page.getByTestId("pos-step-4")); await page.locator('[data-testid="pos-next-step"]:visible').click(); } await expect.poll(() => fixture.markCompletedOrderIds, { timeout: 10_000 }).toContain(9300); await expect .poll( async () => { if (await page.getByTestId("pos-step-4").isVisible()) { return "step4"; } if (/\/admin\/1\/modules\/pos(?:\?|$)/.test(page.url()) && !page.url().includes("step=3")) { return "reset"; } return "pending"; }, { timeout: 10_000 } ) .not.toBe("pending"); if (await page.getByTestId("pos-step-4").isVisible()) { await expectDesktopOrderItemsTable(page.getByTestId("pos-step-4")); } 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.vehicles[0], id: 7003, reg: "EC53240", booking_id: 8993, booking_datetime: "2026-01-14T10:00:00.000Z", status: "booked", }, ]; fixture.orderBookings = [ buildOrderBooking(8991, { reg_1: "EC21235", datetime: "2026-01-03T07:00:00.000Z", }), buildOrderBooking(8992, { reg_1: "EC21234", datetime: null, }), buildOrderBooking(8994, { 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("EC"); 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-icon-7003")).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 }); await expect(page.getByTestId("desktop-booked-date-7003")).toContainText(/\d/, { timeout: 10_000 }); expect( await page.getByTestId("desktop-booked-date-7002").evaluate((element) => { const rect = element.getBoundingClientRect(); const x = rect.left + rect.width / 2; const y = rect.top + rect.height / 2; const topElement = document.elementFromPoint(x, y); return topElement === element || element.contains(topElement); }) ).toBe(true); }); test("desktop hydrates booked dropdown dates and chooser from exact plate bookings beyond the initial preload", 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], id: 7101, reg: "EC21233", booking_id: 9101, status: "booked", }, { ...fixture.vehicles[0], id: 7102, reg: "EC21234", booking_id: 9102, status: "booked", }, ]; fixture.orderBookings = [ ...Array.from({ length: 100 }, (_, index) => buildOrderBooking(9200 + index, { reg_1: `FILL${String(index + 1).padStart(4, "0")}`, datetime: `2026-01-${String((index % 28) + 1).padStart(2, "0")}T06:00:00.000Z`, }) ), buildOrderBooking(9102, { reg_1: "EC21234", datetime: "2026-03-20T08:00:00.000Z", reference: "PRELOAD-MISS-A", reference_number: "PRELOAD-MISS-A", }), buildOrderBooking(9103, { reg_1: "EC21234", datetime: "2026-03-21T09:00:00.000Z", reference: "PRELOAD-MISS-B", reference_number: "PRELOAD-MISS-B", }), ]; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-booking-preload-miss" }); const activeBookingSelector = getActiveDesktopModal(page); await page.locator("#reg_1").fill("EC212"); await expect(page.getByTestId("desktop-booked-date-7102")).toContainText("20.03.2026", { timeout: 10_000 }); await page.locator(".dropdown-item").filter({ hasText: "EC21234 - Pleno Logistics" }).first().click(); await expect(activeBookingSelector).toBeVisible({ timeout: 10_000 }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-use-9102")).toBeVisible({ timeout: 10_000, }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-use-9103")).toBeVisible({ timeout: 10_000, }); }); test("desktop exact reg entry opens the booking chooser when exact plate bookings were not in the initial preload", 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], id: 7110, reg: "EC21234", booking_id: 9110, status: "booked", }, ]; fixture.orderBookings = [ ...Array.from({ length: 100 }, (_, index) => buildOrderBooking(9300 + index, { reg_1: `MISS${String(index + 1).padStart(4, "0")}`, datetime: `2026-02-${String((index % 28) + 1).padStart(2, "0")}T06:00:00.000Z`, }) ), buildOrderBooking(9110, { reg_1: "EC21234", datetime: "2026-03-20T08:00:00.000Z", reference: "EXACT-PRELOAD-MISS-A", reference_number: "EXACT-PRELOAD-MISS-A", }), buildOrderBooking(9111, { reg_1: "EC21234", datetime: "2026-03-21T09:00:00.000Z", reference: "EXACT-PRELOAD-MISS-B", reference_number: "EXACT-PRELOAD-MISS-B", }), ]; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-booking-preload-miss-exact" }); const activeBookingSelector = getActiveDesktopModal(page); await commitDesktopReg1ByBlur(page, "EC21234"); await expect(activeBookingSelector).toBeVisible({ timeout: 10_000 }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-use-9110")).toBeVisible({ timeout: 10_000, }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-use-9111")).toBeVisible({ timeout: 10_000, }); }); test("desktop auto-applies a single matching order booking and completes that 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: "TRAILER1", reference: "REF-TRAILER1", }, ]; fixture.orderBookings = [ buildOrderBooking(8101, { reg_1: "TRACTOR1", reg_2: "TRAILER1", reference: "SINGLE-BOOKING-REF", reference_number: "SINGLE-BOOKING-REF", notes: "Single desktop booking", note: "Single desktop booking", po: "SINGLE-BOOKING-PO", 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"], }, }), ]; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-single-booking" }); const activeBookingSelector = getActiveDesktopModal(page); await page.locator("#reg_1").fill("TRAILER1"); await expect(activeBookingSelector).toHaveCount(0); await expect(page.locator("#reg_1")).toHaveValue("TRACTOR1"); await expect(page.locator("#reg_2")).toHaveValue("TRAILER1"); 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]?.reference || null, { timeout: 10_000 }) .toBe("SINGLE-BOOKING-REF"); await expect.poll(() => fixture.ordersById[9300]?.po || null, { timeout: 10_000 }).toBe("SINGLE-BOOKING-PO"); await expect.poll(() => fixture.ordersById[9300]?.reg_1 || null, { timeout: 10_000 }).toBe("TRACTOR1"); await expect.poll(() => fixture.ordersById[9300]?.reg_2 || null, { timeout: 10_000 }).toBe("TRAILER1"); 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(page.getByTestId("pos-step-4")).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(8101); await expect .poll(() => fixture.bookingOrderAssignments, { timeout: 10_000 }) .toContainEqual({ id: 8101, order_id: 9300, }); await expect.poll(() => fixture.markCompletedOrderIds, { timeout: 10_000 }).toContain(9300); await expect.poll(() => fixture.completedBookingIds.length, { timeout: 10_000 }).toBe(0); }); test("desktop ignores a single matching order booking from another department", 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: "TRAILER2", booking_id: 8105, booking_datetime: "2026-01-07T08:00:00.000Z", status: "booked", reference: "", booking_matches: [ buildOrderBooking(8105, { department: 2, reg_1: "TRACTOR2", reg_2: "TRAILER2", reference: "OTHER-DEPARTMENT-REF", reference_number: "OTHER-DEPARTMENT-REF", po: "OTHER-DEPARTMENT-PO", }), ], }, ]; fixture.orderBookings = [ buildOrderBooking(8105, { department: 2, reg_1: "TRACTOR2", reg_2: "TRAILER2", reference: "OTHER-DEPARTMENT-REF", reference_number: "OTHER-DEPARTMENT-REF", po: "OTHER-DEPARTMENT-PO", }), ]; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-single-booking-other-department" }); const activeBookingSelector = getActiveDesktopModal(page); await commitDesktopReg1ByBlur(page, "TRAILER2"); await expect(activeBookingSelector).toHaveCount(0); await expect(page.locator("#reg_1")).toHaveValue("TRAILER2"); await expect(page.locator("#reg_2")).toHaveValue(""); 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]?.reference ?? null, { timeout: 10_000 }).toBe(""); await expect.poll(() => fixture.ordersById[9300]?.po ?? null, { timeout: 10_000 }).toBe(""); await expect.poll(() => fixture.ordersById[9300]?.reg_1 ?? null, { timeout: 10_000 }).toBe("TRAILER2"); await expect.poll(() => fixture.ordersById[9300]?.reg_2 ?? null, { timeout: 10_000 }).toBe(""); await expect.poll(() => fixture.ordersById[9300]?.booking_id || null, { timeout: 10_000 }).toBe(null); }); test("desktop opens a chooser for multiple matching order bookings and hydrates the selected 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: "MULTITRL", reference: "REF-MULTITRL", }, ]; fixture.orderBookings = [ buildOrderBooking(8102, { reg_1: "TRACTORA", reg_2: "MULTITRL", datetime: "2026-01-01T07:00:00.000Z", reference: "BOOKING-A-REF", reference_number: "BOOKING-A-REF", po: "BOOKING-A-PO", 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(8103, { reg_1: "TRACTORB", reg_2: "MULTITRL", datetime: "2026-01-01T09:00:00.000Z", reference: "BOOKING-B-REF", reference_number: "BOOKING-B-REF", items: [{ id: 63, name: "Dolly", price: 275, quantity: 1 }], parsed_services: { string: "Dolly", array: ["Dolly"], }, }), ]; fixture.orderBookingListStripsDetails = true; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-multi-booking" }); const activeBookingSelector = getActiveDesktopModal(page); await page.locator("#reg_1").fill("MULTITRL"); await expect(activeBookingSelector).toBeVisible({ timeout: 10_000 }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8102")).toBeVisible({ timeout: 10_000, }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8103")).toBeVisible({ timeout: 10_000, }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-settings-8102")).toBeVisible({ timeout: 10_000, }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-services-8102")).toContainText( "Tankvogn", { timeout: 10_000 } ); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-services-8102")).toContainText("Dolly", { timeout: 10_000, }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-service-row-8102-0")).toContainText( "1x", { timeout: 10_000, } ); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-service-row-8102-0")).toContainText( "Tankvogn", { timeout: 10_000, } ); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-service-amount-8102-0")).toContainText( "599", { timeout: 10_000, } ); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-service-amount-8102-1")).toContainText( "275", { timeout: 10_000, } ); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-service-total-8102")).toContainText( "874", { timeout: 10_000, } ); const bookingSettingsDropdown = await openDesktopOrderBookingSettings(activeBookingSelector, 8102); await expect(bookingSettingsDropdown.locator("button.dropdown-item-action")).toHaveCount(4); await page.keyboard.press("Escape"); await expect(bookingSettingsDropdown).toBeHidden({ timeout: 10_000 }); await activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8102").click(); await expect(page.locator("#reg_1")).toHaveValue("TRACTORA"); await expect(page.locator("#reference")).toHaveValue("BOOKING-A-REF"); await expect(page.locator("#reg_2")).toHaveValue("MULTITRL"); 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]?.po || null, { timeout: 10_000 }).toBe("BOOKING-A-PO"); await expect.poll(() => fixture.ordersById[9300]?.reg_1 || null, { timeout: 10_000 }).toBe("TRACTORA"); await expect.poll(() => fixture.ordersById[9300]?.reg_2 || null, { timeout: 10_000 }).toBe("MULTITRL"); 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(page.getByTestId("pos-step-4")).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(8102); await expect .poll(() => fixture.bookingOrderAssignments, { timeout: 10_000 }) .toContainEqual({ id: 8102, order_id: 9300, }); await expect.poll(() => fixture.markCompletedOrderIds, { timeout: 10_000 }).toContain(9300); await expect.poll(() => fixture.completedBookingIds.length, { timeout: 10_000 }).toBe(0); }); test("desktop booking selector prioritizes and highlights bookings scheduled for today", async ({ page, }, testInfo) => { test.skip( testInfo.project.name !== "chromium-desktop", "Desktop POS booking flow is validated on chromium-desktop." ); const fixture = createPosFixture(); const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); fixture.vehicles = [ { ...fixture.vehicles[0], reg: "TODAYFIRST", reference: "REF-TODAYFIRST", }, ]; fixture.orderBookings = [ buildOrderBooking(8151, { reg_1: "TRACTORTOMORROW", reg_2: "TODAYFIRST", datetime: tomorrow.toISOString(), reference: "TOMORROW-BOOKING", reference_number: "TOMORROW-BOOKING", }), buildOrderBooking(8150, { reg_1: "TRACTORTODAY", reg_2: "TODAYFIRST", datetime: buildTodayTimestamp("08:00:00.000Z"), reference: "TODAY-BOOKING", reference_number: "TODAY-BOOKING", }), ]; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-booking-today-priority" }); const activeBookingSelector = getActiveDesktopModal(page); await commitDesktopReg1ByBlur(page, "TODAYFIRST"); await expect(activeBookingSelector).toBeVisible({ timeout: 10_000 }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8150")).toBeVisible({ timeout: 10_000, }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8151")).toBeVisible({ timeout: 10_000, }); const orderedBookingCards = await activeBookingSelector .locator('[data-testid^="pos-desktop-order-booking-option-"]') .evaluateAll((elements) => elements.map((element) => element.getAttribute("data-testid"))); expect(orderedBookingCards[0]).toBe("pos-desktop-order-booking-option-8150"); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-today-8150")).toBeVisible({ timeout: 10_000, }); const isTodayHighlighted = await activeBookingSelector .getByTestId("pos-desktop-order-booking-option-8150") .evaluate((element) => element.classList.contains("pos-desktop-order-booking-card--today")); expect(isTodayHighlighted).toBe(true); }); test("desktop booking selector refreshes after completing a booking from the action wheel and auto-resolves the last remaining match", 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: "WHEELREFRESH", reference: "REF-WHEELREFRESH", }, ]; fixture.orderBookings = [ buildOrderBooking(8140, { reg_1: "TRACTORX", reg_2: "WHEELREFRESH", datetime: "2026-01-01T07:00:00.000Z", reference: "WHEEL-REFRESH-A", reference_number: "WHEEL-REFRESH-A", }), buildOrderBooking(8141, { reg_1: "TRACTORY", reg_2: "WHEELREFRESH", datetime: "2026-01-01T09:00:00.000Z", reference: "WHEEL-REFRESH-B", reference_number: "WHEEL-REFRESH-B", }), ]; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-booking-wheel-refresh" }); const activeBookingSelector = getActiveDesktopModal(page); await commitDesktopReg1ByBlur(page, "WHEELREFRESH"); await expect(activeBookingSelector).toBeVisible({ timeout: 10_000 }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8140")).toBeVisible({ timeout: 10_000, }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8141")).toBeVisible({ timeout: 10_000, }); await completeDesktopOrderBookingFromSettings(page, activeBookingSelector, 8140); await expect(page.locator('[data-testid="pos-desktop-order-booking-modal"].is-active')).toHaveCount(0, { timeout: 10_000, }); await expect(page.locator("#reg_1")).toHaveValue("TRACTORY"); await expect(page.locator("#reg_2")).toHaveValue("WHEELREFRESH"); await expect(page.locator("#reference")).toHaveValue("WHEEL-REFRESH-B"); await expect.poll(() => fixture.completedBookingIds, { timeout: 10_000 }).toContain(8140); expect(fixture.completedBookingIds).not.toContain(8141); }); test("desktop booking selector closes when a wheel action refresh removes all remaining matches", 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: "WHEELZERO", reference: "REF-WHEELZERO", }, ]; fixture.orderBookings = [ buildOrderBooking(8142, { reg_1: "TRACTORZEROA", reg_2: "WHEELZERO", datetime: "2026-01-01T07:00:00.000Z", reference: "WHEEL-ZERO-A", reference_number: "WHEEL-ZERO-A", }), buildOrderBooking(8143, { reg_1: "TRACTORZEROB", reg_2: "WHEELZERO", datetime: "2026-01-01T09:00:00.000Z", reference: "WHEEL-ZERO-B", reference_number: "WHEEL-ZERO-B", }), ]; fixture.onCompleteOrderBooking = (_bookingId, completedBooking, activeFixture) => { const matchingPlate = String(completedBooking?.reg_2 || completedBooking?.reg_1 || "").toUpperCase(); activeFixture.orderBookings = activeFixture.orderBookings.map((booking) => { const bookingPlate = String(booking?.reg_2 || booking?.reg_1 || "").toUpperCase(); if (bookingPlate !== matchingPlate) { return booking; } return { ...booking, status: "completed", }; }); }; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-booking-wheel-zero" }); const activeBookingSelector = getActiveDesktopModal(page); await commitDesktopReg1ByBlur(page, "WHEELZERO"); await expect(activeBookingSelector).toBeVisible({ timeout: 10_000 }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8142")).toBeVisible({ timeout: 10_000, }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8143")).toBeVisible({ timeout: 10_000, }); await completeDesktopOrderBookingFromSettings(page, activeBookingSelector, 8142); await expect(page.locator('[data-testid="pos-desktop-order-booking-modal"].is-active')).toHaveCount(0, { timeout: 10_000, }); await expect(page.locator("#reg_1")).toHaveValue("WHEELZERO"); await expect(page.locator("#reference")).toHaveValue("REF-WHEELZERO"); 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]?.booking_id ?? null, { timeout: 10_000 }).toBe(null); }); test("desktop does not reopen the booking selector after refreshing on a later POS step", async ({ page, }, testInfo) => { test.skip( testInfo.project.name !== "chromium-desktop", "Desktop POS reload behavior is validated on chromium-desktop." ); const fixture = createPosFixture(); fixture.ordersById[9201] = { ...fixture.ordersById[9201], reg_1: "STEPTHREE", reference: "STEP-THREE-REF", }; fixture.orderItemsByOrderId[9201] = [ buildOrderItem(fixture.products[0], { order_id: 9201, product_id: 53, quantity: 1 }, 9801), ]; fixture.orderBookings = [ buildOrderBooking(8160, { reg_1: "STEPTHREE", datetime: "2026-04-14T07:00:00.000Z", reference: "STEP-THREE-A", reference_number: "STEP-THREE-A", }), buildOrderBooking(8161, { reg_1: "STEPTHREE", datetime: "2026-04-14T09:00:00.000Z", reference: "STEP-THREE-B", reference_number: "STEP-THREE-B", }), ]; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-step-3-refresh" }); await page.goto("/admin/1/modules/pos?id=9201&customer_id=12345&step=3"); await expect(page.getByTestId("pos-step-3")).toBeVisible({ timeout: 10_000 }); await expect(page).toHaveURL(/id=9201&customer_id=12345&step=3/); await expect(getActiveDesktopModal(page)).toHaveCount(0); await page.reload(); await expect(page.getByTestId("pos-step-3")).toBeVisible({ timeout: 10_000 }); await expect(page).toHaveURL(/id=9201&customer_id=12345&step=3/); await expect(page.getByTestId("pos-step-3")).toContainText("STEP-THREE-REF"); await expect(getActiveDesktopModal(page)).toHaveCount(0); }); test("desktop shows booking selection before duplicate warning when multiple bookings need selection", 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: "MULTIDUP", reference: "REF-MULTIDUP", }, ]; fixture.orderBookings = [ buildOrderBooking(8130, { reg_1: "TRACTORE", reg_2: "MULTIDUP", datetime: "2026-01-01T07:00:00.000Z", reference: "MULTI-DUP-A", reference_number: "MULTI-DUP-A", }), buildOrderBooking(8131, { reg_1: "TRACTORF", reg_2: "MULTIDUP", datetime: "2026-01-01T09:00:00.000Z", reference: "MULTI-DUP-B", reference_number: "MULTI-DUP-B", }), ]; fixture.orderBookingsDelayMs = 600; fixture.duplicateOrders = [ { id: 9202, customer_id: fixture.customer.customerNumber, department_id: 1, reference: "DUP-REF", notes: "", reg_1: "TRACTORE", reg_2: "MULTIDUP", reg_3: "", booking_id: null, completed_at: null, created_at: buildTodayTimestamp("10:15:00.000Z"), }, ]; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-duplicate-booking-priority" }); const activeBookingSelector = getActiveDesktopModal(page); await commitDesktopReg1ByBlur(page, "MULTIDUP"); await expect(activeBookingSelector).toBeVisible({ timeout: 10_000 }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8130")).toBeVisible({ timeout: 10_000, }); await expect(getInlineDuplicateWarning(page)).toHaveCount(0); await activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8130").click(); await expect(activeBookingSelector).toHaveCount(0); await expect(getInlineDuplicateWarning(page)).toBeVisible({ timeout: 10_000, }); }); test("desktop duplicate warning appears only after committed input when there is no booking", async ({ page, }, testInfo) => { test.skip( testInfo.project.name !== "chromium-desktop", "Desktop POS booking flow is validated on chromium-desktop." ); const fixture = createPosFixture(); fixture.duplicateOrders = [ { id: 9203, customer_id: fixture.customer.customerNumber, department_id: 1, reference: "DUP-NO-BOOKING", notes: "", reg_1: "AB12345", reg_2: "", reg_3: "", booking_id: null, completed_at: null, created_at: buildTodayTimestamp("08:30:00.000Z"), }, ]; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-duplicate-after-commit" }); await page.locator("#reg_1").fill("AB12345"); await expect(getInlineDuplicateWarning(page)).toHaveCount(0); await page.locator("#reference").click(); await expect(getInlineDuplicateWarning(page)).toBeVisible({ timeout: 10_000, }); }); test("desktop single booking duplicate warning uses the rewritten primary registration", 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: "TRAILDUP", reference: "REF-TRAILDUP", }, ]; fixture.orderBookings = [ buildOrderBooking(8132, { reg_1: "TRACTORDUP", reg_2: "TRAILDUP", reference: "TRAILER-DUP-BOOKING", reference_number: "TRAILER-DUP-BOOKING", }), ]; fixture.duplicateOrders = [ { id: 9204, customer_id: fixture.customer.customerNumber, department_id: 1, reference: "TRACTOR-DUPLICATE", notes: "", reg_1: "TRACTORDUP", reg_2: "TRAILDUP", reg_3: "", booking_id: null, completed_at: null, created_at: buildTodayTimestamp("11:00:00.000Z"), }, ]; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-rewritten-duplicate" }); await commitDesktopReg1ByBlur(page, "TRAILDUP"); await expect(page.locator("#reg_1")).toHaveValue("TRACTORDUP"); await expect(page.locator("#reg_2")).toHaveValue("TRAILDUP"); await expect(getInlineDuplicateWarning(page)).toBeVisible({ timeout: 10_000, }); }); test("desktop inline duplicate warning keeps the current step-1 state", async ({ page }, testInfo) => { test.skip( testInfo.project.name !== "chromium-desktop", "Desktop POS booking flow is validated on chromium-desktop." ); const fixture = createPosFixture(); fixture.duplicateOrders = [ { id: 9205, customer_id: fixture.customer.customerNumber, department_id: 1, reference: "DUP-CANCEL", notes: "", reg_1: "AB12345", reg_2: "", reg_3: "", booking_id: null, completed_at: null, created_at: buildTodayTimestamp("09:00:00.000Z"), }, ]; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-duplicate-cancel" }); await commitDesktopReg1ByBlur(page, "AB12345"); await expect(getInlineDuplicateWarning(page)).toBeVisible({ timeout: 10_000, }); await expect(page.locator("#reg_1")).toHaveValue("AB12345"); await expect(page.getByTestId("pos-step-2")).not.toBeVisible(); }); test("desktop duplicate warning can open duplicate details without losing the current state", async ({ page, }, testInfo) => { test.skip( testInfo.project.name !== "chromium-desktop", "Desktop POS booking flow is validated on chromium-desktop." ); const fixture = createPosFixture(); fixture.duplicateOrders = [ { id: 9206, customer_id: fixture.customer.customerNumber, department_id: 1, reference: "DUP-DETAILS", notes: "", reg_1: "AB12345", reg_2: "", reg_3: "", booking_id: null, completed_at: null, created_at: buildTodayTimestamp("10:00:00.000Z"), }, ]; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-duplicate-details" }); await commitDesktopReg1ByBlur(page, "AB12345"); await getInlineDuplicateWarning(page).getByTestId("pos-desktop-duplicate-warning-toggle-details").click(); await expect(page.getByTestId("pos-desktop-duplicate-warning-details-inline")).toBeVisible({ timeout: 10_000, }); await expect(page.getByTestId("pos-desktop-duplicate-order-open-9206")).toBeVisible({ timeout: 10_000, }); await expect(page.locator("#reg_1")).toHaveValue("AB12345"); }); test("desktop next proceeds when duplicates are detected during preflight", async ({ page }, testInfo) => { test.skip( testInfo.project.name !== "chromium-desktop", "Desktop POS booking flow is validated on chromium-desktop." ); const fixture = createPosFixture(); fixture.duplicateOrders = [ { id: 9207, customer_id: fixture.customer.customerNumber, department_id: 1, reference: "DUP-BLOCK-NEXT", notes: "", reg_1: "AB12345", reg_2: "", reg_3: "", booking_id: null, completed_at: null, created_at: buildTodayTimestamp("12:00:00.000Z"), }, ]; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-duplicate-next-block" }); await page.locator("#reg_1").fill("AB12345"); 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_1 || null, { timeout: 10_000 }).toBe("AB12345"); await expect(getActiveDesktopModal(page)).toHaveCount(0); }); test("desktop keyboard selection opens the booking chooser for booked search results", 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], id: undefined, reg: "EC21233", reference: "REF-EC21233", }, { ...fixture.vehicles[0], id: undefined, reg: "EC21234", reference: "REF-EC21234", }, { ...fixture.vehicles[0], id: 7003, reg: "EC21235", reference: "REF-EC21235", }, ]; fixture.unknownVehicles = [ { reg_1: "EC2123", }, ]; fixture.orderBookings = [ buildOrderBooking(8121, { reg_1: "EC21233", datetime: "2026-01-01T06:00:00.000Z", reference: "KEYBOARD-BOOKING-0", reference_number: "KEYBOARD-BOOKING-0", }), buildOrderBooking(8122, { reg_1: "EC21234", datetime: "2026-01-01T07:00:00.000Z", reference: "KEYBOARD-BOOKING-A", reference_number: "KEYBOARD-BOOKING-A", reg_2: "KEY-TRAILER-A", }), buildOrderBooking(8123, { reg_1: "EC21234", datetime: "2026-01-01T09:00:00.000Z", reference: "KEYBOARD-BOOKING-B", reference_number: "KEYBOARD-BOOKING-B", reg_2: "KEY-TRAILER-B", }), ]; fixture.orderBookingsDelayMs = 600; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-keyboard-booking" }); const activeBookingSelector = getActiveDesktopModal(page); const reg1Input = page.locator("#reg_1"); const activeDropdownItems = page.locator(".license-plate-dropdown-item--active"); await reg1Input.fill("EC2123"); await reg1Input.press("ArrowDown"); await reg1Input.press("ArrowDown"); await expect(activeDropdownItems).toHaveCount(1); await expect(activeDropdownItems.first()).toContainText("EC21234"); await expect.poll(() => page.evaluate(() => window.getSelection()?.toString() || ""), { timeout: 10_000 }).toBe(""); await reg1Input.press("Enter"); await expect(reg1Input).toHaveValue("EC21234"); await expect(activeBookingSelector).toBeVisible({ timeout: 10_000 }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8122")).toBeVisible({ timeout: 10_000, }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8123")).toBeVisible({ timeout: 10_000, }); }); test("desktop dropdown click opens the booking chooser for booked search results", 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], id: undefined, reg: "EC21233", reference: "REF-EC21233", }, { ...fixture.vehicles[0], id: undefined, reg: "EC21234", reference: "REF-EC21234", }, { ...fixture.vehicles[0], id: 7003, reg: "EC21235", reference: "REF-EC21235", }, ]; fixture.orderBookings = [ buildOrderBooking(8124, { reg_1: "EC21234", datetime: "2026-01-01T07:00:00.000Z", reference: "MOUSE-BOOKING-A", reference_number: "MOUSE-BOOKING-A", reg_2: "MOUSE-TRAILER-A", }), buildOrderBooking(8125, { reg_1: "EC21234", datetime: "2026-01-01T09:00:00.000Z", reference: "MOUSE-BOOKING-B", reference_number: "MOUSE-BOOKING-B", reg_2: "MOUSE-TRAILER-B", }), ]; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-mouse-booking" }); const activeBookingSelector = getActiveDesktopModal(page); const reg1Input = page.locator("#reg_1"); await reg1Input.fill("EC21"); await page.locator(".dropdown-item").filter({ hasText: "EC21234 - Pleno Logistics" }).first().click(); await expect(reg1Input).toHaveValue("EC21234"); await expect.poll(() => page.evaluate(() => window.getSelection()?.toString() || ""), { timeout: 10_000 }).toBe(""); await expect(activeBookingSelector).toBeVisible({ timeout: 10_000 }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8124")).toBeVisible({ timeout: 10_000, }); await expect(activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8125")).toBeVisible({ timeout: 10_000, }); }); test("desktop blocks next until a multiple-booking chooser is resolved", 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: "MULTITRL", reference: "REF-MULTITRL", }, ]; fixture.orderBookings = [ buildOrderBooking(8126, { reg_1: "TRACTORC", reg_2: "MULTITRL", datetime: "2026-01-01T07:00:00.000Z", reference: "BLOCKING-BOOKING-A", reference_number: "BLOCKING-BOOKING-A", }), buildOrderBooking(8127, { reg_1: "TRACTORD", reg_2: "MULTITRL", datetime: "2026-01-01T09:00:00.000Z", reference: "BLOCKING-BOOKING-B", reference_number: "BLOCKING-BOOKING-B", }), ]; fixture.orderBookingsDelayMs = 600; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-blocked-booking" }); const activeBookingSelector = getActiveDesktopModal(page); await page.locator("#reg_1").fill("MULTITRL"); await page.locator('[data-testid="pos-next-step"]:visible').click(); await expect(activeBookingSelector).toBeVisible({ timeout: 10_000 }); await expect(page.getByTestId("pos-step-2")).not.toBeVisible(); expect(fixture.ordersById[9300]).toBeUndefined(); await activeBookingSelector.getByTestId("pos-desktop-order-booking-use-8126").click(); await expect(page.locator("#reg_1")).toHaveValue("TRACTORC"); await expect(page.locator("#reg_2")).toHaveValue("MULTITRL"); await expect(page.getByTestId("pos-step-2")).toBeVisible({ timeout: 10_000 }); await expect.poll(() => fixture.ordersById[9300]?.reg_1 || null, { timeout: 10_000 }).toBe("TRACTORC"); await expect.poll(() => fixture.ordersById[9300]?.reg_2 || null, { timeout: 10_000 }).toBe("MULTITRL"); }); 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 = getActiveDesktopModal(page); 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.ordersById[9300]?.booking_id ?? null, { timeout: 10_000 }).toBe(8111); 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(page.getByTestId("pos-step-4")).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.markCompletedOrderIds, { timeout: 10_000 }).toContain(9300); await expect.poll(() => fixture.completedBookingIds.length, { timeout: 10_000 }).toBe(0); }); test("desktop continue without booking skips hydration and completion requests", 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(8104, { reference: "SKIP-A-REF", reference_number: "SKIP-A-REF", }), buildOrderBooking(8105, { datetime: "2026-01-01T09:00:00.000Z", reference: "SKIP-B-REF", reference_number: "SKIP-B-REF", }), ]; await setupDesktopPosPage(page, fixture, { token: "pos-desktop-skip-booking" }); const activeBookingSelector = getActiveDesktopModal(page); await commitDesktopReg1ByBlur(page, "AB12345"); await expect(activeBookingSelector).toBeVisible({ timeout: 10_000 }); await activeBookingSelector.getByTestId("pos-desktop-order-booking-skip").click(); 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] || []).length, { timeout: 10_000 }).toBe(0); await page.getByTestId("pos-product-card-53").click(); await expect(page.getByTestId("pos-add-to-cart-53")).toBeVisible({ timeout: 10_000 }); await page.getByTestId("pos-add-to-cart-53").click(); await expect.poll(() => (fixture.orderItemsByOrderId[9300] || []).length, { timeout: 10_000 }).toBe(1); 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.bookingOrderAssignments.length, { timeout: 10_000 }).toBe(0); await expect.poll(() => fixture.completedBookingIds.length, { timeout: 10_000 }).toBe(0); await expect.poll(() => fixture.ordersById[9300]?.booking_id ?? null, { timeout: 10_000 }).toBe(null); }); });