/** * Created by Tony on 2017/6/21. * Modified by CSL, 2017-08-01 引入多套计算程序、费率同步、人工系数同步、改进基数计算、费字段映射等。 * added by CSL, 2017-09-01 增加公式解析对象analyzer,用于解析用户修改公式、自定义表达式。 */ // 需求说小数位数固定为2位,这里预留缓冲接口,防止以后需求变卦。 const Digit_Calc_Program = -2; // 需求指定计算程序用到的小数位数。 const Digit_Calc_Program_Default = -6; // 需求末指定时默认用到的小数位数。 function round(value, useDef = false) { let digit = useDef ? Digit_Calc_Program_Default : Digit_Calc_Program; return scMathUtil.roundTo(value, digit); }; 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 = getRationCalcBase(calcBaseName); base = me.calcBase[calcBaseName]; if (base != null) { let price = 0, aprice = 0, mprice = 0, tmpSum = 0, mdSum = 0; function isSubset(sub, arr){ // if(!(sub instanceof Array) || !(arr instanceof Array)) return false; // if(sub.length > arr.length) return false; for(var i = 0, len = sub.length; i < len; i++){ if(arr.indexOf(sub[i]) == -1) return false; } return true; } // 机上人工费:多一层 if (isSubset(base.gljTypes, [gljType.MACHINE_LABOUR])) { 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) { price = md["base_price"]; if (!price) price = 0; mdSum = mdSum + round(md["consumption"] * price); mdSum = round(mdSum, true); } }; tmpSum = tmpSum + round(glj["quantity"] * mdSum, true); tmpSum = round(tmpSum, true); } }; }else{ for (let glj of me.treeNode.data.gljList) { if (base.gljTypes.indexOf(glj.type) >= 0) { if (base.calcType == baseCalc){ price = glj["basePrice"];} else if (base.calcType == adjustCalc){price = glj["adjustPrice"];} else if (base.calcType == budgetCalc){price = glj["marketPrice"];} else if (base.calcType == diffCalc){ aprice = glj["adjustPrice"]; if (!aprice) aprice = 0; mprice = glj["marketPrice"]; if (!mprice) mprice = 0; price = mprice - aprice; }; if (!price) price = 0; tmpSum = round(tmpSum + round(glj["quantity"] * price, true), true); }; }; }; rst = round(tmpSum); }; return rst; }, HJ: function () { let me = this; return me.treeNode.data.baseTotalPrice; } }; 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; } }; class Calculation { // 先编译公用的基础数据 compilePublics(feeRates, labourCoes, feeTypes, calcBases){ let me = this; me.compiledFeeRates = {}; me.compiledLabourCoes = {}; me.compiledTemplates = {}; me.compiledFeeTypes = {}; me.compiledFeeTypeNames = []; me.compiledCalcBases = {}; me.saveForReports = []; let private_compile_feeRateFile = function() { if (feeRates) { for (let rate of feeRates) { me.compiledFeeRates["feeRate_" + rate.ID] = rate; } } }; let private_compile_labourCoeFile = function() { if (labourCoes) { for (let coe of labourCoes) { me.compiledLabourCoes["LabourCoe_" + coe.ID] = coe; } } }; let private_compile_feeType = function() { if (feeTypes) { for (let ft of feeTypes) { me.compiledFeeTypes[ft.type] = ft.name; me.compiledFeeTypes[ft.name] = ft.type; // 中文预编译,可靠性有待验证 me.compiledFeeTypeNames.push(ft.name); } } }; let private_compile_calcBase = function() { if (calcBases) { for (let cb of calcBases) { me.compiledCalcBases[cb.dispName] = cb; // 中文预编译,可靠性有待验证 } } }; private_compile_feeRateFile(); private_compile_labourCoeFile(); private_compile_feeType(); private_compile_calcBase(); }; compileTemplate(template){ let me = this; me.compiledTemplates[template.ID] = template; 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["LabourCoe_" + 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["feeRate_" + 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.compiledFeeTypes[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()); } }; }; calculate(treeNode){ let me = this; let templateID = treeNode.data.programID; if (!templateID) templateID = 1; let template = me.compiledTemplates[templateID]; treeNode.data.calcTemplate = template; let project = projectObj.project; // 缺省计算程序需要提供总金额作为计算基数,然后每条按比例(费率)计算,不需要工料机明细。 if (treeNode.data.baseTotalPrice != undefined){ delete treeNode.data.gljList; } else { if (treeNode.sourceType === project.Ration.getSourceType()) { treeNode.data.gljList = project.ration_glj.getGljArrByRation(treeNode.data.ID); } else if (treeNode.sourceType === project.Bills.getSourceType()) { let rations = project.Ration.getBillsSortRation(treeNode.source.getID()); treeNode.data.gljList = project.ration_glj.getGatherGljArrByRations(rations); }; }; if (treeNode && template.hasCompiled) { let $CE = executeObj; $CE.treeNode = treeNode; $CE.template = template; $CE.calcBase = me.compiledCalcBases; if (!treeNode.data.fees) { treeNode.data.fees = []; treeNode.data.feesIndex = {}; treeNode.changed = true; }; for (let idx of template.compiledSeq) { let calcItem = template.calcItems[idx]; let feeRate = calcItem.feeRate; if (!feeRate) feeRate = 100; // 100% calcItem.unitFee = round(eval(calcItem.compiledExpr) * feeRate * 0.01); // 如果eval()对清单树有影响,就换成小麦的Expression对象再试 let quantity = treeNode.data.quantity; if (!quantity) quantity = 0; calcItem.totalFee = round(calcItem.unitFee * quantity); // 费用同步到定额 // 引入小麦的字段检测后,快速切换定额出现计算卡顿现象,过多的循环造成。这里把她的代码拆出来,减少微循环。 if (calcItem.fieldName != '') { if (!treeNode.data.feesIndex[calcItem.fieldName]){ let fee = { 'fieldName': calcItem.fieldName, 'unitFee': calcItem.unitFee, 'totalFee': calcItem.totalFee, 'tenderUnitFee': 0, 'tenderTotalFee': 0 }; treeNode.data.fees.push(fee); treeNode.data.feesIndex[calcItem.fieldName] = fee; treeNode.changed = true; } else{ if (treeNode.data.feesIndex[calcItem.fieldName].unitFee != calcItem.unitFee){ treeNode.data.feesIndex[calcItem.fieldName].unitFee = calcItem.unitFee; treeNode.changed = true; }; if (treeNode.data.feesIndex[calcItem.fieldName].totalFee != calcItem.totalFee){ treeNode.data.feesIndex[calcItem.fieldName].totalFee = calcItem.totalFee; treeNode.changed = true; }; } }; }; } } }; /* export default analyzer;*/