/**
|
* 采购订单状态机审批 - 接口级自动化测试
|
*
|
* 自建测试单据(每次新建一张),不依赖历史数据状态,可重复执行。
|
* 覆盖:提交/审核/驳回/重新提交/通过的完整链路 + 全部服务端校验分支。
|
*
|
* 用法: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);
|
});
|