Files
pn-new-crm/tests/unit/services/reports/sales-comparison.test.ts
Matt 681b94a8ef feat(reports): prior-period comparison toggle on the Sales report
Adds a "Compare to prior period" toggle to the Sales report header.
When on, the API recomputes the KPI window for the equal-length window
immediately preceding the selected range (previousPeriodBounds) behind
`?compare=1`, and the five window-derived KPI tiles (Won, Lost, Win
rate, Avg time-to-close, New leads) render colour-correct "vs prior"
deltas. Point-in-time tiles (Active interests, Pipeline value) have no
prior-window analogue and intentionally show no delta. The prior-window
query runs in parallel with the main batch and resolves to null when the
toggle is off (zero cost). Toggle state persists in the saved-template
config.

Closes the spec's "period comparison on every report" gap for Sales;
Operational already rendered period-start deltas.

Pure helpers TDD'd: previousPeriodBounds (range.ts) +
computeSalesKpiComparison (sales-comparison.ts), 7 unit tests. tsc +
lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:49:35 +02:00

68 lines
2.2 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { computeSalesKpiComparison } from '@/lib/services/reports/sales-comparison';
import type { SalesKpis } from '@/lib/services/reports/sales.service';
function makeKpis(overrides: Partial<SalesKpis>): SalesKpis {
return {
activeInterests: 0,
wonInWindow: 0,
lostInWindow: 0,
lossBreakdown: [],
winRate: null,
pipelineValue: 0,
pipelineValueCurrency: 'EUR',
pipelineValueExcludedCount: 0,
pipelineValueTotalActiveCount: 0,
medianTimeToCloseDays: null,
timeToCloseSampleSize: 0,
newLeadsInWindow: 0,
newLeadsBySource: [],
...overrides,
};
}
describe('computeSalesKpiComparison', () => {
it('diffs the window-derived count fields (current - prior)', () => {
const current = makeKpis({ wonInWindow: 10, lostInWindow: 4, newLeadsInWindow: 20 });
const prior = makeKpis({ wonInWindow: 6, lostInWindow: 5, newLeadsInWindow: 12 });
const deltas = computeSalesKpiComparison(current, prior);
expect(deltas.wonInWindow).toBe(4);
expect(deltas.lostInWindow).toBe(-1);
expect(deltas.newLeadsInWindow).toBe(8);
});
it('diffs winRate as a fraction when both periods have a rate', () => {
const current = makeKpis({ winRate: 0.5 });
const prior = makeKpis({ winRate: 0.4 });
const deltas = computeSalesKpiComparison(current, prior);
expect(deltas.winRate).toBeCloseTo(0.1, 10);
});
it('returns null winRate delta when either period has no rate', () => {
expect(
computeSalesKpiComparison(makeKpis({ winRate: 0.5 }), makeKpis({ winRate: null })).winRate,
).toBeNull();
expect(
computeSalesKpiComparison(makeKpis({ winRate: null }), makeKpis({ winRate: 0.5 })).winRate,
).toBeNull();
});
it('diffs medianTimeToCloseDays only when both periods have a median', () => {
expect(
computeSalesKpiComparison(
makeKpis({ medianTimeToCloseDays: 30 }),
makeKpis({ medianTimeToCloseDays: 45 }),
).medianTimeToCloseDays,
).toBe(-15);
expect(
computeSalesKpiComparison(
makeKpis({ medianTimeToCloseDays: null }),
makeKpis({ medianTimeToCloseDays: 45 }),
).medianTimeToCloseDays,
).toBeNull();
});
});