| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440 | /** * 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;    }};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;        let private_compile_feeRateFile = function() {            if (feeRates) {                me.compiledFeeRates = {};                for (let rate of feeRates) {                    me.compiledFeeRates["feeRate_" + rate.ID] = rate;                }            }        };        let private_compile_labourCoeFile = function() {            if (labourCoes) {                me.compiledLabourCoes = {};                for (let coe of labourCoes) {                    me.compiledLabourCoes["LabourCoe_" + coe.ID] = coe;                }            }        };        let private_compile_feeType = function() {            if (feeTypes) {                me.compiledFeeTypes = {};                me.compiledFeeTypeNames = [];                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) {                me.compiledCalcBases = {};                for (let cb of calcBases) {                    me.compiledCalcBases[cb.dispName] = cb;         // 中文预编译,可靠性有待验证                }            }        };        private_compile_feeRateFile();        private_compile_labourCoeFile();        private_compile_feeType();        private_compile_calcBase();        me.compiledTemplates = {};        me.saveForReports = [];    };    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是公式模板,不允许修改:人工系数占位符被修改后变成数值,第二次无法正确替换。                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;        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 = {};            };            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 (!$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;                }                else{                    $treeNode.data.feesIndex[calcItem.fieldName].unitFee = calcItem.unitFee;                    $treeNode.data.feesIndex[calcItem.fieldName].totalFee = calcItem.totalFee;                }            }        }    }};/*export default analyzer;*/
 |