223 lines
7.7 KiB
JavaScript
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,
|
|
},
|
|
});
|
|
});
|
|
});
|