/** * Created by CSL on 2017-07-19. * 计算程序。所有定额、清单、父清单的计算都从此入。 */ /* 新版GLD 取消了默认清单模板,所以这里废弃。先留着,预防不时之需。 let defaultBillTemplate = { ID: 15, name: "清单公式", calcItems: [ { ID: 1, code: "1", name: "定额直接费", dispExpr: "F2+F3+F4", expression: "@('2')+@('3')+@('4')", statement: "人工费+材料费+机械费", feeRate: null, memo: '' }, { ID: 2, code: "1.1", name: "人工费", dispExpr: "HJ", expression: "HJ", statement: "合计", feeRate: 50, fieldName: 'labour', memo: '' }, { ID: 3, code: "1.2", name: "材料费", dispExpr: "HJ", expression: "HJ", statement: "合计", feeRate: 30, fieldName: 'material', memo: '' }, { ID: 4, code: "1.3", name: "机械费", dispExpr: "HJ", expression: "HJ", statement: "合计", feeRate: 20, fieldName: 'machine', memo: '' }, { ID: 5, code: "2", name: "企业管理费", dispExpr: "F1", expression: "@('1')", statement: "定额直接费", feeRate: null, fieldName: 'manage', memo: '' }, { ID: 6, code: "3", name: "利润", dispExpr: "F1", expression: "@('1')", statement: "定额直接费", feeRate: null, fieldName: 'profit', memo: '' }, { ID: 7, code: "4", name: "风险费用", dispExpr: "F1", expression: "@('1')", statement: "定额直接费", feeRate: null, fieldName: 'risk', memo: '' }, { ID: 8, code: "5", name: "综合单价", dispExpr: "F1+F5+F6+F7", expression: "@('1')+@('5')+@('6')+@('7')", statement: "定额直接费+企业管理费+利润+风险费用", feeRate: null, fieldName: 'common', memo: '' } ] };*/ const baseCalcType = {baseCalc: 0, adjustCalc: 1, budgetCalc: 2, diffCalc: 3, offerCalc: 4}; let rationCalcBase = [ { 'dispName': '定额基价人工费', 'calcType': baseCalcType.baseCalc, 'gljTypes': [gljType.LABOUR] }, { 'dispName': '定额基价材料费', 'calcType': baseCalcType.baseCalc, 'gljTypes': [gljType.GENERAL_MATERIAL, gljType.CONCRETE, gljType.MORTAR, gljType.MIX_RATIO, gljType.COMMERCIAL_CONCRETE, gljType.COMMERCIAL_MORTAR] }, { 'dispName': '定额基价机械费', 'calcType': baseCalcType.baseCalc, 'gljTypes': [gljType.GENERAL_MACHINE] }, { 'dispName': '定额基价机上人工费', 'calcType': baseCalcType.baseCalc, 'gljTypes': [gljType.MACHINE_LABOUR] }, { 'dispName': '人工费价差', 'calcType': baseCalcType.diffCalc, 'gljTypes': [gljType.LABOUR] }, { 'dispName': '材料费价差', 'calcType': baseCalcType.diffCalc, 'gljTypes': [gljType.GENERAL_MATERIAL, gljType.CONCRETE, gljType.MORTAR, gljType.MIX_RATIO, gljType.COMMERCIAL_CONCRETE, gljType.COMMERCIAL_MORTAR] }, { 'dispName': '机械费价差', 'calcType': baseCalcType.diffCalc, 'gljTypes': [gljType.GENERAL_MACHINE] }, { 'dispName': '主材费', 'calcType': baseCalcType.budgetCalc, 'gljTypes': [gljType.MAIN_MATERIAL] }, { 'dispName': '设备费', 'calcType': baseCalcType.budgetCalc, 'gljTypes': [gljType.EQUIPMENT] } ]; let cpFeeTypes = [ {type: 'direct', name: '直接费'}, {type: 'labour', name: '人工费'}, {type: 'material', name: '材料费'}, {type: 'machine', name: '机械费'}, {type: 'mainMaterial', name: '主材费'}, {type: 'equipment', name: '设备费'}, {type: 'manage', name: '企业管理费'}, {type: 'profit', name: '利润'}, {type: 'risk', name: '风险费'}, {type: 'labourDiff', name: '人工价差'}, {type: 'materialDiff', name: '材料价差'}, {type: 'machineDiff', name: '机械价差'}, {type: 'adjustLabour', name: '调整人工费'}, {type: 'adjustMachineLabour', name: '调整机上人工费'}, {type: 'estimate', name: '暂估费'}, {type: 'fee1', name: '甲供材料费'}, // 模拟用户新增 {type: 'common', name: '工程造价'} ]; let analyzer = { calcTemplate: null, success: true, 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 return str; }, analyzeCalcBase: function(expr){ // 前提:必须无空格、无特殊符号 function getCalcBase(expr){ let base = '', iPos1 = -1, iPos2 = -1; for (let i = 0; i < expr.length; i++) { if (expr[i] === '['){ iPos1 = i; } else if (iPos1 != -1 && expr[i]===']'){ iPos2 = i; }; if (iPos1 != -1 && iPos2 != -1){ base = expr.slice(iPos1, iPos2 + 1); break; } }; return base; }; function calcBaseToIDExpr(base){ /*// for test. 公路模式,基数到ID let id = -1; if (base == '[人工费]'){ id = 111; } else if (base == '[材料费]'){ id = 222; } else if (base == '[机械费]'){ id = 333; } else id = "错误"; return "@('" + id + "')";*/ let baseValue = base.slice(1, -1); return "base('" + baseValue + "')"; }; while (expr.indexOf('[') > 0) { let base = getCalcBase(expr); let id = calcBaseToIDExpr(base); let baseValue = base.slice(1, -1); // []会给下面的正则带来干扰,这里去掉 var pattBase =new RegExp(baseValue, "g"); expr = expr.replace(pattBase, id); expr = expr.replace(/\[base\('/g, "base('"); // [@(' [base(' expr = expr.replace(/'\)\]/g, "')"); // ')] }; return expr; }, analyzeLineRef: function(expr){ let me = this; function isOperator(char){ var operator = "+-*/()"; return operator.indexOf(char) > -1; }; function lineNumToID(lineNum){ if (lineNum > me.calcTemplate.calcItems.length){ me.success = false; return '越界'; } else{ let id = me.calcTemplate.calcItems[lineNum - 1].ID; return id; } }; // 前提:必须无空格、无特殊符号、标准大写F function getSection(expr){ let section = '', iPos1 = -1, iPos2 = -1; for (let i = 0; i < expr.length; i++) { if (expr[i] === 'F'){ iPos1 = i; } else if (iPos1 != -1 && isOperator(expr[i])){ iPos2 = i; } else if (iPos1 != -1 && i == expr.length - 1){ iPos2 = i + 1; }; if (iPos1 != -1 && iPos2 != -1){ section = expr.slice(iPos1, iPos2); break; } }; return section; }; function sectionToIDExpr(section){ if (section){ let lineNum = section.slice(1); if (isNaN(lineNum)){ me.success = false; return '错误'; // 这里的返回提示不能加上section,因为会无限循环 } else return "@('" + lineNumToID(lineNum) + "')"; } else return ''; }; while (expr.indexOf('F') > 0) { let sec = getSection(expr); let id = sectionToIDExpr(sec); var pattSec =new RegExp(sec, "g"); expr = expr.replace(pattSec, id); }; return expr; }, analyzeUserExpr: function(calcTemplate, calcItem){ let me = this; me.calcTemplate = calcTemplate; let expr = calcItem.dispExpr; // 标准化:处理特殊字符、中文符号、大小写 expr = me.standard(expr); calcItem.dispExpr = expr; // 先换掉计算基数 expr = me.analyzeCalcBase(expr); // 再换掉行引用 expr = me.analyzeLineRef(expr); calcItem.expression = expr; return me.success; } }; let executeObj = { treeNode: null, template: null, calcBase: null, at: function(ID) { let me = executeObj, rst = 0; rst = me.template.compiledCalcItems[ID].unitFee; rst = parseFloat(rst); return rst; }, base: function(calcBaseName) { let me = executeObj, rst = 0, base = me.calcBase[calcBaseName]; if (base != null) { function isSubset(sub, arr){ for(var i = 0, len = sub.length; i < len; i++){ if(arr.indexOf(sub[i]) == -1) return false; } return true; }; // 机上人工费:多一层 function machineLabourFee() { if (!me.treeNode.data.gljList) return 0; let result = 0, mdSum = 0; for (let glj of me.treeNode.data.gljList) { if (glj.type == gljType.GENERAL_MACHINE) { // 获取机械组成物 let mds = projectObj.project.composition.getCompositionByGLJ(glj); if (!mds) mds = []; for (let md of mds){ if (base.gljTypes.indexOf(md.type) >= 0) { let q = md["consumption"] ? md["consumption"] : 0; let p = md["basePrice"] ? md["basePrice"] : 0; mdSum = mdSum + (q * p).toDecimal(decimalObj.process); mdSum = (mdSum).toDecimal(decimalObj.process); } }; result = result + (glj["quantity"] * mdSum).toDecimal(decimalObj.process); result = (result).toDecimal(decimalObj.process); }; }; return result; }; function commonGLJFee(){ if (!me.treeNode.data.gljList) return 0; let result = 0; for (let glj of me.treeNode.data.gljList) { let price = 0, temp = 0; if (base.gljTypes.indexOf(glj.type) >= 0) { if (base.calcType == baseCalcType.diffCalc){ let aprice = glj["adjustPrice"] ? glj["adjustPrice"] : 0; aprice = parseFloat(aprice); let mprice = glj["marketPrice"] ? glj["marketPrice"] : 0; mprice = parseFloat(mprice); temp = (glj["quantity"] * mprice).toDecimal(decimalObj.ration.unitPrice) - (glj["quantity"] * aprice).toDecimal(decimalObj.ration.unitPrice); temp = temp.toDecimal(decimalObj.ration.unitPrice); } else { if (base.calcType == baseCalcType.baseCalc){ price = parseFloat(glj["basePrice"]);} else if (base.calcType == baseCalcType.adjustCalc){price = parseFloat(glj["adjustPrice"]);} else if (base.calcType == baseCalcType.budgetCalc){price = parseFloat(glj["marketPrice"]);} temp = (glj["quantity"] * price).toDecimal(decimalObj.ration.unitPrice); }; result = (result + temp).toDecimal(decimalObj.ration.unitPrice); }; }; return result; }; // 量价。没有具体的工料机类型,但仍然要用定额的计算程序,所以要给计算基数直接指定。 function volumePriceFee() { let result = 0; if ( ( me.treeNode.data.subType === gljType.LABOUR && base.dispName === '定额基价人工费') || ( me.treeNode.data.subType === gljType.GENERAL_MATERIAL && base.dispName === '定额基价材料费') || ( me.treeNode.data.subType === gljType.GENERAL_MACHINE && base.dispName === '定额基价机械费') || ( me.treeNode.data.subType === gljType.MAIN_MATERIAL && base.dispName === '主材费') || ( me.treeNode.data.subType === gljType.EQUIPMENT && base.dispName === '设备费') ) result = me.treeNode.data.marketUnitFee ? me.treeNode.data.marketUnitFee : 0; return result; }; if (me.treeNode.data.type == rationType.volumePrice || me.treeNode.data.type == rationType.gljRation){ rst = volumePriceFee(); } else{ if (isSubset(base.gljTypes, [gljType.MACHINE_LABOUR])) rst = machineLabourFee() else rst = commonGLJFee(); } }; return rst; }, HJ: function () { let me = this; let p = me.treeNode.data.calcBaseValue ? me.treeNode.data.calcBaseValue : 0; let q = me.treeNode.data.quantity ? me.treeNode.data.quantity : 1; let u = (p / q).toDecimal(decimalObj.decimal('unitPrice', me.treeNode)); return u; } }; let treeNodeTools = { isBill: function(treeNode){ return treeNode.sourceType === ModuleNames.bills; }, isLeafBill: function(treeNode){ return treeNode.sourceType === ModuleNames.bills && treeNode.source.children && treeNode.source.children.length === 0; }, isNullBill: 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; }, isRation: function(treeNode){ return treeNode.sourceType === ModuleNames.ration && treeNode.data.type === rationType.ration; }, isVolumePrice: function (treeNode) { return treeNode.sourceType === ModuleNames.ration && treeNode.data.type === rationType.volumePrice; }, isGljRation: function (treeNode) { return treeNode.sourceType === ModuleNames.ration && treeNode.data.type === rationType.gljRation; }, initFees(treeNode){ if (!treeNode.data.fees) { treeNode.data.fees = []; treeNode.data.feesIndex = {}; treeNode.changed = true; }; }, initFeeField(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(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; }; }, initSummaryFee(treeNode){ if (!treeNode.data.summaryFees){ treeNode.data.summaryFees = { totalFee: 0, estimateFee: 0, safetyFee: 0, chargeFee: 0 }; treeNode.changed = true; }; }, getCalcType(treeNode) { if (this.isRationCategory(treeNode)){ return treeNodeCalcType.ctRationCalcProgram; } else if (this.isNullBill(treeNode)){ return treeNodeCalcType.ctCommonUnitFee; } 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; }; } }; 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(); 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 (data) { 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; // 中文预编译,可靠性有待验证 } }; 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 rst = '', lBracket = 0, rBracket = 0, firstIdx = idx, lastIdx = 0; for (let i = idx; i < str.length; i++) { if (str[i] === '(') { lBracket++; if (lBracket == 1) firstIdx = i + 1; } if (str[i] === ')') { rBracket++; if (lBracket == rBracket) { lastIdx = i - 1; if (lastIdx > firstIdx) { if (str[firstIdx] === "'") firstIdx++; if (str[lastIdx] !== "'") lastIdx++; if (lastIdx > firstIdx) { rst = str.slice(firstIdx, lastIdx); } } break; } } } return rst; }; let private_parse_ref = function(item, itemIdx){ let idx = item.expression.indexOf('@(', 0); while (idx >= 0) { let ID = private_extract_ID(item.expression, idx); if (ID.length > 0) { let subItem = template.compiledCalcItems[ID]; if (subItem) { if (subItem.ID !== item.ID) { private_parse_ref(subItem, template.compiledCalcItems[ID + "_idx"]); } else { template.errs.push("There exists the self refer ID: " + ID); } } else { template.errs.push("There exists the invalid ID by which could not find the item: " + ID); console.log('invalid ID: ' + ID); } } idx = item.expression.indexOf('@(', idx + ID.length + 3); } 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]; item.dispExprUser = item.dispExpr; // 用于界面显示。disExpr是公式模板,不允许修改:人工系数占位符被修改后变成数值,第二次无法正确替换。 if (item.expression == 'HJ') item.compiledExpr = '$CE.HJ()' else{ item.compiledExpr = item.expression.split('@(').join('$CE.at('); item.compiledExpr = item.compiledExpr.split('base(').join('$CE.base('); }; if (item.labourCoeID){ let lc = me.compiledLabourCoes[item.labourCoeID].coe; item.dispExprUser = item.dispExpr.replace(/L/gi, lc.toString()); item.compiledExpr = item.compiledExpr.replace(/L/gi, lc.toString()); }; if (item.feeRateID) { let orgFeeRate = item.feeRate; let cmf = me.compiledFeeRates[item.feeRateID]; item.feeRate = cmf?cmf.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]; 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()); } }; }; // 存储、刷新零散的多个结点。 saveNodes(treeNodes){ if (treeNodes.length < 1) return; let me = this; me.project.beginUpdate(''); for (let node of treeNodes){ if (node.changed){ let data = { ID: node.data.ID, projectID: me.project.ID(), unit:node.data.unit,//对清单来说,改变单位,工程量精度会跟着改变从而影响计算。 /* subType、quantity、calcBase、programID、marketUnitFee等等字段较为特殊,它们的改变一定会触发计算并导致计算 结果的变化,从而引发保存动作。将这些字段放在该位置跟计算结果一起保存,可减少前端跟后端的通讯频率。 */ subType: node.data.subType, quantity: node.data.quantity, calcBase: node.data.calcBase, calcBaseValue: node.data.calcBaseValue, programID: node.data.programID, marketUnitFee: node.data.marketUnitFee, marketTotalFee: node.data.marketTotalFee, fees: node.data.fees, isFromDetail:node.data.isFromDetail, feeRate: node.data.feeRate, feeRateID: node.data.feeRateID, contain:node.data.contain, quantityEXP:node.data.quantityEXP }; if (node.data.summaryFees) data.summaryFees = node.data.summaryFees; if(node.sourceType==ModuleNames.ration && node.data.type==rationType.gljRation){//定额类型的工料机做特殊处理 data.code=node.data.code; data.projectGLJID = node.data.projectGLJID; delete data.marketUnitFee; } let newData = {'updateType': 'ut_update', 'updateData': data}; me.project.push(node.sourceType, [newData]); } }; me.project.endUpdate(); for (let node of treeNodes){delete node.changed}; projectObj.mainController.refreshTreeNode(treeNodes); if (activeSubSheetIs(subSheetIndex.ssiCalcProgram)) { // 批量树结点计算后,计算程序早已今非昔比,所以这里要重新计算一下。 calcProgramObj.showData(me.project.mainTree.selected, true); }; }; // 只计算treeNode自身。changedArr: 外部传来的一个数组,专门存储发生变动的节点。 innerCalc(treeNode, changedArr){ let me = this; // 仅用作树节点显示的工料机不能参与计算。 if (treeNode.sourceType === me.project.ration_glj.getSourceType()) return; treeNode.calcType = treeNodeTools.getCalcType(treeNode); // if (treeNode.calcType == treeNodeCalcType.ctCalcBaseValue) return; function isBaseFeeType(type){ return ['labour', 'material', 'machine', 'mainMaterial', 'equipment'].indexOf(type) > -1; }; // 计算暂估费用 function calcEstimateFee(treeNode){ let sumU = 0, sumT = 0; if (treeNodeTools.isTotalCostBill(treeNode)){ let nodes = projectObj.project.mainTree.roots; for (let node of nodes){ if (treeNodeTools.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 (!treeNode.data.gljList) return; let eTypes = [ gljType.GENERAL_MATERIAL, gljType.MAIN_MATERIAL, gljType.EQUIPMENT, gljType.CONCRETE, gljType.MORTAR, gljType.MIX_RATIO, gljType.COMMERCIAL_CONCRETE, gljType.COMMERCIAL_MORTAR]; let eDetailTypes = [gljType.MAIN_MATERIAL, gljType.CONCRETE, gljType.MORTAR, gljType.MIX_RATIO]; let GLJObjs = []; for (let glj of treeNode.data.gljList) { if (eTypes.indexOf(glj.type) >= 0) { if (glj.isEstimate){ 'code','name','specs','unit','type' GLJObjs.push({code: glj.code, name: glj.name, specs: glj.specs, unit: glj.unit, type: glj.type, quantity: glj.quantity, marketPrice: glj.marketPrice}); } else{ // 组成物 if (eDetailTypes.indexOf(glj.type) >= 0){ let mds = projectObj.project.composition.getCompositionByGLJ(glj); if (!mds) mds = []; for (let md of mds){ if (md.isEstimate){ let isExist = false; let mdQ = (parseFloatPlus(glj.quantity) * parseFloatPlus(md.consumption)).toDecimal(decimalObj.process); for (let obj of GLJObjs){ if (gljOprObj.getIndex(md, gljKeyArray) == gljOprObj.getIndex(obj, gljKeyArray)){ isExist = true; obj.quantity = (parseFloatPlus(obj.quantity) + mdQ).toDecimal(decimalObj.process); break; } }; if (!isExist) GLJObjs.push({code: md.code, quantity: mdQ, marketPrice: md.marketPrice}); } } } } }; }; for (let obj of GLJObjs){ sumU = sumU + (parseFloatPlus(obj.quantity) * parseFloatPlus(obj.marketPrice)).toDecimal(decimalObj.process); sumU = sumU.toDecimal(decimalObj.process); let q = (parseFloatPlus(obj.quantity) * parseFloatPlus(treeNode.data.quantity)).toDecimal(decimalObj.process); sumT = sumT + (q * parseFloatPlus(obj.marketPrice)).toDecimal(decimalObj.process); sumT = sumT.toDecimal(decimalObj.process); }; sumU = sumU.toDecimal(decimalObj.bills.unitPrice); if (projectObj.project.property.zanguCalcMode == zanguCalcType.common){ sumT = (parseFloatPlus(treeNode.data.quantity) * sumU).toDecimal(decimalObj.bills.totalPrice); } else if (projectObj.project.property.zanguCalcMode == zanguCalcType.gatherMaterial){ sumT = sumT.toDecimal(decimalObj.bills.totalPrice); }; }; treeNodeTools.checkFeeField(treeNode, {'fieldName': 'estimate', 'unitFee': sumU, 'totalFee': sumT}); }; // 删掉多余的费用。例如:从其它计算方式切换到公式计算方式,会多出其它的费(不光是common) function deleteUselessFees(treeNode){ let reserveArr = treeNodeTools.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.ctGatherRationsFees || treeNode.calcType == treeNodeCalcType.ctGatherBillsFees){ treeNode.data.programID = null; treeNodeTools.initFees(treeNode); let objsArr = (treeNode.calcType == treeNodeCalcType.ctGatherRationsFees) ? me.project.Ration.getRationsByNode(treeNode) : 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; if (treeNode.calcType == treeNodeCalcType.ctGatherBillsFees){ for (let item of objsArr) { let data = item.data; if (data.feesIndex && data.feesIndex[ft.type]) { buf = (buf + parseFloatPlus(data.feesIndex[ft.type].unitFee)).toDecimal(decimalObj.process); btf = (btf + parseFloatPlus(data.feesIndex[ft.type].totalFee)).toDecimal(decimalObj.process); btuf = (btuf + parseFloatPlus(data.feesIndex[ft.type].tenderUnitFee)).toDecimal(decimalObj.process); bttf = (bttf + parseFloatPlus(data.feesIndex[ft.type].tenderTotalFee)).toDecimal(decimalObj.process); }; }; } else if (treeNode.calcType == treeNodeCalcType.ctGatherRationsFees){ // 这里的算法要配合冷姐姐的神图才能看懂^_^ let sum_rtf = 0, sum_rttf = 0; let bq = parseFloat(treeNode.data.quantity ? treeNode.data.quantity : 1); for (let data of objsArr) { let rq = parseFloat(data.quantity ? data.quantity : 0); let ruf = 0, rtuf = 0, rtf = 0, rttf = 0; if (data.feesIndex && data.feesIndex[ft.type]) { ruf = parseFloat(data.feesIndex[ft.type].unitFee); rtuf = parseFloat(data.feesIndex[ft.type].tenderUnitFee); rtf = parseFloat(data.feesIndex[ft.type].totalFee); rttf = parseFloat(data.feesIndex[ft.type].tenderTotalFee); }; if (me.project.property.billsCalcMode === leafBillGetFeeType.rationContent) { buf = (buf + (ruf * rq / bq).toDecimal(decimalObj.process)).toDecimal(decimalObj.process); btuf = (btuf + (rtuf * rq / bq).toDecimal(decimalObj.process)).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 / bq).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 * bq).toDecimal(decimalObj.process); bttf = (btuf * bq).toDecimal(decimalObj.process); }; }; ftObj.unitFee = buf.toDecimal(decimalObj.bills.unitPrice); ftObj.totalFee = btf.toDecimal(decimalObj.bills.totalPrice); ftObj.tenderUnitFee = btuf.toDecimal(decimalObj.bills.unitPrice); ftObj.tenderTotalFee = bttf.toDecimal(decimalObj.bills.totalPrice); treeNodeTools.checkFeeField(treeNode, ftObj); rst.push(ftObj); }; treeNode.data.calcTemplate = {"calcItems": rst}; } // 叶子清单的手工综合单价计算 else if (treeNode.calcType == treeNodeCalcType.ctCommonUnitFee){ delete treeNode.data.gljList; if (treeNode.data.calcBase) treeNode.data.calcBase = null; // 不能直接删除该属性,否则无法冲掉库中已存储的值 if (treeNode.data.calcBaseValue) treeNode.data.calcBaseValue = null; // 不能直接删除该属性,否则无法冲掉库中已存储的值 if (treeNode.data.programID) treeNode.data.programID = null; let uf = (treeNode.data.feesIndex && treeNode.data.feesIndex.common && treeNode.data.feesIndex.common.unitFee) ? treeNode.data.feesIndex.common.unitFee : 0; uf = uf.toDecimal(decimalObj.bills.unitPrice); let tuf = (treeNode.data.feesIndex && treeNode.data.feesIndex.common && treeNode.data.feesIndex.common.tenderUnitFee) ? treeNode.data.feesIndex.common.tenderUnitFee : 0; tuf = tuf.toDecimal(decimalObj.bills.unitPrice); let q = treeNode.data.quantity ? treeNode.data.quantity : 0; let tf = (uf * q).toDecimal(decimalObj.bills.totalPrice); let ttf = (tuf * q).toDecimal(decimalObj.bills.totalPrice); deleteUselessFees(treeNode); treeNodeTools.checkFeeField(treeNode, {'fieldName': 'common', 'unitFee': uf, 'totalFee': tf}); treeNode.data.calcTemplate = {"calcItems": []}; } // 叶子清单公式计算 else if (treeNode.calcType == treeNodeCalcType.ctCalcBaseValue){ delete treeNode.data.gljList; if (treeNode.data.programID) treeNode.data.programID = null; let f = treeNode.data.feeRate ? treeNode.data.feeRate : 100; if (!treeNode.data.quantity) treeNode.data.quantity = 1; let q = treeNode.data.quantity; let b = treeNode.data.calcBaseValue ? treeNode.data.calcBaseValue : 0; let uf = (b * f * q / 100).toDecimal(decimalObj.bills.unitPrice); let tuf = uf; let tf = (me.project.property.billsCalcMode === leafBillGetFeeType.rationPrice) ? (b * f / 100).toDecimal(decimalObj.bills.totalPrice) : (uf * q).toDecimal(decimalObj.bills.totalPrice); let ttf = tf; deleteUselessFees(treeNode); treeNodeTools.checkFeeField(treeNode, {'fieldName': 'common', 'unitFee': uf, 'totalFee': tf}); // 总造价清单还要做单项工程、建设项目的四大项金额汇总 if (treeNodeTools.isTotalCostBill(treeNode)){ // 公式叶子清单没有暂估费,但总造价清单除外。 calcEstimateFee(treeNode); treeNodeTools.initSummaryFee(treeNode); treeNode.data.summaryFees.totalFee = tf; treeNode.data.summaryFees.estimateFee = (treeNode.data.feesIndex && treeNode.data.feesIndex.estimate) ? treeNode.data.feesIndex.estimate.totalFee:0; let bill_safe = cbTools.findBill(fixedFlag.SAFETY_CONSTRUCTION); treeNode.data.summaryFees.safetyFee = (bill_safe && bill_safe.feesIndex && bill_safe.feesIndex.common)?bill_safe.feesIndex.common.totalFee:0; let bill_charge = cbTools.findBill(fixedFlag.CHARGE); treeNode.data.summaryFees.chargeFee = (bill_charge && bill_charge.feesIndex && bill_charge.feesIndex.common)?bill_charge.feesIndex.common.totalFee:0; } treeNode.data.calcTemplate = {"calcItems": []}; } // 定额或叶子清单自己的计算程序计算 else{ if (treeNode.calcType == treeNodeCalcType.ctRationCalcProgram) { if (treeNode.data.type == rationType.volumePrice){ delete treeNode.data.gljList; let muf = treeNode.data.marketUnitFee ? treeNode.data.marketUnitFee : 0; let q = treeNode.data.quantity ? treeNode.data.quantity : 0; treeNode.data.marketTotalFee = (muf * q).toDecimal(decimalObj.ration.totalPrice); } else if (treeNode.data.type == rationType.gljRation){ } else{ treeNode.data.gljList = me.project.ration_glj.getGljArrByRation(treeNode.data.ID); // 计算程序里没有暂估费的计算规则,会漏掉,所以这里要专门算。 calcEstimateFee(treeNode); }; if (treeNode.data.programID == undefined){ treeNode.data.programID = projectInfoObj.projectInfo.property.engineering; }; } else if (treeNode.calcType == treeNodeCalcType.ctBillCalcProgram) { let rations = me.project.Ration.getBillsSortRation(treeNode.source.getID()); treeNode.data.gljList = me.project.ration_glj.getGatherGljArrByRations(rations); if (treeNode.data.programID == undefined){ treeNode.data.programID = projectInfoObj.projectInfo.property.engineering; } // 叶子清单自己的计算程序计算,其暂估费也要汇总算。 calcEstimateFee(treeNode); }; let template = me.compiledTemplates[treeNode.data.programID]; treeNode.data.calcTemplate = template; if (treeNode && template.hasCompiled) { let $CE = executeObj; $CE.treeNode = treeNode; $CE.template = template; $CE.calcBase = me.compiledCalcBases; treeNodeTools.initFees(treeNode); for (let idx of template.compiledSeq) { let calcItem = template.calcItems[idx]; let feeRate = calcItem.feeRate; if (!feeRate) feeRate = 100; // 100% feeRate = feeRate.toDecimal(decimalObj.feeRate); calcItem.unitFee = (eval(calcItem.compiledExpr) * feeRate * 0.01).toDecimal(decimalObj.decimal('unitPrice', treeNode)); // 如果eval()对清单树有影响,就换成小麦的Expression对象再试 let quantity = treeNode.data.quantity; if (!quantity) quantity = 0 else quantity = parseFloat(quantity).toDecimal(decimalObj.decimal('quantity', treeNode)); calcItem.totalFee = (calcItem.unitFee * quantity).toDecimal(decimalObj.decimal('totalPrice', treeNode)); treeNodeTools.checkFeeField(treeNode, calcItem); }; }; }; if (treeNode.changed && !changedArr.includes(treeNode)) changedArr.push(treeNode); }; // 计算本节点、所有父节点(默认,可选)、公式引用节点(默认,可选)。 calculate(treeNode, calcParents = true, calcFormulas = true){ let me = this; let changedNodes = []; me.innerCalc(treeNode, changedNodes); if (treeNode.changed) { // 计算父结点 if (calcParents){ let curNode = treeNode.parent; while (curNode){ me.innerCalc(curNode, changedNodes); curNode = curNode.parent; }; }; // 父结点算完,再计算所有的公式结点(必须先算完父结点,再算公式结点) if (calcFormulas) { me.calcFormulaNodes(changedNodes); }; }; return changedNodes; }; /* 计算所有树结点(分3种情况),并返回发生变动的零散的多个树结点。参数取值如下: calcAllType.catAll 计算所有树结点 (不指定参数时的默认值) calcAllType.catBills 计算所有清单 (改变项目属性中清单取费算法时会用到) calcAllType.catRations 计算所有定额、工料机形式的定额、量价,因为它们都走自己的计算程序 (改变人工系数、费率值、工料机单价时会用到) */ calcAllNodes(calcType = calcAllType.catAll){ 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 != treeNodeCalcType.ctCalcBaseValue) { me.innerCalc(node, changedNodes); }; } }; calcNodes(me.project.mainTree.roots); me.calcFormulaNodes(changedNodes); return changedNodes; }; // 计算全部公式项。 (参数意义:将通过本方法后发生改变的节点存入changedNodesArr中) calcFormulaNodes(changedArr){ 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); // 计算父结点 if (formulaNode.changed){ let curNode = formulaNode.parent; while (curNode){ me.innerCalc(curNode, changedArr); curNode = curNode.parent; }; }; }; }; }; // 计算叶子清单下的所有子结点、自身、所有父结点、公式引用结点(即跟该叶子清单相关的所有结点)。最后打包存储。 calcLeafAndSave(treeNode){ let me = this; if(!treeNodeTools.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); mergeArr(changedNodes, curChangeds); me.saveNodes(changedNodes); }; }; // 计算多条零散的定额,并计算他们所属的清单、父级清单,然后打包存储。如:批量替换工料机后受影响的定额。 calcRationsAndSave(rationNodes){ let me = this, leafBills = [], allChangedNodes = []; for (let node of rationNodes) { me.innerCalc(node, allChangedNodes); let leafBill = node.parent; // 多条定额同属一条叶子清单时,避免叶子清单重复计算 if (leafBill && leafBills.indexOf(leafBill) < 0) leafBills.push(leafBill); }; for (let node of leafBills){ let curChangeds = me.calculate(node); mergeArr(allChangedNodes, curChangeds); }; me.saveNodes(allChangedNodes); }; // 计算多条零散的清单,并计算他们的父清单、引用清单,然后打包存储。如:花选删除树结点(如花选清单、定额等,不区分树结点类型)。 calcBillsAndSave(billNodes){ let me = this, allChangedNodes = []; for (let node of billNodes) { let curChangeds = me.calculate(node, true, false); mergeArr(allChangedNodes, curChangeds); }; me.calcFormulaNodes(allChangedNodes); me.saveNodes(allChangedNodes); }; // 计算并保存指定的一个树节点。修改一个树节点,实际上要计算和保存的是一批树结点:层层父结点、被其它结点(的公式)引用的公式结点。 // 这个方法实际上封装了calculate()和saveNodes()两个方法,主要目的是为了外部调用方便,少写一点累赘代码。 calcAndSave(treeNode){ let changedNodes = this.calculate(treeNode); this.saveNodes(changedNodes); }; }