| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457 |
- /**
- * 全生命周期联调:月度补助 + 特殊业务 + 回盘/待补发(含异常断言)
- * 用法:node e2e-lifecycle-full.js
- * 每次使用独立发放月,避免与历史批次冲突。
- */
- const http = require("http");
- const asserts = [];
- function check(name, cond, detail) {
- asserts.push({ name, pass: !!cond, detail: detail || "" });
- console.log((cond ? "PASS" : "FAIL") + " | " + name + (detail ? " | " + detail : ""));
- }
- function req(method, path, body) {
- return new Promise((resolve) => {
- const data = body == null ? null : JSON.stringify(body);
- const headers = { Accept: "application/json" };
- if (data) {
- headers["Content-Type"] = "application/json";
- headers["Content-Length"] = Buffer.byteLength(data);
- }
- const r = http.request(
- { hostname: "localhost", port: 8088, path: "/sjnmtybt" + path, method, headers },
- (res) => {
- let raw = "";
- res.on("data", (c) => (raw += c));
- res.on("end", () => {
- try {
- resolve(JSON.parse(raw));
- } catch (e) {
- resolve({ code: res.statusCode, parseError: true, raw: String(raw).slice(0, 200) });
- }
- });
- }
- );
- r.on("error", (e) => resolve({ code: 0, error: e.message }));
- if (data) r.write(data);
- r.end();
- });
- }
- /** 生成独立 YYYYMM,降低与存量批次冲突概率 */
- function uniquePayMonth(offsetMonths) {
- const d = new Date();
- d.setMonth(d.getMonth() + (offsetMonths || 0));
- // 再叠毫秒尾数进「虚构年」段,避免同月重复跑
- const base = 2100 + (Date.now() % 50);
- const m = String(((d.getMonth() + Math.abs(offsetMonths || 0)) % 12) + 1).padStart(2, "0");
- return String(base) + m;
- }
- async function purgeVillageMonth(townId, villageId, payMonth) {
- const page = await req("POST", "/api/batches/page", {
- pageNum: 1,
- pageSize: 50,
- townId,
- villageId,
- payMonth,
- batchLevel: "VILLAGE",
- });
- const list = page.data?.list || [];
- for (const b of list) {
- if (b?.id && b.status !== "ARCHIVED") {
- await req("POST", "/api/batches/" + encodeURIComponent(b.id) + "/purge");
- }
- }
- }
- const townId = "T_YEXIE";
- const villageId = "V_YX_001";
- const villageId2 = "V_YX_002";
- const payMonth = uniquePayMonth(0);
- const payMonthFail = uniquePayMonth(1);
- (async () => {
- console.log("联调发放月 payMonth=" + payMonth + " payMonthFail=" + payMonthFail);
- await purgeVillageMonth(townId, villageId, payMonth);
- await purgeVillageMonth(townId, villageId2, payMonthFail);
- console.log("\n=== 0 前置:人员 ===");
- const idNo = "31011719900101" + String(Date.now()).slice(-4);
- const saved = await req("POST", "/api/personnel", {
- name: "生命周期测员",
- idNumber: idNo,
- townId,
- villageId,
- bankCardNumber: "6228480030009999888",
- monthlyStandard: 1280,
- enjoyStartMonth: "202601",
- source: "联调",
- });
- check("新建人员", saved.code === 200 && !!saved.data?.id, saved.message || saved.data?.id);
- const pid = saved.data?.id;
- if (!pid) {
- console.log("无人员,中止");
- process.exit(2);
- }
- console.log("\n=== 1 月度补助全生命周期 ===");
- const batch = await req("POST", "/api/batches", {
- batchNo: payMonth,
- batchLevel: "VILLAGE",
- payMonth,
- townId,
- villageId,
- payDate: "2026-08-20",
- personnelIds: [pid],
- });
- check("建村批", batch.code === 200 && !!batch.data?.id, batch.message || batch.data?.id);
- const bid = batch.data?.id;
- if (!bid) {
- console.log("无批次,中止月度段");
- }
- const badDup = await req("POST", "/api/batches", {
- batchNo: payMonth,
- batchLevel: "VILLAGE",
- payMonth,
- townId,
- villageId,
- personnelIds: [pid],
- });
- check(
- "同村同月重复建批应失败或复用",
- badDup.code !== 200 || badDup.data?.id === bid,
- badDup.code + "/" + (badDup.data?.id || badDup.message)
- );
- if (bid) {
- // 草稿须先提交初审,才能镇审(门禁)
- const submit = await req("POST", "/api/batches/" + encodeURIComponent(bid) + "/submit");
- check(
- "提交初审",
- submit.code === 200 && submit.data?.status === "PENDING_TOWN_APPROVE",
- submit.message + " " + submit.data?.status
- );
- const townPass = await req("POST", "/api/batches/town-approve", {
- bizId: bid,
- action: "PASS",
- opinion: "材料齐全",
- });
- check("镇初审通过", townPass.code === 200, townPass.message);
- let b = await req("GET", "/api/batches/" + encodeURIComponent(bid));
- check("初审后进入居保", b.data?.currentStage === "STAGE3_INSURANCE_MATCH", b.data?.currentStage);
- const match = await req("POST", "/api/insurance-match/trigger", { batchId: bid });
- check("居保匹配", match.code === 200, match.message);
- await req("POST", "/api/batches/" + encodeURIComponent(bid) + "/submit-leader");
- const leaderReject = await req("POST", "/api/batches/leader-approve", {
- bizId: bid,
- action: "REJECT",
- opinion: "先驳回测",
- });
- check("领导可驳回", leaderReject.code === 200, leaderReject.message);
- b = await req("GET", "/api/batches/" + encodeURIComponent(bid));
- check("驳回后状态 REJECTED", b.data?.status === "REJECTED", b.data?.status);
- // 领导驳回后回到清单阶段,再 submit-leader 后复审
- await req("POST", "/api/batches/" + encodeURIComponent(bid) + "/submit-leader");
- const leaderPass = await req("POST", "/api/batches/leader-approve", {
- bizId: bid,
- action: "PASS",
- opinion: "同意出盘",
- });
- check("领导再通过", leaderPass.code === 200, leaderPass.message);
- b = await req("GET", "/api/batches/" + encodeURIComponent(bid));
- check(
- "可出盘或待领导后通过",
- ["READY_EXPORT", "PENDING_LEADER_APPROVE", "EXPORTED"].includes(b.data?.status),
- b.data?.status
- );
- if (b.data?.status === "PENDING_LEADER_APPROVE") {
- await req("POST", "/api/batches/leader-approve", { bizId: bid, action: "PASS", opinion: "同意" });
- b = await req("GET", "/api/batches/" + encodeURIComponent(bid));
- }
- const exp = await req("POST", "/api/payments/export-disk", {
- batchId: bid,
- bypassDeadline: true,
- operator: "业务",
- });
- check("出盘", exp.code === 200, exp.message);
- let ret2 = await req("POST", "/api/return-disk/confirm", {
- batchId: bid,
- manualAllSuccess: false,
- returnDate: "2026-08-25",
- operator: "业务",
- lines: [{ personnelId: pid, success: true }],
- });
- if (ret2.code !== 200) {
- ret2 = await req("POST", "/api/return-disk/confirm", {
- batchId: bid,
- manualAllSuccess: true,
- returnDate: "2026-08-25",
- operator: "业务",
- });
- }
- check("回盘确认", ret2.code === 200, ret2.message);
- const reConfirm = await req("POST", "/api/return-disk/confirm", {
- batchId: bid,
- manualAllSuccess: true,
- returnDate: "2026-08-26",
- });
- check("重复回盘应拒绝", reConfirm.code !== 200, reConfirm.code + " " + reConfirm.message);
- }
- console.log("\n=== 2 特殊业务:丧葬 ===");
- const p2 = await req("POST", "/api/personnel", {
- name: "丧葬测员",
- idNumber: "31011719880101" + String(Date.now()).slice(-4),
- townId,
- villageId: villageId2,
- bankCardNumber: "6228480030007777666",
- monthlyStandard: 1280,
- enjoyStartMonth: "202601",
- });
- const pid2 = p2.data?.id;
- const funNoPerson = await req("POST", "/api/special-biz", { bizType: "FUNERAL", amount: 6000 });
- check("丧葬未选人失败", funNoPerson.code !== 200, funNoPerson.message);
- const fun = await req("POST", "/api/special-biz", {
- bizType: "FUNERAL",
- personnelId: pid2,
- amount: 6000,
- reason: "去世",
- heirName: "继承人甲",
- heirIdNumber: "310117199001011111",
- heirBankCard: "6228480030001111000",
- heirPhone: "13800001111",
- });
- check("创建丧葬单", fun.code === 200 && !!fun.data?.id, fun.message);
- const funId = fun.data?.id;
- check("丧葬金额默认/传入", Number(fun.data?.amount) === 6000, fun.data?.amount);
- const funRe = await req("POST", "/api/special-biz/leader-approve", {
- bizId: funId,
- action: "PASS",
- opinion: "同意",
- });
- check("丧葬审批通过", funRe.code === 200 && funRe.data?.approveStatus === "已通过", funRe.data?.approveStatus);
- const funRe2 = await req("POST", "/api/special-biz/leader-approve", {
- bizId: funId,
- action: "PASS",
- opinion: "再审",
- });
- check("重复审批拒绝", funRe2.code !== 200, funRe2.message);
- const p2g = await req("GET", "/api/personnel/" + encodeURIComponent(pid2));
- check("审批后人员丧葬态", p2g.data?.bizStatus === "FUNERAL", p2g.data?.bizStatus);
- const expSp = await req("POST", "/api/special-biz/" + funId + "/export-disk");
- check("特殊出盘(已通过)", expSp.code === 200, expSp.message);
- const expSp2 = await req("POST", "/api/special-biz/" + funId + "/export-disk");
- check("特殊出盘不可重复", expSp2.code !== 200, expSp2.message);
- console.log("\n=== 3 特殊业务:暂停→恢复 ===");
- const p3 = await req("POST", "/api/personnel", {
- name: "暂停测员",
- idNumber: "31011719770101" + String(Date.now()).slice(-4),
- townId,
- villageId,
- bankCardNumber: "6228480030005555444",
- monthlyStandard: 1280,
- enjoyStartMonth: "202601",
- });
- const pid3 = p3.data?.id;
- const resumeEarly = await req("POST", "/api/special-biz", { bizType: "RESUME", personnelId: pid3 });
- check("非暂停人员恢复应失败", resumeEarly.code !== 200, resumeEarly.message);
- const pause = await req("POST", "/api/special-biz", {
- bizType: "PAUSE",
- personnelId: pid3,
- reason: "外出",
- });
- check("创建暂停", pause.code === 200, pause.message);
- await req("POST", "/api/special-biz/leader-approve", {
- bizId: pause.data.id,
- action: "PASS",
- opinion: "同意暂停",
- });
- const p3g = await req("GET", "/api/personnel/" + encodeURIComponent(pid3));
- check("暂停后状态 PAUSED", p3g.data?.bizStatus === "PAUSED", p3g.data?.bizStatus);
- const preview = await req("GET", "/api/special-biz/resume-preview?personnelId=" + encodeURIComponent(pid3));
- check("恢复预览", preview.code === 200 && preview.data?.amount != null, JSON.stringify(preview.data).slice(0, 120));
- const resume = await req("POST", "/api/special-biz", { bizType: "RESUME", personnelId: pid3 });
- check("创建恢复", resume.code === 200, resume.message);
- await req("POST", "/api/special-biz/leader-approve", {
- bizId: resume.data.id,
- action: "PASS",
- opinion: "同意恢复",
- });
- const p3g2 = await req("GET", "/api/personnel/" + encodeURIComponent(pid3));
- check("恢复后 NORMAL", p3g2.data?.bizStatus === "NORMAL", p3g2.data?.bizStatus);
- console.log("\n=== 4 特殊业务:调标(需历史两档标准)===");
- // 清掉本镇本年未归档调标批,避免「一年仅一次」挡住联调
- const year = String(new Date().getFullYear());
- // 多扫几轮,清掉同号重复残留
- for (let round = 0; round < 3; round++) {
- const page = await req("POST", "/api/batches/page", {
- pageNum: 1,
- pageSize: 200,
- townId,
- batchLevel: "VILLAGE",
- });
- let n = 0;
- for (const b of page.data?.list || []) {
- const id = String(b?.id || "");
- const pm = String(b?.payMonth || "");
- const remarks = String(b?.remarks || "");
- // 调标一年一次会扫历史(含已归档),联调前一并 purge
- if (id.includes(year + "99") || pm === year + "99" || remarks.includes("YEARLY_ADJUST|" + year + "99")) {
- const pr = await req("POST", "/api/batches/" + encodeURIComponent(id) + "/purge");
- console.log(" purged adjust batch " + id + " st=" + b.status + " rows=" + (pr.data?.purgedRows ?? "?"));
- n++;
- }
- }
- if (!n) break;
- }
- // 读取当前有效土地标准,再上调一档,保证有正差额
- const curStd = await req("GET", "/api/amount-standards/current?townId=" + encodeURIComponent(townId));
- const curAmt = Number(curStd.data?.amount || 1280);
- const nextAmt = curAmt + 100 + (Date.now() % 40);
- const stdNew = await req("POST", "/api/amount-standards", {
- townId,
- standardType: "YEARLY_ADJUST",
- amount: nextAmt,
- funeralAmount: 6000,
- effectiveTime: Date.now(),
- adjustReason: "联调调标+" + nextAmt,
- });
- check("写入更高年度标准" + nextAmt, stdNew.code === 200, stdNew.message + " cur=" + curAmt);
- const adjNoTown = await req("POST", "/api/special-biz", { bizType: "STANDARD_ADJUST" });
- check("调标无镇失败", adjNoTown.code !== 200, adjNoTown.message);
- const adj = await req("POST", "/api/special-biz", {
- bizType: "STANDARD_ADJUST",
- townId,
- reason: "联调调标补发",
- });
- check(
- "创建调标特殊单",
- adj.code === 200 && !!adj.data?.id,
- adj.message + " batches=" + (adj.data?.relatedBatchIds || "")
- );
- const adjId = adj.data?.id;
- const adjPassEarly = await req("POST", "/api/special-biz/leader-approve", {
- bizId: adjId,
- action: "PASS",
- opinion: "未审村批就通过",
- });
- check("未审村批时调标特殊单不可通过", adjPassEarly.code !== 200, adjPassEarly.message);
- const batchIds = String(adj.data?.relatedBatchIds || "")
- .split(",")
- .map((s) => s.trim())
- .filter(Boolean);
- check("调标生成村批", batchIds.length >= 1, "n=" + batchIds.length);
- for (const id of batchIds) {
- const lp = await req("POST", "/api/batches/leader-approve", {
- bizId: id,
- action: "PASS",
- opinion: "同意调标村批",
- });
- check("村批领导通过 " + id, lp.code === 200, lp.message);
- }
- const adjPass = await req("POST", "/api/special-biz/leader-approve", {
- bizId: adjId,
- action: "PASS",
- opinion: "村批已齐",
- });
- check(
- "村批通过后特殊单可通过",
- adjPass.code === 200 && adjPass.data?.approveStatus === "已通过",
- adjPass.message
- );
- const adjExp = await req("POST", "/api/special-biz/" + adjId + "/export-disk");
- check("调标不可走特殊出盘", adjExp.code !== 200, adjExp.message);
- console.log("\n=== 5 回盘失败→待补发 ===");
- const p4 = await req("POST", "/api/personnel", {
- name: "回盘失败员",
- idNumber: "31011719660101" + String(Date.now()).slice(-4),
- townId,
- villageId: villageId2,
- bankCardNumber: "6228480030003333222",
- monthlyStandard: 1280,
- enjoyStartMonth: "202601",
- });
- const pid4 = p4.data?.id;
- const bFail = await req("POST", "/api/batches", {
- batchNo: payMonthFail,
- batchLevel: "VILLAGE",
- payMonth: payMonthFail,
- townId,
- villageId: villageId2,
- personnelIds: [pid4],
- });
- const bidFail = bFail.data?.id;
- if (bidFail) {
- await req("POST", "/api/batches/" + encodeURIComponent(bidFail) + "/submit");
- await req("POST", "/api/batches/town-approve", { bizId: bidFail, action: "PASS", opinion: "ok" });
- await req("POST", "/api/insurance-match/trigger", { batchId: bidFail });
- await req("POST", "/api/batches/" + encodeURIComponent(bidFail) + "/submit-leader");
- await req("POST", "/api/batches/leader-approve", { bizId: bidFail, action: "PASS", opinion: "ok" });
- const expFail = await req("POST", "/api/payments/export-disk", {
- batchId: bidFail,
- bypassDeadline: true,
- operator: "联调",
- });
- check("历史月补办出盘", expFail.code === 200, expFail.message);
- const pays = await req("POST", "/api/payments/details/page", {
- batchId: bidFail,
- pageNum: 1,
- pageSize: 20,
- });
- const payId = (pays.data?.list || [])[0]?.id;
- const failRet = await req("POST", "/api/return-disk/confirm", {
- batchId: bidFail,
- returnDate: "2026-07-28",
- lines: [
- {
- paymentDetailId: payId,
- success: false,
- failReasonCode: "CODE_003",
- failReasonDesc: "冻结",
- },
- ],
- });
- check("失败回盘", failRet.code === 200, failRet.message);
- const arrears = await req("POST", "/api/return-arrears/page", {
- pageNum: 1,
- pageSize: 50,
- personnelId: pid4,
- });
- check(
- "生成待补发台账",
- arrears.code === 200 && (arrears.data?.total || 0) >= 1,
- "total=" + arrears.data?.total
- );
- } else {
- check("失败回盘建批", false, bFail.message);
- }
- console.log("\n=== 汇总 ===");
- const pass = asserts.filter((a) => a.pass).length;
- const fail = asserts.filter((a) => !a.pass).length;
- console.log("PASS=" + pass + " FAIL=" + fail + " TOTAL=" + asserts.length);
- if (fail) {
- asserts.filter((a) => !a.pass).forEach((a) => console.log(" - " + a.name + " | " + a.detail));
- process.exit(2);
- }
- console.log("ALL GREEN");
- })().catch((e) => {
- console.error(e);
- process.exit(1);
- });
|