/** * Created by CSL on 2017-07-19. * 计算程序。所有定额、清单、父清单的计算都从此入。 */ /* 新版GLD 取消了默认清单模板,所以这里废弃。先留着,预防不时之需。 let defaultBillTemplate = { ID: 15, name: "清单公式", calcItems: [ { ID: 1, code: "1", name: "定额直接费", dispExpr: "F2+F3+F4", statement: "人工费+材料费+机械费", feeRate: null, memo: '' }, { ID: 2, code: "1.1", name: "人工费", dispExpr: "HJ", statement: "合计", feeRate: 50, fieldName: 'labour', memo: '' }, { ID: 3, code: "1.2", name: "材料费", dispExpr: "HJ", statement: "合计", feeRate: 30, fieldName: 'material', memo: '' }, { ID: 4, code: "1.3", name: "机械费", dispExpr: "HJ", statement: "合计", feeRate: 20, fieldName: 'machine', memo: '' }, { ID: 5, code: "2", name: "企业管理费", dispExpr: "F1", statement: "定额直接费", feeRate: null, fieldName: 'manage', memo: '' }, { ID: 6, code: "3", name: "利润", dispExpr: "F1", statement: "定额直接费", feeRate: null, fieldName: 'profit', memo: '' }, { ID: 7, code: "4", name: "风险费用", dispExpr: "F1", statement: "定额直接费", feeRate: null, fieldName: 'risk', memo: '' }, { ID: 8, code: "5", name: "综合单价", dispExpr: "F1+F5+F6+F7", statement: "定额直接费+企业管理费+利润+风险费用", feeRate: null, fieldName: 'common', memo: '' } ] };*/ 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); }, 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; }, 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; }, isVolumePrice: function (treeNode) { return this.isRationCategory(treeNode) && treeNode.data.type === rationType.volumePrice; }, isGljRation: function (treeNode) { return this.isRationCategory(treeNode) && treeNode.data.type === rationType.gljRation; }, 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); }, getGLJList: function (treeNode, needOneBill) { delete treeNode.data.gljList; if (this.isRationCategory(treeNode)) { if (treeNode.data.type != rationType.volumePrice) { treeNode.data.gljList = projectObj.project.ration_glj.getGljArrByRation(treeNode.data); } } else if (this.isBill(treeNode)){ let nodeQ = this.uiNodeQty(treeNode); let q = nodeQ ? nodeQ : 1; let rNodes = projectObj.project.Ration.getRationNodes(treeNode); let rations = rNodes.map(function (node) {return node.data}); treeNode.data.gljList = projectObj.project.ration_glj.getGatherGljArrByRations(rations, needOneBill, q); }; }, 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; // 初始化前先拦截末定义的情况 if (!treeNode.data.feesIndex || !treeNode.data.feesIndex[feeObj.fieldName]){ if (feeObj.unitFee == 0 && feeObj.totalFee == 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; }; 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; } 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; }; }, // 参数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; for (let glj of treeNode.data.gljList) { if (gljTypes.indexOf(glj.type) >= 0) { calcTools.calcGLJTenderPrice(glj); calcTools.calcGLJTenderQty(treeNode, glj); let qty = isTender ? me.uiGLJQty(glj["tenderQuantity"]) : me.uiGLJQty(glj["quantity"]); let mprice = isTender ? me.uiGLJPrice(glj["tenderPrice"]) : me.uiGLJPrice(glj["marketPrice"]); let aprice = me.uiGLJPrice(glj["adjustPrice"]); 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"]);} else if (priceType == priceTypes.ptAdjustPrice){price = aprice;} else if (priceType == priceTypes.ptMarketPrice){price = mprice;} temp = (qty * price).toDecimal(decimalObj.process); result = (result + temp).toDecimal(decimalObj.process); }; }; }; if (priceType == priceTypes.ptDiffPrice){ result = (temp.toDecimal(decimalObj.ration.unitPrice) - temp2.toDecimal(decimalObj.ration.unitPrice)).toDecimal(decimalObj.ration.unitPrice); } else{ result = result.toDecimal(decimalObj.ration.unitPrice); }; return result; }, machineLabourFee: function (treeNode, gljArr, isTender) { if (!gljArr) return 0; let result = 0; for (let glj of gljArr) { if (glj.type == gljType.GENERAL_MACHINE) { let gljQ; if (isTender){ calcTools.calcGLJTenderQty(treeNode, glj); gljQ = glj.tenderQuantity; } else gljQ = glj.quantity; // 获取机械组成物(调价不深入到组成物) let mds = projectObj.project.composition.getCompositionByGLJ(glj); if (!mds) mds = []; let mdSum = 0; for (let md of mds) { if (md.type == gljType.MACHINE_LABOUR) { let q = md["consumption"] ? md["consumption"] : 0; let p = md["basePrice"] ? md["basePrice"] : 0; mdSum = mdSum + (q * p).toDecimal(decimalObj.ration.unitPrice); mdSum = (mdSum).toDecimal(decimalObj.ration.unitPrice); } } result = result + (gljQ * mdSum).toDecimal(decimalObj.ration.unitPrice); result = (result).toDecimal(decimalObj.ration.unitPrice); } } return result; }, // 总造价清单、叶子清单、定额的暂估费。父清单是汇总子清单的暂估费,走计算程序逻辑,不在这里。 estimateFee: function (treeNode, isBase, isTender){ let me = this, sumU = 0, sumT = 0; let nodeQ = me.uiNodeQty(treeNode, isTender); let isGather = (projectObj.project.property.zanguCalcMode == zanguCalcType.gatherMaterial); // 先汇总数量,再乘市场价。如果是叶子清单,进入这里的gljList中的材料,已经是同类材料跨定额汇总过的了。 function eTFee(){ if (!treeNode.data.gljList) return 0; let GLJObjs = []; for (let glj of treeNode.data.gljList) { if (!allMaterialTypes.includes(glj.type)) continue; if (glj.isEstimate){ GLJObjs.push({code: glj.code, name: glj.name, specs: glj.specs, unit: glj.unit, type: glj.type, // quantity: (nodeQ * glj.quantity).toDecimal(decimalObj.process), quantity: me.uiGLJQty((glj.totalQuantity)).toDecimal(decimalObj.process), marketPrice: glj.marketPrice}); } 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){ let isExist = false; // let totalQ = (nodeQ * me.uiGLJQty(glj.quantity)).toDecimal(decimalObj.glj.quantity); let totalQ = me.uiGLJQty((glj.totalQuantity)).toDecimal(decimalObj.glj.quantity); let mdQ = (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); 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}); } } } }; let rst = 0; for (let obj of GLJObjs){ let tp = (me.uiGLJQty(obj.quantity) * me.uiGLJPrice(obj.marketPrice)).toDecimal(decimalObj.bills.totalPrice); rst = (rst + tp).toDecimal(decimalObj.bills.totalPrice); }; return rst; }; // 汇总子结点的暂估合价 function eTFeeByChildren(){ let rst = 0; for (let node of treeNode.children){ if (node.data.feesIndex && node.data.feesIndex['estimate']) { rst = (rst + parseFloatPlus(node.data.feesIndex['estimate'].totalFee)).toDecimal(decimalObj.process); }; }; rst = (rst).toDecimal(decimalObj.bills.totalPrice); return rst; }; // 先数量乘市场价,再汇总 function eUFee(){ if (!treeNode.data.gljList) return 0; let rst = 0; for (let glj of treeNode.data.gljList) { if (!allMaterialTypes.includes(glj.type)) continue; if (glj.isEstimate){ rst = rst + (me.uiGLJQty(glj.quantity) * me.uiGLJPrice(glj.marketPrice)).toDecimal(decimalObj.process); rst = rst.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 = rst + (mdU * me.uiGLJQty(glj.quantity)).toDecimal(decimalObj.process); rst = rst.toDecimal(decimalObj.process); } } }; rst = rst.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; if (node.data.feesIndex && node.data.feesIndex.estimate){ eU = node.data.feesIndex.estimate.unitFee; eT = node.data.feesIndex.estimate.totalFee; } else { eU = 0, eT = 0; }; sumU = (sumU + parseFloatPlus(eU)).toDecimal(decimalObj.process); sumT = (sumT + parseFloatPlus(eT)).toDecimal(decimalObj.process); }; sumU = (sumU).toDecimal(decimalObj.bills.unitPrice); sumT = (sumT).toDecimal(decimalObj.bills.totalPrice); } else if (me.isParentBill(treeNode)){ // 父清单不要汇总单价。 sumT = eTFeeByChildren(); sumU = undefined; } else if (me.isLeafBill(treeNode)){ if (isGather){ me.getGLJList(treeNode, false); sumT = eTFee(); } else sumT = eTFeeByChildren(); let q = nodeQ ? nodeQ : 1; sumU = (sumT / q).toDecimal(decimalObj.bills.totalPrice); } else if (me.isRationCategory(treeNode)){ me.getGLJList(treeNode, false); sumU = eUFee(); if (isBase) return sumU; if (isGather) sumT = eTFee() else sumT = (nodeQ * sumU).toDecimal(decimalObj.ration.totalPrice); }; me.checkFeeField(treeNode, {'fieldName': 'estimate', 'unitFee': sumU, 'totalFee': sumT}); }, marketPriceToBase: function (treeNode, baseName, isTender) { if (treeNode.data.type != rationType.volumePrice && treeNode.data.type != rationType.gljRation) return; let result = 0, me = this; if ( (treeNode.data.subType === gljType.LABOUR && baseName === calcBaseNames.DEJJRGF) || (baseMaterialTypes.includes(treeNode.data.subType) && baseName === calcBaseNames.DEJJCLF) || (treeNode.data.subType === gljType.GENERAL_MACHINE && baseName === calcBaseNames.DEJJJXF) || (treeNode.data.subType === gljType.MAIN_MATERIAL && baseName === calcBaseNames.ZCF) || (treeNode.data.subType === gljType.EQUIPMENT && baseName === calcBaseNames.SBF)) { if (treeNode.data.type == rationType.volumePrice) result = treeNode.data.marketUnitFee ? parseFloat(treeNode.data.marketUnitFee).toDecimal(decimalObj.ration.unitPrice) : 0 else if (treeNode.data.type == rationType.gljRation) // result = treeNode.data.basePrice ? parseFloat(treeNode.data.basePrice).toDecimal(decimalObj.ration.unitPrice) : 0; // 这里因为是算基数所以要取基价,但不能直接取basePrice,受限于项目属性的三个选项。 result = gljOprObj.getBasePrice(treeNode); } else if (treeNode.data.subType === gljType.GENERAL_MACHINE && baseName === calcBaseNames.DEJJJSRGF) { 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.machineLabourFee(treeNode, [glj], isTender); } else if ( (treeNode.data.type == rationType.gljRation) && ((treeNode.data.subType === gljType.LABOUR && baseName === calcBaseNames.RGFJC) || (baseMaterialTypes.includes(treeNode.data.subType) && baseName === calcBaseNames.CLFJC) || (treeNode.data.subType === gljType.GENERAL_MACHINE && baseName === calcBaseNames.JXFJC)) ) { let aprice = me.uiGLJPrice(treeNode.data.adjustPrice); let mprice = me.uiGLJPrice(treeNode.data.marketUnitFee); result = (mprice - aprice).toDecimal(decimalObj.ration.unitPrice); } return result; }, partASupplyFee: function (treeNode, baseName, isTender) { 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 == calcBaseNames.JGDEJJRGF || baseName == calcBaseNames.JDDEJJRGF){ gljT = [gljType.LABOUR]; } else if (baseName == calcBaseNames.JGDEJJCLF || baseName == calcBaseNames.JDDEJJCLF){ gljT = baseMaterialTypes; compT = compositionTypes; } else if (baseName == calcBaseNames.JGDEJJJXF || baseName == calcBaseNames.JDDEJJJXF){ gljT = baseMachineTypes; compT = [gljType.GENERAL_MACHINE]; } else if (baseName == calcBaseNames.JGZCF || baseName == calcBaseNames.JDZCF){ gljT = [gljType.MAIN_MATERIAL]; compT = [gljType.MAIN_MATERIAL]; } else if (baseName == calcBaseNames.JGSBF || baseName == calcBaseNames.JDSBF){ 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.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; if (isTender){ calcTools.calcGLJTenderQty(treeNode, glj); gljQ = glj.tenderQuantity; } else gljQ = 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; } sum = (sum + glj.basePrice * 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; }; sum = (sum + c.basePrice * 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, 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, targetUnitFee: treeNode.data.targetUnitFee, targetTotalFee: treeNode.data.targetTotalFee }; // 定额大类 if (me.isRationCategory(treeNode)) { data.isSubcontract = treeNode.data.isSubcontract; //定额类型的工料机做特殊处理 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)); }, uiNodeTenderQty: function (treeNode){ return this.calcNodeTenderQty(treeNode); }, calcNodeTenderQty: function (treeNode){ let qCoe = (treeNode.data.rationQuantityCoe == undefined) ? 1 : treeNode.data.rationQuantityCoe; treeNode.data.tenderQuantity = (this.uiNodeQty(treeNode) * qCoe).toDecimal(decimalObj.decimal("quantity", treeNode)); }, calcGLJTenderQty: function (treeNode, glj){ if (treeNode.data.quantityCoe == undefined){ glj.tenderQuantity = glj.quantity; return; }; let qCoe = 1; if (gljType.LABOUR == glj.type){ if (treeNode.data.quantityCoe.labour) qCoe = treeNode.data.quantityCoe.labour; } else if (baseMaterialTypes.indexOf(glj.type)){ if (treeNode.data.quantityCoe.material) qCoe = treeNode.data.quantityCoe.material; } else if (baseMachineTypes.indexOf(glj.type)){ if (treeNode.data.quantityCoe.machine) qCoe = treeNode.data.quantityCoe.machine; } else if (gljType.MAIN_MATERIAL == glj.type){ if (treeNode.data.quantityCoe.main) qCoe = treeNode.data.quantityCoe.main; } else if (gljType.EQUIPMENT == glj.type){ if (treeNode.data.quantityCoe.equipment) qCoe = treeNode.data.quantityCoe.equipment; }; glj.tenderQuantity = (glj.quantity * qCoe).toDecimal(decimalObj.glj.quantity); }, calcGLJTenderPrice: function (glj) { let pCoe = 1; if (projectObj.project.property.tenderSetting && projectObj.project.property.tenderSetting.gljPriceTenderCoe) pCoe = projectObj.project.property.tenderSetting.gljPriceTenderCoe; glj.tenderPrice = (glj.marketPrice * pCoe).toDecimal(decimalObj.glj.unitPrice); }, // 界面显示的工料机价格,包括定额价、市场价等。参数 price 传入一个普通的价格数值即可。 uiGLJPrice: function (price){ if (price) return parseFloat(price).toDecimal(decimalObj.glj.unitPrice) else return 0; }, // 界面显示的工料机数量。参数 quantity 传入一个普通的数量数值即可。 uiGLJQty: function (quantity){ if (quantity) return parseFloat(quantity).toDecimal(decimalObj.glj.quantity) else return 0; }, getRationsByProjectGLJ(PGLJID){ let rationIDs = []; let RGs = projectObj.project.ration_glj.datas; for (let rg of RGs){ if (rg.projectGLJID == PGLJID){ 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 (item.data.projectGLJID == PGLJID) 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; } }; const calcBaseNames = { DEJJRGF: '定额基价人工费', DEJJCLF: '定额基价材料费', DEJJJXF: '定额基价机械费', DEJJJSRGF: '定额基价机上人工费', RGFJC: '人工费价差', CLFJC: '材料费价差', JXFJC: '机械费价差', ZCF: '主材费', SBF: '设备费', RGGR: '人工工日', JGDEJJRGF: '甲供定额基价人工费', JGDEJJCLF: '甲供定额基价材料费', JGDEJJJXF: '甲供定额基价机械费', JGZCF: '甲供主材费', JGSBF: '甲供设备费', JDDEJJRGF: '甲定定额基价人工费', JDDEJJCLF: '甲定定额基价材料费', JDDEJJJXF: '甲定定额基价机械费', JDZCF: '甲定主材费', JDSBF: '甲定设备费', ZGCLF: '暂估材料费', FBDEJJRGF: '分包定额基价人工费', FBDEJJCLF: '分包定额基价材料费', FBDEJJJXF: '分包定额基价机械费', FBZCF: '分包主材费', FBSBF: '分包设备费', FBRGGR: '分包人工工日' }; const 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.machineLabourFee(node, node.data.gljList, 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.ptMarketPrice, isTender); }, '设备费': function (node, isTender) { return calcTools.rationBaseFee(node, [gljType.EQUIPMENT], priceTypes.ptMarketPrice, isTender); }, '人工工日': function (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; if (isTender){ calcTools.calcGLJTenderQty(node, glj); gljQ = glj.tenderQuantity; } else gljQ = glj.quantity; rst = rst + (gljQ * calcTools.uiNodeQty(node)).toDecimal(decimalObj.process); rst = rst.toDecimal(decimalObj.process); } }; return rst.toDecimal(decimalObj.glj.quantity); }, '甲供定额基价人工费': function (node, isTender) { return calcTools.partASupplyFee(node, calcBaseNames.JGDEJJRGF, isTender); }, '甲供定额基价材料费': function (node, isTender) { return calcTools.partASupplyFee(node, calcBaseNames.JGDEJJCLF, isTender); }, '甲供定额基价机械费': function (node, isTender) { return calcTools.partASupplyFee(node, calcBaseNames.JGDEJJJXF, isTender); }, '甲供主材费': function (node, isTender) { return calcTools.partASupplyFee(node, calcBaseNames.JGZCF, isTender); }, '甲供设备费': function (node, isTender) { return calcTools.partASupplyFee(node, calcBaseNames.JGSBF, isTender); }, '甲定定额基价人工费': function (node, isTender) { return calcTools.partASupplyFee(node, calcBaseNames.JDDEJJRGF, isTender); }, '甲定定额基价材料费': function (node, isTender) { return calcTools.partASupplyFee(node, calcBaseNames.JDDEJJCLF, isTender); }, '甲定定额基价机械费': function (node, isTender) { return calcTools.partASupplyFee(node, calcBaseNames.JDDEJJJXF, isTender); }, '甲定主材费': function (node, isTender) { return calcTools.partASupplyFee(node, calcBaseNames.JDZCF, isTender); }, '甲定设备费': function (node, isTender) { return calcTools.partASupplyFee(node, calcBaseNames.JDSBF, isTender); }, '暂估材料费': function (node, isTender) { return calcTools.estimateFee(node, true, isTender); }, '分包定额基价人工费': function (node, isTender) { if (node.data.isSubcontract) return this.定额基价人工费(node, isTender) else return 0; }, '分包定额基价材料费': function (node, isTender) { if (node.data.isSubcontract) return this.定额基价材料费(node, isTender) else return 0; }, '分包定额基价机械费': function (node, isTender) { if (node.data.isSubcontract) return this.定额基价机械费(node, isTender) else return 0; }, '分包主材费': function (node, isTender) { if (node.data.isSubcontract) return this.主材费(node, isTender) else return 0; }, '分包设备费': function (node, isTender) { if (node.data.isSubcontract) return this.设备费(node, isTender) else return 0; }, '分包人工工日': function (node, isTender) { if (node.data.isSubcontract) return this.人工工日(node, isTender) else return 0; } }; let analyzer = { 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 if (expression.includes(`@${ID}`)) 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'); 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)){ 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){ 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){ 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)){ hintBox.infoBox('错误提示', `定额基数“${hintBox.font(cn)}”必须用中括号${hintBox.font('[]')}括起来!`, 1); return false; } }; // 这里要加一个保险。因为上面的 for 循环在“主材费 + [主材费]” 情况下有Bug hintBox.infoBox('错误提示', `定额基数必须用中括号${hintBox.font('[]')}括起来!`, 1); return false; }; if (arrBase){ for (let base of arrBase){ let baseName = base.slice(1, -1); if (!rationCalcBases[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); hintBox.infoBox('错误提示', `表达式中 “${hintBox.font(s)}” 行号引用错误!`, 1); return false; }; }; let expression = me.getExpression(expr, template); if (me.isCycleCalc(expression, itemID, template)){ hintBox.infoBox('错误提示', `表达式中有${hintBox.font('循环计算')}!`, 1); return false; }; if (!testValue(expression)){ 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, "$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; }, 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 = 2; 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.isVolumePrice(me.treeNode) || calcTools.isGljRation(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 = []; project.registerModule(ModuleNames.calc_program, me); }; // 兼容Project框架方法 getSourceType () { return ModuleNames.calc_program; }; // 兼容Project框架方法 loadData (datas) { this.datas = datas; this.compileAllTemps(); }; // 兼容Project框架方法 doAfterUpdate (err, data) { if(!err){ $.bootstrapLoading.end(); } }; // 经测试,全部编译一次耗时0.003~0.004秒。耗时基本忽略不计。 compileAllTemps(){ let me = this; me.compiledFeeRates = {}; me.compiledLabourCoes = {}; me.compiledTemplates = {}; me.compiledTemplateMaps = {}; me.compiledTemplateNames = []; me.compiledFeeTypeMaps = {}; me.compiledFeeTypeNames = []; me.compiledCalcBases = {}; me.saveForReports = []; 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); }; // 存储费率临时数据,报表用。 if (me.saveForReports.length > 0){ let saveDatas = {}; saveDatas.projectID = projectInfoObj.projectInfo.ID; saveDatas.calcItems = me.saveForReports; CommonAjax.post('/calcProgram/saveCalcItems', saveDatas, function (result) { if (result){ me.saveForReports = []; }; }); }; }; 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.expression = analyzer.getExpression(item.dispExpr, template); 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()); } }; }; // 只计算treeNode自身。changedArr: 外部传来的一个数组,专门存储发生变动的节点。 innerCalc(treeNode, changedArr, tender){ let me = this; // 仅用作树节点显示的工料机不能参与计算。 if (treeNode.sourceType === ModuleNames.ration_glj) return; treeNode.calcType = calcTools.getCalcType(treeNode); let nQ = calcTools.uiNodeQty(treeNode); let nTQ = calcTools.uiNodeTenderQty(treeNode); function isBaseFeeType(type){ return ['labour', 'material', 'machine', 'mainMaterial', 'equipment'].indexOf(type) > -1; }; /*删掉多余的费用。例如:从其它计算方式(有很多费)切换到公式计算方式(只需要common费),多出来的费要删除。 fieldNameArr 值取自:遍历treeNode的计算规则,取有绑定的字段名。这些字段名以外的fee是因旧计算多出来的,需要删除。 */ function deleteUselessFees(treeNode, fieldNameArr){ if (fieldNameArr){ // 用于计算程序没有绑定的费用类别,不要同步到清单,而清单因为以前计算过该类别又有值,需删除。如切换取费类别,旧费要清掉。 for (var i = 0; i < treeNode.data.fees.length; i++) { let fee = treeNode.data.fees[i]; if ((fee.fieldName != 'estimate') && !fieldNameArr.includes(fee.fieldName)){ treeNode.data.fees.splice(i, 1); delete treeNode.data.feesIndex[fee.fieldName]; treeNode.changed = true; } } } else{ // 总造价清单只留common, estimate两个费用类别。其它公式清单只留common。 let reserveArr = calcTools.isTotalCostBill(treeNode)? ['common', 'estimate']:['common']; if (treeNode.data.fees && treeNode.data.fees.length > 0){ let feesArr = treeNode.data.fees; for (let i = 0; i < feesArr.length; i++) { if (!reserveArr.includes(feesArr[i].fieldName)) { delete treeNode.data.feesIndex[feesArr[i].fieldName]; feesArr.splice(i, 1); treeNode.changed = true; } } }; }; }; // 父清单汇总子项(子清单或定额)的费用类别 if (treeNode.calcType == treeNodeCalcType.ctGatherBillsFees || treeNode.calcType == treeNodeCalcType.ctGatherRationsFees){ treeNode.data.programID = null; calcTools.initFees(treeNode); let nodes = []; if (treeNode.calcType == treeNodeCalcType.ctGatherRationsFees){ calcTools.getGLJList(treeNode, true); nodes = me.project.Ration.getRationNodes(treeNode); } else nodes = treeNode.children; 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 bq = nQ ? nQ : 1; let btq = nTQ ? nTQ : 1; if (treeNode.calcType == treeNodeCalcType.ctGatherBillsFees){ 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 (treeNode.calcType == treeNodeCalcType.ctGatherRationsFees){ // 这里的算法要配合冷姐姐的神图才能看懂^_^ 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); }; if (me.project.property.billsCalcMode === leafBillGetFeeType.rationContent) { buf = (buf + (ruf * parseFloatPlus(node.data.contain)).toDecimal(decimalObj.bills.unitPrice)).toDecimal(decimalObj.process); btuf = (btuf + (rtuf * parseFloatPlus(node.data.contain)).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}; } // 叶子清单无子结点、无公式计算(啥都没有时) else if (treeNode.calcType == treeNodeCalcType.ctNull){ delete treeNode.data.gljList; // 不能直接删除该属性,否则无法冲掉库中已存储的值。下同。 if (treeNode.data.calcBase){ treeNode.data.calcBase = null; treeNode.changed = true; } if (treeNode.data.calcBaseValue){ treeNode.data.calcBaseValue = null; treeNode.changed = true; } if (treeNode.data.tenderCalcBaseValue){ treeNode.data.tenderCalcBaseValue = null; treeNode.changed = true; } if (treeNode.data.programID) { treeNode.data.programID = null; treeNode.changed = true; }; // 第1、2部分以外的叶子清单在没有公式的情况下可以手工修改综合单价并参与计算。 if(!calcTools.isInheritFrom(treeNode, [fixedFlag.SUB_ENGINERRING, fixedFlag.MEASURE])){ if (treeNode.data.feesIndex && treeNode.data.feesIndex.common){ let ftObj = {}; ftObj.fieldName = 'common'; ftObj.unitFee = parseFloatPlus(treeNode.data.feesIndex.common.unitFee); let q = parseFloatPlus(treeNode.data.quantity); ftObj.totalFee = (ftObj.unitFee * q).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": []}; } // 叶子清单公式计算 else if (treeNode.calcType == treeNodeCalcType.ctCalcBaseValue){ delete treeNode.data.gljList; if (treeNode.data.programID) { treeNode.data.programID = null; treeNode.changed = true; } let f = treeNode.data.feeRate ? treeNode.data.feeRate : 100; 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); deleteUselessFees(treeNode); 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": []}; } // 定额或叶子清单自己的计算程序计算 else{ let fnArr = []; calcTools.getGLJList(treeNode, true); if (treeNode.calcType == treeNodeCalcType.ctRationCalcProgram) { // 量价、工料机类型的定额要求市场合价 if (treeNode.data.type == rationType.volumePrice || treeNode.data.type == rationType.gljRation){ let muf = treeNode.data.marketUnitFee ? treeNode.data.marketUnitFee : 0; let mtf = (muf * nQ).toDecimal(decimalObj.ration.totalPrice); if (treeNode.data.marketTotalFee != mtf){ treeNode.data.marketTotalFee = mtf; treeNode.changed = true; } ; }; }; if (treeNode.data.programID == undefined) treeNode.data.programID = projectInfoObj.projectInfo.property.engineering; let template = me.compiledTemplates[treeNode.data.programID]; treeNode.data.calcTemplate = template; if (treeNode && template.hasCompiled) { 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); // console.log(`[${calcItem.ID}]: ${calcItem.compiledExpr}`); // for test. calcItem.unitFee = (eval(calcItem.compiledExpr) * feeRate * 0.01).toDecimal(decimalObj.decimal('unitPrice', treeNode)); calcItem.totalFee = (calcItem.unitFee * calcTools.uiNodeQty(treeNode)).toDecimal(decimalObj.decimal('totalPrice', treeNode)); if (tender == tenderTypes.ttCalc) { let tExpr = analyzer.getCompiledTenderExpr(calcItem.compiledExpr); calcItem.tenderUnitFee = (eval(tExpr) * feeRate * 0.01).toDecimal(decimalObj.decimal('unitPrice', treeNode)); calcItem.tenderTotalFee = (calcItem.tenderUnitFee * treeNode.data.tenderQuantity).toDecimal(decimalObj.decimal('totalPrice', treeNode)); }; if (calcItem.fieldName) { fnArr.push(calcItem.fieldName); calcTools.checkFeeField(treeNode, calcItem); }; }; if (tender == tenderTypes.ttReverseRation || tender == tenderTypes.ttReverseGLJ) this.calcTenderReverse(treeNode, tender); deleteUselessFees(treeNode, fnArr); }; }; if (!calcTools.isTotalCostBill(treeNode)) // 已在上面的分支中计算过 calcTools.estimateFee(treeNode); if (treeNode.changed && !changedArr.includes(treeNode)) changedArr.push(treeNode); }; // 存储、刷新零散的多个结点。 saveNodes(treeNodes, callback){ if (treeNodes.length < 1) { $.bootstrapLoading.end(); return; } let me = this; /* me.project.beginUpdate(''); for (let node of treeNodes){ if (node.changed){ let data = calcTools.cutNodeForSave(node); let newData = {'updateType': 'ut_update', 'updateData': data}; me.project.push(node.sourceType, [newData]); } }; me.project.endUpdate();*/ 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) { let endShowTime = +new Date(); console.log(`保存所需时间——${endShowTime - startTime}`); if(callback){ callback(data); }; for (let node of treeNodes){delete node.changed}; projectObj.mainController.refreshTreeNode(treeNodes); // 批量树结点计算后,计算程序早已物是人非,所以这里要重新计算一下。 if (activeSubSheetIs(subSheetIndex.ssiCalcProgram)) { calcProgramObj.refreshCurNodeCalcItems(me.project.mainTree.selected); }; $.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){ let changedNodes = this.calculate(treeNode, true, true, tender); this.saveNodes(changedNodes, callback); }; /* 计算所有树结点(分3种情况),并返回发生变动的零散的多个树结点。参数取值如下: calcAllType.catAll 计算所有树结点 (默认值) calcAllType.catBills 计算所有清单 (改变项目属性中清单取费算法时会用到) calcAllType.catRations 计算所有定额、工料机形式的定额、量价,因为它们都走自己的计算程序 (改变人工系数、费率值、工料机单价时会用到) (calcAllType.catRations时程序中做了特殊处理,实际上是计算所有树结点!) */ 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); 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); }; mergeArr(billNodes, leafBills); for (let bill of billNodes){ let changeBills = me.calculate(bill, true, false, tender); mergeArr(allChangedNodes, 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); mergeArr(changedNodes, 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 (!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); }; /*clearTenders(){ let nodes = projectObj.project.mainTree.items; for (let node of nodes) { delete node.data.targetTotalFee; delete node.data.targetUnitFee; if (node.data.feesIndex && node.data.feesIndex.common){ delete node.data.feesIndex.common.tenderUnitFee; delete node.data.feesIndex.common.tenderTotalFee; }; delete node.data.quantityCoe; delete node.data.rationQuantityCoe; }; projectObj.project.property.tenderSetting.gljPriceTenderCoe = 1; };*/ // 反向调价 calcTenderReverse(treeNode, tender){ 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{ return; } }; let coe = (treeNode.data.targetTotalFee / treeNode.data.feesIndex.common.totalFee).toDecimal(decimalObj.process); if (treeNode.data.rationQuantityCoe != coe){ treeNode.data.rationQuantityCoe = coe; treeNode.changed = true; }; treeNode.data.tenderQuantity = (treeNode.data.quantity * coe).toDecimal(decimalObj.decimal("quantity", treeNode)); 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; }; }; }; // export default analyzer;