191 lines
5.7 KiB
TypeScript
191 lines
5.7 KiB
TypeScript
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<T>(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<string, unknown>;
|
|
}): Promise<QuotationPdfRuntimeResult> {
|
|
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<string, string[][]>)
|
|
: {},
|
|
issues,
|
|
pages,
|
|
policies,
|
|
};
|
|
}
|