Files
alla-allaos-fullstack/scripts/audit-pdf-template-inventory.ts
phaichayon a1cac84e2b task p-4.5
2026-06-29 16:07:03 +07:00

126 lines
4.2 KiB
TypeScript

import {
classifyTemplateField,
computeFieldDiff,
createSqlClient,
getEffectiveMappedKeys,
extractTemplateFields,
getActiveTemplateVersions,
getDuplicateFieldNames,
getMappingsForVersion,
getUnknownFieldNames,
summarizeIssues
} from './pdf-audit-utils.ts';
async function main() {
const sql = createSqlClient();
try {
const templates = await getActiveTemplateVersions(sql);
const results = [];
for (const template of templates) {
const fields = extractTemplateFields(template.schemaJson);
const previousVersion =
(
await sql`
select schema_json as "schemaJson", version
from crm_document_template_versions
where template_id = ${template.templateId}
and deleted_at is null
and id <> ${template.versionId}
order by created_at desc
limit 1
`
)[0] ?? null;
const previousFields = previousVersion
? extractTemplateFields(previousVersion.schemaJson)
: [];
const { mappings } = await getMappingsForVersion(sql, template.versionId);
const fieldNames = new Set(fields.map((field) => field.name).filter(Boolean));
const mappedKeys = getEffectiveMappedKeys(fields, mappings);
const duplicateFields = getDuplicateFieldNames(fields);
const unknownFields = getUnknownFieldNames(fields, mappedKeys);
const fieldDiff = computeFieldDiff(fields, previousFields);
const hasProductItemsMarker = fieldNames.has('__section_role__product_items');
const requiredTableFields = hasProductItemsMarker
? ['items_table']
: ['quotation_price_data'];
const missingRequiredTableFields = requiredTableFields.filter(
(fieldName) => !fields.some((field) => field.name === fieldName && field.type === 'table')
);
const tableFields = fields.filter((field) => field.type === 'table').map((field) => field.name);
const dynamicTopicFields = fields
.filter((field) => field.name === 'topic' || field.name === 'data_topic')
.map((field) => field.name);
const placeholderTokens = new Set(fields.flatMap((field) => field.tokens));
const missingMappings = [...placeholderTokens].filter(
(token) => token !== 'item_topic' && !mappedKeys.has(token)
);
const orphanMappings = mappings
.filter(
(mapping) =>
!placeholderTokens.has(mapping.placeholderKey) &&
!fieldNames.has(mapping.placeholderKey) &&
mapping.placeholderKey !== 'items_table'
)
.map((mapping) => mapping.placeholderKey);
const classifications = fields.map((field) => ({
name: field.name,
type: field.type,
classification: classifyTemplateField(field, mappedKeys)
}));
const status = summarizeIssues([
duplicateFields.length ? 'FAIL' : 'PASS',
missingRequiredTableFields.length ? 'FAIL' : 'PASS',
missingMappings.length ? 'FAIL' : 'PASS',
orphanMappings.length ? 'WARNING' : 'PASS',
unknownFields.length ? 'WARNING' : 'PASS'
]);
results.push({
templateName: template.templateName,
version: template.version,
previousVersion: previousVersion?.version ?? null,
organizationId: template.organizationId,
schemaFieldCount: fields.length,
tableFields,
missingRequiredTableFields,
dynamicTopicFields,
deletedFields: fieldDiff.deletedFields,
newFields: fieldDiff.newFields,
duplicateFields,
unknownFields,
classifications,
missingMappings,
orphanMappings,
mappedFieldCount: mappings.length,
status
});
}
const overallStatus = summarizeIssues(
results.map((result) => result.status) as Array<'PASS' | 'WARNING' | 'FAIL'>
);
console.log(
JSON.stringify(
{
audit: 'template-inventory',
overallStatus,
templateCount: results.length,
results
},
null,
2
)
);
} finally {
await sql.end({ timeout: 5 });
}
}
main().catch((error) => {
console.error(`[audit-pdf-template-inventory] ${error.message}`);
process.exit(1);
});