- Introduced `repairText` and `containsSuspiciousEncoding` utilities for handling corrupted text encodings. - Added unit tests (`encoding-repair.spec.js`) to validate encoding repair and encoding marker detection. - Integrated encoding check into unit test workflows (`text:check-encoding` and `text:fix-encoding` scripts). - Replaced corrupted strings across multiple components and locales with proper UTF-8 encodings.
2666 lines
87 KiB
JavaScript
2666 lines
87 KiB
JavaScript
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 persists manual step-one reference and trailer fields when creating the order", 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-desktop-step-one-manual-field-persistence",
|
|
});
|
|
|
|
await page.locator("#reg_1").fill("AB12345");
|
|
await expect(page.getByTestId("pos-step-1").getByText("Pleno Logistics").first()).toBeVisible({ timeout: 10_000 });
|
|
await page.locator("#reg_2").fill("TRAILER9");
|
|
await page.locator("#reference").fill("MANUAL-DESKTOP-SAVE");
|
|
|
|
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]?.id ?? null, { timeout: 10_000 }).toBe(9300);
|
|
await expect.poll(() => fixture.ordersById[9300]?.reg_2 ?? null, { timeout: 10_000 }).toBe("TRAILER9");
|
|
await expect.poll(() => fixture.ordersById[9300]?.reference ?? null, { timeout: 10_000 }).toBe(
|
|
"MANUAL-DESKTOP-SAVE"
|
|
);
|
|
});
|
|
|
|
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);
|
|
});
|
|
});
|