/** * Created by CSL on 2017-07-19. * 计算程序。所有定额、清单、父清单的计算都从此入。 * dispExpr: F8*(L-1); expression: "@('8') * (L-1)"; * 说明:F后跟行号,L替换人工系数值,@后跟ID。用到L的规则必须有labourCoeID属性(反过来不要求), * 用到费率的规则必须有feeRateID属性,当有该属性时,会自动显示费率值。 */ /*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 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.getCompositionByCode(glj.code); if (!mds) mds = []; for (let md of mds){ if (base.gljTypes.indexOf(md.glj_type) >= 0) { let q = md["consumption"] ? md["consumption"] : 0; let p = md["base_price"] ? md["base_price"] : 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; if (base.gljTypes.indexOf(glj.type) >= 0) { 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"]);} else if (base.calcType == baseCalcType.diffCalc){ let aprice = glj["adjustPrice"] ? glj["adjustPrice"] : 0; let mprice = glj["marketPrice"] ? glj["marketPrice"] : 0; price = (parseFloat(mprice) - parseFloat(aprice)).toDecimal(decimalObj.process); }; result = result + (glj["quantity"] * price).toDecimal(decimalObj.process); result = (result).toDecimal(decimalObj.process); }; }; 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; } }; class CalcProgram { constructor(project){ let me = this; me.project = project; me.datas = []; project.registerModule(ModuleNames.calc_program, me); }; getSourceType () { return ModuleNames.calc_program; }; loadData (datas) { this.datas = datas; }; 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 = feeType; 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_setup_seq = function(item, itemIdx){ if (template.compiledSeq.indexOf(itemIdx) < 0) { private_parse_ref(item, 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++) { private_setup_seq(template.calcItems[i], i); } if (template.errs.length == 0) { private_compile_items(); template.hasCompiled = true; } else { console.log('errors: ' + template.errs.toString()); } }; }; isLeafBill(treeNode){ let me = this; return treeNode.sourceType === me.project.Bills.getSourceType() && treeNode.source.children && treeNode.source.children.length === 0; }; isNullBill(treeNode){ let me = this; return me.isLeafBill(treeNode) && (treeNode.children.length ===0) && (!treeNode.data.calcBase); }; initFeeField(treeNode, fieldName){ if (!treeNode.data.fees) { treeNode.data.fees = []; treeNode.data.feesIndex = {}; }; 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; }; }; // 仅内部调用。注意:外部不能直接使用,因为这里传入的树节点必须有一定的初始化。 InnerCalc(treeNode){ let me = this; let project = me.project; function initFees(treeNode){ if (!treeNode.data.fees) { treeNode.data.fees = []; treeNode.data.feesIndex = {}; treeNode.changed = true; }; }; function checkFee(treeNode, feeObj){ if (feeObj.fieldName == '') return; if (!treeNode.data.feesIndex[feeObj.fieldName]){ let fee = { 'fieldName': feeObj.fieldName, 'unitFee': feeObj.unitFee, 'totalFee': feeObj.totalFee, 'tenderUnitFee': 0, 'tenderTotalFee': 0 }; treeNode.data.fees.push(fee); treeNode.data.feesIndex[feeObj.fieldName] = fee; treeNode.changed = true; } else{ 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; }; }; }; function isBaseFeeType(type){ return ['labour', 'material', 'machine', 'mainMaterial', 'equipment'].indexOf(type) > -1; }; // 汇总定额或子清单的费用类别 if (treeNode.calcType == treeNodeCalcType.ctGatherRationsFees || treeNode.calcType == treeNodeCalcType.ctGatherBillsFees){ treeNode.data.programID = null; initFees(treeNode); let objsArr = (treeNode.calcType == treeNodeCalcType.ctGatherRationsFees) ? project.Ration.getRationsByNode(treeNode) : treeNode.children; let rst = []; for (let ft of feeType) { 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.projSetting.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.projSetting.billsCalcMode === leafBillGetFeeType.rationPriceConverse || me.project.projSetting.billsCalcMode === leafBillGetFeeType.rationPrice) { buf = (sum_rtf / bq).toDecimal(decimalObj.process); btuf = (sum_rttf / bq).toDecimal(decimalObj.process); }; if (isBaseFeeType(ft.type) || (me.project.projSetting.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); checkFee(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; let tuf = (treeNode.data.feesIndex && treeNode.data.feesIndex.common && treeNode.data.feesIndex.common.tenderUnitFee) ? treeNode.data.feesIndex.common.tenderUnitFee : 0; 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); delete treeNode.data.fees; // 直接删掉再新增,不用一个个费判断更新,效率更高。 delete treeNode.data.feesIndex; me.initFeeField(treeNode, 'common'); treeNode.data.feesIndex.common.unitFee = uf.toDecimal(decimalObj.bills.unitPrice); treeNode.data.feesIndex.common.totalFee = tf.toDecimal(decimalObj.bills.totalPrice); treeNode.data.feesIndex.common.tenderUnitFee = tuf.toDecimal(decimalObj.bills.unitPrice); treeNode.data.feesIndex.common.tenderTotalFee = ttf.toDecimal(decimalObj.bills.totalPrice); 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; let f = treeNode.data.feeRate ? treeNode.data.feeRate : 100; let q = treeNode.data.quantity ? treeNode.data.quantity : 0; 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.projSetting.billsCalcMode === leafBillGetFeeType.rationPrice) ? (b * f / 100).toDecimal(decimalObj.bills.totalPrice) : (uf * q).toDecimal(decimalObj.bills.totalPrice); let ttf = tf; delete treeNode.data.fees; // 直接删掉再新增,不用一个个费判断更新,效率更高。 delete treeNode.data.feesIndex; me.initFeeField(treeNode, 'common'); treeNode.data.feesIndex.common.unitFee = uf; treeNode.data.feesIndex.common.totalFee = tf; treeNode.data.feesIndex.common.tenderUnitFee = tuf; treeNode.data.feesIndex.common.tenderTotalFee = ttf; treeNode.changed = true; 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); }; if (treeNode.data.programID == undefined){ treeNode.data.programID = projectInfoObj.projectInfo.property.engineering; }; } else if (treeNode.calcType == treeNodeCalcType.ctBillCalcProgram) { let rations = project.Ration.getBillsSortRation(treeNode.source.getID()); treeNode.data.gljList = project.ration_glj.getGatherGljArrByRations(rations); // if (treeNode.data.programID == undefined || treeNode.data.programID == defaultBillTemplate.ID){ 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; $CE.calcBase = me.compiledCalcBases; initFees(treeNode); for (let idx of template.compiledSeq) { let calcItem = template.calcItems[idx]; let feeRate = calcItem.feeRate; if (!feeRate) feeRate = 100; // 100% calcItem.unitFee = (eval(calcItem.compiledExpr) * feeRate * 0.01).toDecimal(decimalObj.decimal('unitPrice', treeNode)); // 如果eval()对清单树有影响,就换成小麦的Expression对象再试 let quantity = treeNode.data.quantity; if (!quantity) quantity = 0; calcItem.totalFee = (calcItem.unitFee * quantity).toDecimal(decimalObj.decimal('totalPrice', treeNode)); checkFee(treeNode, calcItem); }; } }; }; // 计算本节点(默认同时递归计算所有父节点,可选) calculate(treeNode, calcParents = true){ let me = this; let isRation = treeNode.sourceType === me.project.Ration.getSourceType(); let isBill = treeNode.sourceType === me.project.Bills.getSourceType(); if (isRation){ treeNode.calcType = treeNodeCalcType.ctRationCalcProgram; } else if (me.isNullBill(treeNode)){ treeNode.calcType = treeNodeCalcType.ctCommonUnitFee; } else if (me.isLeafBill(treeNode)) { if (treeNode.children && treeNode.children.length > 0){ // me.calcLeafBillChildren(treeNode); // 清单单价计算模式下的叶子清单:取自己的计算程序ID,找到自己的计算程序计算。(汇总清单所有定额的工料机) if (me.project.projSetting.billsCalcMode === leafBillGetFeeType.billsPrice) treeNode.calcType = treeNodeCalcType.ctBillCalcProgram; else // 前三种计算模式下的叶子清单:汇总定额的计算程序的费用类别 treeNode.calcType = treeNodeCalcType.ctGatherRationsFees; } else{ // 公式计算 treeNode.calcType = treeNodeCalcType.ctCalcBaseValue; }; } else if (isBill) // 父清单:汇总子清单的费用类别 treeNode.calcType = treeNodeCalcType.ctGatherBillsFees; me.InnerCalc(treeNode); // 计算所有父结点 if (treeNode.changed && calcParents && treeNode.parent) { me.calculate(treeNode.parent); }; }; // 存储、刷新本节点(默认存储刷新所有父节点,可选) saveNode(treeNode, saveParents = true) { if (!treeNode.changed) return; let me = this; let nodesArr = []; let curNode = treeNode; while (curNode) { if (curNode.changed){nodesArr.push(curNode)}; if (saveParents) curNode = curNode.parent else break; }; me.saveNodes(nodesArr); }; // 多个树结点入库存储,刷新界面显示。 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(), /* 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 }; 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, false); }; }; /* 计算所有树结点(分3种情况),并将发生计算改动的结点入库存储。 参数取值如下: calcAllType.catAll 计算所有树结点 (不指定参数时的默认值) calcAllType.catBills 计算所有清单 (改变项目属性中清单取费算法时会用到) calcAllType.catRations 计算所有定额、工料机形式的定额、量价,因为它们都走自己的计算程序 (改变人工系数、费率值、工料机单价时会用到) */ calcAllNodes(calcType = calcAllType.catAll){ let me = this; let needSaveNodes = []; function calcNodes(nodes) { for (let node of nodes) { if (node.children.length > 0) { calcNodes(node.children); }; if ((calcType == calcAllType.catAll) || (calcType == node.sourceType)) { me.calculate(node, false); if (node.changed) needSaveNodes.push(node); }; } }; calcNodes(me.project.mainTree.roots); me.saveNodes(needSaveNodes); }; // 重新计算叶子清单下的所有子结点:如定额、工料机定额等(calculate算法基于定额、工料机定额的计算结果是正确的,实际上有时它们的计算结果并不是最新的) calcLeafBillChildren(treeNode){ let me = this; if(!me.isLeafBill(treeNode)) return; if (treeNode.children && treeNode.children.length > 0) { let needSaveNodes = []; for (let child of treeNode.children){ me.calculate(child, false); if (child.changed) needSaveNodes.push(child); }; me.saveNodes(needSaveNodes); }; }; }