文件
hyb-oa/scripts/qa/budget-pool-http-fixture.cjs
wangsli 1f8d5b658a 完善借款、编号、付款与预算并发及验收(09-06-release-gate-integrity)
汇总已落盘的借款本金与还款、兼容历史编号的周期化编号、付款责任与回单核验、报销进度和排序、PWA/采购事务、错误原因与日期校验及ERP退役。
统一共享预算池锁序和锁后判断,恢复差旅原预算选择,补原因码固定夹具及反向完整性检查。
纳入配套迁移、测试、受管QA、分层验收合同、规约及任务记录;原始证据留在ignored artifacts。

关联任务:09-17-company-loan-principal-repayment、09-17-erp-exclusive-retirement、09-18-reimbursement-periodic-numbering、09-08-reimbursement-payment-closeout、09-18-local-concurrency-product-fixes、09-19-budget-pool-concurrency、09-05-error-explainability-payment-readiness。
预算补审由Claude定向验证2文件928项通过,原4446/28及r6结果仍仅对应各自历史输入;本轮未重跑测试。
完整新候选门禁、R1真实入口接入与12项公网义务仍开放。生产编号未启用、差旅仍冻结;不推送、不部署,不宣称发布资格。
2026-09-19 13:11:04 +08:00

202 行
14 KiB
JavaScript

