UNPKG

@accounter/server

Version:
157 lines 6.26 kB
import { z } from 'zod'; const dynamicReportNodeData = z .object({ nodeType: z.enum(['synthetic-branch', 'sort-code-branch', 'financial-entity']), isOpen: z.boolean(), hebrewText: z.string().optional(), }) .strict(); const dynamicReportNode = z .object({ id: z.union([z.string(), z.number()]), parent: z.union([z.string(), z.number()]), text: z.string(), droppable: z.boolean(), data: dynamicReportNodeData, }) .strict(); export const dynamicReportTemplate = z.array(dynamicReportNode); export function parseTemplate(raw) { const parsed = JSON.parse(raw); return dynamicReportTemplate.parse(parsed); } export function validateTemplate(raw) { const parsed = JSON.parse(raw); const validated = dynamicReportTemplate.safeParse(parsed); if (!validated.success) { throw new Error(`Error validating report template: ${validated.error}`); } return true; } // ── Legacy format ────────────────────────────────────────────────────────────── const legacyDynamicReportNodeData = z .object({ descendantSortCodes: z.union([z.array(z.number()), z.null()]).optional(), descendantFinancialEntities: z.union([z.array(z.string().uuid()), z.null()]).optional(), mergedSortCodes: z.union([z.array(z.number()), z.null()]).optional(), sortCode: z.number().optional(), isOpen: z.boolean(), hebrewText: z.string().optional(), }) .loose(); // eslint-disable-next-line @typescript-eslint/no-unused-vars const legacyDynamicReportNode = z .object({ id: z.union([z.string(), z.number()]), parent: z.union([z.string(), z.number()]), text: z.string(), droppable: z.boolean(), data: legacyDynamicReportNodeData, }) .loose(); /** * Returns true when the raw parsed node array contains at least one node * whose `data` object has a `descendantSortCodes` key — the hallmark of the * old implicit-membership format. */ export function isLegacyTemplate(nodes) { return nodes.some(node => node !== null && typeof node === 'object' && 'data' in node && node.data !== null && typeof node.data === 'object' && 'descendantSortCodes' in node.data); } /** * Converts a legacy template (implicit-membership format) to the new * explicit-leaf format given the set of live financial entity IDs that belong * to each sort code. * * @param nodes Parsed legacy node array (validated against the old schema) * @param entityBySortCode Map<sortCodeId, string[]> — entity UUIDs that belong to that sort code * @returns Node array in the new explicit format (no hint arrays, explicit leaf nodes) */ export function migrateLegacyTemplate(nodes, entityBySortCode) { const result = []; const explicitLeafIds = new Set(); const sortCodesWithExplicitNode = new Set(); // Pre-pass: collect existing explicit leaf IDs and sort codes that already // have a dedicated branch node in the legacy template. for (const node of nodes) { if (!node.droppable) { explicitLeafIds.add(String(node.id)); } else if ('sortCode' in node.data && node.data.sortCode != null) { sortCodesWithExplicitNode.add(node.data.sortCode); } } // Main pass: migrate branch nodes and inject missing leaf nodes. for (const node of nodes) { if (node.droppable) { const hasSortCode = 'sortCode' in node.data && node.data.sortCode != null; const nodeType = hasSortCode ? 'sort-code-branch' : 'synthetic-branch'; result.push({ id: node.id, parent: node.parent, text: node.text, droppable: true, data: { nodeType, isOpen: node.data.isOpen, ...(node.data.hebrewText == null ? {} : { hebrewText: node.data.hebrewText }), }, }); // Inject explicit leaf nodes from descendantFinancialEntities. const entities = node.data.descendantFinancialEntities ?? []; for (const uuid of entities) { if (!explicitLeafIds.has(uuid)) { explicitLeafIds.add(uuid); result.push({ id: uuid, parent: node.id, text: uuid, droppable: false, data: { nodeType: 'financial-entity', isOpen: false }, }); } } // Inject explicit leaf nodes for descendant sort codes that have no // dedicated branch node in the legacy template. Their entities are // resolved via the entityBySortCode map supplied by the caller. for (const sc of node.data.descendantSortCodes ?? []) { if (sortCodesWithExplicitNode.has(sc)) continue; for (const uuid of entityBySortCode.get(sc) ?? []) { if (!explicitLeafIds.has(uuid)) { explicitLeafIds.add(uuid); result.push({ id: uuid, parent: node.id, text: uuid, droppable: false, data: { nodeType: 'financial-entity', isOpen: false }, }); } } } } else { // Existing explicit leaf — migrate data shape. result.push({ id: node.id, parent: node.parent, text: node.text, droppable: false, data: { nodeType: 'financial-entity', isOpen: node.data.isOpen, ...(node.data.hebrewText == null ? {} : { hebrewText: node.data.hebrewText }), }, }); } } return result; } //# sourceMappingURL=dynamic-report.helper.js.map