taks-p.4.3

This commit is contained in:
phaichayon
2026-06-29 11:18:08 +07:00
parent 2540d52670
commit 0c0450b152
20 changed files with 4900 additions and 505 deletions

View File

@@ -0,0 +1,162 @@
import type { Template } from '@pdfme/common';
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 { createRenderContext, resolveRenderPolicies } from './render-context';
import type {
BuiltSection,
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;
}
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: [],
};
},
};
}
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,
};
},
};
}
function getQuotationSectionBuilders(): SectionBuilder[] {
return [createCustomerSectionBuilder(), createTopicSectionBuilder()];
}
function createResolvedTemplate(
template: ResolvedDocumentTemplate,
): ResolvedTemplate {
return {
documentTemplate: template,
schema: cloneTemplate(template.version.schemaJson as Template),
};
}
function createRuntimeErrorMessage(): string {
return 'Quotation PDF runtime is invalid for the 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 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,
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,
};
}