68 lines
2.2 KiB
TypeScript
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();
|
||
|
|
});
|
||
|
|
});
|