#!/usr/bin/env node /** * 模拟前 3 个月(202605 / 202606 / 202607)全区月批 + 镇统计完整生命周期与流程日志。 * 对齐现行模型:DB{yyyyMM} 全区批、TB{yyyyMM}-{townId} 镇统计。 * * 用法: * node scripts/seed-history-3months.js * node scripts/seed-history-3months.js --dry-run */ const http = require("http"); const qs = require("querystring"); const fs = require("fs"); const path = require("path"); const HOST = process.env.DMS_HOST || "121.43.55.7"; const PORT = Number(process.env.DMS_PORT || 2101); const DRY = process.argv.includes("--dry-run"); const MARKER = "HISTORY_SEED_20260828"; const COL = { pcb: { columnId: 1845, modelId: 1968 }, ffmx: { columnId: 1849, modelId: 1974 }, lcb: { columnId: 1844, modelId: 1967 }, alart: { columnId: 1841, modelId: 1964 }, }; const TOWNS = [ { id: "T_YEXIE", name: "叶榭镇", plan: "17010002" }, { id: "T_SHESHAN", name: "佘山镇", plan: "17010003" }, { id: "T_CHEDUN", name: "车墩镇", plan: "17010004" }, { id: "T_MAOGANG", name: "泖港镇", plan: "17010005" }, { id: "T_XINBANG", name: "新浜镇", plan: "17010006" }, { id: "T_XIAOKUNSHAN", name: "小昆山镇", plan: "17010007" }, ]; function request(method, urlPath, fields, token) { return new Promise((resolve, reject) => { const body = qs.stringify(fields || {}); const headers = { "Content-Type": "application/x-www-form-urlencoded", "Content-Length": Buffer.byteLength(body), }; if (token) { headers.Token = token; headers.token = token; } const req = http.request( { hostname: HOST, port: PORT, path: urlPath, method: method || "POST", headers }, (res) => { let data = ""; res.on("data", (c) => (data += c)); res.on("end", () => { try { resolve(JSON.parse(data)); } catch (e) { resolve({ raw: data, status: res.statusCode }); } }); } ); req.on("error", reject); req.write(body); req.end(); }); } async function login() { const res = await request("POST", "/proxy_oauth/user/login", { userName: "user_liu", password: "WE176852439@lmx", clientId: "1", }); if (res.code != 200) throw new Error("login fail: " + JSON.stringify(res)); return res.message; } async function listAll(token, col) { const rows = []; for (let page = 0; page < 80; page++) { const res = await request( "POST", "/proxy_dms/content/selectContentList", { columnId: String(col.columnId), modelId: String(col.modelId), page: String(page), pageSize: "100", states: "0", }, token ); if (res.code == 202) break; if (res.code != 200) throw new Error("list fail: " + JSON.stringify(res).slice(0, 200)); const data = (res.content && res.content.data) || []; rows.push(...data); const count = Number((res.content && res.content.count) || 0); if (rows.length >= count || data.length === 0) break; } return rows; } async function addContent(token, col, row) { if (DRY) return { code: 200, content: "dry-" + Math.random() }; return request( "POST", "/proxy_dms/content/addContent", { columnId: String(col.columnId), modelId: String(col.modelId), content: JSON.stringify(row), }, token ); } async function softDelete(token, col, dmsId) { if (DRY) return { code: 200 }; return request( "POST", "/proxy_dms/content/updateContent", { columnId: String(col.columnId), modelId: String(col.modelId), content: JSON.stringify({ id: dmsId, state: -1, c_remarks: "PURGED|" + MARKER }), }, token ); } /** 北京时间某时刻 → 毫秒时间戳 */ function ts(y, m, d, h = 10, min = 0) { return Date.UTC(y, m - 1, d, h - 8, min, 0, 0); } function payDate(ym) { const y = ym.slice(0, 4); const m = ym.slice(4, 6); return `${y}-${m}-20`; } function returnDate(ym, day = 25) { const y = ym.slice(0, 4); const m = ym.slice(4, 6); return `${y}-${m}-${String(day).padStart(2, "0")}`; } function mkDistrict(ym, opts) { const id = "DB" + ym; return { title: ym + "-全区月批", c_batch_number: id, c_batch_level: "DISTRICT", c_pay_month: ym, c_district_id: "D_SJ", c_current_stage: opts.stage, c_status: opts.status, c_people_count: opts.people, c_normal_amount: opts.normal, c_supplementary_payment_amount: opts.supplement || 0, c_funeral_amount: opts.funeral || 0, c_deduct_amount: opts.deduct || 0, c_total_amount: opts.total, c_export_status: opts.exportStatus || "未出盘", c_return_status: opts.returnStatus || "未回盘", c_pay_date: payDate(ym), c_return_date: opts.returnDate || null, c_submitter: opts.submitter || null, c_submit_time: opts.submitTime || null, c_remarks: `${MARKER}|DISTRICT_MONTH|${ym}|${opts.note || ""}`, content: MARKER, }; } function mkTown(ym, town, opts) { const id = "TB" + ym + "-" + town.id; const dbId = "DB" + ym; return { title: `${ym}-${town.name}-镇统计`, c_batch_number: id, c_batch_level: "TOWN", c_pay_month: ym, c_district_id: "D_SJ", c_town_id: town.id, c_parent_batch_id: dbId, c_entrust_plan_code: town.plan || "", c_current_stage: opts.stage, c_status: opts.status, c_people_count: opts.people, c_normal_amount: opts.normal, c_supplementary_payment_amount: opts.supplement || 0, c_funeral_amount: opts.funeral || 0, c_deduct_amount: opts.deduct || 0, c_total_amount: opts.total, c_export_disk_no: opts.exportDiskNo || null, c_export_file_name: opts.exportFileName || null, c_export_status: opts.exportStatus || "未出盘", c_return_status: opts.returnStatus || "未回盘", c_summary_no: opts.summaryNo || null, c_pay_bank: "农商", c_pay_date: payDate(ym), c_return_date: opts.returnDate || null, c_submitter: opts.submitter || "镇社区事务受理中心-业务科", c_submit_time: opts.submitTime || null, c_town_opinion: opts.townOpinion || null, c_return_file_name: opts.returnFileName || null, c_remarks: `${MARKER}|PARENT_BATCH=${dbId}|${opts.failCount ? "FAIL_CNT=" + opts.failCount + "|" : ""}${opts.note || ""}`, content: MARKER, }; } function mkLog(seq, row) { return { title: row.title, c_process_number: "PN" + seq, c_batch_id: row.batchId, c_batch_no: row.batchNo || row.batchId.replace(/^DB|^TB/, "").slice(0, 6), c_town_id: row.townId || "", c_village_id: row.villageId || "", c_stage: row.stage, c_biz_type: row.bizType || "月度批次", c_approve_node: row.node, c_operator: row.operator, c_operator_role: row.role || "", c_operator_org: row.org || "", c_action: row.action, c_opinion: row.opinion || "", c_result: row.result, c_status: "已完成", c_operate_time: row.time, c_remarks: `${MARKER}|${row.remark || ""}`, content: MARKER, }; } function mkDetail(ym, town, person, batchId) { const monthly = person.monthly || 1380; const supplement = person.supplement || 0; const funeral = person.funeral || 0; const deduct = person.deduct || 0; const total = person.total != null ? person.total : monthly + supplement + funeral - deduct; return { title: `${person.name}-${ym}`, c_batch_id: "DB" + ym, c_batch_number: "DB" + ym, c_user_id: person.id, c_name: person.name, c_id_number: person.idNo, c_pay_month: ym, c_town_id: town.id, c_village_id: person.villageId || "", c_district_id: "D_SJ", c_standard_amount: monthly, c_one_time_amount: 0, c_supplementary_payment_amount: supplement, c_death_amount: funeral, c_funeral_amount: funeral, c_deduct_amount: deduct, c_total_amount: total, c_actual_amount: total, c_pay_bank: person.bank || "农商", c_bank_card_number: person.card || "", c_exported: person.exported != null ? String(person.exported) : "true", c_return_success: person.returnOk != null ? String(person.returnOk) : "true", c_remarks: `${MARKER}|${person.note || ""}`, content: MARKER, }; } function mkFailDetail(ym, town, person) { const reason = person.failReason || "账户冻结"; const code = person.failCode || "CODE_003"; const row = mkDetail(ym, town, person); row.c_actual_amount = 0; row.c_return_success = "false"; row.c_return_result = "失败"; row.c_fail_reason_code = code; row.c_remarks = `${MARKER}|回盘失败|${reason}|RETURN_FAIL|${code}`; return row; } function mkSuccessDetail(ym, town, person) { const row = mkDetail(ym, town, person); row.c_return_success = "true"; row.c_return_result = "成功"; row.c_remarks = `${MARKER}|回盘成功`; return row; } function mkAlert(ym, town, person, type, content, status) { return { title: `AL-${type}-${person.name}`, c_batch_no: ym, c_batch_id: "TB" + ym + "-" + town.id, c_personnel_id: person.id, c_personnel_name: person.name, c_town_id: town.id, c_village_id: person.villageId || "", c_level: type.includes("FAIL") || type.includes("DEATH") ? "HIGH" : "MEDIUM", c_type: type, c_content: content, c_handle_status: status === "CLOSED" ? "CLOSED" : status === "PENDING" ? "PENDING" : "PROCESSING", c_initiator: "系统", c_recipient: "镇社区事务受理中心-业务部门", c_status: status === "CLOSED" ? "已关闭" : status === "PENDING" ? "待处理" : "处理中", c_remarks: MARKER, content: MARKER, }; } /** 标准 8 步生命周期日志(镇统计视角,含全区节点) */ function lifecycleLogs(ym, town, tbId, scenario, t0) { const logs = []; let seq = Number(ym) * 100; const push = (row) => logs.push(mkLog(++seq, row)); push({ title: `${ym}-生成全区月批`, batchId: "DB" + ym, batchNo: ym, stage: "STAGE3_INSURANCE_MATCH", node: "系统调度", operator: "系统", role: "定时任务", org: "区平台", action: "生成全区月批", opinion: "每月1日自动生成全区批次并纳入符合条件人员", result: "成功", time: t0, remark: "DISTRICT_GENERATE", }); push({ title: `${ym}-居保状态匹配`, batchId: "DB" + ym, batchNo: ym, townId: town.id, stage: "STAGE4_MONTHLY_LIST", node: "社区事务中心-业务部门", operator: "李经办", role: "镇社区事务受理中心", org: town.name + "社区事务受理中心", action: "居保匹配", opinion: scenario.insuranceNote || "本镇人员居保匹配完成", result: scenario.insuranceResult || "成功", time: t0 + 86400000, remark: "INSURANCE_MATCH", }); if (scenario.submitLeader !== false) { push({ title: `${ym}-${town.name}-提交领导`, batchId: tbId, batchNo: ym, townId: town.id, stage: "STAGE5_LEADER_ONLINE_APPROVE", node: "社区事务中心-业务部门", operator: "王专员", role: "镇社区事务受理中心", org: town.name + "社区事务受理中心", action: "提交领导", opinion: "本镇居保已闭环,特殊业务已全部通过,提交分管领导审批", result: "成功", time: t0 + 86400000 * 4, remark: "SUBMIT_LEADER", }); } if (scenario.leaderReject) { push({ title: `${ym}-${town.name}-领导驳回`, batchId: tbId, batchNo: ym, townId: town.id, stage: "STAGE5_LEADER_ONLINE_APPROVE", node: "分管领导", operator: "张分管", role: "分管领导", org: town.name + "社区事务受理中心", action: "驳回", opinion: scenario.rejectOpinion || "清单人数波动较大,请核对补发明细后重提", result: "失败", time: t0 + 86400000 * 5, remark: "LEADER_REJECT", }); push({ title: `${ym}-${town.name}-调整后重提`, batchId: tbId, batchNo: ym, townId: town.id, stage: "STAGE5_LEADER_ONLINE_APPROVE", node: "社区事务中心-业务部门", operator: "王专员", role: "镇社区事务受理中心", org: town.name + "社区事务受理中心", action: "提交领导", opinion: "已核对补发与扣减,再次提交", result: "成功", time: t0 + 86400000 * 7, remark: "RESUBMIT_LEADER", }); } if (scenario.leaderPass !== false && scenario.status !== "REJECTED") { push({ title: `${ym}-${town.name}-领导通过`, batchId: tbId, batchNo: ym, townId: town.id, stage: "STAGE6_COMPARE_FILE", node: "分管领导", operator: "张分管", role: "分管领导", org: town.name + "社区事务受理中心", action: "通过", opinion: scenario.passOpinion || "同意发放", result: "成功", time: t0 + 86400000 * (scenario.leaderReject ? 8 : 6), remark: "LEADER_PASS", }); } if (["EXPORTED", "RETURNED", "ARCHIVED"].includes(scenario.status)) { push({ title: `${ym}-${town.name}-出盘`, batchId: tbId, batchNo: ym, townId: town.id, stage: "STAGE7_OFFLINE_SIGN_PAY", node: "社区事务中心-业务部门", operator: "王专员", role: "镇社区事务受理中心", org: town.name + "社区事务受理中心", action: "出盘", opinion: "已生成 N7C 对照文件并导出", result: "成功", time: t0 + 86400000 * 17, remark: "EXPORT_DISK", }); } if (["RETURNED", "ARCHIVED"].includes(scenario.status)) { push({ title: `${ym}-${town.name}-回盘确认`, batchId: tbId, batchNo: ym, townId: town.id, stage: "STAGE8_RETURN_CONFIRM", node: "社区事务中心-业务部门", operator: "王专员", role: "镇社区事务受理中心", org: town.name + "社区事务受理中心", action: "回盘确认", opinion: scenario.returnNote || "回盘全部成功", result: scenario.returnResult || "成功", time: t0 + 86400000 * 22, remark: "RETURN_CONFIRM", }); } if (scenario.status === "ARCHIVED") { push({ title: `${ym}-${town.name}-归档`, batchId: tbId, batchNo: ym, townId: town.id, stage: "STAGE8_RETURN_CONFIRM", node: "社区事务中心-业务部门", operator: "王专员", role: "镇社区事务受理中心", org: town.name + "社区事务受理中心", action: "归档", opinion: "本月业务闭环完成", result: "成功", time: t0 + 86400000 * 24, remark: "ARCHIVE", }); } return logs; } function buildPlan() { const yexie = TOWNS[0]; const sheshan = TOWNS[1]; const chedun = TOWNS[2]; const maogang = TOWNS[3]; const xinbang = TOWNS[4]; const xiaokun = TOWNS[5]; const batches = []; const logs = []; const details = []; const alerts = []; // ===== 202605:完整闭环月(多镇已归档,佘山部分回盘失败) ===== const ym5 = "202605"; const t5 = ts(2026, 5, 1, 1, 0); batches.push( mkDistrict(ym5, { stage: "STAGE8_RETURN_CONFIRM", status: "ARCHIVED", people: 42150, normal: 58182000, funeral: 248680, total: 58430680, exportStatus: "已导出", returnStatus: "全部成功", returnDate: returnDate(ym5), note: "前三月历史-五月全区已归档", }) ); batches.push( mkTown(ym5, yexie, { stage: "STAGE8_RETURN_CONFIRM", status: "ARCHIVED", people: 9882, normal: 13635780, funeral: 84632, total: 13720412, exportDiskNo: "DP605Y001", exportFileName: "LSJ20260520.N7C", exportStatus: "已导出", returnStatus: "全部成功", summaryNo: "170000061805", returnDate: returnDate(ym5), submitTime: t5 + 86400000 * 4, townOpinion: "同意发放", note: "叶榭镇-完整闭环", }) ); batches.push( mkTown(ym5, sheshan, { stage: "STAGE8_RETURN_CONFIRM", status: "ARCHIVED", people: 5620, normal: 7755600, supplement: 2760, funeral: 24868, total: 7783228, exportDiskNo: "DP605S001", exportFileName: "LSJ20260520-SH.N7C", exportStatus: "已导出", returnStatus: "部分异常", returnDate: returnDate(ym5, 26), submitTime: t5 + 86400000 * 4, townOpinion: "同意发放", failCount: 1, returnFileName: "N7R202605SH.txt", note: "佘山镇-回盘1人账户冻结", }) ); batches.push( mkTown(ym5, chedun, { stage: "STAGE8_RETURN_CONFIRM", status: "ARCHIVED", people: 3180, normal: 4388400, total: 4388400, exportDiskNo: "DP605C001", exportFileName: "LSJ20260520-CD.N7C", exportStatus: "已导出", returnStatus: "全部成功", returnDate: returnDate(ym5), submitTime: t5 + 86400000 * 5, townOpinion: "同意", note: "车墩镇-正常归档", }) ); logs.push( ...lifecycleLogs(ym5, yexie, "TB" + ym5 + "-T_YEXIE", { status: "ARCHIVED" }, t5) ); logs.push( ...lifecycleLogs( ym5, sheshan, "TB" + ym5 + "-T_SHESHAN", { status: "ARCHIVED", returnNote: "1人账户冻结失败,已纳入下月补发台账", returnResult: "部分异常", }, t5 + 3600000 ) ); logs.push( ...lifecycleLogs(ym5, chedun, "TB" + ym5 + "-T_CHEDUN", { status: "ARCHIVED" }, t5 + 7200000) ); const p605 = [ { id: "H605001", name: "姜小妹", idNo: "31022719650312424X", villageId: "V_YX_001", card: "6228480030001111001" }, { id: "H605002", name: "封纪昌", idNo: "310227195804084251", villageId: "V_YX_001", card: "6228480030001111002" }, { id: "H605003", name: "张洪芳", idNo: "310227194912014268", villageId: "V_YX_002", supplement: 1380, card: "6228480030001111003" }, ]; p605.forEach((p) => details.push(mkSuccessDetail(ym5, yexie, p))); details.push( mkFailDetail(ym5, sheshan, { id: "H605101", name: "陈阿伯", idNo: "310227195001011234", villageId: "V_SS_001", card: "6228480030005555001", monthly: 1380, failReason: "账户冻结", failCode: "CODE_003", }) ); details.push( mkSuccessDetail(ym5, sheshan, { id: "H605102", name: "王荷香", idNo: "310227196203156789", villageId: "V_SS_001", card: "6228480030005555002", }) ); alerts.push( mkAlert( ym5, sheshan, { id: "H605101", name: "陈阿伯", villageId: "V_SS_001" }, "RETURN_FAIL", "回盘失败:账户冻结(失败码003)", "PENDING" ) ); // ===== 202606:领导驳回重提 + 部分回盘失败 + 一镇仍驳回 ===== const ym6 = "202606"; const t6 = ts(2026, 6, 1, 1, 0); batches.push( mkDistrict(ym6, { stage: "STAGE4_MONTHLY_LIST", status: "PENDING_STATUS_MAINTAIN", people: 42280, normal: 58351200, funeral: 124340, total: 58475540, note: "六月全区-居保已完成,各镇推进不一", }) ); batches.push( mkTown(ym6, yexie, { stage: "STAGE8_RETURN_CONFIRM", status: "ARCHIVED", people: 9920, normal: 13689600, supplement: 5520, deduct: 1380, total: 13693740, exportDiskNo: "DP606Y001", exportFileName: "LSJ20260620.N7C", exportStatus: "已导出", returnStatus: "部分异常", returnDate: returnDate(ym6, 27), submitTime: t6 + 86400000 * 7, townOpinion: "同意发放", failCount: 2, returnFileName: "N7R202606YX.txt", note: "叶榭-领导驳回后重提并成功归档", }) ); batches.push( mkTown(ym6, maogang, { stage: "STAGE4_MONTHLY_LIST", status: "REJECTED", people: 2105, normal: 2904900, total: 2904900, exportStatus: "未出盘", returnStatus: "未回盘", submitTime: t6 + 86400000 * 5, townOpinion: "清单与上月差异说明不充分,请补充", note: "泖港-领导驳回待调整", }) ); batches.push( mkTown(ym6, xinbang, { stage: "STAGE7_OFFLINE_SIGN_PAY", status: "EXPORTED", people: 1850, normal: 2553000, total: 2553000, exportDiskNo: "DP606X001", exportFileName: "LSJ20260620-XB.N7C", exportStatus: "已导出", returnStatus: "待确认", submitTime: t6 + 86400000 * 6, townOpinion: "同意", note: "新浜-已出盘待回盘", }) ); logs.push( ...lifecycleLogs( ym6, yexie, "TB" + ym6 + "-T_YEXIE", { status: "ARCHIVED", leaderReject: true, rejectOpinion: "补发 5520 元缺少附件说明,请补充后重提", returnNote: "2人卡号变更失败,已生成待补发", returnResult: "部分异常", }, t6 ) ); logs.push( ...lifecycleLogs( ym6, maogang, "TB" + ym6 + "-T_MAOGANG", { status: "REJECTED", leaderPass: false, leaderReject: false, submitLeader: true, }, t6 + 3600000 ) ); logs.push({ ...mkLog(202606999, { title: `${ym6}-泖港-领导驳回`, batchId: "TB" + ym6 + "-T_MAOGANG", batchNo: ym6, townId: maogang.id, stage: "STAGE5_LEADER_ONLINE_APPROVE", node: "分管领导", operator: "张分管", role: "分管领导", org: "泖港镇社区事务受理中心", action: "驳回", opinion: "清单与上月差异说明不充分,请补充", result: "失败", time: t6 + 86400000 * 6, remark: "LEADER_REJECT_FINAL", }), }); logs.push( ...lifecycleLogs( ym6, xinbang, "TB" + ym6 + "-T_XINBANG", { status: "EXPORTED", leaderPass: true }, t6 + 7200000 ) ); details.push( mkSuccessDetail(ym6, yexie, { id: "H606001", name: "陆建民", idNo: "310227196811221234", villageId: "V_YX_001", card: "6228480030002222001", supplement: 1380, }) ); details.push( mkFailDetail(ym6, yexie, { id: "H606002", name: "顾秀珍", idNo: "310227195503156789", villageId: "V_YX_002", card: "6228480030002222002", monthly: 1380, failReason: "卡号变更未生效", failCode: "CODE_005", }) ); details.push( mkFailDetail(ym6, yexie, { id: "H606003", name: "朱福根", idNo: "310227194708081122", villageId: "V_YX_001", card: "6228480030002222003", monthly: 1380, failReason: "账户冻结", failCode: "CODE_003", }) ); alerts.push( mkAlert( ym6, yexie, { id: "H606002", name: "顾秀珍", villageId: "V_YX_002" }, "RETURN_FAIL", "回盘失败:卡号变更未生效", "PROCESSING" ) ); // ===== 202607:当月前一月,多阶段并行 ===== const ym7 = "202607"; const t7 = ts(2026, 7, 1, 1, 0); batches.push( mkDistrict(ym7, { stage: "STAGE4_MONTHLY_LIST", status: "PENDING_STATUS_MAINTAIN", people: 42410, normal: 58525800, total: 58525800, note: "七月全区-居保已完成", }) ); batches.push( mkTown(ym7, yexie, { stage: "STAGE5_LEADER_ONLINE_APPROVE", status: "PENDING_LEADER_APPROVE", people: 9955, normal: 13737900, total: 13737900, exportStatus: "未出盘", returnStatus: "未回盘", submitTime: t7 + 86400000 * 5, note: "叶榭-已提交待领导审批", }) ); batches.push( mkTown(ym7, sheshan, { stage: "STAGE4_MONTHLY_LIST", status: "PENDING_STATUS_MAINTAIN", people: 5655, normal: 7803900, total: 7803900, exportStatus: "未出盘", returnStatus: "未回盘", note: "佘山-居保已完成,特殊业务未齐", }) ); batches.push( mkTown(ym7, xiaokun, { stage: "STAGE6_COMPARE_FILE", status: "READY_EXPORT", people: 1420, normal: 1959600, total: 1959600, exportStatus: "未出盘", returnStatus: "未回盘", submitTime: t7 + 86400000 * 6, townOpinion: "同意", note: "小昆山-领导已通过待出盘", }) ); logs.push( ...lifecycleLogs( ym7, yexie, "TB" + ym7 + "-T_YEXIE", { status: "PENDING_LEADER_APPROVE", leaderPass: false, submitLeader: true }, t7 ) ); logs.push( ...lifecycleLogs( ym7, sheshan, "TB" + ym7 + "-T_SHESHAN", { status: "PENDING_STATUS_MAINTAIN", submitLeader: false, leaderPass: false, insuranceNote: "本镇 3 笔特殊业务待领导审批,暂不可提交月补", }, t7 + 3600000 ) ); logs.push( ...lifecycleLogs( ym7, xiaokun, "TB" + ym7 + "-T_XIAOKUNSHAN", { status: "READY_EXPORT", leaderPass: true }, t7 + 7200000 ) ); details.push( mkDetail(ym7, yexie, { id: "H607001", name: "杨春梅", idNo: "31022719720315****", villageId: "V_YX_001", exported: false, note: "待领导审批", }) ); return { batches, logs, details, alerts }; } async function listBySearch(token, col, search) { const rows = []; for (let page = 0; page < 200; page++) { const res = await request( "POST", "/proxy_dms/content/selectContentList", { columnId: String(col.columnId), modelId: String(col.modelId), page: String(page), pageSize: "100", states: "0", search: JSON.stringify(search), }, token ); if (res.code == 202) break; if (res.code != 200) throw new Error("search fail: " + JSON.stringify(res).slice(0, 200)); const data = (res.content && res.content.data) || []; rows.push(...data); const count = Number((res.content && res.content.count) || 0); if (rows.length >= count || data.length === 0) break; } return rows; } async function purgeExisting(token) { const targets = [ { col: COL.pcb, match: (r) => isHistoryRow(r, ["c_pay_month", "c_batch_number", "c_remarks"]) }, { col: COL.lcb, match: (r) => isHistoryRow(r, ["c_batch_no", "c_batch_id", "c_remarks"]) }, { col: COL.ffmx, match: (r) => isHistoryRow(r, ["c_pay_month", "c_remarks"]), useSearch: true }, { col: COL.alart, match: (r) => isHistoryRow(r, ["c_batch_no", "c_remarks"]) }, ]; const months = ["202605", "202606", "202607"]; const ids = months.flatMap((ym) => [ "DB" + ym, ...TOWNS.map((t) => "TB" + ym + "-" + t.id), ]); let purged = 0; for (const t of targets) { let rows; if (t.useSearch) { const seen = new Set(); rows = []; const markerRows = await listBySearch(token, t.col, [ { field: "content", searchType: 1, content: { value: MARKER } }, ]); for (const r of markerRows) { if (!seen.has(r.id)) { seen.add(r.id); rows.push(r); } } for (const ym of months) { const monthRows = await listBySearch(token, t.col, [ { field: "c_pay_month", searchType: 1, content: { value: ym } }, ]); for (const r of monthRows) { if (!seen.has(r.id)) { seen.add(r.id); rows.push(r); } } } } else { rows = await listAll(token, t.col); } for (const r of rows) { const remark = String(r.c_remarks || r.content || ""); const payMonth = String(r.c_pay_month || ""); const batchNo = String(r.c_batch_number || r.c_batch_id || ""); const hit = remark.includes(MARKER) || ids.some((id) => batchNo === id || batchNo.includes(id)) || months.includes(payMonth); if (hit && t.match(r)) { await softDelete(token, t.col, r.id); purged++; } } } return purged; } function isHistoryRow(r, fields) { const months = ["202605", "202606", "202607"]; for (const f of fields) { const v = String(r[f] || ""); if (v.includes(MARKER)) return true; if (months.some((m) => v.includes(m))) return true; } return false; } async function seedRows(token, col, rows, label) { let ok = 0; let fail = 0; for (const row of rows) { const res = await addContent(token, col, row); if (res.code == 200) ok++; else { fail++; console.log(" x", label, res.code, res.message || res.content, row.title); } } return { ok, fail }; } (async () => { console.log(DRY ? "[dry-run]" : "[write]", "seed history 202605-202607 →", HOST + ":" + PORT); const token = await login(); console.log("login ok"); console.log("\n[0] 清理同月旧数据…"); const purged = await purgeExisting(token); console.log(" purged rows:", purged); const plan = buildPlan(); console.log("\n[1] 批次", plan.batches.length); const b = await seedRows(token, COL.pcb, plan.batches, "pcb"); console.log(" ok", b.ok, "fail", b.fail); console.log("\n[2] 流程日志", plan.logs.length); const l = await seedRows(token, COL.lcb, plan.logs, "lcb"); console.log(" ok", l.ok, "fail", l.fail); console.log("\n[3] 发放明细样例", plan.details.length); const d = await seedRows(token, COL.ffmx, plan.details, "ffmx"); console.log(" ok", d.ok, "fail", d.fail); console.log("\n[4] 关联告警", plan.alerts.length); const a = await seedRows(token, COL.alart, plan.alerts, "alart"); console.log(" ok", a.ok, "fail", a.fail); const summary = { marker: MARKER, months: ["202605", "202606", "202607"], purged, batches: b, logs: l, details: d, alerts: a, scenarios: { "202605": "叶榭/车墩完整归档;佘山部分回盘失败", "202606": "叶榭驳回后重提归档;泖港领导驳回;新浜已出盘待回盘", "202607": "叶榭待领导审;佘山居保完成未提交;小昆山待出盘", }, at: new Date().toISOString(), }; const out = path.join(__dirname, "seed-history-3months-result.json"); fs.writeFileSync(out, JSON.stringify(summary, null, 2)); console.log("\nWrote", out); console.log("\n场景摘要:"); Object.entries(summary.scenarios).forEach(([k, v]) => console.log(" ", k, v)); })().catch((e) => { console.error(e); process.exit(1); });