Files
pn-new-crm/src/lib/pdf/templates/reports/pipeline-report.ts
Matt Ciaccio 49d92234dd fix(test): align stage names with consolidated pipeline enum
Followup to 886119c (refactor(sales): consolidate pipeline stages) — the
runtime enum was renamed but a few test fixtures and PDF report templates
still referenced the legacy names, leaving them broken at the type level
(36 tsc errors before this fix).

Renames in this commit:
  visited        -> in_communication (alerts test) / removed (PDF reports)
  signed_eoi_nda -> eoi_signed
  contract       -> contract_signed (interests test) / contract_sent (factory)

Affected files: pipeline-report, revenue-report, makeCreateInterestInput
factory, alerts-engine, pipeline-transitions, interest-scoring.

Verification: tsc clean, 858/858 vitest passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:14:04 +02:00

112 lines
3.1 KiB
TypeScript

import type { Template } from '@pdfme/common';
import type { PipelineData } from '@/lib/services/report-generators';
export const pipelineReportTemplate: Template = {
basePdf: 'BLANK_PDF' as unknown as string,
schemas: [
[
{
name: 'reportTitle',
type: 'text',
position: { x: 20, y: 15 },
width: 170,
height: 12,
fontSize: 20,
},
{
name: 'portName',
type: 'text',
position: { x: 20, y: 30 },
width: 130,
height: 8,
fontSize: 11,
},
{
name: 'generatedAt',
type: 'text',
position: { x: 140, y: 30 },
width: 50,
height: 8,
fontSize: 9,
},
{
name: 'summaryText',
type: 'text',
position: { x: 20, y: 50 },
width: 170,
height: 100,
fontSize: 10,
},
{
name: 'detailsText',
type: 'text',
position: { x: 20, y: 160 },
width: 170,
height: 100,
fontSize: 9,
},
],
],
};
export function buildPipelineInputs(
data: PipelineData,
portName?: string,
): Record<string, string>[] {
const stageOrder = [
'open',
'details_sent',
'in_communication',
'eoi_sent',
'eoi_signed',
'deposit_10pct',
'contract_sent',
'contract_signed',
'completed',
];
const summaryLines = stageOrder
.filter((stage) => (data.stageCounts[stage] ?? 0) > 0)
.map((stage) => {
const label = stage.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
return `${label}: ${data.stageCounts[stage] ?? 0} interest(s)`;
});
// Include stages not in standard order
const unknownStages = Object.keys(data.stageCounts).filter((s) => !stageOrder.includes(s));
for (const stage of unknownStages) {
summaryLines.push(`${stage}: ${data.stageCounts[stage]} interest(s)`);
}
const totalInterests = Object.values(data.stageCounts).reduce((a, b) => a + b, 0);
summaryLines.unshift(`Total Active Interests: ${totalInterests}`);
summaryLines.unshift('Pipeline Stage Breakdown');
summaryLines.unshift('─────────────────────');
const detailLines = ['Top Interests by Value', '─────────────────────'];
if (data.topInterests.length === 0) {
detailLines.push('No interests with linked berths found.');
} else {
data.topInterests.forEach((interest, i) => {
const price = interest.berthPrice
? `Berth Price: ${Number(interest.berthPrice).toLocaleString()}`
: 'No berth linked';
const stage = interest.pipelineStage
.replace(/_/g, ' ')
.replace(/\b\w/g, (c) => c.toUpperCase());
detailLines.push(`${i + 1}. Stage: ${stage} | ${price}`);
});
}
return [
{
reportTitle: 'Pipeline Summary Report',
portName: portName ?? 'Port Nimara',
generatedAt: `Generated: ${new Date(data.generatedAt).toLocaleString('en-GB')}`,
summaryText: summaryLines.join('\n'),
detailsText: detailLines.join('\n'),
},
];
}