match.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. 'use strict';
  2. /**
  3. *
  4. *
  5. * @author Mai
  6. * @date 2018/4/25
  7. * @version
  8. */
  9. const Service = require('egg').Service;
  10. module.exports = app => {
  11. const paramConst = app.paramConst;
  12. const nodeConst = app.nodeConst;
  13. class Match extends Service {
  14. /**
  15. * 初始化匹配所需变量
  16. * @param {Array|Object} bills - 导入的清单数据
  17. * @private
  18. */
  19. _init(bills) {
  20. this.bills = bills instanceof Array ? bills : [bills];
  21. this.bills[0].match_node = null;
  22. this.fixedBills = this.bills.filter(function (b) {
  23. return b.n_id < 100;
  24. });
  25. this.nodes = [];
  26. this.indexes = [];
  27. this.params = [];
  28. }
  29. /**
  30. * 获取指标模板数据
  31. * @param {Number} templateId - 指标模板Id
  32. * @returns {Promise<void>}
  33. * @private
  34. */
  35. async _getTemplateData (templateId) {
  36. this.templateNodes = await this.ctx.service.templateNode.getAllDataByCondition({ where: { template_id: templateId } });
  37. this.templateIndexes = await this.ctx.service.templateIndex.getAllDataByCondition({ where: { template_id: templateId } });
  38. this.templateParams = await this.ctx.service.templateParam.getAllDataByCondition({ where: { template_id: templateId } });
  39. }
  40. /**
  41. * 检查code是否满足匹配规则rule
  42. * @param {String} code - 匹配编号
  43. * @param {String} rule - 匹配规则
  44. * @returns {boolean}
  45. * @private
  46. */
  47. _matchCodeByRule(code, rule) {
  48. const codeParts = code.split('-');
  49. const ruleParts = rule.split('-');
  50. const numReg = /(^[0-9]+$)/, charReg = /(^[a-z]+$)/i;
  51. if (codeParts.length !== ruleParts.length) { return false; }
  52. for (let i = 0, iLen = codeParts.length; i < iLen; i++) {
  53. if (numReg.test(ruleParts[i])) {
  54. if (ruleParts[i] !== codeParts[i]) {
  55. return false;
  56. }
  57. } else if (!charReg.test(ruleParts[i])) {
  58. return false;
  59. }
  60. }
  61. return true;
  62. }
  63. _matchCodeByRules(code, rules, bills) {
  64. const ruleArr = rules.split(';');
  65. for (const r of ruleArr) {
  66. if (this._matchCodeByRule(code, r)) {
  67. bills.match_key = r;
  68. return true;
  69. }
  70. }
  71. return false;
  72. }
  73. /**
  74. * 过滤符合指标节点node匹配规则的清单
  75. * 已经匹配过的清单不再匹配
  76. *
  77. * @param {Object} node - 指标节点
  78. * @private
  79. */
  80. _filterBills (node) {
  81. const self = this;
  82. return this.bills.filter(function (b) {
  83. if (!b.match_node) {
  84. if (node.match_type === nodeConst.matchType.code) {
  85. return self._matchCodeByRules(b.code, node.match_key, b);
  86. } else {
  87. if (node.match_key === b.name) {
  88. b.match_key = b.name;
  89. return true;
  90. } else {
  91. return false;
  92. }
  93. }
  94. } else {
  95. return false;
  96. }
  97. })
  98. }
  99. /**
  100. * 根据指标参数取值类别,从清单取值
  101. *
  102. * @param {Object} param - 指标参数
  103. * @param {Object} bills - 清单节点
  104. * @returns {number}
  105. * @private
  106. */
  107. _getNodeBillsValue(param, bills) {
  108. if (bills) {
  109. switch (param.match_num) {
  110. case paramConst.matchNum.quantity: return bills.quantity;
  111. case paramConst.matchNum.total_price: return bills.total_price;
  112. case paramConst.matchNum.dgn_quantity1: return bills.dgn_quantity1;
  113. case paramConst.matchNum.dgn_quantity2: return bills.dgn_quantity2;
  114. default: return undefined;
  115. }
  116. } else {
  117. return undefined;
  118. }
  119. }
  120. _getChildrenBillsValue(param, bills) {
  121. if (bills) {
  122. const children = this.ctx.helper.filterObj(this.bills, 'n_pid', bills.n_id);
  123. switch(param.match_num) {
  124. case paramConst.matchNum.quantity: return this.ctx.helper.sumField(children, 'quantity');
  125. case paramConst.matchNum.total_price: return this.ctx.helper.sumField(children, 'total_price');
  126. case paramConst.matchNum.dgn_quantity1: return this.ctx.helper.sumField(children, 'dgn_quantity1');
  127. case paramConst.matchNum.dgn_quantity2: return this.ctx.helper.sumField(children, 'dgn_quantity2');
  128. }
  129. } else {
  130. return undefined;
  131. }
  132. }
  133. /**
  134. * 获取指标参数取值(固定Id匹配)
  135. * @param {Object} param - 指标参数
  136. * @returns {*}
  137. * @private
  138. */
  139. _getFixedIdParamValue(param) {
  140. for (const b of this.fixedBills) {
  141. if (b.n_id == param.match_key) {
  142. return this._getNodeBillsValue(param, b);
  143. }
  144. }
  145. return undefined;
  146. }
  147. /**
  148. * 获取指标参数取值(编号匹配)
  149. * @param param
  150. * @param nodeBills
  151. * @returns {*}
  152. * @private
  153. */
  154. _getCodeParamValue(param) {
  155. for (const b of this.bills) {
  156. if (this._matchCodeByRules(b.code, param.match_key, b)) {
  157. return this._getNodeBillsValue(param, b);
  158. }
  159. }
  160. return undefined;
  161. }
  162. /**
  163. * 获取父项
  164. * @param {Object} node - 分项清单节点
  165. * @private
  166. */
  167. _getParentBills(node) {
  168. return node ? this.ctx.helper.findObj(this.bills, 'n_id', node.n_pid) : null;
  169. }
  170. /**
  171. * 获取指标参数取值
  172. * @param {Object} param - 指标参数
  173. * @param {Object} nodeBills - 指标参数 所属 指标节点,绑定的 清单
  174. * @returns {*}
  175. * @private
  176. */
  177. _getParamValue(param, nodeBills) {
  178. if (!isNaN(Number(param.name))) {
  179. return Number(param.name);
  180. } else {
  181. switch (param.match_type) {
  182. case paramConst.matchType.fixed_id: return this._getFixedIdParamValue(param);
  183. case paramConst.matchType.node_default: return this._getNodeBillsValue(param, nodeBills);
  184. case paramConst.matchType.parent_default: return this._getNodeBillsValue(param, this._getParentBills(nodeBills));
  185. case paramConst.matchType.child_gather: return this._getChildrenBillsValue(param, nodeBills);
  186. case paramConst.matchType.code: return this._getCodeParamValue(param);
  187. // to do 匹配属性
  188. default: return undefined;
  189. }
  190. }
  191. }
  192. /**
  193. * 同步指标节点下的全部指标参数
  194. * @param {Number} nodeId
  195. * @private
  196. */
  197. _syncNodeParam(sourceId, nodeId, nodeBills) {
  198. const nodeParams = this.templateParams.filter(function (p) {
  199. return p.node_id === sourceId;
  200. });
  201. for (const np of nodeParams) {
  202. const newParam = {
  203. node_id: nodeId,
  204. lib_id: nodeBills.lib_id,
  205. param_id: this.params.length + 1,
  206. source_id: np.param_id,
  207. code: np.code,
  208. name: np.name,
  209. match_type: np.match_type,
  210. match_key: np.match_key,
  211. match_num: np.match_num,
  212. };
  213. newParam.match_value = this._getParamValue(newParam, nodeBills);
  214. newParam.calc_value = newParam.match_value;
  215. this.params.push(newParam);
  216. }
  217. }
  218. /**
  219. * 同步指标节点下的全部指标
  220. * @param {Number} nodeId
  221. * @private
  222. */
  223. _syncNodeIndex(node) {
  224. const nodeIndexes = this.templateIndexes.filter(function (i) {
  225. return i.node_id === node.source_id;
  226. });
  227. for (const ni of nodeIndexes) {
  228. const newIndex = {
  229. node_id: node.node_id,
  230. lib_id: node.lib_id,
  231. index_id: this.indexes.length + 1,
  232. source_id: ni.index_id,
  233. code: ni.code,
  234. name: ni.name,
  235. unit1: ni.unit1,
  236. unit2: ni.unit2,
  237. rule: ni.rule,
  238. calc_rule: ni.calc_rule,
  239. parse_rule: ni.parse_rule,
  240. index_type: ni.index_type,
  241. }
  242. this.indexes.push(newIndex);
  243. }
  244. }
  245. _getXMatchId(bills) {
  246. //const reg = /-([a-z])-/i;
  247. const reg = /-([a-z])/i;
  248. if (reg.test(bills.match_key)) {
  249. const ruleParts = bills.match_key.split('-');
  250. const charReg = /(^[a-z]+$)/i;
  251. let node = bills, parent = bills;
  252. for (let i = ruleParts.length - 1; i>= 0; i--) {
  253. if (parent) {
  254. if (charReg.test(ruleParts[i])) {
  255. return parent.n_id;
  256. }
  257. node = parent;
  258. parent = this.bills.find(function(x) {
  259. return x.n_id === node.n_pid;
  260. });
  261. } else {
  262. return -1;
  263. }
  264. }
  265. } else {
  266. return -1;
  267. }
  268. }
  269. /**
  270. * 匹配指标节点
  271. * @param node
  272. * @private
  273. */
  274. _matchNode(node) {
  275. if (!node) { return; }
  276. const matchedBills = this._filterBills(node);
  277. for (const mb of matchedBills) {
  278. const newNode = {
  279. node_id: this.nodes.length + 1,
  280. lib_id: mb.lib_id,
  281. source_id: node.node_id,
  282. code: node.code,
  283. name: node.name,
  284. match_type: node.match_type,
  285. match_key: mb.match_key,
  286. bills_id: mb.n_id,
  287. bills_xid: node.match_type === nodeConst.matchType.code ? this._getXMatchId(mb) : -1,
  288. index_class: node.index_class,
  289. class_name: node.class_name,
  290. };
  291. this.nodes.push(newNode);
  292. this._syncNodeParam(newNode.source_id, newNode.node_id, mb);
  293. this._syncNodeIndex(newNode);
  294. mb.match_node = newNode.node_id;
  295. }
  296. }
  297. /**
  298. * 按模板匹配规则,给清单匹配指标节点
  299. *
  300. * @param {Array} bills - 匹配的清单
  301. * @param {Number} templateId - 用户匹配的模板Id
  302. * @returns {Promise<void>}
  303. */
  304. async matchBills (bills, templateId = 1) {
  305. this._init(bills);
  306. // 获取指标模板全部数据
  307. await this._getTemplateData(templateId);
  308. // 同步全局指标参数
  309. this._syncNodeParam(paramConst.globalParamNodeId, paramConst.globalParamNodeId, this.bills[0]);
  310. // 遍历模板中所有指标节点,匹配清单
  311. for (const node of this.templateNodes) {
  312. this._matchNode(node);
  313. }
  314. // 计算全部指标节点
  315. const globalParams = this.params.filter(function (p) {
  316. return p.node_id === paramConst.globalParamNodeId;
  317. });
  318. for (const node of this.nodes) {
  319. const nodeParams = this.params.filter(function (n) {
  320. return n.node_id === node.node_id;
  321. });
  322. const nodeIndexes = this.indexes.filter(function (i) {
  323. return i.node_id === node.node_id;
  324. });
  325. this.ctx.service.indexCalc.calculate(nodeIndexes, globalParams, nodeParams);
  326. }
  327. for (const b of this.bills) {
  328. delete b.match_key;
  329. }
  330. }
  331. _syncCustomData(data, custom) {
  332. if (!custom || custom.length === 0) return;
  333. for (const d of data) {
  334. const c = custom.find(function (x) {return x.name === d.name});
  335. d.calc_value = c.calc_value;
  336. }
  337. }
  338. async reMatchBills(lib, templateId = 1) {
  339. const bills = await this.ctx.service.bills.getAllDataByCondition({ where: {lib_id: lib.id} });
  340. this._init(bills);
  341. for (const b of bills) {
  342. b.match_node = null;
  343. }
  344. // 获取指标模板全部数据
  345. await this._getTemplateData(templateId);
  346. // 同步全局指标参数
  347. this._syncNodeParam(paramConst.globalParamNodeId, paramConst.globalParamNodeId, this.bills[0]);
  348. // 遍历模板中所有指标节点,匹配清单
  349. for (const node of this.templateNodes) {
  350. this._matchNode(node);
  351. }
  352. // 计算全部指标节点
  353. const globalParams = this.params.filter(function (p) {
  354. return p.node_id === paramConst.globalParamNodeId;
  355. });
  356. const ogp = await this.ctx.service.tenderParam.getAllDataByCondition({ where: {lib_id: lib.id} });
  357. this._syncCustomData(globalParams, ogp);
  358. for (const node of this.nodes) {
  359. const nodeParams = this.params.filter(function (n) {
  360. return n.node_id === node.node_id;
  361. });
  362. const onp = await this.ctx.service.tenderParam.getParams(lib.id, node.bills_id);
  363. this._syncCustomData(nodeParams, onp);
  364. const nodeIndexes = this.indexes.filter(function (i) {
  365. return i.node_id === node.node_id;
  366. });
  367. this.ctx.service.indexCalc.calculate(nodeIndexes, globalParams, nodeParams);
  368. }
  369. for (const b of this.bills) {
  370. delete b.match_key;
  371. }
  372. }
  373. }
  374. return Match;
  375. };