import { buildAuditPayload, createSqlClient, getActiveTemplateVersions, summarizeIssues, writeAuditArtifact } from './pdf-audit-utils.ts'; type IntegrityIssue = { kind: | 'DUPLICATE_ACTIVE_TEMPLATE' | 'DUPLICATE_ACTIVE_VERSION' | 'ACTIVE_VERSION_WITHOUT_TEMPLATE' | 'MAPPING_WITHOUT_VERSION' | 'INVALID_APPROVED_TEMPLATE_VERSION'; message: string; context: Record; }; async function main() { const sql = createSqlClient(); try { const [activeTemplateVersions, versionRows, orphanMappingRows, payload] = await Promise.all([ getActiveTemplateVersions(sql), sql` select v.id, v.template_id as "templateId", v.organization_id as "organizationId", v.version, v.is_active as "isActive", t.id as "resolvedTemplateId" from crm_document_template_versions v left join crm_document_templates t on t.id = v.template_id where v.deleted_at is null `, sql` select m.id, m.template_version_id as "templateVersionId", m.placeholder_key as "placeholderKey" from crm_document_template_mappings m left join crm_document_template_versions v on v.id = m.template_version_id where m.deleted_at is null and (v.id is null or v.deleted_at is not null) `, buildAuditPayload(sql) ]); const issues: IntegrityIssue[] = []; const templateBuckets = new Map(); for (const record of activeTemplateVersions) { const bucketKey = [ record.organizationId, record.documentType, record.productType, record.fileType ].join(':'); const items = templateBuckets.get(bucketKey) ?? []; items.push(record); templateBuckets.set(bucketKey, items); } for (const [bucketKey, records] of templateBuckets.entries()) { const templateIds = [...new Set(records.map((record) => record.templateId))]; if (templateIds.length > 1) { issues.push({ kind: 'DUPLICATE_ACTIVE_TEMPLATE', message: `More than one active template exists for ${bucketKey}`, context: { bucketKey, templateIds, templates: records.map((record) => ({ templateId: record.templateId, templateName: record.templateName, versionId: record.versionId, version: record.version })) } }); } } const versionBuckets = new Map>>(); for (const row of versionRows as Array>) { if (!row.isActive) { continue; } const items = versionBuckets.get(String(row.templateId)) ?? []; items.push(row); versionBuckets.set(String(row.templateId), items); if (!row.resolvedTemplateId) { issues.push({ kind: 'ACTIVE_VERSION_WITHOUT_TEMPLATE', message: `Active version ${String(row.id)} has no parent template`, context: row }); } } for (const [templateId, rows] of versionBuckets.entries()) { if (rows.length > 1) { issues.push({ kind: 'DUPLICATE_ACTIVE_VERSION', message: `Template ${templateId} has more than one active version`, context: { templateId, versions: rows.map((row) => ({ id: row.id, version: row.version })) } }); } } for (const row of orphanMappingRows as Array>) { issues.push({ kind: 'MAPPING_WITHOUT_VERSION', message: `Mapping ${String(row.id)} points to a missing template version`, context: row }); } if ( payload.approvedTemplateVersionId && !activeTemplateVersions.some((record) => record.versionId === payload.approvedTemplateVersionId) ) { issues.push({ kind: 'INVALID_APPROVED_TEMPLATE_VERSION', message: 'Audit quotation approvedTemplateVersionId does not resolve to an active template version', context: { quotationCode: payload.quotationCode, approvedTemplateVersionId: payload.approvedTemplateVersionId } }); } const status = summarizeIssues(issues.length ? ['FAIL'] : ['PASS']); const report = { audit: 'template-version-integrity', overallStatus: status, activeTemplateCount: activeTemplateVersions.length, quotationCode: payload.quotationCode, approvedTemplateVersionId: payload.approvedTemplateVersionId, issues }; const artifactPath = await writeAuditArtifact('template-version-report.json', report); console.log(JSON.stringify({ ...report, artifactPath }, null, 2)); } finally { await sql.end({ timeout: 5 }); } } main().catch((error) => { console.error(`[audit-template-version-integrity] ${error.message}`); process.exit(1); });