task-h.5.3

This commit is contained in:
phaichayon
2026-06-19 17:15:28 +07:00
parent fd01ebf7c7
commit 51d67ef7c2
35 changed files with 4301 additions and 633 deletions

View File

@@ -2,6 +2,7 @@ import {
buildAuditPayload,
createSqlClient,
getActiveTemplateVersions,
loadTemplateSourceByOrganizationName,
summarizeIssues,
writeAuditArtifact
} from './pdf-audit-utils.ts';
@@ -12,7 +13,9 @@ type IntegrityIssue = {
| 'DUPLICATE_ACTIVE_VERSION'
| 'ACTIVE_VERSION_WITHOUT_TEMPLATE'
| 'MAPPING_WITHOUT_VERSION'
| 'INVALID_APPROVED_TEMPLATE_VERSION';
| 'INVALID_APPROVED_TEMPLATE_VERSION'
| 'ACTIVE_VERSION_SCHEMA_DRIFT'
| 'MISSING_RETAINED_INACTIVE_VERSION';
message: string;
context: Record<string, unknown>;
};
@@ -21,19 +24,29 @@ async function main() {
const sql = createSqlClient();
try {
const [activeTemplateVersions, versionRows, orphanMappingRows, payload] = await Promise.all([
const [
activeTemplateVersions,
versionRows,
orphanMappingRows,
payload,
templateRows
] = await Promise.all([
getActiveTemplateVersions(sql),
sql`
select
v.id,
v.template_id as "templateId",
v.organization_id as "organizationId",
o.name as "organizationName",
v.version,
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
where v.deleted_at is null
`,
sql`
@@ -47,7 +60,20 @@ async function main() {
where m.deleted_at is null
and (v.id is null or v.deleted_at is not null)
`,
buildAuditPayload(sql)
buildAuditPayload(sql),
sql`
select
t.id,
t.organization_id as "organizationId",
o.name as "organizationName",
t.template_name as "templateName"
from crm_document_templates t
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>();
@@ -121,6 +147,48 @@ async function main() {
}
}
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
);
const activeRows = rows.filter((row) => Boolean(row.isActive));
const inactiveRows = rows.filter((row) => !row.isActive);
const templateSource = loadTemplateSourceByOrganizationName(String(templateRow.organizationName));
if (templateSource && activeRows.length === 1) {
const activeSchema =
typeof activeRows[0].schemaJson === 'string'
? JSON.parse(String(activeRows[0].schemaJson))
: activeRows[0].schemaJson;
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)}`,
context: {
templateId,
organizationName: templateRow.organizationName,
activeVersionId: activeRows[0].id,
activeVersion: activeRows[0].version,
sourceFile: templateSource.relativePath
}
});
}
}
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`,
context: {
templateId,
organizationName: templateRow.organizationName
}
});
}
}
for (const row of orphanMappingRows as Array<Record<string, unknown>>) {
issues.push({
kind: 'MAPPING_WITHOUT_VERSION',
@@ -129,13 +197,16 @@ async function main() {
});
}
if (
payload.approvedTemplateVersionId &&
!activeTemplateVersions.some((record) => record.versionId === payload.approvedTemplateVersionId)
) {
const approvedTemplateVersionExists = payload.approvedTemplateVersionId
? (versionRows as Array<Record<string, unknown>>).some(
(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 an active template version',
message: 'Audit quotation approvedTemplateVersionId does not resolve to a retained template version',
context: {
quotationCode: payload.quotationCode,
approvedTemplateVersionId: payload.approvedTemplateVersionId
@@ -150,6 +221,9 @@ async function main() {
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);