Add utilities and tests for mobile order management and Subuser navigation:

- Introduced `mobileOrderCompletion.js` for mobile order metadata handling with safety seal syncing, metadata persistence, and booking linking utilities.
- Added tests for Subuser navigation menu (`navigation-menu-items-user-subusers.spec.js`) to validate links and permission gating.
- Created unit and end-to-end tests for Subuser grant permissions and management, including permission nodes and invites (`subuser-grant-permission-nodes.spec.js`, `subuser-management.spec.ts`).
- Implemented `SubuserCompleteRegistrationPage.vue` with validation, token handling, and registration flow for Subuser setup.
- Enhanced Playwright e2e tests for Subuser features (`subuserCompleteRegistration.spec.ts`).
This commit is contained in:
Jeppe Bundgaard
2026-04-14 10:09:10 +02:00
parent a93b0dc954
commit c14f0cc1c4
46 changed files with 4258 additions and 674 deletions
+71
View File
@@ -119,6 +119,61 @@ function normalizeIncludeInInvoiceValue(value) {
return null;
}
function normalizeSafetySealValue(value) {
if (value === null || value === undefined) {
return "";
}
return String(value).trim();
}
function isWashCertificateProduct(product) {
const productId = Number(product?.id ?? product?.product_id ?? product?.product?.id ?? 0);
if (productId === 41) {
return true;
}
return /vaskecertifikat|wash certificate|safety seal/i.test(String(product?.name ?? product?.product?.name ?? ""));
}
function orderContainsWashCertificate(posFixture, orderId) {
return (posFixture.orderItemsByOrderId[orderId] || []).some((item) => isWashCertificateProduct(item?.product || item));
}
function ensureWashCertificateAttachment(posFixture, orderId) {
if (!Array.isArray(posFixture.attachmentsByOrderId[orderId])) {
posFixture.attachmentsByOrderId[orderId] = [];
}
const existingAttachment =
(posFixture.attachmentsByOrderId[orderId] || []).find((attachment) => {
return String(attachment?.content?.other || "").toUpperCase() === "WASH_CERTIFICATE";
}) || null;
if (existingAttachment) {
return existingAttachment;
}
const attachmentId = posFixture.nextAttachmentId++;
const attachment = {
id: attachmentId,
object_type: "orders",
object_id: orderId,
content: {
image: null,
document: `wash_certificate_${orderId}.pdf`,
relation: null,
other: "WASH_CERTIFICATE",
src: null,
},
created_at: toSqlDateTime(),
updated_at: toSqlDateTime(),
deleted_at: null,
};
posFixture.attachmentsByOrderId[orderId].push(attachment);
return attachment;
}
function resolveDepartmentIncludeInInvoice(posFixture, departmentId) {
const department = (posFixture.departments || []).find((entry) => Number(entry.id) === Number(departmentId));
if (!department) {
@@ -977,6 +1032,7 @@ export function createPosFixture(overrides = {}) {
department_id: 12,
reference: "EC21233 - Test Ref. / Intern nummer",
po: "",
safety_seal: "",
notes: "",
reg_1: "EC21235",
reg_2: "",
@@ -1540,6 +1596,7 @@ async function handlePosRoute({ route, request, parsedUrl, pathname, method, pos
department_id: Number(body.department_id || body.department || 12),
reference: body.reference || "",
po: body.po || "",
safety_seal: normalizeSafetySealValue(body.safety_seal),
notes: body.notes || "",
reg_1: normalizeRegistrationValue(body.reg_1),
reg_2: normalizeRegistrationValue(body.reg_2),
@@ -1583,6 +1640,9 @@ async function handlePosRoute({ route, request, parsedUrl, pathname, method, pos
...(Object.prototype.hasOwnProperty.call(body, "created_at")
? { created_at: normalizeCreatedAtValue(body.created_at) }
: {}),
...(Object.prototype.hasOwnProperty.call(body, "safety_seal")
? { safety_seal: normalizeSafetySealValue(body.safety_seal) }
: {}),
...(Object.prototype.hasOwnProperty.call(body, "include_in_invoice")
? { include_in_invoice: normalizeIncludeInInvoiceValue(body.include_in_invoice) }
: {}),
@@ -1604,6 +1664,8 @@ async function handlePosRoute({ route, request, parsedUrl, pathname, method, pos
? normalizeIncludeInInvoiceValue(body.value)
: body.field === "reg_1" || body.field === "reg_2" || body.field === "reg_3"
? normalizeRegistrationValue(body.value)
: body.field === "safety_seal"
? normalizeSafetySealValue(body.value)
: body.field === "created_at"
? normalizeCreatedAtValue(body.value)
: body.value;
@@ -1623,6 +1685,9 @@ async function handlePosRoute({ route, request, parsedUrl, pathname, method, pos
...(Object.prototype.hasOwnProperty.call(body, "created_at")
? { created_at: normalizeCreatedAtValue(body.created_at) }
: {}),
...(Object.prototype.hasOwnProperty.call(body, "safety_seal")
? { safety_seal: normalizeSafetySealValue(body.safety_seal) }
: {}),
...(Object.prototype.hasOwnProperty.call(body, "include_in_invoice")
? { include_in_invoice: normalizeIncludeInInvoiceValue(body.include_in_invoice) }
: {}),
@@ -1751,6 +1816,9 @@ async function handlePosRoute({ route, request, parsedUrl, pathname, method, pos
if (pathname.endsWith("/order/wash-certificate") && method === "POST") {
const body = request.postDataJSON?.() || {};
const orderId = Number(body.id || 0);
if (posFixture.ordersById[orderId]) {
posFixture.ordersById[orderId].safety_seal = normalizeSafetySealValue(body.safety_seal);
}
if (!Array.isArray(posFixture.attachmentsByOrderId[orderId])) {
posFixture.attachmentsByOrderId[orderId] = [];
@@ -1828,6 +1896,9 @@ async function handlePosRoute({ route, request, parsedUrl, pathname, method, pos
const orderId = Number(body.id || 0);
if (posFixture.ordersById[orderId]) {
posFixture.ordersById[orderId].completed_at = new Date().toISOString();
if (orderContainsWashCertificate(posFixture, orderId)) {
ensureWashCertificateAttachment(posFixture, orderId);
}
}
await route.fulfill(json({ success: true, data: { id: orderId } }));
return true;