/** * 松江退养补贴 - DMS 模型字段迁移 * 网关: 121.43.55.7:2101 */ const http = require("http"); const qs = require("querystring"); const fs = require("fs"); const path = require("path"); const HOST = "121.43.55.7"; const PORT = 2101; const PARENT_COLUMN_ID = 1840; // farmers_retirement_subsidies function post(urlPath, fields, token, method) { 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(); }); } function text(shortName, alias, must, seq) { return { alias, customType: "", describe: "格式为富文本", frontType: "content", id: 2, must: !!must, name: "c_" + shortName, searchType: "2", sequence: seq || 1, showParam: "name,alias,desc,type,front_type,must,default_value", type: "text", }; } function num(shortName, alias, must, seq) { return { alias, customType: "", describe: "number", frontType: "float_num", id: 6, must: !!must, name: "c_" + shortName, searchType: "1,3", sequence: seq || 5, showParam: "name,alias,desc,type,front_type,must,default_value", sortType: "1,2", type: "double", }; } function ts(shortName, alias, must, seq) { return { alias, customType: "", describe: "时间戳", frontType: "date_time", id: 3, must: !!must, name: "c_" + shortName, searchType: "3", sequence: seq || 2, showParam: "name,alias,describe,type,front_type,must,date_type,date_picker", sortType: "1,2", type: "timestamp", }; } function intf(shortName, alias, must, seq) { return { alias, customType: "", describe: "number", frontType: "int_num", id: 5, must: !!must, name: "c_" + shortName, searchType: "1,3", sequence: seq || 4, showParam: "name,alias,desc,type,front_type,must,default_value", sortType: "1,2", type: "integer", }; } function normalizeFieldList(raw) { let fl = raw; if (typeof fl === "string") fl = JSON.parse(fl); const out = {}; Object.keys(fl || {}).forEach((k) => { let v = fl[k]; if (typeof v === "string") v = JSON.parse(v); out[k] = v; }); return out; } function shortOf(cName) { return cName.startsWith("c_") ? cName.slice(2) : cName; } async function getModel(token, modelId) { const res = await post("/proxy_dms/model/getModelById", { modelId: String(modelId) }, token); if (res.code != 200) throw new Error("getModel " + modelId + " fail: " + JSON.stringify(res)); return res.content; } /** * 通过 updateFieldList 增删改字段 * addMap: { shortName: paramObjWithCName } * delKeys: ['c_xxx'] * renameMap: { c_old: { newName: 'c_new', alias?: '' } } */ async function updateFieldList(token, modelId, addMap, delKeys, renameMap) { const model = await getModel(token, modelId); const fieldList = normalizeFieldList(model.fieldList); const addfieldList = {}; const delfieldList = {}; const updatefieldList = {}; // deletes (delKeys || []).forEach((k) => { if (fieldList[k]) { delfieldList[k] = fieldList[k]; delete fieldList[k]; } }); // renames Object.keys(renameMap || {}).forEach((oldKey) => { const conf = renameMap[oldKey]; if (!fieldList[oldKey]) return; const old = fieldList[oldKey]; const neu = Object.assign({}, old, { name: conf.newName, alias: conf.alias || old.alias, updateName: conf.newName, }); updatefieldList[oldKey] = Object.assign({}, old, { updateName: conf.newName, type: old.type || "text", }); delete fieldList[oldKey]; fieldList[conf.newName] = neu; }); // adds Object.keys(addMap || {}).forEach((shortName) => { const full = "c_" + shortName; if (fieldList[full]) return; // already exists const p = addMap[shortName]; p.name = full; fieldList[full] = p; addfieldList[shortName] = Object.assign({}, p, { name: shortName }); // add list uses short key; SQL adds c_+key // Fix: ContentCore uses CONTENT_HEAD+key from addFieldList keys, and param type from value. // The name inside add param doesn't matter for SQL; key is shortName. addfieldList[shortName] = { alias: p.alias, type: p.type, frontType: p.frontType, must: p.must, name: shortName, describe: p.describe, searchType: p.searchType, sortType: p.sortType, sequence: p.sequence, showParam: p.showParam, customType: p.customType || "", id: p.id, }; }); const payload = { modelId: String(modelId), fieldList: JSON.stringify(fieldList), addfieldList: JSON.stringify(addfieldList), delfieldList: JSON.stringify(delfieldList), updatefieldList: JSON.stringify(updatefieldList), }; const res = await post("/proxy_dms/model/updateFieldList", payload, token); return { modelId, modelName: model.modelName, code: res.code, message: res.message || res.content, added: Object.keys(addfieldList), deleted: Object.keys(delfieldList), renamed: Object.keys(updatefieldList), finalCount: Object.keys(fieldList).length, }; } async function addPaymentDetailColumn(token) { // field keys WITHOUT c_ prefix for createTable/processModel const fields = { batch_id: text("batch_id", "批次ID", true, 1), batch_number: text("batch_number", "批次号", true, 2), user_id: text("user_id", "人员ID", true, 3), name: text("name", "姓名", true, 4), id_number: text("id_number", "身份证号", true, 5), pay_month: text("pay_month", "发放年月", true, 6), town_id: text("town_id", "镇ID", true, 7), village_id: text("village_id", "村ID", true, 8), standard_amount: num("standard_amount", "标准金额", false, 9), one_time_amount: num("one_time_amount", "一次性代发金额", false, 10), supplementary_payment_amount: num("supplementary_payment_amount", "补发金额", false, 11), death_amount: num("death_amount", "丧葬金额", false, 12), deduct_amount: num("deduct_amount", "扣减金额", false, 13), total_amount: num("total_amount", "合计金额", true, 14), pay_bank: text("pay_bank", "发放银行", false, 15), bank_card_number: text("bank_card_number", "银行卡号", false, 16), standard_id: text("standard_id", "金额标准ID", false, 17), exported: text("exported", "是否已出盘", false, 18), remarks: text("remarks", "备注", false, 19), }; // strip c_ from name for processModel const fieldListNoPrefix = {}; Object.keys(fields).forEach((k) => { const p = Object.assign({}, fields[k]); p.name = k; fieldListNoPrefix[k] = p; }); const payload = { type: 1, parentId: String(PARENT_COLUMN_ID), tag: "sjnmtybt_ffmx", title: "发放明细", content: "批次发放明细表(人均一行,支撑出盘)", state: 0, level: 0, "model.modelName": "payment_detail", "model.modelAlias": "发放明细", "model.pageSize": 20, "model.type": 1, "model.fieldList": JSON.stringify(fieldListNoPrefix), "model.searchField": "[]", "model.sortField": "[]", }; const res = await post("/proxy_dms/column/addColumn", payload, token, "PUT"); return { code: res.code, message: res.message, content: res.content }; } (async () => { const login = await post("/proxy_oauth/user/login", { userName: "user_liu", password: "WE176852439@lmx", clientId: "1", }); if (login.code != 200) throw new Error("login fail " + JSON.stringify(login)); const token = login.message; console.log("login ok"); const results = []; // 1) 人员表 1970 results.push( await updateFieldList(token, 1970, { social_insurance_code: text("social_insurance_code", "个人社会保险登记码", false, 10), household_type: text("household_type", "户籍性质", false, 11), district_id: text("district_id", "区ID", false, 12), town_id: text("town_id", "镇ID", true, 13), village_id: text("village_id", "村ID", true, 14), village_code: text("village_code", "村委代码", false, 15), entrust_plan_code: text("entrust_plan_code", "委托方案代码", false, 16), entrust_plan_name: text("entrust_plan_name", "委托方案名称", false, 17), payee_name: text("payee_name", "收款人名称", false, 18), bank_name: text("bank_name", "开户行", false, 19), bank_type: text("bank_type", "行别", false, 20), bank_branch_code: text("bank_branch_code", "收款银行行号", false, 21), enjoy_start_month: text("enjoy_start_month", "享受起始年月", false, 22), enjoy_end_month: text("enjoy_end_month", "代发截止年月", false, 23), supplement_start_month: text("supplement_start_month", "补发起始年月", false, 24), next_pay_start_month: text("next_pay_start_month", "下次发放起始年月", false, 25), monthly_standard: num("monthly_standard", "当前月度补助标准", false, 26), base_amount: num("base_amount", "代发基数", false, 27), one_time_amount: num("one_time_amount", "一次性代发金额", false, 28), insurance_status: text("insurance_status", "居保养老状态", false, 29), insurance_match_result: text("insurance_match_result", "居保匹配结果", false, 30), bank_card_confirm_status: text("bank_card_confirm_status", "银行卡确认状态", false, 31), biz_status: text("biz_status", "人员业务状态", true, 32), pay_this_month: text("pay_this_month", "是否本月发放", false, 33), source: text("source", "录入来源", false, 34), input_village_id: text("input_village_id", "录入村居ID", false, 35), death_date: text("death_date", "死亡日期", false, 36), heir_name: text("heir_name", "继承人姓名", false, 37), heir_id_number: text("heir_id_number", "继承人身份证", false, 38), heir_bank_card: text("heir_bank_card", "继承人银行卡", false, 39), heir_phone: text("heir_phone", "继承人电话", false, 40), funeral_diff_amount: num("funeral_diff_amount", "丧葬费补差额", false, 41), land_subsidy_increase: num("land_subsidy_increase", "土地退养增资额", false, 42), }) ); console.log("personnel", results[results.length - 1]); // 2) 批次表 1968:补批次头字段,删人员级字段 results.push( await updateFieldList( token, 1968, { batch_level: text("batch_level", "批次层级", true, 20), pay_month: text("pay_month", "发放年月", true, 21), current_stage: text("current_stage", "当前流程阶段", true, 22), status: text("status", "批次状态", true, 23), people_count: intf("people_count", "应发人数", false, 24), normal_amount: num("normal_amount", "正常发放金额", false, 25), funeral_amount: num("funeral_amount", "丧葬费金额合计", false, 26), deduct_amount: num("deduct_amount", "扣减退款金额", false, 27), total_amount: num("total_amount", "合计金额", false, 28), export_disk_no: text("export_disk_no", "出盘编号", false, 29), export_file_name: text("export_file_name", "出盘文件名", false, 30), export_status: text("export_status", "出盘状态", false, 31), return_status: text("return_status", "回盘状态", false, 32), summary_no: text("summary_no", "汇总表编号", false, 33), pay_bank: text("pay_bank", "发放金融机构", false, 34), pay_date: text("pay_date", "应发日期", false, 35), return_date: text("return_date", "回盘日期", false, 36), submitter: text("submitter", "提交人", false, 37), town_opinion: text("town_opinion", "镇审批意见", false, 38), entrust_plan_code: text("entrust_plan_code", "委托方案代码", false, 39), }, ["c_user_id", "c_standard_id"], null ) ); console.log("batch", results[results.length - 1]); // 3) 流程表 1967 results.push( await updateFieldList(token, 1967, { batch_id: text("batch_id", "关联批次ID", true, 10), batch_no: text("batch_no", "统一批次号", false, 11), town_id: text("town_id", "镇ID", false, 12), village_id: text("village_id", "村ID", false, 13), stage: text("stage", "流程节点", true, 14), biz_type: text("biz_type", "业务类型", false, 15), approve_node: text("approve_node", "审批节点", false, 16), operator: text("operator", "操作人", true, 17), operator_role: text("operator_role", "操作角色", false, 18), operator_org: text("operator_org", "操作机构", false, 19), action: text("action", "操作动作", true, 20), opinion: text("opinion", "审批意见", false, 21), result: text("result", "处理结果", false, 22), personnel_id: text("personnel_id", "关联人员ID", false, 23), approval_id: text("approval_id", "关联审批单ID", false, 24), operate_time: ts("operate_time", "操作时间", false, 25), }) ); console.log("process", results[results.length - 1]); // 4) 告警表 1964 results.push( await updateFieldList(token, 1964, { batch_no: text("batch_no", "统一批次号", false, 10), batch_id: text("batch_id", "关联批次ID", false, 11), personnel_id: text("personnel_id", "关联人员ID", false, 12), personnel_name: text("personnel_name", "人员姓名", false, 13), town_id: text("town_id", "镇ID", false, 14), village_id: text("village_id", "村ID", false, 15), level: text("level", "告警等级", true, 16), type: text("type", "告警类型", true, 17), content: text("content", "告警内容", true, 18), fail_reason_code: text("fail_reason_code", "失败原因码", false, 19), handle_status: text("handle_status", "处理状态", true, 20), handle_result: text("handle_result", "处理结果", false, 21), handler: text("handler", "处理人", false, 22), handle_time: ts("handle_time", "处理时间", false, 23), }) ); console.log("alert", results[results.length - 1]); // 5) 批次统计 1969 results.push( await updateFieldList(token, 1969, { batch_id: text("batch_id", "关联批次ID", false, 10), summary_no: text("summary_no", "汇总表编号", false, 11), pay_month: text("pay_month", "发放年月", false, 12), pay_bank: text("pay_bank", "发放银行", false, 13), grain: text("grain", "统计粒度", false, 14), should_pay_count: intf("should_pay_count", "应支付人数", true, 15), should_pay_amount: num("should_pay_amount", "应支付总额", true, 16), actual_pay_count: intf("actual_pay_count", "实支付人数", true, 17), actual_pay_amount: num("actual_pay_amount", "实支付总额", true, 18), fail_count: intf("fail_count", "支付未成功人数", true, 19), fail_amount: num("fail_amount", "支付未成功总额", true, 20), return_date: text("return_date", "回盘日期", false, 21), handler: text("handler", "经办人", false, 22), reviewer: text("reviewer", "复核人", false, 23), }) ); console.log("stat", results[results.length - 1]); // 6) 金额标准 1966:删除错误字段 c_twon_id,新增 c_town_id + 类型/有效期字段 // (避免 DMS rename SQL 在 PostgreSQL 上不兼容) results.push( await updateFieldList( token, 1966, { town_id: text("town_id", "街镇ID", true, 9), standard_type: text("standard_type", "标准类型", true, 10), expire_time: ts("expire_time", "失效时间", false, 11), current_flag: text("current_flag", "是否当前有效", true, 12), adjust_reason: text("adjust_reason", "调整原因", false, 13), }, ["c_twon_id"], null ) ); console.log("amount", results[results.length - 1]); // 7) 新建发放明细栏目 const ffmx = await addPaymentDetailColumn(token); results.push({ action: "addColumn_ffmx", ...ffmx, contentId: ffmx.content && ffmx.content.id }); console.log("ffmx", JSON.stringify(ffmx).slice(0, 500)); // 验证最终字段 const verify = {}; for (const id of [1970, 1969, 1968, 1967, 1966, 1964]) { const m = await getModel(token, id); verify[id] = { modelName: m.modelName, fields: Object.keys(normalizeFieldList(m.fieldList)).sort(), }; } // 找 ffmx const cols = await post("/proxy_dms/column/getColumnList", {}, token); function flatten(nodes, out) { out = out || []; (nodes || []).forEach((n) => { out.push(n); if (n.columnList) flatten(n.columnList, out); }); return out; } const ff = flatten(cols.content || []).find((c) => c.tag === "sjnmtybt_ffmx"); if (ff && ff.modelId) { const m = await getModel(token, ff.modelId); verify.ffmx = { columnId: ff.id, modelId: ff.modelId, modelName: m.modelName, fields: Object.keys(normalizeFieldList(m.fieldList)).sort(), }; } else { verify.ffmx = { error: "not found", addResult: ffmx }; } const out = { results, verify }; const outPath = path.join(__dirname, "migrate-dms-models-result.json"); fs.writeFileSync(outPath, JSON.stringify(out, null, 2), "utf8"); console.log("DONE ->", outPath); console.log(JSON.stringify(verify, null, 2)); })().catch((e) => { console.error(e); process.exit(1); });