cleanup-invalid-paymonth.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. /**
  2. * 清理发放年月脏数据(如 249241 → 界面显示 2492-41,或 202699 非法月份)。
  3. *
  4. * 判定:c_pay_month / payMonth 不是 2020–2030 年合法 yyyyMM。
  5. * 处理:调用线上 POST /api/batches/{id}/purge(明细软摘离 + 批次标记 DELETED)。
  6. *
  7. * 用法:node scripts/cleanup-invalid-paymonth.js
  8. * 可选:BASE=http://localhost:8088/sjnmtybt node scripts/cleanup-invalid-paymonth.js
  9. */
  10. const http = require("http");
  11. const qs = require("querystring");
  12. const BASE = process.env.BASE || "http://121.43.55.7:11091/sjnmtybt";
  13. const DMS_HOST = "121.43.55.7";
  14. const DMS_PORT = 2101;
  15. const STAT_COL = { columnId: 1846, modelId: 1969 };
  16. function isValidPayMonth(v) {
  17. const s = String(v || "").replace(/-/g, "");
  18. if (!/^[0-9]{6}$/.test(s)) return false;
  19. const y = +s.slice(0, 4);
  20. const m = +s.slice(4, 6);
  21. return y >= 2020 && y <= 2030 && m >= 1 && m <= 12;
  22. }
  23. function api(method, path, body) {
  24. return new Promise((resolve, reject) => {
  25. const u = new URL(BASE + path);
  26. const data = body == null ? null : JSON.stringify(body);
  27. const headers = { Accept: "application/json" };
  28. if (data) {
  29. headers["Content-Type"] = "application/json";
  30. headers["Content-Length"] = Buffer.byteLength(data);
  31. }
  32. const req = http.request(
  33. {
  34. hostname: u.hostname,
  35. port: u.port,
  36. path: u.pathname + u.search,
  37. method,
  38. headers,
  39. },
  40. (res) => {
  41. let raw = "";
  42. res.on("data", (c) => (raw += c));
  43. res.on("end", () => {
  44. try {
  45. resolve(JSON.parse(raw));
  46. } catch (e) {
  47. resolve({ code: res.statusCode, raw: String(raw).slice(0, 300) });
  48. }
  49. });
  50. }
  51. );
  52. req.on("error", reject);
  53. if (data) req.write(data);
  54. req.end();
  55. });
  56. }
  57. function dms(path, fields, token) {
  58. return new Promise((resolve, reject) => {
  59. const body = qs.stringify(fields || {});
  60. const headers = {
  61. "Content-Type": "application/x-www-form-urlencoded",
  62. "Content-Length": Buffer.byteLength(body),
  63. };
  64. if (token) {
  65. headers.Token = token;
  66. headers.token = token;
  67. }
  68. const req = http.request(
  69. { hostname: DMS_HOST, port: DMS_PORT, path, method: "POST", headers },
  70. (res) => {
  71. let raw = "";
  72. res.on("data", (c) => (raw += c));
  73. res.on("end", () => {
  74. try {
  75. resolve(JSON.parse(raw));
  76. } catch (e) {
  77. resolve({ raw: String(raw).slice(0, 200) });
  78. }
  79. });
  80. }
  81. );
  82. req.on("error", reject);
  83. req.write(body);
  84. req.end();
  85. });
  86. }
  87. async function login() {
  88. const res = await dms("/proxy_oauth/user/login", {
  89. userName: "user_liu",
  90. password: "WE176852439@lmx",
  91. clientId: "1",
  92. });
  93. if (res.code != 200) throw new Error("login fail: " + JSON.stringify(res));
  94. return res.message;
  95. }
  96. async function listBatches() {
  97. const all = [];
  98. for (let pageNum = 1; pageNum <= 20; pageNum++) {
  99. const res = await api("POST", "/api/batches/page", {
  100. pageNum,
  101. pageSize: 100,
  102. });
  103. const list = (res.data && res.data.list) || [];
  104. all.push.apply(all, list);
  105. if (list.length < 100) break;
  106. }
  107. return all;
  108. }
  109. async function neutralizeStat(token, row) {
  110. return dms(
  111. "/proxy_dms/content/updateContent",
  112. {
  113. columnId: String(STAT_COL.columnId),
  114. modelId: String(STAT_COL.modelId),
  115. content: JSON.stringify({
  116. id: row.id,
  117. state: -1,
  118. c_batch_id: "REMOVED|" + row.id,
  119. c_pay_month: "000000",
  120. c_remarks: "INVALID_PAYMONTH_CLEANED|" + Date.now(),
  121. }),
  122. },
  123. token
  124. );
  125. }
  126. async function cleanStats(token, dirtyBatchIds) {
  127. const idSet = new Set(dirtyBatchIds);
  128. let cleaned = 0;
  129. for (let page = 0; page < 40; page++) {
  130. const res = await dms(
  131. "/proxy_dms/content/selectContentList",
  132. {
  133. columnId: String(STAT_COL.columnId),
  134. modelId: String(STAT_COL.modelId),
  135. page: String(page),
  136. pageSize: "100",
  137. states: "0,1,2,3",
  138. },
  139. token
  140. );
  141. const data = (res.content && res.content.data) || [];
  142. for (const row of data) {
  143. const bid = String(row.c_batch_id || "");
  144. const pm = row.c_pay_month;
  145. const hit = idSet.has(bid) || !isValidPayMonth(pm);
  146. if (!hit) continue;
  147. const r = await neutralizeStat(token, row);
  148. if (r && r.code == 200) {
  149. cleaned++;
  150. console.log("stat OK", row.id, "batch=" + bid, "pm=" + pm);
  151. } else {
  152. console.log("stat FAIL", row.id, JSON.stringify(r).slice(0, 160));
  153. }
  154. }
  155. if (data.length < 100) break;
  156. }
  157. return cleaned;
  158. }
  159. (async () => {
  160. console.log("BASE=" + BASE);
  161. const batches = await listBatches();
  162. const dirty = batches.filter((b) => !isValidPayMonth(b.payMonth));
  163. const months = [...new Set(dirty.map((b) => b.payMonth))].sort();
  164. console.log(
  165. "列表批次=" + batches.length + " 脏批次=" + dirty.length + " 非法月份=" + months.join(",")
  166. );
  167. let ok = 0;
  168. let fail = 0;
  169. const purgedIds = [];
  170. for (const b of dirty) {
  171. const id = b.id || b.batchNo;
  172. const res = await api("POST", "/api/batches/" + encodeURIComponent(id) + "/purge", null);
  173. if (res && res.code == 200 && res.data && res.data.ok !== false) {
  174. ok++;
  175. purgedIds.push(id);
  176. console.log(
  177. "purge OK",
  178. id,
  179. "pm=" + b.payMonth,
  180. "detached=" + (res.data.detachedPayments || 0)
  181. );
  182. } else {
  183. fail++;
  184. console.log("purge FAIL", id, "pm=" + b.payMonth, JSON.stringify(res).slice(0, 220));
  185. }
  186. }
  187. const token = await login();
  188. const stats = await cleanStats(token, purgedIds);
  189. const after = await listBatches();
  190. const stillDirty = after.filter((b) => !isValidPayMonth(b.payMonth));
  191. console.log(
  192. "完成 purgeOk=" +
  193. ok +
  194. " purgeFail=" +
  195. fail +
  196. " statsCleaned=" +
  197. stats +
  198. " 剩余列表=" +
  199. after.length +
  200. " 仍脏=" +
  201. stillDirty.length
  202. );
  203. if (stillDirty.length) {
  204. console.log(
  205. "仍脏样例",
  206. stillDirty.slice(0, 10).map((b) => ({ id: b.id, pm: b.payMonth, no: b.batchNo }))
  207. );
  208. }
  209. })().catch((e) => {
  210. console.error(e);
  211. process.exit(1);
  212. });