Explorar o código

树结构公式计算2.0

MaiXinRong hai 1 ano
pai
achega
7d287b9373
Modificáronse 3 ficheiros con 135 adicións e 74 borrados
  1. 1 1
      app/public/js/ledger.js
  2. 112 31
      app/public/js/shares/tree_expr_calc.js
  3. 22 42
      app/public/js/stage.js

+ 1 - 1
app/public/js/ledger.js

@@ -3369,7 +3369,7 @@ $(document).ready(function() {
             });
             this.initLedgerSearch();
             this.initShowLevel();
-            TreeExprCalc.init(this.exprTree);
+            TreeExprCalc.init({ tree: this.exprTree });
         }
         search(keyword) {
             this.searchResult = [];

+ 112 - 31
app/public/js/shares/tree_expr_calc.js

@@ -10,14 +10,39 @@
 
 const TreeExprCalc = (function(){
     const calcRela = {
+        calcType: {zero: 0, direct: 1, cache: 2, sortCache: 4},
         SerialReg: new RegExp('<<f[0-9]+\\$[a-z]+>>', 'ig'),
         SerialFirstReg: new RegExp('^<<f[0-9]+\\$[a-z]+>>', 'i'),
         IdReg: new RegExp('<<[a-z0-9\-]+\\$[a-z]+>>', 'ig'),
         OrderReg: new RegExp('f[0-9]+', 'ig'),
         valueChar: '$',
+
         calcCache: {},
-        exprInitFields: ['contract_tp', 'qc_tp', 'gather_tp'],
-        calcType: {zero: 0, direct: 1, cache: 2}
+
+        // 计算必须项
+        tree: null,
+        decimal: null,
+        calcField: null,
+        getCalcNum: null,
+        calcCacheFun: null,
+        exprCacheKey: [],
+        exprFieldsIndex: {},
+        exprCacheFields: [],
+    };
+    const getCalcField = function(value) {
+        return calcRela.exprFieldsIndex[value];
+    };
+    const init = function(setting) {
+        calcRela.tree = setting.tree;
+        calcRela.decimal = setting.decimal || { qty: 2, tp: 0 };
+        calcRela.calcField = setting.calcField || { qty: 'quantity', tp: 'total_price' };
+        setCalcType(setting.calcType || calcRela.calcType.zero);
+        calcRela.calcCacheFun = setting.calcCacheFun;
+        if (setting.exprFieldsIndex) calcRela.exprFieldsIndex = setting.exprFieldsIndex;
+        if (setting.exprCacheKey) {
+            calcRela.exprCacheKey = setting.exprCacheKey;
+            calcRela.exprCacheFields = calcRela.exprCacheKey.map(x => { return getCalcField(x); });
+        }
     };
     const checkExprValid = function(expr, invalidOrders = []) {
         if (!expr) return [true, ''];
@@ -119,27 +144,14 @@ const TreeExprCalc = (function(){
             return [false, '输入的表达式非法:"("后无对应的")"'];
         return [true, ''];
     };
-    const init = function(tree, decimal) {
-        calcRela.tree = tree;
-        calcRela.decimal = decimal;
-    };
-    const getCalcField = function(value) {
-        switch(value) {
-            case 'tzje': return 'total_price';
-            case 'qyje': return 'deal_tp';
-            case 'tzsl': return 'quantity';
-            case 'qysl': return 'deal_qty';
-            case 'bqhtje': return 'contract_tp';
-        }
-    };
-    const getCalcNumZero = function (node, field) {
+    const _getCalcNumZero = function (node, field) {
         return node.calc_expr && calcRela.exprInitFields.indexOf(field) >= 0 ? 0 : node[field] || 0;
     };
-    const getCalcNumDirect = function (node, field) {
+    const _getCalcNumDirect = function (node, field) {
         return node[field] || 0;
     };
-    const getCalcNumCache = function (node, field) {
-        if (node.calc_expr && calcRela.exprInitFields.indexOf(field) >= 0) {
+    const _getCalcNumCache = function (node, field) {
+        if (node.calc_expr && calcRela.exprCacheFields.indexOf(field) >= 0) {
             return calcRela.calcCache[node.id] ? calcRela.calcCache[node.id][field] || 0 : 0;
         }
         return node[field] || 0;
@@ -172,22 +184,84 @@ const TreeExprCalc = (function(){
     };
     const addCache = function(expr) {
         const cache = { id: expr.id };
-        if (expr.calcField === 'contract_qty') {
-            cache.contract_qty = ZhCalc.round(expr.value, calcRela.decimal.qty);
-            cache.contract_tp = ZhCalc.mul(cache.contract_tp, expr.unit_price, calcRela.decimal.tp);
-        } else if (expr.calcField === 'contract_tp') {
-            cache.contract_qty = 0;
-            cache.contract_tp = ZhCalc.round(expr.value, calcRela.decimal.tp);
+        if (expr.calcField === 'qty') {
+            cache[calcRela.calcField.qty] = ZhCalc.round(expr.value, calcRela.decimal.qty);
+            cache[calcRela.calcField.tp] = ZhCalc.mul(cache[calcRela.calcField.qty], expr.unit_price, calcRela.decimal.tp);
+        } else if (expr.calcField === 'tp') {
+            cache[calcRela.calcField.qty] = 0;
+            cache[calcRela.calcField.tp] = ZhCalc.round(expr.value, calcRela.decimal.tp);
         }
+        if (calcRela.calcCacheFun) calcRela.calcCacheFun(cache, expr);
         calcRela.calcCache[expr.id] = cache;
     };
-    const calcAllExpr = function(exprList) {
+    const sortExprList = function(exprList) {
+        const exprIds = exprList.map(x => { return x.id; });
+        exprList.forEach(x => {
+            x.sort = calcRela.tree.nodes.findIndex(n => { return n.id === x.id; });
+            x.relaExprId = [];
+            const idParam = x.expr.match(calcRela.IdReg);
+            if (idParam) {
+                for (const ip of idParam) {
+                    const [id, value] = ip.substring(2, ip.length - 2).split(calcRela.valueChar);
+                    if (value.indexOf('bq') < 0) continue;
+
+                    const node = calcRela.tree.nodes.find(x => { return x.id === id; });
+                    if (!node) continue;
+
+                    if (!node.children || node.children.length === 0) {
+                        if (exprIds.indexOf(node.id) >= 0) x.relaExprId.push(node.id);
+                    } else {
+                        const posterity = calcRela.tree.getLeafPosterity(node);
+                        posterity.forEach(p => { if (exprIds.indexOf(p.id) >= 0) x.relaExprId.push(p.id); });
+                    }
+                }
+            }
+        });
+        exprList.forEach(x => {
+            if (x.relaExprId.length === 0) {
+                x.calcSort = 1;
+                return;
+            }
+            let calcSort = 1;
+            let calcExprId = [...x.relaExprId];
+            while (calcExprId.length > 0 && calcSort <= 100) {
+                const sortIds = [];
+                for (const id of calcExprId) {
+                    const relaNode = exprList.find(y => { return y.id === id; });
+                    if (relaNode.relaExprId.length > 0) {
+                        sortIds.push(...relaNode.relaExprId);
+                    }
+                }
+                calcSort++;
+                if (sortIds.indexOf(x.id) >= 0) {
+                    calcSort = 'max';
+                    break;
+                }
+                calcExprId = sortIds;
+            }
+            if (calcSort > 100) calcRela.warningMsg = '公式计算引用过深,请检查公式';
+            if (calcSort === 'max') calcRela.errorMsg = '公式计算存在循环引用,请检查公式';
+            x.calcSort = calcSort;
+        });
+        exprList.sort((x, y) => {
+            if (x.calcSort === 'max') return 1000;
+            if (x.calcSort === 'max') return -1000;
+            return x.calcSort - y.calcSort;
+        });
+    };
+    const _initCache = function() {
         calcRela.calcCache = {};
+        calcRela.warningMsg = '';
+        calcRela.errorMsg = '';
+    };
+    const calcAllExpr = function(exprList) {
+        _initCache();
+        if (calcRela.sort) sortExprList(exprList);
         for (const expr of exprList) {
             [expr.formula, expr.value] = calcExpr(expr.expr);
             addCache(expr);
         }
-        calcRela.calcCache = {};
+        _initCache();
     };
     const expr2ExprStr = function(expr) {
         if (!expr) return '';
@@ -219,10 +293,17 @@ const TreeExprCalc = (function(){
         return formula;
     };
     const setCalcType = function (calcType) {
-        if (calcType === calcRela.calcType.zero) calcRela.getCalcNum = getCalcNumZero;
-        if (calcType === calcRela.calcType.direct) calcRela.getCalcNum = getCalcNumDirect;
-        if (calcType === calcRela.calcType.cache) calcRela.getCalcNum = getCalcNumCache;
+        if (calcType === calcRela.calcType.zero) calcRela.getCalcNum = _getCalcNumZero;
+        if (calcType === calcRela.calcType.direct) calcRela.getCalcNum = _getCalcNumDirect;
+        if (calcType === calcRela.calcType.cache) calcRela.getCalcNum = _getCalcNumCache;
+        if (calcType === calcRela.calcType.sortCache) calcRela.getCalcNum = _getCalcNumCache;
+        calcRela.sort = calcType === calcRela.calcType.sortCache;
     };
 
-    return { init, checkExprValid, calcExpr, calcAllExpr, expr2ExprStr, exprStr2Expr, setCalcType, calcType: calcRela.calcType}
+    return {
+        calcType: calcRela.calcType,
+        init, setCalcType,
+        checkExprValid, expr2ExprStr, exprStr2Expr,
+        calcExpr, calcAllExpr,
+    }
 })();

+ 22 - 42
app/public/js/stage.js

@@ -1706,50 +1706,20 @@ $(document).ready(() => {
                     return !node || !node.calc_expr;
                 }
             },
-            'remainByExprAll': {
-                name: '计算本期合同计量(全部公式项-0)',
-                callback: function(key, opt) {
-                    TreeExprCalc.setCalcType(TreeExprCalc.calcType.zero);
-                    const sheet = spSpread.getActiveSheet();
-
-                    const calcExpr = stageTree.nodes.filter(x => {
-                        return !!x.calc_expr;
-                    }).map(x => { return { id: x.id, expr: x.calc_expr, unit_price: x.unit_price, calcField: x.is_tp ? 'contract_tp' : 'contract_qty' }});
-                    TreeExprCalc.calcAllExpr(calcExpr);
-                    const updateData = calcExpr.map(x => {
-                        const data = { lid: x.id, contract_expr: x.formula };
-                        data[x.calcField] = x.value;
-                        return data;
-                    });
-
-                    postData(window.location.pathname + '/update', {bills: { stage: updateData }}, function (result) {
-                        const nodes = stageTree.loadPostStageData(result);
-                        stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
-                        if (detail) {
-                            detail.loadStageLedgerUpdateData(result, nodes);
-                        } else {
-                            stageIm.loadUpdateLedgerData(result, nodes);
-                        }
-                        stageTreeSpreadObj.loadExprToInput(sheet);
-                    });
-                },
-                visible: function(key, opt) {
-                    return !readOnly;
-                },
-            },
-            'remainByExprAll2': {
-                name: '计算本期合同计量(全部公式项-直取)',
+            'remainByExprAll3': {
+                name: '计算本期合同计量(全部公式项-缓存)',
                 callback: function(key, opt) {
-                    TreeExprCalc.setCalcType(TreeExprCalc.calcType.direct);
                     const sheet = spSpread.getActiveSheet();
+                    TreeExprCalc.setCalcType(TreeExprCalc.calcType.cache);
 
                     const calcExpr = stageTree.nodes.filter(x => {
                         return !!x.calc_expr;
-                    }).map(x => { return { id: x.id, expr: x.calc_expr, unit_price: x.unit_price, calcField: x.is_tp ? 'contract_tp' : 'contract_qty' }});
+                    }).map(x => { return { id: x.id, expr: x.calc_expr, unit_price: x.unit_price, calcField: x.is_tp ? 'tp' : 'qty' }});
                     TreeExprCalc.calcAllExpr(calcExpr);
                     const updateData = calcExpr.map(x => {
                         const data = { lid: x.id, contract_expr: x.formula };
-                        data[x.calcField] = x.value;
+                        const field = x.calcField === 'qty' ? 'contract_qty' : 'contract_tp';
+                        data[field] = x.value;
                         return data;
                     });
 
@@ -1768,19 +1738,20 @@ $(document).ready(() => {
                     return !readOnly;
                 },
             },
-            'remainByExprAll3': {
-                name: '计算本期合同计量(全部公式项-缓存)',
+            'remainByExprAll4': {
+                name: '计算本期合同计量(全部公式项-排序&缓存)',
                 callback: function(key, opt) {
                     const sheet = spSpread.getActiveSheet();
-                    TreeExprCalc.setCalcType(TreeExprCalc.calcType.cache);
+                    TreeExprCalc.setCalcType(TreeExprCalc.calcType.sortCache);
 
                     const calcExpr = stageTree.nodes.filter(x => {
                         return !!x.calc_expr;
-                    }).map(x => { return { id: x.id, expr: x.calc_expr, unit_price: x.unit_price, calcField: x.is_tp ? 'contract_tp' : 'contract_qty' }});
+                    }).map(x => { return { id: x.id, expr: x.calc_expr, unit_price: x.unit_price, calcField: x.is_tp ? 'tp' : 'qty' }});
                     TreeExprCalc.calcAllExpr(calcExpr);
                     const updateData = calcExpr.map(x => {
                         const data = { lid: x.id, contract_expr: x.formula };
-                        data[x.calcField] = x.value;
+                        const field = x.calcField === 'qty' ? 'contract_qty' : 'contract_tp';
+                        data[field] = x.value;
                         return data;
                     });
 
@@ -2406,7 +2377,16 @@ $(document).ready(() => {
         SpreadJsObj.loadTopAndSelect(slSpread.getActiveSheet(), ckBillsSpread);
         stagePosSpreadObj.loadCurPosData();
         SpreadJsObj.resetTopAndSelect(spSpread.getActiveSheet());
-        TreeExprCalc.init(stageTree, tenderInfo.decimal);
+        TreeExprCalc.init({
+            tree: stageTree,
+            decimal: tenderInfo.decimal,
+            calcField: { qty: 'contract_qty', tp: 'contract_tp' }, calcType: TreeExprCalc.calcType.sortCache,
+            exprFieldsIndex: {
+                tzsl: 'quantity', tzje: 'total_price', qysl: 'deal_qty', qyje: 'deal_tp',
+                bqhtje: 'contract_tp',
+            },
+            exprCacheKey: ['bqhtje'],
+        });
         // 加载中间计量
         stageIm.init(stage, imType, tenderInfo.decimal, stage.assist ? stage.assist.ass_ledger_id : '');
         stageIm.loadData(result.ledgerData, result.posData, result.detailData, result.changeData, result.import_change, result.detailAtt);