task-h.5.3
This commit is contained in:
@@ -57,6 +57,29 @@ export interface MappingColumnRecord {
|
||||
formatMask: string | null;
|
||||
}
|
||||
|
||||
export type TemplateFieldClassification =
|
||||
| 'static input field'
|
||||
| 'dynamic topic template'
|
||||
| 'readonly/static visual field'
|
||||
| 'system field'
|
||||
| 'unknown field';
|
||||
|
||||
export interface TemplateMappingSeed {
|
||||
placeholderKey: string;
|
||||
sourcePath: string;
|
||||
dataType: 'scalar' | 'multiline' | 'table' | 'image';
|
||||
defaultValue?: string | null;
|
||||
formatMask?: string | null;
|
||||
sortOrder: number;
|
||||
columns?: Array<{
|
||||
columnName: string;
|
||||
sourceField: string;
|
||||
columnLetter?: string;
|
||||
sortOrder: number;
|
||||
formatMask?: string | null;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface AuditPayload {
|
||||
organizationId: string;
|
||||
quotationId: string;
|
||||
@@ -83,6 +106,7 @@ export interface AuditPayload {
|
||||
const SYSTEM_FIELD_NAMES = new Set([
|
||||
'',
|
||||
'company1',
|
||||
'company',
|
||||
'company2',
|
||||
'company_addr1',
|
||||
'company_addr2',
|
||||
@@ -97,6 +121,220 @@ const DYNAMIC_TOPIC_FIELD_NAMES = new Set(['topic', 'data_topic']);
|
||||
const LEGACY_DYNAMIC_SEED_TOKENS = new Set(['item_topic']);
|
||||
const DYNAMIC_TOPIC_TOKEN_PREFIXES = ['topic_', 'item_topic_'];
|
||||
const DESIGNER_LABEL_NAME_PATTERNS = [/_label$/i, /_lable$/i];
|
||||
const STATIC_VISUAL_FIELD_NAMES = new Set([
|
||||
'colon',
|
||||
'colon copy',
|
||||
'colon_att',
|
||||
'colon_email',
|
||||
'colon_fax',
|
||||
'colon_project',
|
||||
'colon_site',
|
||||
'colon_tel',
|
||||
'dear_sirs',
|
||||
'line',
|
||||
'yours_faithfuly',
|
||||
'quotation_code_lable',
|
||||
'quotation_date_labe'
|
||||
]);
|
||||
export const PRICE_TABLE_LABEL = 'Price (Exclude VAT)';
|
||||
export const PDFME_STATIC_LABELS = {
|
||||
tel: 'Tel',
|
||||
email: 'Email',
|
||||
att: 'Att',
|
||||
project: 'Project',
|
||||
location: 'Location'
|
||||
} as const;
|
||||
export const PDFME_STATIC_LABEL_FIELD_KEYS = [
|
||||
'tel_label',
|
||||
'email_label',
|
||||
'att_label',
|
||||
'project_label',
|
||||
'site_label'
|
||||
] as const;
|
||||
export const SUPPORTED_TEMPLATE_MAPPING_SEEDS: TemplateMappingSeed[] = [
|
||||
{
|
||||
placeholderKey: 'customer_name',
|
||||
sourcePath: 'customer.name',
|
||||
dataType: 'scalar',
|
||||
sortOrder: 1
|
||||
},
|
||||
{
|
||||
placeholderKey: 'customer_addr',
|
||||
sourcePath: 'customer.address',
|
||||
dataType: 'multiline',
|
||||
sortOrder: 2
|
||||
},
|
||||
{
|
||||
placeholderKey: 'customer_tel',
|
||||
sourcePath: 'customer.phone',
|
||||
dataType: 'scalar',
|
||||
sortOrder: 3
|
||||
},
|
||||
{
|
||||
placeholderKey: 'customer_email',
|
||||
sourcePath: 'customer.email',
|
||||
dataType: 'scalar',
|
||||
sortOrder: 4
|
||||
},
|
||||
{
|
||||
placeholderKey: 'customer_att',
|
||||
sourcePath: 'quotation.attention',
|
||||
dataType: 'scalar',
|
||||
sortOrder: 5
|
||||
},
|
||||
{
|
||||
placeholderKey: 'project_name',
|
||||
sourcePath: 'quotation.projectName',
|
||||
dataType: 'scalar',
|
||||
sortOrder: 6
|
||||
},
|
||||
{
|
||||
placeholderKey: 'site_location',
|
||||
sourcePath: 'quotation.projectLocation',
|
||||
dataType: 'multiline',
|
||||
sortOrder: 7
|
||||
},
|
||||
{
|
||||
placeholderKey: 'quotation_date_data',
|
||||
sourcePath: 'pdfme.quotation_date',
|
||||
dataType: 'scalar',
|
||||
sortOrder: 8
|
||||
},
|
||||
{
|
||||
placeholderKey: 'quotation_code_data',
|
||||
sourcePath: 'quotation.code',
|
||||
dataType: 'scalar',
|
||||
sortOrder: 9
|
||||
},
|
||||
{
|
||||
placeholderKey: 'quotation_price_data',
|
||||
sourcePath: 'pdfme.quotation_price_data',
|
||||
dataType: 'table',
|
||||
defaultValue: JSON.stringify([[PRICE_TABLE_LABEL, '-', ' ', 'THB']]),
|
||||
sortOrder: 10
|
||||
},
|
||||
{
|
||||
placeholderKey: 'quotation_price',
|
||||
sourcePath: 'pdfme.quotation_price',
|
||||
dataType: 'scalar',
|
||||
sortOrder: 11
|
||||
},
|
||||
{
|
||||
placeholderKey: 'currency',
|
||||
sourcePath: 'quotation.currency',
|
||||
dataType: 'scalar',
|
||||
sortOrder: 12
|
||||
},
|
||||
{
|
||||
placeholderKey: 'exclusion_data',
|
||||
sourcePath: 'pdfme.exclusion_data',
|
||||
dataType: 'table',
|
||||
sortOrder: 13
|
||||
},
|
||||
{
|
||||
placeholderKey: 'tel_label',
|
||||
sourcePath: 'pdfme.labels.tel',
|
||||
dataType: 'scalar',
|
||||
defaultValue: PDFME_STATIC_LABELS.tel,
|
||||
sortOrder: 14
|
||||
},
|
||||
{
|
||||
placeholderKey: 'email_label',
|
||||
sourcePath: 'pdfme.labels.email',
|
||||
dataType: 'scalar',
|
||||
defaultValue: PDFME_STATIC_LABELS.email,
|
||||
sortOrder: 15
|
||||
},
|
||||
{
|
||||
placeholderKey: 'att_label',
|
||||
sourcePath: 'pdfme.labels.att',
|
||||
dataType: 'scalar',
|
||||
defaultValue: PDFME_STATIC_LABELS.att,
|
||||
sortOrder: 16
|
||||
},
|
||||
{
|
||||
placeholderKey: 'project_label',
|
||||
sourcePath: 'pdfme.labels.project',
|
||||
dataType: 'scalar',
|
||||
defaultValue: PDFME_STATIC_LABELS.project,
|
||||
sortOrder: 17
|
||||
},
|
||||
{
|
||||
placeholderKey: 'site_label',
|
||||
sourcePath: 'pdfme.labels.location',
|
||||
dataType: 'scalar',
|
||||
defaultValue: PDFME_STATIC_LABELS.location,
|
||||
sortOrder: 18
|
||||
},
|
||||
{
|
||||
placeholderKey: 'app1',
|
||||
sourcePath: 'signatures.preparedBy.name',
|
||||
dataType: 'scalar',
|
||||
defaultValue: '-',
|
||||
sortOrder: 19
|
||||
},
|
||||
{
|
||||
placeholderKey: 'app1_position',
|
||||
sourcePath: 'signatures.preparedBy.position',
|
||||
dataType: 'scalar',
|
||||
defaultValue: '-',
|
||||
sortOrder: 20
|
||||
},
|
||||
{
|
||||
placeholderKey: 'app2',
|
||||
sourcePath: 'signatures.approvedBy.name',
|
||||
dataType: 'scalar',
|
||||
defaultValue: '-',
|
||||
sortOrder: 21
|
||||
},
|
||||
{
|
||||
placeholderKey: 'app2_position',
|
||||
sourcePath: 'signatures.approvedBy.position',
|
||||
dataType: 'scalar',
|
||||
defaultValue: '-',
|
||||
sortOrder: 22
|
||||
},
|
||||
{
|
||||
placeholderKey: 'app3',
|
||||
sourcePath: 'signatures.authorizedBy.name',
|
||||
dataType: 'scalar',
|
||||
defaultValue: '-',
|
||||
sortOrder: 23
|
||||
},
|
||||
{
|
||||
placeholderKey: 'app3_position',
|
||||
sourcePath: 'signatures.authorizedBy.position',
|
||||
dataType: 'scalar',
|
||||
defaultValue: '-',
|
||||
sortOrder: 24
|
||||
},
|
||||
{
|
||||
placeholderKey: 'items_table',
|
||||
sourcePath: 'items',
|
||||
dataType: 'table',
|
||||
sortOrder: 25,
|
||||
columns: [
|
||||
{ columnName: 'Item', sourceField: 'itemNumber', columnLetter: 'A', sortOrder: 1 },
|
||||
{ columnName: 'Description', sourceField: 'description', columnLetter: 'B', sortOrder: 2 },
|
||||
{ columnName: 'Qty', sourceField: 'quantity', columnLetter: 'C', sortOrder: 3 },
|
||||
{ columnName: 'Unit', sourceField: 'unitLabel', columnLetter: 'D', sortOrder: 4 },
|
||||
{
|
||||
columnName: 'Unit Price',
|
||||
sourceField: 'unitPrice',
|
||||
columnLetter: 'E',
|
||||
sortOrder: 5,
|
||||
formatMask: 'currency'
|
||||
},
|
||||
{
|
||||
columnName: 'Total',
|
||||
sourceField: 'totalPrice',
|
||||
columnLetter: 'F',
|
||||
sortOrder: 6,
|
||||
formatMask: 'currency'
|
||||
}
|
||||
]
|
||||
}
|
||||
] as const;
|
||||
const SIGNATURE_ROLE_FALLBACK_LABELS: Record<string, string> = {
|
||||
sales: 'Sales Engineer',
|
||||
sales_support: 'Sales Support',
|
||||
@@ -324,13 +562,7 @@ export function getUnknownFieldNames(
|
||||
return fields
|
||||
.filter(
|
||||
(field) =>
|
||||
field.name &&
|
||||
!SYSTEM_FIELD_NAMES.has(field.name) &&
|
||||
!DYNAMIC_TOPIC_FIELD_NAMES.has(field.name) &&
|
||||
!mappedKeys.has(field.name) &&
|
||||
field.tokens.length === 0 &&
|
||||
field.type !== 'table' &&
|
||||
field.type !== 'text'
|
||||
field.name && classifyTemplateField(field, mappedKeys) === 'unknown field'
|
||||
)
|
||||
.map((field) => field.name)
|
||||
.sort();
|
||||
@@ -344,6 +576,40 @@ export function isDesignerLabelField(field: TemplateFieldRecord) {
|
||||
);
|
||||
}
|
||||
|
||||
export function classifyTemplateField(
|
||||
field: TemplateFieldRecord,
|
||||
mappedKeys: Set<string> = new Set()
|
||||
): TemplateFieldClassification {
|
||||
if (!field.name) {
|
||||
return 'system field';
|
||||
}
|
||||
|
||||
if (SYSTEM_FIELD_NAMES.has(field.name)) {
|
||||
return 'system field';
|
||||
}
|
||||
|
||||
if (DYNAMIC_TOPIC_FIELD_NAMES.has(field.name)) {
|
||||
return 'dynamic topic template';
|
||||
}
|
||||
|
||||
if (mappedKeys.has(field.name)) {
|
||||
return 'static input field';
|
||||
}
|
||||
|
||||
if (isDesignerLabelField(field) || STATIC_VISUAL_FIELD_NAMES.has(field.name)) {
|
||||
return 'readonly/static visual field';
|
||||
}
|
||||
|
||||
if (
|
||||
field.tokens.length === 0 &&
|
||||
['text', 'line', 'rectangle', 'ellipse', 'image'].includes(field.type)
|
||||
) {
|
||||
return 'readonly/static visual field';
|
||||
}
|
||||
|
||||
return 'unknown field';
|
||||
}
|
||||
|
||||
export function getEffectiveMappedKeys(
|
||||
fields: TemplateFieldRecord[],
|
||||
mappings: Array<{ placeholderKey: string }>
|
||||
@@ -398,6 +664,78 @@ export function normalizeTemplateInputAliases(
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function loadTemplateSourceByOrganizationName(organizationName: string) {
|
||||
const normalized = organizationName.toUpperCase();
|
||||
const fileName = normalized.includes('ONVALLA')
|
||||
? 'ONVALLA_template_pdfme_fainal3.json'
|
||||
: normalized.includes('ALLA')
|
||||
? 'ALLA_template_pdfme_fainal3.json'
|
||||
: null;
|
||||
|
||||
if (!fileName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filePath = path.resolve(process.cwd(), 'src', 'pdfme_template', fileName);
|
||||
return {
|
||||
fileName,
|
||||
relativePath: `src/pdfme_template/${fileName}`,
|
||||
absolutePath: filePath,
|
||||
schemaJson: JSON.parse(fs.readFileSync(filePath, 'utf8')) as unknown
|
||||
};
|
||||
}
|
||||
|
||||
export function computeFieldDiff(
|
||||
currentFields: TemplateFieldRecord[],
|
||||
previousFields: TemplateFieldRecord[]
|
||||
) {
|
||||
const currentFieldNames = new Set(currentFields.map((field) => field.name).filter(Boolean));
|
||||
const previousFieldNames = new Set(previousFields.map((field) => field.name).filter(Boolean));
|
||||
|
||||
return {
|
||||
deletedFields: [...previousFieldNames].filter((name) => !currentFieldNames.has(name)).sort(),
|
||||
newFields: [...currentFieldNames].filter((name) => !previousFieldNames.has(name)).sort()
|
||||
};
|
||||
}
|
||||
|
||||
export function buildSupportedMappingsForFieldNames(
|
||||
fieldNames: Set<string>,
|
||||
tokenNames: Set<string> = new Set()
|
||||
) {
|
||||
return SUPPORTED_TEMPLATE_MAPPING_SEEDS.filter(
|
||||
(mapping) => fieldNames.has(mapping.placeholderKey) || tokenNames.has(mapping.placeholderKey)
|
||||
);
|
||||
}
|
||||
|
||||
export function bumpMinorTemplateVersion(version: string) {
|
||||
const [majorPart, minorPart = '0'] = version.split('.');
|
||||
const major = Number(majorPart);
|
||||
const minor = Number(minorPart);
|
||||
|
||||
if (!Number.isInteger(major) || !Number.isInteger(minor)) {
|
||||
return `${version}.1`;
|
||||
}
|
||||
|
||||
return `${major}.${minor + 1}`;
|
||||
}
|
||||
|
||||
function normalizeSnapshotPayload(snapshot: unknown) {
|
||||
if (!snapshot) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof snapshot === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(snapshot) as unknown;
|
||||
return parsed && typeof parsed === 'object' ? (parsed as Record<string, unknown>) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return snapshot && typeof snapshot === 'object' ? (snapshot as Record<string, unknown>) : null;
|
||||
}
|
||||
|
||||
export async function getActiveTemplateVersions(sql: SqlClient): Promise<ActiveTemplateVersionRecord[]> {
|
||||
const rows = await sql`
|
||||
select
|
||||
@@ -475,7 +813,9 @@ export async function findAuditQuotation(sql: SqlClient) {
|
||||
q.attention,
|
||||
q.reference,
|
||||
q.currency,
|
||||
q.subtotal,
|
||||
q.total_amount as "totalAmount",
|
||||
currency_option.code as "currencyCode",
|
||||
q.approved_template_version_id as "approvedTemplateVersionId",
|
||||
q.salesman_id as "salesmanId",
|
||||
q.created_by as "createdBy",
|
||||
@@ -483,6 +823,8 @@ export async function findAuditQuotation(sql: SqlClient) {
|
||||
q.approved_snapshot as "approvedSnapshot",
|
||||
q.approved_pdf_url as "approvedPdfUrl"
|
||||
from crm_quotations q
|
||||
left join ms_options currency_option
|
||||
on currency_option.id = q.currency
|
||||
where q.code = ${AUDIT_QUOTATION_CODE}
|
||||
and q.deleted_at is null
|
||||
limit 1
|
||||
@@ -506,13 +848,17 @@ export async function findAuditQuotation(sql: SqlClient) {
|
||||
q.attention,
|
||||
q.reference,
|
||||
q.currency,
|
||||
q.subtotal,
|
||||
q.total_amount as "totalAmount",
|
||||
currency_option.code as "currencyCode",
|
||||
q.salesman_id as "salesmanId",
|
||||
q.created_by as "createdBy",
|
||||
q.created_at as "createdAt",
|
||||
q.approved_snapshot as "approvedSnapshot",
|
||||
q.approved_pdf_url as "approvedPdfUrl"
|
||||
from crm_quotations q
|
||||
left join ms_options currency_option
|
||||
on currency_option.id = q.currency
|
||||
where q.deleted_at is null
|
||||
order by q.updated_at desc
|
||||
limit 1
|
||||
@@ -584,6 +930,40 @@ function formatPdfCurrency(amount: number | string | null | undefined, currencyC
|
||||
}).format(normalized)}`.trim();
|
||||
}
|
||||
|
||||
function formatPdfCurrencyAmount(amount: number | string | null | undefined, currencyCode = 'THB') {
|
||||
if (amount === null || amount === undefined) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
const normalized = typeof amount === 'number' ? amount : Number(String(amount).replaceAll(',', ''));
|
||||
|
||||
if (!Number.isFinite(normalized)) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
const symbols: Record<string, string> = {
|
||||
THB: '฿',
|
||||
USD: '$',
|
||||
EUR: 'EUR '
|
||||
};
|
||||
const prefix = symbols[currencyCode.toUpperCase()] ?? `${currencyCode.toUpperCase()} `;
|
||||
return `${prefix}${new Intl.NumberFormat('en-US', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
}).format(normalized)}`.trim();
|
||||
}
|
||||
|
||||
function buildPdfPriceTableData(amount: number | string | null | undefined, currencyCode?: string | null) {
|
||||
const normalizedCurrency = currencyCode?.trim() || 'THB';
|
||||
|
||||
return [[
|
||||
PRICE_TABLE_LABEL,
|
||||
formatPdfCurrencyAmount(amount, normalizedCurrency),
|
||||
' ',
|
||||
normalizedCurrency
|
||||
]];
|
||||
}
|
||||
|
||||
function formatTopicItems(items: Array<{ content?: string | null; sortOrder?: number | null }> = []) {
|
||||
if (!items.length) {
|
||||
return [['-']];
|
||||
@@ -611,6 +991,30 @@ function applyFormatMask(value: unknown, formatMask?: string | null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function isBlankString(value: unknown) {
|
||||
return typeof value === 'string' && value.trim().length === 0;
|
||||
}
|
||||
|
||||
function resolveScalarMappingValue(
|
||||
rawValue: unknown,
|
||||
formattedValue: unknown,
|
||||
defaultValue?: string | null
|
||||
) {
|
||||
if (formattedValue !== undefined && formattedValue !== null && !isBlankString(formattedValue)) {
|
||||
return formattedValue;
|
||||
}
|
||||
|
||||
if (rawValue !== undefined && rawValue !== null && !isBlankString(rawValue)) {
|
||||
return rawValue;
|
||||
}
|
||||
|
||||
if (defaultValue !== undefined && defaultValue !== null && defaultValue.trim().length > 0) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return '-';
|
||||
}
|
||||
|
||||
function normalizePdfmeTable(
|
||||
value: unknown,
|
||||
columns?: Array<{ sourceField: string; formatMask?: string | null }>
|
||||
@@ -620,7 +1024,21 @@ function normalizePdfmeTable(
|
||||
}
|
||||
|
||||
if (value.every((row) => Array.isArray(row))) {
|
||||
return (value as unknown[][]).map((row) => row.map((cell) => String(cell ?? '-').trim() || '-'));
|
||||
return (value as unknown[][]).map((row) =>
|
||||
row.map((cell) => {
|
||||
if (typeof cell === 'string') {
|
||||
const trimmedValue = cell.trim();
|
||||
|
||||
if (trimmedValue) {
|
||||
return trimmedValue;
|
||||
}
|
||||
|
||||
return cell.length > 0 ? cell : '-';
|
||||
}
|
||||
|
||||
return String(cell ?? '-').trim() || '-';
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (!columns?.length) {
|
||||
@@ -655,7 +1073,18 @@ function mapDocumentDataToTemplateInput(
|
||||
|
||||
if (mapping.dataType === 'table') {
|
||||
const mappingColumns = columnMap.get(mapping.id) ?? [];
|
||||
result[mapping.placeholderKey] = normalizePdfmeTable(rawValue, mappingColumns);
|
||||
let tableValue = rawValue;
|
||||
|
||||
if (!Array.isArray(tableValue) && mapping.defaultValue) {
|
||||
try {
|
||||
const parsedDefault = JSON.parse(mapping.defaultValue) as unknown;
|
||||
tableValue = Array.isArray(parsedDefault) ? parsedDefault : tableValue;
|
||||
} catch {
|
||||
// ignore invalid legacy table defaults
|
||||
}
|
||||
}
|
||||
|
||||
result[mapping.placeholderKey] = normalizePdfmeTable(tableValue, mappingColumns);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -669,12 +1098,18 @@ function mapDocumentDataToTemplateInput(
|
||||
}
|
||||
|
||||
result[mapping.placeholderKey] =
|
||||
typeof rawValue === 'string' ? rawValue : mapping.defaultValue ?? '';
|
||||
typeof rawValue === 'string' && rawValue.trim().length > 0
|
||||
? rawValue
|
||||
: mapping.defaultValue ?? '-';
|
||||
continue;
|
||||
}
|
||||
|
||||
result[mapping.placeholderKey] =
|
||||
applyFormatMask(rawValue, mapping.formatMask) ?? mapping.defaultValue ?? '-';
|
||||
const formattedValue = applyFormatMask(rawValue, mapping.formatMask);
|
||||
result[mapping.placeholderKey] = resolveScalarMappingValue(
|
||||
rawValue,
|
||||
formattedValue,
|
||||
mapping.defaultValue
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -901,6 +1336,10 @@ export async function buildAuditPayload(sql: SqlClient): Promise<AuditPayload> {
|
||||
const businessRole = (userId ? membershipMap.get(userId) : null) ?? roleCode ?? null;
|
||||
return businessRole ? jobTitleMap.get(businessRole) ?? normalizeBusinessRoleLabel(businessRole) : '-';
|
||||
};
|
||||
const currencyCode =
|
||||
typeof quotation.currencyCode === 'string' && quotation.currencyCode.trim()
|
||||
? quotation.currencyCode.trim()
|
||||
: 'THB';
|
||||
|
||||
const documentData: Record<string, unknown> = {
|
||||
company: {
|
||||
@@ -932,7 +1371,9 @@ export async function buildAuditPayload(sql: SqlClient): Promise<AuditPayload> {
|
||||
projectLocation: quotation.projectLocation,
|
||||
attention: quotation.attention,
|
||||
reference: quotation.reference,
|
||||
currency: quotation.currency,
|
||||
currency: currencyCode,
|
||||
currencyCode,
|
||||
subtotal: quotation.subtotal,
|
||||
totalAmount: quotation.totalAmount
|
||||
},
|
||||
items: (itemRows as Array<Record<string, unknown>>).map((row) => ({
|
||||
@@ -951,8 +1392,10 @@ export async function buildAuditPayload(sql: SqlClient): Promise<AuditPayload> {
|
||||
exclusion: exclusionTopic ? [exclusionTopic] : []
|
||||
},
|
||||
pdfme: {
|
||||
labels: PDFME_STATIC_LABELS,
|
||||
quotation_date: formatPdfDate(quotation.quotationDate as string),
|
||||
quotation_price: formatPdfCurrency(quotation.totalAmount as number, 'THB'),
|
||||
quotation_price: formatPdfCurrency(quotation.subtotal as number, currencyCode),
|
||||
quotation_price_data: buildPdfPriceTableData(quotation.subtotal as number, currencyCode),
|
||||
exclusion_data: formatTopicItems(exclusionTopic?.items ?? []),
|
||||
topic_inputs: topicInputs
|
||||
},
|
||||
@@ -1014,10 +1457,7 @@ export async function buildAuditPayload(sql: SqlClient): Promise<AuditPayload> {
|
||||
itemCount: (itemRows as Array<unknown>).length,
|
||||
partyCount: (partyRows as Array<unknown>).length
|
||||
},
|
||||
approvedSnapshot:
|
||||
quotation.approvedSnapshot && typeof quotation.approvedSnapshot === 'object'
|
||||
? (quotation.approvedSnapshot as Record<string, unknown>)
|
||||
: null,
|
||||
approvedSnapshot: normalizeSnapshotPayload(quotation.approvedSnapshot),
|
||||
approvedArtifactReference:
|
||||
typeof quotation.approvedPdfUrl === 'string' ? quotation.approvedPdfUrl : null
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user