/** * 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 ); }, isInvalidNode: function (treeNode) { // 无效的、影响正常计算的行(无意义的空行、没有金额的行等) return !( treeNode.data && treeNode.data.feesIndex && treeNode.data.feesIndex.common && treeNode.data.feesIndex.common.totalFee ); }, 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 = isTender ? (md["tenderPrice"] ? md["tenderPrice"] : 0) : (md["marketPrice"] ? md["marketPrice"] : 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.isNumber(maxPrice)) { return false; } return totalFee > +maxPrice; }, // 清单价格是否小于最低限价 unitFeeLTMinPrice: function (node, feeField) { if (!this.isBill(node)) { return false; } const totalFee = this.getFee(node, feeField); const minPrice = node.data.minPrice; // 最低限价有值才对比 if (!commonUtil.isNumber(minPrice)) { return false; } return totalFee < +minPrice; }, getTenderTypeStr: function () { let tenderSetting = projectObj.project.property.tenderSetting; let ct = tenderSetting && tenderSetting.calcPriceOption ? tenderSetting.calcPriceOption : "priceBase_RCJ"; if (ct == "priceBase") ct = "priceBase_RCJ"; // 兼容旧项目 return ct; }, getTenderType: function () { let rst; let sOption = calcTools.getTenderTypeStr(); if (sOption == "coeBase") rst = tenderTypes.ttCalc; else if (sOption == "priceBase_RCJ") rst = tenderTypes.ttReverseGLJ; else if (sOption == "priceBase_ZM") rst = tenderTypes.ttReverseRation; return rst; }, // 取树结点的调价系数。 getCoe: function (node, tender) { if (tender == tenderTypes.ttReverseGLJ) return calcTools.isVP_or_GLJR(node) ? node.data.rationQuantityCoe : node.data.quantityCoe ? node.data.quantityCoe.labour : 0; else if (tender == tenderTypes.ttReverseRation) return node.data.rationQuantityCoe; }, }; 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/gi); // 4F3 let j = expr.search(/\d\[/gi); // 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 nodes = me.project.Bills.getGatherNodes(treeNode); } else if (commonCalcType == 3) nodes = treeNode.children; function isBaseFeeType(type) { return ( ["labour", "material", "machine", "mainMaterial", "equipment", "labourInc"].indexOf( type ) > -1 ); } let nQ = calcTools.uiNodeQty(treeNode); let nTQ = calcTools.uiNodeTenderQty(treeNode); let bq = nQ ? nQ : 1; let btq = nTQ ? nTQ : 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; 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) { if (treeNode.data.lockUnitPrice == true) { // 清单单价锁定 if (treeNode.data.feesIndex && treeNode.data.feesIndex[ft.type]) { buf = parseFloatPlus( treeNode.data.feesIndex[ft.type].unitFee ).toDecimal(decimalObj.bills.unitPrice); btuf = parseFloatPlus( treeNode.data.feesIndex[ft.type].tenderUnitFee ).toDecimal(decimalObj.bills.unitPrice); btf = (bq * buf).toDecimal(decimalObj.bills.totalPrice); bttf = (btq * btuf).toDecimal(decimalObj.bills.totalPrice); } } else { 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); if (node.data.tenderQuantity) node.data.tenderContaion = ( node.data.tenderQuantity / bq ).toDecimal(decimalObj.process); else node.data.tenderContaion = node.data.contain; 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 = []; // 如果是反算工料机,这里要先保证coe有值。 if (tenderType == tenderTypes.ttReverseGLJ) { let coe = ( treeNode.data.tender_activeTarget / treeNode.data.tender_activeTotal ).toDecimal(decimalObj.process); treeNode.data.quantityCoe = { labour: coe, material: coe, machine: coe, main: coe, equipment: coe, }; } 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.reverseTenderCalc(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) { // console.log(node.data.name); let data = calcTools.cutNodeForSave(node); let newData = { type: node.sourceType, data: data }; dataArr.push(newData); } } if (dataArr.length < 1) { $.bootstrapLoading.end(); return; } if (projectObj.project.projectInfo.lastFileVer != VERSION) { let data = { ID: projectObj.project.ID(), lastFileVer: VERSION, }; let newData = { type: "project", data: data }; dataArr.push(newData); } $.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); projectObj.project.projectInfo.lastFileVer = VERSION; // 批量树结点计算后,计算程序早已物是人非,所以这里要重新计算一下。警告:第二个参数千万不能改成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) { 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); } // 清理调价缓存数据 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; } } initGljPriceTenderCoe() { 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; // 做个标记,告诉回调函数 } } // 反向调价-初始化调价树、清理缓存数据等 reverseTenderInitDatas() { 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:因为既可以调量又可以调价,以哪个为基准进行反调?过于复杂,仅以通用的调量逻辑为基准即可满足需求。 this.initGljPriceTenderCoe(); } // 反向调价简单计算:只计算当前单一结点,由目标金额计算定额量系数或工料机量系数coe,再根据此coe计算定额的调后量、调后合价。 // ⑴只计算参数指定的一个单一的结点,连父结点、引用结点都不计算。此结点只会是定额、量价、工料机款定额,不会是清单。 // ⑵执行此方法的前提:已在其它地方把总的目标金额分摊好了,此结点已获取目标金额。 reverseTenderCalc(treeNode, tender) { if (tender == tenderTypes.ttReverseRation) { if ( treeNode.data.feesIndex.common && 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 && 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 && 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; } };*/ if (qcObj.labour != coe) { qcObj.labour = coe; treeNode.changed = true; } if (qcObj.material != coe) { qcObj.material = coe; treeNode.changed = true; } if (qcObj.machine != coe) { qcObj.machine = coe; treeNode.changed = true; } if (qcObj.main != coe) { qcObj.main = coe; treeNode.changed = true; } if (qcObj.equipment != coe) { qcObj.equipment = coe; treeNode.changed = true; } } projectObj.project.calcProgram.calculate( treeNode, false, false, tenderTypes.ttCalc ); // 再正向算 } } // 反向调价-分摊前的准备工作。包括: // ⑴结点是否满载 // ⑵若是满载,汇总孩子的金额、目标金额给它 // ⑶标记此结点的目标金额来自孩子,未来它不往下分摊(汇上来再摊下去无用功) prepareForDistribute(treeNode) { if (!treeNode) return; if (treeNode.firstChild()) this.prepareForDistribute(treeNode.firstChild()); if (treeNode.children.length == 0) { } 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 (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); } // 反向调价逼近 reverseTenderApproach(callback, tender) { let me = this; function root0() { return tender_obj.tenderTree.roots[0]; } if (!root0().data.targetTotalFee) return; // 根结点没有指定目标金额时,逼近会带来莫名其妙的结果 let G_DIGIT = 0.01; // 系数调整步距(0.1最终结果误差大。0.001目标金额与逼前金额差距大时无法有效逼近) let MaxDiffValue = 0.1; // 可接受的最大差值。(如果实际差值比这个大:说明列表结点耗尽,无法达到指定精度。公路设为1,建筑设为0.1) let times = 300; // 逼近计算的极限次数。正常情况下“单位系数金额”列表中的结点耗尽即退出,这里指定轮数是最后保险阀,防止无限死循环。 let calcModel = 1; // 计算模式:1 精度优先(差值不接近0不停,直到结点用完或极限次数用完。时间长精度高)2 速度优先(达到指定差值范围即熔断逼近。时间短差值大) let diffProp = 0.0001; // 计算模式=2(速度优先)时有效。 通过这个金额比例值计算可接受的最大差值D。差值D = 根结点金额 * diffProp let isTest = true; // 测试 // 按指定的比例获取可接受的差值:如目标金额的万分之一。 function getPropV(node) { let v = parseFloat((node.data.targetTotalFee * diffProp).toFixed(0)); // node.data.feesIndex.common.totalFee return Math.max(v, MaxDiffValue); } // 取根结点的:调后金额跟目标金额差值,看还有多少误差需要处理。 function getRootDiff() { return ( root0().data.feesIndex.common.tenderTotalFee - root0().data.targetTotalFee ).toDecimal(3); } // 生成每单位差值的定额结点列表。 function getNodeDiffs() { let arr = []; for (let i = 0; i < tender_obj.tenderTree.items.length; i++) { let node = tender_obj.tenderTree.items[i]; if (calcTools.isInvalidNode(node)) continue; // 量价还是要参与,因为它贡献了金额,如果它的金额比重很大,它退出了,会导致其它结点过调。 // if (calcTools.isRationCategory(node) && (!calcTools.isVP_or_GLJR(node))){ if (calcTools.isRationCategory(node)) { let coe = calcTools.getCoe(node, tender); if (coe != 0) { let diff = Math.abs( node.data.feesIndex.common.tenderTotalFee - node.data.feesIndex.common.totalFee ); node.data.tender_diffValuePerCoe = ( (diff * G_DIGIT) / coe ).toDecimal(decimalObj.process); node.data.tender_rowNo = i + 1; // node在UI上显示的行号 arr.push(node); } } } arr.sort(function sortArr(a, b) { return a.data.tender_diffValuePerCoe - b.data.tender_diffValuePerCoe; }); if (isTest) { let arr2 = []; for (let i = 0; i < arr.length; i++) { arr2.push({ row: arr[i].data.tender_rowNo, code: arr[i].data.code + " " + arr[i].data.name, diff: arr[i].data.tender_diffValuePerCoe, }); } console.log(arr2); } return arr; } // 每单位差值的定额结点列表中,取离给定值最近的定额结点。 function getCloseNode(arr, value) { let index = 0; let d_value = Number.MAX_VALUE; for (let i = 0; i < arr.length; i++) { let new_d_value = Math.abs(arr[i].data.tender_diffValuePerCoe - value); if (new_d_value <= d_value) { if ( new_d_value === d_value && arr[i].data.tender_diffValuePerCoe < arr[index].data.tender_diffValuePerCoe ) { continue; } index = i; d_value = new_d_value; } } return { idx: index, node: arr[index] }; } // 逼近(单轮)。arr 参考取值列表。返回{type, node, nodeIdx}。 // type:1正常,2无结点,3结点过调。node:结点。nodeIdx:结点在列表中的索引位置。node.data.tender_rowNo 在UI上的行号。 function approach(arr) { let v = getRootDiff(); let obj = getCloseNode(arr, Math.abs(v)); // {idx, node} 极端:{0, undefind} let closeNode = obj.node; if (!closeNode) return { type: 2, node: undefined, nodeIdx: -1 }; // arr 被清空了 let d = v > 0 ? -G_DIGIT : G_DIGIT; let coe = calcTools.getCoe(closeNode, tender); if (coe + d < 0) return { type: 3, node: obj.node, nodeIdx: obj.idx }; // 再调的话,系数就变负数了,过调 if (tender == tenderTypes.ttReverseRation) { closeNode.data.tender_previousCoe = closeNode.data.rationQuantityCoe; // tender_previousCoe: 上一次的调整系数,用于撤回 closeNode.data.rationQuantityCoe = ( closeNode.data.rationQuantityCoe + d ).toDecimal(decimalObj.process); } else if (tender == tenderTypes.ttReverseGLJ) { if (calcTools.isVP_or_GLJR(closeNode)) { closeNode.data.tender_previousCoe = closeNode.data.rationQuantityCoe; closeNode.data.rationQuantityCoe = ( closeNode.data.rationQuantityCoe + d ).toDecimal(decimalObj.process); } else { closeNode.data.tender_previousCoe = closeNode.data.quantityCoe.labour; closeNode.data.quantityCoe.labour = ( closeNode.data.quantityCoe.labour + d ).toDecimal(decimalObj.process); closeNode.data.quantityCoe.material = ( closeNode.data.quantityCoe.material + d ).toDecimal(decimalObj.process); closeNode.data.quantityCoe.machine = ( closeNode.data.quantityCoe.machine + d ).toDecimal(decimalObj.process); closeNode.data.quantityCoe.main = ( closeNode.data.quantityCoe.main + d ).toDecimal(decimalObj.process); closeNode.data.quantityCoe.equipment = ( closeNode.data.quantityCoe.equipment + d ).toDecimal(decimalObj.process); } } me.calculate(closeNode, true, true, tenderTypes.ttCalc); return { type: 1, node: obj.node, nodeIdx: obj.idx }; } // 撤消最后一轮逼近(调过头了,回退一步) function undoLastApproach(obj) { let closeNode = obj.node; let coe = closeNode.data.tender_previousCoe; if (tender == tenderTypes.ttReverseRation) { closeNode.data.rationQuantityCoe = coe; } else if (tender == tenderTypes.ttReverseGLJ) { if (calcTools.isVP_or_GLJR(closeNode)) { closeNode.data.rationQuantityCoe = coe; } else { closeNode.data.quantityCoe.labour = coe; closeNode.data.quantityCoe.material = coe; closeNode.data.quantityCoe.machine = coe; closeNode.data.quantityCoe.main = coe; closeNode.data.quantityCoe.equipment = coe; } } me.calculate(closeNode, true, true, tenderTypes.ttCalc); if (isTest) { let _sp = `        `; // 保留空格,打印对齐 let _node = `[行${obj.node.data.tender_rowNo} 索引${obj.nodeIdx}]`; console.log(`${_sp} ${_node} 过调,已回退`); } } let root = tender_obj.tenderTree.roots[0]; let propV = getPropV(root); let vArr = getNodeDiffs(); let d1 = getRootDiff(); if (isTest) { let _tq = `调前${root.data.feesIndex.common.totalFee}`; let _mb = `目标${root.data.targetTotalFee}`; let _wbj = `调后无逼近${root.data.feesIndex.common.tenderTotalFee}`; let _cz = `差值${d1}`; let _js = `轮${times} 系数步距${G_DIGIT}`; let _yx = calcModel == 2 ? `速度优先(差比${diffProp} 差域0~${propV})` : "精度优先"; console.log(`${_tq}|${_mb}|${_wbj}|${_cz}|${_yx}|${_js}`); } // 多轮逼进 for (let i = 1; i <= times; i++) { // 与目标金额差值在1以内,整数部分已相同,结果很棒。很多时候能达到完美的差值0,但不能用0判断,因为金额取小数时有问题。 if (Math.abs(d1) < MaxDiffValue) break; // 速度优先模式下,结果到达差值范围内,即熔断逼近。 if (calcModel == 2) { if (Math.abs(d1) < propV) break; } let obj = approach(vArr); if (obj.type == 2) break; // 结点耗完,列表已清空,无结点可调。 if (obj.type == 3) { // 结点过调,该结点任务已完成,不再参与调节,从列表中清除 vArr.splice(obj.nodeIdx, 1); continue; } let d2 = getRootDiff(); if (isTest) { let _time = `【第 ${i} 轮】调整`; let _node = `[行${obj.node.data.tender_rowNo} 索引${obj.nodeIdx}]`; let _coe = `${obj.node.data.tender_previousCoe} → ${calcTools.getCoe( obj.node, tender )}`; let _d = `差值${d1} → ${d2}`; console.log(`${_time}${_node} ${_coe},${_d}`); } if (Math.abs(d2) > Math.abs(d1)) { // 逼近后差值反而变大,证明此轮调节不合适,回退一步,并将结点从列表清除 undoLastApproach(obj); vArr.splice(obj.nodeIdx, 1); } else if (Math.abs(d2) == Math.abs(d1)) { // 调前调后差距相同时,会死循环,也从列表中清除 vArr.splice(obj.nodeIdx, 1); } else { d1 = d2; } } // 重要注释:到这里,所有系数已处理就绪,最后直接全局正算即可。 // 到这一步,被处理的定额、及其受影响的父结点、引用结点等均已正算完成(approach方法、undoLastApproach方法),但都未保存。 // 最后全局正算目的:⑴计算除第一部以外的其它部分 ⑵保存。正算在调用外面完成。 // this.calcAllNodesAndSave(calcAllType.catAll, callback, tenderTypes.ttCalc); } 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) { $.bootstrapLoading.start(); setTimeout(() => { let tender = calcTools.getTenderType(); if ( tender == tenderTypes.ttReverseGLJ || tender == tenderTypes.ttReverseRation ) { // 调价计算必须依赖调价树。 if (!tender_obj.tenderTree) { tender_obj.createTree(); tender_obj.createTreeNodes(); } // 反向调价+逼近原理: // 清理调价缓存 → 分摊目标金额(从子往父处理满载、从父往子分摊) → 全局反算 → 逼近 → 全局正算、存储 this.reverseTenderInitDatas(); this.prepareForDistribute(tender_obj.tenderTree.roots[0]); this.distributeTargetTotalFee(tender_obj.tenderTree.roots[0]); this.calcAllNodes(calcAllType.catAll, tender); // 先全局反算:得到每定额的coe、基础调后金额(误差大,需逼近) this.reverseTenderApproach(callback, tender); // 逼近上述基础调后金额 } // 全局正算(用的是主树,无需调价树) this.calcAllNodesAndSave( calcAllType.catAll, callback, tenderTypes.ttCalc ); }); } } // export default analyzer;