Add invoicing billing period calculations module with sum, net amount, and view total functions; include comprehensive unit tests
This commit is contained in:
+64
@@ -0,0 +1,64 @@
|
||||
const toNumber = (value) => {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : 0;
|
||||
};
|
||||
|
||||
export const sumTransactions = (transactions = [], options = {}) => {
|
||||
const { bookedOnly = false, excludeExcluded = true } = options;
|
||||
return (Array.isArray(transactions) ? transactions : []).reduce((sum, transaction) => {
|
||||
if (excludeExcluded && transaction?.excluded) {
|
||||
return sum;
|
||||
}
|
||||
if (bookedOnly && !transaction?.booked) {
|
||||
return sum;
|
||||
}
|
||||
return sum + toNumber(transaction?.amount);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
export const getCustomerNetAmount = (customer = {}, options = {}, fixedPricingCustomer = null) => {
|
||||
const settings = {
|
||||
includeFixedPricing: false,
|
||||
includeTransactions: true,
|
||||
includeVehicleSubscriptions: false,
|
||||
...options,
|
||||
};
|
||||
|
||||
if (settings.includeFixedPricing) {
|
||||
const fixedPrice = fixedPricingCustomer?.meta?.fixed_pricing?.price;
|
||||
if (fixedPrice !== undefined && fixedPrice !== null) {
|
||||
return toNumber(fixedPrice).toFixed(2);
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.includeVehicleSubscriptions) {
|
||||
// Placeholder for future vehicle-subscription specific logic.
|
||||
}
|
||||
|
||||
if (settings.includeTransactions) {
|
||||
return sumTransactions(customer?.transactions).toFixed(2);
|
||||
}
|
||||
|
||||
return "0.00";
|
||||
};
|
||||
|
||||
export const getViewTotals = (customers = []) => {
|
||||
const safeCustomers = Array.isArray(customers) ? customers : [];
|
||||
const total = safeCustomers.reduce((sum, customer) => {
|
||||
if (customer?.meta?.fixed_pricing?.price !== undefined && customer?.meta?.fixed_pricing?.price !== null) {
|
||||
return sum + toNumber(customer.meta.fixed_pricing.price);
|
||||
}
|
||||
return sum + sumTransactions(customer?.transactions);
|
||||
}, 0);
|
||||
|
||||
const booked = safeCustomers.reduce((sum, customer) => {
|
||||
return sum + sumTransactions(customer?.transactions, { bookedOnly: true });
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
total,
|
||||
booked,
|
||||
notBooked: total - booked,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
getCustomerNetAmount,
|
||||
getViewTotals,
|
||||
sumTransactions,
|
||||
} from "@/views/dashboards/superUserDashboard/InvoicingBillingPeriod/imports/invoicingBillingPeriodCalculations.js";
|
||||
|
||||
describe("invoicing billing period calculations", () => {
|
||||
it("sums transactions and excludes excluded entries by default", () => {
|
||||
const sum = sumTransactions([
|
||||
{ amount: 100, excluded: false },
|
||||
{ amount: 50, excluded: true },
|
||||
{ amount: "25.5", excluded: false },
|
||||
]);
|
||||
expect(sum).toBe(125.5);
|
||||
});
|
||||
|
||||
it("supports booked-only transaction summing", () => {
|
||||
const sum = sumTransactions(
|
||||
[
|
||||
{ amount: 100, booked: true, excluded: false },
|
||||
{ amount: 30, booked: false, excluded: false },
|
||||
{ amount: 20, booked: true, excluded: true },
|
||||
],
|
||||
{ bookedOnly: true }
|
||||
);
|
||||
expect(sum).toBe(100);
|
||||
});
|
||||
|
||||
it("uses fixed pricing when requested and available", () => {
|
||||
const value = getCustomerNetAmount(
|
||||
{ transactions: [{ amount: 99, excluded: false }] },
|
||||
{ includeFixedPricing: true, includeTransactions: true, includeVehicleSubscriptions: false },
|
||||
{ meta: { fixed_pricing: { price: 250 } } }
|
||||
);
|
||||
expect(value).toBe("250.00");
|
||||
});
|
||||
|
||||
it("falls back to transaction total when fixed pricing is not included", () => {
|
||||
const value = getCustomerNetAmount(
|
||||
{ transactions: [{ amount: 100, excluded: false }, { amount: 35.2, excluded: false }] },
|
||||
{ includeFixedPricing: false, includeTransactions: true, includeVehicleSubscriptions: false },
|
||||
{ meta: { fixed_pricing: { price: 999 } } }
|
||||
);
|
||||
expect(value).toBe("135.20");
|
||||
});
|
||||
|
||||
it("returns zero amount when all calculation toggles are off", () => {
|
||||
const value = getCustomerNetAmount(
|
||||
{ transactions: [{ amount: 100, excluded: false }] },
|
||||
{ includeFixedPricing: false, includeTransactions: false, includeVehicleSubscriptions: false }
|
||||
);
|
||||
expect(value).toBe("0.00");
|
||||
});
|
||||
|
||||
it("computes total/booked/notBooked view totals", () => {
|
||||
const totals = getViewTotals([
|
||||
{
|
||||
transactions: [
|
||||
{ amount: 100, booked: true, excluded: false },
|
||||
{ amount: 50, booked: false, excluded: false },
|
||||
{ amount: 25, booked: true, excluded: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
meta: { fixed_pricing: { price: 200 } },
|
||||
transactions: [{ amount: 80, booked: true, excluded: false }],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(totals.total).toBe(350);
|
||||
expect(totals.booked).toBe(180);
|
||||
expect(totals.notBooked).toBe(170);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user