import type { DocumentTemplateMappingWithColumns, ResolvedDocumentTemplate } from '@/features/foundation/document-template/types'; import type { QuotationDocumentData } from '../types'; import { buildPdfTopicPages } from './pdf-topic-engine'; import { resolveTemplatePages } from './page-resolver'; import { buildProductItemSectionModel } from './product-item-engine'; import { createRenderContext, resolveRenderPolicies } from './render-context'; import { resolveQuotationStaticTemplateInputs } from './static-input-resolver'; import type { BuiltSection, ProductItemSection, QuotationPdfRuntimeResult, RenderContext, ResolvedTemplate, SectionBuilder, } from './runtime-contracts'; import { composeSections } from './section-composer'; import { adaptTemplateForSectionRuntime } from './template-compatibility-adapter'; import { assembleTemplate } from './template-assembler'; function cloneTemplate(value: T): T { return JSON.parse(JSON.stringify(value)) as T; } export function createCustomerSectionBuilder(): SectionBuilder { return { role: 'customer', build(context: RenderContext): BuiltSection<'customer'> { const page = context.pages.byRole.customer?.[0] ?? null; const policy = context.policyBySection.get('customer'); if (!page) { return { role: 'customer', enabled: policy?.enabled ?? false, rendered: false, anchorPageIndex: null, pages: [], templateInputPatch: {}, issues: [ { code: 'MISSING_MARKER', severity: policy?.required ? 'error' : 'warning', message: 'Customer section page could not be resolved.', }, ], }; } return { role: 'customer', enabled: true, rendered: true, anchorPageIndex: page.pageIndex, pages: [cloneTemplate(page.schema)], templateInputPatch: {}, issues: [], }; }, }; } export function createProductItemSectionBuilder(): SectionBuilder { return { role: 'product_items', build(context: RenderContext): ProductItemSection { const built = buildProductItemSectionModel({ items: context.documentData.items, currencyCode: context.documentData.quotation.currencyCode, }); return { role: 'product_items', enabled: true, rendered: false, anchorPageIndex: null, pages: [], templateInputPatch: {}, issues: built.issues, rows: built.rows, tableModel: built.tableModel, pagination: built.pagination, }; }, }; } export function createTopicSectionBuilder(): SectionBuilder { return { role: 'topics', build(context: RenderContext): BuiltSection<'topics'> { const page = context.pages.byRole.topics?.[0] ?? null; const policy = context.policyBySection.get('topics'); if (!page) { return { role: 'topics', enabled: policy?.enabled ?? false, rendered: false, anchorPageIndex: null, pages: [], templateInputPatch: {}, issues: [ { code: 'MISSING_MARKER', severity: policy?.required ? 'error' : 'warning', message: 'Topic section page could not be resolved.', }, ], }; } const built = buildPdfTopicPages(page.schema, context.documentData.topics.all, { basePageIndex: page.pageIndex, }); return { role: 'topics', enabled: true, rendered: true, anchorPageIndex: page.pageIndex, pages: built.pages, templateInputPatch: built.topicInputs, issues: built.issues, }; }, }; } export function getQuotationSectionBuilders(): SectionBuilder[] { return [ createCustomerSectionBuilder(), createProductItemSectionBuilder(), createTopicSectionBuilder(), ]; } function createResolvedTemplate(template: ResolvedDocumentTemplate): ResolvedTemplate { return { documentTemplate: template, schema: cloneTemplate(template.version.schemaJson as import('@pdfme/common').Template), }; } function createRuntimeErrorMessage(): string { return 'Quotation PDF runtime invalid active document template.'; } export async function buildQuotationPdfRuntime(args: { documentData: QuotationDocumentData; template: ResolvedDocumentTemplate; mappings: DocumentTemplateMappingWithColumns[]; templateInput: Record; }): Promise { const resolvedTemplate = createResolvedTemplate(args.template); const compatibleTemplate = adaptTemplateForSectionRuntime(resolvedTemplate); const pages = resolveTemplatePages(compatibleTemplate); const policies = resolveRenderPolicies(); const staticInputPatch = resolveQuotationStaticTemplateInputs(args.documentData); const context = createRenderContext({ documentData: args.documentData, template: resolvedTemplate, mappings: args.mappings, pages, policies, issues: pages.issues, }); const composed = await composeSections(context, getQuotationSectionBuilders()); const assembled = assembleTemplate({ template: resolvedTemplate.schema, templateInput: args.templateInput, staticInputPatch, sections: composed.sections, }); const issues = [...pages.issues, ...composed.issues, ...assembled.issues]; const topicSection = composed.sections.find((section) => section.role === 'topics'); if (issues.some((issue) => issue.severity === 'error')) { throw new Error(createRuntimeErrorMessage(), { cause: issues }); } return { assembled, topicInputs: topicSection ? (topicSection.templateInputPatch as Record) : {}, issues, pages, policies, }; }