/** * 采购订单状态机审批 - 接口级自动化测试 * * 自建测试单据(每次新建一张),不依赖历史数据状态,可重复执行。 * 覆盖:提交/审核/驳回/重新提交/通过的完整链路 + 全部服务端校验分支。 * * 用法:node scripts/test-purchase-order-api.js * 可选环境变量:TEST_USER / TEST_PASS / PRODUCT_ID / PRODUCT_UNIT_ID */ const BASE = process.env.TEST_BASE || 'http://127.0.0.1:48080/admin-api'; const USER = process.env.TEST_USER || 'admin'; const PASS = process.env.TEST_PASS || 'admin123'; const BIZ_TYPE = 'erp_purchase_order_approve'; // 物料取自 mdm_item(测试库中 id=57「测试」,单位 id=9) const PRODUCT_ID = Number(process.env.PRODUCT_ID || 57); const PRODUCT_UNIT_ID = Number(process.env.PRODUCT_UNIT_ID || 9); const results = []; let TOKEN = ''; function ok(name, extra = '') { results.push({ pass: true, name }); console.log(` ✅ ${name}${extra ? ' — ' + extra : ''}`); } function fail(name, extra = '') { results.push({ pass: false, name, extra }); console.log(` ❌ ${name}${extra ? ' — ' + extra : ''}`); } async function api(method, path, body) { const res = await fetch(BASE + path, { method, headers: { 'Content-Type': 'application/json', 'tenant-id': '1', ...(TOKEN ? { Authorization: `Bearer ${TOKEN}` } : {}), }, body: body ? JSON.stringify(body) : undefined, }); let json = null; try { json = await res.json(); } catch { /* 非 JSON */ } return { status: res.status, json }; } async function login() { console.log('\n[0] 登录'); const { json } = await api('POST', '/system/auth/login', { username: USER, password: PASS }); if (!json || json.code !== 0 || !json.data?.accessToken) { fail('登录失败', JSON.stringify(json)); process.exit(1); } TOKEN = json.data.accessToken; ok('登录成功'); } /** 确保目标业务类型已启用且审批人含 admin(id=1),返回配置对象 */ async function ensureConfig(userIds) { const { json } = await api('GET', `/system/approval-config/page?pageNo=1&pageSize=100&bizType=${BIZ_TYPE}`); const row = (json?.data?.list || []).find((x) => x.bizType === BIZ_TYPE); if (!row) { fail('未找到审批配置种子数据', BIZ_TYPE); return null; } const save = await api('POST', '/system/approval-config/save', { id: row.id, bizType: row.bizType, bizName: row.bizName, approvalEnabled: true, remark: row.remark, userIds, }); if (!save.json || save.json.code !== 0) { fail('保存审批配置失败', JSON.stringify(save.json)); return null; } const again = await api('GET', `/system/approval-config/page?pageNo=1&pageSize=100&bizType=${BIZ_TYPE}`); return (again.json?.data?.list || []).find((x) => x.bizType === BIZ_TYPE) || row; } /** * 采购订单的供应商校验走 SRM(ErpSupplierServiceImpl.validateSupplier -> srmSupplierApi), * 但供应商增删改查与前端下拉又读写 erp_supplier,两处数据源不一致。 * 因此测试数据必须建在 SRM 侧,否则下单必然报「供应商不存在」。 */ async function getSupplierId() { const { json } = await api('GET', '/srm/supplier/simple-list'); const list = json?.data || []; if (list.length > 0) return list[0].id; const seq = Date.now().toString().slice(-6); const created = await api('POST', '/srm/supplier/create', { code: `TESTSUP${seq}`, name: `自动化测试供应商${seq}`, status: 0, remark: '由 test-purchase-order-api.js 创建', }); if (created.json?.code !== 0) { fail('创建 SRM 供应商失败', JSON.stringify(created.json)); return null; } return created.json.data; } async function createOrder(supplierId) { const body = { supplierId, orderTime: '2026-09-16 10:00:00', discountPercent: 0, depositPrice: 0, remark: '自动化测试采购订单', items: [ { productId: PRODUCT_ID, productUnitId: PRODUCT_UNIT_ID, productPrice: 100, count: 5, taxPercent: 13, remark: '测试项', }, ], }; const { json } = await api('POST', '/purchase/order/create', body); return json; } async function getOrder(id) { const { json } = await api('GET', `/purchase/order/get?id=${id}`); return json?.data; } (async () => { await login(); console.log('\n[1] 审批配置'); const cfg = await ensureConfig([1]); if (!cfg) process.exit(1); ok('采购订单审批已启用', `bizName=${cfg.bizName}`); const ap = await api('GET', `/purchase/order/approver-ids`); if (ap.json?.code === 0 && (ap.json.data || []).includes(1)) ok('approver-ids 含 admin(1)', JSON.stringify(ap.json.data)); else fail('approver-ids 异常', JSON.stringify(ap.json)); console.log('\n[2] 新建采购订单'); const supplierId = await getSupplierId(); if (!supplierId) { console.log(' 无法准备供应商,终止'); process.exit(1); } ok('供应商就绪', `supplierId=${supplierId}`); const created = await createOrder(supplierId); if (!created || created.code !== 0) { fail('创建采购订单失败', JSON.stringify(created)); process.exit(1); } const id = created.data; ok('创建采购订单成功', `id=${id}`); const o0 = await getOrder(id); if (o0?.status === 0) ok('新单初始状态为未提交(0)', `no=${o0.no}`); else fail('新单初始状态不是 0', `status=${o0?.status}`); console.log('\n[3] BPM 残留接口应已下线'); const legacy = await api('GET', '/purchase/order/approve-process-list'); if (legacy.status === 404 || (legacy.json && legacy.json.code !== 0)) { ok('approve-process-list 已移除', `HTTP ${legacy.status} code=${legacy.json?.code}`); } else { fail('approve-process-list 仍可用', JSON.stringify(legacy.json).slice(0, 120)); } console.log('\n[4] 未提交不能审核'); const early = await api('PUT', '/purchase/order/audit', { id, pass: true, reviewRemark: '抢跑' }); if (early.json?.code === 1_030_101_016) ok('正确拒绝:非审批中不可审核', `code=${early.json.code}`); else fail('未按预期拒绝', JSON.stringify(early.json)); console.log('\n[5] 提交 → 审批中'); const sub = await api('PUT', `/purchase/order/submit?id=${id}`); if (sub.json?.code === 0) ok('提交成功', JSON.stringify(sub.json)); else fail('提交失败', JSON.stringify(sub.json)); const o1 = await getOrder(id); if (o1?.status === 10) ok('状态已置为审批中(10)'); else fail('提交后状态不对', `status=${o1?.status}`); console.log('\n[6] 审批中不可修改'); const upd = await api('PUT', '/purchase/order/update', { id, supplierId, orderTime: '2026-09-16 10:00:00', remark: '审批中改单', items: [{ productId: PRODUCT_ID, productUnitId: PRODUCT_UNIT_ID, productPrice: 100, count: 5, taxPercent: 13 }], }); if (upd.json?.code === 1_030_101_019) ok('正确拒绝:审批中不可修改', `code=${upd.json.code}`); else fail('审批中居然可修改', JSON.stringify(upd.json)); console.log('\n[7] 非审批人不可审核'); await ensureConfig([111]); // 临时把审批人改成「测试用户」 const notAp = await api('PUT', '/purchase/order/audit', { id, pass: true, reviewRemark: '越权' }); if (notAp.json?.code === 1_002_030_005) ok('正确拒绝:当前用户不是审批人', `code=${notAp.json.code}`); else fail('越权审核未被拦截', JSON.stringify(notAp.json)); await ensureConfig([1]); // 还原 const back = await api('GET', '/purchase/order/approver-ids'); if ((back.json?.data || []).includes(1)) ok('审批人已还原为 admin'); else fail('审批人还原失败', JSON.stringify(back.json?.data)); console.log('\n[8] 驳回必须填原因'); const noWhy = await api('PUT', '/purchase/order/audit', { id, pass: false }); if (noWhy.json?.code === 1_030_101_017) ok('正确拒绝:缺驳回原因', `code=${noWhy.json.code}`); else fail('缺原因未被拦截', JSON.stringify(noWhy.json)); console.log('\n[9] 审核不通过 → 退回'); const rej = await api('PUT', '/purchase/order/audit', { id, pass: false, reviewRemark: '价格偏高,退回重议' }); if (rej.json?.code === 0) ok('驳回成功', JSON.stringify(rej.json)); else fail('驳回失败', JSON.stringify(rej.json)); const o2 = await getOrder(id); if (o2?.status === 30) ok('状态为审核不通过(30)'); else fail('驳回后状态不对', `status=${o2?.status}`); if (o2?.reviewerName && o2?.reviewRemark === '价格偏高,退回重议') ok('审核人与驳回原因已落库', `${o2.reviewerName} / ${o2.reviewRemark}`); else fail('审核信息未落库', JSON.stringify({ rn: o2?.reviewerName, rr: o2?.reviewRemark })); console.log('\n[10] 退回后可修改并重新提交'); const upd2 = await api('PUT', '/purchase/order/update', { id, supplierId, orderTime: '2026-09-16 10:00:00', remark: '已降价重议', items: [{ productId: PRODUCT_ID, productUnitId: PRODUCT_UNIT_ID, productPrice: 80, count: 5, taxPercent: 13 }], }); if (upd2.json?.code === 0) ok('退回后可修改', JSON.stringify(upd2.json)); else fail('退回后仍不可修改', JSON.stringify(upd2.json)); const sub2 = await api('PUT', `/purchase/order/submit?id=${id}`); if (sub2.json?.code === 0) ok('重新提交成功'); else fail('重新提交失败', JSON.stringify(sub2.json)); const o3 = await getOrder(id); if (o3?.status === 10 && !o3?.reviewRemark && !o3?.reviewerName) ok('重新提交已清空上一轮审核信息', `reviewRemark=${JSON.stringify(o3?.reviewRemark ?? null)}`); else fail('重提后审核信息残留', JSON.stringify({ st: o3?.status, rn: o3?.reviewerName, rr: o3?.reviewRemark })); console.log('\n[11] 审核通过'); const app = await api('PUT', '/purchase/order/audit', { id, pass: true, reviewRemark: '同意采购' }); if (app.json?.code === 0) ok('审核通过成功', JSON.stringify(app.json)); else fail('审核通过失败', JSON.stringify(app.json)); const o4 = await getOrder(id); if (o4?.status === 20) ok('状态为审核通过(20)', `审核人=${o4.reviewerName}`); else fail('通过后状态不对', `status=${o4?.status}`); console.log('\n[12] 已审核不可修改'); const upd3 = await api('PUT', '/purchase/order/update', { id, supplierId, orderTime: '2026-09-16 10:00:00', remark: '审核后再改', items: [{ productId: PRODUCT_ID, productUnitId: PRODUCT_UNIT_ID, productPrice: 80, count: 5, taxPercent: 13 }], }); if (upd3.json?.code === 1_030_101_005) ok('正确拒绝:已审核不可修改', `code=${upd3.json.code}`); else fail('已审核仍可修改', JSON.stringify(upd3.json)); console.log('\n[13] 全程不应再出现 500 系统异常'); const calls = [ ['GET', `/purchase/order/page?pageNo=1&pageSize=10`], ['GET', `/purchase/order/approver-ids`], ]; let bad500 = 0; for (const [m, p] of calls) { const r = await api(m, p); if (r.json?.code === 500) { bad500++; console.log(` ${m} ${p} → 500`); } } if (bad500 === 0) ok('关键接口无 500'); else fail(`仍有 ${bad500} 个接口返回 500`); console.log(`\n测试单据:erp_purchase_order id=${id}(${o0?.no})`); const passed = results.filter((r) => r.pass).length; console.log(`===== 汇总:${passed}/${results.length} 通过 =====`); results.filter((r) => !r.pass).forEach((r) => console.log(` 失败: ${r.name} ${r.extra}`)); process.exit(passed === results.length ? 0 : 1); })().catch((e) => { console.error('执行异常:', e.message); process.exit(1); });