Kaynağa Gözat

多人协同

MaiXinRong 2 yıl önce
ebeveyn
işleme
3ba2a55635

+ 4 - 1
app/controller/stage_controller.js

@@ -159,6 +159,9 @@ module.exports = app => {
             if (ctx.stage.revising) {
                 throw '台账修订中,请勿修改提交期数据';
             }
+            if (ctx.stage.assist && ctx.stage.assist.confirm) {
+                throw '协同数据已确认,如需修改,请撤销上报或重新审批';
+            }
         }
 
         /**
@@ -344,6 +347,7 @@ module.exports = app => {
                             } else {
                                 responseData.data.ledgerData = await this._getStageLedgerData(ctx);
                             }
+                            responseData.data.locked = await ctx.service.stageAuditAss.getLockedId(ctx.stage);
                             break;
                         case 'pos':
                             if (hpack) {
@@ -377,7 +381,6 @@ module.exports = app => {
                         case 'cooperation':
                             responseData.data.cooperation = await ctx.service.ledgerCooperation.getValidData(ctx.tender.id, ctx.session.sessionUser.accountId);
                             responseData.data.cooperationConfirm = await ctx.service.cooperationConfirm.getValidData(ctx.tender.id, ctx.stage.id, ctx.stage.times, ctx.session.sessionUser.accountId);
-                            responseData.data.locked = await ctx.service.cooperationConfirm.getLockedId(ctx.tender, ctx.stage);
                             break;
                         case 'spec':
                             const spec = {zlj: JSON.parse(JSON.stringify(stdConst.zlj)), jrg: stdConst.jrg};

+ 50 - 10
app/controller/tender_controller.js

@@ -1063,6 +1063,9 @@ module.exports = app => {
                     case 'company':
                         info = await ctx.service.ledgerCooperation.saveCompany(data);
                         break;
+                    case 'audit-ass':
+                        info = await ctx.service.auditAss.updateData(data);
+                        break;
                     default:break;
                 }
                 ctx.body = { err: 0, msg: '', data: info };
@@ -1151,21 +1154,20 @@ module.exports = app => {
         }
 
         /**
-         * 获取部位明细数据(Ajax)
+         * 获取设置协同人相关(Ajax)
          *
          * @param ctx
          * @return {Promise<void>}
          */
