task-h.4
This commit is contained in:
198
scripts/verify-task-h3.js
Normal file
198
scripts/verify-task-h3.js
Normal file
@@ -0,0 +1,198 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const postgres = require('postgres');
|
||||
|
||||
function loadEnvFile(filePath) {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
for (const rawLine of content.split(/\r?\n/)) {
|
||||
const line = rawLine.trim();
|
||||
|
||||
if (!line || line.startsWith('#')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const separatorIndex = line.indexOf('=');
|
||||
|
||||
if (separatorIndex === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = line.slice(0, separatorIndex).trim();
|
||||
let value = line.slice(separatorIndex + 1).trim();
|
||||
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
|
||||
if (!(key in process.env)) {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadLocalEnv() {
|
||||
loadEnvFile(path.resolve(process.cwd(), '.env.local'));
|
||||
loadEnvFile(path.resolve(process.cwd(), '.env'));
|
||||
}
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
function pickSnapshotValue(snapshot, pathSegments) {
|
||||
return pathSegments.reduce((value, segment) => {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return value[segment];
|
||||
}, snapshot);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
loadLocalEnv();
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL?.trim();
|
||||
|
||||
assert(databaseUrl, 'Missing DATABASE_URL');
|
||||
|
||||
const sql = postgres(databaseUrl, { max: 1 });
|
||||
|
||||
try {
|
||||
const mappingRows = await sql`
|
||||
select placeholder_key, source_path
|
||||
from crm_document_template_mappings
|
||||
where placeholder_key in ('app1', 'app1_position', 'app2', 'app2_position', 'app3', 'app3_position')
|
||||
and deleted_at is null
|
||||
order by placeholder_key asc
|
||||
`;
|
||||
const mappingMap = new Map(mappingRows.map((row) => [row.placeholder_key, row.source_path]));
|
||||
|
||||
assert(mappingMap.get('app1') === 'signatures.preparedBy.name', 'app1 mapping is incorrect');
|
||||
assert(
|
||||
mappingMap.get('app1_position') === 'signatures.preparedBy.position',
|
||||
'app1_position mapping is incorrect'
|
||||
);
|
||||
assert(mappingMap.get('app2') === 'signatures.approvedBy.name', 'app2 mapping is incorrect');
|
||||
assert(
|
||||
mappingMap.get('app2_position') === 'signatures.approvedBy.position',
|
||||
'app2_position mapping is incorrect'
|
||||
);
|
||||
assert(mappingMap.get('app3') === 'signatures.authorizedBy.name', 'app3 mapping is incorrect');
|
||||
assert(
|
||||
mappingMap.get('app3_position') === 'signatures.authorizedBy.position',
|
||||
'app3_position mapping is incorrect'
|
||||
);
|
||||
|
||||
const [snapshotRow] = await sql`
|
||||
select
|
||||
q.id,
|
||||
q.code,
|
||||
q.organization_id,
|
||||
q.approved_snapshot
|
||||
from crm_quotations q
|
||||
where q.approved_snapshot is not null
|
||||
and q.deleted_at is null
|
||||
order by q.updated_at desc
|
||||
limit 1
|
||||
`;
|
||||
|
||||
assert(
|
||||
snapshotRow,
|
||||
'No quotation with approved_snapshot was found. Generate at least one approved PDF before running verify-task-h3.'
|
||||
);
|
||||
|
||||
const snapshot = snapshotRow.approved_snapshot;
|
||||
const signatures = pickSnapshotValue(snapshot, ['documentData', 'signatures']);
|
||||
const templateInput = pickSnapshotValue(snapshot, ['templateInput']);
|
||||
|
||||
assert(signatures && typeof signatures === 'object', 'approvedSnapshot is missing signatures');
|
||||
|
||||
if (
|
||||
typeof signatures.preparedBy === 'string' ||
|
||||
typeof signatures.salesManager === 'string' ||
|
||||
typeof signatures.topManager === 'string'
|
||||
) {
|
||||
throw new Error(
|
||||
'approvedSnapshot still uses the pre-H3 signature shape. Regenerate one approved PDF to backfill snapshot signatures.'
|
||||
);
|
||||
}
|
||||
|
||||
for (const key of ['preparedBy', 'approvedBy', 'authorizedBy']) {
|
||||
const block = signatures[key];
|
||||
|
||||
assert(block && typeof block === 'object', `approvedSnapshot is missing ${key}`);
|
||||
assert(
|
||||
typeof block.name === 'string' && block.name.trim().length > 0 && block.name !== '-',
|
||||
`approvedSnapshot ${key}.name is missing`
|
||||
);
|
||||
assert(
|
||||
typeof block.position === 'string' && block.position.trim().length > 0 && block.position !== '-',
|
||||
`approvedSnapshot ${key}.position is missing`
|
||||
);
|
||||
}
|
||||
|
||||
assert(templateInput && typeof templateInput === 'object', 'approvedSnapshot is missing templateInput');
|
||||
assert(templateInput.app1 === signatures.preparedBy.name, 'templateInput.app1 does not match preparedBy');
|
||||
assert(
|
||||
templateInput.app1_position === signatures.preparedBy.position,
|
||||
'templateInput.app1_position does not match preparedBy.position'
|
||||
);
|
||||
assert(templateInput.app2 === signatures.approvedBy.name, 'templateInput.app2 does not match approvedBy');
|
||||
assert(
|
||||
templateInput.app2_position === signatures.approvedBy.position,
|
||||
'templateInput.app2_position does not match approvedBy.position'
|
||||
);
|
||||
assert(
|
||||
templateInput.app3 === signatures.authorizedBy.name,
|
||||
'templateInput.app3 does not match authorizedBy'
|
||||
);
|
||||
assert(
|
||||
templateInput.app3_position === signatures.authorizedBy.position,
|
||||
'templateInput.app3_position does not match authorizedBy.position'
|
||||
);
|
||||
|
||||
const jobTitleRows = await sql`
|
||||
select code, label
|
||||
from ms_options
|
||||
where organization_id = ${snapshotRow.organization_id}
|
||||
and category = 'crm_job_title'
|
||||
and deleted_at is null
|
||||
and is_active = true
|
||||
order by sort_order asc
|
||||
`;
|
||||
|
||||
assert(jobTitleRows.length >= 5, 'crm_job_title master options are incomplete');
|
||||
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
success: true,
|
||||
quotationId: snapshotRow.id,
|
||||
quotationCode: snapshotRow.code,
|
||||
verifiedMappings: [...mappingMap.entries()],
|
||||
verifiedJobTitles: jobTitleRows
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
} finally {
|
||||
await sql.end({ timeout: 5 });
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(`[verify-task-h3] ${error.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user