pdf task-h
This commit is contained in:
166
scripts/audit-template-version-integrity.ts
Normal file
166
scripts/audit-template-version-integrity.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import {
|
||||
buildAuditPayload,
|
||||
createSqlClient,
|
||||
getActiveTemplateVersions,
|
||||
summarizeIssues,
|
||||
writeAuditArtifact
|
||||
} from './pdf-audit-utils.ts';
|
||||
|
||||
type IntegrityIssue = {
|
||||
kind:
|
||||
| 'DUPLICATE_ACTIVE_TEMPLATE'
|
||||
| 'DUPLICATE_ACTIVE_VERSION'
|
||||
| 'ACTIVE_VERSION_WITHOUT_TEMPLATE'
|
||||
| 'MAPPING_WITHOUT_VERSION'
|
||||
| 'INVALID_APPROVED_TEMPLATE_VERSION';
|
||||
message: string;
|
||||
context: Record<string, unknown>;
|
||||
};
|
||||
|
||||
async function main() {
|
||||
const sql = createSqlClient();
|
||||
|
||||
try {
|
||||
const [activeTemplateVersions, versionRows, orphanMappingRows, payload] = await Promise.all([
|
||||
getActiveTemplateVersions(sql),
|
||||
sql`
|
||||
select
|
||||
v.id,
|
||||
v.template_id as "templateId",
|
||||
v.organization_id as "organizationId",
|
||||
v.version,
|
||||
v.is_active as "isActive",
|
||||
t.id as "resolvedTemplateId"
|
||||
from crm_document_template_versions v
|
||||
left join crm_document_templates t
|
||||
on t.id = v.template_id
|
||||
where v.deleted_at is null
|
||||
`,
|
||||
sql`
|
||||
select
|
||||
m.id,
|
||||
m.template_version_id as "templateVersionId",
|
||||
m.placeholder_key as "placeholderKey"
|
||||
from crm_document_template_mappings m
|
||||
left join crm_document_template_versions v
|
||||
on v.id = m.template_version_id
|
||||
where m.deleted_at is null
|
||||
and (v.id is null or v.deleted_at is not null)
|
||||
`,
|
||||
buildAuditPayload(sql)
|
||||
]);
|
||||
const issues: IntegrityIssue[] = [];
|
||||
const templateBuckets = new Map<string, typeof activeTemplateVersions>();
|
||||
|
||||
for (const record of activeTemplateVersions) {
|
||||
const bucketKey = [
|
||||
record.organizationId,
|
||||
record.documentType,
|
||||
record.productType,
|
||||
record.fileType
|
||||
].join(':');
|
||||
const items = templateBuckets.get(bucketKey) ?? [];
|
||||
items.push(record);
|
||||
templateBuckets.set(bucketKey, items);
|
||||
}
|
||||
|
||||
for (const [bucketKey, records] of templateBuckets.entries()) {
|
||||
const templateIds = [...new Set(records.map((record) => record.templateId))];
|
||||
|
||||
if (templateIds.length > 1) {
|
||||
issues.push({
|
||||
kind: 'DUPLICATE_ACTIVE_TEMPLATE',
|
||||
message: `More than one active template exists for ${bucketKey}`,
|
||||
context: {
|
||||
bucketKey,
|
||||
templateIds,
|
||||
templates: records.map((record) => ({
|
||||
templateId: record.templateId,
|
||||
templateName: record.templateName,
|
||||
versionId: record.versionId,
|
||||
version: record.version
|
||||
}))
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const versionBuckets = new Map<string, Array<Record<string, unknown>>>();
|
||||
|
||||
for (const row of versionRows as Array<Record<string, unknown>>) {
|
||||
if (!row.isActive) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const items = versionBuckets.get(String(row.templateId)) ?? [];
|
||||
items.push(row);
|
||||
versionBuckets.set(String(row.templateId), items);
|
||||
|
||||
if (!row.resolvedTemplateId) {
|
||||
issues.push({
|
||||
kind: 'ACTIVE_VERSION_WITHOUT_TEMPLATE',
|
||||
message: `Active version ${String(row.id)} has no parent template`,
|
||||
context: row
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const [templateId, rows] of versionBuckets.entries()) {
|
||||
if (rows.length > 1) {
|
||||
issues.push({
|
||||
kind: 'DUPLICATE_ACTIVE_VERSION',
|
||||
message: `Template ${templateId} has more than one active version`,
|
||||
context: {
|
||||
templateId,
|
||||
versions: rows.map((row) => ({
|
||||
id: row.id,
|
||||
version: row.version
|
||||
}))
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const row of orphanMappingRows as Array<Record<string, unknown>>) {
|
||||
issues.push({
|
||||
kind: 'MAPPING_WITHOUT_VERSION',
|
||||
message: `Mapping ${String(row.id)} points to a missing template version`,
|
||||
context: row
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
payload.approvedTemplateVersionId &&
|
||||
!activeTemplateVersions.some((record) => record.versionId === payload.approvedTemplateVersionId)
|
||||
) {
|
||||
issues.push({
|
||||
kind: 'INVALID_APPROVED_TEMPLATE_VERSION',
|
||||
message: 'Audit quotation approvedTemplateVersionId does not resolve to an active template version',
|
||||
context: {
|
||||
quotationCode: payload.quotationCode,
|
||||
approvedTemplateVersionId: payload.approvedTemplateVersionId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const status = summarizeIssues(issues.length ? ['FAIL'] : ['PASS']);
|
||||
const report = {
|
||||
audit: 'template-version-integrity',
|
||||
overallStatus: status,
|
||||
activeTemplateCount: activeTemplateVersions.length,
|
||||
quotationCode: payload.quotationCode,
|
||||
approvedTemplateVersionId: payload.approvedTemplateVersionId,
|
||||
issues
|
||||
};
|
||||
const artifactPath = await writeAuditArtifact('template-version-report.json', report);
|
||||
|
||||
console.log(JSON.stringify({ ...report, artifactPath }, null, 2));
|
||||
} finally {
|
||||
await sql.end({ timeout: 5 });
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(`[audit-template-version-integrity] ${error.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user