const assert = require('node:assert/strict');
const { randomUUID } = require('node:crypto');
const { createRequire } = require('node:module');
const path = require('node:path');
const { setTimeout: delay } = require('node:timers/promises');
const req = createRequire(path.resolve(__dirname, '../../server/package.json'));
// Real Fastify routes and Prisma transactions; synthetic authorized identity.
// Authentication and browser behavior remain covered by the ordinary full gate.
module.exports = async ({ prisma, pool, check, contend, used }) => {
const app = req('fastify')({ logger: false });
const department = await prisma.department.create({ data: { name: '合成预算并发部门' } });
const employee = async role => prisma.employee.create({ data: {
email: `${randomUUID()}@example.invalid`, name: '合成预算用户', department_id: department.id,
title: '合成岗位', hire_date: new Date('2025-01-01'), bank_name: 'synthetic',
bank_account_encrypted: 'synthetic-unused', password_hash: 'synthetic-unused',
roles: { create: { role_code: role } },
} });
const owner = await employee('employee'); const manager = await employee('manager'); await employee('finance');
const company = await prisma.legalEntity.create({ data: { code: `SYN-${randomUUID()}`, name: '合成公司' } });
const costCenter = await prisma.costCenter.create({ data: { code: 'SYN', name: '合成成本中心', legal_entity_id: company.id } });
const supplier = await prisma.supplier.create({ data: { code: `SYN-${randomUUID()}`, name: '合成供应商', legal_entity_id: company.id } });
const category = await prisma.documentCategory.create({ data: { code: `SYN-${randomUUID()}`, name: '合成采购类别',
entity_type: 'purchase_request', sequence_prefix: 'SYN', reset_period: 'yearly' } });
const expense = await prisma.expenseType.create({ data: { code: `SYN-${randomUUID()}`, name: '合成办公费',
category: 'office', sort_order: 999, requires_invoice: false } });
await prisma.approvalRule.create({ data: { name: '合成财务审批', min_amount: '0', level1_role: 'finance', priority: 1 } });
for (const entity_type of ['purchase_request', 'reimbursement']) {
await prisma.workflowDefinition.create({ data: { entity_type, name: '合成预算流程', version: 1,
definition_json: { steps: [{ id: 'approve', name: '合成审批', type: 'approval', assignee_rule: { type: 'role', value: 'manager' } }] } } });
}
app.decorate('prisma', prisma);
const config = { appTimezone: 'Asia/Shanghai', budgetPrecheckEnabled: false, budgetControlMode: 'block',
emailEnabled: false, imNotifierEnabled: false, cutoverConsoleEnabled: false, reimbursementInvoiceGuardEnabled: false };
app.decorate('config', config);
const actor = { ...owner, roleCodes: ['employee'], permissionCodes: ['*'] };
app.decorate('hasPermission', () => true);
app.decorate('requirePermission', () => async request => { request.employee = actor; });
app.setErrorHandler((error, request, reply) => reply.code(error.statusCode ?? 500).send({
code: error.code ?? 'SYS_001', reason: error.details?.reasonCode ?? null,
precheckCodes: error.details?.precheck?.map?.(item => item.code) ?? [],
}));
await app.register(req('./src/routes/purchaseRequests'), { prefix: '/api' });
await app.register(req('./src/routes/reimbursements'), { prefix: '/api' });
await app.register(req('./src/routes/travelTransactions'), { prefix: '/api' });
await app.ready();
let selected;
const today = new Date();
async function setup({ enabled, mode = 'block' }) {
config.budgetPrecheckEnabled = enabled; config.budgetControlMode = mode;
await prisma.featureFlag.upsert({ where: { flag_key: 'budget_precheck_v1' }, update: { override_mode: enabled ? 'force_on' : 'force_off' },
create: { flag_key: 'budget_precheck_v1', override_mode: enabled ? 'force_on' : 'force_off', rollout_type: 'global' } });
await prisma.budgetPool.updateMany({ data: { status: 'archived', archived_at: today } });
selected = await prisma.budgetPool.create({ data: { code: `SYN-${randomUUID()}`, name: '合成共享预算',
period_start: new Date(today.getFullYear(), 0, 1), period_end: new Date(today.getFullYear(), 11, 31),
allocated_amount: '100.00', used_amount: '0.00', warning_threshold_percent: 100 } });
}
async function purchase(explicit = false, amount = '60.00') {
const row = await prisma.purchaseRequest.create({ data: { applicant_id: owner.id, department_id: department.id,
title: '合成并发采购', legal_entity_id: company.id, cost_center_id: costCenter.id, supplier_id: supplier.id,
document_category_id: category.id, request_no: `SYN-${randomUUID()}`, total_amount: amount,
budget_pool_id: explicit ? selected.id : null,
items: { create: { description: '合成用品', quantity: '1.00', unit_price: amount, line_amount: amount } } } });
return { id: row.id, type: 'purchase_request', url: `/api/purchase-requests/${row.id}/submit` };
}
async function reimbursement(amount = '60.00') {
const row = await prisma.reimbursementForm.create({ data: { owner_id: owner.id, submitter_id: owner.id,
department_id: department.id, reimbursement_date: today, form_number: `SYN-${randomUUID()}`, total_amount: amount,
items: { create: { expense_type_id: expense.id, expense_date: today, amount, description: '合成并发费用' } } }, include: { items: true } });
await prisma.invoicePool.create({ data: { owner_employee_id: owner.id, uploaded_by_employee_id: owner.id,
storage_path: 'synthetic/not-a-real-file', original_name: 'synthetic.txt', mime_type: 'text/plain', file_size: 1,
file_sha256: randomUUID(), status: 'bound', linked_reimbursement_id: row.id, linked_item_id: row.items[0].id,
total_amount: amount, ocr_completed_at: today } });
return { id: row.id, type: 'reimbursement', url: `/api/reimbursements/${row.id}/submit` };
}
const submit = entity => () => app.inject({ method: 'POST', url: entity.url });
async function assertRace(entities, expectedStatuses, expectedUsage) {
const responses = await contend(selected.id, entities.map(submit), true);
// Only safe codes are logged for unexpected route responses.
if (responses.some(r => !expectedStatuses.includes(r.statusCode))) {
console.log(JSON.stringify({ routeDiagnostics: responses.map(r => ({ status: r.statusCode, ...(r.statusCode >= 400 ? r.json() : {}) })) }));
}
assert.deepEqual(responses.map(r => r.statusCode).sort(), expectedStatuses.slice().sort());
assert.equal(await used(selected.id), expectedUsage);
const winners = responses.filter(r => r.statusCode === 200).length;
assert.equal(await prisma.budgetLedgerEntry.count({ where: { budget_pool_id: selected.id } }), winners);
assert.equal(await prisma.workflowInstance.count({ where: { entity_id: { in: entities.map(e => e.id) } } }), winners);
}
try {
for (const enabled of [false, true]) {
for (const explicit of [false, true]) {
await check(`purchase_block_concurrency_gate_${enabled}_explicit_${explicit}`, async () => {
await setup({ enabled });
await assertRace([await purchase(explicit), await purchase(explicit)], [200, 409], '60.00');
});
}
await check(`reimbursement_block_concurrency_gate_${enabled}`, async () => {
await setup({ enabled });
await assertRace([await reimbursement(), await reimbursement()], [200, 409], '60.00');
});
await check(`mixed_purchase_reimbursement_block_gate_${enabled}`, async () => {
await setup({ enabled });
await assertRace([await purchase(), await reimbursement()], [200, 409], '60.00');
});
await check(`mixed_warn_keeps_both_writes_gate_${enabled}`, async () => {
await setup({ enabled, mode: 'warn' });
await assertRace([await purchase(), await reimbursement()], [200, 200], '120.00');
});
}
await check('exact_capacity_and_duplicate_submit', async () => {
await setup({ enabled: true });
const a = await purchase(false, '40.00'); const b = await reimbursement();
// Precheck block also blocks warning threshold hits under its existing rule.
await prisma.budgetPool.update({ where: { id: selected.id }, data: { warning_threshold_percent: 101 } });
await assertRace([a, b], [200, 200], '100.00');
assert.equal((await submit(a)()).statusCode, 409);
assert.equal(await used(selected.id), '100.00');
});
await check('submit_ledger_failure_rolls_back_usage_and_workflow', async () => {
await setup({ enabled: false }); const form = await reimbursement();
await pool.query(`CREATE FUNCTION synthetic_budget_failure() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'synthetic failure'; END $$;
CREATE TRIGGER synthetic_budget_failure BEFORE INSERT ON budget_ledger_entries FOR EACH ROW EXECUTE FUNCTION synthetic_budget_failure()`);
try {
assert.equal((await submit(form)()).statusCode, 500);
assert.equal(await used(selected.id), '0.00');
assert.equal(await prisma.workflowInstance.count({ where: { entity_id: form.id } }), 0);
assert.equal((await prisma.reimbursementForm.findUniqueOrThrow({ where: { id: form.id } })).status, 'draft');
} finally { await pool.query('DROP TRIGGER synthetic_budget_failure ON budget_ledger_entries; DROP FUNCTION synthetic_budget_failure()'); }
});
for (const kind of ['purchase', 'reimbursement']) {
await check(`legacy_block_exact_decimal_capacity_${kind}`, async () => {
await setup({ enabled: false });
await prisma.budgetPool.update({ where: { id: selected.id }, data: { allocated_amount: '0.30', used_amount: '0.10' } });
const entity = kind === 'purchase' ? await purchase(false, '0.20') : await reimbursement('0.20');
assert.equal((await submit(entity)()).statusCode, 200);
assert.equal(await used(selected.id), '0.30');
});
}
await check('allocation_reduced_during_wait_is_used_by_both_submitters', async () => {
await setup({ enabled: true });
const entities = [await purchase(), await reimbursement()];
const responses = await contend(selected.id, entities.map(submit), true,
blocker => blocker.query('UPDATE budget_pools SET allocated_amount=50 WHERE id=$1', [selected.id]));
assert.deepEqual(responses.map(r => r.statusCode), [409, 409]);
assert.equal(await used(selected.id), '0.00');
assert.equal(await prisma.budgetLedgerEntry.count({ where: { budget_pool_id: selected.id } }), 0);
});
for (const table of ['workflow_history', 'audit_logs']) {
await check(`submit_${table}_failure_rolls_back_all_writes`, async () => {
await setup({ enabled: false }); const form = await reimbursement();
if (table === 'audit_logs') await prisma.reimbursementForm.update({ where: { id: form.id }, data: { submitter_id: manager.id } });
// Table is from the fixed two-element allowlist above, not user input.
await pool.query(`CREATE FUNCTION synthetic_budget_failure() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'synthetic failure'; END $$;
CREATE TRIGGER synthetic_budget_failure BEFORE INSERT ON ${table} FOR EACH ROW EXECUTE FUNCTION synthetic_budget_failure()`);
try {
assert.equal((await submit(form)()).statusCode, 500);
assert.equal(await used(selected.id), '0.00');
assert.equal(await prisma.budgetLedgerEntry.count({ where: { budget_pool_id: selected.id } }), 0);
assert.equal(await prisma.workflowInstance.count({ where: { entity_id: form.id } }), 0);
assert.equal((await prisma.reimbursementForm.findUniqueOrThrow({ where: { id: form.id } })).status, 'draft');
} finally { await pool.query(`DROP TRIGGER synthetic_budget_failure ON ${table}; DROP FUNCTION synthetic_budget_failure()`); }
});
}
await check('authorization_and_refund_share_entity_before_pool_lock_order', async () => {
await setup({ enabled: false });
// Exercise the frozen import handler only in this synthetic Fastify app;
// no deployment flag, external integration or browser scenario is enabled.
config.travelSyncEnabled = true;
const travel = await prisma.travelRequest.create({ data: { applicant_employee_id: owner.id, department_id: department.id,
legal_entity_id: company.id, cost_center_id: costCenter.id, budget_pool_id: selected.id,
purpose: '合成锁序验证', trip_type: 'domestic', start_date: today, end_date: today, estimated_amount: '60.00' } });
const authorization = await prisma.cardAuthorization.create({ data: { source: 'synthetic', external_auth_no: randomUUID(),
employee_id: owner.id, department_id: department.id, travel_request_id: travel.id, budget_pool_id: selected.id,
legal_entity_id: company.id, cost_center_id: costCenter.id, authorized_amount: '60.00' } });
let refund;
try {
await prisma.$transaction(async t => {
await t.$queryRaw`SELECT id FROM card_authorizations WHERE id=${authorization.id}::uuid FOR NO KEY UPDATE`;
refund = app.inject({ method: 'POST', url: '/api/travel/transactions/import', payload: { transactions: [{
source: 'synthetic', externalTxnId: randomUUID(), employeeId: owner.id, travelRequestId: travel.id,
cardAuthorizationId: authorization.id, amount: '20.00', currency: 'CNY', txnType: 'refund', status: 'settled',
}] } });
// Start the injection and observe its wait on the authorization.
refund = Promise.resolve(refund);
const deadline = Date.now() + 2000; let waiting = 0;
while (Date.now() < deadline) {
waiting = (await pool.query("SELECT count(*)::int n FROM pg_stat_activity WHERE datname=current_database() AND wait_event_type='Lock'")).rows[0].n;
if (waiting) break; await delay(10);
}
assert.equal(waiting, 1);
await req('./src/services/travelPayments').authorizeBudgetForEntity(t, { budgetPoolId: selected.id,
entityId: authorization.id, amount: '60.00', actorId: owner.id, note: 'synthetic authorization' });
});
const response = await refund;
assert.equal(response.statusCode, 200);
assert.equal(response.json().data.reversedCount, 1);
assert.equal(await used(selected.id), '40.00');
} finally { if (refund) await refund; config.travelSyncEnabled = false; }
});
} finally { await app.close(); }
};