Просмотр исходного кода

算法结构大调整,减少一层单元嵌套。

Chenshilong 7 лет назад
Родитель
Сommit
cac551a803

+ 1 - 1
config/gulpConfig.js

@@ -79,7 +79,7 @@ module.exports = {
         'web/building_saas/main/js/calc/calc_fees.js',
         'web/building_saas/main/js/calc/ration_calc.js',
         'web/building_saas/main/js/calc/bills_calc.js',
-        'public/calc_util.js',
+        // 'public/calc_util.js',
         'public/web/tree_sheet/tree_sheet_controller.js',
         'public/web/tree_sheet/tree_sheet_helper.js',
         'public/web/sheet/sheet_data_helper.js',

+ 0 - 502
public/calc_util.js

@@ -4,510 +4,8 @@
  * added by CSL, 2017-09-01 增加公式解析对象analyzer,用于解析用户修改公式、自定义表达式。
  */
 
-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 + (md["consumption"] * price).toDecimal(me.digit);
-                                   mdSum = (mdSum).toDecimal(me.digitDefault);
-                               }
-                           };
-                           tmpSum = tmpSum + (glj["quantity"] * mdSum).toDecimal(me.digitDefault);
-                           tmpSum = (tmpSum).toDecimal(me.digitDefault);
-                       }
-                };
-            }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 = tmpSum + (glj["quantity"] * price).toDecimal(me.digitDefault);
-                        tmpSum = (tmpSum).toDecimal(me.digitDefault);
-                    };
-                };
-            };
-
-            rst = (tmpSum).toDecimal(me.digitDefault);
-        };
-
-        return rst;
-    },
-    HJ: function () {
-        let me = this;
-        return me.treeNode.calcBaseValue;
-    }
-};
-
-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 = [];
-        me.digit = 2;
-        me.digitDefault = 6;
-
-        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());
-            }
-        };
-    };
-
-    initFees(treeNode){
-        if (!treeNode.data.fees) {
-            treeNode.data.fees = [];
-            treeNode.data.feesIndex = {};
-            treeNode.changed = true;
-        };
-    };
-
-    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;
-            };
-        };
-    };
-
-    calculate(treeNode){
-        let me = this;
-        let project = projectObj.project;
-
-        // 汇总定额或子清单的费用类别
-        if (treeNode.calcType == treeNodeCalcType.ctGatherRations || treeNode.calcType == treeNodeCalcType.ctGatherBills){
-            me.initFees(treeNode);
-
-            let objsArr = (treeNode.calcType == treeNodeCalcType.ctGatherRations) ? project.Ration.getRationsByNode(treeNode) : treeNode.children;
-            let rst = [];
-            for (let ft of feeType) {
-                let ftObj = {};
-                ftObj.fieldName = ft.type;
-                ftObj.name = ft.name;
-                let uf = 0, tf = 0, tuf = 0, ttf = 0;
-                for (let item of objsArr) {
-                    let data = (treeNode.calcType == treeNodeCalcType.ctGatherRations) ? item : item.data;
-                    if (data.feesIndex && data.feesIndex[ft.type]) {
-                        uf = (uf + parseFloat(data.feesIndex[ft.type].unitFee)).toDecimal(me.digitDefault);
-                        tf = (tf + parseFloat(data.feesIndex[ft.type].totalFee)).toDecimal(me.digitDefault);
-                        tuf = (tuf + parseFloat(data.feesIndex[ft.type].tenderUnitFee)).toDecimal(me.digitDefault);
-                        ttf = (ttf + parseFloat(data.feesIndex[ft.type].tenderTotalFee)).toDecimal(me.digitDefault);
-                    };
-                };
-                ftObj.unitFee = uf.toDecimal(me.digit);
-                ftObj.totalFee = tf.toDecimal(me.digit);
-                ftObj.tenderUnitFee = tuf.toDecimal(me.digit);
-                ftObj.tenderTotalFee = ttf.toDecimal(me.digit);
-
-                me.checkFee(treeNode, ftObj);
-
-                rst.push(ftObj);
-            };
-            treeNode.data.calcTemplate = {"calcItems": rst};
-        }
-        else{
-            // 叶子清单的缺省计算程序需要提供总金额作为计算基数(不需要工料机),然后每条按比例(费率)计算,不需要工料机明细。
-            if (treeNode.calcType == treeNodeCalcType.ctCalcBaseValue){
-                delete treeNode.data.gljList;
-
-                if (treeNode.data.programID == undefined){
-                    treeNode.data.programID = defaultBillTemplate.ID;
-                };
-            }
-            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.data.programID == undefined){
-                    treeNode.data.programID = 1;
-                };
-            };
-
-            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;
-
-                me.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(me.digit);   // 如果eval()对清单树有影响,就换成小麦的Expression对象再试
-
-                    let quantity = treeNode.data.quantity;
-                    if (!quantity) quantity = 0;
-                    calcItem.totalFee = (calcItem.unitFee * quantity).toDecimal(me.digit);
 
