task p-4.5
This commit is contained in:
@@ -35,6 +35,7 @@ export interface ActiveTemplateVersionRecord {
|
||||
fileType: string;
|
||||
versionId: string;
|
||||
version: string;
|
||||
filePath: string | null;
|
||||
schemaJson: unknown;
|
||||
}
|
||||
|
||||
@@ -152,6 +153,7 @@ export const PDFME_STATIC_LABEL_FIELD_KEYS = [
|
||||
'project_label',
|
||||
'site_label'
|
||||
] as const;
|
||||
export type TemplateSourceVariant = 'legacy' | 'product-v1';
|
||||
export const SUPPORTED_TEMPLATE_MAPPING_SEEDS: TemplateMappingSeed[] = [
|
||||
{
|
||||
placeholderKey: 'customer_name',
|
||||
@@ -327,11 +329,18 @@ export const SUPPORTED_TEMPLATE_MAPPING_SEEDS: TemplateMappingSeed[] = [
|
||||
formatMask: 'currency'
|
||||
},
|
||||
{
|
||||
columnName: 'Total',
|
||||
sourceField: 'totalPrice',
|
||||
columnName: 'Discount',
|
||||
sourceField: 'discount',
|
||||
columnLetter: 'F',
|
||||
sortOrder: 6,
|
||||
formatMask: 'currency'
|
||||
},
|
||||
{
|
||||
columnName: 'Total',
|
||||
sourceField: 'totalPrice',
|
||||
columnLetter: 'G',
|
||||
sortOrder: 7,
|
||||
formatMask: 'currency'
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -665,12 +674,23 @@ export function normalizeTemplateInputAliases(
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function loadTemplateSourceByOrganizationName(organizationName: string) {
|
||||
export function loadTemplateSourceByOrganizationName(
|
||||
organizationName: string,
|
||||
variant: TemplateSourceVariant = 'legacy',
|
||||
) {
|
||||
const normalized = organizationName.toUpperCase();
|
||||
const fileName = normalized.includes('ONVALLA')
|
||||
? 'ONVALLA_template_pdfme_fainal3.json'
|
||||
? (
|
||||
variant === 'product-v1'
|
||||
? 'ONVALLA_template_pdfme_product_v1.json'
|
||||
: 'ONVALLA_template_pdfme_fainal3.json'
|
||||
)
|
||||
: normalized.includes('ALLA')
|
||||
? 'ALLA_template_pdfme_fainal3.json'
|
||||
? (
|
||||
variant === 'product-v1'
|
||||
? 'ALLA_template_pdfme_product_v1.json'
|
||||
: 'ALLA_template_pdfme_fainal3.json'
|
||||
)
|
||||
: null;
|
||||
|
||||
if (!fileName) {
|
||||
@@ -686,6 +706,26 @@ export function loadTemplateSourceByOrganizationName(organizationName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
export function loadTemplateSourceByRelativePath(relativePath: string | null | undefined) {
|
||||
if (!relativePath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalizedRelativePath = relativePath.replaceAll('\\', '/');
|
||||
const absolutePath = path.resolve(process.cwd(), normalizedRelativePath);
|
||||
|
||||
if (!fs.existsSync(absolutePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
fileName: path.basename(normalizedRelativePath),
|
||||
relativePath: normalizedRelativePath,
|
||||
absolutePath,
|
||||
schemaJson: JSON.parse(fs.readFileSync(absolutePath, 'utf8')) as unknown
|
||||
};
|
||||
}
|
||||
|
||||
export function computeFieldDiff(
|
||||
currentFields: TemplateFieldRecord[],
|
||||
previousFields: TemplateFieldRecord[]
|
||||
@@ -739,16 +779,17 @@ function normalizeSnapshotPayload(snapshot: unknown) {
|
||||
|
||||
export async function getActiveTemplateVersions(sql: SqlClient): Promise<ActiveTemplateVersionRecord[]> {
|
||||
const rows = await sql`
|
||||
select
|
||||
t.id as "templateId",
|
||||
t.template_name as "templateName",
|
||||
t.organization_id as "organizationId",
|
||||
t.document_type as "documentType",
|
||||
t.product_type as "productType",
|
||||
t.file_type as "fileType",
|
||||
v.id as "versionId",
|
||||
v.version,
|
||||
v.schema_json as "schemaJson"
|
||||
select
|
||||
t.id as "templateId",
|
||||
t.template_name as "templateName",
|
||||
t.organization_id as "organizationId",
|
||||
t.document_type as "documentType",
|
||||
t.product_type as "productType",
|
||||
t.file_type as "fileType",
|
||||
v.id as "versionId",
|
||||
v.version,
|
||||
v.file_path as "filePath",
|
||||
v.schema_json as "schemaJson"
|
||||
from crm_document_templates t
|
||||
inner join crm_document_template_versions v
|
||||
on v.template_id = t.id
|
||||
@@ -975,6 +1016,55 @@ function formatTopicItems(items: Array<{ content?: string | null; sortOrder?: nu
|
||||
.map((item) => [item.content?.trim() || '-']);
|
||||
}
|
||||
|
||||
function formatPdfDecimal(amount: number | string | null | undefined) {
|
||||
if (amount === null || amount === undefined) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
const normalized =
|
||||
typeof amount === 'number' ? amount : Number(String(amount).replaceAll(',', ''));
|
||||
|
||||
if (!Number.isFinite(normalized)) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 3
|
||||
}).format(normalized);
|
||||
}
|
||||
|
||||
function buildAuditItemsTable(
|
||||
items: Array<{
|
||||
itemNumber?: number | null;
|
||||
description?: string | null;
|
||||
quantity?: number | null;
|
||||
unitLabel?: string | null;
|
||||
unitPrice?: number | null;
|
||||
discount?: number | null;
|
||||
totalPrice?: number | null;
|
||||
notes?: string | null;
|
||||
}>,
|
||||
currencyCode?: string | null
|
||||
) {
|
||||
return items.map((item, index) => {
|
||||
const description = [item.description?.trim(), item.notes?.trim()]
|
||||
.filter((value): value is string => Boolean(value))
|
||||
.join('\n')
|
||||
.trim();
|
||||
|
||||
return [
|
||||
String(item.itemNumber ?? index + 1),
|
||||
description || '-',
|
||||
formatPdfDecimal(item.quantity ?? null),
|
||||
item.unitLabel?.trim() || '-',
|
||||
formatPdfCurrency(item.unitPrice ?? null, currencyCode?.trim() || 'THB'),
|
||||
formatPdfCurrency(item.discount ?? 0, currencyCode?.trim() || 'THB'),
|
||||
formatPdfCurrency(item.totalPrice ?? null, currencyCode?.trim() || 'THB')
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
function applyFormatMask(value: unknown, formatMask?: string | null) {
|
||||
if (value === null || value === undefined) {
|
||||
return value;
|
||||
@@ -1436,10 +1526,22 @@ export async function buildAuditPayload(sql: SqlClient): Promise<AuditPayload> {
|
||||
),
|
||||
...topicInputs
|
||||
};
|
||||
const normalizedTemplateInput = normalizeTemplateInputAliases(
|
||||
extractTemplateFields(templateVersion.schemaJson),
|
||||
templateInput
|
||||
);
|
||||
const templateFields = extractTemplateFields(templateVersion.schemaJson);
|
||||
const hasItemsTableField = templateFields.some((field) => field.name === 'items_table');
|
||||
const normalizedTemplateInput = normalizeTemplateInputAliases(
|
||||
templateFields,
|
||||
{
|
||||
...templateInput,
|
||||
...(hasItemsTableField
|
||||
? {
|
||||
items_table: buildAuditItemsTable(
|
||||
(documentData as { items: Parameters<typeof buildAuditItemsTable>[0] }).items,
|
||||
(documentData as { quotation: { currencyCode?: string | null } }).quotation.currencyCode
|
||||
)
|
||||
}
|
||||
: {}),
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
organizationId: quotation.organizationId,
|
||||
|
||||
Reference in New Issue
Block a user