/** * Created by CSL on 2017-07-19. * 计算程序。所有定额、清单、父清单的计算都从此入。 */ let calcTools = { getNodeByFlag: function (flag) { let bill = cbTools.findBill(flag); if (bill) return this.getNodeByID(bill.ID) else return null; }, getNodeByID: function (ID){ return cbTools.getNodeByID(ID); }, // 是否是标题清单 isTitleBills: function (node) { let flag = node.getFlag(); return titleFlags.includes(flag); }, isBill: function(treeNode){ return treeNode.sourceType === ModuleNames.bills; }, isParentBill: function (treeNode) { return this.isBill(treeNode) && treeNode.source.children && treeNode.source.children.length > 0; }, isLeafBill: function(treeNode){ // 下面挂有定额的清单也是叶子清单 return this.isBill(treeNode) && treeNode.source.children && treeNode.source.children.length === 0; }, isLeafNode: function(treeNode){ // 最底层结点,如定额等。 return treeNode.children.length === 0; }, isBill_DXFY: function(treeNode){ return this.isBill(treeNode) && treeNode.data.type == billType.DXFY; }, isBill_FB: function(treeNode){ return this.isBill(treeNode) && treeNode.data.type == billType.FB; }, isBill_FX: function(treeNode){ return this.isBill(treeNode) && treeNode.data.type == billType.FX; }, isBill_BILL: function(treeNode){ return this.isBill(treeNode) && treeNode.data.type == billType.BILL; }, isBill_BX: function(treeNode){ return this.isBill(treeNode) && treeNode.data.type == billType.BX; }, isNullBill: function (treeNode) { return this.isLeafBill(treeNode) && (treeNode.children.length === 0) && (!treeNode.data.calcBase); }, isCalcBaseBill: function(treeNode){ return this.isLeafBill(treeNode) && (treeNode.children.length === 0) && (treeNode.data.calcBase); }, isTotalCostBill: function (treeNode) { return treeNode.data.flagsIndex && treeNode.data.flagsIndex.fixed && treeNode.data.flagsIndex.fixed.flag && treeNode.data.flagsIndex.fixed.flag == fixedFlag.ENGINEERINGCOST; }, isRationCategory: function(treeNode){ return treeNode.sourceType === ModuleNames.ration; }, isRationItem: function(treeNode){ return this.isRationCategory(treeNode) && treeNode.data.type === rationType.ration; }, isCalcManageRation:function(treeNode){ return this.isRationCategory(treeNode) && (treeNode.data.type === rationType.ration || treeNode.data.type === rationType.install|| treeNode.data.type === rationType.itemIncrease); }, isVolumePrice: function (treeNode) { return this.isRationCategory(treeNode) && treeNode.data.type === rationType.volumePrice; }, isGljRation: function (treeNode) { return this.isRationCategory(treeNode) && treeNode.data.type === rationType.gljRation; }, isVP_or_GLJR: function (treeNode) { // 是量价或工料机类型的定额 return this.isRationCategory(treeNode) && (treeNode.data.type == rationType.volumePrice || treeNode.data.type == rationType.gljRation); }, isSameTypeNode: function (node1, node2) { if (node1.parent && node2.parent && (node1.parent === node2.parent) && (node1.sourceType === node1.sourceType) && (node1.data && node2.data && node1.data.type === node2.data.type)){ return true; } return false; }, isInheritFrom: function (treeNode, flagsArr){ let cur = treeNode; while (cur.parent) { cur = cur.parent; }; let flag = -1; if (cur.data.flagsIndex && cur.data.flagsIndex.fixed && cur.data.flagsIndex.fixed.flag) flag = cur.data.flagsIndex.fixed.flag; return flagsArr.includes(flag); }, isFBFX:function (treeNode) { return projectObj.project.Bills.isFBFX(treeNode); }, isTechMeasure:function(treeNode){ return projectObj.project.Bills.isTechMeasure(treeNode) }, canCalcToTalFeeByOwn: function (treeNode) { return !projectObj.project.Bills.cantCalcToTalFeeByOwn(treeNode); }, getChildrenFormulaNodes: function (self, allFormulaNodesArr, parentNodes){ // 获取结点parentNodes下有公式的子结点 let nodes = []; for (let pn of parentNodes){ for (let node of allFormulaNodesArr){ let cur = node; while (cur) { if (cur == pn && node != self){ nodes.push(node); break; } cur = cur.parent; }; }; }; return nodes; }, /* 重要说明: 此时得到的GLJList,每条glj都有tenderQuantity = glj的quantity * 定额的quantity * glj的消耗量调整系数coe。 与定额的tenderQuantity无关,与定额的子目工程量调整系数coe无关。例如: 例:定额AB0003,工程量5,下含工料机“建筑综合工”,消耗量0.202。调价:人材机单价系数4,人工系数3,子目工程量系数2。 则GLJList中的建筑综合工:quantity 1.01, tenderQuantity 3.03。marketPrice 115, tenderPrice 460。 */ getGLJList: function (treeNode, needOneBill) { delete treeNode.data.gljList; if (this.isRationCategory(treeNode)) { if (!calcTools.isVP_or_GLJR(treeNode)){ treeNode.data.gljList = projectObj.project.calcProgram.getGljArrByRation(treeNode.data); } } else if (this.isBill(treeNode)){ treeNode.data.gljList = projectObj.project.ration_glj.getGljArrByBill(treeNode, needOneBill); }; }, getLeafBills: function (treeNode){ let leafBills = []; function getBill(node){ if (!node) return; if (calcTools.isLeafBill(node)) leafBills.push(node); if (node.firstChild()) getBill(node.firstChild()); if (node.nextSibling) getBill(node.nextSibling); }; let fc = treeNode.firstChild(); if (fc) getBill(fc); return leafBills; }, forceSelect: function (treeNode, rowsCount = 1, colsCount = 2){ projectObj.mainController.tree.selected = treeNode; let idx = projectObj.project.mainTree.items.indexOf(treeNode); let sheet = projectObj.mainSpread.getActiveSheet(); sheet.setSelection(idx, 0, rowsCount, colsCount); sheet.showRow(idx, GC.Spread.Sheets.VerticalPosition.center); }, initFees: function (treeNode){ if (!treeNode.data.fees) { treeNode.data.fees = []; treeNode.data.feesIndex = {}; treeNode.changed = true; } else if (!treeNode.data.feesIndex){ treeNode.data.feesIndex = {}; for (let fee of treeNode.data.fees){ treeNode.data.feesIndex[fee.fieldName] = fee; }; treeNode.changed = true; }; }, initFeeField: function (treeNode, fieldName){ this.initFees(treeNode); if (!treeNode.data.feesIndex[fieldName]) { let fee = { 'fieldName': fieldName, 'unitFee': 0, 'totalFee': 0, 'tenderUnitFee': 0, 'tenderTotalFee': 0 }; treeNode.data.fees.push(fee); treeNode.data.feesIndex[fieldName] = fee; treeNode.changed = true; }; }, checkFeeField: function (treeNode, feeObj){ if (!feeObj) return; if (feeObj.fieldName == '') return; // 初始化前,先拦截属性末定义、又要给该属性赋0的情况 if (!treeNode.data.feesIndex || !treeNode.data.feesIndex[feeObj.fieldName]){ if (feeObj.unitFee == 0 && feeObj.totalFee == 0 && feeObj.tenderUnitFee == 0 && feeObj.tenderTotalFee == 0) return; } this.initFeeField(treeNode, feeObj.fieldName); if (treeNode.data.feesIndex[feeObj.fieldName].unitFee != feeObj.unitFee){ treeNode.data.feesIndex[feeObj.fieldName].unitFee = feeObj.unitFee; treeNode.changed = true; }; if (treeNode.data.feesIndex[feeObj.fieldName].totalFee != feeObj.totalFee){ treeNode.data.feesIndex[feeObj.fieldName].totalFee = feeObj.totalFee; treeNode.changed = true; }; // 不知在何种情况下,tenderUnitFee、tenderTotalFee的值会变成NaN,这里提前处理一下 if (isNaN(treeNode.data.feesIndex[feeObj.fieldName].tenderUnitFee)) treeNode.data.feesIndex[feeObj.fieldName].tenderUnitFee = undefined; if (isNaN(treeNode.data.feesIndex[feeObj.fieldName].tenderTotalFee)) treeNode.data.feesIndex[feeObj.fieldName].tenderTotalFee = undefined; if (treeNode.data.feesIndex[feeObj.fieldName].tenderUnitFee != feeObj.tenderUnitFee){ treeNode.data.feesIndex[feeObj.fieldName].tenderUnitFee = feeObj.tenderUnitFee; treeNode.changed = true; }; if (treeNode.data.feesIndex[feeObj.fieldName].tenderTotalFee != feeObj.tenderTotalFee){ treeNode.data.feesIndex[feeObj.fieldName].tenderTotalFee = feeObj.tenderTotalFee; treeNode.changed = true; }; }, setFieldValue: function (treeNode, fieldName, value){ if (fieldName.includes('feesIndex')){ let arr = fieldName.split('.'); this.initFeeField(treeNode, arr[1]); treeNode.data.feesIndex[arr[1]][arr[2]] = value; if(fieldName == 'feesIndex.common.unitFee'){ let fee = _.find(treeNode.data.fees,{'fieldName':arr[1]}); if(fee) fee[arr[2]] = value; } } else{ treeNode.data[fieldName] = value; }; treeNode.changed = true; }, initSummaryFee: function (treeNode){ if (!treeNode.data.summaryFees){ treeNode.data.summaryFees = { totalFee: 0, estimateFee: 0, safetyFee: 0, chargeFee: 0 }; treeNode.changed = true; }; }, hasAdjustPrice: function(){ // 编办是否有材料的调整价发文 if (projectObj.project.projSetting.glj_col && projectObj.project.projSetting.glj_col.showAdjustPrice == true) return true else return false; }, // 参数fieldName值: 'common.totalFee'、'equipment.unitFee' getFee: function (treeNode, fieldName) { if (!treeNode) return 0; let ns = fieldName.split("."); if (ns.length != 2) return 0 else if (treeNode.data.feesIndex && treeNode.data.feesIndex[ns[0]] && treeNode.data.feesIndex[ns[0]][ns[1]]) return parseFloat(treeNode.data.feesIndex[ns[0]][ns[1]]) else return 0; }, rationBaseFee: function (treeNode, gljTypes, priceType, isTender){ if (!treeNode.data.gljList) return 0; let me = this, result = 0; let price = 0, temp = 0, temp2 = 0; // 机械组成物价差: 机上人工、动力燃料 if ((priceType == priceTypes.ptDiffPrice) && (gljTypes.includes(gljType.MACHINE_LABOUR) || gljTypes.includes(gljType.FUEL_POWER_FEE))){ for (let glj of treeNode.data.gljList) { if ([gljType.GENERAL_MACHINE, gljType.INSTRUMENT].includes(glj.type)){ let mds = projectObj.project.composition.getCompositionByGLJ(glj); if (!mds) mds = []; for (let md of mds){ if (gljTypes.includes(md.type)){ let gljQ = isTender ? me.uiGLJQty(glj["tenderQuantity"]) : me.uiGLJQty(glj["quantity"]); let mdMP = isTender ? md["tenderPrice"] : md["marketPrice"]; let mdQ = me.uiGLJQty(md.consumption); let mdAP = calcTools.hasAdjustPrice() ? md["adjustPrice"] : md["basePrice"]; // if (aprice != mprice){ temp = (temp + (gljQ * mdQ * mdMP).toDecimal(decimalObj.process)).toDecimal(decimalObj.process); temp2 = (temp2 + (gljQ * mdQ * mdAP).toDecimal(decimalObj.process)).toDecimal(decimalObj.process); // } } } } } } // 普通基数计算(包括普通价差) else{ for (let glj of treeNode.data.gljList) { if (gljTypes.indexOf(glj.type) >= 0) { let qty = isTender ? me.uiGLJQty(glj["tenderQuantity"]) : me.uiGLJQty(glj["quantity"]); // let mprice = isTender ? me.uiGLJPrice(glj["tenderPrice"], glj) : me.uiGLJPrice(glj["marketPrice"], glj); // let aprice = calcTools.hasAdjustPrice() ? me.uiGLJPrice(glj["adjustPrice"], glj) : me.uiGLJPrice(glj["basePrice"], glj); let mprice = isTender ? glj["tenderPrice"] : glj["marketPrice"]; let aprice = calcTools.hasAdjustPrice() ? glj["adjustPrice"] : glj["basePrice"]; if (priceType == priceTypes.ptDiffPrice){ // if (aprice != mprice){ temp = (temp + (qty * mprice).toDecimal(decimalObj.process)).toDecimal(decimalObj.process); temp2 = (temp2 + (qty * aprice).toDecimal(decimalObj.process)).toDecimal(decimalObj.process); // } } else { // if (priceType == priceTypes.ptBasePrice){ price = me.uiGLJPrice(glj["basePrice"], glj);} if (priceType == priceTypes.ptBasePrice){ price = glj["basePrice"];} else if (priceType == priceTypes.ptAdjustPrice){price = aprice;} else if (priceType == priceTypes.ptMarketPrice){price = mprice;} /*if (projectObj.project.property.areaSetting && treeNode.data.areaIncreaseFee){ let p; if ([gljType.LABOUR].includes(glj.type)) p = projectObj.project.property.areaSetting.labour else if (baseMaterialTypes.includes(glj.type)) p = projectObj.project.property.areaSetting.material else if ([gljType.GENERAL_MACHINE].includes(glj.type)) p = projectObj.project.property.areaSetting.machine; qty = qty * (1 + p * 0.01).toDecimal(decimalObj.process); }*/ temp = (qty * price).toDecimal(decimalObj.process); result = (result + temp).toDecimal(decimalObj.process); }; }; }; }; if (priceType == priceTypes.ptDiffPrice){ if (typeof isCQ2018 != 'undefined'){ // 如下这一句十分重要!JS计算误差导致379.08-331.695=47.38499999999999。如果直接取2位会变成47.38。所以先取6位47.385,再取2位47.39。 result = (temp - temp2).toDecimal(decimalObj.process); result = result.toDecimal(decimalObj.ration.unitPrice) // 重庆2018所有都是先汇总相减后再取舍 } else{ if (gljTypes == baseMaterialTypes) result = (temp - temp2).toDecimal(decimalObj.ration.unitPrice) else result = (temp.toDecimal(decimalObj.ration.unitPrice) - temp2.toDecimal(decimalObj.ration.unitPrice)).toDecimal(decimalObj.ration.unitPrice); }; } else{ result = result.toDecimal(decimalObj.ration.unitPrice); }; return result; }, // masterTypeFilter 过滤机械机型:[]全部, [1,2]特大机械 [3,4]中小机械。 detailType 如机上人工费、机械折旧费等 machineDetailFee: function (treeNode, gljArr, masterTypeFilter, detailType, isTender) { if (!gljArr) return 0; let result = 0; for (let glj of gljArr) { if (baseMachineMasterTypes.includes(glj.type)){ // 机型不符 if ((masterTypeFilter.length > 0) && (glj.model && !masterTypeFilter.includes(glj.model))) continue; let gljQ = isTender ? glj.tenderQuantity : glj.quantity; // 获取机械组成物(调价不深入到组成物) let mds = projectObj.project.composition.getCompositionByGLJ(glj); if (!mds) mds = []; let mdSum = 0; for (let md of mds) { if (md.type == detailType) { let q = md["consumption"] ? md["consumption"] : 0; let p = md["basePrice"] ? md["basePrice"] : 0; mdSum = mdSum + (q * p).toDecimal(decimalObj.glj.unitPriceHasMix); mdSum = (mdSum).toDecimal(decimalObj.glj.unitPriceHasMix); } } if (typeof isCQ2018 != 'undefined') result = (result + (gljQ * mdSum).toDecimal(decimalObj.process)).toDecimal(decimalObj.process) else result = (result + (gljQ * mdSum).toDecimal(decimalObj.ration.unitPrice)).toDecimal(decimalObj.ration.unitPrice); } } result = (result).toDecimal(decimalObj.ration.unitPrice); return result; }, // 叶子清单、定额、总造价清单的暂估费。(父级清单是汇总子清单的暂估费,走计算程序逻辑,不在这里) estimateFee: function (treeNode, isBase, isTender){ // isBase, isTender 这两个参数用于基数计算 let me = this, sumU = 0, sumT = 0, sumTU = 0, sumTT = 0; let nodeQ = me.uiNodeQty(treeNode); let nodeTQ = me.uiNodeTenderQty(treeNode); let isGather = (projectObj.project.property.zanguCalcMode == zanguCalcType.gatherMaterial); // 先汇总数量,再乘市场价。如果是叶子清单,进入这里的gljList中的材料,已经是同类材料跨定额汇总过的了。 function eTFee(){ let rst = {eT: 0, eTT: 0}; if (!treeNode.data.gljList) return rst; let GLJObjs = []; for (let glj of treeNode.data.gljList) { if (!allMaterialTypes.includes(glj.type)) continue; if (glj.isEstimate){ let q = me.uiGLJQty((glj.totalQuantity)).toDecimal(decimalObj.process); GLJObjs.push({code: glj.code, name: glj.name, specs: glj.specs, unit: glj.unit, type: glj.type, quantity: q, marketPrice: glj.marketPrice, tenderQuantity: glj.tenderQuantity, tenderPrice: glj.tenderPrice}); } else{ // 组成物 if (!compositionTypes.includes(glj.type)) continue; let mds = projectObj.project.composition.getCompositionByGLJ(glj); if (!mds) mds = []; for (let md of mds){ if (!md.isEstimate) continue; let isExist = false; // let glj_totalQ = (nodeQ * me.uiGLJQty(glj.quantity)).toDecimal(decimalObj.glj.quantity); let glj_totalQ = me.uiGLJQty((glj.totalQuantity)).toDecimal(decimalObj.glj.quantity); let glj_tender_totalQ = (nodeTQ * me.uiGLJQty(glj.tenderQuantity)).toDecimal(decimalObj.glj.quantity); let mdQ = (glj_totalQ * me.uiGLJQty(md.consumption)).toDecimal(decimalObj.process); let mdTQ = (glj_tender_totalQ * me.uiGLJQty(md.consumption)).toDecimal(decimalObj.process); for (let obj of GLJObjs){ if (gljOprObj.getIndex(md, gljKeyArray) == gljOprObj.getIndex(obj, gljKeyArray)){ isExist = true; obj.quantity = (obj.quantity + mdQ).toDecimal(decimalObj.glj.quantity); obj.tenderQuantity = (obj.tenderQuantity + mdTQ).toDecimal(decimalObj.glj.quantity); break; } }; if (!isExist) GLJObjs.push({code: md.code, name: md.name, specs: md.specs, unit: md.unit, type: md.type, quantity: mdQ, marketPrice: md.marketPrice, tenderQuantity: mdTQ, tenderPrice: md.tenderPrice}); } } }; for (let obj of GLJObjs){ let t = (me.uiGLJQty(obj.quantity) * me.uiGLJPrice(obj.marketPrice, obj)).toDecimal(decimalObj.bills.totalPrice); rst.eT = (rst.eT + t).toDecimal(decimalObj.bills.totalPrice); let tt = (me.uiGLJQty(obj.tenderQuantity) * me.uiGLJPrice(obj.tenderPrice, obj)).toDecimal(decimalObj.bills.totalPrice); rst.eTT = (rst.eTT + tt).toDecimal(decimalObj.bills.totalPrice); }; return rst; }; // 汇总子结点的暂估合价 function eTFeeByChildren(){ let rst = {eT: 0, eTT: 0}; for (let node of treeNode.children){ if (node.data.feesIndex && node.data.feesIndex['estimate']) { rst.eT = (rst.eT + parseFloatPlus(node.data.feesIndex['estimate'].totalFee)).toDecimal(decimalObj.process); rst.eTT = (rst.eTT + parseFloatPlus(node.data.feesIndex['estimate'].tenderTotalFee)).toDecimal(decimalObj.process); }; }; rst.eT = (rst.eT).toDecimal(decimalObj.bills.totalPrice); rst.eTT = (rst.eTT).toDecimal(decimalObj.bills.totalPrice); return rst; }; // 先数量乘市场价,再汇总 function eUFee(){ if (!treeNode.data.gljList) return 0; let rst = {eU: 0, eTU: 0}; for (let glj of treeNode.data.gljList) { if (!allMaterialTypes.includes(glj.type)) continue; if (glj.isEstimate){ rst.eU = rst.eU + (me.uiGLJQty(glj.quantity) * me.uiGLJPrice(glj.marketPrice, glj)).toDecimal(decimalObj.process); rst.eU = rst.eU.toDecimal(decimalObj.process); // 不能直接用glj.tenderPrice,这个值不可靠。当调价界面删除单价系数后,tenderPrice没有实时计算,取得的值为0 rst.eTU = rst.eTU + (me.uiGLJQty(glj.tenderQuantity) * glj.tenderPrice).toDecimal(decimalObj.process); rst.eTU = rst.eTU.toDecimal(decimalObj.process); } else{ // 组成物。 if (!compositionTypes.includes(glj.type)) continue; let mds = projectObj.project.composition.getCompositionByGLJ(glj); if (!mds) mds = []; for (let md of mds){ if (!md.isEstimate) continue; let mdU = (me.uiGLJQty(md.consumption) * me.uiGLJPrice(md.marketPrice)).toDecimal(decimalObj.glj.unitPrice); rst.eU = rst.eU + (mdU * me.uiGLJQty(glj.quantity)).toDecimal(decimalObj.process); rst.eU = rst.eU.toDecimal(decimalObj.process); // 数量只调到工料机级别,工料机下的组成物不调量(如机械、混凝土)。调价调的是工料机下的组成物的价。 let mdTU = (me.uiGLJQty(md.consumption) * md.tenderPrice).toDecimal(decimalObj.glj.unitPrice); rst.eTU = rst.eTU + (mdTU * glj.tenderQuantity).toDecimal(decimalObj.process); rst.eTU = rst.eTU.toDecimal(decimalObj.process); } } }; rst.eU = rst.eU.toDecimal(decimalObj.bills.unitPrice); rst.eTU = rst.eTU.toDecimal(decimalObj.bills.unitPrice); return rst; }; // 总造价暂估费 if (me.isTotalCostBill(treeNode)){ let nodes = projectObj.project.mainTree.roots; for (let node of nodes){ if (me.isTotalCostBill(node)) break; let eU = 0, eT = 0, eTU = 0, eTT = 0; if (node.data.feesIndex && node.data.feesIndex.estimate){ eU = node.data.feesIndex.estimate.unitFee; eT = node.data.feesIndex.estimate.totalFee; eTU = node.data.feesIndex.estimate.tenderUnitFee; eTT = node.data.feesIndex.estimate.tenderTotalFee; } else { eU = 0, eT = 0, eTU = 0, eTT = 0; }; sumU = (sumU + parseFloatPlus(eU)).toDecimal(decimalObj.process); sumT = (sumT + parseFloatPlus(eT)).toDecimal(decimalObj.process); sumTU = (sumTU + parseFloatPlus(eTU)).toDecimal(decimalObj.process); sumTT = (sumTT + parseFloatPlus(eTT)).toDecimal(decimalObj.process); }; sumU = (sumU).toDecimal(decimalObj.bills.unitPrice); sumT = (sumT).toDecimal(decimalObj.bills.totalPrice); sumTU = (sumTU).toDecimal(decimalObj.bills.unitPrice); sumTT = (sumTT).toDecimal(decimalObj.bills.totalPrice); } else if (me.isParentBill(treeNode)){ // 父清单不要汇总单价。 let eTFBC = eTFeeByChildren(); sumT = eTFBC.eT; sumTT = eTFBC.eTT; sumU = undefined; sumTU = undefined; } else if (me.isLeafBill(treeNode)){ if (projectObj.project.Bills.isEngineerEst(treeNode)){ if (treeNode.data.feesIndex['common'] != undefined){ sumT = treeNode.data.feesIndex['common'].totalFee; sumU = treeNode.data.feesIndex['common'].unitFee; sumTT = treeNode.data.feesIndex['common'].tenderTotalFee; sumTU = treeNode.data.feesIndex['common'].tenderUnitFee; } } else{ if (isGather){ me.getGLJList(treeNode, false); let eTF = eTFee(); sumT = eTF.eT; sumTT = eTF.eTT; } else{ let eTFBC = eTFeeByChildren(); sumT = eTFBC.eT; sumTT = eTFBC.eTT; }; let q = nodeQ ? nodeQ : 1; sumU = (sumT / q).toDecimal(decimalObj.bills.totalPrice); let tq = nodeTQ ? nodeTQ : 1; sumTU = (sumTT / tq).toDecimal(decimalObj.bills.totalPrice); } } else if (me.isRationCategory(treeNode)){ me.getGLJList(treeNode, false); let eUF = eUFee(); sumU = eUF.eU; sumTU = eUF.eTU; if (isBase) { if (isTender) return sumTU else return sumU; }; if (isGather){ let eTF = eTFee(); sumT = eTF.eT; sumTT = eTF.eTT; } else{ sumT = (nodeQ * sumU).toDecimal(decimalObj.ration.totalPrice); sumTT = (nodeTQ * sumTU).toDecimal(decimalObj.ration.totalPrice); } }; me.checkFeeField(treeNode, {'fieldName': 'estimate', 'unitFee': sumU, 'totalFee': sumT, 'tenderUnitFee': sumTU, 'tenderTotalFee': sumTT}); }, marketPriceToBase: function (treeNode, baseName, isTender) { if (!calcTools.isVP_or_GLJR(treeNode)) return; let result = 0, me = this; function isRCJZC(treeNode, baseName) { // 基数名称中是否包含人材机主设,且树结点类型要匹配一致 let rst = (treeNode.data.subType === gljType.LABOUR && baseName.includes('人工')) || // 人工费、市场人工费 (baseMaterialTypes.includes(treeNode.data.subType) && baseName.includes('材料')) || // (treeNode.data.subType === gljType.GENERAL_MACHINE && (baseName.includes('机械') || baseName.includes('机具'))) || (baseMachineTypes.includes(treeNode.data.subType) && (baseName.includes('机械') || baseName.includes('机具'))) || (treeNode.data.subType === gljType.MAIN_MATERIAL && baseName.includes('主材')) || (treeNode.data.subType === gljType.EQUIPMENT && baseName.includes('设备')); return rst; } if (baseName.includes('甲供') || baseName.includes('甲定') || baseName.includes('分包')) { result = 0; } else if (baseName.includes('价差')) { if (treeNode.data.type == rationType.gljRation) { if (isRCJZC(treeNode, baseName)) { let aprice = me.uiGLJPrice(treeNode.data.basePrice); // 量价虚拟的工料机不可能有发文,这里直接取定额价。 let mprice = me.uiGLJPrice(treeNode.data.marketUnitFee); result = (mprice - aprice).toDecimal(decimalObj.ration.unitPrice); } } } else if (baseName.includes('机上人工')) { if (treeNode.data.subType === gljType.GENERAL_MACHINE) { let glj = { 'code': treeNode.data.code, 'name': treeNode.data.name, 'specs': treeNode.data.specs, 'unit': treeNode.data.unit, 'quantity': 1, 'type': treeNode.data.subType // 注意:这里要取subType }; result = me.machineDetailFee(treeNode, [glj], [], gljType.MACHINE_LABOUR, isTender); } } else { if (isRCJZC(treeNode, baseName)) { if (calcTools.isVolumePrice(treeNode)){ if (isTender){ let coe = this.tenderCoe_GLJPrice(); if (treeNode.data.marketUnitFee) result = (parseFloat(treeNode.data.marketUnitFee) * coe).toDecimal(decimalObj.ration.unitPrice) else result = 0; } else result = treeNode.data.marketUnitFee ? parseFloat(treeNode.data.marketUnitFee).toDecimal(decimalObj.ration.unitPrice) : 0 } else if (calcTools.isGljRation(treeNode)) { // result = treeNode.data.basePrice ? parseFloat(treeNode.data.basePrice).toDecimal(decimalObj.ration.unitPrice) : 0; // 这里要取基价或市场价,但不能直接取basePrice,受限于项目属性的三个选项。 if (baseName.includes('定额')) result = gljOprObj.getBasePrice(treeNode) else result = treeNode.data.marketUnitFee ? parseFloat(treeNode.data.marketUnitFee).toDecimal(decimalObj.ration.unitPrice) : gljOprObj.getBasePrice(treeNode); }; } }; return result; }, partASupplyFee: function (treeNode, baseName, isTender, isRationPirce = true) { // isRationPirce 为true时表示取定额价。如:甲供定额材料费 if (!treeNode.data.gljList) return 0; let projectGLJ = projectObj.project.projectGLJ; let supplyT = []; if (baseName.includes('甲供')) supplyT = [supplyType.BFJG, supplyType.WQJG] // 字段中存储的是汉字、数字混杂! else if (baseName.includes('甲定')) supplyT = [supplyType.JDYG]; let gljT = [], compT = []; if (baseName.includes('人工')) gljT = [gljType.LABOUR]; // 含人工的:如甲供人工、甲定人工 else if (baseName.includes('材料')){ gljT = baseMaterialTypes; compT = compositionTypes; } else if (baseName.includes('机械') || baseName.includes('机具')) { gljT = baseMachineTypes; compT = [gljType.GENERAL_MACHINE, gljType.INSTRUMENT, gljType.OTHER_MACHINE_USED]; // 取并集,兼容重庆2018新定额 } else if (baseName.includes('主材')) { gljT = [gljType.MAIN_MATERIAL]; compT = [gljType.MAIN_MATERIAL]; } else if (baseName.includes('设备')) { gljT = [gljType.EQUIPMENT]; }; // alert(JSON.stringify(projectGLJ.testGLJs())); let supplyGLJs = projectGLJ.getGLJsBySupply(supplyT, gljT); if (supplyGLJs.length == 0) return 0; let supplyGLJsIdx = {}; for (let sglj of supplyGLJs){supplyGLJsIdx[sglj.id] = sglj}; function isSupply(composition, supplies) { for (let supply of supplies){ if(supply.code == composition.code && supply.name == composition.name && supply.unit == composition.unit && supply.specs == composition.specs && supply.type == composition.type ){ composition.basePrice = supply.unit_price.base_price; composition.marketPrice = supply.unit_price.market_price; composition.supply = supply.supply; composition.supplyX = 1; if (composition.supply == supplyType.BFJG){ let Q = supply.quantity ? supply.quantity : 1; composition.supplyX = supply.supply_quantity / Q; } return true; } }; return false; }; let sum = 0; for (let glj of treeNode.data.gljList){ let gljQ = isTender ? glj.tenderQuantity : glj.quantity; let X = 1; // 部分甲供系数(默认1,即完全甲供) let tempSGLJ = supplyGLJsIdx[glj.projectGLJID]; // 当前材料是甲供材料:①普通 ②商品硂等不计组成物的母体材料 if (tempSGLJ) { // 处理部分甲供 if (baseName.includes('甲供') && (tempSGLJ.supply == supplyType.BFJG)){ let Q = isTender ? tempSGLJ.tenderQuantity : tempSGLJ.quantity; // let Q = tempSGLJ.quantity; Q = Q ? Q : 1; X = tempSGLJ.supply_quantity / Q; }; let gljP = isRationPirce ? glj.basePrice : (isTender ? projectGLJ.getTenderMarketPrice(calcTools.getProjectGLJ(glj)) : glj.marketPrice); sum = (sum + gljP * gljQ * X).toDecimal(decimalObj.process); } else{ // 当前材料不是甲供材料 // 混凝土等。组成物的母体,母体如果有组成物,则母体无法作为甲供材料,无法设置,此时要看其组成物是否是甲供材料;母体如果没有组成物,则母体有可能成为甲供材料。 if (compT.includes(glj.type)) { let pGLJ = projectGLJ.getDataByID(glj.projectGLJID); let compositions = pGLJ.ratio_data; // 组成物明细 if (compositions.length > 0){ for (let c of compositions){ if (isSupply(c, supplyGLJs)) { X = 1; if (baseName.includes('甲供') && (c.supply == supplyType.BFJG)){ X = c.supplyX; }; let cP = isRationPirce ? c.basePrice : (isTender ? projectGLJ.getTenderMarketPrice(calcTools.getProjectGLJ(c)) : c.marketPrice); sum = (sum + cP * c.consumption * gljQ * X).toDecimal(decimalObj.process); } } }; } }; }; sum = sum.toDecimal(decimalObj.ration.unitPrice); return sum; }, getCalcType: function (treeNode) { if (this.isRationCategory(treeNode)){ return treeNodeCalcType.ctRationCalcProgram; } else if (this.isNullBill(treeNode)){ return treeNodeCalcType.ctNull; } else if (this.isLeafBill(treeNode)) { if (treeNode.children && treeNode.children.length > 0){ // 清单单价计算模式下的叶子清单:取自己的计算程序ID,找到自己的计算程序计算。(汇总清单所有定额的工料机) if (projectObj.project.property.billsCalcMode === leafBillGetFeeType.billsPrice) return treeNodeCalcType.ctBillCalcProgram; else // 前三种计算模式下的叶子清单:汇总定额的计算程序的费用类别 return treeNodeCalcType.ctGatherRationsFees; } else{ // 公式计算 return treeNodeCalcType.ctCalcBaseValue; }; } else if (this.isBill(treeNode)) { // 父清单:汇总子清单的费用类别 return treeNodeCalcType.ctGatherBillsFees; } else { return treeNodeCalcType.ctRationCalcProgram; }; }, cutNodeForSave(treeNode){ let me = this; /* subType、quantity、calcBase、programID、marketUnitFee等等字段较为特殊,它们的改变一定会触发计算并导致计算 结果的变化,从而引发保存动作。将这些字段放在该位置跟计算结果一起保存,可减少前端跟后端的通讯频率。 */ let data = { projectID: projectObj.project.ID(), ID: treeNode.data.ID, unit: treeNode.data.unit, //对清单来说,改变单位,工程量精度会跟着改变从而影响计算。 subType: treeNode.data.subType, quantity: treeNode.data.quantity, calcBase: treeNode.data.calcBase, calcBaseValue: treeNode.data.calcBaseValue, tenderCalcBaseValue: treeNode.data.tenderCalcBaseValue, programID: treeNode.data.programID, marketUnitFee: treeNode.data.marketUnitFee, marketTotalFee: treeNode.data.marketTotalFee, fees: treeNode.data.fees, isFromDetail:treeNode.data.isFromDetail, feeRate: treeNode.data.feeRate, feeRateID: treeNode.data.feeRateID, contain: treeNode.data.contain, quantityEXP: treeNode.data.quantityEXP, summaryFees: treeNode.data.summaryFees, name:treeNode.data.name, rationQuantityCoe: treeNode.data.rationQuantityCoe, quantityCoe: treeNode.data.quantityCoe == null ? {} : treeNode.data.quantityCoe, targetUnitFee: treeNode.data.targetUnitFee, targetTotalFee: treeNode.data.targetTotalFee }; // 定额大类 if (me.isRationCategory(treeNode)) { data.isSubcontract = treeNode.data.isSubcontract; data.evaluationProject = treeNode.data.evaluationProject; //定额类型的工料机做特殊处理 if(me.isGljRation(treeNode)){ data.code = treeNode.data.code; data.projectGLJID = treeNode.data.projectGLJID; delete data.marketUnitFee; }; }; // 优化掉 undefined 属性 data = JSON.parse(JSON.stringify(data)); return data; }, uiNodeQty: function (treeNode){ return parseFloatPlus(treeNode.data.quantity).toDecimal(decimalObj.decimal("quantity", treeNode)); }, // 在项目工料机里检查该工料机是否参与调价 isTenderProjectGLJ: function (glj){ let projGLJ = this.getProjectGLJ(glj); return !(projGLJ && projGLJ.is_adjust_price == 1); }, // 取单价调价系数 tenderCoe_GLJPrice: function (){ let coe = 1; if (projectObj.project.property.tenderSetting && projectObj.project.property.tenderSetting.gljPriceTenderCoe){ coe = projectObj.project.property.tenderSetting.gljPriceTenderCoe; if (coe == '0') coe = 1; // 这里加个保护 }; return coe; }, tenderCoe_NodeQty: function (treeNode){ let coe = 1; if (treeNode.data.rationQuantityCoe){ coe = treeNode.data.rationQuantityCoe; if (coe == '0') coe = 1; // 这里加个保护 }; return coe; }, tenderCoe_GLJQty: function (treeNode, glj){ let coe = 1; if (!treeNode.data.quantityCoe) return coe; if (!calcTools.isTenderProjectGLJ(glj)) return coe; if (gljType.LABOUR == glj.type){ if (treeNode.data.quantityCoe.labour) coe = treeNode.data.quantityCoe.labour; } else if (baseMaterialTypes.indexOf(glj.type)){ if (treeNode.data.quantityCoe.material) coe = treeNode.data.quantityCoe.material; } else if (baseMachineTypes.indexOf(glj.type)){ if (treeNode.data.quantityCoe.machine) coe = treeNode.data.quantityCoe.machine; } else if (gljType.MAIN_MATERIAL == glj.type){ if (treeNode.data.quantityCoe.main) coe = treeNode.data.quantityCoe.main; } else if (gljType.EQUIPMENT == glj.type){ if (treeNode.data.quantityCoe.equipment) coe = treeNode.data.quantityCoe.equipment; }; if (coe == '0') coe = 1; // 这里加个保护 return coe; }, uiNodeTenderQty: function (treeNode){ return this.calcNodeTenderQty(treeNode); }, calcNodeTenderQty: function (treeNode){ if (this.isBill(treeNode)) // 清单只有一个工程量,没有调整后工程量。 return this.uiNodeQty(treeNode) else{ let qCoe = 1; /* 量价、工料机类型的定额,在反向调价之调整人材机消耗量系数时,因为他们没有工料机可调,调价结果没变,影响汇总后的父结点金额。 所以要特殊处理:此种情况下仍然要调量价的消耗量,即还是要像"子目工程量调整系数"方式那样调,但系数又不能在"子目工程量调整系数" 中显示出来(明明是调工料机,你却调到树结点上,这比较搞笑啊),所以可以改变tenderQuantity达到同样的效果以瞒天过海。所以在取系数时, 无论什么系数,只要能取到就算正确。 2020-04-05 注: 以上是老黄历。新思路是量价只作为定额调整,不作为工料机调整。两种反调模式下,统一都只调子目消耗量系数。 为避免歧义,量价的工料机调整系数不允许输入。 */ // if (calcTools.isVP_or_GLJR(treeNode)){ // if (treeNode.data.rationQuantityCoe) // qCoe = treeNode.data.rationQuantityCoe // else if (treeNode.data.quantityCoe && treeNode.data.quantityCoe.labour) // qCoe = treeNode.data.quantityCoe.labour; // } // else { if (treeNode.data.rationQuantityCoe) qCoe = treeNode.data.rationQuantityCoe; // }; if (qCoe == '0' || qCoe == 0) qCoe = 1; treeNode.data.tenderQuantity = (this.uiNodeQty(treeNode) * qCoe).toDecimal(decimalObj.decimal("quantity", treeNode)); return treeNode.data.tenderQuantity; } }, calcGLJTenderQty: function (treeNode, glj){ if (treeNode.data.quantityCoe == undefined){ glj.tenderQuantity = glj.quantity; } else{ let coe = 1; if (this.isTenderProjectGLJ(glj)) coe = this.tenderCoe_GLJQty(treeNode, glj); glj.tenderQuantity = (glj.quantity * coe).toDecimal(decimalObj.glj.quantity); } return glj.tenderQuantity; }, calcGLJTenderPrice: function (glj) { let projGLJ = calcTools.getProjectGLJ(glj); if (projGLJ == null){ // 量价定额虚拟出来的工料机,在项目工料机中查不到。 glj.tenderPrice = projectObj.project.projectGLJ.getTenderMarketPrice(projGLJ); }else{ let pCoe = 1; // 先从项目工料机里检查该工料机是否参与调价 if (projGLJ.is_adjust_price != 1) pCoe = this.tenderCoe_GLJPrice(); glj.tenderPrice = (glj.marketPrice * pCoe).toDecimal(decimalObj.glj.unitPrice); }; return glj.tenderPrice; }, // 界面显示的工料机价格,包括定额价、市场价等。参数 price 传入一个普通的价格数值即可。 uiGLJPrice: function (price, glj){ if (price){ let projGLJ = glj ? calcTools.getProjectGLJ(glj) : null; let d = (projGLJ && projGLJ.ratio_data.length > 0) ? decimalObj.glj.unitPriceHasMix : decimalObj.glj.unitPrice; return parseFloat(price).toDecimal(d); } else return 0; }, // 界面显示的工料机数量。参数 quantity 传入一个普通的数量数值即可。 uiGLJQty: function (quantity){ if (quantity) return parseFloat(quantity).toDecimal(decimalObj.glj.quantity) else return 0; }, hasTargetTotalFee: function (treeNode){ // targetTotalFee 有时为字符串“0”,此种情况会执行if 条件引起逻辑错误。所以这里封闭成方法直接调用。 return (treeNode.data.targetTotalFee && parseFloat(treeNode.data.targetTotalFee)); }, hasQuantity: function (treeNode){ return (treeNode.data.quantity && parseFloat(treeNode.data.quantity)); }, getRationsByProjectGLJ(PGLJID){ let rationIDs = []; let RGs = projectObj.project.ration_glj.datas; let PGLJIDs = []; if(Array.isArray(PGLJID)){//为了提高效率,改成支持传入数组 PGLJIDs = PGLJID }else { PGLJIDs = [PGLJID]; } for (let rg of RGs){ if (PGLJIDs.indexOf(rg.projectGLJID) !== -1){ rationIDs.push(rg.rationID); } }; let rationNodes = []; let nodes = projectObj.project.mainTree.nodes; for (let rID of rationIDs){ rationNodes.push(nodes['id_' + rID]); }; // 工料机形式的定额 let items = projectObj.project.mainTree.items; for (let item of items){ if (PGLJIDs.indexOf(item.data.projectGLJID) !== -1) rationNodes.push(item); }; return rationNodes; }, getNodesByProgramID(programID){ let discreteNodes = []; let nodes = projectObj.project.mainTree.items; for (let node of nodes){ if (node.data.programID == programID) discreteNodes.push(node); }; return discreteNodes; }, getProjectGLJ(glj){ if (glj.projectGLJID){ return projectObj.project.projectGLJ.getDataByID(glj.projectGLJID); } else return null; }, labourDays(node, isTender){ if (!node.data.gljList) return 0; let rst = 0; calcTools.uiNodeQty(node); for (let glj of node.data.gljList) { if (glj.type == gljType.LABOUR) { let gljQ = isTender ? glj.tenderQuantity : glj.quantity; gljQ = gljQ.toDecimal(decimalObj.glj.quantity); rst = (rst + gljQ).toDecimal(decimalObj.process); } }; return rst.toDecimal(decimalObj.glj.quantity); }, getProjectFeatureProperty(propertyKey){ for (let o of projectObj.project.property.projectFeature){ if (o.key == propertyKey){ return o.value; } }; }, getFeeRateByNode(node){ let decimal = getDecimal("feeRate"); if(node.data.feeRateID) { let r = projectObj.project.FeeRate.getFeeRateByID(node.data.feeRateID); if (r) return scMathUtil.roundForObj(r.rate, decimal); }; if (node.data.feeRate || node.data.feeRate == 0) return scMathUtil.roundForObj(node.data.feeRate,decimal); return 100; }, isEmptyObject(obj){ let arr = Object.keys(obj); return arr.length == 0; }, // 清单价格是否大于最高限价 unitFeeGTMaxPrice: function (node, feeField) { if (!this.isBill(node)) { return false; } const totalFee = this.getFee(node, feeField); const maxPrice = node.data.maxPrice; // 最高限价有值才对比 if (!commonUtil.isDef(maxPrice)) { return false; } return totalFee > maxPrice; }, getTenderCalcType: function () { let tenderSetting = projectObj.project.property.tenderSetting; let ct = tenderSetting && tenderSetting.calcPriceOption? tenderSetting.calcPriceOption : "coeBase"; if (ct == 'priceBase') ct = 'priceBase_RCJ'; // 兼容旧项目 return ct; } }; let rationCalcBases = { '定额基价人工费': function (node, isTender) { return calcTools.rationBaseFee(node, [gljType.LABOUR], priceTypes.ptBasePrice, isTender); }, '定额基价材料费': function (node, isTender) { return calcTools.rationBaseFee(node, baseMaterialTypes, priceTypes.ptBasePrice, isTender); }, '定额基价机械费': function (node, isTender) { return calcTools.rationBaseFee(node, [gljType.GENERAL_MACHINE], priceTypes.ptBasePrice, isTender); }, '定额基价机上人工费': function (node, isTender) { return calcTools.machineDetailFee(node, node.data.gljList, [], gljType.MACHINE_LABOUR, isTender); }, '人工费价差': function (node, isTender) { return calcTools.rationBaseFee(node, [gljType.LABOUR], priceTypes.ptDiffPrice, isTender); }, '材料费价差': function (node, isTender) { return calcTools.rationBaseFee(node, baseMaterialTypes, priceTypes.ptDiffPrice, isTender); }, '机械费价差': function (node, isTender) { return calcTools.rationBaseFee(node, [gljType.GENERAL_MACHINE], priceTypes.ptDiffPrice, isTender); }, '主材费价差': function (node, isTender) { return calcTools.rationBaseFee(node, [gljType.MAIN_MATERIAL], priceTypes.ptDiffPrice, isTender); }, '设备费价差': function (node, isTender) { return calcTools.rationBaseFee(node, [gljType.EQUIPMENT], priceTypes.ptDiffPrice, isTender); }, '主材费': function (node, isTender) { return calcTools.rationBaseFee(node, [gljType.MAIN_MATERIAL], priceTypes.ptBasePrice, isTender); }, '设备费': function (node, isTender) { return calcTools.rationBaseFee(node, [gljType.EQUIPMENT], priceTypes.ptBasePrice, isTender); }, '人工工日': function (node, isTender) { return calcTools.labourDays(node, isTender); }, '甲供定额基价人工费': function (node, isTender) { return calcTools.partASupplyFee(node, '甲供定额基价人工费', isTender, true); }, '甲供定额基价材料费': function (node, isTender) { return calcTools.partASupplyFee(node, '甲供定额基价材料费', isTender, true); }, '甲供定额基价机械费': function (node, isTender) { return calcTools.partASupplyFee(node, '甲供定额基价机械费', isTender, true); }, '甲供主材费': function (node, isTender) { return calcTools.partASupplyFee(node, '甲供主材费', isTender, false); }, '甲供设备费': function (node, isTender) { return calcTools.partASupplyFee(node, '甲供设备费', isTender, false); }, '甲定定额基价人工费': function (node, isTender) { return calcTools.partASupplyFee(node, '甲定定额基价人工费', isTender, true); }, '甲定定额基价材料费': function (node, isTender) { return calcTools.partASupplyFee(node, '甲定定额基价材料费', isTender, true); }, '甲定定额基价机械费': function (node, isTender) { return calcTools.partASupplyFee(node, '甲定定额基价机械费', isTender, true); }, '甲定主材费': function (node, isTender) { return calcTools.partASupplyFee(node, '甲定主材费', isTender, false); }, '甲定设备费': function (node, isTender) { return calcTools.partASupplyFee(node, '甲定设备费', isTender, false); }, '暂估材料费': function (node, isTender) { return calcTools.estimateFee(node, true, isTender); }, '分包定额基价人工费': function (node, isTender) { if (node.data.isSubcontract) return calcTools.rationBaseFee(node, [gljType.LABOUR], priceTypes.ptBasePrice, isTender) else return 0; }, '分包定额基价材料费': function (node, isTender) { if (node.data.isSubcontract) return calcTools.rationBaseFee(node, baseMaterialTypes, priceTypes.ptBasePrice, isTender) else return 0; }, '分包定额基价机械费': function (node, isTender) { if (node.data.isSubcontract) return calcTools.rationBaseFee(node, [gljType.GENERAL_MACHINE], priceTypes.ptBasePrice, isTender) else return 0; }, '分包主材费': function (node, isTender) { if (node.data.isSubcontract) return calcTools.rationBaseFee(node, [gljType.MAIN_MATERIAL], priceTypes.ptBasePrice, isTender) else return 0; }, '分包设备费': function (node, isTender) { if (node.data.isSubcontract) return calcTools.rationBaseFee(node, [gljType.EQUIPMENT], priceTypes.ptBasePrice, isTender) else return 0; }, '分包人工工日': function (node, isTender) { if (node.data.isSubcontract) return calcTools.labourDays(node, isTender) else return 0; } }; let analyzer = { error: '', standard: function(expr){ let str = expr; str = str.replace(/\s/g, ""); // 去空格、去中文空格 str = str.replace(/(/g, "("); // 中文括号"("换成英文括号"(" str = str.replace(/)/g, ")"); // 中文括号")"换成英文括号")" str = str.replace(/f/g, "F"); // f换成F str = str.replace(/l/g, "L"); // l换成L return str; }, getFArr: function (expr) { let pattF = new RegExp(/F\d+/gi); let arrF = expr.match(pattF); return arrF ? arrF : []; }, getAtIDArr: function (expr) { // ['@1','@2'] let patt = new RegExp(/@\d+/gi); let arr = expr.match(patt); return arr ? arr : []; }, getBaseArr: function (expr) { let pattBase = new RegExp(/\[[\u4E00-\u9FA5]+\]/gi); let arrBase = expr.match(pattBase); return arrBase ? arrBase : []; }, getID: function (FName, template) { // F13 → 4 let idx = FName.slice(1) - 1; let id = template.calcItems[idx] ? template.calcItems[idx].ID : null; return id; }, getFName: function (atID, template) { // @3 → F7 for (var i = 0; i < template.calcItems.length; i++) { if (template.calcItems[i].ID == atID.slice(1)) { return 'F'+ (i + 1); } } return null; }, getItem: function (FName, template){ // F3 → calcItems[2] → {Object} let idx = FName.slice(1) - 1; return template.calcItems[idx]; }, isCycleCalc: function (expression, ID, template) { // 这里判断expression,是ID引用: @5+@6 let atID = `@${ID}`; // 避免部分匹配,如:@10匹配@1 let idx = expression.indexOf(atID); if (idx >= 0){ let nextPos = idx + atID.length; if (nextPos >= expression.length) return true; let char = expression.charAt(nextPos); if (!char.isNumberStr()) return true; }; let atIDs = analyzer.getAtIDArr(expression); for (let atID of atIDs) { let item = template.compiledCalcItems[atID.slice(1)]; if (analyzer.isCycleCalc(item.expression, ID, template)) return true; } return false; }, isLegal: function (dispExpr, itemID, template) { // 检测包括:无效字符、基数是否中括号、基数是否定义、行引用、循环计算 function testValue(expr){ try { expr = expr.replace(/\[[\u4E00-\u9FA5]+\]/gi, '0'); expr = expr.replace(/@\d+/gi, '0'); expr = expr.replace(/L/gi, '0'); expr = expr.replace(/%/gi, '*0.01'); if (expr.includes('00')) return false; eval(expr); return true; } catch (err) { return false; } }; let me = analyzer; let expr = me.standard(dispExpr); let invalidChars = /[^0-9\u4e00-\u9fa5\+\-\*\/\(\)\.\[\]FL%]/g; if (invalidChars.test(expr)){ analyzer.error = `表达式中含有${hintBox.font('无效字符')}!`; // hintBox.infoBox('错误提示',`表达式中含有${hintBox.font('无效字符')}!`,1); return false; }; let i = expr.search(/\df/ig); // 4F3 let j = expr.search(/\d\[/ig); // 4[定额基价人工费] if (i > -1 || j > -1){ analyzer.error = `表达式中${hintBox.font('缺少运算符')}!`; // hintBox.infoBox('错误提示', `表达式中${hintBox.font('缺少运算符')}!`, 1); return false; } let pattCn = new RegExp(/[\u4E00-\u9FA5]+/gi); let arrCn = expr.match(pattCn); let pattBase = new RegExp(/\[[\u4E00-\u9FA5]+\]/gi); let arrBase = expr.match(pattBase); if (arrCn && !arrBase){ analyzer.error = `定额基数必须用中括号${hintBox.font('[]')}括起来!`; // hintBox.infoBox('错误提示', `定额基数必须用中括号${hintBox.font('[]')}括起来!`, 1); return false; }; if (arrCn && arrBase && (arrCn.length != arrBase.length)){ for (let cn of arrCn){ let tempBase = `[${cn}]`; if (!arrBase.includes(tempBase)){ analyzer.error = `定额基数“${hintBox.font(cn)}”必须用中括号${hintBox.font('[]')}括起来!`; // hintBox.infoBox('错误提示', `定额基数“${hintBox.font(cn)}”必须用中括号${hintBox.font('[]')}括起来!`, 1); return false; } }; // 这里要加一个保险。因为上面的 for 循环在“主材费 + [主材费]” 情况下有Bug analyzer.error =`定额基数必须用中括号${hintBox.font('[]')}括起来!`; // hintBox.infoBox('错误提示', `定额基数必须用中括号${hintBox.font('[]')}括起来!`, 1); return false; }; if (arrBase){ for (let base of arrBase){ let baseName = base.slice(1, -1); if (!rationCalcBases[baseName]){ analyzer.error = `定额基数${hintBox.font('[' +baseName + ']')}未定义!`; // hintBox.infoBox('错误提示', `定额基数${hintBox.font('[' +baseName + ']')}未定义!`, 1); return false; } }; }; let arrF = me.getFArr(expr); for (let F of arrF){ let num = F.slice(1); if (num > template.calcItems.length){ let s = hintBox.font('F'+num); analyzer.error = `表达式中 “${hintBox.font(s)}” 行号引用错误!`; // hintBox.infoBox('错误提示', `表达式中 “${hintBox.font(s)}” 行号引用错误!`, 1); return false; }; }; let expression = me.getExpression(expr, template); if (me.isCycleCalc(expression, itemID, template)){ analyzer.error = `表达式中有${hintBox.font('循环计算')}!`; // hintBox.infoBox('错误提示', `表达式中有${hintBox.font('循环计算')}!`, 1); return false; }; if (!testValue(expression)){ analyzer.error = `表达式中有${hintBox.font('语法错误')}!`; // hintBox.infoBox('错误提示', `表达式中有${hintBox.font('语法错误')}!`, 1); return false; }; return true; // 表达式合法 }, refIDToLines: function (expression, template) { // @2、@3 → F7、F8 let rst = expression; let atIDArr = analyzer.getAtIDArr(rst); let fArr = []; for (let atID of atIDArr) { let FN = analyzer.getFName(atID, template); fArr.push(FN); }; for (let i = 0; i < atIDArr.length; i++) { let patt = new RegExp(atIDArr[i]); let val = fArr[i]; rst = rst.replace(patt, val); }; return rst; }, refLineToIDs: function (dispExpr, template) { // F7、F8 → @2、@3 let rst = analyzer.standard(dispExpr); let fArr = analyzer.getFArr(rst); let IDArr = []; for (let F of fArr) { let ID = analyzer.getID(F, template); IDArr.push(ID); }; for (let i = 0; i < fArr.length; i++) { let patt = new RegExp(fArr[i]); let val = `@${IDArr[i]}`; rst = rst.replace(patt, val); }; return rst; }, getDispExpr: function (expression, template) { return analyzer.refIDToLines(expression, template); }, getExpression: function (dispExpr, template) { return analyzer.refLineToIDs(dispExpr, template); }, getDispExprUser: function (dispExpr, labourCoe) { // labourCoe 是 str 类型 let rst = analyzer.standard(dispExpr); rst = rst.replace(/L/g, labourCoe); return rst; }, getCompiledExpr: function (expression, labourCoe) { // labourCoe 是 str 类型 let rst = expression; let atIDArr = analyzer.getAtIDArr(rst); let IDArr = atIDArr.map(function (atID) { return atID.slice(1); }); for (var i = 0; i < atIDArr.length; i++) { let patt = new RegExp(atIDArr[i]); let val = `$CE.at(${IDArr[i]},false)`; rst = rst.replace(patt, val); }; rst = rst.replace(/%/g, "*0.01"); rst = rst.replace(/\[/g, "$CE.base('"); rst = rst.replace(/\]/g, "',false)"); rst = rst.replace(/L/g, labourCoe); return rst; }, getCompiledTenderExpr: function (compiledExpr) { let rst = compiledExpr.replace(/false/g, "true"); return rst; }, getStatement: function (expression, template) { let rst = expression; let atIDArr = analyzer.getAtIDArr(rst); let IDArr = atIDArr.map(function (atID) { return atID.slice(1); }); for (var i = 0; i < atIDArr.length; i++) { let patt = new RegExp(atIDArr[i]); let val = projectObj.project.calcProgram.compiledTemplates[template.ID].compiledCalcItems[IDArr[i]].name; rst = rst.replace(patt, val); }; rst = rst.replace(/\[/g, ""); rst = rst.replace(/\]/g, ""); rst = rst.replace(/L/g, '人工系数'); return rst; }, calcItemMaxID: function(template){ let MaxID = 0; for (let item of template.calcItems){ if (item.ID > MaxID) MaxID = item.ID; }; return MaxID; }, calcItemIsUsed: function(template, calcItem){ let atID = '@' + calcItem.ID; for (var i = 0; i < template.calcItems.length; i++) { let item = template.calcItems[i]; let atIDArr = analyzer.getAtIDArr(item.expression); if (atIDArr.indexOf(atID) >= 0){ calcItem.tempUsed = i; return true; } } return false; }, refreshUsedCalcItemsStatement: function(template, calcItem){ let atID = '@' + calcItem.ID; for (var i = 0; i < template.calcItems.length; i++) { let item = template.calcItems[i]; let atIDArr = analyzer.getAtIDArr(item.expression); if (atIDArr.indexOf(atID) >= 0){ item.statement = analyzer.getStatement(item.expression, template); }; } }, calcItemLabourCoe: function(calcItem){ let lc = 0; if (calcItem.labourCoeID) lc = projectObj.project.calcProgram.compiledLabourCoes[calcItem.labourCoeID].coe.toString(); return lc; }, templateRefresh: function(template){ for (let item of template){ item.dispExpr = analyzer.getDispExpr(item.expression, template); item.dispExprUser = analyzer.getDispExprUser(item.dispExpr, me.calcItemLabourCoe(item)); }; }, templateMaxID: function(){ let ts = projectObj.project.calcProgram.templates; let MaxID = 0; for (let t of ts){ if (t.ID > MaxID) MaxID = t.ID; }; return MaxID; }, templateNewName: function (name) { let i = 1; while (projectObj.project.calcProgram.compiledTemplateMaps[name + i]) { i++; }; return name + i; }, templateNameIsExist: function (name) { if (projectObj.project.calcProgram.compiledTemplateMaps[name]) return true else return false; }, templateIsUsed: function (ID){ let nodes = projectObj.project.mainTree.items; for (let node of nodes){ if (node.data && node.data.programID && node.data.programID == ID) { return true; } }; return false; }, fieldNameIsUsed: function(template, fieldName){ let fieldNameEn = projectObj.project.calcProgram.compiledFeeTypeMaps[fieldName]; for (var i = 0; i < template.calcItems.length; i++) { if (template.calcItems[i].fieldName == fieldNameEn){ template.fieldNameTempUsed = i; return true; } } return false; } }; let executeObj = { treeNode: null, template: null, tempCalcItem: null, at: function(ID, isTender) { let item = executeObj.template.compiledCalcItems[ID]; let rst = isTender ? item.tenderUnitFee : item.unitFee; rst = parseFloat(rst); return rst; }, base: function(baseName, isTender) { let me = executeObj; // 量价、工料机形式的定额, 要把自己的市场单价用于计算程序中的基数。 if (calcTools.isVP_or_GLJR(me.treeNode)) return calcTools.marketPriceToBase(me.treeNode, baseName, isTender) else{ if (!rationCalcBases[baseName]){ hintBox.infoBox('系统提示', '定额基数“' + baseName + '”未定义,计算错误。 (模板 ' + me.template.ID + ',规则 ' + me.tempCalcItem.ID +')', 1); return 0; } else return rationCalcBases[baseName](me.treeNode, isTender); } }, HJ: function () { let me = this; let p = me.treeNode.data.calcBaseValue ? me.treeNode.data.calcBaseValue : 0; let q = calcTools.uiNodeQty(me.treeNode) ? calcTools.uiNodeQty(me.treeNode) : 1; let u = (p / q).toDecimal(decimalObj.decimal('unitPrice', me.treeNode)); return u; } }; class CalcProgram { constructor(project){ let me = this; me.project = project; me.datas = []; me.rationMap = null;//定额 - 工料机映射临时变量 me.pgljMap = null; project.registerModule(ModuleNames.calc_program, me); }; // 兼容Project框架方法 getSourceType () { return ModuleNames.calc_program; }; // 兼容Project框架方法 // isInit:是否初始化,进入单位工程为true,导出接口为false,不需要存储费率临时数据 loadData (datas, isInit) { this.datas = datas; this.compileAllTemps(isInit); }; // 兼容Project框架方法 doAfterUpdate (err, data) { if(!err){ $.bootstrapLoading.end(); } }; // 经测试,全部编译一次耗时0.003~0.004秒。耗时基本忽略不计。 compileAllTemps(isInit = false){ let me = this; me.compiledFeeRates = {}; me.compiledLabourCoes = {}; me.compiledTemplates = {}; me.compiledTemplateMaps = {}; me.compiledTemplateNames = []; me.compiledFeeTypeMaps = {}; me.compiledFeeTypeNames = []; me.compiledCalcBases = {}; me.feeRates = this.project.FeeRate.datas.rates; me.labourCoes = this.project.labourCoe.datas.coes; me.feeTypes = cpFeeTypes; // me.calcBases = rationCalcBase; me.templates = this.project.calcProgram.datas.templates; // me.templates.push(defaultBillTemplate); // 先编译公用的基础数据 me.compilePublics(); me.compileTemplateMaps(); for (let t of me.templates){ me.compileTemplate(t); }; }; compilePublics(){ let me = this; for (let rate of me.feeRates) { me.compiledFeeRates[rate.ID] = rate; } for (let coe of me.labourCoes) { me.compiledLabourCoes[coe.ID] = coe; } for (let ft of me.feeTypes) { me.compiledFeeTypeMaps[ft.type] = ft.name; me.compiledFeeTypeMaps[ft.name] = ft.type; // 中文预编译,可靠性有待验证 me.compiledFeeTypeNames.push(ft.name); } /* for (let cb of me.calcBases) { me.compiledCalcBases[cb.dispName] = cb; // 中文预编译,可靠性有待验证 }*/ }; compileTemplateMaps(){ let me = this; function clearObj(obj){ for (let key in obj) { delete obj[key]; } }; clearObj(me.compiledTemplates); clearObj(me.compiledTemplateMaps); me.compiledTemplateNames.splice(0, me.compiledTemplateNames.length); for (let t of me.templates){ me.compiledTemplates[t.ID] = t; me.compiledTemplateMaps[t.ID] = t.name; me.compiledTemplateMaps[t.name] = t.ID; me.compiledTemplateNames.push(t.name); }; }; compileTemplate(template){ let me = this; // me.compiledTemplates[template.ID] = template; // me.compiledTemplateMaps[template.ID] = template.name; // me.compiledTemplateMaps[template.name] = template.ID; // me.compiledTemplateNames.push(template.name); template.hasCompiled = false; template.errs = []; let private_extract_ID = function(str, idx){ let subStr = str.slice(idx); let patt = new RegExp(/@\d+/); let atID = subStr.match(patt); let ID = atID ? atID[0].slice(1) : null; return ID; }; let private_parse_ref = function(item, itemIdx){ let idx = item.expression.indexOf('@', 0); while (idx >= 0) { let ID = private_extract_ID(item.expression, idx); let len = ID ? ID.toString().length : 0; if (len) { let subItem = template.compiledCalcItems[ID]; if (subItem) { if (subItem.ID !== item.ID) { private_parse_ref(subItem, template.compiledCalcItems[ID + "_idx"]); } else { template.errs.push("循环引用ID: " + ID); } } else { template.errs.push("找不到ID: " + ID); console.log('找不到ID: ' + ID); } } idx = item.expression.indexOf('@', idx + len + 1); } if (template.compiledSeq.indexOf(itemIdx) < 0) { template.compiledSeq.push(itemIdx); } }; let private_compile_items = function() { for (let idx of template.compiledSeq) { let item = template.calcItems[idx]; let lc = analyzer.calcItemLabourCoe(item); // 用于界面显示。disExpr是公式模板,不允许修改:人工系数占位符被修改后变成数值,第二次无法正确替换。 item.dispExprUser = analyzer.getDispExprUser(item.dispExpr, lc); if (item.expression == 'HJ') item.compiledExpr = '$CE.HJ()' else item.compiledExpr = analyzer.getCompiledExpr(item.expression, lc); if (item.feeRateID) { let orgFeeRate = item.feeRate; let cfr = me.compiledFeeRates[item.feeRateID]; item.feeRate = cfr ? cfr.rate : 100; // if (!orgFeeRate || (orgFeeRate && orgFeeRate != item.feeRate)){ // me.saveForReports.push({templatesID: template.ID, calcItem: item}); // } }; // 字段名映射 item.displayFieldName = me.compiledFeeTypeMaps[item.fieldName]; } }; if (template && template.calcItems && template.calcItems.length > 0) { template.compiledSeq = []; template.compiledCalcItems = {}; for (let i = 0; i < template.calcItems.length; i++) { let item = template.calcItems[i]; item.dispExpr = analyzer.getDispExpr(item.expression, template); template.compiledCalcItems[item.ID] = item; template.compiledCalcItems[item.ID + "_idx"] = i; } for (let i = 0; i < template.calcItems.length; i++) { let item = template.calcItems[i]; if (template.compiledSeq.indexOf(i) < 0) { private_parse_ref(item, i); } } if (template.errs.length == 0) { private_compile_items(); template.hasCompiled = true; } else { console.log('errors: ' + template.errs.toString()); } }; }; // 删掉多余的费用。例如:①切换取费类别 ②从其它计算方式(有很多费)切换到公式计算方式(只需要common费),多出来的费要删除。 // 如果指定了保留字段,则按用户指定的来。如果没指定保留字段,则按默认的来:总造价清单只留common, estimate两个费用类别。其它公式清单只留common。 deleteUselessFees(treeNode, fieldNameArr){ if (!(treeNode.data.fees && treeNode.data.fees.length > 0)) return; let keeps = fieldNameArr ? fieldNameArr : []; // 这两个默认是要保留的 if (!keeps.includes('common')) keeps.push('common'); if (!keeps.includes('estimate')) keeps.push('estimate'); for (let i = 0; i < treeNode.data.fees.length; i++) { if (!keeps.includes(treeNode.data.fees[i].fieldName)) { delete treeNode.data.feesIndex[treeNode.data.fees[i].fieldName]; treeNode.data.fees.splice(i, 1); treeNode.changed = true; } }; }; // 不能直接删除该属性,否则无法冲掉库中已存储的值。下同。 deleteProperties (treeNode, propNamesArr){ for (let pn of propNamesArr){ if (treeNode.data[pn]){ treeNode.data[pn] = null; treeNode.changed = true; } }; }; // 只计算treeNode自身。changedArr: 外部传来的一个数组,专门存储发生变动的节点。 innerCalc(treeNode, changedArr, tenderType){ if (treeNode.sourceType === ModuleNames.ration_glj) return; // 仅用作树节点显示的工料机不能参与计算。 let me = this; //设置定额工料机映射表 me.setRationMap(); treeNode.calcType = calcTools.getCalcType(treeNode); // 父清单汇总子清单的费用类别 if (treeNode.calcType == treeNodeCalcType.ctGatherBillsFees) me.innerCalcBill(treeNode, 2) // 叶子清单汇总定额的费用类别 else if (treeNode.calcType == treeNodeCalcType.ctGatherRationsFees) me.innerCalcBill(treeNode, 1) // 叶子清单:公式计算 else if (treeNode.calcType == treeNodeCalcType.ctCalcBaseValue) me.innerCalcBillExpr(treeNode) // 叶子清单:手工修改单价或金额(无定额、无公式计算,什么都没有时)。 else if (treeNode.calcType == treeNodeCalcType.ctNull) me.innerCalcBillCustom(treeNode) // 定额:计算程序 else me.innerCalcRation(treeNode, tenderType); if (!calcTools.isTotalCostBill(treeNode)) // 已在上面的分支中计算过 calcTools.estimateFee(treeNode); if (treeNode.changed && !changedArr.includes(treeNode)) changedArr.push(treeNode); }; // 清单部分抽取出来,供分摊清单公用。commonCalcType:1 叶子清单汇总定额的费用类别; 2 父清单汇总子清单的费用类别。3: 分摊:叶子清单汇总定额的费用类别。 innerCalcBill(treeNode, commonCalcType, tender = tenderTypes.ttCalc){ let me = this; treeNode.data.programID = null; calcTools.initFees(treeNode); let nodes = []; if (commonCalcType == 1){ calcTools.getGLJList(treeNode, true); nodes = me.project.Ration.getRationNodes(treeNode); } else if (commonCalcType == 2) nodes = treeNode.children else if (commonCalcType == 3) nodes = treeNode.children; function isBaseFeeType(type){ return ['labour', 'material', 'machine', 'mainMaterial', 'equipment'].indexOf(type) > -1; }; let rst = []; for (let ft of cpFeeTypes) { let ftObj = {}; ftObj.fieldName = ft.type; ftObj.name = ft.name; let buf = 0, btf = 0, btuf = 0, bttf = 0; let nQ = calcTools.uiNodeQty(treeNode); let nTQ = calcTools.uiNodeTenderQty(treeNode); let bq = nQ ? nQ : 1; let btq = nTQ ? nTQ : 1; if (commonCalcType == 2){ for (let node of nodes) { if (node.data.feesIndex && node.data.feesIndex[ft.type]) { btf = (btf + parseFloatPlus(node.data.feesIndex[ft.type].totalFee)).toDecimal(decimalObj.process); bttf = (bttf + parseFloatPlus(node.data.feesIndex[ft.type].tenderTotalFee)).toDecimal(decimalObj.process); }; }; } else if ((commonCalcType == 1) || (commonCalcType == 3)){ let sum_rtf = 0, sum_rttf = 0; for (let node of nodes) { let ruf = 0, rtuf = 0, rtf = 0, rttf = 0; if (node.data.feesIndex && node.data.feesIndex[ft.type]) { ruf = parseFloatPlus(node.data.feesIndex[ft.type].unitFee).toDecimal(decimalObj.bills.unitPrice); rtuf = parseFloatPlus(node.data.feesIndex[ft.type].tenderUnitFee).toDecimal(decimalObj.bills.unitPrice); rtf = parseFloatPlus(node.data.feesIndex[ft.type].totalFee).toDecimal(decimalObj.bills.totalPrice); rttf = parseFloatPlus(node.data.feesIndex[ft.type].tenderTotalFee).toDecimal(decimalObj.bills.totalPrice); }; // 取费方式为子目含量,清单行/列的XX单价应 =ROUND( ∑ROUND(定额XX单价*含量,清单单价精度),清单单价精度) if (me.project.property.billsCalcMode === leafBillGetFeeType.rationContent) { buf = (buf + (ruf * parseFloatPlus(node.data.contain)).toDecimal(decimalObj.bills.unitPrice)).toDecimal(decimalObj.process); node.data.tenderContaion = (node.data.tenderQuantity / bq).toDecimal(decimalObj.process); btuf = (btuf + (rtuf * parseFloatPlus(node.data.tenderContaion)).toDecimal(decimalObj.bills.unitPrice)).toDecimal(decimalObj.process); }; sum_rtf = (sum_rtf + rtf).toDecimal(decimalObj.process); sum_rttf = (sum_rttf + rttf).toDecimal(decimalObj.process); }; if (me.project.property.billsCalcMode == leafBillGetFeeType.rationPriceConverse || me.project.property.billsCalcMode == leafBillGetFeeType.rationPrice) { buf = (sum_rtf / bq).toDecimal(decimalObj.process); btuf = (sum_rttf / btq).toDecimal(decimalObj.process); }; if (isBaseFeeType(ft.type) || (me.project.property.billsCalcMode === leafBillGetFeeType.rationPrice && ft.type == "common")){ btf = sum_rtf; bttf = sum_rttf; } else{ btf = (buf.toDecimal(decimalObj.bills.unitPrice) * bq).toDecimal(decimalObj.process); bttf = (btuf.toDecimal(decimalObj.bills.unitPrice) * btq).toDecimal(decimalObj.process); }; }; ftObj.totalFee = btf.toDecimal(decimalObj.bills.totalPrice); ftObj.tenderTotalFee = bttf.toDecimal(decimalObj.bills.totalPrice); ftObj.unitFee = buf.toDecimal(decimalObj.bills.unitPrice); ftObj.tenderUnitFee = btuf.toDecimal(decimalObj.bills.unitPrice); calcTools.checkFeeField(treeNode, ftObj); rst.push(ftObj); }; treeNode.data.calcTemplate = {"calcItems": rst}; }; innerCalcBillExpr(treeNode){ delete treeNode.data.gljList; let me = this; me.deleteProperties(treeNode, ['programID']); me.deleteUselessFees(treeNode, ['common', 'rationCommon']); let nQ = calcTools.uiNodeQty(treeNode); let nTQ = calcTools.uiNodeTenderQty(treeNode); let f = calcTools.getFeeRateByNode(treeNode); let b = treeNode.data.calcBaseValue ? treeNode.data.calcBaseValue : 0; let tb = treeNode.data.tenderCalcBaseValue ? treeNode.data.tenderCalcBaseValue : 0; let q = nQ ? nQ : 1; let tq = nTQ ? nTQ : 1; let uf = (b * f * 0.01 / q).toDecimal(decimalObj.bills.unitPrice); let tuf = (tb * f * 0.01 / tq).toDecimal(decimalObj.bills.unitPrice); let tf = (me.project.property.billsCalcMode === leafBillGetFeeType.rationPrice) ? (b * f / 100) : (uf * q); tf = tf.toDecimal(decimalObj.bills.totalPrice); let ttf = (me.project.property.billsCalcMode === leafBillGetFeeType.rationPrice) ? (tb * f / 100) : (tuf * tq); ttf = ttf.toDecimal(decimalObj.bills.totalPrice); calcTools.checkFeeField(treeNode, {'fieldName': 'common', 'unitFee': uf, 'totalFee': tf, 'tenderUnitFee': tuf, 'tenderTotalFee': ttf}); // 总造价清单还要做单项工程、建设项目的四大项金额汇总 if (calcTools.isTotalCostBill(treeNode)){ // 公式叶子清单没有暂估费,但总造价清单除外。 calcTools.estimateFee(treeNode); calcTools.initSummaryFee(treeNode); treeNode.data.summaryFees.totalFee = tf; treeNode.data.summaryFees.estimateFee = calcTools.getFee(treeNode, 'estimate.totalFee'); treeNode.data.summaryFees.safetyFee = calcTools.getFee(calcTools.getNodeByFlag(fixedFlag.SAFETY_CONSTRUCTION), 'common.totalFee'); treeNode.data.summaryFees.chargeFee = calcTools.getFee(calcTools.getNodeByFlag(fixedFlag.CHARGE), 'common.totalFee'); } treeNode.data.calcTemplate = {"calcItems": []}; }; innerCalcBillCustom(treeNode){ let me = this; delete treeNode.data.gljList; me.deleteProperties(treeNode, ['calcBase', 'calcBaseValue', 'tenderCalcBaseValue', 'programID']); me.deleteUselessFees(treeNode, ['rationCommon']); // 2017-09-27 需求改了,除了第 1 、 2.2部分以外,都可以手工修改综合单价、综合合价并参与计算 // 在没有公式的情况下可以手工修改综合单价并参与计算 if(calcTools.canCalcToTalFeeByOwn(treeNode)){ if (treeNode.data.feesIndex && treeNode.data.feesIndex.common){ let ftObj = {fieldName: 'common'}; let nQ = calcTools.uiNodeQty(treeNode); let nTQ = calcTools.uiNodeTenderQty(treeNode); ftObj.unitFee = parseFloatPlus(treeNode.data.feesIndex.common.unitFee); ftObj.totalFee = (ftObj.unitFee * nQ).toDecimal(decimalObj.bills.totalPrice); ftObj.tenderUnitFee = ftObj.unitFee; ftObj.tenderTotalFee = (ftObj.tenderUnitFee * nTQ).toDecimal(decimalObj.bills.totalPrice); calcTools.checkFeeField(treeNode, ftObj); } } else{ if (treeNode.data.fees && treeNode.data.fees.length > 0){ treeNode.data.fees = null; treeNode.data.feesIndex = null; treeNode.changed = true; } }; treeNode.data.calcTemplate = {"calcItems": []}; }; // 定额部分抽取出来,供分摊定额公用。 innerCalcRation(treeNode, tenderType = tenderTypes.ttCalc){ let me = this; let fnArr = []; calcTools.getGLJList(treeNode, true); let nQ = calcTools.uiNodeQty(treeNode); let nTQ = calcTools.uiNodeTenderQty(treeNode); if (treeNode.calcType == treeNodeCalcType.ctRationCalcProgram) { // 量价、工料机类型的定额要求"市场合价" if (calcTools.isVP_or_GLJR(treeNode)){ let u = treeNode.data.marketUnitFee ? treeNode.data.marketUnitFee : 0; let t = (u * nQ).toDecimal(decimalObj.ration.totalPrice); if (treeNode.data.marketTotalFee != t){ treeNode.data.marketTotalFee = t; treeNode.changed = true; } ; }; }; let template = me.compiledTemplates[treeNode.data.programID]; treeNode.data.calcTemplate = template; if (treeNode && template && template.hasCompiled) {//2018-08-27 空行的时候,取费专业为空,template也为空,加入template非空判断 let $CE = executeObj; $CE.treeNode = treeNode; $CE.template = template; calcTools.initFees(treeNode); for (let idx of template.compiledSeq) { let calcItem = template.calcItems[idx]; $CE.tempCalcItem = calcItem; let feeRate = 100; // 100% if (calcItem.feeRate != undefined) feeRate = parseFloat(calcItem.feeRate).toDecimal(decimalObj.feeRate); calcItem.unitFee = (eval(calcItem.compiledExpr) * feeRate * 0.01).toDecimal(decimalObj.decimal('unitPrice', treeNode)); calcItem.totalFee = (calcItem.unitFee * nQ).toDecimal(decimalObj.decimal('totalPrice', treeNode)); let tExpr = analyzer.getCompiledTenderExpr(calcItem.compiledExpr); calcItem.tenderUnitFee = (eval(tExpr) * feeRate * 0.01).toDecimal(decimalObj.decimal('unitPrice', treeNode)); calcItem.tenderTotalFee = (calcItem.tenderUnitFee * nTQ).toDecimal(decimalObj.decimal('totalPrice', treeNode)); if (calcItem.fieldName) { fnArr.push(calcItem.fieldName); calcTools.checkFeeField(treeNode, calcItem); }; }; if (tenderType == tenderTypes.ttReverseRation || tenderType == tenderTypes.ttReverseGLJ) this.calcTenderReverse(treeNode, tenderType); me.deleteUselessFees(treeNode, fnArr); }; }; // 存储、刷新零散的多个结点。 saveNodes(treeNodes, callback){ if (treeNodes.length < 1) { $.bootstrapLoading.end(); this.rationMap = null; this.pgljMap = null; return; } let me = this; let dataArr = []; for (let node of treeNodes){ if (node.changed){ let data = calcTools.cutNodeForSave(node); let newData = {'type': node.sourceType, 'data': data}; dataArr.push(newData); } }; if (dataArr.length < 1) { $.bootstrapLoading.end(); return; }; $.bootstrapLoading.start(); let startTime = +new Date(); me.project.updateNodes(dataArr, function (data) { me.rationMap = null; me.pgljMap = null; if(callback){ callback(data); }; for (let node of treeNodes) { delete node.changed }; projectObj.mainController.refreshTreeNode(treeNodes,false,false); // 批量树结点计算后,计算程序早已物是人非,所以这里要重新计算一下。警告:第二个参数千万不能改成3,否则死循环! if (activeSubSheetIsCalcProgram()) calcProgramObj.refreshCalcProgram(projectObj.project.mainTree.selected, 2); $.bootstrapLoading.end(); }); }; // 计算本节点及所有会被影响到的节点,如:所有父节点(默认,可选)、公式引用节点(默认,可选)。修改一个树节点,实际上要计算和保存的是一批树结点:层层父结点、被其它结点(的公式)引用的公式结点。 calculate(treeNode, calcParents = true, calcFormulas = true, tender){ let me = this; let changedNodes = []; me.innerCalc(treeNode, changedNodes, tender); if (treeNode.changed) { // 计算父结点 if (calcParents){ let curNode = treeNode.parent; while (curNode){ me.innerCalc(curNode, changedNodes, tender); curNode = curNode.parent; }; }; // 父结点算完,再计算所有的公式结点(必须先算完父结点,再算公式结点) if (calcFormulas) { me.calcFormulaNodes(changedNodes, tender); }; }; return changedNodes; }; // 计算并保存一个树节点。(修改一个树节点,实际上要计算和保存的是一批树结点:层层父结点、被其它结点(的公式)引用的公式结点) calcAndSave(treeNode, callback, tender){ this.calcNodesAndSave([treeNode],callback,tender); /* let changedNodes = this.calculate(treeNode, true, true, tender); 统一调用相同的方法 this.saveNodes(changedNodes, callback);*/ }; /* 计算所有树结点(分3种情况),并返回发生变动的零散的多个树结点。参数取值如下: calcAllType.catAll 计算所有树结点 (默认值) calcAllType.catBills 计算所有清单 (改变项目属性中清单取费算法时会用到) calcAllType.catRations 计算所有定额、工料机形式的定额、量价,因为它们都走自己的计算程序 (改变人工系数、费率值、工料机单价时会用到) (calcAllType.catRations时程序中做了特殊处理,实际上是计算所有树结点!) 调价相关参数: tender: null:不调价(普通计算)。 1: 正向调价 2:反向调价-调子目 3: 反向调价-调工料机 */ calcAllNodes(calcType = calcAllType.catAll, tender){ let me = this; let changedNodes = []; function calcNodes(nodes) { for (let node of nodes) { if (node.children.length > 0) { calcNodes(node.children); }; if (calcType == calcAllType.catAll || calcType == node.sourceType) { node.calcType = calcTools.getCalcType(node); if (node.calcType != treeNodeCalcType.ctCalcBaseValue) me.innerCalc(node, changedNodes, tender); }; } }; // calcAllType.catRations 参数情况不会计算父结点(因为父结点是清单),所以这里进行特殊处理。 if (calcType == calcAllType.catRations) calcType == calcAllType.catAll; calcNodes(me.project.mainTree.roots); me.calcFormulaNodes(changedNodes, tender); if (tender){ // 普通计算不执行,只有3种调价计算才清。 for(let node of projectObj.project.mainTree.items){ this.clearTenderCache(node); }; }; return changedNodes; }; // tender: null:不调价(普通计算)。 1: 正向调价 2:反向调价-调子目 3: 反向调价-调工料机 calcAllNodesAndSave(calcType = calcAllType.catAll, callback, tender){ let changedNodes = this.calcAllNodes(calcType, tender); this.saveNodes(changedNodes, callback); }; // 计算零散的、混杂的树节点:清单、定额混合等(如:用到某一计算程序的定额和清单)。 // 计算多条零散的定额,并计算他们所属的清单、父清单、引用清单。如:批量替换工料机后受影响的定额。 // 计算多条零散的清单,并计算他们的父清单、引用清单。如:花选删除树结点(如花选清单、定额等,不区分树结点类型)。 calcNodesAndSave(nodes, callback, tender){ let me = this, rationNodes = [], billNodes = [], leafBills = [], allChangedNodes = []; for (let node of nodes) { if (node.sourceType == ModuleNames.ration) rationNodes.push(node) else billNodes.push(node); }; // 多条定额同属一条叶子清单时,避免叶子清单重复计算 for (let ration of rationNodes) { me.innerCalc(ration, allChangedNodes, tender); let leafBill = ration.parent; if (leafBill && leafBills.indexOf(leafBill) < 0) leafBills.push(leafBill); }; billNodes.merge(leafBills); for (let bill of billNodes){ let changeBills = me.calculate(bill, true, false, tender); allChangedNodes.merge(changeBills); }; me.calcFormulaNodes(allChangedNodes, tender); me.saveNodes(allChangedNodes, callback); }; // 计算全部公式项。 (changedArr:将通过本方法后发生改变的节点存入changedArr中) calcFormulaNodes(changedArr, tender){ let me = this; let formulaNodes = cbTools.getFormulaNodes(true); if (formulaNodes.length == 0) return; for (let formulaNode of formulaNodes){ formulaNode.data.userCalcBase = formulaNode.data.calcBase; // 这句不该出现,projectObj.project.calcBase中要改进。 projectObj.project.calcBase.calculate(formulaNode, true); if (projectObj.project.calcBase.success){ // 计算公式结点 me.innerCalc(formulaNode, changedArr, tender); // 计算父结点 if (formulaNode.changed){ let curNode = formulaNode.parent; while (curNode){ me.innerCalc(curNode, changedArr, tender); curNode = curNode.parent; }; }; }; }; }; // 计算叶子清单下的所有子结点、自身、所有父结点、公式引用结点(即跟该叶子清单相关的所有结点)。最后打包存储。 calcLeafAndSave(treeNode, tender){ let me = this; if(!calcTools.isLeafBill(treeNode)) return; if (treeNode.children && treeNode.children.length > 0) { let changedNodes = []; for (let child of treeNode.children){ me.innerCalc(child, changedNodes); }; let curChangeds = me.calculate(treeNode, true, true, tender); changedNodes.merge(curChangeds); me.saveNodes(changedNodes); }; }; // 排除指定项的综合合价计算(用于带循环计算的情况。这里的汇总只到清单级别即可:清单单价取费时,汇总到清单和汇总到定额两个值不一样) getTotalFee(baseNodes, excludeNodes, tender){ let rst = 0; const totalFeeType = tender ? 'common.tenderTotalFee' : 'common.totalFee'; function calcNodes(nodes) { for (let node of nodes) { if(!node){ continue; } if (!excludeNodes.includes(node)){ if (node.source && node.source.children && node.source.children.length > 0) { calcNodes(node.children); } else{ if (node.sourceType == ModuleNames.bills) { rst = (rst + calcTools.getFee(node, totalFeeType)).toDecimal(decimalObj.decimal("totalPrice", node)); }; } } } }; calcNodes(baseNodes); return rst; }; // 税前工程造价 ,adj调价 getBeforeTaxTotalFee(excludeNodes, tender){ let baseNodes = [], me = this; baseNodes.push(calcTools.getNodeByFlag(fixedFlag.SUB_ENGINERRING)); baseNodes.push(calcTools.getNodeByFlag(fixedFlag.MEASURE)); baseNodes.push(calcTools.getNodeByFlag(fixedFlag.OTHER)); baseNodes.push(calcTools.getNodeByFlag(fixedFlag.CHARGE)); return me.getTotalFee(baseNodes, excludeNodes, tender); }; // 反向调价需初始化调价树、缓存数据等 initReverseTenderDatas (){ for(let node of tender_obj.tenderTree.items){ if (node.data.rationQuantityCoe) node.data.rationQuantityCoe = null; let qcObj = node.data.quantityCoe; if (qcObj){ for (let pn in qcObj){ qcObj[pn] = null; }; }; this.clearTenderCache(node); }; // 反向调价时人材机单价调整系数要归1:因为既可以调量又可以调价,以哪个为基准进行反调?过于复杂,仅以通用的调量逻辑为基准即可满足需求。 if (projectObj.project.property.tenderSetting && projectObj.project.property.tenderSetting.gljPriceTenderCoe && (projectObj.project.property.tenderSetting.gljPriceTenderCoe != 1)){ projectObj.project.property.tenderSetting.gljPriceTenderCoe = 1; // 修改缓存值,用于计算 projectObj.project.property.needRestoreGgljPriceTenderCoe = true; // 做个标记,告诉回调函数 } }; // 反向调价 calcTenderReverse(treeNode, tender){ if (tender == tenderTypes.ttReverseRation) { if (treeNode.data.feesIndex.common.tenderUnitFee != treeNode.data.feesIndex.common.unitFee) { treeNode.data.feesIndex.common.tenderUnitFee = treeNode.data.feesIndex.common.unitFee; treeNode.changed = true; } }; if (!treeNode.data.targetTotalFee){ if (treeNode.data.targetUnitFee){ treeNode.data.targetTotalFee = (treeNode.data.targetUnitFee * treeNode.data.quantity).toDecimal(decimalObj.decimal('totalPrice', treeNode)); treeNode.changed = true; } else{ // 既没有目标金额也没有目标单价,此时要初始化使调价合价=原始综合合价,调价单价=原始综合单价。 if (treeNode.data.feesIndex.common.tenderUnitFee != treeNode.data.feesIndex.common.unitFee) { treeNode.data.feesIndex.common.tenderUnitFee = treeNode.data.feesIndex.common.unitFee; treeNode.changed = true; }; if (treeNode.data.feesIndex.common.tenderTotalFee != treeNode.data.feesIndex.common.totalFee) { treeNode.data.feesIndex.common.tenderTotalFee = treeNode.data.feesIndex.common.totalFee; treeNode.changed = true; }; return; } }; if (!treeNode.data.targetUnitFee || (parseFloat(treeNode.data.targetUnitFee) == 0)){ if (calcTools.hasQuantity(treeNode)) treeNode.data.targetUnitFee = (treeNode.data.targetTotalFee / treeNode.data.quantity).toDecimal(decimalObj.decimal('unitPrice', treeNode)); } let coe = 1; if (treeNode.data.feesIndex.common.totalFee != 0) coe = (treeNode.data.targetUnitFee / treeNode.data.feesIndex.common.unitFee).toDecimal(decimalObj.process); // 调价情况之————量价反调工料机(量价无工料机可调,还是按定额来调,即直接调树结点的消耗量) let isVP_RevGLJ =(tender == tenderTypes.ttReverseGLJ) && calcTools.isVP_or_GLJR(treeNode); if ((tender == tenderTypes.ttReverseRation) || isVP_RevGLJ){ treeNode.data.tenderQuantity = (treeNode.data.quantity * coe).toDecimal(decimalObj.decimal("quantity", treeNode)); if (treeNode.data.rationQuantityCoe != coe){ treeNode.data.rationQuantityCoe = coe; treeNode.changed = true; }; if (isVP_RevGLJ){ treeNode.data.quantityCoe = {labour: 0, material: 0, machine: 0, main: 0, equipment: 0}; treeNode.changed = true; }; let ttf = (treeNode.data.tenderQuantity * treeNode.data.feesIndex.common.tenderUnitFee).toDecimal(decimalObj.decimal('totalPrice', treeNode)); if (treeNode.data.feesIndex.common.tenderTotalFee != ttf){ treeNode.data.feesIndex.common.tenderTotalFee = ttf; treeNode.changed = true; }; }else if (tender == tenderTypes.ttReverseGLJ){ let qcObj = treeNode.data.quantityCoe; if (!qcObj || calcTools.isEmptyObject(qcObj)){ treeNode.data.quantityCoe = {labour: coe, material: coe, machine: coe, main: coe, equipment: coe}; treeNode.changed = true; }else{ for (let pn in qcObj){ if (qcObj[pn] != coe){ qcObj[pn] = coe; treeNode.changed = true; } }; }; projectObj.project.calcProgram.calculate(treeNode, false, false, tenderTypes.ttCalc); // 再正向算 }; }; // 清理调价缓存数据 clearTenderCache(treeNode){ // 这些属性值为什么不定义在一个对象里?因为每次要判断对象是否存在,十分麻烦。不如直接写简单。分散书写,统一处理也很好用。 if (treeNode.data.tender_activeTotal) delete treeNode.data.tender_activeTotal; if (treeNode.data.tender_activeTarget) delete treeNode.data.tender_activeTarget; if (treeNode.data.tender_fullLoad) delete treeNode.data.tender_fullLoad; if (treeNode.data.tender_distribute){ delete treeNode.data.targetTotalFee; delete treeNode.data.targetUnitFee; delete treeNode.data.tender_distribute; }; }; // 调价:分摊前的准备工作。主要标记满载的结点。 prepareForDistribute(treeNode){ if (!treeNode) return; if (treeNode.firstChild()) this.prepareForDistribute(treeNode.firstChild()); if (treeNode.children.length == 0){ // if (!treeNode.data.feesIndex['common']) // treeNode.data.tender_fullLoad = true // else // treeNode.data.tender_fullLoad = false; } else{ let full = true; for (let i = 0; i < treeNode.children.length; i++) { let child = treeNode.children[i]; if (!child.data.tender_fullLoad && !calcTools.hasTargetTotalFee(child)){ full = false; break; }; } if (full) { let total = 0, target = 0; for (let i = 0; i < treeNode.children.length; i++) { let child = treeNode.children[i]; total = total + parseFloat(child.data.feesIndex['common'].totalFee); target = target + parseFloat(child.data.targetTotalFee); }; treeNode.data.tender_activeTotal = total; treeNode.data.tender_activeTarget = target; treeNode.data.targetTotalFee = target; treeNode.data.tender_distribute = 2; }; treeNode.data.tender_fullLoad = full; }; /*if (calcTools.isBill(treeNode) && (treeNode.data.tender_fullLoad != undefined)){ console.log(treeNode.data.name + ',tender_fullLoad: ' + treeNode.data.tender_fullLoad + ', target: ' + calcTools.hasTargetTotalFee(treeNode)); };*/ /* if (calcTools.isRationItem(treeNode)){ console.log(treeNode.data); }*/ if (treeNode.nextSibling) this.prepareForDistribute(treeNode.nextSibling); }; // 调价之分摊目标合价:从父到子往下分摊。 distributeTargetTotalFee(treeNode){ if (!treeNode) return; if (treeNode.data.feesIndex && treeNode.data.feesIndex['common']){ // 空清单忽略 // 默认能够执行到这里时每个节点已经被初始化,缓存已删除 treeNode.data.tender_activeTotal = treeNode.data.feesIndex['common'].totalFee; // 开始分摊:只分摊自有目标金额、从父结点分摊到的金额(不分摊子结点摊上来的金额) if (calcTools.hasTargetTotalFee(treeNode) && !(treeNode.data.tender_distribute && treeNode.data.tender_distribute == 2)){ treeNode.data.tender_activeTarget = treeNode.data.targetTotalFee; // 先把会破坏金额比例关系的孩子排除:1.有目标金额的 2.满载的(孙子全满,没有分摊空间,所以实质上该孩子的金额已被锁死无法分摊) for (let i = 0; i < treeNode.children.length; i++) { let child = treeNode.children[i]; if (!child.data.feesIndex || !child.data.feesIndex['common']) continue; // 空白行清单、定额 child.data.tender_activeTotal = child.data.feesIndex['common'].totalFee; if (calcTools.hasTargetTotalFee(child)){ child.data.tender_activeTarget = child.data.targetTotalFee; console.log(treeNode.data.tender_activeTotal); console.log(child.data.tender_activeTotal); treeNode.data.tender_activeTotal = treeNode.data.tender_activeTotal - child.data.tender_activeTotal; treeNode.data.tender_activeTarget = treeNode.data.tender_activeTarget - child.data.tender_activeTarget; }; } if (treeNode.data.tender_activeTotal != 0){ let coe = (treeNode.data.tender_activeTarget / treeNode.data.tender_activeTotal).toDecimal(decimalObj.process); for (let i = 0; i < treeNode.children.length; i++) { let child = treeNode.children[i]; if (!child.data.feesIndex || !child.data.feesIndex['common']) continue; // 空白行清单、定额 if (!calcTools.hasTargetTotalFee(child)){ child.data.tender_activeTarget = (coe * child.data.tender_activeTotal).toDecimal(decimalObj.decimal('totalPrice', treeNode)); child.data.targetTotalFee = child.data.tender_activeTarget; child.data.tender_distribute = 1; // 1表示分摊金额来自父结点。2表示分摊金额来自孩子结点。 } } }; }; } if (treeNode.firstChild()) this.distributeTargetTotalFee(treeNode.firstChild()); if (treeNode.nextSibling) this.distributeTargetTotalFee(treeNode.nextSibling); }; setRationMap(){ if(this.rationMap == null){ this.rationMap = {}; for(let glj of projectObj.project.ration_glj.datas){ if(this.rationMap[glj.rationID]){ this.rationMap[glj.rationID].push(glj) }else { this.rationMap[glj.rationID] = [glj]; } } } if(this.pgljMap == null ){ this.pgljMap = _.indexBy(projectObj.project.projectGLJ.datas.gljList, 'id'); } }; getGljArrByRation(ration){ if (ration.type == rationType.gljRation){ let glj = JSON.parse(JSON.stringify(ration)); glj.type = glj.subType; glj.quantity = 1; glj.totalQuantity = parseFloatPlus(ration.quantity); return [glj]; } else{ if(!this.rationMap) return []; let result = this.rationMap[ration.ID]; if(!result) return []; result = gljOprObj.combineWithProjectGlj(result,false,ration,this.pgljMap); return result; } }; doTenderCalc(callback){ let sOption = calcTools.getTenderCalcType(); let tender; if (sOption =='coeBase') tender = tenderTypes.ttCalc else if (sOption =='priceBase_RCJ') tender = tenderTypes.ttReverseGLJ else if (sOption =='priceBase_ZM') tender = tenderTypes.ttReverseRation; if (tender == tenderTypes.ttReverseGLJ || tender == tenderTypes.ttReverseRation){ this.initReverseTenderDatas(); this.prepareForDistribute(tender_obj.tenderTree.roots[0]); this.distributeTargetTotalFee(tender_obj.tenderTree.roots[0]); }; this.calcAllNodesAndSave(calcAllType.catAll, callback, tender); }; }; // export default analyzer;