task p-4.5

This commit is contained in:
phaichayon
2026-06-29 16:07:03 +07:00
parent aa73b9aed2
commit a1cac84e2b
19 changed files with 4812 additions and 413 deletions

View File

@@ -3,8 +3,9 @@ import {
createSqlClient,
getActiveTemplateVersions,
loadTemplateSourceByOrganizationName,
loadTemplateSourceByRelativePath,
summarizeIssues,
writeAuditArtifact
writeAuditArtifact,
} from './pdf-audit-utils.ts';
type IntegrityIssue = {
@@ -29,7 +30,7 @@ async function main() {
versionRows,
orphanMappingRows,
payload,
templateRows
templateRows,
] = await Promise.all([
getActiveTemplateVersions(sql),
sql`
@@ -39,14 +40,13 @@ async function main() {
v.organization_id as "organizationId",
o.name as "organizationName",
v.version,
v.file_path as "filePath",
v.is_active as "isActive",
v.schema_json as "schemaJson",
t.id as "resolvedTemplateId"
from crm_document_template_versions v
left join crm_document_templates t
on t.id = v.template_id
left join organizations o
on o.id = v.organization_id
left join crm_document_templates t on t.id = v.template_id
left join organizations o on o.id = v.organization_id
where v.deleted_at is null
`,
sql`
@@ -55,8 +55,7 @@ async function main() {
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
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)
`,
@@ -68,47 +67,40 @@ async function main() {
o.name as "organizationName",
t.template_name as "templateName"
from crm_document_templates t
inner join organizations o
on o.id = t.organization_id
inner join organizations o on o.id = t.organization_id
where t.deleted_at is null
and t.document_type = 'quotation'
and t.file_type = 'pdfme'
`
`,
]);
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);
const issues: IntegrityIssue[] = [];
const templateBuckets = new Map<string, Array<Record<string, unknown>>>();
for (const record of activeTemplateVersions as unknown as Array<Record<string, unknown>>) {
const bucketKey = `${String(record.organizationId)}:${String(record.templateName)}`;
const records = templateBuckets.get(bucketKey) ?? [];
records.push(record);
templateBuckets.set(bucketKey, records);
}
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
}))
}
});
if (records.length <= 1) {
continue;
}
issues.push({
kind: 'DUPLICATE_ACTIVE_TEMPLATE',
message: `Multiple active templates found for ${bucketKey}.`,
context: {
bucketKey,
templateIds: records.map((record) => record.templateId),
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>>>();
@@ -125,36 +117,42 @@ async function main() {
if (!row.resolvedTemplateId) {
issues.push({
kind: 'ACTIVE_VERSION_WITHOUT_TEMPLATE',
message: `Active version ${String(row.id)} has no parent template`,
context: row
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
}))
}
});
if (rows.length <= 1) {
continue;
}
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 templateRow of templateRows as Array<Record<string, unknown>>) {
const templateId = String(templateRow.id);
const rows = (versionRows as Array<Record<string, unknown>>).filter(
(row) => String(row.templateId) === templateId
(row) => String(row.templateId) === templateId,
);
const activeRows = rows.filter((row) => Boolean(row.isActive));
const inactiveRows = rows.filter((row) => !row.isActive);
const templateSource = loadTemplateSourceByOrganizationName(String(templateRow.organizationName));
const templateSource =
loadTemplateSourceByRelativePath(
(activeRows[0]?.filePath as string | null | undefined) ?? null,
) ??
loadTemplateSourceByOrganizationName(String(templateRow.organizationName));
if (templateSource && activeRows.length === 1) {
const activeSchema =
@@ -165,14 +163,14 @@ async function main() {
if (JSON.stringify(activeSchema) !== JSON.stringify(templateSource.schemaJson)) {
issues.push({
kind: 'ACTIVE_VERSION_SCHEMA_DRIFT',
message: `Active template version does not match source JSON for ${String(templateRow.templateName)}`,
message: `Active template version does not match source JSON for ${String(templateRow.templateName)}.`,
context: {
templateId,
organizationName: templateRow.organizationName,
activeVersionId: activeRows[0].id,
activeVersion: activeRows[0].version,
sourceFile: templateSource.relativePath
}
sourceFile: templateSource.relativePath,
},
});
}
}
@@ -180,11 +178,11 @@ async function main() {
if (rows.length > 1 && inactiveRows.length === 0) {
issues.push({
kind: 'MISSING_RETAINED_INACTIVE_VERSION',
message: `Template ${String(templateRow.templateName)} has no retained inactive historical version`,
message: `Template ${String(templateRow.templateName)} has no retained inactive historical version.`,
context: {
templateId,
organizationName: templateRow.organizationName
}
organizationName: templateRow.organizationName,
},
});
}
}
@@ -192,43 +190,67 @@ async function main() {
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
message: `Mapping ${String(row.id)} points to a missing template version.`,
context: row,
});
}
const approvedTemplateVersionExists = payload.approvedTemplateVersionId
? (versionRows as Array<Record<string, unknown>>).some(
(row) => String(row.id) === payload.approvedTemplateVersionId
(row) => String(row.id) === payload.approvedTemplateVersionId,
)
: true;
if (payload.approvedTemplateVersionId && !approvedTemplateVersionExists) {
issues.push({
kind: 'INVALID_APPROVED_TEMPLATE_VERSION',
message: 'Audit quotation approvedTemplateVersionId does not resolve to a retained template version',
message: 'Approved snapshot references a template version that no longer exists.',
context: {
quotationCode: payload.quotationCode,
approvedTemplateVersionId: payload.approvedTemplateVersionId
}
approvedTemplateVersionId: payload.approvedTemplateVersionId,
},
});
}
const status = summarizeIssues(issues.length ? ['FAIL'] : ['PASS']);
const report = {
const overallStatus = summarizeIssues(
issues.length === 0 ? ['PASS'] : issues.map(() => 'FAIL'),
);
const artifactPath = await writeAuditArtifact('template-version-report.json', {
audit: 'template-version-integrity',
overallStatus: status,
overallStatus,
activeTemplateCount: activeTemplateVersions.length,
quotationCode: payload.quotationCode,
approvedTemplateVersionId: payload.approvedTemplateVersionId,
approvedTemplateVersionIsActive: activeTemplateVersions.some(
(record) => record.versionId === payload.approvedTemplateVersionId
),
issues
};
const artifactPath = await writeAuditArtifact('template-version-report.json', report);
approvedTemplateVersionIsActive: payload.approvedTemplateVersionId
? (versionRows as Array<Record<string, unknown>>).some(
(row) =>
String(row.id) === payload.approvedTemplateVersionId && Boolean(row.isActive),
)
: false,
issues,
});
console.log(JSON.stringify({ ...report, artifactPath }, null, 2));
console.log(
JSON.stringify(
{
audit: 'template-version-integrity',
overallStatus,
activeTemplateCount: activeTemplateVersions.length,
quotationCode: payload.quotationCode,
approvedTemplateVersionId: payload.approvedTemplateVersionId,
approvedTemplateVersionIsActive: payload.approvedTemplateVersionId
? (versionRows as Array<Record<string, unknown>>).some(
(row) =>
String(row.id) === payload.approvedTemplateVersionId && Boolean(row.isActive),
)
: false,
issues,
artifactPath,
},
null,
2,
),
);
} finally {
await sql.end({ timeout: 5 });
}