migrate-dms-models.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. /**
  2. * 松江退养补贴 - DMS 模型字段迁移
  3. * 网关: 121.43.55.7:2101
  4. */
  5. const http = require("http");
  6. const qs = require("querystring");
  7. const fs = require("fs");
  8. const path = require("path");
  9. const HOST = "121.43.55.7";
  10. const PORT = 2101;
  11. const PARENT_COLUMN_ID = 1840; // farmers_retirement_subsidies
  12. function post(urlPath, fields, token, method) {
  13. return new Promise((resolve, reject) => {
  14. const body = qs.stringify(fields || {});
  15. const headers = {
  16. "Content-Type": "application/x-www-form-urlencoded",
  17. "Content-Length": Buffer.byteLength(body),
  18. };
  19. if (token) {
  20. headers.Token = token;
  21. headers.token = token;
  22. }
  23. const req = http.request(
  24. { hostname: HOST, port: PORT, path: urlPath, method: method || "POST", headers },
  25. (res) => {
  26. let data = "";
  27. res.on("data", (c) => (data += c));
  28. res.on("end", () => {
  29. try {
  30. resolve(JSON.parse(data));
  31. } catch (e) {
  32. resolve({ raw: data, status: res.statusCode });
  33. }
  34. });
  35. }
  36. );
  37. req.on("error", reject);
  38. req.write(body);
  39. req.end();
  40. });
  41. }
  42. function text(shortName, alias, must, seq) {
  43. return {
  44. alias,
  45. customType: "",
  46. describe: "格式为富文本",
  47. frontType: "content",
  48. id: 2,
  49. must: !!must,
  50. name: "c_" + shortName,
  51. searchType: "2",
  52. sequence: seq || 1,
  53. showParam: "name,alias,desc,type,front_type,must,default_value",
  54. type: "text",
  55. };
  56. }
  57. function num(shortName, alias, must, seq) {
  58. return {
  59. alias,
  60. customType: "",
  61. describe: "number",
  62. frontType: "float_num",
  63. id: 6,
  64. must: !!must,
  65. name: "c_" + shortName,
  66. searchType: "1,3",
  67. sequence: seq || 5,
  68. showParam: "name,alias,desc,type,front_type,must,default_value",
  69. sortType: "1,2",
  70. type: "double",
  71. };
  72. }
  73. function ts(shortName, alias, must, seq) {
  74. return {
  75. alias,
  76. customType: "",
  77. describe: "时间戳",
  78. frontType: "date_time",
  79. id: 3,
  80. must: !!must,
  81. name: "c_" + shortName,
  82. searchType: "3",
  83. sequence: seq || 2,
  84. showParam: "name,alias,describe,type,front_type,must,date_type,date_picker",
  85. sortType: "1,2",
  86. type: "timestamp",
  87. };
  88. }
  89. function intf(shortName, alias, must, seq) {
  90. return {
  91. alias,
  92. customType: "",
  93. describe: "number",
  94. frontType: "int_num",
  95. id: 5,
  96. must: !!must,
  97. name: "c_" + shortName,
  98. searchType: "1,3",
  99. sequence: seq || 4,
  100. showParam: "name,alias,desc,type,front_type,must,default_value",
  101. sortType: "1,2",
  102. type: "integer",
  103. };
  104. }
  105. function normalizeFieldList(raw) {
  106. let fl = raw;
  107. if (typeof fl === "string") fl = JSON.parse(fl);
  108. const out = {};
  109. Object.keys(fl || {}).forEach((k) => {
  110. let v = fl[k];
  111. if (typeof v === "string") v = JSON.parse(v);
  112. out[k] = v;
  113. });
  114. return out;
  115. }
  116. function shortOf(cName) {
  117. return cName.startsWith("c_") ? cName.slice(2) : cName;
  118. }
  119. async function getModel(token, modelId) {
  120. const res = await post("/proxy_dms/model/getModelById", { modelId: String(modelId) }, token);
  121. if (res.code != 200) throw new Error("getModel " + modelId + " fail: " + JSON.stringify(res));
  122. return res.content;
  123. }
  124. /**
  125. * 通过 updateFieldList 增删改字段
  126. * addMap: { shortName: paramObjWithCName }
  127. * delKeys: ['c_xxx']
  128. * renameMap: { c_old: { newName: 'c_new', alias?: '' } }
  129. */
  130. async function updateFieldList(token, modelId, addMap, delKeys, renameMap) {
  131. const model = await getModel(token, modelId);
  132. const fieldList = normalizeFieldList(model.fieldList);
  133. const addfieldList = {};
  134. const delfieldList = {};
  135. const updatefieldList = {};
  136. // deletes
  137. (delKeys || []).forEach((k) => {
  138. if (fieldList[k]) {
  139. delfieldList[k] = fieldList[k];
  140. delete fieldList[k];
  141. }
  142. });
  143. // renames
  144. Object.keys(renameMap || {}).forEach((oldKey) => {
  145. const conf = renameMap[oldKey];
  146. if (!fieldList[oldKey]) return;
  147. const old = fieldList[oldKey];
  148. const neu = Object.assign({}, old, {
  149. name: conf.newName,
  150. alias: conf.alias || old.alias,
  151. updateName: conf.newName,
  152. });
  153. updatefieldList[oldKey] = Object.assign({}, old, {
  154. updateName: conf.newName,
  155. type: old.type || "text",
  156. });
  157. delete fieldList[oldKey];
  158. fieldList[conf.newName] = neu;
  159. });
  160. // adds
  161. Object.keys(addMap || {}).forEach((shortName) => {
  162. const full = "c_" + shortName;
  163. if (fieldList[full]) return; // already exists
  164. const p = addMap[shortName];
  165. p.name = full;
  166. fieldList[full] = p;
  167. addfieldList[shortName] = Object.assign({}, p, { name: shortName }); // add list uses short key; SQL adds c_+key
  168. // Fix: ContentCore uses CONTENT_HEAD+key from addFieldList keys, and param type from value.
  169. // The name inside add param doesn't matter for SQL; key is shortName.
  170. addfieldList[shortName] = {
  171. alias: p.alias,
  172. type: p.type,
  173. frontType: p.frontType,
  174. must: p.must,
  175. name: shortName,
  176. describe: p.describe,
  177. searchType: p.searchType,
  178. sortType: p.sortType,
  179. sequence: p.sequence,
  180. showParam: p.showParam,
  181. customType: p.customType || "",
  182. id: p.id,
  183. };
  184. });
  185. const payload = {
  186. modelId: String(modelId),
  187. fieldList: JSON.stringify(fieldList),
  188. addfieldList: JSON.stringify(addfieldList),
  189. delfieldList: JSON.stringify(delfieldList),
  190. updatefieldList: JSON.stringify(updatefieldList),
  191. };
  192. const res = await post("/proxy_dms/model/updateFieldList", payload, token);
  193. return {
  194. modelId,
  195. modelName: model.modelName,
  196. code: res.code,
  197. message: res.message || res.content,
  198. added: Object.keys(addfieldList),
  199. deleted: Object.keys(delfieldList),
  200. renamed: Object.keys(updatefieldList),
  201. finalCount: Object.keys(fieldList).length,
  202. };
  203. }
  204. async function addPaymentDetailColumn(token) {
  205. // field keys WITHOUT c_ prefix for createTable/processModel
  206. const fields = {
  207. batch_id: text("batch_id", "批次ID", true, 1),
  208. batch_number: text("batch_number", "批次号", true, 2),
  209. user_id: text("user_id", "人员ID", true, 3),
  210. name: text("name", "姓名", true, 4),
  211. id_number: text("id_number", "身份证号", true, 5),
  212. pay_month: text("pay_month", "发放年月", true, 6),
  213. town_id: text("town_id", "镇ID", true, 7),
  214. village_id: text("village_id", "村ID", true, 8),
  215. standard_amount: num("standard_amount", "标准金额", false, 9),
  216. one_time_amount: num("one_time_amount", "一次性代发金额", false, 10),
  217. supplementary_payment_amount: num("supplementary_payment_amount", "补发金额", false, 11),
  218. death_amount: num("death_amount", "丧葬金额", false, 12),
  219. deduct_amount: num("deduct_amount", "扣减金额", false, 13),
  220. total_amount: num("total_amount", "合计金额", true, 14),
  221. pay_bank: text("pay_bank", "发放银行", false, 15),
  222. bank_card_number: text("bank_card_number", "银行卡号", false, 16),
  223. standard_id: text("standard_id", "金额标准ID", false, 17),
  224. exported: text("exported", "是否已出盘", false, 18),
  225. remarks: text("remarks", "备注", false, 19),
  226. };
  227. // strip c_ from name for processModel
  228. const fieldListNoPrefix = {};
  229. Object.keys(fields).forEach((k) => {
  230. const p = Object.assign({}, fields[k]);
  231. p.name = k;
  232. fieldListNoPrefix[k] = p;
  233. });
  234. const payload = {
  235. type: 1,
  236. parentId: String(PARENT_COLUMN_ID),
  237. tag: "sjnmtybt_ffmx",
  238. title: "发放明细",
  239. content: "批次发放明细表(人均一行,支撑出盘)",
  240. state: 0,
  241. level: 0,
  242. "model.modelName": "payment_detail",
  243. "model.modelAlias": "发放明细",
  244. "model.pageSize": 20,
  245. "model.type": 1,
  246. "model.fieldList": JSON.stringify(fieldListNoPrefix),
  247. "model.searchField": "[]",
  248. "model.sortField": "[]",
  249. };
  250. const res = await post("/proxy_dms/column/addColumn", payload, token, "PUT");
  251. return { code: res.code, message: res.message, content: res.content };
  252. }
  253. (async () => {
  254. const login = await post("/proxy_oauth/user/login", {
  255. userName: "user_liu",
  256. password: "WE176852439@lmx",
  257. clientId: "1",
  258. });
  259. if (login.code != 200) throw new Error("login fail " + JSON.stringify(login));
  260. const token = login.message;
  261. console.log("login ok");
  262. const results = [];
  263. // 1) 人员表 1970
  264. results.push(
  265. await updateFieldList(token, 1970, {
  266. social_insurance_code: text("social_insurance_code", "个人社会保险登记码", false, 10),
  267. household_type: text("household_type", "户籍性质", false, 11),
  268. district_id: text("district_id", "区ID", false, 12),
  269. town_id: text("town_id", "镇ID", true, 13),
  270. village_id: text("village_id", "村ID", true, 14),
  271. village_code: text("village_code", "村委代码", false, 15),
  272. entrust_plan_code: text("entrust_plan_code", "委托方案代码", false, 16),
  273. entrust_plan_name: text("entrust_plan_name", "委托方案名称", false, 17),
  274. payee_name: text("payee_name", "收款人名称", false, 18),
  275. bank_name: text("bank_name", "开户行", false, 19),
  276. bank_type: text("bank_type", "行别", false, 20),
  277. bank_branch_code: text("bank_branch_code", "收款银行行号", false, 21),
  278. enjoy_start_month: text("enjoy_start_month", "享受起始年月", false, 22),
  279. enjoy_end_month: text("enjoy_end_month", "代发截止年月", false, 23),
  280. supplement_start_month: text("supplement_start_month", "补发起始年月", false, 24),
  281. next_pay_start_month: text("next_pay_start_month", "下次发放起始年月", false, 25),
  282. monthly_standard: num("monthly_standard", "当前月度补助标准", false, 26),
  283. base_amount: num("base_amount", "代发基数", false, 27),
  284. one_time_amount: num("one_time_amount", "一次性代发金额", false, 28),
  285. insurance_status: text("insurance_status", "居保养老状态", false, 29),
  286. insurance_match_result: text("insurance_match_result", "居保匹配结果", false, 30),
  287. bank_card_confirm_status: text("bank_card_confirm_status", "银行卡确认状态", false, 31),
  288. biz_status: text("biz_status", "人员业务状态", true, 32),
  289. pay_this_month: text("pay_this_month", "是否本月发放", false, 33),
  290. source: text("source", "录入来源", false, 34),
  291. input_village_id: text("input_village_id", "录入村居ID", false, 35),
  292. death_date: text("death_date", "死亡日期", false, 36),
  293. heir_name: text("heir_name", "继承人姓名", false, 37),
  294. heir_id_number: text("heir_id_number", "继承人身份证", false, 38),
  295. heir_bank_card: text("heir_bank_card", "继承人银行卡", false, 39),
  296. heir_phone: text("heir_phone", "继承人电话", false, 40),
  297. funeral_diff_amount: num("funeral_diff_amount", "丧葬费补差额", false, 41),
  298. land_subsidy_increase: num("land_subsidy_increase", "土地退养增资额", false, 42),
  299. })
  300. );
  301. console.log("personnel", results[results.length - 1]);
  302. // 2) 批次表 1968:补批次头字段,删人员级字段
  303. results.push(
  304. await updateFieldList(
  305. token,
  306. 1968,
  307. {
  308. batch_level: text("batch_level", "批次层级", true, 20),
  309. pay_month: text("pay_month", "发放年月", true, 21),
  310. current_stage: text("current_stage", "当前流程阶段", true, 22),
  311. status: text("status", "批次状态", true, 23),
  312. people_count: intf("people_count", "应发人数", false, 24),
  313. normal_amount: num("normal_amount", "正常发放金额", false, 25),
  314. funeral_amount: num("funeral_amount", "丧葬费金额合计", false, 26),
  315. deduct_amount: num("deduct_amount", "扣减退款金额", false, 27),
  316. total_amount: num("total_amount", "合计金额", false, 28),
  317. export_disk_no: text("export_disk_no", "出盘编号", false, 29),
  318. export_file_name: text("export_file_name", "出盘文件名", false, 30),
  319. export_status: text("export_status", "出盘状态", false, 31),
  320. return_status: text("return_status", "回盘状态", false, 32),
  321. summary_no: text("summary_no", "汇总表编号", false, 33),
  322. pay_bank: text("pay_bank", "发放金融机构", false, 34),
  323. pay_date: text("pay_date", "应发日期", false, 35),
  324. return_date: text("return_date", "回盘日期", false, 36),
  325. submitter: text("submitter", "提交人", false, 37),
  326. town_opinion: text("town_opinion", "镇审批意见", false, 38),
  327. entrust_plan_code: text("entrust_plan_code", "委托方案代码", false, 39),
  328. },
  329. ["c_user_id", "c_standard_id"],
  330. null
  331. )
  332. );
  333. console.log("batch", results[results.length - 1]);
  334. // 3) 流程表 1967
  335. results.push(
  336. await updateFieldList(token, 1967, {
  337. batch_id: text("batch_id", "关联批次ID", true, 10),
  338. batch_no: text("batch_no", "统一批次号", false, 11),
  339. town_id: text("town_id", "镇ID", false, 12),
  340. village_id: text("village_id", "村ID", false, 13),
  341. stage: text("stage", "流程节点", true, 14),
  342. biz_type: text("biz_type", "业务类型", false, 15),
  343. approve_node: text("approve_node", "审批节点", false, 16),
  344. operator: text("operator", "操作人", true, 17),
  345. operator_role: text("operator_role", "操作角色", false, 18),
  346. operator_org: text("operator_org", "操作机构", false, 19),
  347. action: text("action", "操作动作", true, 20),
  348. opinion: text("opinion", "审批意见", false, 21),
  349. result: text("result", "处理结果", false, 22),
  350. personnel_id: text("personnel_id", "关联人员ID", false, 23),
  351. approval_id: text("approval_id", "关联审批单ID", false, 24),
  352. operate_time: ts("operate_time", "操作时间", false, 25),
  353. })
  354. );
  355. console.log("process", results[results.length - 1]);
  356. // 4) 告警表 1964
  357. results.push(
  358. await updateFieldList(token, 1964, {
  359. batch_no: text("batch_no", "统一批次号", false, 10),
  360. batch_id: text("batch_id", "关联批次ID", false, 11),
  361. personnel_id: text("personnel_id", "关联人员ID", false, 12),
  362. personnel_name: text("personnel_name", "人员姓名", false, 13),
  363. town_id: text("town_id", "镇ID", false, 14),
  364. village_id: text("village_id", "村ID", false, 15),
  365. level: text("level", "告警等级", true, 16),
  366. type: text("type", "告警类型", true, 17),
  367. content: text("content", "告警内容", true, 18),
  368. fail_reason_code: text("fail_reason_code", "失败原因码", false, 19),
  369. handle_status: text("handle_status", "处理状态", true, 20),
  370. handle_result: text("handle_result", "处理结果", false, 21),
  371. handler: text("handler", "处理人", false, 22),
  372. handle_time: ts("handle_time", "处理时间", false, 23),
  373. })
  374. );
  375. console.log("alert", results[results.length - 1]);
  376. // 5) 批次统计 1969
  377. results.push(
  378. await updateFieldList(token, 1969, {
  379. batch_id: text("batch_id", "关联批次ID", false, 10),
  380. summary_no: text("summary_no", "汇总表编号", false, 11),
  381. pay_month: text("pay_month", "发放年月", false, 12),
  382. pay_bank: text("pay_bank", "发放银行", false, 13),
  383. grain: text("grain", "统计粒度", false, 14),
  384. should_pay_count: intf("should_pay_count", "应支付人数", true, 15),
  385. should_pay_amount: num("should_pay_amount", "应支付总额", true, 16),
  386. actual_pay_count: intf("actual_pay_count", "实支付人数", true, 17),
  387. actual_pay_amount: num("actual_pay_amount", "实支付总额", true, 18),
  388. fail_count: intf("fail_count", "支付未成功人数", true, 19),
  389. fail_amount: num("fail_amount", "支付未成功总额", true, 20),
  390. return_date: text("return_date", "回盘日期", false, 21),
  391. handler: text("handler", "经办人", false, 22),
  392. reviewer: text("reviewer", "复核人", false, 23),
  393. })
  394. );
  395. console.log("stat", results[results.length - 1]);
  396. // 6) 金额标准 1966:删除错误字段 c_twon_id,新增 c_town_id + 类型/有效期字段
  397. // (避免 DMS rename SQL 在 PostgreSQL 上不兼容)
  398. results.push(
  399. await updateFieldList(
  400. token,
  401. 1966,
  402. {
  403. town_id: text("town_id", "街镇ID", true, 9),
  404. standard_type: text("standard_type", "标准类型", true, 10),
  405. expire_time: ts("expire_time", "失效时间", false, 11),
  406. current_flag: text("current_flag", "是否当前有效", true, 12),
  407. adjust_reason: text("adjust_reason", "调整原因", false, 13),
  408. },
  409. ["c_twon_id"],
  410. null
  411. )
  412. );
  413. console.log("amount", results[results.length - 1]);
  414. // 7) 新建发放明细栏目
  415. const ffmx = await addPaymentDetailColumn(token);
  416. results.push({ action: "addColumn_ffmx", ...ffmx, contentId: ffmx.content && ffmx.content.id });
  417. console.log("ffmx", JSON.stringify(ffmx).slice(0, 500));
  418. // 验证最终字段
  419. const verify = {};
  420. for (const id of [1970, 1969, 1968, 1967, 1966, 1964]) {
  421. const m = await getModel(token, id);
  422. verify[id] = {
  423. modelName: m.modelName,
  424. fields: Object.keys(normalizeFieldList(m.fieldList)).sort(),
  425. };
  426. }
  427. // 找 ffmx
  428. const cols = await post("/proxy_dms/column/getColumnList", {}, token);
  429. function flatten(nodes, out) {
  430. out = out || [];
  431. (nodes || []).forEach((n) => {
  432. out.push(n);
  433. if (n.columnList) flatten(n.columnList, out);
  434. });
  435. return out;
  436. }
  437. const ff = flatten(cols.content || []).find((c) => c.tag === "sjnmtybt_ffmx");
  438. if (ff && ff.modelId) {
  439. const m = await getModel(token, ff.modelId);
  440. verify.ffmx = {
  441. columnId: ff.id,
  442. modelId: ff.modelId,
  443. modelName: m.modelName,
  444. fields: Object.keys(normalizeFieldList(m.fieldList)).sort(),
  445. };
  446. } else {
  447. verify.ffmx = { error: "not found", addResult: ffmx };
  448. }
  449. const out = { results, verify };
  450. const outPath = path.join(__dirname, "migrate-dms-models-result.json");
  451. fs.writeFileSync(outPath, JSON.stringify(out, null, 2), "utf8");
  452. console.log("DONE ->", outPath);
  453. console.log(JSON.stringify(verify, null, 2));
  454. })().catch((e) => {
  455. console.error(e);
  456. process.exit(1);
  457. });