Преглед на файлове

1. 联动计量部分代码
2. 计量台账,变更后计算调整

MaiXinRong преди 1 месец
родител
ревизия
c9c41867eb

+ 91 - 1
app/controller/setting_controller.js

@@ -979,6 +979,8 @@ module.exports = app => {
                 if (projectData === null) throw '没有对应的项目数据';
                 if (ctx.session.sessionUser.is_admin === 0) throw '没有访问权限';
 
+                const limitList = await ctx.service.multiLimit.getLimits(ctx.session.sessionProject.id);
+
                 const tenders = await ctx.service.tender.getAllDataByCondition({where: {project_id: projectId}});
                 for (const t of tenders) {
                     t.measure_type_str = t.measure_type === measureType.tz.value ? measureType.tz.title : measureType.gcl.title;
@@ -991,7 +993,9 @@ module.exports = app => {
                 await this.layout('setting/s2b.ejs', {
                     projectData,
                     tenders,
-                });
+                    limitList,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.setting.s2b),
+                }, 'setting/s2b_modal.ejs');
             } catch (error) {
                 ctx.helper.log(error);
                 ctx.redirect('/dashboard');
@@ -1050,6 +1054,92 @@ module.exports = app => {
             }
         }
 
+        async saveLimit(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const result = await ctx.service.multiLimit.saveLimit(data);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '修改数据失败');
+            }
+        }
+
+        async saveLimitOption(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const result = await ctx.service.multiLimit.saveLimitOption(data);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '修改数据失败');
+            }
+        }
+
+        async tender(ctx) {
+            try {
+                const projectId = ctx.session.sessionProject.id;
+                await this._checkMenu(projectId);
+                const projectData = await ctx.service.project.getDataById(projectId);
+                if (projectData === null) throw '没有对应的项目数据';
+                if (ctx.session.sessionUser.is_admin === 0) throw '没有访问权限';
+
+                const limits = await this.ctx.service.multiLimit.getLimitList(this.ctx.session.sessionProject.id);
+                const renderData = {
+                    projectData,
+                    tender: ctx.tender.data,
+                    limits,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.setting.tender),
+                };
+                await this.layout('setting/tender.ejs', renderData);
+            } catch (err) {
+                ctx.log(err);
+            }
+        }
+
+        async tenderLoad(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.filter) throw '请求参数错误';
+
+                const filter = data.filter.split(';');
+                const result = {};
+                for (const f of filter) {
+                    switch (f) {
+                        case 'xmj':
+                            result[f] = await this.ctx.service.ledger.getAllDataByCondition({
+                                columns: ['id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf', 'code', 'name', 'unit'],
+                                where: { tender_id: ctx.tender.id },
+                            });
+                            break;
+                        case 'limit':
+                            result[f] = await this.ctx.service.ledgerExtra.getAllDataByCondition({
+                                columns: ['id', 'multi_limit'],
+                                where: { tid: ctx.tender.id },
+                            });
+                            break;
+                        default:
+                            throw '未知数据类型';
+                    }
+                }
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '加载数据错误');
+            }
+        }
+
+        async tenderUpdate(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                await this.ctx.service.ledgerExtra.updateMultiLimit(data.id, data.multi_limit);
+                ctx.body = {err: 0, msg: '', data };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存数据错误');
+            }
+        }
+
         async spread(ctx) {
             try {
                 // 获取项目数据

+ 16 - 1
app/controller/stage_controller.js

@@ -184,6 +184,7 @@ module.exports = app => {
                 renderData.sfAttDelPower = ctx.session.sessionUser.accountId === ctx.stage.user_id || ctx.helper._.findIndex(ctx.stage.auditors2, { aid: ctx.session.sessionUser.accountId }) !== -1;
                 renderData.hintOver = projectFunInfo.hintOver && ctx.tender.info.fun_rela.hintOver;
                 renderData.hintMinusCb = projectFunInfo.banMinusChangeBills && ctx.tender.info.ledger_check.banMinusChangeBills;
+                renderData.limits = await this.ctx.service.multiLimit.getLimits(ctx.session.sessionProject.id);
                 renderData.categoryData = await ctx.service.category.getAllCategory(ctx.subProject);
                 renderData.settleStatus = ctx.service.settle.settleStatus;
                 renderData.deleteFilePermission = PermissionCheck.delFile(this.ctx.session.sessionUser.permission);
@@ -375,11 +376,18 @@ module.exports = app => {
                         c.bills.push(b);
                     }
                     b.checked_amount = this.ctx.helper.add(b.checked_amount, cb.checked_amount);
+                    if (cb.is_valuation) {
+                        b.qty = this.ctx.helper.add(b.qty, cb.checked_amount);
+                    } else {
+                        b.minus_qty = this.ctx.helper.add(b.minus_qty, cb.checked_amount);
+                    }
                     b.pos.push(cb);
                 } else {
                     c.bills.push({
                         code: cb.code, name: cb.name, unit: cb.unit, unit_price: cb.unit_price,
                         gcl_id: '', checked_amount: cb.checked_amount, checked_price: cb.checked_price,
+                        qty: cb.is_valuation ? cb.checked_amount : 0, tp: cb.is_valuation ? cb.checked_price : 0,
+                        minus_qty: !cb.is_valuation ? cb.checked_amount : 0, minus_tp: !cb.is_valuation ? cb.checked_price : 0,
                         pos: [cb],
                     });
                 }
@@ -387,7 +395,12 @@ module.exports = app => {
             const helper = ctx.helper;
             change.forEach(x => {
                 const tpDecimal = x.tp_decimal || ctx.tender.info.decimal.tp;
-                x.bills.forEach(b => { b.checked_price = helper.mul(b.unit_price, b.checked_amount, tpDecimal); });
+                x.bills.forEach(b => {
+                    if (!b.gcl_id) return;
+                    b.checked_price = helper.mul(b.unit_price, b.checked_amount, tpDecimal);
+                    b.tp = helper.mul(b.unit_price, b.qty, tpDecimal);
+                    b.minus_tp = helper.mul(b.unit_price, b.minus_qty, tpDecimal);
+                });
                 x.bills.sort((a, b) => { return helper.compareCode(a.code, b.code)});
             });
             return change;
@@ -514,6 +527,8 @@ module.exports = app => {
                 checkData.loadData(ledgerData, posData);
                 await this.ctx.service.s2bProj.refreshSessionS2b();
                 checkData.check3fLimit(ctx.tender.data);
+                const limits = await this.ctx.service.multiLimit.getLimits(this.ctx.session.sessionProject.id);
+                checkData.check3fMultiLimit(ctx.tender.data, limits);
 
                 if (projRela.banOver && ctx.tender.info.ledger_check.over) {
                     checkData.checkOverRange(ctx.tender.info.checkOverInfo);

+ 145 - 3
app/lib/ledger.js

@@ -10,6 +10,51 @@
 
 const itemsPre = 'id_';
 
+class ValueCheck {
+    constructor(ctx) {
+        this.moment = ctx.moment;
+        this._ = ctx.helper._;
+    }
+    num(value, checkValue, operation) {
+        switch (operation) {
+            case '=':
+                return !this._.isNil(value) ? value === checkValue : false;
+            case '>':
+                return !this._.isNil(value) ? value > checkValue : false;
+            case '<':
+                return !this._.isNil(value) ? value < checkValue : false;
+            case '>=':
+                return !this._.isNil(value) ? value >= checkValue : false;
+            case '<=':
+                return !this._.isNil(value) ? value <= checkValue : false;
+            default:
+                return true;
+        }
+    }
+    date(value, range, operation) {
+        const curDate = new Date();
+        switch (operation) {
+            case '=':
+                return this.moment(value).add({ days: range }).isSame(curDate, 'day');
+            case '>':
+                return this.moment(value).add({ days: range }).isBefore(curDate);
+            case '<':
+                return this.moment(value).add({ days: range }).isAfter(curDate);
+            default:
+                return true;
+        }
+    }
+    gxby(value, checkValue, operation) {
+        return this.num(value, checkValue, operation);
+    }
+    dagl(value, checkValue, operation) {
+        return this.num(value, checkValue, operation);
+    }
+    gxbyDate(checkDate, operation) {
+        return this.date(checkDate, operation);
+    }
+}
+
 class baseTree {
     /**
      * 构造函数
@@ -748,13 +793,11 @@ class checkData {
         }
         return 0; // 合法
     }
-
     _getRatio(type, status) {
         const statusConst = type === 'gxby' ? this.ctx.session.sessionProject.gxby_status : this.ctx.session.sessionProject.dagl_status;
         const sc = statusConst.find(x => { return x.value === status });
         return sc ? sc.ratio : null;
     }
-
     _getValid = function (type, status, limit) {
         if (limit) {
             const statusConst = type === 'gxby' ? this.ctx.session.sessionProject.gxby_status : this.ctx.session.sessionProject.dagl_status;
@@ -764,7 +807,6 @@ class checkData {
             return -1;
         }
     };
-
     _checkLeafBills3fLimit(checkType, bills, checkInfo) {
         const over = [], lost = [];
         const posRange = this.checkPos.getLedgerPos(bills.id);
@@ -838,6 +880,97 @@ class checkData {
         }
     }
 
+    _checkMultiCondition(data, condition) {
+        let result = true;
+        for (const c of condition) {
+            result = c.rela && c.rela === 'or'
+                ? (result || this.valueCheck[c.check](data[c.field], c.value, c.operation))
+                : (result && this.valueCheck[c.check](data[c.field], c.value, c.operation));
+        }
+        return result;
+    }
+    _check3fMultiTp(data, limit, range) {
+        if (limit === 0) {
+            if (data.contract_tp || data.pre_contract_tp) return 1; // 违规
+        }
+        if (limit === 1) {
+            const lower = this.ctx.helper.mul(data.total_price, this.ctx.helper.div(range.lower, 100, 4), this.ctx.tender.info.decimal.tp);
+            const upper = this.ctx.helper.mul(data.total_price, this.ctx.helper.div(range.upper, 100, 4), this.ctx.tender.info.decimal.tp);
+            const checkTp = this.ctx.helper.add(data.contract_tp, data.pre_contract_tp) || 0;
+            if (checkTp > upper) return 1; // 违规
+            if (checkTp < lower) return 2; // 漏计
+        }
+        return 0; // 合法
+    }
+    _check3fMultiQty(data, limit, range, unit) {
+        if (limit === 0) {
+            if (data.contract_qty || data.qc_qty || data.pre_contract_qty || data.pre_qc_qty) return 1; // 违规
+        }
+        if (limit === 1) {
+            const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, unit);
+            const lower = this.ctx.helper.mul(data.final_1_qty, this.ctx.helper.div(range.lower, 100, 4), precision.value);
+            const upper = this.ctx.helper.mul(data.final_1_qty, this.ctx.helper.div(range.upper, 100, 4), precision.value);
+            const checkQty = this.ctx.helper.add(data.contract_qty, data.pre_contract_qty) || 0;
+            if (checkQty > upper) return 1; // 违规
+            if (checkQty < lower) return 2; // 漏计
+        }
+        return 0; // 合法
+    }
+    _checkMulti3f(checkData, statusData, limitOption){
+        if (!this._checkMultiCondition(statusData, limitOption.condition)) return;
+
+        if (checkData.is_tp) return this._check3fMultiTp(checkData, limitOption.limit, limitOption);
+        return this._check3fMultiQty(checkData, limitOption.limit, limitOption, checkData.unit);
+    }
+    _checkLeafPos3fMultiLimit(xmj, leafXmj, pos, posTree, multiLimit) {
+        if (!multiLimit) return;
+
+        const limitOption = this.limits.find(x => { return x.limit_id === multiLimit; });
+        if (!limitOption) return;
+
+        const ctNode = leafXmj || xmj;
+
+        for (const lo of limitOption.options) {
+            const checkResult = this._checkMulti3f(pos, ctNode, lo);
+            if (checkResult > 0) {
+                const errorType = checkResult === 1 ? '超计' : '漏计';
+                if (!lo.rangeHint) lo.rangeHint = lo.limit ? (lo.lower === lo.upper ? `${lo.lower}%` : `${lo.lower}%~${lo.upper}%`) : '不允许计量';
+                this.checkResult.error.push({
+                    xid: xmj.ledger_id, pid: pos.tree_id, errorType: 's2b_multi',
+                    code: pos.code, b_code: pos.b_code, name: pos.name, data: pos,
+                    memo: `${errorType}:${lo.hint}(${lo.rangeHint})`,
+                });
+            }
+        }
+    }
+    _recursiveCheckPos3fMultiLimit(xmj, leafXmj, pos, posTree, parentLimit) {
+        if (pos.children && pos.children.length > 0) {
+            for (const p of pos.children) {
+                this._recursiveCheckPos3fMultiLimit(xmj, pos.b_code ? leafXmj : pos, p, posTree, parentLimit);
+            }
+        } else {
+            this._checkLeafPos3fMultiLimit(xmj, leafXmj, pos, posTree, parentLimit);
+        }
+    }
+    _checkLeafXmj3fMultiLimit(xmj, multiLimit) {
+        const posRange = this.checkPos.minorTree(xmj.id);
+        if (posRange && posRange.nodes.length > 0) {
+            for (const p of posRange.children) {
+                this._recursiveCheckPos3fMultiLimit(xmj, null, p, posRange, multiLimit);
+            }
+        }
+    }
+    _recursiveCheckXmj3fMultiLimit(xmj, parentLimit = '') {
+        const limit = xmj.multi_limit || parentLimit;
+        if (xmj.children && xmj.children.length > 0) {
+            for (const c of xmj.children) {
+                this._recursiveCheckXmj3fMultiLimit(c, limit);
+            }
+        } else {
+            this._checkLeafXmj3fMultiLimit(xmj, limit);
+        }
+    }
+
     loadData(bills, pos) {
         this.checkBills.loadDatas(bills);
         this.checkPos.loadDatas(pos);
@@ -919,6 +1052,15 @@ class checkData {
             this._recursiveCheckBills3fLimit(check, b, {});
         }
     }
+    check3fMultiLimit(tender, limits) {
+        this.limits = limits;
+        if (!tender.s2b_multi_limit) return;
+        this.valueCheck = new ValueCheck(this.ctx);
+
+        for (const b of this.checkBills.children) {
+            this._recursiveCheckXmj3fMultiLimit(b);
+        }
+    }
     checkBillsQty(fields) {
         for (const b of this.checkBills.nodes) {
             if (b.children && b.children.length > 0) continue;

+ 148 - 2
app/public/js/ledger_check.js

@@ -28,8 +28,11 @@ const ledgerCheckType = {
             { value: 11, text: '遗漏计量(档案管理)', key: 'daglLost', type: 'dagl', },
         ]
     },
-    minus_cb: { value: 12, text: '负变更清单漏计', url: window.location.pathname + '/stageCheck?type=minus_cb' },
-    change_over: { value: 13, text: '变更令超计', url: window.location.pathname + '/stageCheck?type=change_over'},
+    multiLimit3f: {
+        value: 12, text: '联动计量', fun: 'check3fMultiLimit'
+    },
+    minus_cb: { value: 13, text: '负变更清单漏计', url: window.location.pathname + '/stageCheck?type=minus_cb' },
+    change_over: { value: 14, text: '变更令超计', url: window.location.pathname + '/stageCheck?type=change_over'},
 };
 const ledgerCheckUtil = {
     checkSibling: function (ledgerTree, ledgerPos, decimal, option) {
@@ -337,6 +340,149 @@ const ledgerCheckUtil = {
         }
         return error;
     },
+    check3fMultiLimit: function(ledgerTree, ledgerPos, decimal, option) {
+        const error = [];
+        if (option.limits.length === 0) return error;
+
+        const findPrecision = function (list, unit) {
+            if (unit) {
+                for (const p in list) {
+                    if (list[p].unit && list[p].unit === unit) {
+                        return list[p];
+                    }
+                }
+            }
+            return list.other;
+        };
+        const check3fTp = function (data, limit, range) {
+            if (limit === 0) {
+                if (data.contract_tp || data.pre_contract_tp) return 1; // 违规
+            }
+            if (limit === 1) {
+                const lower = ZhCalc.mul(data.total_price, ZhCalc.div(range.lower, 100, 4), decimal.tp);
+                const upper = ZhCalc.mul(data.total_price, ZhCalc.div(range.upper, 100, 4), decimal.tp);
+                const checkTp = ZhCalc.add(data.contract_tp, data.pre_contract_tp) || 0;
+                if (checkTp > upper) return 1; // 违规
+                if (checkTp < lower) return 2; // 漏计
+            }
+            return 0; // 合法
+        };
+        const check3fQty = function (data, limit, range, unit) {
+            if (limit === 0) {
+                if (data.contract_qty || data.qc_qty || data.pre_contract_qty || data.pre_qc_qty) return 1; // 违规
+            }
+            if (limit === 1) {
+                const precision = findPrecision(tenderInfo.precision, unit);
+                const lower = ZhCalc.mul(data.final_1_qty, ZhCalc.div(range.lower, 100, 4), precision.value);
+                const upper = ZhCalc.mul(data.final_1_qty, ZhCalc.div(range.upper, 100, 4), precision.value);
+                const checkQty = ZhCalc.add(data.contract_qty, data.pre_contract_qty) || 0;
+                if (checkQty > upper) return 1; // 违规
+                if (checkQty < lower) return 2; // 漏计
+            }
+            return 0; // 合法
+        };
+        const valueCheck = {
+            num(value, checkValue, operation) {
+                switch (operation) {
+                    case '=':
+                        return !_.isNil(value) ? value === checkValue : false;
+                    case '>':
+                        return !_.isNil(value) ? value > checkValue : false;
+                    case '<':
+                        return !_.isNil(value) ? value < checkValue : false;
+                    case '>=':
+                        return !_.isNil(value) ? value >= checkValue : false;
+                    case '<=':
+                        return !_.isNil(value) ? value <= checkValue : false;
+                    default:
+                        return true;
+                }
+            },
+            date(value, range, operation) {
+                const curDate = new Date();
+                switch (operation) {
+                    case '=':
+                        return moment(value).add({ days: range }).isSame(curDate, 'day');
+                    case '>':
+                        return moment(value).add({ days: range }).isBefore(curDate);
+                    case '<':
+                        return moment(value).add({ days: range }).isAfter(curDate);
+                    default:
+                        return true;
+                }
+            },
+            gxby(value, checkValue, operation) {
+                return valueCheck.num(value, checkValue, operation);
+            },
+            dagl(value, checkValue, operation) {
+                return valueCheck.num(value, checkValue, operation);
+            },
+            gxbyDate(checkDate, operation) {
+                return valueCheck.date(checkDate, operation);
+            }
+        };
+        const checkMultiCondition = function (data, condition) {
+            let result = true;
+            for (const c of condition) {
+                result = c.rela && c.rela === 'or'
+                    ? (result || valueCheck[c.check](data[c.field], c.value, c.operation))
+                    : (result && valueCheck[c.check](data[c.field], c.value, c.operation));
+            }
+            return result;
+        };
+        const checkMulti3f = function(checkData, statusData, limitOption) {
+            if (!checkMultiCondition(statusData, limitOption.condition)) return;
+
+            if (checkData.is_tp) return check3fTp(checkData, limitOption.limit, limitOption);
+            return check3fQty(checkData, limitOption.limit, limitOption, checkData.unit);
+        };
+
+        const getErrorInfo = function(bills, lo, checkResult, pos = null) {
+            if (checkResult <= 0) return;
+            const errorType = checkResult === 1 ? '超计' : '漏计';
+            if (!lo.rangeHint) lo.rangeHint = lo.limit ? (lo.lower === lo.upper ? `${lo.lower}%` : `${lo.lower}%~${lo.upper}%`) : '不允许计量';
+            if (pos) {
+                error.push({lid: ledgerTree.getNodeKey(bills), pid: pos.id, code: pos.code, b_code: '', name: pos.name, data: pos, memo: `${errorType}:${lo.hint}(${lo.rangeHint})` });
+            } else {
+                error.push({lid: ledgerTree.getNodeKey(bills), pid: '', code: bills.code, b_code: bills.b_code, name: bills.name, data: bills, memo: `${errorType}:${lo.hint}(${lo.rangeHint})` });
+            }
+        };
+        const checkLeafBills3fMultiLimit = function(bills, multiLimit) {
+            if (!multiLimit) return;
+            const limitOption = option.limits.find(x => { return x.id === multiLimit });
+            if (!limitOption) return;
+
+            const posRange = pos.getLedgerPos(bills.id);
+            if (posRange && posRange.nodes.length > 0) {
+                for (const p of posRange) {
+                    for (const lo of limitOption.options) {
+                        const checkResult = checkMulti3f(p, bills, lo);
+                        getErrorInfo(bills, lo, checkResult, p);
+                    }
+                }
+            } else {
+                for (const lo of limitOption) {
+                    const checkResult = checkMulti3f(bills, bills, lo);
+                    getErrorInfo(bills, lo, checkResult);
+                }
+            }
+        };
+        const recursiveCheckBills3fMultiLimit = function (bills, parentLimit = '') {
+            const limit = bills.multi_limit || parentLimit;
+            if (bills.children && bills.children.length > 0) {
+                for (const c of bills.children) {
+                    recursiveCheckBills3fMultiLimit(c, limit);
+                }
+            } else {
+                checkLeafBills3fMultiLimit(bills, limit);
+            }
+        };
+
+        for (const x of ledgerTree.children) {
+            recursiveCheckBills3fMultiLimit(x);
+        }
+        return error;
+    },
 };
 
 const ledgerCheck2 = async function (setting) {

+ 299 - 0
app/public/js/setting_s2b.js

@@ -0,0 +1,299 @@
+'use strict';
+$(() => {
+    autoFlashHeight();
+
+    const limitObj = (function(list){
+        const limits = list;
+        let curLimit;
+
+        const getConditionHtml = function(condition) {
+            const html = [];
+            for (const c of condition) {
+                if (html.length > 0) html.push('</br>');
+                html.push(`${c.alias} ${c.operation} ${c.value}`);
+            }
+            return html.join('');
+        };
+        const getOptionHtml = function(option) {
+            const html = [];
+            html.push('<td>', option.limit ? '<i class="fa fa-check"></i>' : '','</td>');
+            html.push(`<td>`, getConditionHtml(option.condition), '</td>');
+            html.push(`<td>${ (!option.limit ? '--' : option.lower + '%') } </td>`);
+            html.push(`<td>${ (!option.limit ? '--' : option.upper + '%') } </td>`);
+            html.push(`<td>${option.hint}</td>`);
+            html.push(`<td><a href="javascript: void(0);" class="mr-1" data-toggle="tooltip" data-placement="bottom" data-original-title="编辑" name="editLimitOption"><i class="fa fa-pencil fa-fw"></i></a>
+                <a href="javascript: void(0);" class="mr-1" data-toggle="tooltip" data-placement="bottom" data-original-title="删除" name="delLimitOption"><i class="fa fa-trash-o fa-fw text-danger"></i></a></td>`);
+            return html.join('');
+        };
+        const loadLimitOption = function(limit) {
+            const html = [];
+            for (const option of limit.options) {
+                html.push(`<tr class="text-center" optionId = "${option.id}">`, getOptionHtml(option), '</tr>');
+            }
+            $('#limitOptions').html(html.join(''));
+        };
+        const setCurLimit = function(limit) {
+            curLimit = limit;
+            if (!limit) return;
+            loadLimitOption(limit);
+            $('dd[limitId]').removeClass('bg-warning');
+            $(`dd[limitId=${curLimit.limit_id}]`).addClass('bg-warning');
+        };
+        const getCurLimit = function() {
+            return curLimit;
+        };
+        const getLimitCaptionHtml = function(limit) {
+            return `<div class="d-flex justify-content-between align-items-center table-file" limitId="${limit.limit_id}"><div>${limit.name}</div>` +
+                '    <div class="btn-group-table" style="display: none;">\n' +
+                '    <a href="javascript: void(0);" class="mr-1" data-toggle="tooltip" data-placement="bottom" data-original-title="编辑" name="renameLimit"><i class="fa fa-pencil fa-fw"></i></a>\n' +
+                '    <a href="javascript: void(0);" class="mr-1" data-toggle="tooltip" data-placement="bottom" data-original-title="删除" name="delLimit"><i class="fa fa-trash-o fa-fw text-danger"></i></a>\n' +
+                '</div></div>';
+        };
+        const getLimitHtml = function(limit) {
+            const html = [];
+            html.push(`<dd class="list-group-item" limitId="${limit.limit_id}">`, getLimitCaptionHtml(limit), '</dd>');
+            return html.join('');
+        };
+
+        const addLimit = function() {
+            postData('/setting/limit/save', {add: 1}, function(result) {
+                limits.push(result.add);
+                $('#limit-list').append(getLimitHtml(result.add));
+            });
+        };
+        const renameLimit = function(limit_id, name) {
+            postData('/setting/limit/save', { update: { limit_id, name }}, function(result){
+                const limit = limits.find(x => { return x.limit_id === result.update.limit_id; });
+                limit.name = result.update.name;
+                limit.options.forEach(x => { x.name === limit.name; });
+                $(`dd[limitId=${limit.limit_id}]`).html(getLimitCaptionHtml(limit));
+            });
+        };
+        const delLimit = function(limit_id){
+            postData('/setting/limit/save', {del: limit_id}, function(result) {
+                $(`dd[limitId=${result.del}]`).remove();
+            });
+        };
+
+        const addOption = function(option) {
+            const limit = limits.find(x => { return x.limit_id === option.limit_id; });
+            limit.options.push(option);
+            loadLimitOption(curLimit);
+        };
+        const updateOption = function(option) {
+            const limit = limits.find(x => { return x.limit_id === option.limit_id; });
+            const orgOption = limit.options.find(x => { return x.id === option.id; });
+            if (!orgOption) return;
+            _.assignIn(orgOption, option);
+            loadLimitOption(curLimit);
+        };
+        const delOption = function (option) {
+            const limit = limits.find(x => { return x.limit_id === option.limit_id; });
+            const index = limit.options.findIndex(x => { return x.id === option.id; });
+            limit.options.splice(index, 1);
+            loadLimitOption(curLimit);
+        };
+
+        if (limits.length > 0) setCurLimit(limits[0]);
+        return { setCurLimit, getCurLimit, addLimit, delLimit, renameLimit, addOption, updateOption, delOption, }
+    })(limitList);
+
+
+    const optionSaveObj = (function(){
+        let limit_id, limit_name;
+        const addCheckHtml = function(check) {
+            const html = [];
+            html.push('<tr>', `<td locInfo="${check.alias}&^&${check.field}&^&${check.check}&^&${check.operation}&^&${check.value}">${check.alias} ${check.operation} ${check.value}</td>`,
+                '<td><a href="javascript: void(0);" class="mr-1" data-toggle="tooltip" data-placement="bottom" data-original-title="删除" name="loc-del"><i class="fa fa-trash-o fa-fw text-danger"></i></a></td>', '</tr>');
+            $('#loc-list').append(html.join(''));
+        };
+
+        $('#loc-add').click(() => {
+            const field = $('#loc-field').val();
+            const locType = locInfo[field];
+            if (!locType) {
+                toastr.warning('未知判断条件');
+                return;
+            }
+
+            const data = JSON.parse(JSON.stringify(locType));
+            data.operation = $('#loc-operation').val();
+            data.value = $('#loc-value').val();
+            if (!data.value) {
+                toastr.warning('请输入判断值');
+                return;
+            }
+            data.value = _.toInteger(data.value);
+            if (_.isNil(data.value) || data.value < 0) {
+                toastr.warning('判断值仅限0、正整数');
+                return;
+            }
+            addCheckHtml(data);
+        });
+
+        $('body').on('click', 'a[name=loc-del]', function() {
+            $(this).parent().parent().remove();
+        });
+
+        const showOptionModal = function(option, limit) {
+            limit_id = option ? option.limit_id : limit.limit_id;
+            limit_name = option ? option.name : limit.name;
+            $('#lo-id').val(option ? option.id : '');
+            $('#lo-limit')[0].checked = option && option.limit;
+            $('#lo-lower').val(option ? option.lower : 0);
+            $('#lo-upper').val(option ? option.upper : 100);
+            $('#lo-hint').val(option ? option.hint : '');
+            $('#loc-list').html('');
+            if (option) {
+                for (const c of option.condition) {
+                    addCheckHtml(c);
+                }
+            }
+            $('#save-limit-option').modal('show');
+        };
+
+        const getOptionData = function() {
+            const data = {};
+            try {
+                data.limit = $('#lo-limit')[0].checked;
+                data.lower = parseInt($('#lo-lower').val());
+                data.upper = parseInt($('#lo-upper').val());
+                data.hint = $('#lo-hint').val();
+                data.condition = [];
+                const clist = $('td[locinfo]');
+                for (const c of clist) {
+                    const info = c.getAttribute('locinfo').split('&^&');
+                    data.condition.push({
+                        alias: info[0],
+                        field: info[1],
+                        check: info[2],
+                        operation: info[3],
+                        value: parseInt(info[4]),
+                    });
+                }
+                const id = $('#lo-id').val();
+                if (id) {
+                    data.id = id;
+                } else {
+                    data.limit_id = limit_id;
+                }
+                return data;
+            } catch(err) {
+                if (!data.lower) {
+                    toastr.warning('计量下限请输入0-100的整数');
+                    return;
+                }
+                if (!data.upper) {
+                    toastr.warning('计量上限请输入0-100的整数');
+                    return;
+                }
+                if (data.hint.length > 20) {
+                    toastr.warning('超限提示过长,请再精简');
+                    return;
+                }
+                toastr.warning('判断条件错误');
+                return;
+            }
+        };
+
+        return { show: showOptionModal, data: getOptionData, }
+    })();
+    $('#add-lo').click(() => {
+        optionSaveObj.show(null, limitObj.getCurLimit());
+    });
+    $('body').on('click', 'a[name=editLimitOption]', function() {
+        const optionId = $(this).parent().parent().attr('optionId');
+        const curLimit = limitObj.getCurLimit();
+        if (!curLimit) return;
+        const option = curLimit.options.find(x => { return x.id == optionId; });
+        optionSaveObj.show(option, curLimit);
+    });
+    $('body').on('click', 'a[name=delLimitOption]', function() {
+        const optionId = $(this).parent().parent().attr('optionId');
+        const curLimit = limitObj.getCurLimit();
+        if (!curLimit) return;
+        const option = curLimit.options.find(x => { return x.id == optionId; });
+        if (curLimit.options.length === 1) {
+            toastr.warning('当前配置,仅剩最后一个判断,如需删除,请直接删除配置');
+            return;
+        }
+
+        postData('/setting/limit/saveOption', { del: option }, function (result) {
+            if (result.del) limitObj.delOption(result.del);
+            $('#save-limit-option').modal('hide');
+        });
+    });
+
+    $('#save-lo-ok').click(function() {
+        const loData = optionSaveObj.data();
+        const updateData = {};
+        if (loData.id) {
+            updateData.update = loData;
+        } else {
+            updateData.add = loData;
+        }
+        postData('/setting/limit/saveOption', updateData, function (result) {
+            if (result.add) limitObj.addOption(result.add);
+            if (result.update) limitObj.updateOption(result.update);
+            $('#save-limit-option').modal('hide');
+        })
+    });
+
+    $('body').on('click', '.table-file', function(e) {
+        if (this.getAttribute('renaming') === '1') return;
+        if (e.target.tagName === 'A' || e.target.tagName === 'I' || e.target.tagName === 'INPUT') return;
+        const limitId = this.getAttribute('limitId');
+        const limit = limitList.find(x => { return x.limit_id === limitId; });
+        limitObj.setCurLimit(limit);
+    });
+    $('body').on('mouseenter', ".table-file", function(){
+        $(this).children(".btn-group-table").css("display","block");
+    });
+    $('body').on('mouseleave', ".table-file", function(){
+        $(this).children(".btn-group-table").css("display","none");
+    });
+
+    $('body').on('click', 'a[name=renameLimit]', function(e){
+        $(this).parents('.table-file').attr('renaming', '1');
+        $(`#${this.getAttribute('aria-describedby')}`).remove();
+        const limitId = $(this).parents('.table-file').attr('limitId');
+        const limit = limitList.find(x => { return x.limit_id === limitId; });
+        if (!limit) return;
+
+        const html = [];
+        html.push(`<div><input type="text" class="form-control form-control-sm" style="width: 160px" value="${limit.name}"/></div>`);
+        html.push('<div class="btn-group-table" style="display: none;">',
+            `<a href="javascript: void(0)" name="renameOk" class="mr-1"><i class="fa fa-check fa-fw"></i></a>`,
+            `<a href="javascript: void(0)" class="mr-1" name="renameCancel"><i class="fa fa-remove fa-fw text-danger"></i></a>`, '</div>');
+        $(`.table-file[limitId=${limitId}]`).html(html.join(''));
+        e.stopPropagation();
+    });
+    $('body').on('click', 'a[name=renameOk]', function(){
+        const limitId = $(this).parents('.table-file').attr('limitId');
+        const newName = $(this).parents('.table-file').find('input').val();
+        limitObj.renameLimit(limitId, newName);
+        $(this).parents('.table-file').attr('renaming', '0');
+    });
+    $('body').on('click', 'a[name=renameCancel]', function() {
+        $(this).parents('.table-file').attr('renaming', '0');
+        const limitId = $(this).parents('.table-file').attr('limitId');
+        const limit = limitList.find(x => { return x.limit_id === limitId; });
+        if (!limit) return;
+
+        const html = [];
+        html.push(`<div>${limit.name}</div>`);
+        html.push('<div class="btn-group-table" style="display: none;">',
+            '<a href="javascript: void(0);" class="mr-1" data-toggle="tooltip" data-placement="bottom" data-original-title="编辑" name="renameLimit"><i class="fa fa-pencil fa-fw"></i></a>',
+            '<a href="javascript: void(0);" class="mr-1" data-toggle="tooltip" data-placement="bottom" data-original-title="删除" name="delLimit"><i class="fa fa-trash-o fa-fw text-danger"></i></a>',
+            '</div>');
+        $(`.table-file[limitId=${limitId}]`).html(html.join(''));
+    });
+    $('body').on('click', 'a[name=delLimit]', function(e){
+        e.stopPropagation();
+        const limitId = $(this).parents('.table-file').attr('limitId');
+        limitObj.delLimit(limitId);
+    });
+    $('#addLimit').click(function() {
+        limitObj.addLimit();
+    });
+});

+ 13 - 3
app/public/js/stage.js

@@ -256,6 +256,7 @@ $(document).ready(() => {
     };
     if (tender.s2b_gxby_check) checkOption.limit3f.checkType.push('gxby');
     if (tender.s2b_dagl_check) checkOption.limit3f.checkType.push('dagl');
+    if (tender.s2b_multi_check) checkOption.multiLimit3f = { enable: 1, limits };
     // 界面布局
     autoFlashHeight();
     let featureDisplay;
@@ -338,7 +339,11 @@ $(document).ready(() => {
                 node.end_correct_tp = node.end_gather_tp;
             }
         }
-        node.tz2_tp = ZhCalc.sum([node.total_price, node.tz_qc_tp, node.tz_qc_minus_tp]);
+        if (tenderInfo.calc_type === 'tp') {
+            node.tz2_tp = ZhCalc.sum([node.total_price, node.tz_qc_tp]);
+        } else if (tenderInfo.calc_type === 'up') {
+            node.tz2_tp = ZhCalc.sum([node.total_price, node.tz_qc_tp, node.tz_qc_minus_tp]);
+        }
         node.end_gather_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_tp), 100, 2);
         node.end_correct_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_tp), 100, 2);
         node.final_dgn_price = ZhCalc.round(ZhCalc.div(node.end_gather_tp, ZhCalc.add(node.deal_dgn_qty1, node.c_dgn_qty1)), tenderInfo.decimal.up);
@@ -1557,8 +1562,13 @@ $(document).ready(() => {
                         : findEmptyBills(tree, change, changeBills);
 
                     if (!node) continue;
-                    node.tz_qc_qty = ZhCalc.add(node.tz_qc_qty, changeBills.checked_amount);
-                    node.tz_qc_tp = ZhCalc.add(node.tz_qc_tp, changeBills.checked_price);
+                    if (tenderInfo.calc_type === 'up') {
+                        node.tz_qc_qty = ZhCalc.add(node.tz_qc_qty, changeBills.checked_amount);
+                        node.tz_qc_tp = ZhCalc.add(node.tz_qc_tp, changeBills.checked_price);
+                    } else {
+                        node.tz_qc_qty = ZhCalc.add(node.tz_qc_qty, changeBills.qty);
+                        node.tz_qc_tp = ZhCalc.add(node.tz_qc_tp, changeBills.tp);
+                    }
 
                     const posData = pos.getLedgerPos(node.id);
                     if (posData && posData.length > 0) {

+ 6 - 0
app/router.js

@@ -184,6 +184,12 @@ module.exports = app => {
     app.get('/setting/api', sessionAuth, 'settingController.s2b');
     app.post('/setting/api/update', sessionAuth, 'settingController.s2bUpdate');
     app.post('/setting/api/update-status', sessionAuth, 'settingController.s2bUpdateStatus');
+    // 联动计量
+    app.post('/setting/limit/save', sessionAuth, projectManagerCheck, 'settingController.saveLimit');
+    app.post('/setting/limit/saveOption', sessionAuth, projectManagerCheck, 'settingController.saveLimitOption');
+    app.get('/setting/api/tender/:id', sessionAuth, projectManagerCheck, tenderCheck, 'settingController.tender');
+    app.post('/setting/api/tender/:id/load', sessionAuth, projectManagerCheck, tenderCheck, 'settingController.tenderLoad');
+    app.post('/setting/api/tender/:id/update', sessionAuth, projectManagerCheck, tenderCheck, 'settingController.tenderUpdate');
     // 显示设置
     app.get('/setting/show', sessionAuth, 'settingController.show');
     app.post('/setting/show/update', sessionAuth, 'settingController.showListUpdate');

+ 126 - 0
app/service/multi_limit.js

@@ -0,0 +1,126 @@
+'use strict';
+
+/**
+ * 联合限制计量
+ *
+ * @author Mai
+ * @date 2024/6/27
+ * @version
+ */
+
+module.exports = app => {
+
+    class MultiLimit extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'multi_limit';
+        }
+
+        async getLimitList(pid) {
+            const sql = `SELECT limit_id, name FROM ${this.tableName} WHERE pid = ? GROUP BY limit_id`;
+            return await this.db.query(sql, [pid]);
+        }
+
+        analysisLimits(datas) {
+            const result = [];
+            datas.forEach(x => {
+                x.condition = x.condition ? JSON.parse(x.condition) : [];
+                let limit = result.find(r => { return x.limit_id === r.limit_id; });
+                if (!limit) {
+                    limit = { limit_id: x.limit_id, name: x.name, options: [] };
+                    result.push(limit);
+                }
+                limit.options.push(x);
+            });
+            return result;
+        }
+
+        async getLimits(pid) {
+            const result = await this.getAllDataByCondition({
+                columns: ['id', 'limit_id', 'name', 'limit', 'lower', 'upper', 'condition', 'hint'],
+                where: { pid },
+            });
+            return this.analysisLimits(result);
+        }
+
+        async getLimitOptions(limit_id) {
+            const result = await this.getAllDataByCondition({
+                columns: ['limit_id', 'name', 'limit', 'lower', 'upper', 'condition', 'hint'],
+                where: { limit_id },
+            });
+            result.forEach(x => { x.condition = x.condition ? JSON.parse(x.condition) : []; });
+            return result;
+        }
+
+        async getLimitOption(id) {
+            const result = await this.getDataById(id);
+            result.condition = result.condition ? JSON.parse(result.condition) : [];
+            return result;
+        }
+
+        async _delLimit(id) {
+            await this.db.delete(this.tableName, { limit_id: id });
+            return id;
+        }
+
+        async _saveLimit(id, name) {
+            if (id) {
+                await this.db.update(this.tableName, { name }, { where: { limit_id: id } });
+                return { limit_id: id, name };
+            }
+
+            const newLimitOption = {
+                limit_id: this.uuid.v4(), pid: this.ctx.session.sessionProject.id,
+                user_id: this.ctx.session.sessionUser.accountId,
+                name: name || '新增计量配置', condition: '[]', limit: 1,
+            };
+            const result = await this.db.insert(this.tableName, newLimitOption);
+            const lo = await this.getLimitOption(result.insertId);
+            return { limit_id: lo.limit_id, name: lo.name, options: [lo] };
+        }
+
+        async saveLimit(data) {
+            const result = {};
+            if (data.add) result.add = await this._saveLimit();
+            if (data.del) result.del = await this._delLimit(data.del);
+            if (data.update) result.update = await this._saveLimit(data.update.limit_id, data.update.name);
+            return result;
+        }
+
+        async _saveLimitOption(data) {
+            if (data.id) {
+                data.condition = JSON.stringify(data.condition);
+                await this.db.update(this.tableName, data);
+            } else {
+                data.pid = this.ctx.session.sessionProject.id;
+                data.user_id = this.ctx.session.sessionUser.accountId;
+                data.condition = JSON.stringify(data.condition);
+                const result = await this.db.insert(this.tableName, data);
+                data.id = result.insertId;
+            }
+            return await this.getLimitOption(data.id);
+        }
+
+        async _delLimitOption(data) {
+            await this.deleteById(data.id);
+            return data;
+        }
+
+        async saveLimitOption(data) {
+            const result = {};
+            if (data.add) result.add = await this._saveLimitOption(data.add);
+            if (data.del) result.del = await this._delLimitOption(data.del);
+            if (data.update) result.update = await this._saveLimitOption(data.update);
+            return result;
+        }
+    }
+
+    return MultiLimit;
+};

+ 124 - 0
app/view/setting/limit.ejs

@@ -0,0 +1,124 @@
+<div class="panel-content">
+    <div class="panel-title fluid">
+        <div class="title-main  d-flex justify-content-between">
+            <div>联动计量检查配置</div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0 row">
+                <div class="col-2">
+                    <div class="d-flex flex-row">
+                        <form class="ml-2 p-2" method="POST" action="/setting/limit/save">
+                            <input type="hidden" name="_csrf_j2" value="<%= ctx.csrf %>" />
+                            <button class="btn btn-sm btn-light text-primary"><i class="fa fa-plus" aria-hidden="true" type="submit"></i> 新增配置</button>
+                        </form>
+                    </div>
+                    <div>
+                        <dl class="list-group">
+                            <% for (const limit of limitList) { %>
+                            <dd class="list-group-item <%- (limit.limit_id === curLimit.limit_id ? 'bg-warning' : '')%>">
+                                <div class="d-flex justify-content-between align-items-center table-file" limitId="<%- limit.limit_id %>">
+                                    <div><%- limit.name %>%></div>
+                                    <div class="btn-group-table" style="display: none;">
+                                        <a href="javascript: void(0);" class="mr-1" data-toggle="tooltip" data-placement="bottom" data-original-title="编辑" name="renameLimit"><i class="fa fa-pencil fa-fw"></i></a>
+                                        <a href="javascript: void(0);" class="mr-1" data-toggle="tooltip" data-placement="bottom" data-original-title="删除" name="delLimit"><i class="fa fa-trash-o fa-fw text-danger"></i></a>
+                                    </div>
+                                </div>
+                            </dd>
+                            <% } %>
+                        </dl>
+                    </div>
+                </div>
+                <div class="col-10">
+                    <div class="d-flex flex-row">
+                        <div class="p-2">
+                            <button class="btn btn-sm btn-light text-primary"><i class="fa fa-plus" aria-hidden="true" type="submit"></i> 新增判断</button>
+                        </div>
+                    </div>
+                    <div>
+                        <table class="table table-sm table-bordered">
+                            <tr class="text-center"><th>序号</th><th>允许计量</th><th>条件</th><th>计量下限</th><th>计量上限</th><th>超限提示</th></tr>
+                            <tbody>
+
+                            </tbody>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div stype="display: none">
+        <form id="hiddenForm" action="" method="POST">
+            <input type="hidden" name="_csrf_j2" value="<%= ctx.csrf %>" />
+            <input type="hidden" id="extra" name="" value="">
+        </form>
+    </div>
+</div>
+<script>
+    const limitList = JSON.parse('<%- JSON.stringify(limitList) %>');
+    const limitOptions = JSON.parse('<%- JSON.stringify(limitOptions) %>');
+    $('.table-file').click(function(e) {
+        if (this.getAttribute('renaming') === '1') return;
+        if (e.target.tagName === 'A' || e.target.tagName === 'I' || e.target.tagName === 'INPUT') return;
+        window.location.href = '/setting/limit?id=' +  this.getAttribute('limitId');
+    });
+    $('body').on('mouseenter', ".table-file", function(){
+        $(this).children(".btn-group-table").css("display","block");
+    });
+    $('body').on('mouseleave', ".table-file", function(){
+        $(this).children(".btn-group-table").css("display","none");
+    });
+
+    const hiddenSubmit = function(action, extraName, extraValue) {
+        $('#hiddenForm').attr('action', action);
+        if (extraName) {
+            $('#extra').attr('name', extraName);
+            $('#extra').val(extraValue);
+        };
+        $('#hiddenForm').submit();
+    };
+    $('body').on('click', 'a[name=renameLimit]', function(e){
+        $(this).parents('.table-file').attr('renaming', '1');
+        $(`#${this.getAttribute('aria-describedby')}`).remove();
+        const limitId = $(this).parents('.table-file').attr('limitId');
+        const limit = limitList.find(x => { return x.limit_id === limitId; });
+        if (!limit) return;
+
+        const html = [];
+        html.push(`<div><input type="text" class="form-control form-control-sm" style="width: 160px" value="${limit.name}"/></div>`);
+        html.push('<div class="btn-group-table" style="display: none;">',
+            `<a href="javascript: void(0)" name="renameOk" class="mr-1"><i class="fa fa-check fa-fw"></i></a>`,
+            `<a href="javascript: void(0)" class="mr-1" name="renameCancel"><i class="fa fa-remove fa-fw text-danger"></i></a>`, '</div>');
+        $(`.table-file[limitId=${limitId}]`).html(html.join(''));
+        e.stopPropagation();
+    });
+    $('body').on('click', 'a[name=renameOk]', function(){
+        const limitId = $(this).parents('.table-file').attr('limitId');
+        const newName = $(this).parents('.table-file').find('input').val();
+        hiddenSubmit('/setting/limit/save?id='+limitId, 'name', newName);
+        $(this).parents('.table-file').attr('renaming', '0');
+    });
+    $('body').on('click', 'a[name=renameCancel]', function() {
+        $(this).parents('.table-file').attr('renaming', '0');
+        const limitId = $(this).parents('.table-file').attr('limitId');
+        const limit = limitList.find(x => { return x.limit_id === limitId; });
+        if (!limit) return;
+
+        const html = [];
+        html.push(`<div>${limit.name}</div>`);
+        html.push('<div class="btn-group-table" style="display: none;">',
+            '<a href="javascript: void(0);" class="mr-1" data-toggle="tooltip" data-placement="bottom" data-original-title="编辑" name="renameLimit"><i class="fa fa-pencil fa-fw"></i></a>',
+            '<a href="javascript: void(0);" class="mr-1" data-toggle="tooltip" data-placement="bottom" data-original-title="删除" name="delLimit"><i class="fa fa-trash-o fa-fw text-danger"></i></a>',
+            '</div>');
+        $(`.table-file[limitId=${limitId}]`).html(html.join(''));
+    });
+    $('body').on('click', 'a[name=delLimit]', function(e){
+        e.stopPropagation();
+        const limitId = $(this).parents('.table-file').attr('limitId');
+        hiddenSubmit('/setting/limit/del?id='+limitId);
+    });
+    $(document).ready(() => {
+        autoFlashHeight();
+    });
+</script>

+ 45 - 0
app/view/setting/s2b.ejs

@@ -10,6 +10,7 @@
             <div class="sjs-height-0">
                 <nav class="nav nav-tabs m-3" role="tablist">
                     <a class="nav-item nav-link active" data-toggle="tab" href="#user-purview" role="tab">业务配置</a>
+                    <a class="nav-item nav-link" data-toggle="tab" href="#multi-limit" role="tab">联动计量配置</a>
                     <a class="nav-item nav-link" data-toggle="tab" href="#user-list" role="tab">标段列表</a>
                 </nav>
                 <div class="m-3">
@@ -79,6 +80,44 @@
                                 </table>
                             </div>
                         </div>
+                        <div class="tab-pane" id="multi-limit">
+                            <div class="row">
+                                <div class="col-3">
+                                    <div class="d-flex flex-row">
+                                        <button class="btn btn-sm btn-light text-primary" id="addLimit"><i class="fa fa-plus" aria-hidden="true"></i> 新增配置</button>
+                                    </div>
+                                    <div>
+                                        <dl class="list-group" id="limit-list">
+                                            <% for (const limit of limitList) { %>
+                                            <dd class="list-group-item" limitId="<%- limit.limit_id %>">
+                                                <div class="d-flex justify-content-between align-items-center table-file" limitId="<%- limit.limit_id %>">
+                                                    <div><%- limit.name %>%></div>
+                                                    <div class="btn-group-table" style="display: none;">
+                                                        <a href="javascript: void(0);" class="mr-1" data-toggle="tooltip" data-placement="bottom" data-original-title="编辑" name="renameLimit"><i class="fa fa-pencil fa-fw"></i></a>
+                                                        <a href="javascript: void(0);" class="mr-1" data-toggle="tooltip" data-placement="bottom" data-original-title="删除" name="delLimit"><i class="fa fa-trash-o fa-fw text-danger"></i></a>
+                                                    </div>
+                                                </div>
+                                            </dd>
+                                            <% } %>
+                                        </dl>
+                                    </div>
+                                </div>
+                                <div class="col-9">
+                                    <div class="d-flex flex-row">
+                                        <div class="p-2">
+                                            <a href="javascript: void(0);" class="btn btn-sm btn-light text-primary" id="add-lo"><i class="fa fa-plus" aria-hidden="true"></i> 新增判断</a>
+                                        </div>
+                                    </div>
+                                    <div>
+                                        <table class="table table-sm table-bordered">
+                                            <tr class="text-center"><th width="10%">允许计量</th><th width="40%">条件</th><th width="10%">计量下限</th><th width="10%">计量上限</th><th width="20%">超限提示</th><th width="10%">操作</th></tr>
+                                            <tbody id="limitOptions">
+                                            </tbody>
+                                        </table>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
                         <div class="tab-pane" id="user-list">
                             <!--没有标段数据-->
                             <% if (tenders.length === 0) { %>
@@ -141,6 +180,12 @@
     </div>
 </div>
 <script>
+    const limitList = JSON.parse('<%- JSON.stringify(limitList) %>');
+    const locInfo = {
+        gxby_status: { check: 'num', field: 'gxby_status', alias: '工序报验'},
+        dagl_status: { check: 'num', field: 'dagl_status', alias: '档案管理'},
+        gxby_date_ago: { check: 'date', field: 'gxby_date', alias: '完工日期 距今'},
+    }
     const updateStatusLimit = function (obj, type) {
         const data = { type };
         data.status = parseInt(obj.getAttribute('status'));

+ 74 - 0
app/view/setting/s2b_modal.ejs

@@ -0,0 +1,74 @@
+<!--弹出添加类别-->
+<div class="modal fade" id="save-limit-option" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">新增/修改判断</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label>计量相关</label>
+                    <div class="ml-2 row">
+                        <div class="form-check form-check-inline col-10 ml-3 mb-2">
+                            <input class="form-check-input" type="checkbox" id="lo-limit">
+                            <label class="form-check-label" for="loc-limit">允许计量</label>
+                            <lable class="text-warning ml-2">不允许计量时,上下限无效</lable>
+                        </div>
+                        <div class="input-group input-group-sm col-5">
+                            <label class="mr-2">计量下限 </label>
+                            <input type="number" class="form-control" max="100" min="0" step="2" value="0" id="lo-lower">
+                            <div class="input-group-append">
+                                <span class="input-group-text">%</span>
+                            </div>
+                        </div>
+                        <div class="input-group input-group-sm col-5">
+                            <label class="mr-2">计量上限 </label>
+                            <input type="number" class="form-control" max="100" min="0" step="2" value="100" id="lo-upper">
+                            <div class="input-group-append">
+                                <span class="input-group-text">%</span>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="form-group">
+                    <label>超限提示</label>
+                    <input class="form-control form-control-sm col-11 ml-3"  placeholder="请使用精简的提示" type="text" id="lo-hint">
+                </div>
+                <div class="form-group">
+                    <label>判断条件</label>
+                    <div class="ml-4 mb-2">
+                        <div class="d-inline-block">
+                            <select class="form-control form-control-sm" id="loc-field">
+                                <option value="gxby_status">工序报验</option>
+                                <option value="dagl_status">档案管理</option>
+                                <!--<option value="gxby_date_ago">完工日期</option>-->
+                            </select>
+                        </div>
+                        <div class="d-inline-block ml-2">
+                            <select class="form-control form-control-sm" id="loc-operation">
+                                <option value="="> = </option>
+                                <option value=">"> &gt; </option>
+                                <option value="<"> &lt; </option>
+                            </select>
+                        </div>
+                        <div class="d-inline-block ml-2">
+                            <input class="form-control form-control-sm"  placeholder="请输入判断值" type="text" id="loc-value">
+                        </div>
+                        <div class="d-inline-block ml-2">
+                            <button class="btn btn-sm btn-primary" id="loc-add">新增判断</button>
+                        </div>
+                    </div>
+                    <table class="table table-sm table-bordered col-11 ml-3">
+                        <tr class="text-center"><th width="80%">判断条件</th><th width="20%">操作</th></tr>
+                        <tbody id="loc-list"></tbody>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" id="lo-id">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary btn-sm" id="save-lo-ok">确认</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 5 - 0
config/web.js

@@ -1662,6 +1662,11 @@ const JsFiles = {
                 mergeFiles: [],
                 mergeFile: 'setting_user',
             },
+            s2b: {
+                files: [],
+                mergeFiles: ['/public/js/setting_s2b.js'],
+                mergeFile: 'setting_s2b',
+            },
             sp_permission: {
                 files: [],
                 mergeFiles: [

+ 16 - 0
sql/update.sql

@@ -25,6 +25,22 @@ ADD COLUMN `last_time` timestamp NULL DEFAULT NULL COMMENT '最近使用时间'
 ALTER TABLE `zh_tender_info`
 ADD COLUMN `calc_type` varchar(20) NOT NULL DEFAULT 'up' COMMENT '计价类型(单价up,总价tp)' AFTER `s_type`;
 
+CREATE TABLE `zh_multi_limit`  (
+  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+  `pid` int(11) UNSIGNED NOT NULL COMMENT '项目id',
+  `limit_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid',
+  `user_id` int(11) NOT NULL COMMENT '创建人',
+  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
+  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '名称',
+  `limit` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '限制计量',
+  `lower` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '计量下限',
+  `upper` tinyint(4) UNSIGNED NOT NULL DEFAULT 100 COMMENT '计量上限',
+  `condition` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL COMMENT '判断条件',
+  `hint` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '提示文本',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
+
 ------------------------------------
 -- 表数据
 ------------------------------------