Browse Source

1. 合同支付行计算调整
2. 导入在线计价,界面调整

MaiXinRong 6 months ago
parent
commit
5024972c98

+ 7 - 2
app/controller/pay_controller.js

@@ -129,8 +129,13 @@ module.exports = app => {
                         responseData.data = await this.ctx.service.phasePayDetail.downMoveDetailNode(ctx.phasePay, data.postData.id, data.postData.count || 1);
                         break;
                     case 'update':
-                        await this.ctx.service.phasePayDetail.updateCalc(ctx.phasePay, data.postData);
-                        responseData.data.reload = await this.ctx.service.phasePayDetail.calculateSave(ctx.phasePay);
+                        const updateDetail = await this.ctx.service.phasePayDetail.updateDetail(ctx.phasePay, data.postData);
+                        if (this.ctx.service.phasePayDetail.checkCalc(data.postData)) {
+                            await this.ctx.service.phasePayDetail.calculateSave(ctx.phasePay);
+                            responseData.data.reload = await this.ctx.service.phasePayDetail.getDetailData(ctx.phasePay);
+                        } else {
+                            responseData.data.update = updateDetail;
+                        }
                         break;
                     case 'calc':
                         await this.ctx.service.phasePayDetail.calculateSave(ctx.phasePay);

+ 0 - 1
app/lib/ybp_tree.js

@@ -156,7 +156,6 @@ class YbpImportTree {
         const children = parent ? parent.children : this.children;
         return this.helper._.find(children, data);
         // return children.find(x => {
-        // return children.find(x => {
         //   return x.kind === data.kind &&
         //       x.code === data.code && x.b_code === data.b_code && x.name === data.name && x.unit === data.unit &&
         //       x.unit_price === data.unit_price;

+ 264 - 39
app/public/js/phase_pay_detail.js

@@ -1,9 +1,5 @@
 'use strict';
 
-math.config({
-    number: 'BigNumber',
-});
-
 $(document).ready(() => {
     const payUtils = {
         tips: {
@@ -47,9 +43,9 @@ $(document).ready(() => {
             }
         }
     };
-    const payCalc = (function (b) {
+    const payCalc = (function (b, a) {
         class PayCalc {
-            constructor (bases) {
+            constructor (bases, add) {
                 this.percentReg = /((\d+)|((\d+)(\.\d+)))%/g;
                 this.bases = bases;
                 this.bases.sort(function (a, b) {
@@ -58,6 +54,7 @@ $(document).ready(() => {
                 for (const b of this.bases) {
                     b.reg = new RegExp(b.code, 'igm');
                 }
+                this.addBase = add;
                 this.orderReg = /f\d+/ig;
                 this.nodeReg = /<<[a-z0-9\-]+>>/ig;
             }
@@ -83,17 +80,241 @@ $(document).ready(() => {
                 }
                 return expr;
             }
-            checkSfExpr() {
+            checkExprValid(expr, invalidParam, selfId, payTree) {
+                if (!expr) return [true, ''];
+                const param = [];
+                let num = '', base = '';
+                let fixedIdParam;
+                for (let i = 0, iLen = expr.length; i < iLen; i++) {
+                    const subExpr = expr.substring(i, expr.length);
+                    if (/^[\d\.%]+/.test(expr[i])) {
+                        if (base !== '') {
+                            param.push({type: 'base', value: base});
+                            base = '';
+                        }
+                        num = num + expr[i];
+                    } else if (this.nodeReg.test(subExpr)) {
+                        if (num !== '') {
+                            param.push({type: 'num', value: num});
+                            num = '';
+                        }
+                        if (base !== '') {
+                            param.push({type: 'base', value: base});
+                            base = '';
+                        }
+                        // const node = this.nodeReg.exec(subExpr);
+                        const node = subExpr.match(this.nodeReg);
+                        param.push({type: 'node', value: node[0]});
+                        i = i + node[0].length - 1;
+                    } else if (/^[a-z]/.test(expr[i])) {
+                        if (num !== '') {
+                            param.push({type: 'num', value: num});
+                            num = '';
+                        }
+                        base = base + expr[i];
+                    } else if (expr[i] === '(') {
+                        if (num !== '') {
+                            param.push({type: 'num', value: num});
+                            num = '';
+                        }
+                        if (base !== '') {
+                            param.push({type: 'base', value: base});
+                            base = '';
+                        }
+                        param.push({type: 'left', value: '('});
+                    } else if (expr[i] === ')') {
+                        if (num !== '') {
+                            param.push({type: 'num', value: num});
+                            num = '';
+                        }
+                        if (base !== '') {
+                            param.push({type: 'base', value: base});
+                            base = '';
+                        }
+                        param.push({type: 'right', value: ')'});
+                    } else if (/^[\+\-*\/]/.test(expr[i])) {
+                        if (num !== '') {
+                            param.push({type: 'num', value: num});
+                            num = '';
+                        }
+                        if (base !== '') {
+                            param.push({type: 'base', value: base});
+                            base = '';
+                        }
+                        param.push({type: 'calc', value: expr[i]});
+                    } else {
+                        return [false, '输入的表达式含有非法字符: ' + expr[i]];
+                    }
+                }
+                if (num !== '') {
+                    param.push({type: 'num', value: num});
+                    num = '';
+                }
+                if (base !== '') {
+                    param.push({type: 'base', value: base});
+                    base = '';
+                }
+                if (param.length === 0) return [true, ''];
+                if (param.length > 1) {
+                    if (param[0].value === '-' && param[1].type === 'num') {
+                        param[1].value = '-' + param[1].value;
+                        param.shift();
+                    }
+                }
+                const iLen = param.length;
+                let iLeftCount = 0, iRightCount = 0;
+                for (const [i, p] of param.entries()) {
+                    if (p.type === 'calc') {
+                        if (i === 0 || i === iLen - 1)
+                            return [false, '输入的表达式非法:计算符号' + p.value + '前后应有数字或计算基数'];
+                    }
+                    if (p.type === 'num') {
+                        num = p.value.replace('%', '');
+                        if (p.value.length - num.length > 1)
+                            return [false, '输入的表达式非法:' + p.value + '不是一个有效的数字'];
+                        num = _.toNumber(num);
+                        if (num === undefined || num === null || _.isNaN(num))
+                            return [false, '输入的表达式非法:' + p.value + '不是一个有效的数字'];
+                        if (i > 0) {
+                            if (param[i - 1].type !== 'calc' && param[i - 1].type !== 'left') {
+                                return [false, '输入的表达式非法:' + p.value + '前应有运算符'];
+                            } else if (param[i - 1].value === '/' && num === 0) {
+                                return [false, '输入的表达式非法:请勿除0'];
+                            }
+                        }
+                    }
+                    if (p.type === 'base') {
+                        const baseParam = _.find(calcBase, {code: p.value});
+                        if (!baseParam)
+                            return [false, '输入的表达式非法:不存在计算基数' + p.value];
+                        if (invalidParam && invalidParam.indexOf(p.value) >= 0)
+                            return [false, '不可使用计算基数' + p.value];
+                        if (i > 0 && (param[i - 1].type === 'num' || param[i - 1].type === 'right'))
+                            return [false, '输入的表达式非法:' + p.value + '前应有运算符'];
+                    }
+                    if (p.type === 'node') {
+                        if (!selfId) return [false, '输入的表达式错误:不支持行号引用'];
 
-            }
-            checkExpr() {
+                        if ([`<<${selfId}>>`].indexOf(p.value) >= 0) return [false, '输入的表达式非法:请勿引用自己'];
+                        if (!fixedIdParam) {
+                            fixedIdParam = payTree.nodes.filter(x => { return x.is_fixed; }).map(x => { return `<<${x.uuid}>>`});
+                        }
+                        if (fixedIdParam.indexOf(p.value) >= 0) return [false, '输入的表达式非法:请勿引用固定项'];
+                    }
+                    if (p.type === 'left') {
+                        iLeftCount += 1;
+                        if (i !== 0 && param[i-1].type !== 'calc')
+                            return [false, '输入的表达式非法:(前应有运算符'];
+                    }
+                    if (p.type === 'right') {
+                        iRightCount += 1;
+                        if (i !== iLen - 1 && param[i+1].type !== 'calc')
+                            return [false, '输入的表达式非法:)后应有运算符'];
+                        if (iRightCount > iLeftCount)
+                            return [false, '输入的表达式非法:")"前无对应的"("'];
+                    }
+                }
+                if (iLeftCount > iRightCount)
+                    return [false, '输入的表达式非法:"("后无对应的")"'];
 
+                if (selfId) {
+                    const circular = payCalc.checkCircularExpr(expr, selfId, payTree);
+                    // 当前循环计算不检查父项
+                    if (circular) return [false, '输入的表达式非法:循环引用'];
+                }
+                return [true, ''];
             }
-            checkRangeExpr() {
+            checkSfExpr(text, data, payNode, payTree) {
+                if (text) {
+                    const num = _.toNumber(text);
+                    if (num) {
+                        data.expr = num;
+                    } else {
+                        const expr = this.trans2NodeExpr($.trim(text).replace('\t', '').replace('=', '').toLowerCase(), payTree);
+                        const [valid, msg] = this.checkExprValid(expr, [], payNode.uuid, payTree);
+                        if (!valid) return [valid, msg];
+                        data.expr = expr;
+                    }
+                } else {
+                    data.tp = 0;
+                    data.expr = '';
+                }
+                return [true, ''];
+            }
+            checkExpr(text, data, payNode, payTree) {
+                if (text) {
+                    const num = _.toNumber(text);
+                    if (num) {
+                        data.tp = num;
+                        data.expr = '';
+                    } else {
+                        const expr = this.trans2NodeExpr($.trim(text).replace('\t', '').replace('=', '').toLowerCase(), payTree);
+                        const [valid, msg] = this.checkExprValid(expr, ['bqyf'], payNode.uuid, payTree);
+                        if (!valid) return [valid, msg];
+                        data.expr = expr;
+                        data.tp = 0;
+                    }
+                } else {
+                    data.tp = 0;
+                    data.expr = '';
+                }
+                return [true, ''];
+            }
+            checkRangeExpr(payNode, text, data) {
+                if (!payNode) return [false, '数据错误'];
 
+                const num = text ? _.toNumber(text) : null;
+                let expr = text ? (num ? null : text) : null;
+                expr = expr ? $.trim(expr).replace('\t', '').replace('=', '').toLowerCase(): null;
+                const [valid, msg] = this.checkExprValid(expr, ['bqwc', 'ybbqwc', 'bqht', 'bqbg', 'bqyf']);
+                if (!valid) return [valid, msg];
+
+                if (payUtils.check.isStarted(payNode)) {
+                    if (payUtils.check.isSf(payNode)) {
+                        const value = expr ? payCalc.calculateExpr(expr) : num;
+                        if (payNode.pre_tp && value < payNode.pre_tp) return [false, '截止上期已计量' + payNode.pre_tp + ',扣款限额请勿少于改值'];
+                        data.rprice = num;
+                        data.rexpr = expr;
+                        return [true, ''];
+                    } else {
+                        // if (payNode.pre_finish) return [false, '已达扣款限额,请勿修改'];
+                        // const value = expr ? payCalc.calculateExpr(expr) : num;
+                        // if (payNode.pre_tp && value < payNode.pre_tp) return [false, '截止上期已计量' + payNode.pre_tp + ',扣款限额请勿少于改值'];
+                        // data.rprice = num;
+                        // data.rexpr = expr;
+                        return [false, '已经开始使用,请勿修改扣款限额'];
+                    }
+                } else {
+                    data.rprice = num;
+                    data.rexpr = expr;
+                    return [true, ''];
+                }
             }
-            checkStartExpr() {
+            checkStartExpr(payNode, text, data) {
+                if (!payNode) return [false, '数据错误'];
 
+                const num = text ? _.toNumber(text) : null;
+                let expr = text ? (num ? null : text) : null;
+                expr = expr ? $.trim(expr).replace('\t', '').replace('=', '').toLowerCase(): null;
+                const [valid, msg] = this.checkExprValid(expr, ['bqwc', 'ybbqwc', 'bqht', 'bqbg', 'bqyf']);
+                if (!valid) return [valid, msg];
+
+                if (payUtils.check.isStarted(payNode)) {
+                    return [false, '已经开始计量,请勿修改起扣金额'];
+                } else {
+                    if (this.addBase.pre_gather_tp) {
+                        const value = expr ? payCalc.calculateExpr(expr) : num;
+                        if (this.addBase.pre_gather_tp && value < this.addBase.pre_gather_tp)
+                            return [false, '起扣金额请勿少于本期完成截止上期计量金额' + this.addBase.pre_gather_tp];
+                        data.sprice = num;
+                        data.sexpr = expr;
+                        return [true, ''];
+                    } else {
+                        data.sprice = num;
+                        data.sexpr = expr;
+                        return [true, ''];
+                    }
+                }
             }
             getExprInfo(field, converse = false) {
                 const exprField = [
@@ -104,34 +325,32 @@ $(document).ready(() => {
                 if (converse) return _.find(exprField, { expr: field });
                 return _.find(exprField, {qty: field});
             }
-
-            getLeafOrder(data, parentOrder) {
+            getLeafOrder(data, parentReg, tree) {
                 if (!data) return [];
-                const defaultResult = data.order ? [`f${data.order}`] : [];
+                const defaultResult = data.uuid ? [`<<${data.uuid}>>`] : [];
                 if (!data.expr) return defaultResult;
-                const orderParam = data.expr.match(this.orderReg);
-                if (!orderParam || orderParam.length === 0) return defaultResult;
+                const nodeParam = data.expr.match(this.nodeReg);
+                if (!nodeParam || nodeParam.length === 0) return defaultResult;
 
-                const result = [], payData = paySheet.zh_data || [];
-                for (const op of orderParam) {
-                    const order = op.substring(1, op.length);
-                    if (data.order === parseInt(order) || op === parentOrder) {
+                const result = [];
+                for (const op of nodeParam) {
+                    const id = op.substring(2, op.length - 2);
+                    if (data.uuid === id || op === parentReg) {
                         result.push(op);
                     } else {
-                        const subOrderParam = this.getLeafOrder(payData[parseInt(order) -1], data.order ? `f${data.order}` : parentOrder);
+                        const payNode = tree.nodes.find(x => {return x.uuid === id; });
+                        const subOrderParam = this.getLeafOrder(payNode, data.uuid ? `<<${data.uuid}>>` : parentReg, tree);
                         result.push(...subOrderParam);
                     }
                 }
                 return result;
             }
+            checkCircularExpr(expr, selfId, tree) {
+                const leafOrder = this.getLeafOrder({expr}, `<<${selfId}>>`, tree);
 
-            checkCircularExpr(expr, selfOrder) {
-                const leafOrder = this.getLeafOrder({expr}, `f${selfOrder}`);
-
-                if (leafOrder.indexOf(`f${selfOrder}`) >= 0 || leafOrder.indexOf(`F${selfOrder}`) >= 0) return true;
+                if (leafOrder.indexOf(`<<${selfId}>>`) >= 0) return true;
                 return false;
             }
-
             calculateExpr(expr) {
                 let formula = expr;
                 for (const b of this.bases) {
@@ -145,7 +364,7 @@ $(document).ready(() => {
                     }
                 }
                 try {
-                    const value = parseFloat(math.evaluate(formula));
+                    const value = ZhCalc.mathCalcExpr(formula);
                     return value;
                 } catch(err) {
                     return 0;
@@ -153,8 +372,8 @@ $(document).ready(() => {
             }
         }
 
-        return new PayCalc(b);
-    })(calcBase);
+        return new PayCalc(b, a);
+    })(calcBase, addBase);
     const payObj = (function() {
         const spread = SpreadJsObj.createNewSpread($('#pay-spread')[0]);
         const sheet = spread.getActiveSheet();
@@ -261,7 +480,6 @@ $(document).ready(() => {
                         const rows = [];
                         for (const u of data.update) {
                             rows.push(tree.nodes.indexOf(u));
-                            billsTag.refreshBillsTagView(u);
                         }
                         SpreadJsObj.reLoadRowsData(sheet, rows);
                     }
@@ -305,7 +523,7 @@ $(document).ready(() => {
                 const col = info.sheet.zh_setting.cols[info.col];
                 if (col.field === 'is_gather') return;
                 // 未改变值则不提交
-                const validText = info.editingText ? info.editingText.replace('\n', '') : null;
+                const validText = info.editingText ? info.editingText.replace('\n', '') : '';
                 let orgValue;
                 if (col.field === 'tp') {
                     orgValue = select.expr ? payCalc.trans2OrderExpr(select.expr, payTree) : select.tp;
@@ -316,16 +534,18 @@ $(document).ready(() => {
                 } else {
                     orgValue = select[col.field];
                 }
-                if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                orgValue = orgValue || '';
+                if (orgValue == validText) {
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
                     return;
                 }
-                const data = { postType: 'update', postData: {} };
+
+                const data = { postType: 'update', postData: { id: select.id } };
                 switch(col.field) {
                     case 'tp':
-                        const [tpValid, tpMsg] = payUtils.isSF(select)
-                            ? payCalc.checkSfExpr(validText, data.updateData, select.order)
-                            : payCalc.checkExpr(validText, data.updateData, select.order);
+                        const [tpValid, tpMsg] = payUtils.check.isSf(select)
+                            ? payCalc.checkSfExpr(validText, data.postData, select, payTree)
+                            : payCalc.checkExpr(validText, data.postData, select, payTree);
                         if (!tpValid) {
                             toastr.warning(tpMsg);
                             SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -333,7 +553,7 @@ $(document).ready(() => {
                         }
                         break;
                     case 'start_tp':
-                        const [sValid, sMsg] = payCalc.checkStartExpr(select, validText, data.updateData);
+                        const [sValid, sMsg] = payCalc.checkStartExpr(select, validText, data.postData);
                         if (!sValid) {
                             toastr.warning(sMsg);
                             SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -341,7 +561,7 @@ $(document).ready(() => {
                         }
                         break;
                     case 'range_tp':
-                        const [rValid, rMsg] = payCalc.checkRangeExpr(select, validText, data.updateData);
+                        const [rValid, rMsg] = payCalc.checkRangeExpr(select, validText, data.postData);
                         if (!rValid) {
                             toastr.warning(rMsg);
                             SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -357,7 +577,12 @@ $(document).ready(() => {
                         break;
                 }
                 postData('update', data, function (result) {
-                    payEvent.reloadPays(result.reload);
+                    if (result.reload) {
+                        payEvent.reloadPays(result.reload);
+                    } else {
+                        const refreshData = payTree.loadPostData(result);
+                        payEvent.refreshTree(refreshData);
+                    }
                 }, function () {
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
                 });

+ 6 - 5
app/public/js/shares/dsk.js

@@ -116,12 +116,13 @@ const dsk = (function () {
         subjectSheet: null,
         compliation: null,
         loadCompilation: async function() {
-            if (this.compliation) return;
-
-            this.compliation = await loadCompilation();
-            for (const c of this.compliation) {
-                c.project = await loadProject(c.ID);
+            if (!this.compliation) {
+                this.compliation = await loadCompilation();
+                for (const c of this.compliation) {
+                    c.project = await loadProject(c.ID);
+                }
             }
+
             const html = [];
             for (const c of this.compliation) {
                 html.push(`<option value="${c.ID}">${c.name}</option>`);

+ 21 - 7
app/service/phase_pay_detail.js

@@ -34,6 +34,8 @@ math.config({
     number: 'BigNumber',
 });
 const validField = ['name', 'is_pause', 'is_gather', 'start_tp', 'start_expr', 'range_tp', 'range_expr', 'tp', 'expr', 'postil', 'dl_type', 'dl_value'];
+const infoField = ['name', 'postil'];
+const calcField = validField.filter(x => { return infoField.indexOf(x) < 0; });
 
 class PayCalculator {
     constructor (ctx, phasePay) {
@@ -186,7 +188,7 @@ class PayCalculator {
 
         const result = [...nodeParam];
         for (const op of nodeParam) {
-            const id = op.substring(2, op.length - 4);
+            const id = op.substring(2, op.length - 2);
             if (id === data.uuid || id === parentId) {
                 result.push(op);
             } else {
@@ -204,9 +206,9 @@ class PayCalculator {
     sortPaysByCalc(payTree) {
         const calcArr = [];
         for (const pay of payTree.nodes) {
-            if (pay.payType === payType.bqyf) {
+            if (pay.pay_type === payType.bqyf) {
                 pay.calcSort = 3;
-            } else if (pay.payType === payType.bqsf) {
+            } else if (pay.pay_type === payType.bqsf) {
                 pay.calcSort = 4;
             } else if (pay.children && pay.children.length > 0) {
                 pay.calcSort = 2;
@@ -522,27 +524,39 @@ class PhasePayDetail extends TreeService {
 
     async upMoveDetailNode(phasePay, targetId, count = 1) {
         const masterId = this.getMasterKey(phasePay);
-        await this.upMoveNode(masterId, targetId, count);
+        return await this.upMoveNode(masterId, targetId, count);
     }
 
     async downMoveDetailNode(phasePay, targetId, count = 1) {
         const masterId = this.getMasterKey(phasePay);
-        await this.downMoveNode(masterId, targetId, count);
+        return await this.downMoveNode(masterId, targetId, count);
     }
 
     async deleteDetailNode(phasePay, targetId, count = 1) {
         const masterId = this.getMasterKey(phasePay);
-        await this.delete(masterId, targetId, count);
+        return await this.delete(masterId, targetId, count);
     }
 
     _filterValidField(id, data) {
         const ud = { id };
         for (const prop in data) {
-            if (validField.indexOf(prop) >= 0) ud[prop] = data[prop]
+            if (validField.indexOf(prop) >= 0) {
+                ud[prop] = data[prop];
+            }
         }
         return ud;
     }
 
+    checkCalc(data) {
+        const datas = data instanceof Array ? data : [data];
+        for (const d of datas) {
+            for (const prop in d) {
+                if (calcField.indexOf(prop) > 0) return true;
+            }
+        }
+        return false;
+    }
+
     async updateDetail(phasePay, data) {
         const masterId = this.getMasterKey(phasePay);
         if (Array.isArray(data)) {

+ 1 - 0
app/view/phase_pay/detail.ejs

@@ -63,4 +63,5 @@
     const readOnly = <%- ctx.phasePay.readOnly %>;
     const details = JSON.parse('<%- JSON.stringify(pays) %>');
     const calcBase = JSON.parse('<%- JSON.stringify(calcBase) %>');
+    const addBase = JSON.parse('<%- JSON.stringify(ctx.phasePay.calc_base) %>');
 </script>

+ 1 - 0
config/web.js

@@ -697,6 +697,7 @@ const JsFiles = {
                 files: [
                     '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
                     '/public/js/decimal.min.js',
+                    '/public/js/math.min.js',
                 ],
                 mergeFiles: [
                     '/public/js/component/menu.js',