Introduce footer totals for department allocations:

- Add `buildDepartmentAllocationFooterTotals` utility to compute department footer totals, excluding customer prices from the total column.
- Update department allocation summaries to prioritize actual distributions for fixed and subscription amounts.
- Adjust distribution metrics calculation logic and related tests for consistency.
- Enhance footer visuals with clearly marked totals and accompanying notes.
- Expand `buildDepartmentAllocations` to properly account for totals in UI rendering.
- Update associated unit tests to cover footer totals and exclude customer price impacts.
This commit is contained in:
Jeppe Bundgaard
2026-03-16 13:04:56 +01:00
parent aba9f63788
commit 412339744f
5 changed files with 218 additions and 45 deletions
@@ -4,6 +4,7 @@ import {
getMonthRange,
buildMonthSummary,
buildDepartmentAllocations,
buildDepartmentAllocationFooterTotals,
buildCustomerAllocationRows,
buildMonthComparison,
buildSourceComposition,
@@ -62,9 +63,56 @@ describe('invoice distribution calculations', () => {
expect(summary.fixedPricingAmount).toBe(40);
expect(summary.subscriptionAmount).toBe(20);
expect(summary.customerPriceAmount).toBe(15);
expect(summary.distributionAmount).toBe(75);
expect(summary.distributionAmount).toBe(60);
expect(summary.otherBookedAmount).toBe(25);
expect(summary.totalAmount).toBe(175);
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', () => {
@@ -105,8 +153,33 @@ describe('invoice distribution calculations', () => {
expect(rows).toHaveLength(2);
expect(rows[0].departmentName).toBe('Alpha');
expect(rows[0].totalAmount).toBe(90);
expect(rows[0].sharePercent).toBe(64.29);
expect(rows[0].estimatedBookedAllocation).toBe(192.86);
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', () => {
@@ -196,6 +269,7 @@ describe('invoice distribution calculations', () => {
expect(normalized).toEqual({
compareMonth: '2026-02',
compareMode: 'line_by_line',
compareVisibility: 'all',
customerSearch: 'acme',
customerSource: 'customer_prices',
customerDepartment: 'Ops',
@@ -241,7 +315,7 @@ describe('invoice distribution calculations', () => {
fixedPricingAmount: 60,
subscriptionAmount: 40,
customerPriceAmount: 20,
distributionAmount: 120,
distributionAmount: 100,
});
expect(composition.fixedPercent).toBe(50);
expect(composition.subscriptionPercent).toBe(33.33);