-        async loadLedgerData(ctx) {
+        async loadAuditAss(ctx) {
             try {
-                const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
-                const posData = ctx.tender.data.measure_type === measureType.tz.value
-                    ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
-                const convert = new billsPosConvert(ctx);
-                convert.loadData(ledgerData, posData, []);
-                const result = await convert.convert();
-                const ledgerCooperationList = await ctx.service.ledgerCooperation.getAllDataByCondition({ where: { tid: ctx.tender.id, status: 1 } });
-                ctx.body = { err: 0, msg: '', data: { ledgerList: result, ledgerCooperationList } };
+                const ledgerData = await ctx.service.ledger.getAllDataByCondition({
+                    columns: ['id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf', 'code', 'b_code', 'name'],
+                    where: { tender_id: ctx.tender.id }
+                });
+                const result = ledgerData.filter(x => { return !x.b_code; });
+                const auditAssList = await ctx.service.auditAss.getData(ctx.tender.id);
+                ctx.body = { err: 0, msg: '', data: { ledgerList: result, auditAssList } };
             } catch (err) {
                 this.log(err);
                 ctx.body = { err: 1, msg: err.toString(), data: [] };
@@ -1328,6 +1330,44 @@ module.exports = app => {
             }
         }
 
+        async auditAssist(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+
+                if (!data.user_id || !data.type) throw '数据错误';
+                switch(data.type) {
+                    case 'stage':
+                        const stageAssists = await this.ctx.service.stageAuditAss.getUserAssist(ctx.stage, data.user_id);
+                        ctx.body = { err: 0, msg: '', data: stageAssists };
+                        break;
+                    default:
+                        throw '数据错误';
+                }
+            } catch (err) {
+                this.log(err);
+                ctx.ajaxErrorBody(err, '操作失败');
+            }
+        }
+
+        async auditAssistConfirm(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+
+                if (!data.user_id || !data.ass_user_id || !data.type || data.confirm === undefined) throw '数据错误';
+                switch(data.type) {
+                    case 'stage':
+                        const stageAss = await this.ctx.service.stageAuditAss.updateData(data);
+                        ctx.body = { err: 0, msg: '', data: stageAss };
+                        break;
+                    default:
+                        throw '数据错误';
+                }
+            } catch (err) {
+                this.log(err);
+                ctx.ajaxErrorBody(err, '操作失败');
+            }
+        }
+
         /**
          * 地图数据设置(Ajax)
          *

+ 1 - 0
app/lib/pay_calc.js

@@ -125,6 +125,7 @@ class PayCalculate {
             //     if (p.csaorder === order) {
             //     }
             // }
+            // 上一期已计量的合同支付项,不予计算起扣金额、扣款限额
             if (!p.pre_used) {
                 if (!p.sprice && p.sexpr && p.sexpr !== '') {
                     p.sprice = this.ctx.helper.round(this._calculateExpr(p.sexpr), this.decimal);

+ 22 - 14
app/middleware/stage_check.js

@@ -49,6 +49,9 @@ module.exports = options => {
             // 读取原报、审核人数据
             stage.auditors = yield this.service.stageAudit.getAuditors(stage.id, stage.times);
             stage.curAuditor = yield this.service.stageAudit.getCurAuditor(stage.id, stage.times);
+            const auditAssists = yield this.service.stageAuditAss.getData(stage);
+            stage.userAssists = auditAssists.filter(x => { return x.user_id === stage.user_id; }); // 原报协同人
+            stage.auditAssists = auditAssists.filter(x => { return x.user_id !== stage.user_id; }); // 审批协同人
 
             // 历史台账
             if (stage.status === status.checked) {
@@ -67,15 +70,26 @@ module.exports = options => {
             // todo 校验权限 (标段参与人、分享、游客)
             const accountId = this.session.sessionUser.accountId,
                 auditorIds = _.map(stage.auditors, 'aid'),
+                userAssistIds = _.map(stage.userAssists, 'ass_user_id'),
+                auditAssistIds = _.map(stage.auditAssists, 'ass_user_id'),
                 shareIds = [];
-            stage.users = stage.status === status.uncheck ? [stage.user_id] : [stage.user_id, ...auditorIds];
+            stage.users = stage.status === status.uncheck ? [stage.user_id, ...userAssistIds] : [stage.user_id, ...userAssistIds, ...auditorIds, ...auditAssistIds];
+            stage.relaAssists = auditAssists.filter(x => { return x.user_id === accountId });
+            if (stage.status === status.uncheck || stage.status === status.checkNo) {
+                stage.readOnly = accountId !== stage.user_id && userAssistIds.indexOf(accountId) < 0;
+                if (!stage.readOnly) stage.assist = stage.userAssists.find(x => { return x.ass_user_id === accountId; });
+            } else if (stage.status === status.checked) {
+                stage.readOnly = true;
+            } else {
+                const ass = stage.auditAssists.find(x => { return x.user_id === stage.curAuditor.aid && x.ass_user_id === accountId; });
+                stage.readOnly = stage.curAuditor.aid !== accountId && !ass;
+                if (stage.readOnly) stage.assist = ass;
+            }
+            if (stage.readOnly)  {
+                stage.assist = accountId === stage.user_id || auditorIds.indexOf(accountId) >= 0 ? null : auditAssists.find(x => { return x.ass_user_id === accountId});
+            }
             const permission = this.session.sessionUser.permission;
-            if (accountId === stage.user_id) { // 原报
-                if (stage.curAuditor) {
-                    stage.readOnly = stage.curAuditor.aid !== accountId;
-                } else {
-                    stage.readOnly = stage.status !== status.uncheck && stage.status !== status.checkNo;
-                }
+            if (accountId === stage.user_id || userAssistIds.indexOf(accountId) >= 0) { // 原报
                 stage.curTimes = stage.times;
                 if (stage.status === status.uncheck || stage.status === status.checkNo) {
                     stage.curOrder = 0;
@@ -85,7 +99,7 @@ module.exports = options => {
                     stage.curOrder = stage.curAuditor.aid === accountId ? stage.curAuditor.order : stage.curAuditor.order - 1;
                 }
                 stage.filePermission = true;
-            } else if (auditorIds.indexOf(accountId) !== -1) { // 审批人
+            } else if (auditorIds.indexOf(accountId) !== -1 || auditAssistIds.indexOf(accountId) >= 0) { // 审批人/协审
                 if (stage.status === status.uncheck) {
                     throw '您无权查看该数据';
                 }
@@ -100,7 +114,6 @@ module.exports = options => {
                 } else {
                     stage.curOrder = accountId === stage.curAuditor.aid ? stage.curAuditor.order : stage.curAuditor.order - 1;
                 }
-                stage.readOnly = (stage.status !== status.checking && stage.status !== status.checkNoPre) || accountId !== stage.curAuditor.aid;
                 stage.filePermission = true;
             } else if (shareIds.indexOf(accountId) !== -1 || (permission !== null && permission.tender !== undefined && permission.tender.indexOf('2') !== -1)) { // 分享人
                 if (stage.status === status.uncheck) {
@@ -118,11 +131,6 @@ module.exports = options => {
                 }
                 stage.filePermission = false;
             } else if (this.tender.isTourist || this.session.sessionUser.is_admin) {
-                if (auditorIds.indexOf(accountId) !== -1) {
-                    stage.readOnly = (stage.status !== status.checking && stage.status !== status.checkNoPre) || accountId !== stage.curAuditor.aid;
-                } else {
-                    stage.readOnly = true;
-                }
                 stage.curTimes = stage.times;
                 if (stage.status === status.uncheck || stage.status === status.checkNo) {
                     stage.curOrder = 0;

+ 4 - 1
app/middleware/tender_check.js

@@ -57,6 +57,8 @@ module.exports = options => {
             const auditorsId = this.helper._.map(auditors, 'audit_id');
             const stageAuditors = yield this.service.stageAudit.getAllAuditors(tender.id);
             const stageAuditorsId = this.helper._.map(stageAuditors, 'aid');
+            const auditAssists = yield this.service.auditAss.getData(tender.id);
+            const auditAssistsId = this.helper._.map(auditAssists, 'ass_user_id');
             const changeAuditors = yield this.service.changeAudit.getAllAuditors(tender.id);
             const changeAuditorsId = this.helper._.map(changeAuditors, 'uid');
             const reviseAuditors = yield this.service.reviseAudit.getAllAuditors(tender.id);
@@ -79,7 +81,7 @@ module.exports = options => {
             tender.touristPermission = yield this.service.tenderTourist.getTouristPermission(isTenderTourist);
             if (auditorsId.indexOf(accountId) === -1 && tender.data.user_id !== accountId &&
                 (tenderPermission === null || tenderPermission === undefined || tenderPermission.indexOf('2') === -1) &&
-                stageAuditorsId.indexOf(accountId) === -1 && changeAuditorsId.indexOf(accountId) === -1 &&
+                stageAuditorsId.indexOf(accountId) === -1 && auditAssistsId.indexOf(accountId) === -1 && changeAuditorsId.indexOf(accountId) === -1 &&
                 reviseAuditorsId.indexOf(accountId) === -1 && materialAuditorsId.indexOf(accountId) === -1 &&
                 changeProjectAuditorsId.indexOf(accountId) === -1 && changeProjectXsAuditorsId.indexOf(accountId) === -1 &&
                 changeApplyAuditorsId.indexOf(accountId) === -1 && changePlanAuditorsId.indexOf(accountId) === -1 &&
@@ -112,6 +114,7 @@ module.exports = options => {
             tender.schedule_permission = schedule_permission;
             yield next;
         } catch (err) {
+            console.log(err);
             // 输出错误到日志
             if (err.stack) {
                 this.logger.error(err);

+ 12 - 89
app/public/js/path_tree.js

@@ -406,7 +406,7 @@ const createNewPathTree = function (type, setting) {
                     p.filter = !defaultValue;
                 }
             }
-            for (const node of this.datas) {
+            for (const node of this.nodes) {
                 const parent = this.getParent(node);
                 node.visible = parent ? (parent.expanded && parent.visible && !node.filter) : !node.filter;
             }
@@ -1317,13 +1317,17 @@ const createNewPathTree = function (type, setting) {
         _loadLockedNodes(ids){
             this.Locked = ids || [];
             for (const f of this.Locked) {
-                const node = this.getItems(f.ledger_id);
-                node.locked = true;
-                const posterity = this.getPosterity(node);
-                for (const pn of posterity) {
-                    pn.locked = true;
-                    pn.lock = true;
+                f.ass_ledger_id = f.ass_ledger_id ? f.ass_ledger_id.split(',') : [];
+                for (const id of f.ass_ledger_id) {
+                    const node = this.getItems(id);
+                    node.locked = true;
+                    const posterity = this.getPosterity(node);
+                    for (const pn of posterity) {
+                        pn.locked = true;
+                        pn.lock = true;
+                    }
                 }
+
             }
         }
         /**
@@ -1341,52 +1345,6 @@ const createNewPathTree = function (type, setting) {
             }
             this._loadLockedNodes(locked)
         }
-        // 解锁相关
-        _loadPwdCache() {
-            const cache = getLocalCache(this.pwdCacheKey);
-            if (!cache) return;
-
-            const cacheArr = cache.split('|');
-            for (const ca of cacheArr) {
-                if (!ca) continue;
-
-                const [ledger_id, pwd] = ca.split(':');
-                if (!ledger_id || !pwd) continue;
-
-                const p = this.pwd.find(x => {return x.ledger_id == ledger_id});
-                if (p) p.check = p.pwd === pwd;
-            }
-        }
-        loadPwd(data, cacheKey, confirmList) {
-            this.loadingPwd = true;
-            try {
-                this.pwdCacheKey = cacheKey;
-                this.confirmList = confirmList;
-                this.pwd = data;
-                this._loadPwdCache();
-                this._loadOnlineConfirm();
-                for (const p of this.pwd) {
-                    p.node = this.getItems(p.ledger_id);
-                    this.lockNode(p, !p.check);
-                }
-            } catch(err) {}
-            this.loadingPwd = false;
-        }
-
-        // 确认相关
-        _loadOnlineConfirm() {
-            const cacheArr = this.confirmList;
-            for (const ca of cacheArr) {
-                if (!ca) continue;
-                if (!ca.ledger_id) continue;
-
-                const p = this.pwd.find(x => {return x.ledger_id == ca.ledger_id});
-                if (p) {
-                    p.confirm = true;
-                    p.confirm_time = ca.create_time;
-                }
-            }
-        }
 
         getStageItems(id) {
             return this.stageItems[itemsPre + id];
@@ -1545,46 +1503,11 @@ const createNewPathTree = function (type, setting) {
             return result;
         }
 
-        _savePwdCache() {
-            const cache = [];
-            for (const p of this.pwd) {
-                if (p.check && p.pwd) cache.push(p.ledger_id + ':' + p.pwd);
-            }
-            setLocalCache(this.pwdCacheKey, cache.join('|'));
-        }
-
-        _getPwdPosterity(node) {
-            const children = [];
-            for (const c of node.children) {
-                const cPwd = this.pwd.find(x => {return x.node && x.node.id === c.id});
-                if (!cPwd) children.push(c);
-            }
-            let posterity = [...children];
-            for (const c of children) {
-                posterity = posterity.concat(this._getPwdPosterity(c));
-            }
-            return posterity;
-        }
-
-        lockNode(p, isLock) {
-            const refresh = [];
-            p.check = !isLock;
-            p.node.lock = isLock;
-            refresh.push(this.getNodeIndex(p.node));
-            const posterity = this._getPwdPosterity(p.node);
-            for (const pn of posterity) {
-                pn.lock = pn.locked || isLock;
-                refresh.push(this.getNodeIndex(pn));
-            }
-            if (!this.loadingPwd) this._savePwdCache();
-            return refresh;
-        }
-
         getLockedInfo(node) {
             const ownerNodes = this.getAllParents(node);
             const lockedInfo = [];
             for (const on of ownerNodes) {
-                const filter = this.Locked.filter(x => { return x.ledger_id === on.ledger_id; });
+                const filter = this.Locked.filter(x => { return x.ass_ledger_id.indexOf(on.ledger_id + '') >= 0; });
                 lockedInfo.push(...filter);
             }
             return lockedInfo;

+ 179 - 517
app/public/js/shenpi.js

@@ -224,7 +224,7 @@ function getShenpiHtml (this_code) {
 $(document).ready(function () {
     let timer = null;
     let oldSearchVal = null;
-    const needYB = ['ledger', 'revise', 'change'];
+    const needYB = ['advance', 'ledger', 'revise', 'change', 'audit-ass'];
     $('body').on('input propertychange', '.gr-search', function(e) {
         oldSearchVal = e.target.value;
         timer && clearTimeout(timer);
@@ -341,12 +341,17 @@ $(document).ready(function () {
     // 选中审批人
     $('body').on('click', 'dl dd', function () {
         const id = parseInt($(this).data('id'));
-        if (id) {
-            const user = _.find(accountList, function (item) {
-                return item.id === id;
-            });
+        if (!id) return;
+
+        let this_code = $(this).parents('.lc-show').siblings('.form-group').find('input:checked').data('code');
+        if (!this_code) this_code = $(this).parents('.dropdown').data('code');
+        const user = _.find(accountList, function (item) {
+            return item.id === id;
+        });
+        if (this_code === 'audit-ass') {
+            auditAss.setAuditAssist(user);
+        } else {
             const this_status = parseInt($(this).parents('.lc-show').siblings('.form-group').find('input:checked').val());
-            const this_code = $(this).parents('.lc-show').siblings('.form-group').find('input:checked').data('code');
             if (this_status === sp_status.gdspl) {
                 // 判断是否已存在审批人
                 const aid_num = $(this).parents('ul').find('.remove-audit').length;
@@ -383,7 +388,7 @@ $(document).ready(function () {
                     '                                                            </div>\n' +
                     '                                                        </span> ' +
                     '                                            </span></span></span>\n');
-                    // <a href="javascript:void(0);" class="remove-audit btn-sm text-danger px-1" title="移除" data-id="'+ user.id +'"><i class="fa fa-remove"></i></a></span> </span>');
+                // <a href="javascript:void(0);" class="remove-audit btn-sm text-danger px-1" title="移除" data-id="'+ user.id +'"><i class="fa fa-remove"></i></a></span> </span>');
             });
         }
     });
@@ -605,421 +610,191 @@ $(document).ready(function () {
         })
     });
 
-    const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
-    const treeSetting = {
-        id: 'ledger_id',
-        pid: 'ledger_pid',
-        order: 'order',
-        level: 'level',
-        rootId: -1,
-        fullPath: 'full_path',
-    };
-    const ledgerTree = createNewPathTree('base', treeSetting);
-
-    const ledgerSpreadSetting = {
-        cols: [
-            {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 165, formatter: '@', readOnly: true, cellType: 'tree'},
-            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', readOnly: true},
-            {title: '密码', colSpan: '1', rowSpan: '2', field: 'pwd', hAlign: 0, width: 100, formatter: '@', getValue:'getValue.pwd', readOnly: 'readOnly.pwd'},
-        ],
-        emptyRows: 0,
-        headRows: 1,
-        headRowHeight: [25, 25],
-        defaultRowHeight: 21,
-        headerFont: '12px 微软雅黑',
-        font: '12px 微软雅黑',
-        // readOnly: true,
-        localCache: {
-            key: 'ledger-cooperation',
-            colWidth: true,
-        }
-    };
-
-    const ledgerCol = {
-        getValue: {
-            pwd: function (data) {
-                let txt = '';
-                // console.log(data);
-                // if (data.is_leaf) {
-                if (data.pwd && data.pwd !== '' && data.pwd !== null) {
-                    txt = data.pwd;
-                } else if(data.can_edit) {
-                    txt = '请输入密码';
+    class AuditAss {
+        constructor() {
+            this.spread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
+            this.sheet = this.spread.getActiveSheet();
+            this.tree = createNewPathTree('base', {
+                id: 'ledger_id',
+                pid: 'ledger_pid',
+                order: 'order',
+                level: 'level',
+                rootId: -1,
+            });
+            this.spreadSetting = {
+                cols: [
+                    {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 165, formatter: '@', cellType: 'tree'},
+                    {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@'},
+                    {title: '协同人', colSpan: '1', rowSpan: '2', field: 'ass_name', hAlign: 1, width: 100, formatter: '@'},
+                ],
+                emptyRows: 0,
+                headRows: 1,
+                headRowHeight: [25, 25],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                readOnly: true,
+                localCache: {
+                    key: 'ledger-cooperation',
+                    colWidth: true,
                 }
-                // }
-                return txt;
-            }
-        },
-        readOnly: {
-            pwd: function (data) {
-                return !data.can_edit;
-            },
-        },
-    };
+            };
+            sjsSettingObj.setFxTreeStyle(this.spreadSetting, sjsSettingObj.FxTreeStyle.jz);
+            SpreadJsObj.initSheet(this.sheet, this.spreadSetting);
+
+            const self = this;
+            SpreadJsObj.addDeleteBind(this.spread, function() { return; });
+            SpreadJsObj.selChangedRefreshBackColor(this.sheet);
+            this.spread.bind(spreadNS.Events.SelectionChanged, function() {
+                self.refreshOperate();
+            });
 
-    const ledgerSpreadObj = {
-        setFontColor: function(row = null) {
-            if(row) {
-                const value = ledgerSpread.getActiveSheet().getValue(row, 2);
-                if (value === '请输入密码') {
-                    ledgerSpread.getActiveSheet().getCell(row, 2).foreColor('#007bff');
-                } else {
-                    ledgerSpread.getActiveSheet().getCell(row, 2).foreColor('#000');
-                }
-            } else {
-                const rowCount = ledgerSpread.getActiveSheet().getRowCount();
-                for(let i = 0; i < rowCount; i++){
-                    const value = ledgerSpread.getActiveSheet().getValue(i, 2);
-                    if (value === '请输入密码') {
-                        ledgerSpread.getActiveSheet().getCell(i, 2).foreColor('#007bff');
-                    } else {
-                        ledgerSpread.getActiveSheet().getCell(i, 2).foreColor('#000');
-                    }
-                }
-            }
-        },
-        setAllRightPwd: function(uid) {
-            selects = [];
-            for (const l of ledgerTree.nodes) {
-                const coo = _.find(ledger_cooperation_list, { 'ledger_id': l.ledger_id, 'user_id': parseInt(uid) });
-                if (l.pwd && !coo) {
-                    delete l.pwd;
-                    l.can_edit = true;
-                    selects.push(l);
-                } else if(coo) {
-                    l.pwd = coo.pwd;
-                    l.can_edit = true;
-                    selects.push(l);
-                } else if (l.can_edit === false) {
-                    l.can_edit = true;
-                    selects.push(l);
-                }
-            }
-            if(selects.length > 0) {
-                // updateByCanEdit(ledgerTree.nodes, _.filter(ledger_cooperation_list, { user_id: parseInt(uid) }), false);
-                const refreshNode = ledgerTree.loadPostData({update: selects});
-                ledgerSpreadObj.refreshTree(ledgerSpread.getActiveSheet(), refreshNode);
-                ledgerSpreadObj.setFontColor();
-            }
-        },
-        refreshTree: function (sheet, data) {
-            SpreadJsObj.massOperationSheet(sheet, function () {
-                const tree = sheet.zh_tree;
-                // 处理删除
-                if (data.delete) {
-                    data.delete.sort(function (x, y) {
-                        return y.deleteIndex - x.deleteIndex;
-                    });
-                    for (const d of data.delete) {
-                        sheet.deleteRows(d.deleteIndex, 1);
-                    }
-                }
-                // 处理新增
-                if (data.create) {
-                    const newNodes = data.create;
-                    if (newNodes) {
-                        newNodes.sort(function (a, b) {
-                            return a.index - b.index;
-                        });
+            $('#stage_audits').change(function () {
+                self.uid = $(this).val();
+            });
 
-                        for (const node of newNodes) {
-                            sheet.addRows(node.index, 1);
-                            SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(node), 1);
-                        }
-                    }
-                }
-                // 处理更新
-                if (data.update) {
-                    const rows = [];
-                    for (const u of data.update) {
-                        rows.push(tree.nodes.indexOf(u));
-                    }
-                    SpreadJsObj.reLoadRowsData(sheet, rows);
-                }
-                // 处理展开
-                if (data.expand) {
-                    const expanded = [];
-                    for (const e of data.expand) {
-                        if (expanded.indexOf(e) === -1) {
-                            const posterity = tree.getPosterity(e);
-                            for (const p of posterity) {
-                                sheet.setRowVisible(tree.nodes.indexOf(p), p.visible);
-                                expanded.push(p);
-                            }
+            // 多人协同
+            $('#cooperation').on('shown.bs.modal', function () {
+                // 执行一些动作...
+                // 更新新的多人协同表格信息
+                const newUidList = [];
+                $('.stage_div ul li').each(function (k, v) {
+                    const uid = $(v).find('button').eq(0).data('id');
+                    if(uid) newUidList.push(uid);
+                });
+                const oldUidList = [];
+                $('#stage_audits option').each(function (k, v) {
+                    const uid = parseInt($(v).val());
+                    if(k !== 0) oldUidList.push(uid);
+                });
+                if (!_.isEqual(oldUidList, newUidList)) {
+                    const yb = _.find(accountList, { 'id': cur_uid });
+                    let newhtml = '<option value="' + yb.id + '">' + yb.name + '(原报)</option>';
+                    if(newUidList.length > 0) {
+                        for (const [i,id] of newUidList.entries()) {
+                            const audit = _.find(accountList, { 'id': id });
+                            newhtml += '<option value="' + audit.id + '">' + audit.name + '(' + transFormToChinese(i+1) + '审)</option>';
                         }
                     }
+                    $('#stage_audits').html(newhtml);
+                    self.uid = cur_uid;
                 }
+                self.initLedgerTree(cur_uid);
             });
-        },
-        enterCell: function(e, info) {
-            if (info.sheet.zh_setting && info.col === 2) {
-                if (ledgerSpread.getActiveSheet().getValue(info.row, info.col) !== '') {
-                    const col = info.sheet.zh_setting.cols[info.col];
-                    info.sheet.setActiveCell(info.row, 2);
-                    info.sheet.startEdit(true);
-                }
+            $('#del-audit-ass').click(function () {
+                self.setAuditAssist();
+            });
+        }
+        set uid(id) {
+            this._uid = parseInt(id);
+            this._refreshAss();
+        }
+        get uid() {
+            return this._uid;
+        }
+        _refreshAssTable(){
+            $('#stage_audit').text($("#stage_audits option:selected").text());
+            const html = [];
+            for (const sa of this.showAssList) {
+                const lid = sa.ass_ledger_id ? sa.ass_ledger_id.split(',') : [];
+                html.push(`<tr><td>${sa.name}</td><td>${sa.company}</td><td>${lid.length}</td></tr>`);
             }
-        },
-        editStarting: function (e, info) {
-            if (info.sheet.zh_setting) {
-                const select = SpreadJsObj.getSelectObject(info.sheet);
-                const col = info.sheet.zh_setting.cols[info.col];
-                ledgerSpread.getActiveSheet().getCell(info.row, 2).foreColor('#000');
-                if(col.getValue(select) === '请输入密码') {
-                    ledgerSpread.getActiveSheet().setValue(info.row, 2, '');
-                }
+            $('#coo_table').html(html.join(''));
+        }
+        _refreshAssTree() {
+            const ledgerAss = {};
+            for (const sa of this.showAssList) {
+                const ledger = sa.ass_ledger_id ? sa.ass_ledger_id.split(',') : [];
+                ledger.forEach(l => { ledgerAss[l] = sa; });
             }
-        },
-        editEnded: function (e, info) {
-            if (info.sheet.zh_setting) {
-                const select = SpreadJsObj.getSelectObject(info.sheet);
-                const col = info.sheet.zh_setting.cols[info.col];
-                const validText = trimInvalidChar(info.editingText);
-                const user_id = parseInt($('#stage_audits').val());
-                const orgValue = select[col.field];
-                const reg = /^[0-9a-zA-Z]+$/;
-                if(validText !== '' && !reg.test(validText)) {
-                    toastr.error('不能输入非数字和字母的字符');
-                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                    ledgerSpreadObj.setFontColor(info.row);
-                    return;
-                }
-                if((validText === '' && orgValue === undefined) || validText == orgValue) {
-                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                    ledgerSpreadObj.setFontColor(info.row);
-                    return;
-                }
-                // const num = _.filter(ledger_cooperation_list, { pwd: validText, status: 1, user_id });
-                // console.log(num, ledger_cooperation_list);
-                // if(num.length > 0) {
-                //     toastr.error('同一个审批人密码不能相同');
-                //     SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                //     ledgerSpreadObj.setFontColor(info.row);
-                //     return;
-                // }
-                select.pwd = validText;
-                const data = {
-                    type: 'pwd',
-                    ledger_id: select.ledger_id,
-                    user_id,
-                    pwd: validText,
-                };
-                info.sheet.setSelection(info.row, 0, 1, 1);
-                postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
-                    const lcindex = _.findIndex(ledger_cooperation_list, { ledger_id: select.ledger_id, user_id });
-                    let flag = false;
-                    if(lcindex !== -1) {
-                        validText === '' ? ledger_cooperation_list.splice(lcindex, 1) : ledger_cooperation_list.splice(lcindex, 1, result);
-                        flag = validText === '';
-                    } else {
-                        ledger_cooperation_list.push(result);
-                        flag = false;
-                    }
-                    $('#cooperation-num').text(ledger_cooperation_list.length);
-                    setLeftTable(ledgerTree.nodes, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
-                    selects = [select];
-                    // if(select) {
-                    //     setAllChildrenCanEdit(select, flag);
-                    //     setParentCanEdit(ledgerTree.nodes, select.ledger_pid, flag);
-                    // }
-                    const refreshNode = ledgerTree.loadPostData({update: selects});
-                    ledgerSpreadObj.refreshTree(info.sheet, refreshNode);
-                    ledgerSpreadObj.setFontColor();
-                });
+            for (const n of this.tree.nodes) {
+                const la = ledgerAss[n.ledger_id];
+                n.ass_audit_id = la ? la.ass_user_id : null;
+                n.ass_name = la ? la.name : '';
             }
-        },
-        deletePress: function (sheet) {
-            return;
-        },
-        clipboardPasted(e, info) {
-            // 禁止复制粘贴
-            SpreadJsObj.reLoadSheetHeader(ledgerSpread.getActiveSheet());
-            SpreadJsObj.reLoadSheetData(ledgerSpread.getActiveSheet());
-            return;
+            SpreadJsObj.reloadColData(this.sheet, 2);
+        }
+        _refreshAss() {
+            this.showAssList = _.filter(this.assList, { 'user_id': parseInt(this.uid) });
+            this._refreshAssTable();
+            this._refreshAssTree();
+        }
+        initLedgerTree(uid) {
+            if (this.loaded) return;
+            this.spread.refresh();
+            const self = this;
+
+            postData('/tender/' + cur_tenderid + '/shenpi/ass/load', {}, function (data) {
+                self.loaded = true;
+                self.assList = data.auditAssList;
+                self.tree.loadDatas(data.ledgerList);
+                SpreadJsObj.loadSheetData(self.sheet, SpreadJsObj.DataType.Tree, self.tree);
+                self.uid = uid;
+                self.refreshOperate();
+            }, null, true);
         }
-    };
-
-    sjsSettingObj.setFxTreeStyle(ledgerSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
-    if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
-    SpreadJsObj.initSpreadSettingEvents(ledgerSpreadSetting, ledgerCol);
-    SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
-    SpreadJsObj.selChangedRefreshBackColor(ledgerSpread.getActiveSheet());
-    ledgerSpread.bind(spreadNS.Events.EditStarting, ledgerSpreadObj.editStarting);
-    ledgerSpread.bind(spreadNS.Events.EditEnded, ledgerSpreadObj.editEnded);
-    SpreadJsObj.addDeleteBind(ledgerSpread, ledgerSpreadObj.deletePress);
-    ledgerSpread.bind(spreadNS.Events.ClipboardPasted, ledgerSpreadObj.clipboardPasted);
-    ledgerSpread.bind(spreadNS.Events.EnterCell, ledgerSpreadObj.enterCell);
-    let ledger_data, ledger_cooperation_list = [];
+        loadPostData(data) {
+            if (data.add) {
+                this.assList.push(data.add);
+            }
+            if (data.del) {
+                this.assList.splice(this.assList.findIndex(x => { return x.id === data.del.id }), 1);
+            }
+            if (data.update) {
+                for (const d of data.update) {
+                    const od = this.assList.find(x => { return x.id === d.id });
+                    if (!od) continue;
 
-    // 多人协同
-    $('#cooperation').on('shown.bs.modal', function () {
-        // 执行一些动作...
-        // 更新新的多人协同表格信息
-        const newUidList = [];
-        $('.stage_div ul li').each(function (k, v) {
-            const uid = $(v).find('button').eq(0).data('id');
-            if(uid) newUidList.push(uid);
-        });
-        const oldUidList = [];
-        $('#stage_audits option').each(function (k, v) {
-            const uid = parseInt($(v).val());
-            if(k !== 0) oldUidList.push(uid);
-        });
-        if (!_.isEqual(oldUidList, newUidList)) {
-            const yb = _.find(accountList, { 'id': cur_uid });
-            let newhtml = '<option value="' + yb.id + '">' + yb.name + '(原报)</option>';
-            if(newUidList.length > 0) {
-                for (const [i,id] of newUidList.entries()) {
-                    const audit = _.find(accountList, { 'id': id });
-                    newhtml += '<option value="' + audit.id + '">' + audit.name + '(' + transFormToChinese(i+1) + '审)</option>';
+                    od.ass_ledger_id = d.ass_ledger_id;
                 }
             }
-            $('#stage_audits').html(newhtml);
-            if(ledger_data) {
-                setLeftTable(ledgerTree.nodes, ledger_cooperation_list, cur_uid, yb.name + '(原报)');
-                ledgerSpreadObj.setAllRightPwd(cur_uid);
+            this._refreshAss();
+        }
+        setAuditAssist(assist){
+            const node = SpreadJsObj.getSelectObject(this.sheet);
+            if (assist && node.ass_audit_id === assist.id) return;
+            if (assist && assist.id === this.uid) {
+                toastr.warning('请勿添加本人');
+                return;
             }
-        }
-        if(!ledger_data) {
-            postData('/tender/' + cur_tenderid + '/shenpi/ledger/load', {}, function (data) {
-                ledger_data = true;
-                const ledgerList = setRightData(data.ledgerList, data.ledgerCooperationList);
-                ledgerTree.loadDatas(ledgerList);
-                // treeCalc.calculateAll(ledgerTree);
-                selects = [];
-                // updateByCanEdit(ledgerTree.nodes, _.filter(ledger_cooperation_list, { user_id: cur_uid }), false);
-                ledgerTree.loadPostData({update: selects});
-                console.log(ledgerTree);
-                ledger_cooperation_list = data.ledgerCooperationList;
-                const yb = _.find(accountList, { 'id': cur_uid });
-                setLeftTable(ledgerTree.nodes, ledger_cooperation_list, cur_uid, yb.name + '(原报)');
-                SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, ledgerTree);
-                ledgerSpreadObj.setFontColor();
-            }, null, true);
-        }
-        ledgerSpread.refresh();
-    });
-
-    $('#stage_audits').change(function () {
-        const uid = $(this).val();
-        const title = $("#stage_audits option:selected").text();
-        setLeftTable(ledgerTree.nodes, ledger_cooperation_list, uid, title);
-        ledgerSpreadObj.setAllRightPwd(uid);
-    });
-
-    $('body').on('click', '.del-pwd', function () {
-        const ledger_id = parseInt($(this).data('lid'));
-        const user_id = parseInt($(this).data('uid'));
-        const data = {
-            type: 'pwd',
-            user_id,
-            ledger_id,
-            pwd: '',
-        };
-        postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
-            const lcindex = _.findIndex(ledger_cooperation_list, { ledger_id, user_id });
-            ledger_cooperation_list.splice(lcindex, 1);
-            setLeftTable(ledgerTree.nodes, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
-            const select = _.find(ledgerTree.nodes, { ledger_id });
-            delete select.pwd;
-            // const refreshNode = ledgerTree.loadPostData({update: select});
-            selects = [select];
-            // if(select) {
-            //     setAllChildrenCanEdit(select, true);
-                // setParentCanEdit(ledgerTree.nodes, select.ledger_pid, true);
-            // }
-            const refreshNode = ledgerTree.loadPostData({update: selects});
-            ledgerSpreadObj.refreshTree(ledgerSpread.getActiveSheet(), refreshNode);
-            ledgerSpreadObj.setFontColor();
-            $('#cooperation-num').text(ledger_cooperation_list.length);
-        });
-    });
-
-    $('body').on('click', '.edit-pwd', function () {
-        const pwd = $(this).data('pwd');
-        const html = `<div class="input-group input-group-sm">
-            <input type="text" class="form-control" value="${pwd}" placeholder="输入新密码" aria-describedby="button-${$(this).data('lid')}" style="width:50px">
-            <div class="input-group-append" id="button-${$(this).data('lid')}">
-                <button class="btn btn-outline-primary confirm-btn" data-pwd="${pwd}" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" type="button">确认</button>
-                <button class="btn btn-outline-secondary cancel-btn" data-pwd="${pwd}" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" type="button">取消</button>
-            </div>
-            </div>`;
-        $(this).parents('td').html(html);
-    });
-
-    $('body').on('click', '.cancel-btn', function () {
-        const html = `<p class="mb-0">${$(this).data('pwd')}</p><a href="javascript:void(0);" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" data-pwd="${$(this).data('pwd')}" class="edit-pwd">修改</a> <a href="javascript:void(0)" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" class="del-pwd text-danger">移除</a>`;
-        $(this).parents('td').html(html);
-    });
-
-    $('body').on('click', '.confirm-btn', function () {
-        const validText = $(this).parents('td').find('input').val();
-        const orgValue = $(this).data('pwd');
-        const ledger_id = parseInt($(this).data('lid'));
-        const user_id = parseInt($(this).data('uid'));
-        const reg = /^[0-9a-zA-Z]+$/;
-        if(!reg.test(validText)) {
-            toastr.error('不能输入非数字和字母的字符');
-            return;
-        }
-        if(validText == orgValue) {
-            const html = `<p class="mb-0">${$(this).data('pwd')}</p><a href="javascript:void(0);" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" data-pwd="${$(this).data('pwd')}" class="edit-pwd">修改</a> <a href="javascript:void(0)" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" class="del-pwd text-danger">移除</a>`;
-            $(this).parents('td').html(html);
-            return;
-        }
-        // const num = _.filter(ledger_cooperation_list, { pwd: validText, status: 1, user_id });
-        // if(num.length > 0) {
-        //     toastr.error('同一个审批人密码不能相同');
-        //     return;
-        // }
-        const data = {
-            type: 'pwd',
-            ledger_id,
-            user_id,
-            pwd: validText,
-        };
-        postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
-            const lcindex = _.findIndex(ledger_cooperation_list, { ledger_id, user_id });
-            ledger_cooperation_list.splice(lcindex, 1, result);
-            setLeftTable(ledgerTree.nodes, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
-            const select = _.find(ledgerTree.nodes, { ledger_id });
-            select.pwd = validText;
-            const refreshNode = ledgerTree.loadPostData({update: select});
-            ledgerSpreadObj.refreshTree(ledgerSpread.getActiveSheet(), refreshNode);
-            ledgerSpreadObj.setFontColor();
-        });
-    });
 
-    // 上传图片
-    $('body').on('click', '.upload-img', function () {
-        $(this).siblings('input').trigger('click');
-        // $('.upload-img-file').trigger('click');
-    });
-    $('body').on('change', '.upload-img-file', function () {
-        const file = this.files[0];
-        const ext = file.name.toLowerCase().split('.').splice(-1)[0];
-        const imgStr = /(png|PNG)$/;
-        if (!imgStr.test(ext)) {
-            toastr.error('请上传签名大小为600x300,格式PNG透明背景。');
-            return
-        }
-        if ($(this).val()) {
-            var _self = $(this);
-            const formData = new FormData();
-            formData.append('id', $(this).data('id'));
-            formData.append('file', this.files[0]);
-            postDataWithFile(window.location.pathname + '/save-sign', formData, function (result) {
-                _self.siblings('img').attr('src', '/' + result);
-                _self.siblings('img').show();
-                _self.siblings('a').removeClass('btn btn-outline-primary btn-sm').addClass('d-inline-flex').text('更改');
-                _self.val('');
-                const lcindex = _.findIndex(ledger_cooperation_list, { id: _self.data('id') });
-                ledger_cooperation_list[lcindex].sign_path = result;
-                ledger_cooperation_list.splice(lcindex, 1, ledger_cooperation_list[lcindex]);
+            const self = this;
+            const data = { type: 'audit-ass'};
+            if (assist) {
+                const newAss = this.assList.find(x => { return x.ass_user_id === assist.id; });
+                if (!newAss) {
+                    data.add = { user_id: this.uid, ass_user_id: assist.id, name: assist.name, company: assist.company, role: assist.role, ass_ledger_id: node.ledger_id + '' };
+                } else {
+                    data.update = [{ id: newAss.id, ass_ledger_id: newAss.ass_ledger_id + ',' + node.ledger_id}];
+                }
+            }
+            if (node.ass_audit_id) {
+                const orgAss = this.assList.find(x => { return x.ass_user_id === node.ass_audit_id; });
+                const rela_lid = orgAss.ass_ledger_id ? orgAss.ass_ledger_id.split(',') : [];
+                rela_lid.splice(rela_lid.indexOf(node.ledger_id + ''), 1);
+                if (rela_lid.length === 0) {
+                    data.del = { id: orgAss.id };
+                } else {
+                    if (data.update) data.update = [];
+                    data.update.push({ id: orgAss.id, ass_ledger_id: rela_lid.join(',')});
+                }
+            }
+            postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
+                self.loadPostData(result);
             });
         }
-    });
-
+        refreshOperate() {
+            const node = SpreadJsObj.getSelectObject(this.sheet);
+            if (node.ass_audit_id) {
+                $('#del-audit-ass').show();
+                $('#audit-ass_dropdownMenuButton')[0].innerHTML = '替换协同人';
+            } else {
+                $('#del-audit-ass').hide();
+                $('#audit-ass_dropdownMenuButton')[0].innerHTML = '添加协同人';
+            }
+        }
+    }
+    const auditAss = new AuditAss();
 
     $.subMenu({
         menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
@@ -1106,117 +881,4 @@ $(document).ready(function () {
         const X = $('#tender-list').find('.result').eq(now-1).offset().top;
         $('#tender-list').scrollTop(X - $('#tender-list').offset().top + $('#tender-list').scrollTop() -30);
     });
-
-    $('body').on('blur', '#coo_table .edit-company', function () {
-        const id = $(this).data('id');
-        const newVal = $(this).val();
-        const cooInfo = _.find(ledger_cooperation_list, { id: id });
-        if(cooInfo && cooInfo.company !== newVal) {
-            const data = {
-                type: 'company',
-                id,
-                company: newVal,
-            };
-            postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
-                const lcindex = _.findIndex(ledger_cooperation_list, { id: id });
-                cooInfo.company = newVal;
-                ledger_cooperation_list.splice(lcindex, 1, cooInfo);
-            });
-        }
-    })
 });
-
-function setRightData(datas, coolist) {
-    const newdatas = [];
-    const reg = /(^GD([0-9\-]+))|(^([0-9\-]+)$)/;
-    for (const l of datas) {
-        // if (reg.test(l.code) && l.level <= 4) {
-        if (reg.test(l.code)) {
-            // if(l.level === 4 && l.is_leaf === false) {
-            //     l.is_leaf = true;
-            // }
-            const coo = _.find(coolist, { 'ledger_id': l.ledger_id, 'user_id': cur_uid });
-            if(coo) {
-                l.pwd = coo.pwd;
-            }
-            l.can_edit = true;
-            newdatas.push(l);
-        }
-    }
-    // for (const nd of newdatas) {
-    //     const child = _.find(newdatas, { 'ledger_pid': nd.ledger_id });
-    //     if (!child && !nd.is_leaf) {
-    //         nd.is_leaf = true;
-    //     }
-    // }
-    return newdatas;
-}
-function updateByCanEdit(datas, coolist, flag) {
-    for (const coo of coolist) {
-        const ledgerInfo = _.find(datas, { 'ledger_id': coo.ledger_id });
-        if(ledgerInfo) {
-            setAllChildrenCanEdit(ledgerInfo, flag);
-            // setParentCanEdit(datas, ledgerInfo.ledger_pid, flag);
-        }
-    }
-}
-function setParentCanEdit(datas, id, flag) {
-    if(id !== -1) {
-        const pl = _.find(datas, { 'ledger_id': id });
-        // 判断父节点下所有子节点有无pwd,有则为false
-        if (pl) {
-            const existfalse = _.find(pl.children, function (item) {
-                return item.can_edit === false || (item.pwd && item.pwd !== '' && item.pwd !== null);
-            });
-            pl.can_edit = existfalse ? false : flag;
-            selects.push(pl);
-            setParentCanEdit(datas, pl.ledger_pid, flag);
-        }
-    }
-}
-function setAllChildrenCanEdit(ledgerInfo, flag) {
-    if (ledgerInfo.children && ledgerInfo.children.length > 0) {
-        for(const li of ledgerInfo.children) {
-            if (li.pwd === undefined || li.pwd === null || li.pwd === '') {
-                li.can_edit = flag;
-                selects.push(li);
-                setAllChildrenCanEdit(li, flag);
-            }
-        }
-    }
-}
-function setLeftTable(ledgerList, coolist, uid, title) {
-    $('#stage_audit').text(title);
-    const showCooList = _.filter(coolist, { 'user_id': parseInt(uid) });
-    const removeList = [];
-    for (const sc of showCooList) {
-        const info = _.find(ledgerList, { 'ledger_id': sc.ledger_id });
-        if (info) {
-            sc.code = info.code;
-            sc.name = info.name;
-        } else {
-            removeList.push(sc);
-        }
-    }
-    if (removeList.length > 0) {
-        // 移除
-        _.pull(showCooList, ...removeList);
-        console.log(removeList);
-    }
-
-    let html = '';
-    // showCooList 需要根据右边台账顺序进行排序
-    showCooList.sort(function (a, b) {
-        return _.findIndex(ledgerList, { ledger_id: a.ledger_id }) - _.findIndex(ledgerList, { ledger_id: b.ledger_id });
-    });
-    for (const sc of showCooList) {
-        const pichtml = sc.sign_path ? `<img src="/${sc.sign_path}" width="60"><input type="file" data-id="${sc.id}" class="upload-img-file" style="display: none;"><a href="javascript: void(0);" class="d-inline-flex upload-img">更改</a>`
-            : `<img src="" style="display: none" width="60"><input type="file" data-id="${sc.id}" class="upload-img-file" style="display: none;"><a href="javascript: void(0);" class="btn btn-outline-primary btn-sm upload-img">上传签名</a>`;
-        html += `<tr>` +
-            `<td>${sc.code} ${sc.name}</td>` +
-            `<td><p class="mb-0">${sc.pwd}</p><a href="javascript:void(0);" data-lid="${sc.ledger_id}" data-uid="${sc.user_id}" data-pwd="${sc.pwd}" class="edit-pwd">修改</a> <a href="javascript:void(0)" data-lid="${sc.ledger_id}" data-uid="${sc.user_id}" class="del-pwd text-danger">移除</a></td>` +
-            `<td>${pichtml}</td><td><input type="text" class="form-control form-control-sm edit-company" data-id="${sc.id}" value="${sc.company}" placeholder="输入单位名称或备注"></td>` +
-            `</tr>`;
-    }
-    $('#coo_table').html(html);
-}

+ 0 - 1
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -1208,7 +1208,6 @@ const SpreadJsObj = {
                 for (let iRow = 0; iRow < sheet.getRowCount(); iRow++) {
                     sheet.getCell(iRow, i).locked(col.readOnly || sheet.zh_setting.readOnly || false);
                 }
-                //sheet.getRange(-1, i, -1, 1, GC.Spread.Sheets.SheetArea.viewport).locked(col.readOnly || sheet.zh_setting.readOnly || false);
             });
         }
         this.endMassOperation(sheet);

+ 74 - 141
app/public/js/stage.js

@@ -324,16 +324,6 @@ $(document).ready(() => {
     };
     const stagePos = new StagePosData(stagePosSetting);
 
-    const setCooperationSelectHtml = function () {
-        const selectHtml = [];
-        selectHtml.push('<option>全部</option>');
-        const cooName = _.uniqWith(_.map(stageTree.pwd, 'company'));
-        for (const i of cooName) {
-            selectHtml.push('<option>', i, '</option>');
-        }
-        $('#cooperationSelect').html(selectHtml.join(''));
-    };
-
     function checkUsed(tree, pos, node) {
         if (node.children && node.children.length > 0) {
             const posterity = tree.getPosterity(node);
@@ -359,48 +349,6 @@ $(document).ready(() => {
                 if (!checkZero(pp.contract_qty) || !checkZero(pp.qc_qty) || !checkZero(pp.qc_minus_qty)) return true;
             }
         }
-    }
-
-    const reloadCooperationHtml = function () {
-        const html = [];
-        const select = $('#cooperationSelect').val();
-        const list = select !== '全部' ? _.filter(stageTree.pwd, { company: select }) : stageTree.pwd;
-        // 根据台账排序
-        list.sort(function (a, b) {
-            return _.findIndex(stageTree.nodes, { ledger_id: a.ledger_id }) - _.findIndex(stageTree.nodes, { ledger_id: b.ledger_id });
-        });
-        for (const p of list) {
-            if (!p.node) continue;
-            if (p.confirm) {
-                html.push(`<tr class="text-success">`);
-            } else {
-                html.push(`<tr>`);
-            }
-            html.push(`<td>${p.node.code}</td>`, `<td>${p.node.name}</td>`);
-
-            let lockedHint = '';
-            const lockedInfo = stageTree.getLockedInfo(p.node);
-            if (lockedInfo.length > 0) {
-                const lockedUser = lockedInfo.map(x => { return  x.name; });
-                lockedHint = ` (审批人 ${lockedUser.join(',')} 确认通过)`;
-            }
-            html.push('<td>', p.check ? `已解锁` : `<a name="ledger-unlock" lid="${p.ledger_id}" href="javascript: void(0);">解锁</a>`, lockedHint, '</td>');
-            html.push('<td>', p.company, '</td>');
-            if ((stage.status === auditConst.status.checking || stage.status === auditConst.status.checkNoPre) && !checkUsed(stageTree, stagePos, p.node)) {
-                html.push('<td>', '本期未计量', '</td>');
-            } else if (p.check && p.confirm) {
-                html.push('<td>', moment(p.confirm_time).format('YYYY-MM-DD HH:mm') + ' ' + `<a name="ledger-unconfirm" href="javascript: void(0);" lid="${p.ledger_id}">重新审批</a>`, '</td>');
-            } else if (!p.check && p.confirm) {
-                html.push('<td>', moment(p.confirm_time).format('YYYY-MM-DD HH:mm'), '</td>');
-            } else if (p.check && !p.confirm && !readOnly) {
-                html.push('<td>', `<a name="ledger-confirm" href="javascript: void(0);" lid="${p.ledger_id}" class="btn btn-sm btn-success">确认</a>`, '</td>');
-            } else {
-                html.push('<td>', '</td>');
-            }
-            html.push('</tr>');
-        }
-        $('#cooperationList').html(html.join(''));
-
     };
 
     class Changes {
@@ -773,7 +721,7 @@ $(document).ready(() => {
     ];
     ledgerSpreadSetting.readOnly = function (data) {
         if (!data) return false;
-        return data.lock || false;
+        return data.lock || stageTreeSpreadObj.assistReadOnly;
     };
     SpreadJsObj.initSheet(slSpread.getActiveSheet(), ledgerSpreadSetting);
 
@@ -2098,22 +2046,14 @@ $(document).ready(() => {
     });
 
     // 加载计量单元数据 - 暂时统一加载,如有需要,切换成动态加载并缓存
-    postData(window.location.pathname + '/load', { filter: 'ledger;pos;detail;change;import_change;tag;cooperation' }, function (result) {
+    postData(window.location.pathname + '/load', { filter: 'ledger;pos;detail;change;import_change;tag;' }, function (result) {
         // 加载树结构
-        console.log(result.locked);
         stageTree.loadDatas(result.ledgerData, result.locked);
+        if (stage.assist) stageTree.loadFilter(stage.assist.ass_ledger_id);
         // 加载部位明细
         stagePos.loadDatas(result.posData);
         checkShowLast(result.ledgerData.length);
         treeCalc.calculateAll(stageTree);
-        // 加载解锁相关
-        if (result.cooperation) {
-            stageTree.loadPwd(result.cooperation, 'bills-p-' + userID + '-' + window.location.pathname.split('/')[2], result.cooperationConfirm);
-            $('#cooperationCount').html(stageTree.pwd.length || '');
-            if (stageTree.pwd.length > 0) $('#cooperationCount').parent().show();
-            setCooperationSelectHtml();
-            reloadCooperationHtml();
-        }
         for (const t of result.tags) {
             t.node = stageTree.datas.find(x => {return x.id === t.lid});
         }
@@ -2124,7 +2064,7 @@ $(document).ready(() => {
         stagePosSpreadObj.loadCurPosData();
         SpreadJsObj.resetTopAndSelect(spSpread.getActiveSheet());
         // 加载中间计量
-        stageIm.init(stage, imType, tenderInfo.decimal);
+        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);
 
         errorList.loadHisErrorData();
@@ -4624,7 +4564,7 @@ $(document).ready(() => {
     $('[name=stage-start]').submit(function (e) {
         if (checkAuditorFrom()) {
             // 再检查多人协同确认情况
-            const list = stageTree.pwd.find(x => {return !x.confirm });
+            const list = stage.relaAssists.find(x => {return !x.confirm });
             if(list) {
                 toastr.error('请检查多人协同确认情况再上报');
                 $('#hide-all').hide();
@@ -4638,17 +4578,17 @@ $(document).ready(() => {
     });
     $('#audit-check0').submit(function (e) {
         // 再检查多人协同确认情况
-        const list = stageTree.pwd.find(x => {return !x.confirm && checkUsed(stageTree, stagePos, x.node) });
+        const list = stage.relaAssists.find(x => {return !x.confirm && stageAssistRela.checkAssistPartUsed(x) });
         if(list) {
             toastr.error('请检查多人协同确认情况再审批通过');
             $('#hide-all').hide();
             return false;
         }
         const checkType = parseInt($('[name=checkType]').val());
-        const noUsed = stageTree.pwd.filter(x => {return !checkUsed(stageTree, stagePos, x.node) });
+        const noUsed = stage.relaAssists.filter(x => {return !stageAssistRela.checkAssistPartUsed(x) });
         const data = {
             opinion: $(`${'#sp-done'}`).find('[name=opinion]').val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' '),
-            coop_noUsed: noUsed && noUsed.length > 0 ? noUsed.map(x => { return x.id; }) : [],
+            coop_noUsed: noUsed && noUsed.length > 0 ? noUsed.map(x => { return x.ass_ledger_id; }) : [],
             checkType,
         };
         $('#sp-done').modal('hide');
@@ -4658,7 +4598,7 @@ $(document).ready(() => {
     });
     $('#audit-check1').submit(function (e) {
         const checkType = parseInt($('[name=checkType]:checked').val());
-        const confirm = stageTree.pwd.filter(x => {return x.confirm });
+        const confirm = stage.relaAssists.filter(x => { return x.confirm });
         const data = {
             opinion: $(`${'#sp-back'}`).find('[name=opinion]').val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' '),
             coop_checked: confirm && confirm.length > 0 ? confirm.map(x => { return x.id; }) : [],
@@ -4707,77 +4647,6 @@ $(document).ready(() => {
         }
         stageTreeSpreadObj.measureByBatch(posName, ratio, apply2sibling);
     });
-
-    $('body').on('click', '[name=ledger-unlock]', function() {
-        $('#cooperation').modal('hide');
-        const lid = this.getAttribute('lid');
-        if (!lid) return;
-
-        const p = stageTree.pwd.find(x => {return x.ledger_id == lid});
-        if (!p) return;
-
-        $('#unlock-info').html(`${p.node.code} ${p.node.name} 解锁密码<b class="text-danger">*</b>`);
-        $('#unlock-pwd').val('').removeClass('is-invalid');
-        $('.invalid-feedback', '#unlock').hide();
-        $('.alert-warning', '#unlock').hide();
-        $('#unlock-ok').attr('lid', lid);
-        $('#unlock').modal('show');
-    });
-    $('#cooperation').on('show.bs.modal', function(){reloadCooperationHtml();});
-    $('#unlock-ok').click(function () {
-        const lid = this.getAttribute('lid');
-        if (!lid) return;
-
-        const p = stageTree.pwd.find(x => {return x.ledger_id == lid});
-        if (!p) return;
-
-        if (p.pwd === $('#unlock-pwd').val()) {
-            const refresh = stageTree.lockNode(p, false);
-            // 修改线上
-            SpreadJsObj.reloadRowsReadonly(slSpread.getActiveSheet(), refresh);
-            stagePosSpreadObj.loadCurPosData();
-            $('#unlock').modal('hide');
-            reloadCooperationHtml();
-        } else {
-            $('#unlock-pwd').addClass('is-invalid');
-            $('.invalid-feedback', '#unlock').show();
-            $('.alert-warning', '#unlock').show();
-        }
-    });
-    $('#cooperationSelect').change(function () {
-        reloadCooperationHtml();
-    });
-    // 确认
-    $('body').on('click', '[name=ledger-confirm]', function() {
-        const lid = this.getAttribute('lid');
-        if (!lid) return;
-
-        const p = stageTree.pwd.find(x => {return x.ledger_id == lid});
-        if (!p) return;
-        // 修改线上
-        postData(window.location.pathname + '/save/cooperation', { type: 'save-confirm', postData: { ledger_id: lid } }, function (result) {
-            p.confirm = true;
-            p.confirm_time = new Date();
-            stageTree.confirmList = result.cooperationConfirm;
-            reloadCooperationHtml();
-        });
-    });
-
-    // 确认
-    $('body').on('click', '[name=ledger-unconfirm]', function() {
-        const lid = this.getAttribute('lid');
-        if (!lid) return;
-
-        const p = stageTree.pwd.find(x => {return x.ledger_id == lid});
-        if (!p) return;
-        // 修改线上
-        postData(window.location.pathname + '/save/cooperation', { type: 'del-confirm', postData: { ledger_id: lid } }, function (result) {
-            p.confirm = false;
-            p.confirm_time = null;
-            stageTree.confirmList = result.cooperationConfirm;
-            reloadCooperationHtml();
-        });
-    });
     // 收方单生成
     $('#setshoufang').click(function () {
         const lid = $('#shoufang-lid').val();
@@ -4975,7 +4844,71 @@ $(document).ready(() => {
                 }, null);
             }
         });
-    })
+    });
+    const stageAssistRela = {
+        refreshAssConfirmBtn: function () {
+            if (stage.assist) {
+                const caption = stage.assist.user_id === stage.user_id
+                    ? (stage.assist.confirm ? '撤销上报' : '确认上报')
+                    : (stage.assist.confirm ? '审批通过' : '重新审批');
+                if (!stage.readOnly || stage.assist.locked) $('#ass-confirm-btn').show().html(caption);
+            } else {
+                $('#ass-confirm-btn').hide();
+            }
+        },
+        checkAssistPartUsed(assist) {
+            const ass_lid = assist.ass_ledger_id.split(',');
+            for (const lid of ass_lid) {
+                const node = stageTree.getItems(lid);
+                if (checkUsed(stageTree, stagePos, node)) return true;
+            }
+            return false;
+        },
+        refreshAssistTable: function () {
+            const html = [];
+            for (const ra of stage.relaAssists) {
+                const used = this.checkAssistPartUsed(ra);
+                const hint = used
+                    ? (ra.confirm ? `<td class="text-success">${moment(ra.update_time).format('YYYY-MM-DD HH:mm')}</td>` : '<td class="text-warning">待确认</td>')
+                    : '<td>本期未计量</td>';
+                html.push(`<tr><td>${ra.name}</td><td>${ra.company}</td>${hint}</tr>`);
+            }
+            return $('#assistList').html(html.join(''));
+        },
+        refreshAssists: function () {
+            if (stage.relaAssists.length > 0) {
+                $('#assistCount').html(stage.relaAssists.length || '').parent().show();
+            } else {
+                $('#assistCount').parent().hide();
+            }
+        },
+        refreshReadOnly(){
+            stageTreeSpreadObj.assistReadOnly = stage.readOnly || stage.revising || (!!stage.assist && !!stage.assist.confirm);
+            SpreadJsObj.refreshSheetReadOnly(slSpread.getActiveSheet());
+            stagePosSpreadObj.loadCurPosData();
+        },
+        init: function() {
+            this.refreshAssConfirmBtn();
+            this.refreshReadOnly();
+            this.refreshAssists();
+        },
+    };
+    stageAssistRela.init();
+    $('#ass-confirm-btn').click(() => {
+        const data = { user_id: stage.assist.user_id, ass_user_id: stage.assist.ass_user_id, confirm: !stage.assist.confirm, type: 'stage' };
+        postData(window.location.pathname + '/ass-confirm', data, function(result) {
+            window.location.reload();
+            // stage.assist = result;
+            // stageAssistRela.refreshAssConfirmBtn();
+            // stageAssistRela.refreshReadOnly();
+        });
+    });
+    $('#assists').on('show.bs.modal', function(){
+        postData(window.location.pathname + '/ass', { user_id: userID, type: 'stage' }, function (result) {
+            stage.relaAssists = result;
+            stageAssistRela.refreshAssistTable();
+        });
+    });
 });
 function makeOneShouFang(sf) {
     const lData = _.find(ledgerData, { id: sf.lid });

+ 8 - 2
app/public/js/stage_im.js

@@ -13,7 +13,7 @@ const stageIm = (function () {
     const resetFields = ['peg', 'bw', 'xm', 'drawing_code', 'calc_memo', 'position', 'jldy'];
     const splitChar = '-';
     const mergeChar = ';';
-    let stage, imType, decimal, details, changes, importChanges, detailsAtt, ImData, pre, orgImData;
+    let stage, imType, decimal, filter, details, changes, importChanges, detailsAtt, ImData, pre, orgImData;
     let up_field = 'unit_price';
     const gsTreeSetting = {
         id: 'ledger_id',
@@ -55,10 +55,11 @@ const stageIm = (function () {
     };
     const gsPos = new StagePosData(gsPosSetting);
 
-    function init (s, i, d) {
+    function init (s, i, d, f) {
         stage = s;
         imType = i;
         decimal = d;
+        filter = f;
     }
 
     function initCheck () {
@@ -71,6 +72,7 @@ const stageIm = (function () {
     function loadData (ledger, pos, stageDetail, stageChange, stageImportChange, stageDetailAtt) {
         up_field = 'unit_price';
         gsTree.loadDatas(ledger);
+        gsTree.loadFilter(filter);
         treeCalc.calculateAll(gsTree);
 
         gsPos.loadDatas(pos);
@@ -929,6 +931,8 @@ const stageIm = (function () {
     function recursiveBuildImData (nodes) {
         if (!nodes || nodes.length === 0) { return; }
         for (const node of nodes) {
+            if (node.filter !== undefined && node.filter) continue;
+
             if (gsTree.isLeafXmj(node) || ((stage.im_type !== imType.bw.value && stage.im_type !== imType.bb.value) && stage.im_gather && node.check)) {
                 if (stage.im_type === imType.tz.value) {
                     generateTzImData(node);
@@ -1007,6 +1011,8 @@ const stageIm = (function () {
         if (!nodes || nodes.length === 0) { return; }
         for (const node of nodes) {
             if (refreshNodeIds.indexOf(node.id) < 0) continue;
+            if (node.filter) continue;
+
             if (gsTree.isLeafXmj(node) || ((stage.im_type !== imType.bw.value && stage.im_type !== imType.bb.value) && stage.im_gather && node.check)) {
                 if (stage.im_type === imType.tz.value) {
                     generateTzImData(node);

+ 5 - 1
app/router.js

@@ -158,7 +158,7 @@ module.exports = app => {
     app.get('/tender/:id/shenpi', sessionAuth, tenderCheck, 'tenderController.shenpiSet');
     app.post('/tender/:id/shenpi/save', sessionAuth, tenderCheck, 'tenderController.saveTenderInfoShenpi');
     app.post('/tender/:id/shenpi/audit/save', sessionAuth, tenderCheck, 'tenderController.saveShenpiAudit');
-    app.post('/tender/:id/shenpi/ledger/load', sessionAuth, tenderCheck, 'tenderController.loadLedgerData');
+    app.post('/tender/:id/shenpi/ass/load', sessionAuth, tenderCheck, 'tenderController.loadAuditAss');
     app.post('/tender/:id/shenpi/save-sign', sessionAuth, tenderCheck, 'tenderController.saveCooperateSign');
     app.post('/tender/:id/copy-setting', sessionAuth, tenderCheck, 'tenderController.copyTender');
     app.post('/tender/:id/tourist/audit/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.saveTourist');
@@ -657,6 +657,10 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/sumLoad', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'tenderController.sumLoad');
     app.post('/tender/:id/revise/:rid/info/sumLoad', sessionAuth, tenderCheck, uncheckTenderCheck, reviseCheck, 'tenderController.sumLoad');
 
+    // 多人协同
+    app.post('/tender/:id/measure/stage/:order/ass', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'tenderController.auditAssist');
+    app.post('/tender/:id/measure/stage/:order/ass-confirm', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'tenderController.auditAssistConfirm');
+
     // 扫码登录
     app.get('/wxAuth', 'loginController.wxAuth');
     app.get('/wxproject', 'loginController.wxProject');

+ 99 - 0
app/service/audit_ass.js

@@ -0,0 +1,99 @@
+'use strict';
+
+/**
+ *  奖罚金
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class AuditAss extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'audit_ass';
+        }
+
+        async getData(tid) {
+            return await this.getAllDataByCondition({ where: { tid } });
+        }
+
+        async getUserData(tid, user_id) {
+            return await this.getAllDataByCondition({ where: { tid, user_id } });
+        }
+
+        async _addData(data) {
+            if (!data.user_id || !data.ass_user_id) throw '新增协审错误';
+            const nd = {
+                pid: this.ctx.session.sessionProject.id,
+                tid: this.ctx.tender.id,
+                user_id: data.user_id,
+                ass_user_id: data.ass_user_id,
+                name: data.name,
+                company: data.company,
+                role: data.role,
+                ass_ledger_id: data.ass_ledger_id,
+            };
+            const result = await this.db.insert(this.tableName, nd);
+            nd.id = result.insertId;
+            return nd;
+        }
+
+        async _delData(data) {
+            await this.db.delete(this.tableName, {id: data.id});
+            return data;
+        }
+
+        async _updateData(data) {
+            if (!data) return;
+            const datas = data instanceof Array ? data : [datas];
+
+            const orgDatas = await this.getAllDataByCondition({
+                where: { id: this.ctx.helper._.map(datas, 'id') }
+            });
+            const uDatas = [];
+            for (const d of datas) {
+                const od = this.ctx.helper._.find(orgDatas, {id: d.id});
+                if (!od) continue;
+
+                const nd = {id: od.id};
+                if (d.ass_ledger_id !== undefined) nd.ass_ledger_id = d.ass_ledger_id;
+                uDatas.push(nd);
+            }
+            if (uDatas.length > 0) {
+                await this.db.updateRows(this.tableName, uDatas);
+                return uDatas;
+            } else {
+                return [];
+            }
+        }
+
+        async updateData(data) {
+            const result = { update: [] };
+            try {
+                if (data.add) {
+                    result.add = await this._addData(data.add);
+                }
+                if (data.update) {
+                    result.update = await this._updateData(data.update);
+                }
+                if (data.del) {
+                    result.del = await this._delData(data.del);
+                }
+                return result;
+            } catch (err) {
+                if (err) result.err = err;
+                return result;
+            }
+        }
+    }
+
+    return AuditAss;
+};

+ 0 - 70
app/service/cooperation_confirm.js

@@ -59,76 +59,6 @@ module.exports = app => {
         async delBycheckNoPre(uid, stage, transaction) {
             return await transaction.delete(this.tableName, { tid: stage.tid, sid: stage.id, times: stage.times, uid });
         }
-
-        async lockConfirm4CheckNoPre(stage, uid, pre_uid, transaction) {
-            if (!transaction) throw '缺少参数';
-
-            const locked = await this.getAllDataByCondition({ where: { sid: stage.id, locked: 1, times: stage.curTimes } });
-            // 审批人数据锁定
-            const locking = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, uid: uid } });
-            if (locking.length > 0) {
-                const updateLock = locking.map(x => { return { id: x.id, locked: 1 }; });
-                await transaction.updateRows(this.tableName, updateLock);
-            }
-            const preConfirm = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, uid: pre_uid } });
-            // 前审批人数据,与被锁定数据相关数据确认状态保留
-            locked.push(...locking);
-            if (preConfirm.length > 0) {
-                const delConfirm = [];
-                for (const pc of preConfirm) {
-                    const bills = await this.ctx.service.ledger.getDataByCondition({ tender_id: stage.tid, ledger_id: pc.ledger_id });
-                    const billsOwner = bills.full_path.split('-');
-                    const exist = locked.find(x => { return billsOwner.indexOf(x.ledger_id + '') >= 0; });
-                    if (!exist) delConfirm.push(pc.id);
-                }
-                if (delConfirm.length > 0) await transaction.delete(this.tableName, { id: delConfirm });
-            }
-        }
-
-        async lockComfirm4CheckNo(stage, uid, auditors, transaction) {
-            if (!transaction) throw '缺少参数';
-            // 审批人数据锁定
-            const lockConfirm = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, uid: uid } });
-            if (lockConfirm.length === 0) return;
-
-            const insertData = [];
-            lockConfirm.forEach(x => {
-                delete x.id;
-                x.locked = 1;
-                x.times = x.times + 1;
-                insertData.push(x);
-            });
-            // 审批人前所有角色,与锁定数据相关确认状态保留
-            const keepUid = [ stage.user_id ];
-            for (const a of auditors) {
-                if (a.aid === uid) break;
-                keepUid.push(a.aid);
-            }
-            const keepConfirm = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, uid: keepUid } });
-            for (const kc of keepConfirm) {
-                const bills = await this.ctx.service.ledger.getDataByCondition({ tender_id: stage.tid, ledger_id: kc.ledger_id });
-                const billsOwner = bills.full_path.split('-');
-                const exist = lockConfirm.find(x => { return billsOwner.indexOf(x.ledger_id + '') >= 0; });
-                if (exist) {
-                    delete kc.id;
-                    kc.locked = 0;
-                    kc.times = kc.times + 1;
-                    insertData.push(kc);
-                }
-            }
-            if (insertData.length > 0) await transaction.insert(this.tableName, insertData);
-        }
-
-        async cancelLock(stage, uid, transaction) {
-            await transaction.update(this.tableName, { locked: 0 }, { where: { sid: stage.id, times: stage.curTimes, uid } });
-        }
-
-        async getLockedId(tender, stage) {
-            const sql = `SELECT cc.ledger_id, cc.uid, pa.name FROM ${this.tableName} cc` +
-                    `  LEFT JOIN ${this.ctx.service.projectAccount.tableName} pa ON cc.uid = pa.id` +
-                    '  WHERE cc.sid = ? AND cc.times = ? AND cc.locked = 1';
-            return await this.db.query(sql, [stage.id, stage.times]);
-        }
     }
 
     return CooperationConfirm;

+ 7 - 2
app/service/stage.js

@@ -288,8 +288,12 @@ module.exports = app => {
             }
             if (stages.length !== 0 && !this.ctx.session.sessionUser.is_admin) {
                 const lastStage = stages[0];
-                if (lastStage.status === auditConst.status.uncheck && lastStage.user_id !== this.ctx.session.sessionUser.accountId && !this.ctx.tender.isTourist) {
-                    stages.splice(0, 1);
+                if (lastStage.status === auditConst.status.uncheck && !this.ctx.tender.isTourist) {
+                    const assist = await this.ctx.service.auditAss.getAllDataByCondition({ where: { tid: tenderId, user_id: lastStage.user_id } });
+                    const assistId = assist.map(x => { return x.ass_user_id });
+                    if (lastStage.user_id !== this.ctx.session.sessionUser.accountId && assistId.indexOf(this.ctx.session.sessionUser.accountId) < 0) {
+                        stages.splice(0, 1);
+                    }
                 }
             }
             // 最新一期计量(未审批完成),当前操作人的期详细数据,应实时计算
@@ -581,6 +585,7 @@ module.exports = app => {
                 await transaction.delete(this.ctx.service.stageRelaBillsFinal.tableName, { sid: id });
                 await transaction.delete(this.ctx.service.stageRelaIm.tableName, { sid: id });
                 await transaction.delete(this.ctx.service.stageRelaImBills.tableName, { sid: id });
+                await transaction.delete(this.ctx.service.stageAuditAss.tableName, { sid: id });
                 // 删除计量合同支付附件
                 const payList = await this.ctx.service.stagePay.getAllDataByCondition({ where: { sid: id } });
                 if (payList) {

+ 50 - 26
app/service/stage_audit.js

@@ -316,7 +316,7 @@ module.exports = app => {
                     cache_time_r: this.ctx.stage.cache_time_l,
                 });
                 // 多人协同,取消下一审批人存在的锁定
-                await this.ctx.service.cooperationConfirm.cancelLock(this.ctx.stage, audit.aid, transaction);
+                await this.ctx.service.stageAuditAss.cancelLock(this.ctx.stage, audit.aid, transaction);
 
                 // 添加短信通知-需要审批提醒功能
                 // const smsUser = await this.ctx.service.projectAccount.getDataById(audit.aid);
@@ -336,7 +336,9 @@ module.exports = app => {
                 // }
                 const stageInfo = await this.ctx.service.stage.getDataById(audit.sid);
                 const shenpiUrl = await this.ctx.helper.urlToShort(this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/stage/' + stageInfo.order);
-                await this.ctx.helper.sendAliSms(audit.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, {
+                const users = this._.map(this.ctx.stage.auditAssists.filter(x => { return x.user_id === audit.aid }), 'ass_user_id');
+                users.push(audit.aid);
+                await this.ctx.helper.sendAliSms(users, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, {
                     qi: stageInfo.order,
                     code: shenpiUrl,
                 });
@@ -349,7 +351,7 @@ module.exports = app => {
                     tips: wxConst.tips.check,
                     code: this.ctx.session.sessionProject.code,
                 };
-                await this.ctx.helper.sendWechat(audit.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), wxConst.template.stage, wechatData);
+                await this.ctx.helper.sendWechat(users, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), wxConst.template.stage, wechatData);
 
                 // 上报/审批 - 检查三方特殊推送
                 await this.ctx.service.specMsg.addStageMsg(transaction, this.ctx.session.sessionProject.id, this.ctx.stage, pushOperate.stage.flow);
@@ -391,16 +393,25 @@ module.exports = app => {
                 // 添加推送
                 const noticeContent = await this.getNoticeContent(pid, audit.tid, stageId, audit.aid, checkData.opinion);
                 const auditors = await this.getAuditGroupByListWithOwner(stageId, times);
+                const defaultNoticeRecord = {
+                    pid,
+                    type: pushType.stage,
+                    status: auditConst.status.checked,
+                    content: noticeContent,
+                };
                 const records = [];
                 auditors.forEach(audit => {
                     records.push({
-                        pid,
-                        type: pushType.stage,
                         uid: audit.aid,
-                        status: auditConst.status.checked,
-                        content: noticeContent,
+                        ...defaultNoticeRecord,
                     });
                 });
+                stage.userAssists.forEach(u => {
+                    records.push({ uid: u.ass_user_id, ...defaultNoticeRecord});
+                });
+                stage.auditAssists.forEach(u => {
+                    records.push({ uid: u.ass_user_id, ...defaultNoticeRecord});
+                });
                 await transaction.insert('zh_notice', records);
 
                 await transaction.update(this.tableName, {
@@ -450,7 +461,7 @@ module.exports = app => {
                         cache_time_r: this.ctx.stage.cache_time_l,
                     });
                     // 多人协同,取消下一审批人存在的锁定
-                    await this.ctx.service.cooperationConfirm.cancelLock(this.ctx.stage, nextAudit.aid, transaction);
+                    await this.ctx.service.stageAuditAss.cancelLock(this.ctx.stage, nextAudit.aid, transaction);
 
                     // 添加短信通知-需要审批提醒功能
                     // const smsUser = await this.ctx.service.projectAccount.getDataById(nextAudit.aid);
@@ -471,7 +482,9 @@ module.exports = app => {
                     // }
                     const stageInfo = await this.ctx.service.stage.getDataById(nextAudit.sid);
                     const shenpiUrl = await this.ctx.helper.urlToShort(this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/stage/' + stageInfo.order);
-                    await this.ctx.helper.sendAliSms(nextAudit.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, {
+                    const users = this._.map(this.ctx.auditAssists.filter(x => { return x.user_id === nextAudit.id; }), 'ass_user_id');
+                    users.push(nextAudit.aid);
+                    await this.ctx.helper.sendAliSms(users, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, {
                         qi: stageInfo.order,
                         code: shenpiUrl,
                     });
@@ -483,7 +496,7 @@ module.exports = app => {
                         tips: wxConst.tips.check,
                         code: this.ctx.session.sessionProject.code,
                     };
-                    await this.ctx.helper.sendWechat(nextAudit.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), wxConst.template.stage, wechatData);
+                    await this.ctx.helper.sendWechat(users, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), wxConst.template.stage, wechatData);
                 } else {
                     await this.ctx.service.tenderTag.saveTenderTag(this.ctx.tender.id, {stage_time: new Date()}, transaction);
                     const his_id = await this.ctx.service.ledgerHistory.checkBackupLedgerHistory(this.ctx.stage.tid, this.ctx.stage.id);
@@ -537,7 +550,7 @@ module.exports = app => {
                     //     const content = '【纵横计量支付】' + ptmsg + '第' + stageInfo.order + '期,审批通过。';
                     //     sms.send(mobile_array, content);
                     // }
-                    const users = this._.uniq(this._.concat(this._.map(auditList, 'aid'), stageInfo.user_id));
+                    const users = this._.uniq(this._.concat(this._.map(auditList, 'aid'), stageInfo.user_id, this._.map(this.ctx.stage.userAssists, 'ass_user_id'), this._.map(this.ctx.stage.auditAssists, 'ass_user_id')));
                     await this.ctx.helper.sendAliSms(users, smsTypeConst.const.JL, smsTypeConst.judge.result.toString(), SmsAliConst.template.stage_result, {
                         qi: stageInfo.order,
                         status: SmsAliConst.status.success,
@@ -589,24 +602,30 @@ module.exports = app => {
                 await this._updateTender(transaction);
                 // 添加推送
                 const noticeContent = await this.getNoticeContent(pid, audit.tid, stageId, audit.aid, checkData.opinion);
+                const defaultNoticeRecord = {
+                    pid,
+                    type: pushType.stage,
+                    status: auditConst.status.checkNo,
+                    content: noticeContent,
+                };
                 const records = [
                     {
-                        pid,
-                        type: pushType.stage,
                         uid: this.ctx.stage.user_id,
-                        status: auditConst.status.checkNo,
-                        content: noticeContent,
+                        ...defaultNoticeRecord,
                     },
                 ];
                 auditors.forEach(audit => {
                     records.push({
-                        pid,
-                        type: pushType.stage,
                         uid: audit.aid,
-                        status: auditConst.status.checkNo,
-                        content: noticeContent,
+                        ...defaultNoticeRecord,
                     });
                 });
+                stage.userAssists.forEach(u => {
+                    records.push({ uid: u.ass_user_id, ...defaultNoticeRecord});
+                });
+                stage.auditAssists.forEach(u => {
+                    records.push({ uid: u.ass_user_id, ...defaultNoticeRecord});
+                });
                 await transaction.insert(this.ctx.service.noticePush.tableName, records);
 
                 // 计算并合同支付最终数据
@@ -654,7 +673,7 @@ module.exports = app => {
                 await this.ctx.service.stageTempLand.updateHistory(this.ctx.stage, transaction);
 
                 // 锁定本人数据,保留锁定数据相关确认状态
-                await this.ctx.service.cooperationConfirm.lockComfirm4CheckNo(this.ctx.stage, audit.aid, auditors, transaction);
+                await this.ctx.service.stageAuditAss.lockConfirm4CheckNo(this.ctx.stage, audit.aid, auditors, transaction);
 
                 // 添加短信通知-审批退回提醒功能
                 // const mobile_array = [];
@@ -685,7 +704,7 @@ module.exports = app => {
                 //     const content = '【纵横计量支付】' + ptmsg + '第' + stageInfo.order + '期,审批退回。';
                 //     sms.send(mobile_array, content);
                 // }
-                const users = this._.uniq(this._.concat(this._.map(auditList, 'aid'), stageInfo.user_id));
+                const users = this._.uniq(this._.concat(this._.map(auditList, 'aid'), stageInfo.user_id, this._.map(this.ctx.stage.userAssists, 'ass_user_id'), this._.map(this.ctx.stage.auditAssists, 'ass_user_id')));
                 await this.ctx.helper.sendAliSms(users, smsTypeConst.const.JL, smsTypeConst.judge.result.toString(), SmsAliConst.template.stage_result, {
                     qi: stageInfo.order,
                     status: SmsAliConst.status.back,
@@ -821,7 +840,7 @@ module.exports = app => {
                 await this.ctx.service.stageTempLand.updateHistory(this.ctx.stage, transaction);
 
                 // 锁定本人数据,保留锁定数据相关确认状态
-                await this.ctx.service.cooperationConfirm.lockConfirm4CheckNoPre(this.ctx.stage, audit.aid, preAuditor.aid, transaction);
+                await this.ctx.service.stageAuditAss.lockConfirm4CheckNoPre(this.ctx.stage, audit.aid, preAuditor.aid, transaction);
 
                 // 同步 期信息
                 await transaction.update(this.ctx.service.stage.tableName, {
@@ -848,7 +867,9 @@ module.exports = app => {
                 // }
                 const stageInfo = await this.ctx.service.stage.getDataById(audit.sid);
                 const shenpiUrl = await this.ctx.helper.urlToShort(this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/stage/' + stageInfo.order);
-                await this.ctx.helper.sendAliSms(preAuditor.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, {
+                const users = this._.map(this.ctx.stage.auditAssists.filter(x => {return x.user_id === preAuditor.aid}), 'ass_user_id');
+                user.push(preAuditor.aid);
+                await this.ctx.helper.sendAliSms(users, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, {
                     qi: stageInfo.order,
                     code: shenpiUrl,
                 });
@@ -860,7 +881,7 @@ module.exports = app => {
                     tips: wxConst.tips.check,
                     code: this.ctx.session.sessionProject.code,
                 };
-                await this.ctx.helper.sendWechat(preAuditor.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), wxConst.template.stage, wechatData);
+                await this.ctx.helper.sendWechat(users, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), wxConst.template.stage, wechatData);
 
                 // 上报/审批 - 检查三方特殊推送
                 await this.ctx.service.specMsg.addStageMsg(transaction, pid, this.ctx.stage, pushOperate.stage.flow);
@@ -1011,7 +1032,9 @@ module.exports = app => {
                 // }
                 const stageInfo = await this.ctx.service.stage.getDataById(audit.sid);
                 const shenpiUrl = await this.ctx.helper.urlToShort(this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/stage/' + stageInfo.order);
-                await this.ctx.helper.sendAliSms(audit.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, {
+                const users = this._.map(this.ctx.stage.auditAssist.filter(x => { return x.user_id == audit.aid; }), 'ass_user_id');
+                users.push(audit.aid);
+                await this.ctx.helper.sendAliSms(users, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, {
                     qi: stageInfo.order,
                     code: shenpiUrl,
                 });
@@ -1023,7 +1046,7 @@ module.exports = app => {
                     tips: wxConst.tips.check,
                     code: this.ctx.session.sessionProject.code,
                 };
-                await this.ctx.helper.sendWechat(audit.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), wxConst.template.stage, wechatData);
+                await this.ctx.helper.sendWechat(users, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), wxConst.template.stage, wechatData);
 
                 // 上报/审批 - 检查三方特殊推送
                 await this.ctx.service.specMsg.addStageMsg(transaction, this.ctx.session.sessionProject.id, this.ctx.stage, pushOperate.stage.flow);
@@ -1302,6 +1325,7 @@ module.exports = app => {
             await transaction.delete(this.ctx.service.stageChange.tableName, { sid, stimes: times });
             await transaction.delete(this.ctx.service.stagePay.tableName, { sid, stimes: times });
             await transaction.delete(this.ctx.service.pay.tableName, { csid: sid, cstimes: times });
+            await transaction.delete(this.ctx.serivce.stageAuditAss.tableName, { sid, times });
             // 其他台账
             await this.ctx.service.stageJgcl.deleteStageTimesData(sid, times, transaction);
             await this.ctx.service.stageOther.deleteStageTimesData(sid, times, transaction);

+ 175 - 0
app/service/stage_audit_ass.js

@@ -0,0 +1,175 @@
+'use strict';
+
+/**
+ *  奖罚金
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const auditConst = require('../const/audit');
+
+module.exports = app => {
+    class StageAuditAss extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'stage_audit_ass';
+        }
+
+        async getData(stage) {
+            const stageData = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.times } });
+            if (stage.status === auditConst.stage.status.checked) return stageData;
+
+            const result = [];
+            const data = await this.ctx.service.auditAss.getData(stage.tid);
+            for (const sd of stageData) {
+                const index = data.findIndex(x => { return x.user_id === sd.user_id && x.ass_user_id === sd.ass_user_id; });
+                if (index >= 0) {
+                    data.splice(index, 1);
+                    result.push(sd);
+                }
+            }
+            result.push(...data);
+            return result;
+        }
+
+        async getUserAssist(stage, user_id) {
+            const stageData = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.times, user_id } });
+            if (stage.status === auditConst.stage.status.checked) return stageData;
+
+            const result = [];
+            const data = await this.ctx.service.auditAss.getUserData(stage.tid, user_id);
+            for (const sd of stageData) {
+                const index = data.findIndex(x => { return x.user_id === sd.user_id && x.ass_user_id === sd.ass_user_id; });
+                if (index >= 0) {
+                    data.splice(index, 1);
+                    result.push(sd);
+                }
+            }
+            result.push(...data);
+            return result;
+        }
+
+        async updateData(data) {
+            if (!data.user_id || !data.ass_user_id || data.confirm === undefined) throw '缺少参数';
+            const stageData = await this.getDataByCondition({ sid: this.ctx.stage.id, times: this.ctx.stage.times, user_id: data.user_id, ass_user_id: data.ass_user_id });
+            if (data.confirm) {
+                if (stageData && stageData.confirm) throw '数据错误';
+                const auditAss = await this.ctx.service.auditAss.getDataByCondition({ tid: this.ctx.tender.id, user_id: data.user_id, ass_user_id: data.ass_user_id });
+                if (stageData) {
+                    await this.defaultUpdate({ id: stageData.id, confirm: 1, ass_ledger_id: auditAss.ass_ledger_id });
+                    stageData.confirm = 1;
+                    return stageData;
+                } else {
+                    const assInfo = await this.ctx.service.projectAccount.getDataById(data.ass_user_id);
+                    const insertData = {
+                        pid: this.ctx.session.sessionProject.id, tid: this.ctx.tender.id,
+                        sid: this.ctx.stage.id, times: this.ctx.stage.times,
+                        user_id: data.user_id, ass_user_id: data.ass_user_id, ass_ledger_id: auditAss.ass_ledger_id,
+                        name: assInfo.name, company: assInfo.company, role: assInfo.role,
+                        confirm: 1,
+                    };
+                    const result = await this.db.insert(this.tableName, insertData);
+                    insertData.id = result.insertId;
+                    return insertData;
+                }
+            } else {
+                if (!stageData && !stage.confirm) throw '数据错误';
+                await this.defaultUpdate({ id: stageData.id, confirm: 0, locked: 0 });
+                stageData.confirm = 0;
+                stageData.locked = 0;
+                return stageData;
+            }
+        }
+
+        async lockConfirm4CheckNoPre(stage, uid, pre_uid, transaction) {
+            if (!transaction) throw '缺少参数';
+
+            const locked = await this.getAllDataByCondition({ where: { sid: stage.id, locked: 1, times: stage.curTimes } });
+            locked.forEach(x => {
+                x.locked_ledger_id = x.ass_ledger_id.split(',');
+            });
+            // 审批人数据锁定
+            const locking = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, user_id: uid } });
+            if (locking.length > 0) {
+                const updateLock = locking.map(x => { return { id: x.id, locked: 1 }; });
+                await transaction.updateRows(this.tableName, updateLock);
+            }
+            const preConfirm = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, user_id: pre_uid } });
+            // 前审批人数据,与被锁定数据相关数据确认状态保留
+            locked.push(...locking);
+            if (preConfirm.length > 0) {
+                const delConfirm = [];
+                for (const pc of preConfirm) {
+                    const bills = await this.ctx.service.ledger.getDataByCondition({ tender_id: stage.tid, ledger_id: pc.ledger_id });
+                    const billsOwner = bills.full_path.split('-');
+                    const exist = locked.find(x => {
+                        for (const id of x.locked_ledger_id) {
+                            if (billsOwner.indexOf(id) >= 0) return true;
+                        }
+                        return false;
+                    });
+                    if (!exist) delConfirm.push(pc.id);
+                }
+                if (delConfirm.length > 0) await transaction.delete(this.tableName, { id: delConfirm });
+            }
+        }
+
+        async lockConfirm4CheckNo(stage, uid, auditors, transaction) {
+            if (!transaction) throw '缺少参数';
+            // 审批人数据锁定
+            const lockConfirm = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, user_id: uid } });
+            if (lockConfirm.length === 0) return;
+
+            const insertData = [];
+            lockConfirm.forEach(x => {
+                delete x.id;
+                x.locked = 1;
+                x.times = x.times + 1;
+                insertData.push(x);
+            });
+            // 审批人前所有角色,与锁定数据相关确认状态保留
+            const keepUid = [ stage.user_id ];
+            for (const a of auditors) {
+                if (a.aid === uid) break;
+                keepUid.push(a.aid);
+            }
+            const keepConfirm = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, uid: keepUid } });
+            for (const kc of keepConfirm) {
+                const bills = await this.ctx.service.ledger.getDataByCondition({ tender_id: stage.tid, ledger_id: kc.ledger_id });
+                const billsOwner = bills.full_path.split('-');
+                const exist = lockConfirm.find(x => {
+                    const lid = x.ass_ledger_id.split(',');
+                    for (const id of lid) {
+                        if (billsOwner.indexOf(id) >= 0) return true;
+                    }
+                    return false;
+                });
+                if (exist) {
+                    delete kc.id;
+                    kc.locked = 0;
+                    kc.times = kc.times + 1;
+                    insertData.push(kc);
+                }
+            }
+            if (insertData.length > 0) await transaction.insert(this.tableName, insertData);
+        }
+
+        async cancelLock(stage, uid, transaction) {
+            await transaction.update(this.tableName, { locked: 0 }, { where: { sid: stage.id, times: stage.times, user_id: uid } });
+        }
+
+        async getLockedId(stage) {
+            return await this.getAllDataByCondition({ where: { sid: stage.id, time: stage.times, locked: 1 } });
+        }
+    }
+
+    return StageAuditAss;
+};

+ 3 - 0
app/service/stage_bills.js

@@ -240,6 +240,7 @@ module.exports = app => {
                         await this._insertStageBillsData(transaction, d, stageBills, ledgerBills);
                     } else {
                         d.id = stageBills.id;
+                        d.said = this.ctx.session.sessionUser.accountId;
                         this._calcStageBillsData(d, stageBills, ledgerBills);
                         await transaction.update(this.tableName, d);
                     }
@@ -267,6 +268,7 @@ module.exports = app => {
                     (data.qc_qty === undefined || stageBills.qc_qty !== data.qc_qty)) {
                     if (stageBills.times === this.ctx.stage.curTimes && stageBills.order === this.ctx.stage.curOrder) {
                         data.id = stageBills.id;
+                        data.said = this.ctx.session.sessionUser.accountId;
                         this._calcStageBillsData(data, stageBills, ledgerBills);
                         await transaction.update(this.tableName, data);
                     } else {
@@ -349,6 +351,7 @@ module.exports = app => {
                         await this._insertStageBillsData(transaction, updateData, stageBills, ledgerBills);
                     } else {
                         updateData.id = stageBills.id;
+                        updateData.said = this.ctx.session.sessionUser.accountId;
                         await transaction.update(this.tableName, updateData);
                     }
                 }

+ 5 - 4
app/service/stage_pos.js

@@ -276,7 +276,7 @@ module.exports = app => {
                 if (d.contract_qty !== undefined || d.qc_qty !== undefined || d.postil !== undefined) {
                     const osp = this._.find(orgStagePos, function (p) { return p.pid === d.pid; });
                     if (osp && osp.times === this.ctx.stage.curTimes && osp.order === this.ctx.stage.curOrder) {
-                        const sp = {id: osp.id};
+                        const sp = { id: osp.id, said: this.ctx.session.sessionUser.accountId };
                         if (d.contract_qty !== undefined) {
                             sp.contract_qty = this.ctx.helper.round(d.contract_qty, precision.value);
                         }
@@ -380,7 +380,7 @@ module.exports = app => {
                 const osp = this._.find(orgStagePos, function (p) { return p.pid === d.pid; });
 
                 if (osp && osp.times === this.ctx.stage.curTimes && osp.order === this.ctx.stage.curOrder) {
-                    const sp = {id: osp.id, pid: osp.pid};
+                    const sp = { id: osp.id, pid: osp.pid, said: this.ctx.session.sessionUser.accountId };
                     if (d.contract_qty !== undefined) {
                         sp.contract_qty = this.ctx.helper.round(d.contract_qty, b.precision.value);
                     }
@@ -424,6 +424,7 @@ module.exports = app => {
                     if (contract_qty === stageBills.contract_qty) continue;
                     updateBillsStage.push({
                         id: stageBills.id,
+                        said: this.ctx.session.sessionUser.accountId,
                         contract_qty: contract_qty,
                         contract_tp: this.ctx.helper.mul(contract_qty, b.unit_price, info.decimal.tp),
                     });
@@ -556,8 +557,8 @@ module.exports = app => {
             }
             if (orgPos && orgPos.times === this.ctx.stage.curTimes && orgPos.order === this.ctx.stage.curOrder) {
                 await transaction.update(this.tableName, noValue
-                    ? { id: orgPos.id, qc_minus_qty: qty || 0 }
-                    : { id: orgPos.id, qc_qty: qty, positive_qc_qty: positiveQty, negative_qc_qty: negativeQty }
+                    ? { id: orgPos.id, qc_minus_qty: qty || 0, said: this.ctx.session.sessionUser.accountId, }
+                    : { id: orgPos.id, qc_qty: qty, positive_qc_qty: positiveQty, negative_qc_qty: negativeQty, said: this.ctx.session.sessionUser.accountId }
                 );
             } else {
                 await transaction.insert(this.tableName, {

+ 4 - 1
app/service/tender.js

@@ -117,7 +117,6 @@ module.exports = app => {
                     '        t.id IN ( SELECT caa.`tid` FROM ' + this.ctx.service.changeApplyAudit.tableName + ' AS caa WHERE caa.`aid` = ' + session.sessionUser.accountId + ' GROUP BY caa.`tid`))' : '';
                 const changePlanSql = this.ctx.session.sessionProject.page_show.openChangePlan ? '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
                     '        t.id IN ( SELECT cpla.`tid` FROM ' + this.ctx.service.changePlanAudit.tableName + ' AS cpla WHERE cpla.`aid` = ' + session.sessionUser.accountId + ' GROUP BY cpla.`tid`))' : '';
-                // 协审sql
                 const changeProjectXsSql = this.ctx.session.sessionProject.page_show.openChangeProject ? '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
                     '        t.id IN ( SELECT cpxa.`tid` FROM ' + this.ctx.service.changeProjectXsAudit.tableName + ' AS cpxa WHERE cpxa.`aid` = ' + session.sessionUser.accountId + ' GROUP BY cpxa.`tid`))' : '';
                 sql = 'SELECT t.`id`, t.`project_id`, t.`name`, t.`status`, t.`category`, t.`ledger_times`, t.`ledger_status`, t.`measure_type`, t.`user_id`, t.`create_time`, t.`total_price`, t.`deal_tp`,' +
@@ -136,6 +135,9 @@ module.exports = app => {
                     // 参与审批 计量期 的标段
                     '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
                     '        t.id IN ( SELECT sa.`tid` FROM ?? As sa WHERE sa.`aid` = ? GROUP BY sa.`tid`))' +
+                    // 参与协审
+                    '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
+                    '        t.id IN ( SELECT sa.`tid` FROM ?? As sa WHERE sa.`ass_user_id` = ? GROUP BY sa.`tid`))' +
                     // 参与审批 变更令 的标段
                     '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
                     '        t.id IN ( SELECT ca.`tid` FROM ?? AS ca WHERE ca.`uid` = ? GROUP BY ca.`tid`))' +
@@ -156,6 +158,7 @@ module.exports = app => {
                 sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, session.sessionProject.id, session.sessionUser.accountId,
                     this.ctx.service.ledgerAudit.tableName, session.sessionUser.accountId,
                     this.ctx.service.stageAudit.tableName, session.sessionUser.accountId,
+                    this.ctx.service.auditAss.tableName, session.sessionUser.accountId,
                     this.ctx.service.changeAudit.tableName, session.sessionUser.accountId,
                     this.ctx.service.reviseAudit.tableName, session.sessionUser.accountId,
                     this.ctx.service.materialAudit.tableName, session.sessionUser.accountId,

+ 3 - 1
app/view/stage/index.ejs

@@ -41,6 +41,8 @@
                 </div>
                 <div class="d-inline-block">
                     <a class="btn btn-sm btn-danger" href="#cooperation" data-toggle="modal" data-target="#cooperation" style="display: none;">多人协同 <i class="fa fa-lock"></i> <span id="cooperationCount"></span></a>
+                    <a id="ass-confirm-btn" class="btn btn-danger btn-sm" href="javascript: void(0)" style="display: none">协同确认</a>
+                    <a class="btn btn-sm btn-danger" href="#assists" data-toggle="modal" data-target="#assists" style="display: none;">多人协同 <i class="fa fa-lock"></i> <span id="assistCount"></span></a>
                 </div>
             </div>
             <div class="ml-auto">
@@ -509,7 +511,7 @@
         regExp = new RegExp(FindText, 'g');
         return this.replace(regExp, RepText);
     }
-    const readOnly = <%- stage.readOnly || stage.revising %>;
+    const readOnly = <%- stage.readOnly || stage.revising || (!!stage.assist && !stage.assist.confirm) %>;
     const ledgerSpreadSetting = JSON.parse('<%- JSON.stringify(ledgerSpread) %>');
     ledgerSpreadSetting.localCache = {
         key: 'stage-bills',

+ 24 - 0
app/view/stage/modal.ejs

@@ -504,6 +504,30 @@
         </div>
     </div>
 </div>
+
+<div class="modal fade" id="assists" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">多人协同</h5>
+            </div>
+            <div class="modal-body">
+                <div class="modal-height-300">
+                    <table class="table table-hover table-bordered">
+                        <thead>
+                        <tr class="text-center"><th>协同人</th><th>单位</th><th>确认数据</th></tr>
+                        </thead>
+                        <tbody id="assistList">
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
 <!--解锁-->
 <div class="modal fade" id="unlock" data-backdrop="static">
     <div class="modal-dialog" role="document">

+ 0 - 1
app/view/tender/shenpi.ejs

@@ -175,6 +175,5 @@
     const cur_tenderid = parseInt('<%- ctx.tender.id %>');
     const tenders = JSON.parse(unescape('<%- escape(JSON.stringify(tenders)) %>'));
     const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
-    const thousandth = <%- ctx.tender.info.display.thousandth %>;
     const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
 </script>

+ 48 - 14
app/view/tender/shenpi_modal.ejs

@@ -96,13 +96,13 @@
             </div>
             <div class="modal-body">
                 <div class="row">
+                    <% const yb = ctx.helper._.find(accountList, { id: ctx.tender.data.user_id }) %>
                     <div class="col-6">
-                        <% const yb = ctx.helper._.find(accountList, { id: ctx.tender.data.user_id }) %>
                         <div class="modal-height-500" style="overflow: auto;">
                             <table class="table table-hover table-bordered">
-                                <thead>
-                                <tr><th colspan="4" class="text-center" id="stage_audit"><%- yb.name %>(原报)</th></tr>
-                                <tr><th>项目节编号/名称</th><th>密码</th><th>签名</th><th>单位/协同人名称</th></tr>
+                                <thead class="text-center" >
+                                <tr><th colspan="4" id="stage_audit"><%- yb.name %>(原报)</th></tr>
+                                <tr><th>姓名</th><th>单位</th><th>协同处理</th></tr>
                                 </thead>
                                 <tbody id="coo_table">
                                 </tbody>
@@ -110,15 +110,50 @@
                         </div>
                     </div>
                     <div class="col-6">
-                        <div class="mb-2">
-                            <select class="form-control form-control-sm" id="stage_audits">
-                                <option value="<%- ctx.tender.data.user_id %>"><%- yb.name %>(原报)</option>
-                                <% if (shenpi.sp_lc[shenpi.sp_type.stage-1].auditList) { %>
-                                <% for (const [i, audit] of shenpi.sp_lc[shenpi.sp_type.stage-1].auditList.entries()) { %>
-                                <option value="<%- audit.audit_id %>"><%- audit.name %>(<%- ctx.helper.transFormToChinese(i+1) %>审)</option>
-                                <% } %>
-                                <% } %>
-                            </select>
+                        <div class="row">
+                            <div class="col-6">
+                                <div class="mb-2">
+                                    <select class="form-control form-control-sm" id="stage_audits">
+                                        <option value="<%- ctx.tender.data.user_id %>"><%- yb.name %>(原报)</option>
+                                        <% if (shenpi.sp_lc[shenpi.sp_type.stage-1].auditList) { %>
+                                        <% for (const [i, audit] of shenpi.sp_lc[shenpi.sp_type.stage-1].auditList.entries()) { %>
+                                        <option value="<%- audit.audit_id %>"><%- audit.name %>(<%- ctx.helper.transFormToChinese(i+1) %>审)</option>
+                                        <% } %>
+                                        <% } %>
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="col-6">
+                                <div class="text-right">
+                                    <div class="d-inline-block">
+                                        <button class="btn btn-outline-danger btn-sm" id="del-audit-ass">
+                                            移除协同人
+                                        </button>
+                                    </div>
+                                    <div class="dropdown d-inline-block" data-code="audit-ass">
+                                        <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="audit-ass_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                            添加协同人
+                                        </button>
+                                        <div class="dropdown-menu dropdown-menu-right" aria-labelledby="audit-ass_dropdownMenuButton" style="width:220px" id="audit-ass_dropdownMenu">
+                                            <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search" placeholder="姓名/手机 检索" autocomplete="off" data-code="audit-ass"></div>
+                                            <dl class="list-unstyled book-list">
+                                                <% accountGroup.forEach((group, idx) => { %>
+                                                <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>" data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                                                <div class="dd-content" data-toggleid="<%- idx %>">
+                                                    <% group.groupList.forEach(item => { %>
+                                                    <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>">
+                                                        <p class="mb-0 d-flex"><span class="text-primary"><%- item.name %></span><span
+                                                                    class="ml-auto"><%- item.mobile %></span></p>
+                                                        <span class="text-muted"><%- item.role %></span>
+                                                    </dd>
+                                                    <% });%>
+                                                </div>
+                                                <% }) %>
+                                            </dl>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
                         </div>
                         <div class="modal-height-500" style="overflow: auto;" id="ledger-spread">
                         </div>
@@ -127,7 +162,6 @@
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
-                <!--<button type="button" class="btn btn-sm btn-primary">确认</button>-->
             </div>
         </div>
     </div>

+ 37 - 1
sql/update.sql

@@ -4,4 +4,40 @@ ALTER TABLE `zh_revise_price`
 ADD COLUMN `rela_lid`  varchar(1000) NOT NULL DEFAULT '' COMMENT '关联台账id(zh_ledger.ledger_id)' AFTER `use_stage_order`;
 
 ALTER TABLE `zh_revise_price`
-ADD COLUMN `rela_cid`  varchar(5000) NOT NULL DEFAULT '' COMMENT '关联变更令id(zh_change.cid)' AFTER `rela_lid`;
+ADD COLUMN `rela_cid`  varchar(5000) NOT NULL DEFAULT '' COMMENT '关联变更令id(zh_change.cid)' AFTER `rela_lid`;
+
+CREATE TABLE `zh_audit_ass` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `pid` int(11) unsigned NOT NULL COMMENT '项目id',
+  `tid` int(11) unsigned NOT NULL COMMENT '标段id',
+  `user_id` int(11) unsigned NOT NULL COMMENT '主账号id',
+  `ass_user_id` int(11) unsigned NOT NULL COMMENT '协审账号id',
+  `name` varchar(50) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '用户名',
+  `company` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '单位',
+  `role` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '角色',
+  `ass_ledger_id` varchar(1000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '协审台账id(zh_ledger.ledger_id)',
+  PRIMARY KEY (`id`),
+  KEY `idx_tid` (`tid`) USING BTREE,
+  KEY `idx_tid_user` (`tid`,`user_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+CREATE TABLE `zh_stage_audit_ass` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `pid` int(11) unsigned NOT NULL COMMENT '项目id',
+  `tid` int(11) unsigned NOT NULL COMMENT '标段id',
+  `sid` int(11) unsigned NOT NULL COMMENT '期id',
+  `times` int(11) unsigned NOT NULL DEFAULT '1' COMMENT '审批次',
+  `user_id` int(11) unsigned NOT NULL COMMENT '主账号id',
+  `ass_user_id` int(11) unsigned NOT NULL COMMENT '协审人账号id',
+  `ass_ledger_id` varchar(5000) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '协审台账id(zh_ledger.ledger_id)',
+  `name` varchar(50) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '名称',
+  `company` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '单位',
+  `role` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '角色',
+  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `confirm` tinyint(4) unsigned NOT NULL DEFAULT '1',
+  `locked` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '锁定',
+  PRIMARY KEY (`id`),
+  KEY `idx_sid_times` (`sid`,`times`) USING BTREE,
+  KEY `idx_sid_times_user` (`sid`,`times`,`user_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;