gcl_gather.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. 'use strict';
  2. /**
  3. *
  4. * 清单汇总(需使用 decimal.min.js, zh_calc.js, path_tree.js, lodash.js)
  5. *
  6. * @author Mai
  7. * @date
  8. * @version
  9. */
  10. const gclGatherModel = (function () {
  11. // 需要汇总计算的字段
  12. const ledgerGatherFields = ['quantity', 'total_price', 'deal_qty', 'deal_tp',
  13. 'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp',
  14. 'pre_contract_qty', 'pre_contract_tp', 'pre_qc_qty', 'pre_qc_tp',
  15. 'end_contract_qty', 'end_contract_tp', 'end_qc_qty', 'end_qc_tp'];
  16. const posGatherFields = ['quantity', 'contract_qty', 'qc_qty', 'gather_qty',
  17. 'pre_contract_qty', 'pre_qc_qty', 'pre_gather_qty', 'end_contract_qty', 'end_qc_qty', 'end_gather_qty'];
  18. // 初始化 清单树
  19. const gsTreeSetting = {
  20. id: 'ledger_id',
  21. pid: 'ledger_pid',
  22. order: 'order',
  23. level: 'level',
  24. rootId: -1,
  25. keys: ['id', 'tender_id', 'ledger_id'],
  26. stageId: 'id',
  27. };
  28. gsTreeSetting.updateFields = ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'];
  29. const gsTree = createNewPathTree('stage', gsTreeSetting);
  30. // 初始化 部位明细
  31. const posSetting = {
  32. id: 'id', ledgerId: 'lid',
  33. updateFields: ['contract_qty', 'qc_qty'],
  34. };
  35. const gsPos = new StagePosData(posSetting);
  36. let deal = [], change;
  37. const gclList = [], leafXmjs = [];
  38. const mergeChar = ';';
  39. let tpDecimal = 0;
  40. /**
  41. * 将所有数据加载至树结构
  42. *
  43. * @param ledger - 台账数据
  44. * @param curStage - 当期计量数据
  45. * @param preStage - 截止上期计量数据
  46. * @param bgl - 变更令数据
  47. */
  48. function loadLedgerData (ledger, curStage, preStage, bgl) {
  49. // 加载树结构数据
  50. gsTree.loadDatas(ledger);
  51. // 加载本期计量数据
  52. if (curStage) {
  53. gsTree.loadCurStageData(curStage);
  54. }
  55. if (preStage) {
  56. gsTree.loadPreStageData(preStage);
  57. }
  58. }
  59. function loadPosData(pos, curPos, prePos) {
  60. gsPos.loadDatas(pos);
  61. gsPos.loadCurStageData(curPos);
  62. gsPos.loadPreStageData(prePos);
  63. }
  64. function loadDealBillsData(dealBills) {
  65. deal = dealBills;
  66. }
  67. function loadChangeBillsData(data, decimal) {
  68. change = data;
  69. tpDecimal = decimal;
  70. }
  71. function gatherfields(obj, src, fields) {
  72. if (obj && src) {
  73. for (const f of fields) {
  74. obj[f] = ZhCalc.add(obj[f], src[f]);
  75. }
  76. }
  77. }
  78. /**
  79. * 新建 清单汇总节点
  80. * @param node - 最底层 工程量清单节点
  81. * @returns {{b_code: *|string[], name, unit, unit_price: *|string[], leafXmjs: Array}}
  82. */
  83. function newGclNode(node) {
  84. const gcl = {
  85. b_code: node.b_code,
  86. name: node.name,
  87. unit: node.unit,
  88. unit_price: node.unit_price,
  89. leafXmjs: [],
  90. };
  91. gclList.push(gcl);
  92. return gcl;
  93. }
  94. /**
  95. * 获取清单汇总节点
  96. *
  97. * @param node - 最底层清单节点
  98. * @returns {*}
  99. */
  100. function getGclNode(node) {
  101. const gcl = gclList.find(function (g) {
  102. return g.b_code === node.b_code &&
  103. (g.name || node.name ? g.name === node.name : true) &&
  104. (g.unit || node.unit ? g.unit === node.unit : true) &&
  105. checkZero(ZhCalc.sub(g.unit_price, node.unit_price));
  106. });
  107. if (gcl) {
  108. return gcl
  109. } else {
  110. return newGclNode(node);
  111. }
  112. }
  113. /**
  114. * 检查 text 是否是Peg
  115. * e.g. K123+000(true) Kab+123(false) K123.234+234(false) K12+324.234(true)
  116. *
  117. * @param text
  118. * @returns {*}
  119. * @constructor
  120. */
  121. function CheckPeg(text) {
  122. const pegReg = /[a-zA-Z]*[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
  123. return pegReg.test(text);
  124. }
  125. /**
  126. * 获取 桩号节点
  127. * @param node - 检索起始节点
  128. * @returns {*}
  129. */
  130. function getPegNode (node) {
  131. if (node) {
  132. if (CheckPeg(node.name)) {
  133. return node;
  134. } else {
  135. const parent = gsTree.getParent(node);
  136. return parent ? getPegNode(parent) : null;
  137. }
  138. }
  139. }
  140. /**
  141. * 获取节点的第N层父节点
  142. *
  143. * @param node - 节点(检索起点)
  144. * @param level - 第N层
  145. * @returns {*}
  146. */
  147. function getNodeByLevel(node, level) {
  148. let cur = node;
  149. while (cur && cur.level > level) {
  150. cur = gsTree.getParent(cur);
  151. }
  152. return cur;
  153. }
  154. /**
  155. * 获取 单位工程
  156. *
  157. * @param xmj - 计量单元(最底层项目节)
  158. * @returns {string}
  159. */
  160. function getDwgc(peg, xmj) {
  161. if (peg) {
  162. return peg.name;
  163. } else {
  164. const node = getNodeByLevel(xmj, 2);
  165. return node ? node.name : '';
  166. }
  167. }
  168. /**
  169. * 获取 分部工程
  170. *
  171. * @param peg - 桩号节点
  172. * @param xmj - 计量单元(最底层项目节)
  173. * @returns {string}
  174. */
  175. function getFbgc(peg, xmj) {
  176. if (peg && peg.id !== xmj.id) {
  177. const node = getNodeByLevel(xmj, peg.level + 1);
  178. return node ? node.name : '';
  179. } else {
  180. const node = getNodeByLevel(xmj, 3);
  181. return node ? node.name : '';
  182. }
  183. }
  184. /**
  185. * 获取 分项工程
  186. *
  187. * @param peg - 桩号节点
  188. * @param xmj - 计量单元(最底层项目节)
  189. * @returns {string}
  190. */
  191. function getFxgc(peg, xmj) {
  192. if (!peg) {
  193. const node = getNodeByLevel(xmj, 4);
  194. return node ? node.name : '';
  195. } else if (peg.id === xmj.id) {
  196. if (xmj.level > 4) {
  197. let value = '';
  198. for (let level = 4; level < xmj.level; level++) {
  199. const node = getNodeByLevel(xmj, level);
  200. value = value === '' ? node.name : value + mergeChar + node.name;
  201. }
  202. return value;
  203. } else {
  204. return '';
  205. }
  206. } else {
  207. if (peg.level + 2 < xmj.level) {
  208. let value = '';
  209. for (let level = peg.level + 2; level < xmj.level; level++) {
  210. const node = getNodeByLevel(xmj, level);
  211. value = value === '' ? node.name : value + mergeChar + node.name;
  212. }
  213. return value;
  214. } else {
  215. return '';
  216. }
  217. }
  218. }
  219. /**
  220. * 新建 最底层项目节 缓存数据
  221. * @param leafXmj
  222. * @returns {{id, code: *|string[], jldy, fbgc: string, fxgc: string, dwgc: string, bwmx: string, drawing_code: string}}
  223. */
  224. function newCacheLeafXmj(leafXmj) {
  225. const peg = getPegNode(leafXmj);
  226. const cacheLX = {
  227. id: leafXmj.id,
  228. code: leafXmj.code,
  229. jldy: leafXmj.name,
  230. fbgc: getFbgc(peg, leafXmj),
  231. fxgc: getFxgc(peg, leafXmj),
  232. dwgc: getDwgc(peg, leafXmj),
  233. drawing_code: leafXmj.drawing_code,
  234. };
  235. leafXmjs.push(cacheLX);
  236. return cacheLX;
  237. }
  238. /**
  239. * 获取缓存的最底层项目节数据
  240. *
  241. * @param leafXmj - 最底层项目节
  242. * @returns {*}
  243. */
  244. function getCacheLeafXmj(leafXmj) {
  245. const cacheLX = leafXmjs.find(function (lx) {
  246. return lx.id === leafXmj.id;
  247. });
  248. if (!cacheLX) {
  249. return newCacheLeafXmj(leafXmj);
  250. } else {
  251. return cacheLX;
  252. }
  253. }
  254. /**
  255. * 汇总节点
  256. * @param node - 最底层 工程量清单 节点
  257. * @param leafXmj - 所属 最底层 项目节
  258. */
  259. function loadGatherGclNode(node, leafXmj) {
  260. const gcl = getGclNode(node);
  261. gatherfields(gcl, node, ledgerGatherFields);
  262. const cacheLeafXmj = getCacheLeafXmj(leafXmj);
  263. const posRange = gsPos.getLedgerPos(node.id);
  264. const detail = posRange && posRange.length > 0 ? posRange : [node];
  265. for (const d of detail) {
  266. const dx = _.assign({}, cacheLeafXmj);
  267. gatherfields(dx, d, posGatherFields);
  268. dx.gcl_id = node.id;
  269. if (d.name !== node.name) {
  270. dx.bwmx = d.name;
  271. dx.mx_id = d.id;
  272. }
  273. if (d.drawing_code) {
  274. dx.drawing_code = d.drawing_code;
  275. }
  276. gcl.leafXmjs.push(dx);
  277. }
  278. }
  279. /**
  280. * (递归)汇总树节点
  281. * @param nodes - 汇总节点列表
  282. * @param leafXmj - 汇总节点所属的底层项目节
  283. */
  284. function recursiveGatherGclData(nodes, leafXmj) {
  285. for (const node of nodes) {
  286. if (node.b_code) {
  287. if (node.children.length > 0) {
  288. const gcl = getGclNode(node);
  289. recursiveGatherGclData(node.children, leafXmj);
  290. } else {
  291. loadGatherGclNode(node, leafXmj);
  292. }
  293. } else if (node.children.length > 0) {
  294. recursiveGatherGclData(node.children, node);
  295. }
  296. }
  297. }
  298. function gatherDealBillsData() {
  299. if (deal && deal.length > 0) {
  300. for (const node of deal) {
  301. node.b_code = node.code;
  302. const gcl = getGclNode(node);
  303. if (!node.quantity || !node.unit_price) continue;
  304. gcl.deal_bills_qty = ZhCalc.add(gcl.deal_bills_qty, node.quantity);
  305. gcl.deal_bills_tp = ZhCalc.add(gcl.deal_bills_tp, node.total_price);
  306. }
  307. }
  308. }
  309. function gatherChangeBillsData() {
  310. if (change && change.length > 0) {
  311. for (const node of change) {
  312. node.b_code = node.code;
  313. node.quantity = parseFloat(node.samount);
  314. node.total_price = ZhCalc.mul(node.quantity, node.unit_price, node.tp_decimal);
  315. const gcl = getGclNode(node);
  316. gcl.change_bills_qty = ZhCalc.add(gcl.change_bills_qty, node.quantity);
  317. gcl.change_bills_tp = ZhCalc.add(gcl.change_bills_tp, node.total_price);
  318. }
  319. }
  320. }
  321. function calculateGatherData() {
  322. for (const gcl of gclList) {
  323. gcl.pre_gather_qty = ZhCalc.add(gcl.pre_contract_qty, gcl.pre_qc_qty);
  324. gcl.pre_gather_tp = ZhCalc.add(gcl.pre_contract_tp, gcl.pre_qc_tp);
  325. gcl.gather_qty = ZhCalc.add(gcl.contract_qty, gcl.qc_qty);
  326. gcl.end_contract_qty = ZhCalc.add(gcl.pre_contract_qty, gcl.contract_qty);
  327. gcl.end_qc_qty = ZhCalc.add(gcl.pre_qc_qty, gcl.qc_qty);
  328. gcl.end_gather_qty = ZhCalc.add(gcl.pre_gather_qty, gcl.gather_qty);
  329. gcl.gather_tp = ZhCalc.add(gcl.contract_tp, gcl.qc_tp);
  330. gcl.end_contract_tp = ZhCalc.add(gcl.pre_contract_tp, gcl.contract_tp);
  331. gcl.end_qc_tp = ZhCalc.add(gcl.pre_qc_tp, gcl.qc_tp);
  332. gcl.end_gather_tp = ZhCalc.add(gcl.pre_gather_tp, gcl.gather_tp);
  333. gcl.dgn_price = ZhCalc.round(ZhCalc.div(gcl.total_price, gcl.dgn_qty1), 2);
  334. gcl.end_final_qty = ZhCalc.add(gcl.end_qc_qty, gcl.quantity);
  335. gcl.end_final_tp = ZhCalc.add(gcl.end_qc_tp, gcl.total_price);
  336. gcl.final_qty = ZhCalc.add(gcl.quantity, gcl.change_bills_qty);
  337. gcl.final_tp = ZhCalc.add(gcl.total_price, gcl.change_bills_tp);
  338. gcl.end_gather_percent = gcl.end_final_qty && gcl.end_gather_qty
  339. ? ZhCalc.mul(ZhCalc.div(gcl.end_gather_qty, gcl.end_final_qty), 100, 2)
  340. : ZhCalc.mul(ZhCalc.div(gcl.end_gather_tp, gcl.end_final_tp), 100, 2);
  341. gcl.final_percent = gcl.final_qty && gcl.end_gather_qty
  342. ? ZhCalc.mul(ZhCalc.div(gcl.end_gather_qty, gcl.final_qty), 100, 2)
  343. : ZhCalc.mul(ZhCalc.div(gcl.end_gather_tp, gcl.final_tp), 100, 2);
  344. for (const xmj of gcl.leafXmjs) {
  345. xmj.pre_gather_qty = ZhCalc.add(xmj.pre_contract_qty, xmj.pre_qc_qty);
  346. xmj.gather_qty = ZhCalc.add(xmj.contract_qty, xmj.qc_qty);
  347. xmj.end_contract_qty = ZhCalc.add(xmj.pre_contract_qty, xmj.contract_qty);
  348. xmj.end_qc_qty = ZhCalc.add(xmj.pre_qc_qty, xmj.qc_qty);
  349. xmj.end_gather_qty = ZhCalc.add(xmj.pre_gather_qty, xmj.gather_qty);
  350. xmj.end_final_qty = ZhCalc.add(xmj.end_qc_qty, xmj.quantity);
  351. xmj.end_gather_percent = ZhCalc.mul(ZhCalc.div(xmj.end_gather_qty, xmj.end_final_qty), 100, 2);
  352. }
  353. }
  354. }
  355. function compareCode(str1, str2, symbol = '-') {
  356. if (!str1) {
  357. return 1;
  358. } else if (!str2) {
  359. return -1;
  360. }
  361. function compareSubCode(code1, code2) {
  362. if (numReg.test(code1)) {
  363. if (numReg.test(code2)) {
  364. return parseInt(code1) - parseInt(code2);
  365. } else {
  366. return -1
  367. }
  368. } else {
  369. if (numReg.test(code2)) {
  370. return 1;
  371. } else {
  372. return code1 === code2 ? 0 : (code1 < code2 ? -1 : 1); //code1.localeCompare(code2);
  373. }
  374. }
  375. }
  376. const numReg = /^[0-9]+$/;
  377. const aCodes = str1.split(symbol), bCodes = str2.split(symbol);
  378. for (let i = 0, iLength = Math.min(aCodes.length, bCodes.length); i < iLength; ++i) {
  379. const iCompare = compareSubCode(aCodes[i], bCodes[i]);
  380. if (iCompare !== 0) {
  381. return iCompare;
  382. }
  383. }
  384. return aCodes.length - bCodes.length;
  385. }
  386. /**
  387. * 根据树结构 清单汇总
  388. */
  389. function gatherGclData() {
  390. // 清空旧数据
  391. if (gclList.length > 0) {
  392. gclList.length = 0; //splice(0, gclList.length);
  393. }
  394. recursiveGatherGclData(gsTree.children, null);
  395. gatherDealBillsData();
  396. gatherChangeBillsData();
  397. calculateGatherData();
  398. gclList.sort(function (a, b) {
  399. return compareCode(a.b_code, b.b_code) || ZhCalc.sub(a.unit_price, b.unit_price);
  400. });
  401. return gclList;
  402. }
  403. function checkDiffer(gclList) {
  404. for (const gcl of gclList) {
  405. gcl.differ = false;
  406. }
  407. for (const [i, gcl] of gclList.entries()) {
  408. if (i === gclList.length - 1) continue;
  409. const next = gclList[i+1];
  410. if (gcl.b_code === next.b_code) {
  411. if (gcl.name !== next.name || gcl.unit !== next.unit || !checkZero(gcl.unit_price - next.unit_price)) {
  412. gcl.differ = true;
  413. next.differ = true;
  414. }
  415. }
  416. }
  417. }
  418. function _getCalcChapter(chapter, option) {
  419. const gclChapter = [], otherChapter = {}, gclChapterFilter = [];
  420. let serialNo = 1;
  421. for (const c of chapter) {
  422. const cc = { code: c.code, name: c.name, cType: 1 };
  423. cc.serialNo = serialNo++;
  424. cc.filter = '^[^0-9]*' + c.code.substr(0, c.code.length - 2) + '[0-9]{2}(-|$)';
  425. gclChapter.push(cc);
  426. }
  427. gclChapter.push({ name: '未计入章节清单合计', cType: 21, serialNo: serialNo+1 });
  428. otherChapter.hj = { name: '合计(C=A+B+Z)', cType: 41, serialNo: serialNo+5, deal_bills_tp: option.zlj.deal_bills_tp };
  429. gclChapterFilter.push({node_type: option.jrg.value});
  430. gclChapterFilter.push({field: 'name', part: option.jrg.text});
  431. const zlChapter = {
  432. name: '暂列金额(Z)', cType: 32, serialNo: serialNo+4,
  433. deal_bills_tp: option.zlj.deal_bills_tp, match: [], matchPath: []
  434. };
  435. zlChapter.match.push({node_type: option.zlj.value});
  436. zlChapter.match.push({field: 'name', part: option.zlj.text});
  437. otherChapter.zlj = zlChapter;
  438. otherChapter.qd = { name: '清单小计(A)', cType: 11, serialNo: serialNo+2 };
  439. otherChapter.fqd = { name: '非清单项费用(B)', cType: 31, serialNo: serialNo+3 };
  440. return [gclChapter, otherChapter, gclChapterFilter];
  441. }
  442. function _gatherChapterFields(chapter, data, fields) {
  443. for (const f of fields) {
  444. chapter[f] = ZhCalc.add(chapter[f], data[f]);
  445. }
  446. }
  447. function _getGclChapter(chapter, data) {
  448. for (const c of chapter) {
  449. if (c.filter) {
  450. const reg = new RegExp(c.filter);
  451. if (reg.test(data.b_code)) {
  452. return c;
  453. }
  454. } else {
  455. return c;
  456. }
  457. }
  458. }
  459. function _checkFilter(d, filter) {
  460. for (const f of filter) {
  461. if (f.node_type && f.node_type === d.node_type) return true;
  462. if (f.field) {
  463. if (f.part && d[f.field] && d[f.field].indexOf(f.part) >= 0) return true;
  464. if (f.all && d[f.all] && d[f.all] === f.all) return true;
  465. }
  466. }
  467. return false;
  468. }
  469. function gatherChapterData(chapter, option, fields) {
  470. const chapterFilterPath = [];
  471. const checkFilterPath = function (data, filterPath) {
  472. for (const fp of filterPath) {
  473. if (data.full_path.indexOf(fp + '-') === 0 || data.full_path === fp) return true;
  474. }
  475. return false;
  476. };
  477. const [gclChapter, otherChapter, gclChapterFilter] = _getCalcChapter(chapter, option);
  478. for (const d of gsTree.nodes) {
  479. if (_checkFilter(d, gclChapterFilter)) chapterFilterPath.push(d.full_path);
  480. if (_checkFilter(d, otherChapter.zlj.match)) otherChapter.zlj.matchPath.push(d.full_path);
  481. if (d.children && d.children.length > 0) continue;
  482. if (checkFilterPath(d,otherChapter.zlj.matchPath)) {
  483. gatherfields(otherChapter.zlj, d, fields);
  484. gatherfields(otherChapter.hj, d, fields);
  485. } else {
  486. gatherfields(otherChapter.hj, d, fields);
  487. if (d.b_code) {
  488. gatherfields(otherChapter.qd, d, fields);
  489. }
  490. if (!d.b_code || d.b_code === '') {
  491. gatherfields(otherChapter.fqd, d, fields);
  492. }
  493. if (d.b_code) {
  494. const c = checkFilterPath(d, chapterFilterPath)
  495. ? gclChapter.find(x => { return x.cType === 21})
  496. : _getGclChapter(gclChapter, d);
  497. gatherfields(c, d, fields);
  498. }
  499. }
  500. }
  501. for (const d of deal) {
  502. if (!d.quantity || !d.unit_price) continue;
  503. otherChapter.hj.deal_bills_tp = ZhCalc.add(otherChapter.hj.deal_bills_tp, d.total_price);
  504. otherChapter.qd.deal_bills_tp = ZhCalc.add(otherChapter.qd.deal_bills_tp, d.total_price);
  505. const c = _getGclChapter(gclChapter, d);
  506. c.deal_bills_tp = ZhCalc.add(c.deal_bills_tp, d.total_price);
  507. }
  508. const result = gclChapter.concat([otherChapter.hj, otherChapter.zlj, otherChapter.qd, otherChapter.fqd]);
  509. result.sort((x, y) => {return x.serialNo - y.serialNo});
  510. return result;
  511. }
  512. return {
  513. loadLedgerData,
  514. loadPosData,
  515. loadDealBillsData,
  516. loadChangeBillsData,
  517. gatherGclData,
  518. checkDiffer,
  519. gatherChapterData,
  520. };
  521. })();