'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, chapterfilter; 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 _checkFilter(d, filter) { for (const f of filter) { if (f.node_type && f.node_type === d.node_type) return true; if (f.field) { if (f.part && d[f.field] && d[f.field].indexOf(f.part) >= 0) return true; if (f.all && d[f.all] && d[f.all] === f.all) return true; } } return false; } function _gatherChapter() { const filterPath = []; const checkFilterPath = function (data) { for (const fp of filterPath) { if (data.full_path.indexOf(fp + '-') === 0 || data.full_path === fp) return true; } return false; }; for (const d of gsTree.nodes) { if (_checkFilter(d, chapterfilter)) { filterPath.push(d.full_path); } if (d.children && d.children.length > 0) continue; for (const c of otherChapter) { if (c.cType === 41) { gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix); } if (c.cType === 11 && (d.b_code)) { gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix); } if (c.cType === 31 && (!d.b_code || d.b_code === '')) { gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix); } } for (const fp of filterPath) { if (d.full_path.indexOf(fp + '-') === 0 || d.full_path === fp) continue; } if (d.b_code) { const c = checkFilterPath(d) ? gclChapter.find(x => { return x.cType === 21}) : _getGclChapter(gclChapter, d); gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix); } } } function init (gclData, chapter, filter) { gclList = gclData; [gclChapter, otherChapter] = _getCalcChapter(chapter); chapterfilter = filter || []; } /** * * @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; } function compareCode(str1, str2, symbol = '-') { if (!str1) { return 1; } else if (!str2) { return -1; } function compareSubCode(code1, code2) { if (numReg.test(code1)) { if (numReg.test(code2)) { return parseInt(code1) - parseInt(code2); } else { return -1 } } else { if (numReg.test(code2)) { return 1; } else { return code1 === code2 ? 0 : (code1 < code2 ? -1 : 1); //code1.localeCompare(code2); } } } const numReg = /^[0-9]+$/; const aCodes = str1.split(symbol), bCodes = str2.split(symbol); for (let i = 0, iLength = Math.min(aCodes.length, bCodes.length); i < iLength; ++i) { const iCompare = compareSubCode(aCodes[i], bCodes[i]); if (iCompare !== 0) { return iCompare; } } return aCodes.length - bCodes.length; } /** * * @param data {Array} - 签约清单数据 * @returns */ 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 (!node.quantity || !node.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, node.total_price); } } const c = _getGclChapter(gclChapter, node); c.deal_bills_tp = ZhCalc.add(c.deal_bills_tp, node.total_price); } } } /** * 检查汇总完的工程量清单中,同编号 & 不同名称/单位/单价 的清单并标记 */ function checkDiffer() { gclList.sort((a, b) => { return compareCode(a.b_code, b.b_code); }); for (const gcl of gclList) { gcl.differ = false; } for (const [i, gcl] of gclList.entries()) { gcl.differ_qty = ZhCalc.sub(gcl.new_quantity, gcl.org_quantity, 6); gcl.deal_differ_qty = ZhCalc.sub(gcl.new_quantity, gcl.deal_bills_qty, 6); 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 }; })();