Files
pleno-vue/tests/unit/invoice-distribution-calculations.spec.js
T
Jeppe Bundgaard b39b458f4f Add .prettierrc.json and refactor test files for improved formatting consistency:
- Introduced `.prettierrc.json` to enforce consistent code formatting across the project.
- Updated unit and e2e test files to address formatting issues, improve readability, and ensure alignment with the new Prettier configuration.
2026-04-13 09:13:27 +02:00

409 lines
12 KiB
JavaScript

import { describe, expect, it } from "vitest";
import {
buildDistributionQueryState,
getMonthRange,
buildMonthSummary,
buildDepartmentAllocations,
buildDepartmentAllocationFooterTotals,
buildCustomerAllocationRows,
buildMonthComparison,
buildSourceComposition,
normalizeDistributionQueryState,
normalizeCompareResult,
aggregateCompareRows,
sortCompareRows,
sortMonthSummaries,
} from "@/views/dashboards/superUserDashboard/invoiceDistribution/imports/invoiceDistributionCalculations.js";
describe("invoice distribution calculations", () => {
it("builds month ranges from first month to current month", () => {
const result = getMonthRange(new Date("2026-01-10T00:00:00Z"), new Date("2026-03-20T00:00:00Z"), true);
expect(result.map((item) => item.key)).toEqual(["2026-03", "2026-02", "2026-01"]);
});
it("builds month summary with booked and source split", () => {
const summary = buildMonthSummary({
year: 2026,
month: 2,
periodData: {
types: {
all: [
{
transactions: [
{ amount: 100, booked: true, excluded: false },
{ amount: 25, booked: false, excluded: false },
{ amount: 30, booked: true, excluded: true },
],
},
],
},
},
fixedDistribution: {
includes: {
collective_fixed_pricing_results: {
total_fixed_price: 40,
},
},
},
subscriptionDistribution: {
includes: {
collective_subscription_results: {
total_subscription_price: 20,
},
},
},
customerPriceDistribution: {
collective_results: {
total_customer_price: 15,
},
},
});
expect(summary.bookedAmount).toBe(100);
expect(summary.fixedPricingAmount).toBe(40);
expect(summary.subscriptionAmount).toBe(20);
expect(summary.customerPriceAmount).toBe(15);
expect(summary.distributionAmount).toBe(60);
expect(summary.otherBookedAmount).toBe(25);
expect(summary.totalAmount).toBe(160);
});
it("prefers actually distributed fixed and subscription amounts for summary totals", () => {
const summary = buildMonthSummary({
year: 2026,
month: 2,
periodData: {
types: {
all: [
{
transactions: [{ amount: 200, booked: true, excluded: false }],
},
],
},
},
fixedDistribution: {
collective_results: {
total_fixed_price: 50,
total_department_totals_relative_parsed: {
Alpha: 30,
Beta: 10,
},
},
},
subscriptionDistribution: {
collective_results: {
total_subscription_price: 120,
subscription_price_department_distribution_parsed: {
Alpha: 70,
Beta: 20,
},
},
},
customerPriceDistribution: {
collective_results: {
total_customer_price: 15,
},
},
});
expect(summary.fixedPricingAmount).toBe(40);
expect(summary.subscriptionAmount).toBe(90);
expect(summary.customerPriceAmount).toBe(15);
expect(summary.distributionAmount).toBe(130);
expect(summary.totalAmount).toBe(330);
});
it("builds department allocations with percentages and booked estimate", () => {
const rows = buildDepartmentAllocations({
departments: [
{ id: 1, name: "Alpha" },
{ id: 2, name: "Beta" },
],
bookedAmount: 300,
fixedDistribution: {
includes: {
collective_fixed_pricing_results: {
total_department_totals_relative_parsed: {
Alpha: 60,
Beta: 40,
},
},
},
},
subscriptionDistribution: {
includes: {
collective_subscription_results: {
subscription_price_department_distribution_parsed: {
Alpha: 30,
},
},
},
},
customerPriceDistribution: {
collective_results: {
customer_price_department_distribution_parsed: {
Beta: 10,
},
},
},
});
expect(rows).toHaveLength(2);
expect(rows[0].departmentName).toBe("Alpha");
expect(rows[0].totalAmount).toBe(90);
expect(rows[0].sharePercent).toBe(69.23);
expect(rows[0].estimatedBookedAllocation).toBe(207.69);
expect(rows[1].departmentName).toBe("Beta");
expect(rows[1].customerPriceAmount).toBe(10);
expect(rows[1].totalAmount).toBe(40);
});
it("builds department footer totals without adding customer prices to the total column", () => {
const totals = buildDepartmentAllocationFooterTotals([
{
fixedAmount: 120000,
subscriptionAmount: 80000,
customerPriceAmount: 15000,
totalAmount: 215000,
},
{
fixedAmount: 40000,
subscriptionAmount: 57000,
customerPriceAmount: 12000,
totalAmount: 109000,
},
]);
expect(totals.fixedAmount).toBe(160000);
expect(totals.subscriptionAmount).toBe(137000);
expect(totals.customerPriceAmount).toBe(27000);
expect(totals.totalAmount).toBe(297000);
});
it("builds customer rows and respects creation date filter", () => {
const rows = buildCustomerAllocationRows({
year: 2026,
month: 2,
departments: [{ id: 1, name: "Alpha" }],
fixedDistribution: {
data: [
{
id: 1,
customer_number: 10,
customer_name: "Customer A",
meta: {
fixed_pricing: {
created_at: "2026-02-01T00:00:00Z",
price: 100,
original_price: 140,
department_totals_relative: { 1: 100 },
},
},
},
{
id: 2,
customer_number: 20,
customer_name: "Future Customer",
meta: {
fixed_pricing: {
created_at: "2026-03-01T00:00:00Z",
price: 100,
original_price: 140,
department_totals_relative: { 1: 100 },
},
},
},
],
},
subscriptionDistribution: {
data: [],
},
customerPriceDistribution: {
customers: [
{
id: 7,
customer_number: 99,
customer_name: "Discount Customer",
transactions: [{ id: 1, amount: 22, booked: true, excluded: false, date: "2026-02-05T00:00:00Z" }],
meta: {
customer_price: {
created_at: "2026-02-05T00:00:00Z",
price: 22,
department_totals_relative: { 1: 22 },
},
},
},
],
},
});
expect(rows).toHaveLength(2);
expect(rows[0].customerName).toBe("Customer A");
expect(rows[0].departmentAllocations).toEqual({ Alpha: 100 });
expect(rows[1].source).toBe("customer_prices");
});
it("builds month-over-month comparison deltas", () => {
const comparison = buildMonthComparison(
{ totalAmount: 200, bookedAmount: 140, distributionAmount: 60 },
{ totalAmount: 100, bookedAmount: 120, distributionAmount: 30 }
);
expect(comparison.totalAmount.delta).toBe(100);
expect(comparison.bookedAmount.delta).toBe(20);
expect(comparison.distributionAmount.delta).toBe(30);
});
it("normalizes and serializes distribution query state", () => {
const normalized = normalizeDistributionQueryState({
compareMonth: "2026-02",
compareMode: "line_by_line",
customerSearch: " acme ",
customerSource: "customer_prices",
customerDepartment: "Ops",
departmentSearch: " nord ",
});
expect(normalized).toEqual({
compareMonth: "2026-02",
compareMode: "line_by_line",
compareVisibility: "all",
customerSearch: "acme",
customerSource: "customer_prices",
customerDepartment: "Ops",
departmentSearch: "nord",
});
const serialized = buildDistributionQueryState(normalized);
expect(serialized).toEqual({
compareMonth: "2026-02",
compareMode: "line_by_line",
customerSearch: "acme",
customerSource: "customer_prices",
customerDepartment: "Ops",
departmentSearch: "nord",
});
});
it("sorts month summaries and compare rows for decision-first tables", () => {
const sortedMonths = sortMonthSummaries(
[
{ key: "2026-01", year: 2026, month: 1, totalAmount: 10 },
{ key: "2026-03", year: 2026, month: 3, totalAmount: 40 },
{ key: "2026-02", year: 2026, month: 2, totalAmount: 20 },
],
"month",
"desc"
);
expect(sortedMonths.map((row) => row.key)).toEqual(["2026-03", "2026-02", "2026-01"]);
const sortedTotals = sortMonthSummaries(sortedMonths, "total", "asc");
expect(sortedTotals.map((row) => row.totalAmount)).toEqual([10, 20, 40]);
const sortedCompare = sortCompareRows([
{ invoiceId: 3, status: "ok", warningCount: 0, difference: 0 },
{ invoiceId: 2, status: "ok", warningCount: 2, difference: 4 },
{ invoiceId: 1, status: "mismatch", warningCount: 1, difference: 10 },
]);
expect(sortedCompare.map((row) => row.invoiceId)).toEqual([1, 2, 3]);
});
it("builds source composition percentages for KPI cues", () => {
const composition = buildSourceComposition({
fixedPricingAmount: 60,
subscriptionAmount: 40,
customerPriceAmount: 20,
distributionAmount: 100,
});
expect(composition.fixedPercent).toBe(50);
expect(composition.subscriptionPercent).toBe(33.33);
expect(composition.customerPricePercent).toBe(16.67);
});
it("normalizes compare payloads for both modes and aggregates", () => {
const okRow = normalizeCompareResult(
{ id: 1, customer_name: "A", total_net_amount: 100 },
{
internal_total: 100,
booked_total: 100,
difference: 0,
warnings: [],
order_ids: [1, 2],
},
"invoice_total"
);
const mismatchRow = normalizeCompareResult(
{ id: 2, customer_name: "B", total_net_amount: 100 },
{
internal_total: 100,
booked_total: 90,
difference: 10,
warnings: ["Line mismatch"],
order_ids: [3],
},
"line_by_line"
);
const v2Row = normalizeCompareResult(
{ id: 3, customer_name: "C", total_net_amount: 200 },
{
collected_invoice_id: 3,
warnings: ["Top-level warning"],
details: {
order_ids: [10, 20],
internal: {
normalized: {
totals: {
net_total: 200,
billable_line_count: 2,
},
},
},
customer: {
name: "Customer V2",
},
},
comparison: {
totals: {
internal_net_total: 200,
},
targets: {
booked: {
status: "partial_mismatch",
overall_match: false,
totals: {
target_net_total: 180,
},
mismatch_reasons: ["department_total_mismatch"],
lines: {
summary: {
mismatch_count: 1,
target_billable_count: 2,
},
},
},
},
warnings: ["comparison warning"],
},
},
"line_by_line"
);
expect(okRow.status).toBe("ok");
expect(mismatchRow.status).toBe("mismatch");
expect(mismatchRow.lineMismatchCount).toBe(1);
expect(v2Row.status).toBe("mismatch");
expect(v2Row.bookedTotal).toBe(180);
expect(v2Row.difference).toBe(20);
expect(v2Row.mismatchReasons).toContain("department_total_mismatch");
expect(v2Row.lineMismatchCount).toBe(1);
const aggregate = aggregateCompareRows([okRow, mismatchRow, v2Row]);
expect(aggregate.total).toBe(3);
expect(aggregate.mismatches).toBe(2);
expect(aggregate.warningCount).toBe(4);
expect(aggregate.totalDifference).toBe(30);
});
});