-                    me.checkFee(treeNode, calcItem);
-                };
-            }
-        };
-    }
 };
 
 /*

+ 1 - 1
web/building_saas/main/html/main.html

@@ -652,7 +652,7 @@
         <script type="text/javascript" src="/web/building_saas/main/js/calc/calc_fees.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/calc/ration_calc.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/calc/bills_calc.js"></script>
-        <script type="text/javascript" src="/public/calc_util.js"></script>
+        <!--<script type="text/javascript" src="/public/calc_util.js"></script>-->
         <!-- Controller -->
         <script type="text/javascript" src="/public/web/tree_sheet/tree_sheet_controller.js"></script>
         <script type="text/javascript" src="/public/web/tree_sheet/tree_sheet_helper.js"></script>

+ 512 - 23
web/building_saas/main/js/models/calc_program.js

@@ -1,5 +1,6 @@
 /**
  * Created by CSL on 2017-07-19.
+ * 计算程序。所有定额、清单、父清单的计算都从此入。
  *  dispExpr: F8*(L-1); expression: "@('8') * (L-1)";
  *  说明:F后跟行号,L替换人工系数值,@后跟ID。用到L的规则必须有labourCoeID属性(反过来不要求),
  *  用到费率的规则必须有feeRateID属性,当有该属性时,会自动显示费率值。
@@ -99,14 +100,232 @@ let defaultBillTemplate = {
     ]
 };
 
+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 = 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 + (md["consumption"] * price).toDecimal(me.digit);
+                                mdSum = (mdSum).toDecimal(me.digitDefault);
+                            }
+                        };
+                        tmpSum = tmpSum + (glj["quantity"] * mdSum).toDecimal(me.digitDefault);
+                        tmpSum = (tmpSum).toDecimal(me.digitDefault);
+                    }
+                };
+            }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 = tmpSum + (glj["quantity"] * price).toDecimal(me.digitDefault);
+                        tmpSum = (tmpSum).toDecimal(me.digitDefault);
+                    };
+                };
+            };
+
+            rst = (tmpSum).toDecimal(me.digitDefault);
+        };
+
+        return rst;
+    },
+    HJ: function () {
+        let me = this;
+        return me.treeNode.calcBaseValue;
+    }
+};
 
 class CalcProgram {
     constructor(project){
-        this.project = project;
-        this.datas = [];
-        this.calc = new Calculation();
-        project.registerModule(ModuleNames.calc_program, this);
+        let me = this;
+        me.project = project;
+        me.datas = [];
+        project.registerModule(ModuleNames.calc_program, me);
     };
 
     getSourceType () {
@@ -125,27 +344,298 @@ class CalcProgram {
 
     // 经测试,全部编译一次耗时0.003~0.004秒。耗时基本忽略不计。
     compileAllTemps(){
-        let calcFeeRates = this.project.FeeRate.datas.rates;
-        let calcLabourCoes = this.project.labourCoe.datas.coes;
-        let calcTemplates = this.project.calcProgram.datas.templates;
-        calcTemplates.push(defaultBillTemplate);
+        let me = this;
+        me.digit = 2;
+        me.digitDefault = 6;
+
+        me.compiledFeeRates = {};
+        me.compiledLabourCoes = {};
+        me.compiledTemplates = {};
+        me.compiledFeeTypes = {};
+        me.compiledFeeTypeNames = [];
+        me.compiledCalcBases = {};
+        me.saveForReports = [];
 
-        this.calc.compilePublics(calcFeeRates, calcLabourCoes, feeType, rationCalcBase);
-        for (let ct of calcTemplates){
-            this.calc.compileTemplate(ct);
+        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 (this.calc.saveForReports.length > 0){
+        if (me.saveForReports.length > 0){
             let saveDatas = {};
             saveDatas.projectID = projectInfoObj.projectInfo.ID;
-            saveDatas.calcItems = this.calc.saveForReports;
+            saveDatas.calcItems = me.saveForReports;
             CommonAjax.post('/calcProgram/saveCalcItems', saveDatas, function (data) {
-                this.calc.saveForReports = [];
+                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.compiledFeeTypes[ft.type] = ft.name;
+            me.compiledFeeTypes[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;
+        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.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());
+            }
+        };
+    };
+
+    // 内部调用,外部不能直接使用
+    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;
+                };
+            };
+        };
+
+        // 汇总定额或子清单的费用类别
+        if (treeNode.calcType == treeNodeCalcType.ctGatherRations || treeNode.calcType == treeNodeCalcType.ctGatherBills){
+            initFees(treeNode);
+
+            let objsArr = (treeNode.calcType == treeNodeCalcType.ctGatherRations) ? project.Ration.getRationsByNode(treeNode) : treeNode.children;
+            let rst = [];
+            for (let ft of feeType) {
+                let ftObj = {};
+                ftObj.fieldName = ft.type;
+                ftObj.name = ft.name;
+                let uf = 0, tf = 0, tuf = 0, ttf = 0;
+                for (let item of objsArr) {
+                    let data = (treeNode.calcType == treeNodeCalcType.ctGatherRations) ? item : item.data;
+                    if (data.feesIndex && data.feesIndex[ft.type]) {
+                        uf = (uf + parseFloat(data.feesIndex[ft.type].unitFee)).toDecimal(me.digitDefault);
+                        tf = (tf + parseFloat(data.feesIndex[ft.type].totalFee)).toDecimal(me.digitDefault);
+                        tuf = (tuf + parseFloat(data.feesIndex[ft.type].tenderUnitFee)).toDecimal(me.digitDefault);
+                        ttf = (ttf + parseFloat(data.feesIndex[ft.type].tenderTotalFee)).toDecimal(me.digitDefault);
+                    };
+                };
+                ftObj.unitFee = uf.toDecimal(me.digit);
+                ftObj.totalFee = tf.toDecimal(me.digit);
+                ftObj.tenderUnitFee = tuf.toDecimal(me.digit);
+                ftObj.tenderTotalFee = ttf.toDecimal(me.digit);
+
+                checkFee(treeNode, ftObj);
+
+                rst.push(ftObj);
+            };
+            treeNode.data.calcTemplate = {"calcItems": rst};
+        }
+        else{
+            // 叶子清单的缺省计算程序需要提供总金额作为计算基数(不需要工料机),然后每条按比例(费率)计算,不需要工料机明细。
+            if (treeNode.calcType == treeNodeCalcType.ctCalcBaseValue){
+                delete treeNode.data.gljList;
+
+                if (treeNode.data.programID == undefined){
+                    treeNode.data.programID = defaultBillTemplate.ID;
+                };
+            }
+            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.data.programID == undefined){
+                    treeNode.data.programID = 1;
+                };
+            };
+
+            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(me.digit);   // 如果eval()对清单树有影响,就换成小麦的Expression对象再试
+
+                    let quantity = treeNode.data.quantity;
+                    if (!quantity) quantity = 0;
+                    calcItem.totalFee = (calcItem.unitFee * quantity).toDecimal(me.digit);
+
+                    checkFee(treeNode, calcItem);
+                };
+            }
+        };
+    }
+
     // 计算本节点(默认同时递归计算所有父节点,可选)
     calculate(treeNode, calcParents = true){
         let me = this;
@@ -184,7 +674,7 @@ class CalcProgram {
         else if (isBill)                                 // 父清单:汇总子清单的费用类别
             treeNode.calcType = treeNodeCalcType.ctGatherBills;
 
-        me.calc.calculate(treeNode);
+        me.InnerCalc(treeNode);
 
         // 计算所有父结点
         if (treeNode.changed && calcParents && treeNode.parent) {
@@ -193,31 +683,30 @@ class CalcProgram {
     };
 
     // 存储、刷新本节点(默认存储刷新所有父节点,可选)
-    saveNode(treeNode, saveParent = true) {
+    saveNode(treeNode, saveParents = true) {
         if (!treeNode.changed) return;
-
+        let me = this;
         let newDataArr = [], nodesArr = [];
-        let project = projectObj.project;
 
-        project.beginUpdate('');
+        me.project.beginUpdate('');
         let curNode = treeNode;
         while (curNode) {
             if (curNode.changed){
                 let data = {
                     ID: curNode.data.ID,
-                    projectID: projectObj.project.ID(),
+                    projectID: me.project.ID(),
                     quantity: curNode.data.quantity,
                     fees: curNode.data.fees
                 };
                 let newDta = {'updateType': 'ut_update', 'updateData': data};
                 newDataArr.push(newDta);
                 nodesArr.push(curNode);
-                project.push(curNode.sourceType, newDataArr);
+                me.project.push(curNode.sourceType, newDataArr);
             };
-            if (saveParent) curNode = curNode.parent
+            if (saveParents) curNode = curNode.parent
             else break;
         };
-        project.endUpdate();
+        me.project.endUpdate();
 
         for (let node of nodesArr){delete node.changed;};
         projectObj.mainController.refreshTreeNode(nodesArr);

+ 1 - 1
web/building_saas/main/js/views/calc_program_manage.js

@@ -53,7 +53,7 @@ let rationPM = {
         me.mainSpread = sheetCommonObj.buildSheet($('#mainSpread')[0], me.mainSetting, me.datas.length);
         me.detailSpread = sheetCommonObj.buildSheet($('#detailSpread')[0], me.detailSetting, me.datas[0].calcItems.length);
         var fieldName = new GC.Spread.Sheets.CellTypes.ComboBox();
-        fieldName.items(projectObj.project.calcProgram.calc.compiledFeeTypeNames);
+        fieldName.items(projectObj.project.calcProgram.compiledFeeTypeNames);
         me.detailSpread.getSheet(0).getRange(-1, 4, -1, 1).cellType(fieldName);
 
         me.mainSpread.getSheet(0).bind(GC.Spread.Sheets.Events.EnterCell, me.onMainEnterCell);