|
@@ -0,0 +1,412 @@
|
|
|
+'use strict';
|
|
|
+
|
|
|
+/**
|
|
|
+ *
|
|
|
+ * 清单汇总对比(需使用 decimal.min.js, zh_calc.js, path_tree.js, lodash.js)
|
|
|
+ *
|
|
|
+ * @author Mai
|
|
|
+ * @date 2020/10/28
|
|
|
+ * @version
|
|
|
+ */
|
|
|
+
|
|
|
+const gclCompareModel = (function () {
|
|
|
+ const leafXmjs = [], mergeChar = ';';
|
|
|
+ let gclList, gclChapter, otherChapter;
|
|
|
+ let ledgerSetting, gsTree;
|
|
|
+
|
|
|
+ function gatherfields(obj, src, fields, prefix = '') {
|
|
|
+ if (obj && src) {
|
|
|
+ for (const f of fields) {
|
|
|
+ obj[prefix + f] = ZhCalc.add(obj[prefix + f], src[f]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 新建 清单汇总节点
|
|
|
+ * @param node - 最底层 工程量清单节点
|
|
|
+ * @returns {obj}
|
|
|
+ */
|
|
|
+ function newGclNode(node) {
|
|
|
+ const gcl = {
|
|
|
+ b_code: node.b_code,
|
|
|
+ name: node.name,
|
|
|
+ unit: node.unit,
|
|
|
+ unit_price: node.unit_price,
|
|
|
+ leafXmjs: [],
|
|
|
+ };
|
|
|
+ gclList.push(gcl);
|
|
|
+ return gcl;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取清单汇总节点
|
|
|
+ *
|
|
|
+ * @param node - 最底层清单节点
|
|
|
+ * @returns {*}
|
|
|
+ */
|
|
|
+ function getGclNode(node) {
|
|
|
+ const gcl = gclList.find(function (g) {
|
|
|
+ return g.b_code === node.b_code &&
|
|
|
+ (g.name || node.name ? g.name === node.name : true) &&
|
|
|
+ (g.unit || node.unit ? g.unit === node.unit : true) &&
|
|
|
+ checkZero(ZhCalc.sub(g.unit_price, node.unit_price));
|
|
|
+ });
|
|
|
+ if (gcl) {
|
|
|
+ return gcl
|
|
|
+ } else {
|
|
|
+ return newGclNode(node);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查 text 是否是Peg
|
|
|
+ * e.g. K123+000(true) Kab+123(false) K123.234+234(false) K12+324.234(true)
|
|
|
+ *
|
|
|
+ * @param text
|
|
|
+ * @returns {*}
|
|
|
+ * @constructor
|
|
|
+ */
|
|
|
+ function CheckPeg(text) {
|
|
|
+ const pegReg = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
|
|
|
+ return pegReg.test(text);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取 桩号节点
|
|
|
+ * @param node - 检索起始节点
|
|
|
+ * @returns {*}
|
|
|
+ */
|
|
|
+ function getPegNode (node) {
|
|
|
+ if (node) {
|
|
|
+ if (CheckPeg(node.name)) {
|
|
|
+ return node;
|
|
|
+ } else {
|
|
|
+ const parent = gsTree.getParent(node);
|
|
|
+ return parent ? getPegNode(parent) : null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取节点的第N层父节点
|
|
|
+ *
|
|
|
+ * @param node - 节点(检索起点)
|
|
|
+ * @param level - 第N层
|
|
|
+ * @returns {*}
|
|
|
+ */
|
|
|
+ function getNodeByLevel(node, level) {
|
|
|
+ let cur = node;
|
|
|
+ while (cur && cur.level > level) {
|
|
|
+ cur = gsTree.getParent(cur);
|
|
|
+ }
|
|
|
+ return cur;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取 单位工程
|
|
|
+ *
|
|
|
+ * @param xmj - 计量单元(最底层项目节)
|
|
|
+ * @returns {string}
|
|
|
+ */
|
|
|
+ function getDwgc(peg, xmj) {
|
|
|
+ if (peg) {
|
|
|
+ return peg.name;
|
|
|
+ } else {
|
|
|
+ const node = getNodeByLevel(xmj, 2);
|
|
|
+ return node ? node.name : '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取 分部工程
|
|
|
+ *
|
|
|
+ * @param peg - 桩号节点
|
|
|
+ * @param xmj - 计量单元(最底层项目节)
|
|
|
+ * @returns {string}
|
|
|
+ */
|
|
|
+ function getFbgc(peg, xmj) {
|
|
|
+ if (peg && peg.id !== xmj.id) {
|
|
|
+ const node = getNodeByLevel(xmj, peg.level + 1);
|
|
|
+ return node ? node.name : '';
|
|
|
+ } else {
|
|
|
+ const node = getNodeByLevel(xmj, 3);
|
|
|
+ return node ? node.name : '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取 分项工程
|
|
|
+ *
|
|
|
+ * @param peg - 桩号节点
|
|
|
+ * @param xmj - 计量单元(最底层项目节)
|
|
|
+ * @returns {string}
|
|
|
+ */
|
|
|
+ function getFxgc(peg, xmj) {
|
|
|
+ if (!peg) {
|
|
|
+ const node = getNodeByLevel(xmj, 4);
|
|
|
+ return node ? node.name : '';
|
|
|
+ } else if (peg.id === xmj.id) {
|
|
|
+ if (xmj.level > 4) {
|
|
|
+ let value = '';
|
|
|
+ for (let level = 4; level < xmj.level; level++) {
|
|
|
+ const node = getNodeByLevel(xmj, level);
|
|
|
+ value = value === '' ? node.name : value + mergeChar + node.name;
|
|
|
+ }
|
|
|
+ return value;
|
|
|
+ } else {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (peg.level + 2 < xmj.level) {
|
|
|
+ let value = '';
|
|
|
+ for (let level = peg.level + 2; level < xmj.level; level++) {
|
|
|
+ const node = getNodeByLevel(xmj, level);
|
|
|
+ value = value === '' ? node.name : value + mergeChar + node.name;
|
|
|
+ }
|
|
|
+ return value;
|
|
|
+ } else {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 新建 最底层项目节 缓存数据
|
|
|
+ * @param leafXmj
|
|
|
+ * @returns {{id, code: *|string[], jldy, fbgc: string, fxgc: string, dwgc: string, bwmx: string, drawing_code: string}}
|
|
|
+ */
|
|
|
+ function newCacheLeafXmj(leafXmj) {
|
|
|
+ const peg = getPegNode(leafXmj);
|
|
|
+ const cacheLX = {
|
|
|
+ id: leafXmj.id,
|
|
|
+ code: leafXmj.code,
|
|
|
+ jldy: leafXmj.name,
|
|
|
+ fbgc: getFbgc(peg, leafXmj),
|
|
|
+ fxgc: getFxgc(peg, leafXmj),
|
|
|
+ dwgc: getDwgc(peg, leafXmj),
|
|
|
+ drawing_code: leafXmj.drawing_code,
|
|
|
+ };
|
|
|
+ leafXmjs.push(cacheLX);
|
|
|
+ return cacheLX;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取缓存的最底层项目节数据
|
|
|
+ *
|
|
|
+ * @param leafXmj - 最底层项目节
|
|
|
+ * @returns {*}
|
|
|
+ */
|
|
|
+ function getCacheLeafXmj(leafXmj) {
|
|
|
+ const cacheLX = leafXmjs.find(function (lx) {
|
|
|
+ return lx.id === leafXmj.id;
|
|
|
+ });
|
|
|
+ if (!cacheLX) {
|
|
|
+ return newCacheLeafXmj(leafXmj);
|
|
|
+ } else {
|
|
|
+ return cacheLX;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 汇总节点
|
|
|
+ * @param node - 最底层 工程量清单 节点
|
|
|
+ * @param leafXmj - 所属 最底层 项目节
|
|
|
+ */
|
|
|
+ function loadGatherGclNode(node, leafXmj, gsPos) {
|
|
|
+ const gcl = getGclNode(node);
|
|
|
+ gatherfields(gcl, node, ledgerSetting.billsFields, ledgerSetting.prefix);
|
|
|
+ const cacheLeafXmj = getCacheLeafXmj(leafXmj);
|
|
|
+ const posRange = gsPos.getLedgerPos(node.id);
|
|
|
+ const detail = posRange && posRange.length > 0 ? posRange : [node];
|
|
|
+ for (const d of detail) {
|
|
|
+ const lx = gcl.leafXmjs.find(x => {return x.id === leafXmj.id && (x.mx_id === d.id || x.gcl_id === d.id)});
|
|
|
+ if (lx) {
|
|
|
+ gatherfields(lx, d, ledgerSetting.posFields, ledgerSetting.prefix);
|
|
|
+ } else {
|
|
|
+ const dx = _.assign({}, cacheLeafXmj);
|
|
|
+ gatherfields(dx, d, ledgerSetting.posFields, ledgerSetting.prefix);
|
|
|
+ dx.gcl_id = node.id;
|
|
|
+ if (d.name !== node.name) {
|
|
|
+ dx.bwmx = d.name;
|
|
|
+ dx.mx_id = d.id;
|
|
|
+ }
|
|
|
+ if (d.drawing_code) {
|
|
|
+ dx.drawing_code = d.drawing_code;
|
|
|
+ }
|
|
|
+ gcl.leafXmjs.push(dx);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * (递归)汇总树节点
|
|
|
+ * @param nodes - 汇总节点列表
|
|
|
+ * @param leafXmj - 汇总节点所属的底层项目节
|
|
|
+ */
|
|
|
+ function recursiveGatherGclData(nodes, leafXmj, gsPos) {
|
|
|
+ for (const node of nodes) {
|
|
|
+ if (node.b_code) {
|
|
|
+ if (node.children.length > 0) {
|
|
|
+ recursiveGatherGclData(node.children, leafXmj, gsPos);
|
|
|
+ } else {
|
|
|
+ loadGatherGclNode(node, leafXmj, gsPos);
|
|
|
+ }
|
|
|
+ } else if (node.children.length > 0) {
|
|
|
+ recursiveGatherGclData(node.children, node, gsPos);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function _getCalcChapter(chapter) {
|
|
|
+ const gclChapter = [], otherChapter = [];
|
|
|
+ let serialNo = 1;
|
|
|
+ for (const c of chapter) {
|
|
|
+ const cc = { code: c.code, name: c.name, cType: 1 };
|
|
|
+ cc.serialNo = serialNo++;
|
|
|
+ cc.filter = '^[^0-9]*' + c.code.substr(0, c.code.length - 2) + '[0-9]{2}-';
|
|
|
+ gclChapter.push(cc);
|
|
|
+ }
|
|
|
+ gclChapter.push({ name: '未计入章节清单合计', cType: 21, serialNo: serialNo++, });
|
|
|
+ otherChapter.push({ name: '清单小计(A)', cType: 11, serialNo: serialNo++ });
|
|
|
+ otherChapter.push({ name: '非清单项费用(B)', cType: 31, serialNo: serialNo++ });
|
|
|
+ otherChapter.push({ name: '合计(C=A+B)', cType: 41, serialNo: serialNo });
|
|
|
+ return [gclChapter, otherChapter];
|
|
|
+ }
|
|
|
+
|
|
|
+ function _gatherChapterFields(chapter, data, fields) {
|
|
|
+ for (const f of fields) {
|
|
|
+ chapter[f] = ZhCalc.add(chapter[f], data[f]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function _getGclChapter(chapter, data) {
|
|
|
+ for (const c of chapter) {
|
|
|
+ if (c.filter) {
|
|
|
+ const reg = new RegExp(c.filter);
|
|
|
+ if (reg.test(data.b_code)) {
|
|
|
+ return c;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return c;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function _gatherChapter() {
|
|
|
+ for (const d of gsTree.datas) {
|
|
|
+ if (d.children && d.children.length > 0) continue;
|
|
|
+
|
|
|
+ for (const c of otherChapter) {
|
|
|
+ if (c.cType === 41) {
|
|
|
+ gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
|
|
|
+ } else if (c.cType === 31 && (!d.b_code || d.b_code === '')) {
|
|
|
+ gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
|
|
|
+ } else if (c.cType === 11 && (d.b_code)) {
|
|
|
+ gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (d.b_code) {
|
|
|
+ const c = _getGclChapter(gclChapter, d);
|
|
|
+ gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function init (gclData, chapter) {
|
|
|
+ gclList = gclData;
|
|
|
+ [gclChapter, otherChapter] = _getCalcChapter(chapter);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ *
|
|
|
+ * @param bills - 项目节+清单数据
|
|
|
+ * @param pos - 计量单元数据
|
|
|
+ * @param setting 配置
|
|
|
+ * e.g.
|
|
|
+ * {
|
|
|
+ * tree: {
|
|
|
+ * id: 'ledger_id',
|
|
|
+ * pid: 'ledger_pid',
|
|
|
+ * order: 'order',
|
|
|
+ * level: 'level',
|
|
|
+ * rootId: -1,
|
|
|
+ * keys: ['id', 'tender_id', 'ledger_id'],
|
|
|
+ * stageId: 'id',
|
|
|
+ * },
|
|
|
+ * pos: { id: 'id', ledgerId: 'lid', },
|
|
|
+ * billsFields: ['quantity', 'total_price', 'deal_qty', 'deal_tp'],
|
|
|
+ * posFields: ['quantity'],
|
|
|
+ * chapterFields: [new_total_price],
|
|
|
+ * prefix: 'org' // 'new'
|
|
|
+ * }
|
|
|
+ */
|
|
|
+ function gatherLedgerData(bills, pos, setting) {
|
|
|
+ ledgerSetting = setting;
|
|
|
+ try {
|
|
|
+ if (leafXmjs.length > 0) leafXmjs.length = 0;
|
|
|
+ gsTree = createNewPathTree('ledger', setting.tree);
|
|
|
+ gsTree.loadDatas(bills);
|
|
|
+ const gsPos = new PosData(setting.pos);
|
|
|
+ gsPos.loadDatas(pos);
|
|
|
+ recursiveGatherGclData(gsTree.children, null, gsPos);
|
|
|
+ _gatherChapter();
|
|
|
+ } catch(err) {
|
|
|
+ console.log(err);
|
|
|
+ }
|
|
|
+ ledgerSetting = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ *
|
|
|
+ * @param data {Array} - 签约清单数据
|
|
|
+ * @returns <void>
|
|
|
+ */
|
|
|
+ function gatherDealBills(data) {
|
|
|
+ if (data instanceof Array && data.length > 0) {
|
|
|
+ for (const node of data) {
|
|
|
+ node.b_code = node.code;
|
|
|
+ const gcl = getGclNode(node);
|
|
|
+ if (!d.quantity || !d.unit_price) continue;
|
|
|
+ gcl.deal_bills_qty = node.quantity || 0;
|
|
|
+ gcl.deal_bills_tp = node.total_price || 0;
|
|
|
+
|
|
|
+ for (const c of otherChapter) {
|
|
|
+ if (c.cType === 41 || c.cType === 11) {
|
|
|
+ c.deal_bills_tp = ZhCalc.add(c.deal_bills_tp, d.total_price);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const c = _getGclChapter(gclChapter, d);
|
|
|
+ c.deal_bills_tp = ZhCalc.add(c.deal_bills_tp, d.total_price);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查汇总完的工程量清单中,同编号 & 不同名称/单位/单价 的清单并标记
|
|
|
+ */
|
|
|
+ function checkDiffer() {
|
|
|
+ for (const gcl of gclList) {
|
|
|
+ gcl.differ = false;
|
|
|
+ }
|
|
|
+ for (const [i, gcl] of gclList.entries()) {
|
|
|
+ if (i === gclList.length - 1) continue;
|
|
|
+ const next = gclList[i+1];
|
|
|
+ if (gcl.b_code === next.b_code) {
|
|
|
+ if (gcl.name !== next.name || gcl.unit !== next.unit || !checkZero(gcl.unit_price - next.unit_price)) {
|
|
|
+ gcl.differ = true;
|
|
|
+ next.differ = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function chapterData () {
|
|
|
+ return gclChapter.concat(otherChapter);
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ init,
|
|
|
+ gatherLedgerData, gatherDealBills, checkDiffer,
|
|
|
+ chapterData
|
|
|
+ };
|
|
|
+})();
|