Files
pleno-vue/tests/unit/invoicing-period-tree-nodes.spec.js
T

223 lines
7.7 KiB
JavaScript

import { describe, expect, it } from "vitest";
import {
TREE_CATEGORY_TYPES,
TREE_NODE_TYPES,
buildRelativeCollectionLabel,
buildCollectionRootNodes,
classifyAttachment,
getTreeAmount,
hasWashCertificateOrderItem,
makeAttachmentNode,
makeBookingItemNode,
makeBookingNode,
makeCategoryNode,
makeEconomicInvoiceNode,
makeOrderItemNode,
makeOrderNode,
makeXlvaskInferredItemNode,
makeXlvaskNode,
} from "@/views/dashboards/superUserDashboard/InvoicingBillingPeriod/services/invoicingPeriodTreeNodes.js";
describe("invoicing period tree node builders", () => {
it("groups visible orders by invoice collection and keeps ungrouped orders in a category", () => {
const nodes = buildCollectionRootNodes(
{ customer_number: 2001, customer_name: "ACME" },
[
{ id: 11, invoice_collection_id: 3002, total_net_amount: 125 },
{ id: 12, invoice_collection_id: 3001, total_net_amount: 50 },
{ id: 13, invoice_collection_id: null, amount: 75 },
{ id: 14, invoice_collection_id: 3001, amount: 25, total_net_amount: 0 },
],
[11],
{
collection: (id) => `Collection ${id}`,
ordersWithoutCollection: "Loose orders",
}
);
expect(nodes).toHaveLength(2);
expect(nodes[0]).toMatchObject({
id: `${TREE_NODE_TYPES.COLLECTION}:3001`,
label: "Collection 3001",
selectable: true,
actionable: true,
meta: {
collectionId: 3001,
customerNumber: 2001,
orderCount: 2,
totalNetAmount: 75,
},
});
expect(nodes[1]).toMatchObject({
label: "Loose orders",
type: TREE_NODE_TYPES.CATEGORY,
category: TREE_CATEGORY_TYPES.COLLECTION_ORDERS,
selectable: false,
checkable: true,
actionable: false,
meta: {
count: 1,
},
});
});
it("uses relative collection labels for whole-month and multi-collection ranges", () => {
const wholeMonthNodes = buildCollectionRootNodes(
{ customer_number: 2001, customer_name: "ACME" },
[
{ id: 11, invoice_collection_id: 3001, created_at: "2026-06-02", total_net_amount: 125 },
{ id: 12, invoice_collection_id: 3001, created_at: "2026-06-18", total_net_amount: 50 },
],
[],
{
collection: (id) => `Collection ${id}`,
dateFrom: "2026-06-01",
dateTo: "2026-06-30",
locale: "da-DK",
}
);
expect(wholeMonthNodes[0].label).toBe("Juni");
expect(wholeMonthNodes[0].meta.relativeLabel).toMatchObject({
kind: "month",
monthLabel: "Juni",
});
const rangeNodes = buildCollectionRootNodes(
{ customer_number: 2001, customer_name: "ACME" },
[
{ id: 11, invoice_collection_id: 3001, created_at: "2026-06-01", total_net_amount: 125 },
{ id: 12, invoice_collection_id: 3001, created_at: "2026-06-18", total_net_amount: 50 },
{ id: 13, invoice_collection_id: 3002, created_at: "2026-06-19", total_net_amount: 75 },
],
[],
{
collection: (id) => `Collection ${id}`,
dateFrom: "2026-06-01",
dateTo: "2026-06-30",
locale: "da-DK",
}
);
expect(rangeNodes[0].label).toBe("1 Juni → 18 Juni");
expect(rangeNodes[0].meta.relativeLabel).toMatchObject({
kind: "range",
startLabel: "1 Juni",
endLabel: "18 Juni",
});
expect(buildRelativeCollectionLabel({
collectionId: 3003,
orders: [],
fallbackLabel: "Collection 3003",
}).label).toBe("Collection 3003");
});
it("normalizes tree amounts from the first non-zero backend amount field", () => {
expect(getTreeAmount({ amount: 240, total_net_amount: 0 })).toBe(240);
expect(getTreeAmount({ amount: 0, total_net_amount: 140, total: 99 })).toBe(140);
expect(getTreeAmount({ amount: 0, total_net_amount: 0, total: 99 })).toBe(99);
expect(makeOrderNode({ id: 91, amount: 240, total_net_amount: 0 }).meta.totalNetAmount).toBe(240);
});
it("creates non-selectable but checkable category branch nodes for bulk selection", () => {
const node = makeCategoryNode({
id: "category:orders",
label: "Orders",
category: TREE_CATEGORY_TYPES.COLLECTION_ORDERS,
parentType: TREE_NODE_TYPES.COLLECTION,
parentId: "collection:1",
checkable: true,
});
expect(node).toMatchObject({
selectable: false,
checkable: true,
actionable: false,
});
});
it("classifies attachments into certificate, image, and other buckets", () => {
expect(classifyAttachment({ content: { other: "wash_certificate_42.pdf" } })).toBe("certificate");
expect(classifyAttachment({ file_name: "front.jpg", content_type: "application/octet-stream" })).toBe("image");
expect(classifyAttachment({ file_name: "note.txt", content_type: "text/plain" })).toBe("other");
});
it("detects wash certificate order items from product names and pictograms", () => {
expect(hasWashCertificateOrderItem([
{ product_name: "Normal vask" },
{ product: { name: "Vaskecertifikat" } },
])).toBe(true);
expect(hasWashCertificateOrderItem([{ piktogram: "certificate", name: "Seal" }])).toBe(true);
expect(hasWashCertificateOrderItem([{ product_name: "Kassevogn/Varevogn" }])).toBe(false);
});
it("creates selectable object nodes with typed metadata for lazy children and actions", () => {
const orderNode = makeOrderNode(
{
id: 91,
invoice_collection_id: 3001,
booking_id: 77,
wash_id: "W-12",
total_net_amount: 240,
},
{ label: "Order label" }
);
const itemNode = makeOrderItemNode({ id: 501, order_id: 91, product_name: "Premium wash", quantity: 2, price: 120 });
const attachmentNode = makeAttachmentNode({ id: 8, file_name: "photo.png" }, 91);
const bookingNode = makeBookingNode({ id: 77, order_id: 91, items: [{ id: 1, name: "Slot" }] });
const bookingItemNode = makeBookingItemNode({ id: 1, name: "Slot" }, 77);
const xlvaskNode = makeXlvaskNode({ id: 456, WashId: "W-12", WashItems: [{ Name: "Wash" }] }, { id: 91 });
const inferredNode = makeXlvaskInferredItemNode({ Name: "Wash" }, "W-12", 0);
expect(orderNode).toMatchObject({
label: "Order label",
type: TREE_NODE_TYPES.ORDER,
isLeaf: false,
selectable: true,
actionable: true,
meta: {
orderId: 91,
collectionId: 3001,
bookingId: 77,
washId: "W-12",
},
});
expect(itemNode).toMatchObject({ type: TREE_NODE_TYPES.ORDER_ITEM, isLeaf: true, actionable: true });
expect(attachmentNode).toMatchObject({
type: TREE_NODE_TYPES.ATTACHMENT,
meta: { attachmentId: 8, orderId: 91, attachmentType: "image" },
});
expect(bookingNode).toMatchObject({ type: TREE_NODE_TYPES.BOOKING, meta: { bookingId: 77, orderId: 91 } });
expect(bookingItemNode).toMatchObject({ type: TREE_NODE_TYPES.BOOKING_ITEM, actionable: false });
expect(xlvaskNode).toMatchObject({
type: TREE_NODE_TYPES.XLVASK_WASH,
actionable: true,
meta: { usageId: 456, washId: "W-12", orderId: 91 },
});
expect(inferredNode).toMatchObject({ type: TREE_NODE_TYPES.XLVASK_INFERRED_ITEM, actionable: false });
});
it("creates economic invoice nodes with draft/booked metadata", () => {
const node = makeEconomicInvoiceNode({
collectionId: 3001,
invoiceType: "draft",
economicInvoiceId: 812,
details: { economic: { draft_id: 812 } },
});
expect(node).toMatchObject({
id: `${TREE_NODE_TYPES.ECONOMIC_INVOICE}:3001:draft:812`,
label: "E-conomic kladde #812",
type: TREE_NODE_TYPES.ECONOMIC_INVOICE,
isLeaf: true,
actionable: false,
icon: "fa-file-alt",
meta: {
collectionId: 3001,
economicType: "draft",
economicInvoiceId: 812,
},
});
});
});