Ver código fonte

公式解析

chenshilong 7 anos atrás
pai
commit
86c9961428

+ 1 - 2
test/calculation/test_analyzer.js

@@ -210,9 +210,8 @@ let calcTemplate = {
     t.end();
 });*/
 
-analyzer.tempCalcItem = calcTemplate;
 let expr = "F2+F5+F6+F10";
 let arr = analyzer.getFArr(expr);
 console.log(JSON.stringify(arr));
-let id = analyzer.getFID('F10');
+let id = analyzer.getFID('F10', calcTemplate);
 console.log(JSON.stringify(id));

+ 102 - 73
web/building_saas/main/js/models/calc_program.js

@@ -13,7 +13,6 @@ let defaultBillTemplate = {
             code: "1",
             name: "定额直接费",
             dispExpr: "F2+F3+F4",
-            expression: "@('2')+@('3')+@('4')",
             statement: "人工费+材料费+机械费",
             feeRate: null,
             memo: ''
@@ -23,7 +22,6 @@ let defaultBillTemplate = {
             code: "1.1",
             name: "人工费",
             dispExpr: "HJ",
-            expression: "HJ",
             statement: "合计",
             feeRate: 50,
             fieldName: 'labour',
@@ -34,7 +32,6 @@ let defaultBillTemplate = {
             code: "1.2",
             name: "材料费",
             dispExpr: "HJ",
-            expression: "HJ",
             statement: "合计",
             feeRate: 30,
             fieldName: 'material',
@@ -45,7 +42,6 @@ let defaultBillTemplate = {
             code: "1.3",
             name: "机械费",
             dispExpr: "HJ",
-            expression: "HJ",
             statement: "合计",
             feeRate: 20,
             fieldName: 'machine',
@@ -56,7 +52,6 @@ let defaultBillTemplate = {
             code: "2",
             name: "企业管理费",
             dispExpr: "F1",
-            expression: "@('1')",
             statement: "定额直接费",
             feeRate: null,
             fieldName: 'manage',
@@ -67,7 +62,6 @@ let defaultBillTemplate = {
             code: "3",
             name: "利润",
             dispExpr: "F1",
-            expression: "@('1')",
             statement: "定额直接费",
             feeRate: null,
             fieldName: 'profit',
@@ -78,7 +72,6 @@ let defaultBillTemplate = {
             code: "4",
             name: "风险费用",
             dispExpr: "F1",
-            expression: "@('1')",
             statement: "定额直接费",
             feeRate: null,
             fieldName: 'risk',
@@ -89,7 +82,6 @@ let defaultBillTemplate = {
             code: "5",
             name: "综合单价",
             dispExpr: "F1+F5+F6+F7",
-            expression: "@('1')+@('5')+@('6')+@('7')",
             statement: "定额直接费+企业管理费+利润+风险费用",
             feeRate: null,
             fieldName: 'common',
@@ -702,14 +694,13 @@ const rationCalcBases = {
 };
 
 let analyzer = {
-    calcTemplate: null,
-
     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
+        str = str.replace(/l/g, "L");               // l换成L
         return str;
     },
     getFArr: function (expr) {
@@ -722,45 +713,44 @@ let analyzer = {
         let arr = expr.match(patt);
         return arr ? arr : [];
     },
-    getFID: function (FName) {          // F3、F22
-        let me = analyzer;
-        let idx = FName.slice(1) - 1;
-        let id = me.calcTemplate.calcItems[idx] ? me.calcTemplate.calcItems[idx].ID : null;
-        return id;
-    },
-    getFItem: function (FName){
-        let me = this;
-        let idx = FName.slice(1) - 1;
-        return me.calcTemplate.calcItems[idx];
-    },
     getBaseArr: function (expr) {
         let pattBase = new RegExp(/\[[\u4E00-\u9FA5]+\]/gi);
         let arrBase = expr.match(pattBase);
         return arrBase ? arrBase : [];
     },
-    isCycleCalc: function (expr) {     // @5+@6  这里已经是ID引用
-        let me = this;
+
+    getFID: function (FName, calcTemplate) {          // F3、F22 → 4、99
+        let idx = FName.slice(1) - 1;
+        let id = calcTemplate.calcItems[idx] ? calcTemplate.calcItems[idx].ID : null;
+        return id;
+    },
+    getFItem: function (FName, calcTemplate){      // F3 → calcItems[2] → {Object}
+        let idx = FName.slice(1) - 1;
+        return calcTemplate.calcItems[idx];
+    },
+    isCycleCalc: function (expression, calcTemplate) {     // 这里判断expression,是ID引用: @5+@6
+        let me = analyzer;
         
-        function isCycle(nodeExpr) {
+        function isCycle(nodeExpr, template) {
             let atIDArr = me.getAtIDArr(nodeExpr);
             for (let atID of atIDArr){
                 let ID = atID.slice(1);
-                let item = me.calcTemplate.compiledCalcItems[ID];
+                let item = template.compiledCalcItems[ID];
                 if (item.expression.includes(atID)) {
                     return true;
                 }
                 else {
-                    isCycle(item.expression);
+                    isCycle(item.expression, template);
                 }
             };
             return false;
         };
 
-        return isCycle(expr);
+        return isCycle(expression, calcTemplate);
     },
-    isLegal: function (expr) {   // 调用前必须先标准化
-        let me = this;
-
+    isLegal: function (dispExpr, calcTemplate) {  // 检测包括:无效字符、基数是否中括号、基数是否定义、行引用、循环计算
+        let me = analyzer;
+        let expr = me.standard(dispExpr);
         let invalidChars = /[^0-9\u4e00-\u9fa5\+\-\*\/\(\)\.\[\]F%]/g;
         if (invalidChars.test(expr)){
             alert('表达式中含有无效的字符!');
@@ -787,69 +777,77 @@ let analyzer = {
         for (let base of arrBase){
             let baseName = base.slice(1, -1);
             if (!rationCalcBases[baseName]){
-                // alert('定额基数 [' + baseName + '] 末定义!');
+                alert('定额基数 [' + baseName + '] 末定义!');
                 return false;
             }
         };
 
-        // 行引用检测、行引用转ID引用
         let arrF = me.getFArr(expr);
         for (let F of arrF){
             let num = F.slice(1);
-            if (num > me.calcTemplate.calcItems.length){
+            if (num > calcTemplate.calcItems.length){
                 alert('表达式中 “F'+ num +'” 行号引用错误!');
                 return false;
             };
-            let id = me.getFID(F);
-            let fn = new RegExp(F, "g");
-            expr = expr.replace(fn, `@('${id}')`);
         };
 
-        // 循环计算
-        if (me.isCycleCalc(expr)){
+        let expression = me.getExpression(expr, calcTemplate);
+        if (me.isCycleCalc(expression, calcTemplate)){
             alert('表达式中有循环计算!');
             return false;
         }
 
         return true;
     },
-    analyzeUserExpr: function(calcTemplate, calcItem){
-        let me = this;
-        me.calcTemplate = calcTemplate;
+    analyzeUserExpr: function(calcItem, calcTemplate){
+        let me = analyzer;
         let expr = calcItem.dispExpr;
         expr = me.standard(expr);
         calcItem.dispExpr = expr;
-        if (me.isLegal(expr)){
+
+        if (me.isLegal(expr, calcTemplate)){
             calcItem.expression = expr;
             return true;
         }else
             return false;
     },
-    dispExprToExpression: function (calcTemplate, calcItem, dispExpr) {
-        let me = this;
-        me.calcTemplate = calcTemplate;
-        let expr = me.standard(dispExpr);
-        calcItem.dispExpr = expr;
-        let fArr = me.getFArr(expr);
-        let IDArr = fArr.map(me.getFID);
-        for (var i = 0; i < fArr.length; i++) {
-            let patt = new RegExp(fArr[i], 'g');
-            let val = `@('${IDArr[i]}')`;
-            expr = expr.replace(patt, val);
-        };
 
-/*        let bArr = me.getBaseArr(expr);
-        for (var i = 0; i < bArr.length; i++) {
-            let bPatt = new RegExp(bArr[i], 'g');
-            let val = bArr[i].slice(1, -1);
-            val = `base('${val}')`;
-            expr = expr.replace(bPatt, val);
-        };*/
-        expr = expr.replace(/\[/g, "base('");
-        expr = expr.replace(/\]/g, "')");
+    getExpression: function (dispExpr, calcTemplate) {
+        function refLineToID(expr, template) {
+            let rst = expr;
+            let fArr = me.getFArr(rst);
+            let IDArr = [];
+            for (let F of fArr){
+                let ID = me.getFID(F, template);
+                IDArr.push(ID);
+            };
+            for (let i = 0; i < fArr.length; i++) {
+                let patt = new RegExp(fArr[i]);
+                let val = `@${IDArr[i]}`;
+                rst = rst.replace(patt, val);
+            };
+            return rst;
+        };
 
-        calcItem.expression = expr;
-        return calcItem.expression;
+        let me = analyzer;
+        let expr = me.standard(dispExpr);
+        return refLineToID(expr, calcTemplate);
+    },
+    getCompiledExpr: function (expression) {
+        let me = analyzer;
+        let rst = expression;
+        let atIDArr = me.getAtIDArr(rst);
+        let IDArr = atIDArr.map(function (atID) {
+            return atID.slice(1);
+        });
+        for (var i = 0; i < atIDArr.length; i++) {
+            let patt = new RegExp(atIDArr[i]);
+            let val = `$CE.at(${IDArr[i]})`;
+            rst = rst.replace(patt, val);
+        };
+        rst = rst.replace(/\[/g, "$CE.base('");
+        rst = rst.replace(/\]/g, "')");
+        return rst;
     }
 };
 
@@ -981,7 +979,7 @@ class CalcProgram {
         template.hasCompiled = false;
         template.errs = [];
 
-        let private_extract_ID = function(str, idx){
+        /*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] === '(') {
@@ -1004,8 +1002,8 @@ class CalcProgram {
                 }
             }
             return rst;
-        };
-        let private_parse_ref = function(item, itemIdx){
+        };*/
+        /*let private_parse_ref = function(item, itemIdx){
             let idx = item.expression.indexOf('@(', 0);
             while (idx >= 0) {
                 let ID = private_extract_ID(item.expression, idx);
@@ -1027,6 +1025,37 @@ class CalcProgram {
             if (template.compiledSeq.indexOf(itemIdx) < 0) {
                 template.compiledSeq.push(itemIdx);
             }
+        };*/
+        let private_extract_ID = function(str, idx){
+            let subStr = str.slice(idx);
+            let patt = new RegExp(/@\d+/);
+            let atID = subStr.match(patt);
+            let ID = atID ? atID[0].slice(1) : null;
+            return ID;
+        };
+        let private_parse_ref = function(item, itemIdx){
+            let idx = item.expression.indexOf('@', 0);
+            while (idx >= 0) {
+                let ID = private_extract_ID(item.expression, idx);
+                let len = ID ? ID.toString().length : 0;
+                if (len) {
+                    let subItem = template.compiledCalcItems[ID];
+                    if (subItem) {
+                        if (subItem.ID !== item.ID) {
+                            private_parse_ref(subItem, template.compiledCalcItems[ID + "_idx"]);
+                        } else {
+                            template.errs.push("循环引用ID: " + ID);
+                        }
+                    } else {
+                        template.errs.push("找不到ID: " + ID);
+                        console.log('找不到ID: ' + ID);
+                    }
+                }
+                idx = item.expression.indexOf('@', idx + len + 1);
+            }
+            if (template.compiledSeq.indexOf(itemIdx) < 0) {
+                template.compiledSeq.push(itemIdx);
+            }
         };
         let private_compile_items = function() {
             for (let idx of template.compiledSeq) {
@@ -1034,10 +1063,8 @@ class CalcProgram {
                 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(');
-                };
+                else
+                    item.compiledExpr = analyzer.getCompiledExpr(item.expression);
 
                 if (item.labourCoeID){
                     let lc = me.compiledLabourCoes[item.labourCoeID].coe;
@@ -1066,6 +1093,7 @@ class CalcProgram {
 
             for (let i = 0; i < template.calcItems.length; i++) {
                 let item = template.calcItems[i];
+                item.expression = analyzer.getExpression(item.dispExpr, template);
                 template.compiledCalcItems[item.ID] = item;
                 template.compiledCalcItems[item.ID + "_idx"] = i;
             }
@@ -1410,7 +1438,8 @@ class CalcProgram {
     /* 计算所有树结点(分3种情况),并返回发生变动的零散的多个树结点。参数取值如下:
         calcAllType.catAll       计算所有树结点 (不指定参数时的默认值)
         calcAllType.catBills     计算所有清单 (改变项目属性中清单取费算法时会用到)
-        calcAllType.catRations   计算所有定额、工料机形式的定额、量价,因为它们都走自己的计算程序 (改变人工系数、费率值、工料机单价时会用到)
+        calcAllType.catRations   计算所有定额、工料机形式的定额、量价,因为它们都走自己的计算程序 (改变人工系数、费率值、工料机单价时会用到)  不要用
+        缺陷:calcAllType.catRations 参数情况不会计算父结点。(calcAllType.catBills 可以,因为清单的父结点也是清单会计算)
     */
     calcAllNodes(calcType = calcAllType.catAll){
         let me = this;

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

@@ -191,7 +191,7 @@ let calcBaseView = {
 
             let calcItem = calcProgramManage.getSelectionInfo().calcItem;
             if (calcItem.dispExprUser) {
-                me.inputExpr.val(calcItem.dispExprUser);
+                me.inputExpr.val(calcItem.dispExpr);
             }
             let bnArr = Object.keys(rationCalcBases);
             let baseArr = [];

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

@@ -118,8 +118,8 @@ let calcProgramManage = {
     onEnterCell: function (sender, args) {
 /*        let t = calcProgramManage.getSelectionInfo().template;
         let c = calcProgramManage.getSelectionInfo().calcItem;
-        analyzer.dispExprToExpression(t, c, c.dispExpr);
-        let e = c.dispExpr + '   ' + c.expression;
+        c.expression = analyzer.getExpression(c.dispExpr, t);
+        let e = c.dispExpr + '  ' + c.expression;
         projectObj.testDisplay('', e);*/
     },
     saveCalcItem: function (data,callback) {//data