瀏覽代碼

Merge branch 'dev' of http://192.168.1.41:3000/maixinrong/Calculation into dev

Tony Kang 1 年之前
父節點
當前提交
f168324125
共有 49 個文件被更改,包括 2338 次插入1743 次删除
  1. 4 3
      app/const/audit.js
  2. 2 2
      app/const/spread.js
  3. 31 29
      app/controller/change_controller.js
  4. 3 2
      app/controller/ledger_audit_controller.js
  5. 15 40
      app/controller/ledger_controller.js
  6. 8 21
      app/controller/revise_controller.js
  7. 3 1
      app/controller/stage_controller.js
  8. 32 1
      app/controller/tender_controller.js
  9. 38 35
      app/middleware/ledger_audit_check.js
  10. 41 39
      app/middleware/revise_audit_check.js
  11. 10 4
      app/middleware/stage_check.js
  12. 10 9
      app/public/js/change_information.js
  13. 1 1
      app/public/js/change_information_add_list.js
  14. 3 7
      app/public/js/change_information_set.js
  15. 4 2
      app/public/js/change_revise.js
  16. 1 1
      app/public/js/path_tree.js
  17. 3 1
      app/public/js/shares/cs_tools.js
  18. 2 2
      app/public/js/shares/tender_select.js
  19. 113 2
      app/public/js/shenpi.js
  20. 21 6
      app/public/js/stage.js
  21. 11 0
      app/public/js/stage_im.js
  22. 1 1
      app/public/report/js/rpt_custom.js
  23. 2 0
      app/router.js
  24. 1 1
      app/service/change.js
  25. 2 2
      app/service/change_audit_list.js
  26. 424 206
      app/service/ledger_audit.js
  27. 2 0
      app/service/ledger_revise.js
  28. 473 248
      app/service/revise_audit.js
  29. 1 1
      app/service/settle_audit.js
  30. 16 0
      app/service/shenpi_audit.js
  31. 2 1
      app/service/stage.js
  32. 111 19
      app/service/stage_audit.js
  33. 1 1
      app/service/stage_audit_ass.js
  34. 13 11
      app/service/tender_cache.js
  35. 0 19
      app/view/change/apply_information.ejs
  36. 0 19
      app/view/change/plan_information.ejs
  37. 0 19
      app/view/change/project_information.ejs
  38. 3 3
      app/view/file/file.ejs
  39. 1 1
      app/view/layout/menu.ejs
  40. 221 248
      app/view/ledger/audit_modal.ejs
  41. 3 3
      app/view/ledger/explode.ejs
  42. 122 121
      app/view/ledger/explode_modal.ejs
  43. 1 1
      app/view/revise/info.ejs
  44. 403 485
      app/view/revise/info_modal.ejs
  45. 123 123
      app/view/tender/detail.ejs
  46. 1 1
      app/view/tender/modal.ejs
  47. 5 1
      app/view/tender/shenpi.ejs
  48. 32 0
      app/view/tender/shenpi_modal.ejs
  49. 18 0
      sql/update.sql

+ 4 - 3
app/const/audit.js

@@ -10,9 +10,10 @@
 
 const auditType = (function () {
     const types = [
-        { key: 'common', name: '个人', value: 1, short: '', long: '', class: '' },
-        { key: 'and', name: '会签', value: 2, short: '会', long: '多人会签', class: 'primary' },
-        { key: 'or', name: '或签', value: 3, short: '或', long: '多人或签', class: 'success' },
+        { key: 'common', name: '个人', value: 1, short: '', long: '', class: '', },
+        { key: 'and', name: '会签', value: 2, short: '会', long: '多人会签', class: 'primary', valid: ['ledger', 'revise', 'stage', 'change'] },
+        { key: 'or', name: '或签', value: 3, short: '或', long: '多人或签', class: 'success', valid: ['ledger', 'revise', 'stage', 'change'] },
+        { key: 'union', name: '协同', value: 4, short: '协', long: '多人协同', class: 'warning', valid: ['stage']},
     ];
     const key = {};
     const info = [];

+ 2 - 2
app/const/spread.js

@@ -284,7 +284,7 @@ const glSpreadTemplate = {
         { key: 'position', valid: 1 },
         { key: 'unit', valid: 1 },
         { key: 'unit_price', valid: 1 },
-        { key: 'deal_calc', valid: 0, },
+        { key: 'deal_calc', valid: 1, },
         { key: 'tz_calc', valid: 1 },
         { key: 'real_qty', valid: 1 },
         { key: 'estimate_qty', valid: 1 },
@@ -792,7 +792,7 @@ const SpreadSpec = {
 };
 const SpecSpreadColFields = [
     { key: 'ex_memo1', fields: ['ex_memo1'] },
-    { key: 'ex_memo3', fields: ['ex_memo2'] },
+    { key: 'ex_memo2', fields: ['ex_memo2'] },
     { key: 'ex_memo3', fields: ['ex_memo3'] },
 ];
 

+ 31 - 29
app/controller/change_controller.js

@@ -428,7 +428,7 @@ module.exports = app => {
                             }
                         }
                         if (change.readOnly && !change.shenpiPower) {
-                            cl.checked_amount = audit_amount.length > 0 ? parseFloat(audit_amount[audit_amount.length - 1]) : 0;
+                            cl.checked_amount = audit_amount.length > 0 ? parseFloat(audit_amount[audit_amount.length - 1]) : cl.camount;
                         }
                     }
                 }
@@ -516,39 +516,41 @@ module.exports = app => {
                     renderData.companyList = companyList;
                     const stateInfo = ctx.helper._.find(fun_set.change_state, { order: ctx.change.state });
                     renderData.deLimit = stateInfo.value;
-                    if (stateInfo.value !== ctx.change.delimit) {
-                        // 需要更新所有的计量上限值,除了已调用的值
-                        const updateList = [];
-                        for (const cl of changeList) {
-                            const one = {
-                                id: cl.id,
-                            };
-                            if (useChangeUsedData.length > 0 && ctx.helper._.findIndex(useChangeUsedData, { cbid: cl.id }) !== -1) {
-                                // 获取比例值
-                                const uc = ctx.helper._.find(useChangeUsedData, { cbid: cl.id });
-                                const minLimit = Math.ceil(ctx.helper.mul(ctx.helper.div(uc.qty, cl.camount), 100));
-                                if (minLimit <= renderData.deLimit) {
+                    if (!change.readOnly) {
+                        if (stateInfo.value !== ctx.change.delimit) {
+                            // 需要更新所有的计量上限值,除了已调用的值
+                            const updateList = [];
+                            for (const cl of changeList) {
+                                const one = {
+                                    id: cl.id,
+                                };
+                                if (useChangeUsedData.length > 0 && ctx.helper._.findIndex(useChangeUsedData, {cbid: cl.id}) !== -1) {
+                                    // 获取比例值
+                                    const uc = ctx.helper._.find(useChangeUsedData, {cbid: cl.id});
+                                    const minLimit = Math.ceil(ctx.helper.mul(ctx.helper.div(uc.qty, cl.camount), 100));
+                                    if (minLimit <= renderData.deLimit) {
+                                        one.delimit = renderData.deLimit;
+                                        cl.delimit = renderData.deLimit;
+                                    }
+                                } else if (cl.delimit !== renderData.deLimit) {
                                     one.delimit = renderData.deLimit;
                                     cl.delimit = renderData.deLimit;
                                 }
-                            } else if (cl.delimit !== renderData.deLimit) {
-                                one.delimit = renderData.deLimit;
-                                cl.delimit = renderData.deLimit;
+                                if (!ctx.helper._.isEqual(one, {id: cl.id})) updateList.push(one);
                             }
-                            if (!ctx.helper._.isEqual(one, { id: cl.id })) updateList.push(one);
+                            console.log(updateList);
+                            if (updateList.length > 0) await ctx.service.changeAuditList.defaultUpdateRows(updateList);
+                            await ctx.service.change.defaultUpdate({delimit: stateInfo.value}, {where: {cid: change.cid}});
+                        }
+                        // 判断是否更新变更类别
+                        if (ctx.helper._.findIndex(fun_set.change_class, {value: change.class, checked: true}) === -1) {
+                            renderData.change.class = ctx.helper._.find(fun_set.change_class, {checked: true}).value;
+                            await ctx.service.change.saveInfo({class: ctx.helper._.find(fun_set.change_class, {checked: true}).value});
+                        }
+                        // 获取固定审批流列表
+                        if (tender.info.shenpi.change === shenpiConst.sp_status.gdspl) {
+                            renderData.spGroupList = await ctx.service.shenpiGroup.getGroupListByChangeType(tender.id, shenpiConst.sp_type.change, 'change');
                         }
-                        console.log(updateList);
-                        if (updateList.length > 0) await ctx.service.changeAuditList.defaultUpdateRows(updateList);
-                        await ctx.service.change.defaultUpdate({ delimit: stateInfo.value }, { where: { cid: change.cid } });
-                    }
-                    // 判断是否更新变更类别
-                    if (ctx.helper._.findIndex(fun_set.change_class, { value: change.class, checked: true }) === -1) {
-                        renderData.change.class = ctx.helper._.find(fun_set.change_class, { checked: true }).value;
-                        await ctx.service.change.saveInfo({ class: ctx.helper._.find(fun_set.change_class, { checked: true }).value });
-                    }
-                    // 获取固定审批流列表
-                    if (tender.info.shenpi.change === shenpiConst.sp_status.gdspl) {
-                        renderData.spGroupList = await ctx.service.shenpiGroup.getGroupListByChangeType(tender.id, shenpiConst.sp_type.change, 'change');
                     }
                 }
                 renderData.changeList = changeList;

+ 3 - 2
app/controller/ledger_audit_controller.js

@@ -143,8 +143,9 @@ module.exports = app => {
                 if (!tender.data || tender.data.ledger_status !== auditConst.status.checking) {
                     throw '当前标段数据有误';
                 }
-                const curAudit = await ctx.service.ledgerAudit.getCurAuditor(tender.id, tender.data.ledger_times);
-                if (curAudit.audit_id !== ctx.session.sessionUser.accountId) {
+                const curAudits = await ctx.service.ledgerAudit.getCurAuditors(tender.id, tender.data.ledger_times);
+                const curAuditIds = curAudits.map(x => { return x.audit_id; });
+                if (curAuditIds.indexOf(ctx.session.sessionUser.accountId) < 0) {
                     throw '审批失败';
                 }
                 const checkType = parseInt(ctx.request.body.checkType);

+ 15 - 40
app/controller/ledger_controller.js

@@ -98,19 +98,10 @@ module.exports = app => {
                 const tender = ctx.tender;
                 const [ledgerSpread, posSpread] = await spreadSetting.getLedgerSpreadSetting(ctx, tender.id,
                     this._ledgerReadOnly(tender.data));
-                const times = tender.data.ledger_status === auditConst.status.checkNo ? tender.data.ledger_times - 1 : tender.data.ledger_times;
 
-                const curAuditor = await ctx.service.ledgerAudit.getCurAuditor(tender.id, tender.data.ledger_times);
-                const auditors = tender.data.ledger_status === auditConst.status.checkNo && tender.data.user_id !== ctx.session.sessionUser.accountId && !ctx.tender.isTourist ?
-                    await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, times) :
-                    await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, tender.data.ledger_times);
-                const user = await ctx.service.projectAccount.getAccountInfoById(ctx.tender.data.user_id);
-                const auditHistory = [];
-                if (times >= 1) {
-                    for (let i = 1; i <= times; i++) {
-                        auditHistory.push(await ctx.service.ledgerAudit.getAuditors(ctx.tender.id, i));
-                    }
-                }
+                await ctx.service.ledgerAudit.loadLedgerUser(tender.data);
+                await ctx.service.ledgerAudit.loadLedgerAuditViewData(tender.data);
+
                 const [stdBills, stdChapters] = await this.ctx.service.valuation.getValuationStdList(
                     ctx.tender.data.valuation, ctx.tender.data.measure_type);
 
@@ -135,12 +126,13 @@ module.exports = app => {
                     tender: tender.data,
                     tenderInfo: tender.info,
                     auditConst,
-                    auditors,
-                    curAuditor,
-                    user,
+                    auditType: audit.auditType,
+                    auditors: tender.data.auditors,
+                    curAuditors: tender.data.curAuditors,
+                    auditHistory: tender.data.auditHistory,
+                    user: tender.data.user,
                     attData,
                     whiteList,
-                    auditHistory,
                     ledgerSpreadSetting: JSON.stringify(ledgerSpread),
                     posSpreadSetting: JSON.stringify(posSpread),
                     tenderMenu,
@@ -149,7 +141,7 @@ module.exports = app => {
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.explode),
                     stdBills,
                     stdChapters,
-                    dealBillsPermission: this._canUpdateDealBills(tender.data, auditors.filter(x => {return x.audit_order > 0})),
+                    dealBillsPermission: this._canUpdateDealBills(tender.data, tender.data.auditors),
                     shenpiConst,
                     categoryData,
                     syncLedgerUrl: syncLedger ? `${ctx.app.config.url3f}/${syncLedger.pull_class}/sync-tz/${tender.id}` : '',
@@ -157,8 +149,8 @@ module.exports = app => {
                     authMobile: pa.auth_mobile,
                     deleteFilePermission: PermissionCheck.delFile(this.ctx.session.sessionUser.permission),
                 };
-                if ((tender.data.ledger_status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) || ((tender.data.ledger_status === auditConst.status.uncheck || tender.data.ledger_status === auditConst.status.checkNo) && tender.data.user_id === ctx.session.sessionUser.accountId)) {
-                    // renderData.accountGroup = accountGroup;
+                if ((tender.data.ledger_status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) ||
+                    ((tender.data.ledger_status === auditConst.status.uncheck || tender.data.ledger_status === auditConst.status.checkNo) && tender.data.user_id === ctx.session.sessionUser.accountId)) {
                     // 获取所有项目参与者
                     const accountList = await ctx.service.projectAccount.getAllDataByCondition({
                         where: { project_id: ctx.session.sessionProject.id, enable: 1 },
@@ -776,27 +768,10 @@ module.exports = app => {
          */
         async ledgerAuditors(ctx) {
             try {
-                const responseData = {
-                    err: 0, msg: '', data: {},
-                };
-                const tender = ctx.tender;
-                const times = tender.data.ledger_status === auditConst.status.checkNo ? tender.data.ledger_times - 1 : tender.data.ledger_times;
-                const auditors = tender.data.ledger_status === auditConst.status.checkNo && tender.data.user_id !== ctx.session.sessionUser.accountId && !ctx.tender.isTourist ?
-                    await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, times) :
-                    await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, tender.data.ledger_times);
-                const user = await ctx.service.projectAccount.getAccountInfoById(ctx.tender.data.user_id);
-                const auditHistory = [];
-                if (times >= 1) {
-                    for (let i = 1; i <= times; i++) {
-                        auditHistory.push(await ctx.service.ledgerAudit.getAuditors(ctx.tender.id, i));
-                    }
-                }
-                responseData.data.auditHistory = auditHistory;
-                // 获取审批流程中左边列表
-                responseData.data.auditors = auditors;
-
-                responseData.data.user = user;
-                ctx.body = responseData;
+                const tender = ctx.tender.data;
+                await ctx.service.ledgerAudit.loadLedgerUser(tender);
+                await ctx.service.ledgerAudit.loadLedgerAuditViewData(tender);
+                ctx.body = {err: 0, msg: '', data: tender};
             } catch (error) {
                 this.log(error);
                 ctx.body = { err: 1, msg: error.toString(), data: null };

+ 8 - 21
app/controller/revise_controller.js

@@ -289,30 +289,19 @@ module.exports = app => {
                 revise.status === audit.revise.status.checking || revise.status === audit.revise.status.checked);
             const [stdBills, stdChapters] = await this.ctx.service.valuation.getValuationStdList(
                 ctx.tender.data.valuation, ctx.tender.data.measure_type);
-            const curAuditor = await ctx.service.reviseAudit.getCurAuditor(revise.id, revise.times);
-            const user = await ctx.service.projectAccount.getAccountInfoById(revise.uid);
-            const times = revise.status === audit.revise.status.checkNo ? revise.times - 1 : revise.times;
-            const auditors = revise.status === audit.revise.status.checkNo && revise.uid === ctx.session.sessionUser.accountId ?
-                await ctx.service.reviseAudit.getAuditorsWithOwner(revise.id, revise.times) :
-                await ctx.service.reviseAudit.getAuditorsWithOwner(revise.id, times);
-            const auditHistory = [];
-            if (times >= 1) {
-                for (let i = 1; i <= times; i++) {
-                    auditHistory.push(await ctx.service.reviseAudit.getAuditors(revise.id, i));
-                }
-            }
+
+            await ctx.service.reviseAudit.loadReviseUser(revise);
+            await ctx.service.reviseAudit.loadReviseAuditViewData(revise);
+
             return {
                 revise, tender: ctx.tender.data,
                 ledgerSpread, posSpread, tenderMenu, measureType,
                 audit: audit.revise,
+                auditType: audit.auditType,
                 jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.revise.info),
                 stdBills,
                 auditConst: audit.revise,
                 stdChapters,
-                curAuditor,
-                auditors,
-                user,
-                auditHistory,
                 shenpiConst,
                 nodeType: stdConst.nodeType,
                 settleStatus: ctx.service.settle.settleStatus,
@@ -352,7 +341,6 @@ module.exports = app => {
             renderData.posSpread.readOnly = true;
             renderData.history = false;
             renderData.historyRevise = [];
-            renderData.curAuditor = await ctx.service.reviseAudit.getCurAuditor(revise.id, revise.times);
             renderData.categoryData = await this.ctx.service.category.getAllCategory(this.ctx.session.sessionProject.id);
             renderData.preUrl = ctx.url.replace('/info', '');
             await this.layout('revise/info.ejs', renderData, 'revise/info_modal.ejs');
@@ -376,8 +364,6 @@ module.exports = app => {
                 const groupList = renderData.accountList.filter(item1 => item1.company === item.name);
                 return { groupName: item.name, groupList };
             });
-            renderData.auditorList = await ctx.service.reviseAudit.getAuditors(revise.id, revise.times);
-            renderData.curAuditor = await ctx.service.reviseAudit.getCurAuditor(revise.id, revise.times);
             renderData.categoryData = await this.ctx.service.category.getAllCategory(this.ctx.session.sessionProject.id);
             renderData.preUrl = ctx.url.replace('/info', '');
             await this.layout('revise/info.ejs', renderData, 'revise/info_modal.ejs');
@@ -841,8 +827,9 @@ module.exports = app => {
                 const revise = ctx.revise;
                 if (!revise || revise.status !== audit.revise.status.checking) throw '台账修订数据有误';
 
-                const curAudit = await ctx.service.reviseAudit.getCurAuditor(revise.id, revise.times);
-                if (curAudit.audit_id !== ctx.session.sessionUser.accountId) throw '审批失败';
+                const curAudits = await ctx.service.reviseAudit.getCurAuditors(revise.id, revise.times);
+                const curAuditorIds = curAudits.map(x => { return x.audit_id; });
+                if (curAuditorIds.indexOf(ctx.session.sessionUser.accountId) < 0) throw '审批失败';
 
                 const checkType = parseInt(ctx.request.body.checkType);
                 if (!checkType || isNaN(checkType)) throw '提交数据错误';

+ 3 - 1
app/controller/stage_controller.js

@@ -368,7 +368,9 @@ module.exports = app => {
                             } else {
                                 responseData.data.ledgerData = await this._getStageLedgerData(ctx);
                             }
-                            responseData.data.locked = await ctx.service.stageAuditAss.getLockedId(ctx.stage);
+                            const assistLocked = await ctx.service.stageAuditAss.getLockedId(ctx.stage);
+                            const auditLocked = await ctx.service.stageAudit.getLockedId(ctx.stage);
+                            responseData.data.locked = [...assistLocked, ...auditLocked];
                             break;
                         case 'pos':
                             if (hpack) {

+ 32 - 1
app/controller/tender_controller.js

@@ -439,7 +439,8 @@ module.exports = app => {
                         const status_name = await this.ctx.service.ledgerAudit.getStatusName(tender.id, tender.ledger_times);
                         tender.status_users = status_name ? status_name.name : '';
                         const times = tender.ledger_status === auditConst.ledger.status.checkNo ? tender.ledger_times - 1 : tender.ledger_times;
-                        tender.auditors = await ctx.service.ledgerAudit.getFinalAuditGroup(tender.id, times);
+                        const history = await ctx.service.ledgerAudit.getAuditorHistory(tender.id, times);
+                        tender.auditors = history[history.length - 1];
                     } else {
                         const status_name = await this.ctx.service.projectAccount.getAccountInfoById(tender.user_id);
                         tender.status_users = status_name ? status_name.name : '';
@@ -1252,6 +1253,36 @@ module.exports = app => {
             }
         }
 
+        async loadUnionAudit(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const result = {};
+                result.unionAuditors = await ctx.service.shenpiAudit.getUnionAudit(ctx.tender.id, shenpiConst.sp_type[data.sp_type], data.audit_order);
+                if (data.ledger) {
+                    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 }
+                    });
+                    result.ledgerList = ledgerData.filter(x => { return !x.b_code; });
+                }
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: [] };
+            }
+        }
+
+        async saveUnionAudit(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                await ctx.service.shenpiAudit.saveUnionAudit(ctx.tender.id, data);
+                ctx.body = {err: 0, msg: '', data: null};
+            } catch(err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存协同审批数据错误');
+            }
+        }
+
         async billsTag(ctx) {
             try {
                 const isValidTourist = ctx.tender.isTourist && ctx.tender.touristPermission.tag;

+ 38 - 35
app/middleware/ledger_audit_check.js

@@ -12,6 +12,42 @@ const status = require('../const/audit').ledger.status;
 const shenpiConst = require('../const/shenpi');
 const _ = require('lodash');
 
+const checkAuditFlow = async function(ctx) {
+    const tender = ctx.tender.data;
+    const info = ctx.tender.info;
+    if (info.shenpi.ledger === shenpiConst.sp_status.sqspr) return;
+    if (tender.ledger_status !== status.uncheck && tender.ledger_status !== status.checkNo) return;
+
+    const shenpi_status = info.shenpi.ledger;
+    // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
+    const auditList = await ctx.service.ledgerAudit.getAuditors(tender.id, tender.times);
+    if (shenpi_status === shenpiConst.sp_status.gdspl) {
+        const shenpiList = await ctx.service.shenpiAudit.getAllDataByCondition({ where: { tid: tender.id, sp_type: shenpiConst.sp_type.ledger, sp_status: shenpi_status } });
+        // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
+        let sameAudit = auditList.length === shenpiList.length;
+        if (sameAudit) {
+            for (const audit of auditList) {
+                const shenpi = shenpiList.find(x => { return x.audit_id === audit.audit_id; });
+                if (!shenpi || shenpi.audit_order !== audit.audit_order || shenpi.audit_type !== audit.audit_type || shenpi.audit_ledger_id !== audit.audit_ledger_id) {
+                    sameAudit = false;
+                    break;
+                }
+            }
+        }
+        if (!sameAudit) await ctx.service.ledgerAudit.updateNewAuditList(tender, shenpiList);
+    } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
+        const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition({ tid: tender.id, sp_type: shenpiConst.sp_type.ledger, sp_status: shenpi_status });
+        // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
+        const lastAuditors = auditList.filter(x => { x.order === auditList[auditList.length - 1].order; });
+        if (shenpiInfo && (lastAuditors.length === 0 || (lastAuditors.length > 1 || shenpiInfo.audit_id !== lastAuditors[0].audit_id))) {
+            await ctx.service.ledgerAudit.updateLastAudit(tender, auditList, shenpiInfo.audit_id);
+        } else if (!shenpiInfo) {
+            // 不存在终审人的状态下这里恢复为授权审批人
+            info.shenpi.ledger = shenpiConst.sp_status.sqspr;
+        }
+    }
+};
+
 module.exports = options => {
     /**
      * 标段校验 中间件
@@ -23,43 +59,10 @@ module.exports = options => {
      */
     return function* ledgerAuditCheck(next) {
         try {
-            if ((this.tender.data.ledger_status === status.uncheck || this.tender.data.ledger_status === status.checkNo) && this.tender.info.shenpi.ledger !== shenpiConst.sp_status.sqspr) {
-                const shenpi_status = this.tender.info.shenpi.ledger;
-                // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
-                const auditList = yield this.service.ledgerAudit.getAllDataByCondition({ where: { tender_id: this.tender.id, times: this.tender.data.ledger_times }, orders: [['audit_order', 'asc']] });
-                const auditIdList = _.map(auditList, 'audit_id');
-                if (shenpi_status === shenpiConst.sp_status.gdspl) {
-                    const shenpiList = yield this.service.shenpiAudit.getAllDataByCondition({ where: { tid: this.tender.id, sp_type: shenpiConst.sp_type.ledger, sp_status: shenpi_status } });
-                    const shenpiIdList = _.map(shenpiList, 'audit_id');
-                    // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
-                    if (!_.isEqual(auditIdList, shenpiIdList)) {
-                        yield this.service.ledgerAudit.updateNewAuditList(this.tender.data, shenpiIdList);
-                    }
-                } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
-                    const shenpiInfo = yield this.service.shenpiAudit.getDataByCondition({ tid: this.tender.id, sp_type: shenpiConst.sp_type.ledger, sp_status: shenpi_status });
-                    // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
-                    if (shenpiInfo && shenpiInfo.audit_id !== _.last(auditIdList)) {
-                        yield this.service.ledgerAudit.updateLastAudit(this.tender.data, auditList, shenpiInfo.audit_id);
-                    } else if (!shenpiInfo) {
-                        // 不存在终审人的状态下这里恢复为授权审批人
-                        this.tender.info.shenpi.ledger = shenpiConst.sp_status.sqspr;
-                    }
-                }
-            }
+            yield checkAuditFlow(this);
             yield next;
         } catch (err) {
-            console.log(err);
-            // 输出错误到日志
-            if (err.stack) {
-                this.logger.error(err);
-            } else {
-                this.getLogger('fail').info(JSON.stringify({
-                    error: err,
-                    project: this.session.sessionProject,
-                    user: this.session.sessionUser,
-                    body: this.session.body,
-                }));
-            }
+            this.log(err);
             // 重定向值标段管理
             this.redirect(this.request.headers.referer);
         }

+ 41 - 39
app/middleware/revise_audit_check.js

@@ -12,6 +12,44 @@ const status = require('../const/audit').revise.status;
 const shenpiConst = require('../const/shenpi');
 const _ = require('lodash');
 
+const checkAuditFlow = async function(ctx) {
+    const info = ctx.tender.info;
+    const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+    if (!revise) throw '台账修订数据有误';
+
+    if (info.shenpi.revise === shenpiConst.sp_status.sqspr) return;
+    if (revise.status !== status.uncheck && revise.status !== status.checkNo) return;
+
+    const shenpi_status = info.shenpi.ledger;
+    // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
+    const auditList = await ctx.service.reviseAudit.getAuditors(revise.id, revise.times);
+    if (shenpi_status === shenpiConst.sp_status.gdspl) {
+        const shenpiList = await ctx.service.shenpiAudit.getAllDataByCondition({ where: { tid: revise.tid, sp_type: shenpiConst.sp_type.revise, sp_status: shenpi_status } });
+        // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
+        let sameAudit = auditList.length === shenpiList.length;
+        if (sameAudit) {
+            for (const audit of auditList) {
+                const shenpi = shenpiList.find(x => { return x.audit_id === audit.audit_id; });
+                if (!shenpi || shenpi.audit_order !== audit.audit_order || shenpi.audit_type !== audit.audit_type || shenpi.audit_ledger_id !== audit.audit_ledger_id) {
+                    sameAudit = false;
+                    break;
+                }
+            }
+        }
+        if (!sameAudit) await ctx.service.reviseAudit.updateNewAuditList(revise, shenpiList);
+    } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
+        const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition({ tid: revise.tid, sp_type: shenpiConst.sp_type.revise, sp_status: shenpi_status });
+        // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
+        const lastAuditors = auditList.filter(x => { x.order === auditList[auditList.length - 1].order; });
+        if (shenpiInfo && (lastAuditors.length === 0 || (lastAuditors.length > 1 || shenpiInfo.audit_id !== lastAuditors[0].audit_id))) {
+            await ctx.service.reviseAudit.updateLastAudit(revise, auditList, shenpiInfo.audit_id);
+        } else if (!shenpiInfo) {
+            // 不存在终审人的状态下这里恢复为授权审批人
+            info.shenpi.revise = shenpiConst.sp_status.sqspr;
+        }
+    }
+};
+
 module.exports = options => {
     /**
      * 标段校验 中间件
@@ -23,46 +61,10 @@ module.exports = options => {
      */
     return function* reviseAuditCheck(next) {
         try {
-            // 获取revise
-            const revise = yield this.service.ledgerRevise.getLastestRevise(this.tender.id);
-            if (!revise) throw '台账修订数据有误';
-            if ((revise.status === status.uncheck || revise.status === status.checkNo) && this.tender.info.shenpi.revise !== shenpiConst.sp_status.sqspr) {
-                const shenpi_status = this.tender.info.shenpi.revise;
-                // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
-                const auditList = yield this.service.reviseAudit.getAllDataByCondition({ where: { rid: revise.id, times: revise.times }, orders: [['audit_order', 'asc']] });
-                const auditIdList = _.map(auditList, 'audit_id');
-                if (shenpi_status === shenpiConst.sp_status.gdspl) {
-                    const shenpiList = yield this.service.shenpiAudit.getAllDataByCondition({ where: { tid: this.tender.id, sp_type: shenpiConst.sp_type.revise, sp_status: shenpi_status } });
-                    const shenpiIdList = _.map(shenpiList, 'audit_id');
-                    // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
-                    if (!_.isEqual(auditIdList, shenpiIdList)) {
-                        yield this.service.reviseAudit.updateNewAuditList(revise, shenpiIdList);
-                    }
-                } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
-                    const shenpiInfo = yield this.service.shenpiAudit.getDataByCondition({ tid: this.tender.id, sp_type: shenpiConst.sp_type.revise, sp_status: shenpi_status });
-                    // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
-                    if (shenpiInfo && shenpiInfo.audit_id !== _.last(auditIdList)) {
-                        yield this.service.reviseAudit.updateLastAudit(revise, auditList, shenpiInfo.audit_id);
-                    } else if (!shenpiInfo) {
-                        // 不存在终审人的状态下这里恢复为授权审批人
-                        this.tender.info.shenpi.revise = shenpiConst.sp_status.sqspr;
-                    }
-                }
-            }
-            yield next;
+            yield checkAuditFlow(this);
+            yield next
         } catch (err) {
-            console.log(err);
-            // 输出错误到日志
-            if (err.stack) {
-                this.logger.error(err);
-            } else {
-                this.getLogger('fail').info(JSON.stringify({
-                    error: err,
-                    project: this.session.sessionProject,
-                    user: this.session.sessionUser,
-                    body: this.session.body,
-                }));
-            }
+            this.log(err);
             // 重定向值标段管理
             this.redirect(this.request.headers.referer);
         }

+ 10 - 4
app/middleware/stage_check.js

@@ -9,6 +9,7 @@
 
 const status = require('../const/audit').stage.status;
 const reviseStatus = require('../const/audit').revise.status;
+const auditType = require('../const/audit').auditType;
 const shenpiConst = require('../const/shenpi');
 const _ = require('lodash');
 
@@ -103,8 +104,13 @@ module.exports = options => {
                     stage.curOrder = stage.curAuditors[0].order - 1
                 }
                 if (!stage.readOnly) {
-                    stage.readOnly = !_.isEqual(stage.flowAuditorIds, stage.curAuditorIds);
-                    stage.canCheck = true;
+                    if (stage.curAuditors[0].audit_type === auditType.key.and) {
+                        stage.readOnly = !_.isEqual(stage.flowAuditorIds, stage.curAuditorIds);
+                        stage.canCheck = true;
+                    } else if (stage.curAuditors[0].audit_type === auditType.key.union) {
+                        stage.readOnly = stage.relaAuditor.status === status.checked;
+                        stage.canCheck = !stage.readOnly
+                    }
                 }
             }
             if (stage.readOnly) {
@@ -148,7 +154,7 @@ module.exports = options => {
                     if (sameAudit) {
                         for (const audit of auditList) {
                             const shenpi = shenpiList.find(x => { return x.audit_id === audit.aid; });
-                            if (!shenpi || shenpi.audit_order !== audit.audit_order || shenpi.audit_type !== audit.audit_type) {
+                            if (!shenpi || shenpi.audit_order !== audit.audit_order || shenpi.audit_type !== audit.audit_type || shenpi.audit_ledger_id !== audit.audit_ledger_id) {
                                 sameAudit = false;
                                 break;
                             }
@@ -161,7 +167,7 @@ module.exports = options => {
                 } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
                     const shenpiInfo = yield this.service.shenpiAudit.getDataByCondition({ tid: stage.tid, sp_type: shenpiConst.sp_type.stage, sp_status: shenpi_status });
                     // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
-                    const lastAuditors = auditList.filter(x => { x.order === auditList.order; });
+                    const lastAuditors = auditList.filter(x => { x.order === auditList[auditList.length - 1].order; });
                     if (shenpiInfo && (lastAuditors.length === 0 || (lastAuditors.length > 1 || shenpiInfo.audit_id !== lastAuditors[0].aid))) {
                         yield this.service.stageAudit.updateLastAudit(stage, auditList, shenpiInfo.audit_id);
                         yield this.service.stage.loadStageUser(stage);

+ 10 - 9
app/public/js/change_information.js

@@ -424,6 +424,7 @@ $(document).ready(() => {
 
     // 清单汇总信息获取
     let hzSpread = null;
+    console.log(readOnly, shenpiPower);
     const hzSpreadSetting = {
         cols: [
             {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 80},
@@ -432,12 +433,12 @@ $(document).ready(() => {
             {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.unit_price'},
             {title: '申报变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'camount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.camount'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'ca_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.ca_tp'},
-            {title: '审批变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'amount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.amount', visible: !readOnly && shenpiPower},
-            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'a_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.a_tp', visible: !readOnly && shenpiPower},
-            {title: '审批变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'spamount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.spamount', visible: !readOnly && !shenpiPower},
-            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'spa_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.spa_tp', visible: !readOnly && !shenpiPower},
-            {title: '审批变更|数量', colSpan: '2|1', rowSpan: '1|1', hAlign: 2, width: 60, type: 'Number', getValue: '', visible: readOnly},
-            {title: '|金额', colSpan: '|1', rowSpan: '|1', hAlign: 2, width: 80, type: 'Number', getValue: '', visible: readOnly},
+            {title: '审批变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'amount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.amount', visible: readOnly && !shenpiPower},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'a_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.a_tp', visible: readOnly && !shenpiPower},
+            {title: '审批变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'spamount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.spamount', visible: readOnly && shenpiPower},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'spa_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.spa_tp', visible: readOnly && shenpiPower},
+            {title: '审批变更|数量', colSpan: '2|1', rowSpan: '1|1', hAlign: 2, width: 60, type: 'Number', getValue: '', visible: !readOnly},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', hAlign: 2, width: 80, type: 'Number', getValue: '', visible: !readOnly},
         ],
         emptyRows: 0,
         headRows: 2,
@@ -480,11 +481,11 @@ $(document).ready(() => {
         makeBackColor: function () {
             const rowCount = hzSpread.getActiveSheet().getRowCount();
             for (let i = 0; i < rowCount; i++) {
-                if (!readOnly && !shenpiPower && hzSpread.getActiveSheet().zh_data[i].camount != hzSpread.getActiveSheet().zh_data[i].spamount) {
+                if (readOnly && shenpiPower && hzSpread.getActiveSheet().zh_data[i].camount != hzSpread.getActiveSheet().zh_data[i].spamount) {
                     hzSpread.getActiveSheet().getRange(i, -1, 1, -1).backColor('#ffeeba');
-                } else if (!readOnly && shenpiPower && hzSpread.getActiveSheet().zh_data[i].camount != hzSpread.getActiveSheet().zh_data[i].amount) {
+                } else if (readOnly && !shenpiPower && hzSpread.getActiveSheet().zh_data[i].camount != hzSpread.getActiveSheet().zh_data[i].amount) {
                     hzSpread.getActiveSheet().getRange(i, -1, 1, -1).backColor('#ffeeba');
-                } else if (readOnly && !(hzSpread.getActiveSheet().zh_data[i].camount === '' || hzSpread.getActiveSheet().zh_data[i].camount === 0 || hzSpread.getActiveSheet().zh_data[i].camount === null)) {
+                } else if (!readOnly && !(hzSpread.getActiveSheet().zh_data[i].camount === '' || hzSpread.getActiveSheet().zh_data[i].camount === 0 || hzSpread.getActiveSheet().zh_data[i].camount === null)) {
                     hzSpread.getActiveSheet().getRange(i, -1, 1, -1).backColor('#ffeeba');
                 }
             }

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

@@ -252,7 +252,7 @@ $(document).ready(() => {
                                 return item.gcl_id === select.gcl_id && (item.mx_id === undefined || item.mx_id === select.mx_id);
                             });
                             const newData = {
-                                lid: select.gcl_id,
+                                lid: gclInfo.leafXmjs[0].gcl_id || select.gcl_id,
                                 code: gclInfo.b_code,
                                 name: gclInfo.name,
                                 unit: gclInfo.unit,

+ 3 - 7
app/public/js/change_information_set.js

@@ -1020,13 +1020,9 @@ $(document).ready(() => {
                         return _.findIndex(gcl.leafXmjs, { gcl_id: item.gcl_id }) !== -1;
                     });
                     if (cl.length > 0) {
-                        if (gcl.leafXmjs.length === 1 && !gcl.leafXmjs[0].bwmx) {
-                            newChangeList.push(cl[0]);
-                        } else {
-                            for (const l of gcl.leafXmjs) {
-                                const c = _.find(cl, { mx_id: l.mx_id });
-                                if (c) newChangeList.push(c);
-                            }
+                        for (const l of gcl.leafXmjs) {
+                            const c = _.find(cl, { gcl_id: l.gcl_id, mx_id: l.mx_id || '' });
+                            if (c) newChangeList.push(c);
                         }
                     }
                 } else {

+ 4 - 2
app/public/js/change_revise.js

@@ -623,10 +623,12 @@ $(document).ready(() => {
                         const gclInfo = _.find(gclGatherData, function (item) {
                             return item.leafXmjs && _.find(item.leafXmjs, {gcl_id: select.id });
                         });
-                        const xmjInfo = gclInfo.leafXmjs[0];
+                        const xmjInfo = gclInfo.leafXmjs.find(function (item) {
+                            return item.gcl_id === select.id;
+                        });
                         const oldCInfo = _.find(oldChangeList, { gcl_id: select.id, mx_id: '' });
                         const data = {
-                            lid: select.id,
+                            lid: gclInfo.leafXmjs[0].gcl_id || select.id,
                             code: gclInfo.b_code,
                             name: gclInfo.name || '',
                             unit: gclInfo.unit || '',

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

@@ -1384,7 +1384,7 @@ const createNewPathTree = function (type, setting) {
         _loadLockedNodes(ids){
             this.Locked = ids || [];
             for (const f of this.Locked) {
-                f.ass_ledger_id = f.ass_ledger_id ? f.ass_ledger_id.split(',') : [];
+                f.locked_ledger_id = f.ass_ledger_id ? f.ass_ledger_id.split(',') : (f.audit_ledger_id ? f.audit_ledger_id.split(',') : []);
                 for (const id of f.ass_ledger_id) {
                     const node = this.getItems(id);
                     node.locked = true;

+ 3 - 1
app/public/js/shares/cs_tools.js

@@ -581,6 +581,7 @@ const showSelectTab = function(select, spread, afterShow) {
             searchResult = [];
             const sortData = SpreadJsObj.getSortData(searchSheet);
             for (const node of sortData) {
+                if (node.filter) continue;
                 if ((node.code && node.code.indexOf(keyword) > -1) ||
                     (node.b_code && node.b_code.indexOf(keyword) > -1) ||
                     (node.name && node.name.indexOf(keyword) > -1) ||
@@ -956,7 +957,8 @@ const showSelectTab = function(select, spread, afterShow) {
         const getTagDisplayHtml = function (tag) {
             const tagClass = classIndexes.find(x => {return x.color === tag.color}) || {};
             const tagHtml = [];
-            tagHtml.push('<div name="tag-view">');
+            const hidden = tag.node.filter ? 'style="display: none;"' : '';
+            tagHtml.push(`<div name="tag-view" ${hidden}>`);
             tagHtml.push('<div class="card-header p-2"><div class="dropdown">');
             tagHtml.push(`<div class="pull-left mr-2"><i class="fa fa-tag ${tagClass.tagClass}"></i></div>`);
             tagHtml.push('</div>');

+ 2 - 2
app/public/js/shares/tender_select.js

@@ -162,11 +162,11 @@ const TenderSelect = function (setting) {
             };
             if (this.setting.type === 'ledger' || this.setting.type === 'revise') {
                 resultSpreadSetting.cols.push(
-                    {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 230, formatter: '@', readOnly: true}
+                    {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 230, formatter: '@', readOnly: true, cellType: 'ellipsisAutoTip'}
                 );
             } else if (this.setting.type === 'stage') {
                 resultSpreadSetting.cols.push(
-                    {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 180, formatter: '@', readOnly: true},
+                    {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 180, formatter: '@', readOnly: true, cellType: 'ellipsisAutoTip'},
                     {title: '可选期', colSpan: '1', rowSpan: '1', field: 'stage', hAlign: 0, width: 60},
                 )
             }

+ 113 - 2
app/public/js/shenpi.js

@@ -249,9 +249,9 @@ $(document).ready(function () {
         },
         getAuditTypeHtml: function(code, type) {
             const html = [];
-            const hide = ['stage', 'change'].indexOf(code) === -1 ? 'style="display: none;"' : '';
-            html.push(`<span class="d-inline-block"><select class="form-control form-control-sm audit-type-key" ${hide} data-type="${type}">`);
+            html.push(`<span class="d-inline-block"><select class="form-control form-control-sm audit-type-key" data-type="${type}">`);
             for (const t of auditType.types) {
+                if (t.valid && t.valid.indexOf(code) < 0) continue;
                 html.push(`<option value="${t.value}" ${t.value === type ? 'selected' : ''}>${t.name}</option>`);
             }
             html.push('</select></span> ');
@@ -302,6 +302,9 @@ $(document).ready(function () {
             if (type !== auditType.key.common || auditGroup.length === 0) {
                 html.push(this.getSelectAuditHtml(code));
             }
+            if (type === auditType.key.union && auditGroup.length > 0) {
+                html.push(`<button class="btn btn-sm btn-outline-primary" sp_type="${code}" audit_order="${i}" name="union-set">协同设置</button>`);
+            }
             html.push('</span>');
             return html.join('');
         },
@@ -961,6 +964,114 @@ $(document).ready(function () {
     }
     const auditAss = new AuditAss();
 
+    class AuditUnion {
+        constructor() {
+            this.spread = SpreadJsObj.createNewSpread($('#union-spread')[0]);
+            this.sheet = this.spread.getActiveSheet();
+            this.tree = createNewPathTree('base', {
+                id: 'ledger_id',
+                pid: 'ledger_pid',
+                order: 'order',
+                level: 'level',
+                rootId: -1,
+            });
+            this.selectUnion = [{ value: 0, text: '' }];
+            this.spreadSetting = {
+                cols: [
+                    {title: '编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 165, formatter: '@', cellType: 'tree', readOnly: true },
+                    {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 185, formatter: '@', readOnly: true },
+                    {title: '协同人', colSpan: '1', rowSpan: '1', field: 'audit_id', hAlign: 1, width: 100, cellType: 'customizeCombo', comboItems: this.selectUnion },
+                ],
+                emptyRows: 0,
+                headRows: 1,
+                headRowHeight: [32],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+            };
+            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.EditEnded, function(e, info) {
+                const node = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                node[col.field] = info.editingText;
+            });
+
+            $('#union').on('shown.bs.modal', function() {
+               self.spread.refresh();
+            });
+            $('#union-ok').click(function() {
+                const data = self.getUnionAuditLedgerData();
+                postData(`/tender/${cur_tenderid}/shenpi/union/save`, data, function(result) {
+                    $('#union').modal('hide');
+                });
+            });
+        }
+        _refreshUnionTree() {
+            const ledgerAss = {};
+            for (const a of this.auditors) {
+                a.lid.forEach(l => { ledgerAss[l] = a; });
+            }
+            for (const n of this.tree.nodes) {
+                const la = ledgerAss[n.ledger_id];
+                n.audit_id = la ? la.audit_id : 0;
+                n.audit_name = la ? la.name : '';
+            }
+            SpreadJsObj.reloadColData(this.sheet, 2);
+        }
+        setUnionAuditors(auditors) {
+            this.auditors = auditors;
+            this.selectUnion.length = 1;
+            const html = [];
+            for (const auditor of auditors) {
+                auditor.lid = auditor.audit_ledger_id ? auditor.audit_ledger_id.split(',') : [];
+                html.push(`<tr><td>${auditor.name}</td><td>${auditor.company}</td><td>${auditor.lid.length}<button class="ml-2 btn btn-sm btn-outline-danger" jaid="${auditor.audit_id}">清空</button></td></tr>`);
+                this.selectUnion.push({ value: auditor.audit_id, text: auditor.name });
+            }
+            $('#union_table').html(html.join(''));
+            this._refreshUnionTree();
+        }
+        loadUnionData(sp_type, audit_order) {
+            const data = { sp_type, audit_order };
+            if (!this.loaded) data.ledger = 1;
+
+            const self = this;
+            postData(`/tender/${cur_tenderid}/shenpi/union/load`, data, function(result) {
+                self.loaded = true;
+                if (result.ledgerList) {
+                    self.tree.loadDatas(result.ledgerList);
+                    SpreadJsObj.loadSheetData(self.sheet, SpreadJsObj.DataType.Tree, self.tree);
+                }
+                self.setUnionAuditors(result.unionAuditors);
+                if (self.auditors.length > 0) {
+                    $('#union').modal('show');
+                } else {
+                    toastr.warning(`${audit_order}审未添加任何协同人,请先添加协同人再分配协同台账`);
+                }
+            });
+        }
+        getUnionAuditLedgerData() {
+            this.auditors.forEach(a => { a.lid = []; });
+            for (const node of this.tree.nodes) {
+                if (node.audit_id > 0) {
+                    const relaAudit = this.auditors.find(x => { return x.audit_id === node.audit_id; });
+                    if (relaAudit) relaAudit.lid.push(node.ledger_id);
+                }
+            }
+            return this.auditors.map(a => {
+                return { id: a.id, audit_ledger_id: a.lid.join(',') };
+            });
+        }
+    }
+    const auditUnion = new AuditUnion();
+    $('body').on('click', '[name=union-set]', function() {
+        auditUnion.loadUnionData(this.getAttribute('sp_type'), this.getAttribute('audit_order'));
+    });
+
     $.subMenu({
         menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
         toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',

+ 21 - 6
app/public/js/stage.js

@@ -2385,7 +2385,11 @@ $(document).ready(() => {
     postData(window.location.pathname + '/load', { filter: 'ledger;pos;detail;stageChange;import_change;tag' }, function (result) {
         // 加载树结构
         stageTree.loadDatas(result.ledgerData, result.locked);
-        if (stage.assist) stageTree.loadFilter(stage.assist.ass_ledger_id);
+        if (stage.assist) {
+            stageTree.loadFilter(stage.assist.ass_ledger_id);
+        } else if (stage.relaAuditor && stage.relaAuditor.audit_ledger_id) {
+            stageTree.loadFilter(stage.relaAuditor.audit_ledger_id);
+        }
         // 加载部位明细
         stagePos.loadDatas(result.posData);
         checkShowLast(result.ledgerData.length);
@@ -2413,6 +2417,11 @@ $(document).ready(() => {
         // 加载中间计量
         stageIm.init(tenderInfo, stage, imType, tenderInfo.decimal, stage.assist ? stage.assist.ass_ledger_id : '');
         stageIm.loadData(result.ledgerData, result.posData, result.detailData, result.stageChange, result.import_change, result.detailAtt);
+        if (stage.assist) {
+            stageIm.loadFilter(stage.assist.ass_ledger_id);
+        } else if (stage.relaAuditor) {
+            stageIm.loadFilter(stage.relaAuditor.audit_ledger_id);
+        }
 
         errorList.loadHisErrorData();
         checkList.loadHisCheckData();
@@ -4155,13 +4164,16 @@ $(document).ready(() => {
                             const changeBills = SpreadJsObj.getSelectObject(self.changeBillsSheet);
                             if (change.is_import) {
                                 if (changeBills.pos && changeBills.pos.length > 0) {
+                                    const node = stageTree.getItems(changeBills.pos[0].ledger_id);
+                                    if (!node || node.filter) return;
+
                                     SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), changeBills.pos[0].ledger_id, true);
                                     SpreadJsObj.resetTopAndSelect(spSpread.getActiveSheet());
                                     stagePosSpreadObj.loadCurPosData();
                                 }
                             } else if (changeBills.gcl_id) {
                                 const node = stageTree.nodes.find(x => {return x.id === changeBills.gcl_id});
-                                if (!node) return;
+                                if (!node || node.filter) return;
 
                                 SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), node.ledger_id, true);
                                 SpreadJsObj.resetTopAndSelect(spSpread.getActiveSheet());
@@ -4180,6 +4192,7 @@ $(document).ready(() => {
                                 };
                                 for (const node of stageTree.nodes) {
                                     if (node.children && node.children.length > 0) continue;
+                                    if (node.filter) continue;
 
                                     const b = {
                                         b_code: node.b_code || '',
@@ -4234,7 +4247,7 @@ $(document).ready(() => {
                             return !changeBills || curChange.is_import;
                         },
                         visible: function (key, opt) {
-                            return is_debug && stage.status === 1;
+                            return stage.status === 1 && stage.isCheckFirst && !stage.assist;
                         }
                     },
                 }
@@ -4284,7 +4297,7 @@ $(document).ready(() => {
                             return !curChange || !changeBills || curChange.is_import;
                         },
                         visible: function (key, opt) {
-                            return stage.status === 1 && stage.isCheckFirst;
+                            return stage.status === 1 && stage.isCheckFirst && !stage.assist;
                         }
                     },
                     'autoUseAll': {
@@ -4300,7 +4313,7 @@ $(document).ready(() => {
                             return !curChange || curChange.is_import;
                         },
                         visible: function (key, opt) {
-                            return stage.status === 1 && stage.isCheckFirst;
+                            return stage.status === 1 && stage.isCheckFirst && !stage.assist;
                         }
                     }
                 }
@@ -4398,6 +4411,7 @@ $(document).ready(() => {
                 return null;
             } else if (changeBills.gcl_id) {
                 const node = stageTree.nodes.find(x => {return x.id === changeBills.gcl_id});
+                if (node.filter) return null;
                 if (node.settle_status === settleStatus.finish) return null;
 
                 posData = stagePos.getLedgerPos(node.id) || [];
@@ -4421,7 +4435,8 @@ $(document).ready(() => {
                 };
                 for (const node of stageTree.nodes) {
                     if (node.children && node.children.length > 0) continue;
-                    if (node.settle_status === settleStatus.finish) return null;
+                    if (node.filter) continue;
+                    if (node.settle_status === settleStatus.finish) continue;
 
                     const b = {
                         b_code: node.b_code || '',

+ 11 - 0
app/public/js/stage_im.js

@@ -92,6 +92,10 @@ const stageIm = (function () {
         detailsAtt = stageDetailAtt;
     }
 
+    function loadFilter(filter) {
+        gsTree.loadFilter(filter);
+    }
+
     function loadData4Rela(ledger, pos, stageDetail, stageChange, stageDetailAtt) {
         up_field = 'org_unit_price';
         gsTree.loadDatas(ledger);
@@ -607,6 +611,7 @@ const stageIm = (function () {
                 custom_define: [],
                 source: [{id: node.ledger_id, code: node.code, b_code: node.b_code}],
                 im_type: imType.tz.value,
+                visible: !node.filter,
             };
             if (stage.im_gather && node.check) {
                 im.bw = getZlGatherBw(node, peg);
@@ -673,6 +678,7 @@ const stageIm = (function () {
                 custom_define: [],
                 source: [{id: node.ledger_id, code: node.code, b_code: node.b_code}],
                 im_type: imType.bb.value,
+                visible: !node.filter,
             };
             for (const p of posterity) {
                 if (p.children && p.children.length > 0) continue;
@@ -697,6 +703,7 @@ const stageIm = (function () {
                                 custom_define: [],
                                 source: [],
                                 im_type: imType.bb.value,
+                                visible: !node.filter,
                             };
                             nodeImData.push(im);
                         }
@@ -840,6 +847,7 @@ const stageIm = (function () {
                     custom_define: [],
                     source: [],
                     im_type: imType.zl.value,
+                    visible: !node.filter,
                 };
                 if (stage.im_gather && node.check) {
                     im.bw = getZlGatherBw(node, peg);
@@ -891,6 +899,7 @@ const stageIm = (function () {
                         custom_define: [],
                         source: [{id: p.ledger_id, code: p.code, b_code: p.b_code, pos_id: pp.id, pos_name: pp.name}],
                         im_type: imType.bw.value,
+                        visible: !node.filter,
                     };
                     im.calc_memo = '本期计量:' + (checkZero(im.jl) ? 0 : im.jl) + (im.qc_minus_jl ? (` (不计价 ${im.qc_minus_jl}) `) : ' ') + im.unit;
                     ImData.push(im);
@@ -917,6 +926,7 @@ const stageIm = (function () {
                     custom_define: [],
                     source: [{id: p.ledger_id, code: p.code, b_code: p.b_code}],
                     im_type: imType.bw.value,
+                    visible: !node.filter,
                 };
                 im.calc_memo = '本期计量:' + (checkZero(im.jl) ? 0 : im.jl) + (im.qc_minus_jl ? (` (不计价 ${im.qc_minus_jl}) `) : ' ') + im.unit;
                 ImData.push(im);
@@ -1223,6 +1233,7 @@ const stageIm = (function () {
         init,
         initCheck,
         loadData,
+        loadFilter,
         loadData4Rela,
         buildImData,
         loadUpdateDetailData,

+ 1 - 1
app/public/report/js/rpt_custom.js

@@ -29,7 +29,7 @@ const rptCustomObj = (function () {
     const sStageSelect = 'stage_select';
     const grSpreadSetting = {
         baseCols: [
-            {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 240, formatter: '@', readOnly: true},
+            {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 240, formatter: '@', readOnly: true, cellType: 'ellipsisAutoTip'},
         ],
         extraCols: [
             {title: '%s', colSpan: '1', rowSpan: '1', field: '%s', hAlign: 1, vAlign: '1', width: 60, cellType: 'checkbox', readOnly: true},

+ 2 - 0
app/router.js

@@ -189,6 +189,8 @@ module.exports = app => {
     app.post('/tender/:id/shenpi/audit/save', sessionAuth, tenderCheck, 'tenderController.saveShenpiAudit');
     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/shenpi/union/load', sessionAuth, tenderCheck, 'tenderController.loadUnionAudit');
+    app.post('/tender/:id/shenpi/union/save', sessionAuth, tenderCheck, 'tenderController.saveUnionAudit');
     app.post('/tender/:id/copy-setting', sessionAuth, tenderCheck, 'tenderController.copyTender');
     app.post('/tender/:id/tourist/audit/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.saveTourist');
     app.post('/tender/:id/map/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.saveMap');

+ 1 - 1
app/service/change.js

@@ -1317,7 +1317,7 @@ module.exports = app => {
                 const updateList = [];
                 for (const cl of changeList) {
                     const audit_amount = cl.audit_amount.split(',');
-                    const last_amount = audit_amount.length - 1 !== 0 && audit_amount[audit_amount.length - 1] ? audit_amount[audit_amount.length - 1] : cl.camount;
+                    const last_amount = audit_amount.length - 1 !== 0 && audit_amount[audit_amount.length - 1] ? audit_amount[audit_amount.length - 1] : (cl.camount ? cl.camount : 0);
                     audit_amount.splice(-1, 1);
                     const list_update = {
                         id: cl.id,

+ 2 - 2
app/service/change_audit_list.js

@@ -308,7 +308,7 @@ module.exports = app => {
          * @param {int} order 期数
          * @return {void}
          */
-        async save(data, order) {
+        async save(data) {
             if (!this.ctx.tender || !this.ctx.change) {
                 throw '数据错误';
             }
@@ -481,7 +481,7 @@ module.exports = app => {
             // const sqlParam = [this.tableName, this.change.cid];
             // const tp = await transaction.queryOne(sql, sqlParam);
             // 防止小数位不精确,采用取值计算
-            const sql = 'SELECT unit_price, spamount FROM ?? WHERE cid = ?';
+            const sql = 'SELECT unit_price, spamount, is_valuation FROM ?? WHERE cid = ?';
             const sqlParam = [this.tableName, this.ctx.change.cid];
             const changeList = await transaction.query(sql, sqlParam);
             let total_price = 0;

+ 424 - 206
app/service/ledger_audit.js

@@ -9,6 +9,7 @@
  */
 
 const auditConst = require('../const/audit').ledger;
+const auditType = require('../const/audit').auditType;
 const smsTypeConst = require('../const/sms_type');
 const SMS = require('../lib/sms');
 const SmsAliConst = require('../const/sms_alitemplate');
@@ -30,6 +31,7 @@ module.exports = app => {
             this.tableName = 'ledger_audit';
         }
 
+
         /**
          * 获取标段审核人信息
          *
@@ -39,7 +41,7 @@ module.exports = app => {
          * @return {Promise<*>}
          */
         async getAuditor(tenderId, auditorId, times = 1) {
-            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`audit_type`, la.`audit_ledger_id`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
                 '  FROM ?? AS la Left Join ?? AS pa ON la.`audit_id` = pa.`id`' +
                 '  WHERE la.`tender_id` = ? and la.`audit_id` = ? and la.`times` = ?';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, auditorId, times];
@@ -47,17 +49,29 @@ module.exports = app => {
         }
 
         async getAuditorByOrder(tenderId, order, times = 1) {
-            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`audit_type`, la.`audit_ledger_id`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
                 '  FROM ?? AS la Left Join ?? AS pa ON la.`audit_id` = pa.`id`' +
                 '  WHERE la.`tender_id` = ? and la.`audit_order` = ? and la.`times` = ?';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, order, times];
             return await this.db.queryOne(sql, sqlParam);
         }
 
+        async getAuditorsByOrder(tenderId, order, times) {
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`,' +
+                '    la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, la.audit_type, la.audit_ledger_id ' +
+                '    FROM ' + this.tableName + ' AS la' +
+                '    Left Join ' + this.ctx.service.projectAccount.tableName + ' AS pa ON la.`audit_id` = pa.`id`' +
+                '  WHERE la.`tender_id` = ? and la.`audit_order` = ? and la.`times` = ?' +
+                '  ORDER BY `audit_order` DESC';
+            const sqlParam = [tenderId, order, times ? times: 1];
+            return await this.db.query(sql, sqlParam);
+        }
+
         async getLastestAuditor(tenderId, times, status) {
             const sql =
                 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`,' +
-                '    la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+                '    la.`times`, la.`audit_order`, la.`audit_type`, la.`audit_ledger_id`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
                 '    FROM ' + this.tableName + ' AS la' +
                 '    Left Join ' + this.ctx.service.projectAccount.tableName + ' AS pa ON la.`audit_id` = pa.`id`' +
                 '  WHERE la.`tender_id` = ? and la.`status` = ? and la.`times` = ?' +
@@ -66,44 +80,239 @@ module.exports = app => {
             return await this.db.queryOne(sql, sqlParam);
         }
 
+        async getLastestAuditors(tenderId, times, status) {
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.audit_type, la.audit_order, la.audit_ledger_id,' +
+                '    la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+                '    FROM ' + this.tableName + ' AS la' +
+                '    Left Join ' + this.ctx.service.projectAccount.tableName + ' AS pa ON la.`audit_id` = pa.`id`' +
+                '  WHERE la.`tender_id` = ? and la.`status` = ? and la.`times` = ?' +
+                '  ORDER BY `audit_order` DESC';
+            const sqlParam = [tenderId, status, times ? times: 1];
+            const result = await this.db.query(sql, sqlParam);
+            if (result.length === 0) return [];
+            return result.filter(x => { return x.audit_order === result[0].audit_order });
+        }
+
         /**
-         * 获取标段审核人最后一位的名称
+         * 获取标段审核列表信息
          *
          * @param {Number} tenderId - 标段id
-         * @param {Number} auditorId - 审核人id
          * @param {Number} times - 第几次审批
          * @return {Promise<*>}
          */
-        async getStatusName(tenderId, times) {
-            const sql = 'SELECT pa.`name` FROM ?? AS la Left Join ?? AS pa ON la.`audit_id` = pa.`id`' +
-                '  WHERE la.`tender_id` = ? and la.`status` != ? ORDER BY la.`times` DESC, la.`audit_order` DESC';
-            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, auditConst.status.uncheck];
-            return await this.db.queryOne(sql, sqlParam);
+        async getAuditors(tenderId, times = 1, order_sort = 'asc') {
+            const sql = 'SELECT la.id, la.audit_id, la.times, la.audit_order, la.audit_type, la.audit_ledger_id, la.status, la.opinion, la.begin_time, la.end_time, ' +
+                '    pa.name, pa.company, pa.role, pa.mobile, pa.telephone, pa.sign_path' +
+                `  FROM ${this.tableName} la LEFT JOIN ${this.ctx.service.projectAccount.tableName} pa ON la.audit_id = pa.id` +
+                '  WHERE la.tender_id = ? AND la.times = ?' +
+                '  ORDER BY la.audit_order ' + order_sort;
+            const sqlParam = [tenderId, times];
+            const result = await this.db.query(sql, sqlParam);
+            const max_sort = this._.max(result.map(x => { return x.audit_order; }));
+            for (const i in result) {
+                result[i].max_sort = max_sort;
+            }
+            return result;
         }
 
         /**
-         * 获取标段审核列表信息
+         * 获取标段当前审核人
          *
          * @param {Number} tenderId - 标段id
          * @param {Number} times - 第几次审批
          * @return {Promise<*>}
          */
-        async getAuditors(tenderId, times = 1) {
+        async getCurAuditor(tenderId, times = 1) {
             const sql =
-                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
-                'FROM ?? AS la, ?? AS pa, (SELECT t1.`audit_id`,(@i:=@i+1) as `sort` FROM (SELECT t.`audit_id`, t.`audit_order` FROM (select `audit_id`, `audit_order` from ?? WHERE `tender_id` = ? AND `times` = ? ORDER BY `audit_order` LIMIT 200) t GROUP BY t.`audit_id` ORDER BY t.`audit_order`) t1, (select @i:=0) as it) as g ' +
-                'WHERE la.`tender_id` = ? and la.`times` = ? and la.`audit_id` = pa.`id` and g.`audit_id` = la.`audit_id` order by la.`audit_order`';
-            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, tenderId, times, tenderId, times];
-            const result = await this.db.query(sql, sqlParam);
-            const sql2 = 'SELECT COUNT(a.`audit_id`) as num FROM (SELECT `audit_id` FROM ?? WHERE `tender_id` = ? AND `times` = ? GROUP BY `audit_id`) as a';
-            const sqlParam2 = [this.tableName, tenderId, times];
-            const count = await this.db.queryOne(sql2, sqlParam2);
-            for (const i in result) {
-                result[i].max_sort = count.num;
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`audit_type`, la.`audit_ledger_id`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+                '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id`' +
+                '  WHERE la.`tender_id` = ? and la.`status` = ? and la.`times` = ?';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, auditConst.status.checking, times];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        async getCurAuditors(tenderId, times = 1) {
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, la.audit_type, la.audit_order, la.audit_ledger_id ' +
+                '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id`' +
+                '  WHERE la.`tender_id` = ? and la.`status` = ? and la.`times` = ?';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, auditConst.status.checking, times];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        async getAuditorGroup(tenderId, times) {
+            const auditors = await this.getAuditors(tenderId, times); // 全部参与的审批人
+            return this.ctx.helper.groupAuditors(auditors, 'audit_order');
+        }
+
+        async getUserGroup(tenderId, times) {
+            const group = await this.getAuditorGroup(tenderId, times);
+            const sql =
+                'SELECT pa.`id` As audit_id, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As tender_id, 0 As audit_order, 1 As audit_type' +
+                '  FROM ' + this.ctx.service.tender.tableName + ' As t' +
+                '  LEFT JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa' +
+                '  ON t.user_id = pa.id' +
+                '  WHERE t.id = ?';
+            const sqlParam = [times, tenderId, tenderId];
+            const user = await this.db.queryOne(sql, sqlParam);
+            group.unshift([ user ]);
+            return group;
+        }
+
+        async getUniqUserGroup(tenderId, times) {
+            const group = await this.getAuditorGroup(tenderId, times);
+            const sql =
+                'SELECT pa.`id` As audit_id, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As tender_id, 0 As audit_order, 1 As audit_type' +
+                '  FROM ' + this.ctx.service.tender.tableName + ' As t' +
+                '  LEFT JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa' +
+                '  ON t.user_id = pa.id' +
+                '  WHERE t.id = ?';
+            const sqlParam = [times, tenderId, tenderId];
+            const user = await this.db.queryOne(sql, sqlParam);
+            group.unshift([ user ]);
+            return this.ctx.helper.groupAuditorsUniq(group, 'audit_order');
+        }
+
+        async getAuditorHistory(tenderId, times, reverse = false) {
+            const history = [];
+            if (times >= 1) {
+                for (let i = 1; i <= times; i++) {
+                    const auditors = await this.getAuditors(tenderId, i);
+                    const group = this.ctx.helper.groupAuditors(auditors, 'audit_order');
+                    const historyGroup = [];
+                    const max_order = group.length > 0 && group[group.length - 1].length > 0 ? group[group.length - 1][0].audit_order : -1;
+                    for (const g of group) {
+                        const his = {
+                            beginYear: '', beginDate: '', beginTime: '', endYear: '', endDate: '', endTime: '', begin_time: null, end_time: null,
+                            audit_type: g[0].audit_type, audit_order: g[0].audit_order,
+                            auditors: g
+                        };
+                        if (his.audit_type === auditType.key.common) {
+                            his.name = g[0].name;
+                        } else {
+                            his.name = this.ctx.helper.transFormToChinese(his.audit_order) + '审';
+                        }
+                        his.is_final = his.audit_order === max_order;
+                        if (g[0].begin_time) {
+                            his.begin_time = g[0].begin_time;
+                            const beginTime = this.ctx.moment(g[0].begin_time);
+                            his.beginYear = beginTime.format('YYYY');
+                            his.beginDate = beginTime.format('MM-DD');
+                            his.beginTime = beginTime.format('HH:mm:ss');
+                        }
+                        let end_time;
+                        g.forEach(x => {
+                            if (x.status === auditConst.status.checkSkip) return;
+                            if (!his.status || x.status === auditConst.status.checking) his.status = x.status;
+                            if (x.end_time && (!end_time || x.end_time > end_time)) {
+                                end_time = x.end_time;
+                                if (his.status !== auditConst.status.checking) his.status = x.status;
+                            }
+                        });
+                        if (end_time) {
+                            his.end_time = end_time;
+                            const endTime = this.ctx.moment(end_time);
+                            his.endYear = endTime.format('YYYY');
+                            his.endDate = endTime.format('MM-DD');
+                            his.endTime = endTime.format('HH:mm:ss');
+                        }
+                        historyGroup.push(his);
+                    }
+                    if (reverse) {
+                        history.push(historyGroup.reverse());
+                    } else {
+                        history.push(historyGroup);
+                    }
+                }
             }
+            return history;
+        }
+
+        async getUniqAuditor(tenderId, times) {
+            const auditors = await this.getAuditors(tenderId, times); // 全部参与的审批人
+            const result = [];
+            auditors.forEach(x => {
+                if (result.findIndex(r => { return x.audit_id === r.audit_id && x.audit_order === r.audit_order; }) < 0) {
+                    result.push(x);
+                }
+            });
             return result;
         }
 
+        async loadLedgerUser(tender) {
+            const status = auditConst.status;
+            const accountId = this.ctx.session.sessionUser.accountId;
+
+            tender.user = await this.ctx.service.projectAccount.getAccountInfoById(tender.user_id);
+            tender.auditors = await this.getAuditors(tender.id, tender.ledger_times); // 全部参与的审批人
+            tender.auditorIds = this._.map(tender.auditors, 'audit_id');
+            tender.curAuditors = tender.auditors.filter(x => { return x.status === status.checking; }); // 当前流程中审批中的审批人
+            tender.curAuditorIds = this._.map(tender.curAuditors, 'audit_id');
+            tender.flowAuditors = tender.curAuditors.length > 0 ? tender.auditors.filter(x => { return x.audit_order === tender.curAuditors[0].audit_order; }) : []; // 当前流程中参与的审批人(包含会签时,审批通过的人)
+            tender.flowAuditorIds = this._.map(tender.flowAuditors, 'audit_id');
+            tender.nextAuditors = tender.curAuditors.length > 0 ? tender.auditors.filter(x => { return x.audit_order === tender.curAuditors[0].audit_order + 1; }) : [];
+            tender.nextAuditorIds = this._.map(tender.nextAuditors, 'audit_id');
+            tender.auditorGroups = this.ctx.helper.groupAuditors(tender.auditors, 'audit_order');
+            tender.userGroups = this.ctx.helper.groupAuditorsUniq(tender.auditorGroups);
+            tender.userGroups.unshift([{
+                aid: tender.user.id, order: 0, times: tender.ledger_times, audit_order: 0, audit_type: auditType.key.common,
+                name: tender.user.name, role: tender.user.role, company: tender.user.company
+            }]);
+            tender.finalAuditorIds = tender.userGroups[tender.userGroups.length - 1].map(x => { return x.aid; });
+            tender.relaAuditor = tender.auditors.find(x => { return x.aid === accountId });
+
+            tender.assists = [];// await this.service.ledgerAuditAss.getData(tender); // 全部协同人
+            tender.assists = tender.assists.filter(x => {
+                return x.user_id === tender.user_id || tender.auditorIds.indexOf(x.user_id) >= 0;
+            }); // 过滤无效协同人
+            tender.userAssists = tender.assists.filter(x => { return x.user_id === tender.user_id; }); // 原报协同人
+            tender.userAssistIds = this._.map(tender.userAssists, 'ass_user_id');
+            tender.auditAssists = tender.assists.filter(x => { return x.user_id !== tender.user_id; }); // 审批协同人
+            tender.auditAssistIds = this._.map(tender.auditAssists, 'ass_user_id');
+            tender.relaAssists = tender.assists.filter(x => { return x.user_id === accountId }); // 登录人的协同人
+            tender.userIds = tender.ledger_status === status.uncheck // 当前流程下全部参与人id
+                ? [tender.user_id]
+                : [tender.user_id, ...tender.userAssistIds, ...tender.auditorIds, ...tender.auditAssistIds];
+        }
+
+        async loadLedgerAuditViewData(tender) {
+            const times = tender.ledger_status === auditConst.status.checkNo ? tender.ledger_times - 1 : tender.ledger_times;
+
+            if (!tender.user) tender.user = await this.ctx.service.projectAccount.getAccountInfoById(tender.user_id);
+            tender.auditHistory = await this.getAuditorHistory(tender.id, times);
+            // 获取审批流程中左边列表
+            if (tender.status === auditConst.status.checkNo && tender.user_id !== this.ctx.session.sessionUser.accountId) {
+                const auditors = await this.getAuditors(tender.id, times); // 全部参与的审批人
+                const auditorGroups = this.ctx.helper.groupAuditors(auditors, 'audit_order');
+                tender.auditors2 = this.ctx.helper.groupAuditorsUniq(auditorGroups, 'audit_order');
+                tender.auditors2.unshift([{
+                    aid: tender.user.id, order: 0, times: tender.ledger_times - 1, audit_order: 0, audit_type: auditType.key.common,
+                    name: tender.user.name, role: tender.user.role, company: tender.user.company
+                }]);
+            } else {
+                tender.auditors2 = tender.userGroups;
+            }
+            if (tender.ledger_status === auditConst.status.uncheck || tender.ledger_status === auditConst.status.checkNo) {
+                tender.auditorList = await this.getAuditors(tender.id, tender.ledger_times);
+            }
+        }
+
+        /**
+         * 获取标段审核人最后一位的名称
+         *
+         * @param {Number} tenderId - 标段id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<*>}
+         */
+        async getStatusName(tenderId, times) {
+            const sql = 'SELECT pa.`name` FROM ?? AS la Left Join ?? AS pa ON la.`audit_id` = pa.`id`' +
+                '  WHERE la.`tender_id` = ? and la.`status` != ? ORDER BY la.`times` DESC, la.`audit_order` DESC';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, auditConst.status.uncheck];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
         async getFinalAuditGroup(tenderId, times = 1) {
             const sql =
                 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, pa.`sign_path`, la.`times`, la.`tender_id`, Max(la.`audit_order`) as max_order ' +
@@ -122,22 +331,6 @@ module.exports = app => {
         }
 
         /**
-         * 获取标段当前审核人
-         *
-         * @param {Number} tenderId - 标段id
-         * @param {Number} times - 第几次审批
-         * @return {Promise<*>}
-         */
-        async getCurAuditor(tenderId, times = 1) {
-            const sql =
-                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
-                '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id`' +
-                '  WHERE la.`tender_id` = ? and la.`status` = ? and la.`times` = ?';
-            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, auditConst.status.checking, times];
-            return await this.db.queryOne(sql, sqlParam);
-        }
-
-        /**
          * 获取最新审核顺序
          *
          * @param {Number} tenderId - 标段id
@@ -250,8 +443,8 @@ module.exports = app => {
          * @return {Promise<boolean>}
          */
         async start(tenderId, times = 1) {
-            const audit = await this.getDataByCondition({ tender_id: tenderId, times, audit_order: 1 });
-            if (!audit) {
+            const audits = await this.getAllDataByCondition({ where: { tender_id: tenderId, times, audit_order: 1 } });
+            if (audits.length === 0) {
                 if(this.ctx.tender.info.shenpi.ledger === shenpiConst.sp_status.gdspl) {
                     throw '请联系管理员添加审批人';
                 } else {
@@ -264,11 +457,9 @@ module.exports = app => {
 
             const transaction = await this.db.beginTransaction();
             try {
-                await transaction.update(this.tableName, {
-                    id: audit.id,
-                    status: auditConst.status.checking,
-                    begin_time: new Date(),
-                });
+                const begin_time = new Date();
+                const updateAuditData = audits.map(a => { return { id: a.id, status: auditConst.status.checking, begin_time }; });
+                await transaction.updateRows(this.tableName, updateAuditData);
                 await transaction.update(this.ctx.service.tender.tableName, {
                     id: tenderId,
                     ledger_status: auditConst.status.checking,
@@ -276,27 +467,30 @@ module.exports = app => {
                     deal_tp: sum.deal_tp,
                     his_id: his_id,
                 });
-                await this.ctx.service.tenderCache.updateLedgerCache4Start(transaction, tenderId, auditConst.status.checking, audit, sum);
+                await this.ctx.service.tenderCache.updateLedgerCache4Start(transaction, tenderId, auditConst.status.checking, audits, sum);
 
                 // 添加短信通知-需要审批提醒功能
-                await this.ctx.helper.sendAliSms(audit.audit_id, smsTypeConst.const.TZ, smsTypeConst.judge.approval.toString(), SmsAliConst.template.ledger_check);
+                const auditorIds = audits.map(x => { return x.audit_id; });
+                await this.ctx.helper.sendAliSms(auditorIds, smsTypeConst.const.TZ, smsTypeConst.judge.approval.toString(), SmsAliConst.template.ledger_check);
                 // 微信模板通知
                 const wechatData = {
                     status: wxConst.status.check,
                     tips: wxConst.tips.check,
                     begin_time: Date.parse(new Date()),
                 };
-                await this.ctx.helper.sendWechat(audit.audit_id, smsTypeConst.const.TZ, smsTypeConst.judge.approval.toString(), wxConst.template.ledger, wechatData);
-                await this.ctx.service.noticeAgain.addNoticeAgain(transaction, smsTypeConst.const.TZ, {
-                    pid: this.ctx.session.sessionProject.id,
-                    tid: tenderId,
-                    uid: audit.audit_id,
-                    sp_type: 'ledger',
-                    sp_id: audit.id,
-                    table_name: this.tableName,
-                    template: wxConst.template.ledger,
-                    wx_data: wechatData,
-                });
+                await this.ctx.helper.sendWechat(auditorIds, smsTypeConst.const.TZ, smsTypeConst.judge.approval.toString(), wxConst.template.ledger, wechatData);
+                for (const audit of audits) {
+                    await this.ctx.service.noticeAgain.addNoticeAgain(transaction, smsTypeConst.const.TZ, {
+                        pid: this.ctx.session.sessionProject.id,
+                        tid: tenderId,
+                        uid: audit.audit_id,
+                        sp_type: 'ledger',
+                        sp_id: audit.id,
+                        table_name: this.tableName,
+                        template: wxConst.template.ledger,
+                        wx_data: wechatData,
+                    });
+                }
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
@@ -305,105 +499,156 @@ module.exports = app => {
             return true;
         }
 
-        /**
-         * 审批
-         * @param {Number} tenderId - 标段id
-         * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
-         * @param {String} opinion 审批意见
-         * @param {Number} times - 第几次审批
-         * @return {Promise<void>}
-         */
-        async check(tenderId, checkType, opinion, times = 1) {
-            if (checkType !== auditConst.status.checked && checkType !== auditConst.status.checkNo) {
-                throw '提交数据错误';
-            }
+        async _checkNo(tenderId, opinion, times) {
+            const accountId = this.ctx.session.sessionUser.accountId;
             const pid = this.ctx.session.sessionProject.id;
+            const auditors = await this.getAllDataByCondition({ where: { tender_id: tenderId, times, status: auditConst.status.checking } });
+            if (auditors.length === 0) throw '审核数据错误';
+            const selfAuditor = auditors.find(x => { return x.audit_id === accountId; });
+            if (!selfAuditor) throw '当前标段您无权审批';
+
+            const beginAuditors = await this.getAllDataByCondition({ where: { tender_id: tenderId, audit_order: 1, times } });
+            const time = new Date();
+
+            const noticeContent = await this.getNoticeContent(selfAuditor.tender_id, pid, accountId, opinion);
+            const defaultNoticeRecord = { pid, type: pushType.ledger, status: auditConst.status.checkNo, content: noticeContent };
+
             const transaction = await this.db.beginTransaction();
             try {
-                // 整理当前流程审核人状态更新
-                const time = new Date();
-                const audit = await this.getDataByCondition({
-                    tender_id: tenderId,
-                    times,
-                    status: auditConst.status.checking,
+                // 获取审核人列表,添加提醒
+                const records = [{ uid: this.ctx.tender.data.user_id, ...defaultNoticeRecord } ];
+                const auditList = await this.getAuditors(tenderId, times);
+                auditList.forEach(audit => {
+                    records.push({ uid: audit.audit_id, ...defaultNoticeRecord});
                 });
-                if (!audit) {
-                    throw '审核数据错误';
-                }
+                await transaction.insert('zh_notice', records);
+                const users = this._.uniq(this._.concat(this._.map(auditList, 'audit_id'), this.ctx.tender.data.user_id));
+
+                // 更新当前审核流程
+                const updateAuditData = auditors.map(x => {
+                    return {
+                        id: x.id,
+                        status: x.audit_id === selfAuditor.audit_id ? auditConst.status.checkNo : auditConst.status.checkSkip,
+                        opinion: x.audit_id === selfAuditor.aid ? opinion : '',
+                        end_time: x.audit_id === selfAuditor.aid ? time : null,
+                    };
+                });
+                await transaction.updateRows(this.tableName, updateAuditData);
+                // 审批提醒
+                await this.ctx.service.noticeAgain.stopNoticeAgain(transaction, this.tableName, this._.map(auditors, 'id'));
+
+                // 同步标段信息
+                await transaction.update(this.ctx.service.tender.tableName, {
+                    id: tenderId, ledger_times: times + 1, ledger_status: auditConst.status.checkNo,
+                });
+                await this.ctx.service.tenderCache.updateLedgerCache(transaction, tenderId, auditConst.status.checkNo, auditConst.status.checkNo, [{ audit_id: this.ctx.tender.data.user_id }]);
+                // 拷贝新一次审核流程列表
+                const orgAuditors = await this.getAllDataByCondition({
+                    where: { tender_id: tenderId, times },
+                    columns: ['tender_id', 'audit_order', 'audit_id', 'audit_type', 'audit_ledger_id'],
+                });
+                const insertAuditors = orgAuditors.map(x => {
+                    return { ...x, times: times + 1, status: auditConst.status.uncheck };
+                });
+                await transaction.insert(this.tableName, insertAuditors);
+
+                await this.ctx.helper.sendAliSms(users, smsTypeConst.const.TZ, smsTypeConst.judge.result.toString(), SmsAliConst.template.ledger_result, {
+                    status: SmsAliConst.status.back,
+                });
+                // 微信模板通知
+                const wechatData = {
+                    status: wxConst.status.back,
+                    tips: wxConst.tips.back,
+                    begin_time: Date.parse(beginAuditors[0].begin_time),
+                };
+                await this.ctx.helper.sendWechat(users, smsTypeConst.const.TZ, smsTypeConst.judge.result.toString(), wxConst.template.ledger, wechatData);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
 
-                // 获取审核人列表
+        async _checked(tenderId, opinion, times) {
+            const accountId = this.ctx.session.sessionUser.accountId;
+            const pid = this.ctx.session.sessionProject.id;
+            const auditors = await this.getAllDataByCondition({ where: { tender_id: tenderId, times, status: auditConst.status.checking } });
+            if (auditors.length === 0) throw '审核数据错误';
+            const selfAuditor = auditors.find(x => { return x.audit_id === accountId; });
+            if (!selfAuditor) throw '当前标段您无权审批';
+
+            const flowAuditors = await this.getAllDataByCondition({ where: { tender_id: tenderId, times, audit_order: selfAuditor.audit_order } });
+            const nextAuditors = await this.getAllDataByCondition({ where: { tender_id: tenderId, times, audit_order: selfAuditor.audit_order + 1 } });
+            const beginAuditors = await this.getAllDataByCondition({ where: { tender_id: tenderId, audit_order: 1, times } });
+            const time = new Date();
+
+            const noticeContent = await this.getNoticeContent(selfAuditor.tender_id, pid, accountId, opinion);
+            const defaultNoticeRecord = { pid, type: pushType.ledger, status: auditConst.status.checked, content: noticeContent };
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 获取审核人列表,添加提醒
+                const records = [{ uid: this.ctx.tender.data.user_id, ...defaultNoticeRecord } ];
                 const auditList = await this.getAuditors(tenderId, times);
-                // 添加推送
-                const noticeContent = await this.getNoticeContent(audit.tender_id, pid, audit.audit_id, opinion);
-                const records = [
-                    {
-                        pid,
-                        type: pushType.ledger,
-                        uid: this.ctx.tender.data.user_id,
-                        status: checkType,
-                        content: noticeContent,
-                    },
-                ];
                 auditList.forEach(audit => {
-                    records.push({
-                        pid,
-                        type: pushType.ledger,
-                        uid: audit.audit_id,
-                        status: checkType,
-                        content: noticeContent,
-                    });
+                    records.push({ uid: audit.audit_id, ...defaultNoticeRecord});
                 });
                 await transaction.insert('zh_notice', records);
+                const users = this._.uniq(this._.concat(this._.map(auditList, 'audit_id'), this.ctx.tender.data.user_id));
+
                 // 更新当前审核流程
-                await transaction.update(this.tableName, { id: audit.id, status: checkType, opinion, end_time: time });
-                await this.ctx.service.noticeAgain.stopNoticeAgain(transaction, this.tableName, audit.id);
-                const begin_audit = await this.getDataByCondition({
-                    tender_id: tenderId,
-                    audit_order: 1,
-                });
-                const tenderMsg = await this.ctx.service.tender.getDataById(tenderId);
-                const users = this._.uniq(this._.concat(this._.map(auditList, 'audit_id'), tenderMsg.user_id));
-                if (checkType === auditConst.status.checked) {
-                    const nextAudit = await this.getDataByCondition({
-                        tender_id: tenderId,
-                        times,
-                        audit_order: audit.audit_order + 1,
-                    });
+                await transaction.update(this.tableName, { id: selfAuditor.id, status: auditConst.status.checked, opinion, end_time: time });
+                // 审批提醒
+                await this.ctx.service.noticeAgain.stopNoticeAgain(transaction, this.tableName, this._.map(auditors, 'id'));
+
+                if (auditors.length === 1 || selfAuditor.audit_type === auditType.key.or) {
+                    // 或签更新他人审批状态
+                    if (selfAuditor.audit_type === auditType.key.or) {
+                        const updateOther = [];
+                        for (const audit of auditors) {
+                            if (audit.audit_id === selfAuditor.audit_id) continue;
+                            updateOther.push({ id: audit.id, status: auditConst.status.checkSkip, opinion: '', end_time: time });
+                        }
+                        if (updateOther.length > 0) {
+                            await transaction.updateRows(this.tableName, updateOther);
+                            await this.ctx.service.noticeAgain.stopNoticeAgain(transaction, this.tableName, updateOther.map(x => { return x.id}));
+                        }
+                    }
                     // 无下一审核人表示,审核结束
-                    if (nextAudit) {
-                        await transaction.update(this.tableName, {
-                            id: nextAudit.id,
-                            status: auditConst.status.checking,
-                            begin_time: time,
-                        });
-                        await this.ctx.service.tenderCache.updateLedgerCache(transaction, tenderId, auditConst.status.checking, checkType, nextAudit);
-                        await this.ctx.helper.sendAliSms(nextAudit.audit_id, smsTypeConst.const.TZ, smsTypeConst.judge.approval.toString(), SmsAliConst.template.ledger_check);
+                    if (nextAuditors.length > 0) {
+                        const nextAuditUpdateData = nextAuditors.map(x => { return {id: x.id, status: auditConst.status.checking, begin_time: time }; });
+                        await transaction.updateRows(this.tableName, nextAuditUpdateData);
+
+                        await this.ctx.service.tenderCache.updateLedgerCache(transaction, tenderId, auditConst.status.checking, auditConst.status.checked, nextAuditors);
+                        const nextAuditorIds = nextAuditors.map(x => { return x.audit_id; });
+                        await this.ctx.helper.sendAliSms(nextAuditorIds, smsTypeConst.const.TZ, smsTypeConst.judge.approval.toString(), SmsAliConst.template.ledger_check);
                         // 微信模板通知
                         const wechatData = {
                             status: wxConst.status.check,
                             tips: wxConst.tips.check,
-                            begin_time: Date.parse(begin_audit.begin_time),
+                            begin_time: Date.parse(beginAuditors[0].begin_time),
                         };
-                        await this.ctx.helper.sendWechat(nextAudit.audit_id, smsTypeConst.const.TZ, smsTypeConst.judge.approval.toString(), wxConst.template.ledger, wechatData);
-                        await this.ctx.service.noticeAgain.addNoticeAgain(transaction, smsTypeConst.const.TZ, {
-                            pid: this.ctx.session.sessionProject.id,
-                            tid: tenderId,
-                            uid: nextAudit.audit_id,
-                            sp_type: 'ledger',
-                            sp_id: nextAudit.id,
-                            table_name: this.tableName,
-                            template: wxConst.template.ledger,
-                            wx_data: wechatData,
-                        });
+                        await this.ctx.helper.sendWechat(nextAuditorIds, smsTypeConst.const.TZ, smsTypeConst.judge.approval.toString(), wxConst.template.ledger, wechatData);
+                        for (const audit of nextAuditors) {
+                            await this.ctx.service.noticeAgain.addNoticeAgain(transaction, smsTypeConst.const.TZ, {
+                                pid: this.ctx.session.sessionProject.id,
+                                tid: tenderId,
+                                uid: audit.audit_id,
+                                sp_type: 'ledger',
+                                sp_id: audit.id,
+                                table_name: this.tableName,
+                                template: wxConst.template.ledger,
+                                wx_data: wechatData,
+                            });
+                        }
                     } else {
                         // 同步标段信息
                         await transaction.update(this.ctx.service.tender.tableName, {
-                            id: tenderId,
-                            ledger_status: checkType,
+                            id: tenderId, ledger_status: auditConst.status.checked,
                         });
-                        await this.ctx.service.tenderCache.updateLedgerCache(transaction, tenderId, checkType, checkType);
-                        await this.ctx.service.tenderTag.saveTenderTag(tenderId, {ledger_time: new Date()}, transaction);
-                        // const users = this._.pull(this._.map(auditList, 'audit_id'), audit.id);
+                        await this.ctx.service.tenderCache.updateLedgerCache(transaction, tenderId, auditConst.status.checked, auditConst.status.checked);
+                        // 审批通过,添加标记
+                        await this.ctx.service.tenderTag.saveTenderTag(tenderId, { ledger_time: time }, transaction);
                         await this.ctx.helper.sendAliSms(users, smsTypeConst.const.TZ, smsTypeConst.judge.result.toString(), SmsAliConst.template.ledger_result, {
                             status: SmsAliConst.status.success,
                         });
@@ -411,48 +656,13 @@ module.exports = app => {
                         const wechatData = {
                             status: wxConst.status.success,
                             tips: wxConst.tips.success,
-                            begin_time: Date.parse(begin_audit.begin_time),
+                            begin_time: Date.parse(beginAuditors[0].begin_time),
                         };
                         await this.ctx.helper.sendWechat(users, smsTypeConst.const.TZ, smsTypeConst.judge.result.toString(), wxConst.template.ledger, wechatData);
                         // 审批通过 - 检查三方特殊推送
                         await this.ctx.service.specMsg.addLedgerMsg(transaction, pid, this.ctx.tender, pushOperate.ledger.checked);
                     }
-                } else {
-                    // 同步标段信息
-                    await transaction.update(this.ctx.service.tender.tableName, {
-                        id: tenderId,
-                        ledger_times: times + 1,
-                        ledger_status: checkType,
-                    });
-                    await this.ctx.service.tenderCache.updateLedgerCache(transaction, tenderId, checkType, checkType, { audit_id: this.ctx.tender.data.user_id });
-                    // 拷贝新一次审核流程列表
-                    const auditors = await this.getAllDataByCondition({
-                        where: { tender_id: tenderId, times },
-                        columns: ['tender_id', 'audit_order', 'audit_id'],
-                    });
-                    const insertAuditors = [];
-                    for (const a of auditors) {
-                        if (insertAuditors.find(x => { return x.audit_id === a.audit_id; })) continue;
-                        a.audit_order = insertAuditors.length + 1;
-                        a.times = times + 1;
-                        a.status = auditConst.status.uncheck;
-                        insertAuditors.push(a)
-                    }
-                    await transaction.insert(this.tableName, insertAuditors);
-
-                    // const users = this._.pull(this._.map(auditList, 'audit_id'), audit.id);
-                    await this.ctx.helper.sendAliSms(users, smsTypeConst.const.TZ, smsTypeConst.judge.result.toString(), SmsAliConst.template.ledger_result, {
-                        status: SmsAliConst.status.back,
-                    });
-                    // 微信模板通知
-                    const wechatData = {
-                        status: wxConst.status.back,
-                        tips: wxConst.tips.back,
-                        begin_time: Date.parse(begin_audit.begin_time),
-                    };
-                    await this.ctx.helper.sendWechat(users, smsTypeConst.const.TZ, smsTypeConst.judge.result.toString(), wxConst.template.ledger, wechatData);
                 }
-
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
@@ -462,8 +672,25 @@ module.exports = app => {
 
 
         /**
+         * 审批
+         * @param {Number} tenderId - 标段id
+         * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
+         * @param {String} opinion 审批意见
+         * @param {Number} times - 第几次审批
+         * @return {Promise<void>}
+         */
+        async check(tenderId, checkType, opinion, times = 1) {
+            switch (checkType) {
+                case auditConst.status.checked: await this._checked(tenderId, opinion, times); break;
+                case auditConst.status.checkNo: await this._checkNo(tenderId, opinion, times); break;
+                default: throw '提交数据错误';
+            }
+        }
+
+
+        /**
          * 重新审批
-         * @param {Number} stageId - 标段id
+         * @param {Number} tenderId - 标段id
          * @param {Number} times - 第几次审批
          * @return {Promise<void>}
          */
@@ -647,19 +874,18 @@ module.exports = app => {
             return result;
         }
 
-        async updateNewAuditList(tender, newIdList) {
+        async updateNewAuditList(tender, newList) {
             const transaction = await this.db.beginTransaction();
             try {
                 // 先删除旧的审批流,再添加新的
                 await transaction.delete(this.tableName, { tender_id: tender.id, times: tender.ledger_times });
                 const newAuditors = [];
-                let order = 1;
-                for (const aid of newIdList) {
+                for (const auditor of newList) {
                     newAuditors.push({
-                        tender_id: tender.id, audit_id: aid,
-                        times: tender.ledger_times, audit_order: order, status: auditConst.status.uncheck,
-                    });
-                    order++;
+                        tender_id: tender.id, audit_id: auditor.audit_id,
+                        times: tender.ledger_times, audit_order: auditor.audit_order, status: auditConst.status.uncheck,
+                        audit_type: auditor.audit_type, audit_ledger_id: auditor.audit_type === auditType.key.union ? auditor.audit_ledger_id : '',
+                    })
                 }
                 if(newAuditors.length > 0) await transaction.insert(this.tableName, newAuditors);
                 await transaction.commit();
@@ -673,14 +899,22 @@ module.exports = app => {
             const transaction = await this.db.beginTransaction();
             try {
                 // 先判断auditList里的aid是否与lastId相同,相同则删除并重新更新order
-                const idList = this._.map(auditList, 'audit_id');
-                let order = idList.length + 1;
-                if (idList.indexOf(lastId) !== -1) {
-                    await transaction.delete(this.tableName, { tender_id: tender.id, times: tender.ledger_times, audit_id: lastId });
-                    const audit = this._.find(auditList, { 'audit_id': lastId });
-                    // 顺移之后审核人流程顺序
-                    await this._syncOrderByDelete(transaction, tender.id, audit.audit_order, tender.ledger_times);
-                    order = order - 1;
+                const existAudit = auditList.find(x => { return x.aid === lastId });
+                let order = auditList.length > 0 ? auditList.reduce((rst, a) => { return Math.max(rst, a.audit_order)}, 0) + 1 : 1; // 最大值 + 1
+                if (existAudit) {
+                    await transaction.delete(this.tableName, { sid: stage.id, times: stage.times, audit_id: lastId });
+                    const sameOrder = auditList.filter(x => { return x.audit_order === existAudit.audit_order });
+                    if (sameOrder.length === 1) {
+                        const updateData = [];
+                        auditList.forEach(x => {
+                            if (x.audit_order <= existAudit.audit_order) return;
+                            updateData.push({id: x.id, audit_order: x.audit_order - 1});
+                        });
+                        if (updateData.length > 0) {
+                            await transaction.updateRows(updateData);
+                        }
+                        order = order - 1;
+                    }
                 }
 
                 // 添加终审
@@ -698,7 +932,7 @@ module.exports = app => {
 
         /**
          * 删除本次审批流程
-         * @param {Number} stageId - 标段id
+         * @param {Number} tenderId - 标段id
          * @param {Number} times - 第几次审批
          * @param {Object} data - 更改参数
          * @return {Promise<void>}
@@ -729,14 +963,6 @@ module.exports = app => {
                     }
                     await transaction.delete(this.tableName, { tender_id: tenderId, times, audit_id: now_audit.audit_id, audit_order: now_audit.audit_order });
                     await this._syncOrderByDelete(transaction, tenderId, now_audit.audit_order, times);
-                    // // 旧的更新为is_old为1
-                    // await transaction.update(this.tableName, { is_old: 1 }, {
-                    //     where: {
-                    //         sid: stageId,
-                    //         times,
-                    //         aid: data.old_aid,
-                    //     }
-                    // });
                 } else if (data.operate === 'change') {
                     const nowAudit = await this.getDataByCondition({ tender_id: tenderId, times, audit_id: now_audit.audit_id, audit_order: now_audit.audit_order });
                     if (now_audit.status !== auditConst.status.uncheck || !nowAudit) {
@@ -744,14 +970,6 @@ module.exports = app => {
                     }
                     nowAudit.audit_id = data.new_aid;
                     await transaction.update(this.tableName, nowAudit);
-                    // 旧的更新为is_old为1
-                    // await transaction.update(this.tableName, { is_old: 1 }, {
-                    //     where: {
-                    //         sid: stageId,
-                    //         times,
-                    //         aid: data.old_aid,
-                    //     }
-                    // });
                 }
                 if (this.ctx.tender.info.shenpi.ledger === shenpiConst.sp_status.gdspl || this.ctx.tender.info.shenpi.ledger === shenpiConst.sp_status.gdzs) {
                     const newAuditors = await this.getAuditGroupByList(tenderId, times, transaction);

+ 2 - 0
app/service/ledger_revise.js

@@ -159,6 +159,8 @@ module.exports = app => {
                     in_time: inTime,
                     audit_order: newAuditors.length + 1,
                     audit_id: a.audit_id,
+                    audit_type: a.audit_type,
+                    audit_ledger_id: a.audit_ledger_id,
                     times: 1,
                     status: audit.status.uncheck,
                 });

+ 473 - 248
app/service/revise_audit.js

@@ -9,6 +9,7 @@
  */
 
 const auditConst = require('../const/audit').revise;
+const auditType = require('../const/audit').auditType;
 const smsTypeConst = require('../const/sms_type');
 const SmsAliConst = require('../const/sms_alitemplate');
 const wxConst = require('../const/wechat_template');
@@ -47,6 +48,52 @@ module.exports = app => {
             return await this.db.queryOne(sql, sqlParam);
         }
 
+        async getAuditorByOrder(reviseId, order, times = 1) {
+            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`audit_type`, la.`audit_ledger_id`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+                '  FROM ?? AS la Left Join ?? AS pa ON la.`audit_id` = pa.`id`' +
+                '  WHERE la.`rid` = ? and la.`audit_order` = ? and la.`times` = ?';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, reviseId, order, times];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        async getAuditorsByOrder(reviseId, order, times) {
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`,' +
+                '    la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, la.audit_type, la.audit_ledger_id ' +
+                '    FROM ' + this.tableName + ' AS la' +
+                '    Left Join ' + this.ctx.service.projectAccount.tableName + ' AS pa ON la.`audit_id` = pa.`id`' +
+                '  WHERE la.`rid` = ? and la.`audit_order` = ? and la.`times` = ?' +
+                '  ORDER BY `audit_order` DESC';
+            const sqlParam = [reviseId, order, times ? times: 1];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        async getLatestAuditor(reviseId, times, status) {
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`,' +
+                '    la.`times`, la.`audit_order`, la.`audit_type`, la.`audit_ledger_id`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+                '    FROM ' + this.tableName + ' AS la' +
+                '    Left Join ' + this.ctx.service.projectAccount.tableName + ' AS pa ON la.`audit_id` = pa.`id`' +
+                '  WHERE la.`rid` = ? and la.`status` = ? and la.`times` = ?' +
+                '  ORDER BY `audit_order` DESC';
+            const sqlParam = [reviseId, status, times ? times : 1];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        async getLatestAuditors(reviseId, times, status) {
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.audit_type, la.audit_order, la.audit_ledger_id,' +
+                '    la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+                '    FROM ' + this.tableName + ' AS la' +
+                '    Left Join ' + this.ctx.service.projectAccount.tableName + ' AS pa ON la.`audit_id` = pa.`id`' +
+                '  WHERE la.`rid` = ? and la.`status` = ? and la.`times` = ?' +
+                '  ORDER BY `audit_order` DESC';
+            const sqlParam = [reviseId, status, times ? times: 1];
+            const result = await this.db.query(sql, sqlParam);
+            if (result.length === 0) return [];
+            return result.filter(x => { return x.audit_order === result[0].audit_order });
+        }
+
         /**
          * 获取标段审核列表信息
          *
@@ -54,16 +101,19 @@ module.exports = app => {
          * @param {Number} times - 第几次审批
          * @return {Promise<*>}
          */
-        async getAuditors(reviseId, times = 1) {
-            const sql =
-                'SELECT la.`audit_id`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`,' +
-                '    pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`' +
-                '  FROM ' + this.tableName + ' AS la ' +
-                '  INNER JOIN ' + this.ctx.service.projectAccount.tableName +
-                ' AS pa ON la.`rid` = ? and la.`times` = ? and la.`audit_id` = pa.`id`' +
-                '  ORDER BY la.`audit_order`';
+        async getAuditors(reviseId, times = 1, order_sort = 'asc') {
+            const sql = 'SELECT la.id, la.audit_id, la.times, la.audit_order, la.audit_type, la.audit_ledger_id, la.status, la.opinion, la.begin_time, la.end_time, ' +
+                '    pa.name, pa.company, pa.role, pa.mobile, pa.telephone, pa.sign_path' +
+                `  FROM ${this.tableName} la LEFT JOIN ${this.ctx.service.projectAccount.tableName} pa ON la.audit_id = pa.id` +
+                '  WHERE la.rid = ? AND la.times = ?' +
+                '  ORDER BY la.audit_order ' + order_sort;
             const sqlParam = [reviseId, times];
-            return await this.db.query(sql, sqlParam);
+            const result = await this.db.query(sql, sqlParam);
+            const max_sort = this._.max(result.map(x => { return x.audit_order; }));
+            for (const i in result) {
+                result[i].max_sort = max_sort;
+            }
+            return result;
         }
 
         /**
@@ -105,6 +155,172 @@ module.exports = app => {
             return await this.db.queryOne(sql, sqlParam);
         }
 
+        async getCurAuditors(reviseId, times = 1) {
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, la.audit_type, la.audit_order, la.audit_ledger_id ' +
+                '  FROM ?? AS la Left Join ?? AS pa On la.`audit_id` = pa.`id`' +
+                '  WHERE la.`rid` = ? and la.`status` = ? and la.`times` = ?';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, reviseId, auditConst.status.checking, times];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        async getAuditorGroup(reviseId, times) {
+            const auditors = await this.getAuditors(reviseId, times); // 全部参与的审批人
+            return this.ctx.helper.groupAuditors(auditors, 'audit_order');
+        }
+
+        async getUserGroup(reviseId, times) {
+            const group = await this.getAuditorGroup(reviseId, times);
+            const sql =
+                'SELECT pa.`id` As audit_id, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As rid, 0 As audit_order, 1 As audit_type' +
+                '  FROM ' + this.ctx.service.ledgerRevise.tableName + ' As r' +
+                '  LEFT JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa' +
+                '  ON r.uid = pa.id' +
+                '  WHERE r.id = ?';
+            const sqlParam = [times, reviseId, reviseId];
+            const user = await this.db.queryOne(sql, sqlParam);
+            group.unshift([ user ]);
+            return group;
+        }
+
+        async getUniqUserGroup(reviseId, times) {
+            const group = await this.getAuditorGroup(reviseId, times);
+            const sql =
+                'SELECT pa.`id` As audit_id, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As rid, 0 As audit_order, 1 As audit_type' +
+                '  FROM ' + this.ctx.service.ledgerRevise.tableName + ' As r' +
+                '  LEFT JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa' +
+                '  ON r.uid = pa.id' +
+                '  WHERE r.id = ?';
+            const sqlParam = [times, reviseId, reviseId];
+            const user = await this.db.queryOne(sql, sqlParam);
+            group.unshift([ user ]);
+            return this.ctx.helper.groupAuditorsUniq(group, 'audit_order');
+        }
+
+        async getAuditorHistory(reviseId, times, reverse = false) {
+            const history = [];
+            if (times >= 1) {
+                for (let i = 1; i <= times; i++) {
+                    const auditors = await this.getAuditors(reviseId, i);
+                    const group = this.ctx.helper.groupAuditors(auditors, 'audit_order');
+                    const historyGroup = [];
+                    const max_order = group.length > 0 && group[group.length - 1].length > 0 ? group[group.length - 1][0].audit_order : -1;
+                    for (const g of group) {
+                        const his = {
+                            beginYear: '', beginDate: '', beginTime: '', endYear: '', endDate: '', endTime: '', begin_time: null, end_time: null,
+                            audit_type: g[0].audit_type, audit_order: g[0].audit_order,
+                            auditors: g
+                        };
+                        if (his.audit_type === auditType.key.common) {
+                            his.name = g[0].name;
+                        } else {
+                            his.name = this.ctx.helper.transFormToChinese(his.audit_order) + '审';
+                        }
+                        his.is_final = his.audit_order === max_order;
+                        if (g[0].begin_time) {
+                            his.begin_time = g[0].begin_time;
+                            const beginTime = this.ctx.moment(g[0].begin_time);
+                            his.beginYear = beginTime.format('YYYY');
+                            his.beginDate = beginTime.format('MM-DD');
+                            his.beginTime = beginTime.format('HH:mm:ss');
+                        }
+                        let end_time;
+                        g.forEach(x => {
+                            if (x.status === auditConst.status.checkSkip) return;
+                            if (!his.status || x.status === auditConst.status.checking) his.status = x.status;
+                            if (x.end_time && (!end_time || x.end_time > end_time)) {
+                                end_time = x.end_time;
+                                if (his.status !== auditConst.status.checking) his.status = x.status;
+                            }
+                        });
+                        if (end_time) {
+                            his.end_time = end_time;
+                            const endTime = this.ctx.moment(end_time);
+                            his.endYear = endTime.format('YYYY');
+                            his.endDate = endTime.format('MM-DD');
+                            his.endTime = endTime.format('HH:mm:ss');
+                        }
+                        historyGroup.push(his);
+                    }
+                    if (reverse) {
+                        history.push(historyGroup.reverse());
+                    } else {
+                        history.push(historyGroup);
+                    }
+                }
+            }
+            return history;
+        }
+
+        async getUniqAuditor(reviseId, times) {
+            const auditors = await this.getAuditors(reviseId, times); // 全部参与的审批人
+            const result = [];
+            auditors.forEach(x => {
+                if (result.findIndex(r => { return x.audit_id === r.audit_id && x.audit_order === r.audit_order; }) < 0) {
+                    result.push(x);
+                }
+            });
+            return result;
+        }
+
+        async loadReviseUser(revise) {
+            const status = auditConst.status;
+            const accountId = this.ctx.session.sessionUser.accountId;
+
+            revise.user = await this.ctx.service.projectAccount.getAccountInfoById(revise.uid);
+            revise.auditors = await this.getAuditors(revise.id, revise.times); // 全部参与的审批人
+            revise.auditorIds = this._.map(revise.auditors, 'audit_id');
+            revise.curAuditors = revise.auditors.filter(x => { return x.status === status.checking; }); // 当前流程中审批中的审批人
+            revise.curAuditorIds = this._.map(revise.curAuditors, 'audit_id');
+            revise.flowAuditors = revise.curAuditors.length > 0 ? revise.auditors.filter(x => { return x.audit_order === revise.curAuditors[0].audit_order; }) : []; // 当前流程中参与的审批人(包含会签时,审批通过的人)
+            revise.flowAuditorIds = this._.map(revise.flowAuditors, 'audit_id');
+            revise.nextAuditors = revise.curAuditors.length > 0 ? revise.auditors.filter(x => { return x.audit_order === revise.curAuditors[0].audit_order + 1; }) : [];
+            revise.nextAuditorIds = this._.map(revise.nextAuditors, 'audit_id');
+            revise.auditorGroups = this.ctx.helper.groupAuditors(revise.auditors, 'audit_order');
+            revise.userGroups = this.ctx.helper.groupAuditorsUniq(revise.auditorGroups);
+            revise.userGroups.unshift([{
+                aid: revise.user.id, order: 0, times: revise.times, audit_order: 0, audit_type: auditType.key.common,
+                name: revise.user.name, role: revise.user.role, company: revise.user.company
+            }]);
+            revise.finalAuditorIds = revise.userGroups[revise.userGroups.length - 1].map(x => { return x.aid; });
+            revise.relaAuditor = revise.auditors.find(x => { return x.aid === accountId });
+
+            revise.assists = [];// await this.service.ledgerAuditAss.getData(tender); // 全部协同人
+            revise.assists = revise.assists.filter(x => {
+                return x.user_id === revise.uid || revise.auditorIds.indexOf(x.uid) >= 0;
+            }); // 过滤无效协同人
+            revise.userAssists = revise.assists.filter(x => { return x.user_id === revise.uid; }); // 原报协同人
+            revise.userAssistIds = this._.map(revise.userAssists, 'ass_user_id');
+            revise.auditAssists = revise.assists.filter(x => { return x.user_id !== revise.uid; }); // 审批协同人
+            revise.auditAssistIds = this._.map(revise.auditAssists, 'ass_user_id');
+            revise.relaAssists = revise.assists.filter(x => { return x.user_id === accountId }); // 登录人的协同人
+            revise.userIds = revise.status === status.uncheck // 当前流程下全部参与人id
+                ? [revise.user_id, ...revise.userAssistIds]
+                : [revise.user_id, ...revise.userAssistIds, ...revise.auditorIds, ...revise.auditAssistIds];
+        }
+
+        async loadReviseAuditViewData(revise) {
+            const times = revise.status === auditConst.status.checkNo ? revise.times - 1 : revise.times;
+
+            if (!revise.user) revise.user = await this.ctx.service.projectAccount.getAccountInfoById(revise.uid);
+            revise.auditHistory = await this.getAuditorHistory(revise.id, times);
+            // 获取审批流程中左边列表
+            if (revise.status === auditConst.status.checkNo && revise.user_id !== this.ctx.session.sessionUser.accountId) {
+                const auditors = await this.getAuditors(revise.id, times); // 全部参与的审批人
+                const auditorGroups = this.ctx.helper.groupAuditors(auditors, 'audit_order');
+                revise.auditors2 = this.ctx.helper.groupAuditorsUniq(auditorGroups, 'audit_order');
+                revise.auditors2.unshift([{
+                    aid: revise.user.id, order: 0, times: revise.times - 1, audit_order: 0, audit_type: auditType.key.common,
+                    name: revise.user.name, role: revise.user.role, company: revise.user.company
+                }]);
+            } else {
+                revise.auditors2 = revise.userGroups;
+            }
+            if (revise.status === auditConst.status.uncheck || revise.status === auditConst.status.checkNo) {
+                revise.auditorList = await this.getAuditors(revise.id, revise.times);
+            }
+        }
+
         /**
          * 获取最新审核顺序
          *
@@ -222,8 +438,8 @@ module.exports = app => {
          * @return {Promise<boolean>}
          */
         async start(revise, times = 1) {
-            const audit = await this.getDataByCondition({ rid: revise.id, times, audit_order: 1 });
-            if (!audit) {
+            const audits = await this.getAllDataByCondition({ where: { rid: revise.id, times, audit_order: 1 } });
+            if (!audits.length === 0) {
                 if(this.ctx.tender.info.shenpi.revise === shenpiConst.sp_status.gdspl) {
                     throw '请联系管理员添加审批人';
                 } else {
@@ -237,21 +453,17 @@ module.exports = app => {
 
             const transaction = await this.db.beginTransaction();
             try {
-                await transaction.update(this.tableName, {
-                    id: audit.id,
-                    status: auditConst.status.checking,
-                    begin_time: time,
-                    bills_file: revise.bills_file,
-                    pos_file: revise.pos_file,
-                });
+                const updateAuditData = audits.map(a => { return {
+                    id: a.id, status: auditConst.status.checking, begin_time: time,
+                    bills_file: revise.bills_file, pos_file: revise.pos_file
+                }; });
+                await transaction.updateRows(this.tableName, updateAuditData);
                 const reviseData = {
                     id: revise.id,
                     status: auditConst.status.checking,
                     his_id, sum: JSON.stringify(sum),
                 };
-                if (revise.times === 1) {
-                    reviseData.begin_time = time;
-                }
+                if (revise.times === 1) reviseData.begin_time = time;
                 await transaction.update(this.ctx.service.ledgerRevise.tableName, reviseData);
 
                 // 投资进度改变状态
@@ -261,7 +473,8 @@ module.exports = app => {
                 // 下一人
                 // await this.ctx.helper.sendUserSms(audit.audit_id, smsTypeConst.const.XD,
                 //     smsTypeConst.judge.approval.toString(), '台账修订需要您审批,请登录系统处理。');
-                await this.ctx.helper.sendAliSms(audit.audit_id, smsTypeConst.const.XD, smsTypeConst.judge.approval.toString(), SmsAliConst.template.revise_check);
+                const auditorIds = audits.map(x => { return x.audit_id; });
+                await this.ctx.helper.sendAliSms(auditorIds, smsTypeConst.const.XD, smsTypeConst.judge.approval.toString(), SmsAliConst.template.revise_check);
                 // 微信模板通知
                 const shenpiUrl = await this.ctx.helper.urlToShort(
                     this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + revise.tid + '/revise/' + revise.id + '/info'
@@ -273,20 +486,22 @@ module.exports = app => {
                     code: this.ctx.session.sessionProject.code,
                     begin_time: Date.parse(time),
                 };
-                await this.ctx.helper.sendWechat(audit.audit_id, smsTypeConst.const.XD, smsTypeConst.judge.approval.toString(), wxConst.template.revise, wechatData);
-                await this.ctx.service.noticeAgain.addNoticeAgain(transaction, smsTypeConst.const.XD, {
-                    pid: this.ctx.session.sessionProject.id,
-                    tid: this.ctx.tender.id,
-                    uid: audit.audit_id,
-                    sp_type: 'revise',
-                    sp_id: audit.id,
-                    table_name: this.tableName,
-                    template: wxConst.template.revise,
-                    wx_data: wechatData,
-                });
+                await this.ctx.helper.sendWechat(auditorIds, smsTypeConst.const.XD, smsTypeConst.judge.approval.toString(), wxConst.template.revise, wechatData);
+                for (const audit of audits) {
+                    await this.ctx.service.noticeAgain.addNoticeAgain(transaction, smsTypeConst.const.XD, {
+                        pid: this.ctx.session.sessionProject.id,
+                        tid: this.ctx.tender.id,
+                        uid: audit.audit_id,
+                        sp_type: 'revise',
+                        sp_id: audit.id,
+                        table_name: this.tableName,
+                        template: wxConst.template.revise,
+                        wx_data: wechatData,
+                    });
+                }
                 // 其他参与人
                 const auditList = await this.getAuditors(revise.id, times);
-                const users = this._.pull(this._.map(auditList, 'user_id'), audit.audit_id);
+                const users = this._.map(auditList.filter(x => { return auditorIds.indexOf(x.audit_id) < 0; }, 'audit_id'));
                 // await this.ctx.helper.sendUserSms(users, smsTypeConst.const.XD,
                 //     smsTypeConst.judge.result.toString(), '台账修订已上报。');
                 await this.ctx.helper.sendAliSms(users, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_report);
@@ -345,243 +560,230 @@ module.exports = app => {
             await transaction.query(pSql, sqlParam);
         }
 
-        /**
-         * 审批
-         * @param {Object} revise - 修订
-         * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
-         * @param {String} opinion - 审批意见
-         * @param {Number} times - 第几次审批
-         * @return {Promise<void>}
-         */
-        async check(revise, checkType, opinion, times = 1) {
-            if (checkType !== auditConst.status.checked && checkType !== auditConst.status.checkNo) throw '提交数据错误';
-            const audit = await this.getDataByCondition({
-                rid: revise.id,
-                times,
-                status: auditConst.status.checking,
-            });
-            if (!audit) throw '审核数据错误';
+        async _checked(revise, opinion, times) {
+            const accountId = this.ctx.session.sessionUser.accountId;
             const pid = this.ctx.session.sessionProject.id;
+            const auditors = await this.getAllDataByCondition({ where: { rid: revise.id, times, status: auditConst.status.checking } });
+            const selfAuditor = auditors.find(x => { return x.audit_id === accountId; });
+            if (!selfAuditor) throw '审核数据错误';
+
+            const flowAuditors = await this.getAllDataByCondition({ where: { rid: revise.id, times, audit_order: selfAuditor.audit_order } });
+            const nextAuditors = await this.getAllDataByCondition({ where: { rid: revise.id, times, audit_order: selfAuditor.audit_order + 1 } });
+            const beginAuditors = await this.getAllDataByCondition({ where: { rid: revise.id, audit_order: 1, times } });
+            const time = new Date();
+
+            const noticeContent = await this.getNoticeContent(pid, selfAuditor.tender_id, revise.id, selfAuditor.audit_id, opinion);
+            const defaultNoticeRecord = { pid, type: pushType.revise, status: auditConst.status.checked, content: noticeContent};
+
             const transaction = await this.db.beginTransaction();
             try {
                 const auditList = await this.getAuditors(revise.id, times);
                 // 审核通过添加到推送表
-                const noticeContent = await this.getNoticeContent(pid, audit.tender_id, audit.rid, audit.audit_id, opinion);
-                const records = [
-                    {
-                        pid,
-                        type: pushType.revise,
-                        uid: revise.uid,
-                        status: checkType,
-                        content: noticeContent,
-                    },
-                ];
+                const records = [ { uid: revise.uid, ...defaultNoticeRecord } ];
                 auditList.forEach(audit => {
-                    records.push({
-                        pid,
-                        type: pushType.revise,
-                        uid: audit.audit_id,
-                        status: checkType,
-                        content: noticeContent,
-                    });
+                    records.push({ uid: audit.audit_id, ...defaultNoticeRecord });
                 });
                 await transaction.insert('zh_notice', records);
 
                 // 整理当前流程审核人状态更新
-                const time = new Date();
                 // 更新当前审核流程
-                await transaction.update(this.tableName, {
-                    id: audit.id,
-                    status: checkType,
-                    opinion,
-                    end_time: time,
-                });
-                await this.ctx.service.noticeAgain.stopNoticeAgain(transaction, this.tableName, audit.id);
-                if (checkType === auditConst.status.checked) {
-                    const nextAudit = await this.getDataByCondition({
-                        rid: revise.id,
-                        times,
-                        audit_order: audit.audit_order + 1,
-                    });
-                    // 无下一审核人表示,审核结束
-                    if (nextAudit) {
-                        await transaction.update(this.tableName, {
-                            id: nextAudit.id,
-                            status: auditConst.status.checking,
-                            begin_time: time,
-                        });
+                await transaction.update(this.tableName, { id: selfAuditor.id, status: auditConst.status.checked, opinion, end_time: time });
+                await this.ctx.service.noticeAgain.stopNoticeAgain(transaction, this.tableName, selfAuditor.id);
+                if (auditors.length === 1 || selfAuditor.audit_type === auditType.key.or) {
+                    // 或签更新他人审批状态
+                    if (selfAuditor.audit_type === auditType.key.or) {
+                        const updateOther = [];
+                        for (const audit of auditors) {
+                            if (audit.audit_id === selfAuditor.audit_id) continue;
+                            updateOther.push({ id: audit.id, status: auditConst.status.checkSkip, opinion: '', end_time: time });
+                        }
+                        if (updateOther.length > 0) {
+                            await this.ctx.service.noticeAgain.stopNoticeAgain(transaction, this.tableName, updateOther.map(x => { return x.id}));
+                            await transaction.updateRows(this.tableName, updateOther);
+                        }
+                    }
+                }
 
-                        // 短信通知-需要审批提醒功能
-                        // 下一人
-                        // await this.ctx.helper.sendUserSms(nextAudit.user_id, smsTypeConst.const.XD,
-                        //     smsTypeConst.judge.approval.toString(), '台账修订需要您审批,请登录系统处理。');
-                        await this.ctx.helper.sendAliSms(nextAudit.audit_id, smsTypeConst.const.XD, smsTypeConst.judge.approval.toString(), SmsAliConst.template.revise_check);
-                        // 微信模板通知
-                        const shenpiUrl = await this.ctx.helper.urlToShort(
-                            this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + revise.tid + '/revise/' + revise.id + '/info'
-                        );
-                        const wechatData = {
-                            wap_url: shenpiUrl,
-                            status: wxConst.status.check,
-                            tips: wxConst.tips.check,
-                            code: this.ctx.session.sessionProject.code,
-                            begin_time: Date.parse(revise.begin_time),
-                        };
-                        await this.ctx.helper.sendWechat(nextAudit.audit_id, smsTypeConst.const.XD, smsTypeConst.judge.approval.toString(), wxConst.template.revise, wechatData);
+                // 无下一审核人表示,审核结束
+                if (nextAuditors.length > 0) {
+                    const nextAuditUpdateData = nextAuditors.map(x => { return { id: x.id, status: auditConst.status.checking, begin_time: time }});
+                    await transaction.updateRows(this.tableName, nextAuditUpdateData);
+
+                    // 短信通知-需要审批提醒功能
+                    const nextAuditorIds = nextAuditors.map(x => { return x.audit_id; });
+                    await this.ctx.helper.sendAliSms(nextAuditorIds, smsTypeConst.const.XD, smsTypeConst.judge.approval.toString(), SmsAliConst.template.revise_check);
+                    // 微信模板通知
+                    const shenpiUrl = await this.ctx.helper.urlToShort(
+                        this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + revise.tid + '/revise/' + revise.id + '/info'
+                    );
+                    const wechatData = {
+                        wap_url: shenpiUrl,
+                        status: wxConst.status.check,
+                        tips: wxConst.tips.check,
+                        code: this.ctx.session.sessionProject.code,
+                        begin_time: Date.parse(revise.begin_time),
+                    };
+                    await this.ctx.helper.sendWechat(nextAuditorIds, smsTypeConst.const.XD, smsTypeConst.judge.approval.toString(), wxConst.template.revise, wechatData);
+                    for (const audit of nextAuditors) {
                         await this.ctx.service.noticeAgain.addNoticeAgain(transaction, smsTypeConst.const.XD, {
                             pid: this.ctx.session.sessionProject.id,
                             tid: this.ctx.tender.id,
-                            uid: nextAudit.audit_id,
+                            uid: audit.audit_id,
                             sp_type: 'revise',
-                            sp_id: nextAudit.id,
+                            sp_id: audit.id,
                             table_name: this.tableName,
                             template: wxConst.template.revise,
                             wx_data: wechatData,
                         });
-                        // 其他参与人
-                        const users = this._.pull(this._.map(auditList, 'audit_id'), audit.audit_id);
-                        users.push(revise.uid);
-                        // await this.ctx.helper.sendUserSms(users, smsTypeConst.const.XD,
-                        //     smsTypeConst.judge.result.toString(), '台账修订审批通过。');
-                        await this.ctx.helper.sendAliSms(users, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result2, {
-                            status: SmsAliConst.status.success,
-                        });
-
-                        // 微信模板通知
-                        // const wechatData2 = {
-                        //     status: wxConst.status.success,
-                        //     tips: wxConst.tips.success,
-                        //     begin_time: Date.parse(revise.begin_time),
-                        // };
-                        // await this.ctx.helper.sendWechat(users, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), wxConst.template.revise, wechatData2);
-                    } else {
-                        // 同步修订信息
-                        await transaction.update(this.ctx.service.ledgerRevise.tableName, {
-                            id: revise.id,
-                            status: checkType,
-                            end_time: time,
-                        });
-                        await this.ctx.service.tenderTag.saveTenderTag(revise.tid, {revise_time: new Date()}, transaction);
-                        // 最新一期跟台账相关的缓存数据应过期
-                        const lastStage = await this.ctx.service.stage.getFlowLatestStage(revise.tid, true);
-                        const cacheTime = new Date();
-                        if (lastStage) {
-                            await transaction.update(this.ctx.service.stage.tableName, {
-                                id: lastStage.id,
-                                cache_time_l: cacheTime,
-                                cache_time_r: cacheTime,
-                            });
-                        }
-                        // 重算台账、计量、工程变更
-                        const reviseCalc = new RevisePrice(this.ctx);
-                        const pcTp = await reviseCalc.calcRevise(revise, transaction);
-                        const sum = revise.sum || await this.ctx.service.reviseBills.addUp({
-                            tender_id: revise.tid, /* , is_leaf: true*/
-                        });
-                        await transaction.update(this.ctx.service.tender.tableName, {
-                            id: revise.tid,
-                            total_price: sum.total_price,
-                            deal_tp: sum.deal_tp,
-                        });
-                        // 修订附件取消修订中标记
-                        await transaction.update(this.ctx.service.ledgerAtt.tableName, { revising: 0 }, { where: { revise_id: revise.id } });
-                        await this.ctx.service.tenderCache.updateStageCache4Revise(transaction, revise.tid, sum, pcTp);
-                        // 清除变更新增部位maxLid缓存,防止树结构混乱
-                        await this.ctx.service.changeLedger._removeCacheMaxLid(audit.tender_id);
-                        // 短信通知-审批通过提醒功能
-                        // 下一人
-                        // const msg = '台账修订审批通过,请登录系统处理。';
-                        // await this.ctx.helper.sendUserSms(revise.uid, smsTypeConst.const.XD,
-                        //     smsTypeConst.judge.result.toString(), msg);
-                        await this.ctx.helper.sendAliSms(revise.uid, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result, {
-                            status: SmsAliConst.status.success,
-                        });
-                        // 其他参与人
-                        const users = this._.pull(this._.map(auditList, 'audit_id'), revise.uid);
-                        // await this.ctx.helper.sendUserSms(users, smsTypeConst.const.XD,
-                        //     smsTypeConst.judge.result.toString(), '台账修订审批通过。');
-                        await this.ctx.helper.sendAliSms(users, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result2, {
-                            status: SmsAliConst.status.success,
-                        });
-
-                        users.push(revise.uid);
-
-                        // 微信模板通知
-                        const shenpiUrl = await this.ctx.helper.urlToShort(
-                            this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + revise.tid + '/revise/' + revise.id + '/info'
-                        );
-                        const wechatData2 = {
-                            wap_url: shenpiUrl,
-                            status: wxConst.status.success,
-                            tips: wxConst.tips.success,
-                            begin_time: Date.parse(revise.begin_time),
-                            code: this.ctx.session.sessionProject.code,
-                        };
-                        await this.ctx.helper.sendWechat(users, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), wxConst.template.revise, wechatData2);
-                        // 审批通过 - 检查三方特殊推送
-                        await this.ctx.service.specMsg.addReviseMsg(transaction, pid, revise, pushOperate.ledger.checked);
                     }
+                    const users = this._.map(auditList.filter(x => { return x.audit_id !== accountId}), 'audit_id');
+                    users.push(revise.uid);
+                    await this.ctx.helper.sendAliSms(users, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result2, {
+                        status: SmsAliConst.status.success,
+                    });
                 } else {
                     // 同步修订信息
                     await transaction.update(this.ctx.service.ledgerRevise.tableName, {
-                        id: revise.id,
-                        times: times + 1,
-                        status: checkType,
-                    });
-                    // 拷贝新一次审核流程列表
-                    const auditors = await this.getAllDataByCondition({
-                        where: { rid: revise.id, times },
-                        columns: ['tender_id', 'rid', 'audit_order', 'audit_id'],
+                        id: revise.id, status: auditConst.status.checked, end_time: time,
                     });
-                    for (const a of auditors) {
-                        a.times = times + 1;
-                        a.status = auditConst.status.uncheck;
-                        a.in_time = time;
+                    await this.ctx.service.tenderTag.saveTenderTag(revise.tid, {revise_time: new Date()}, transaction);
+                    // 最新一期跟台账相关的缓存数据应过期
+                    const lastStage = await this.ctx.service.stage.getFlowLatestStage(revise.tid, true);
+                    const cacheTime = new Date();
+                    if (lastStage) {
+                        await transaction.update(this.ctx.service.stage.tableName, {
+                            id: lastStage.id,
+                            cache_time_l: cacheTime,
+                            cache_time_r: cacheTime,
+                        });
                     }
-                    await transaction.insert(this.tableName, auditors);
-                    await transaction.update(this.ctx.service.ledgerHistory.tableName, { id: revise.his_id, valid: 0 });
+                    // 重算台账、计量、工程变更
+                    const reviseCalc = new RevisePrice(this.ctx);
+                    const pcTp = await reviseCalc.calcRevise(revise, transaction);
+                    const sum = revise.sum || await this.ctx.service.reviseBills.addUp({
+                        tender_id: revise.tid, /* , is_leaf: true*/
+                    });
+                    await transaction.update(this.ctx.service.tender.tableName, {
+                        id: revise.tid,
+                        total_price: sum.total_price, deal_tp: sum.deal_tp,
+                    });
+                    // 修订附件取消修订中标记
+                    await transaction.update(this.ctx.service.ledgerAtt.tableName, { revising: 0 }, { where: { revise_id: revise.id } });
+                    // 更新台账缓存
+                    await this.ctx.service.tenderCache.updateStageCache4Revise(transaction, revise.tid, sum, pcTp);
+                    // 清除变更新增部位maxLid缓存,防止树结构混乱
+                    await this.ctx.service.changeLedger._removeCacheMaxLid(audit.tender_id);
 
-                    // 短信通知-审批退回提醒功能
-                    // 下一人
-                    // await this.ctx.helper.sendUserSms(revise.uid, smsTypeConst.const.XD,
-                    //     smsTypeConst.judge.result.toString(), '台账修订审批退回,请登录系统处理。');
+                    // 短信通知-审批通过提醒功能
                     await this.ctx.helper.sendAliSms(revise.uid, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result, {
-                        status: SmsAliConst.status.back,
+                        status: SmsAliConst.status.success,
+                    });
+                    const users = this._.pull(this._.map(auditList, 'audit_id'), revise.uid);
+                    await this.ctx.helper.sendAliSms(users, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result2, {
+                        status: SmsAliConst.status.success,
                     });
+                    users.push(revise.uid);
                     // 微信模板通知
                     const shenpiUrl = await this.ctx.helper.urlToShort(
                         this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + revise.tid + '/revise/' + revise.id + '/info'
                     );
-                    const wechatData = {
-                        wap_url: shenpiUrl,
-                        status: wxConst.status.back,
-                        tips: wxConst.tips.back,
-                        begin_time: Date.parse(revise.begin_time),
-                        code: this.ctx.session.sessionProject.code,
-                    };
-                    await this.ctx.helper.sendWechat(revise.uid, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), wxConst.template.revise, wechatData);
-                    // 其他参与人
-                    // await this.ctx.helper.sendUserSms(this._.map(auditors, 'user_id'),
-                    //     smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), '台账修订审批退回。');
-                    await this.ctx.helper.sendAliSms(
-                        this._.map(auditors, 'user_id'),
-                        smsTypeConst.const.XD,
-                        smsTypeConst.judge.result.toString(),
-                        SmsAliConst.template.revise_result2,
-                        {
-                            status: SmsAliConst.status.back,
-                        }
-                    );
-                    // 微信模板通知
                     const wechatData2 = {
                         wap_url: shenpiUrl,
-                        status: wxConst.status.back,
-                        tips: wxConst.tips.back,
+                        status: wxConst.status.success,
+                        tips: wxConst.tips.success,
                         begin_time: Date.parse(revise.begin_time),
                         code: this.ctx.session.sessionProject.code,
                     };
-                    await this.ctx.helper.sendWechat(this._.map(auditors, 'user_id'), smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), wxConst.template.revise, wechatData2);
+                    await this.ctx.helper.sendWechat(users, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), wxConst.template.revise, wechatData2);
+                    // 审批通过 - 检查三方特殊推送
+                    await this.ctx.service.specMsg.addReviseMsg(transaction, pid, revise, pushOperate.ledger.checked);
                 }
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async _checkNo(revise, opinion, times) {
+            const accountId = this.ctx.session.sessionUser.accountId;
+            const pid = this.ctx.session.sessionProject.id;
+            let audits = await this.getAllDataByCondition({ where: { rid: revise.id, times, status: auditConst.status.checking } });
+            const selfAuditor = audits.find(x => { return x.audit_id === accountId; });
+            if (!selfAuditor) throw '当前标段您无权审批';
+
+            const beginAuditors = await this.getAllDataByCondition({ where: { rid: revise.id, audit_order: 1, times } });
+            const time = new Date();
+
+            const noticeContent = await this.getNoticeContent(pid, revise.tid, revise.id, accountId, opinion);
+            const defaultNoticeRecord = { pid, type: pushType.revise, status: auditConst.status.checkNo, content: noticeContent };
+
+            // 整理当前流程审核人状态更新 warning(在try catch中写,会报auditors未初始化)
+            const updateAuditData = audits.map(x => {
+                return {
+                    id: x.id,
+                    status: x.audit_id === selfAuditor.audit_id ? auditConst.status.checkNo : auditConst.status.checkSkip,
+                    opinion: x.audit_id === selfAuditor.aid ? opinion : '',
+                    end_time: x.audit_id === selfAuditor.aid ? time : null,
+                };
+            });
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                const auditList = await this.getAuditors(revise.id, times);
+                // 审核通过添加到推送表
+                const records = [{ uid: revise.uid, ...defaultNoticeRecord }];
+                auditList.forEach(audit => {
+                    records.push({ uid: audit.audit_id, ...defaultNoticeRecord });
+                });
+                await transaction.insert('zh_notice', records);
+
+                // 更新当前审核流程
+                await transaction.updateRows(this.tableName, updateAuditData);
+                await this.ctx.service.noticeAgain.stopNoticeAgain(transaction, this.tableName, this._.map(audits, 'id'));
+                // 同步修订信息
+                await transaction.update(this.ctx.service.ledgerRevise.tableName, {
+                    id: revise.id, times: times + 1, status: auditConst.status.checkNo,
+                });
+                // 拷贝新一次审核流程列表
+                const orgAuditors = await this.getAllDataByCondition({
+                    where: { rid: revise.id, times },
+                    columns: ['tender_id', 'rid', 'audit_order', 'audit_id', 'audit_type', 'audit_ledger_id'],
+                });
+                const insertAuditors = orgAuditors.map(x => {
+                    return { ...x, times: times + 1, status: auditConst.status.uncheck, in_time: time };
+                });
+                await transaction.insert(this.tableName, insertAuditors);
+                await transaction.update(this.ctx.service.ledgerHistory.tableName, { id: revise.his_id, valid: 0 });
+
+                // 短信通知-审批退回提醒功能
+                await this.ctx.helper.sendAliSms(revise.uid, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result, {
+                    status: SmsAliConst.status.back,
+                });
+                // 微信模板通知
+                const shenpiUrl = await this.ctx.helper.urlToShort(
+                    this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + revise.tid + '/revise/' + revise.id + '/info'
+                );
+                const wechatData = {
+                    wap_url: shenpiUrl,
+                    status: wxConst.status.back,
+                    tips: wxConst.tips.back,
+                    begin_time: Date.parse(revise.begin_time),
+                    code: this.ctx.session.sessionProject.code,
+                };
+                await this.ctx.helper.sendWechat(revise.uid, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), wxConst.template.revise, wechatData);
+                // 其他参与人
+                const users = this._.map(orgAuditors.filter(x => { return x.audit_id !== accountId; }), 'audit_id');
+                await this.ctx.helper.sendAliSms(users, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result2, {status: SmsAliConst.status.back});
+                // 微信模板通知
+                const wechatData2 = {
+                    wap_url: shenpiUrl,
+                    status: wxConst.status.back,
+                    tips: wxConst.tips.back,
+                    begin_time: Date.parse(revise.begin_time),
+                    code: this.ctx.session.sessionProject.code,
+                };
+                await this.ctx.helper.sendWechat(users, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), wxConst.template.revise, wechatData2);
 
                 await transaction.commit();
             } catch (err) {
@@ -591,6 +793,22 @@ module.exports = app => {
         }
 
         /**
+         * 审批
+         * @param {Object} revise - 修订
+         * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
+         * @param {String} opinion - 审批意见
+         * @param {Number} times - 第几次审批
+         * @return {Promise<void>}
+         */
+        async check(revise, checkType, opinion, times = 1) {
+            switch (checkType) {
+                case auditConst.status.checked: await this._checked(revise, opinion, times); break;
+                case auditConst.status.checkNo: await this._checkNo(revise, opinion, times); break;
+                default: throw '提交数据错误';
+            }
+        }
+
+        /**
          * 取待审批修订列表(wap用)
          *
          * @param auditorId
@@ -808,20 +1026,19 @@ module.exports = app => {
             return this.db.query(sql, sqlParam);
         }
 
-        async updateNewAuditList(revise, newIdList) {
+        async updateNewAuditList(revise, newList) {
             const transaction = await this.db.beginTransaction();
             try {
                 // 先删除旧的审批流,再添加新的
                 await transaction.delete(this.tableName, { rid: revise.id, times: revise.times });
                 const newAuditors = [];
-                let order = 1;
-                for (const aid of newIdList) {
+                const in_time = new Date();
+                for (const auditor of newList) {
                     newAuditors.push({
-                        tender_id: revise.tid, audit_id: aid,
-                        times: revise.times, audit_order: order, status: auditConst.status.uncheck,
-                        rid: revise.id,in_time: new Date(),
-                    });
-                    order++;
+                        tender_id: revise.tid, audit_id: auditor.audit_id, rid: revise.id, in_time,
+                        times: revise.times, audit_order: auditor.audit_order, status: auditConst.status.uncheck,
+                        audit_type: auditor.audit_type, audit_ledger_id: auditor.audit_type === auditType.key.union ? auditor.audit_ledger_id : '',
+                    })
                 }
                 if(newAuditors.length > 0) await transaction.insert(this.tableName, newAuditors);
                 await transaction.commit();
@@ -835,14 +1052,22 @@ module.exports = app => {
             const transaction = await this.db.beginTransaction();
             try {
                 // 先判断auditList里的aid是否与lastId相同,相同则删除并重新更新order
-                const idList = this._.map(auditList, 'audit_id');
-                let order = idList.length + 1;
-                if (idList.indexOf(lastId) !== -1) {
-                    await transaction.delete(this.tableName, { rid: revise.id, times: revise.times, audit_id: lastId });
-                    const audit = this._.find(auditList, { 'audit_id': lastId });
-                    // 顺移之后审核人流程顺序
-                    await this._syncOrderByDelete(transaction, revise.id, audit.audit_order, revise.times);
-                    order = order - 1;
+                const existAudit = auditList.find(x => { return x.aid === lastId });
+                let order = auditList.length > 0 ? auditList.reduce((rst, a) => { return Math.max(rst, a.audit_order)}, 0) + 1 : 1; // 最大值 + 1
+                if (existAudit) {
+                    await transaction.delete(this.tableName, { sid: stage.id, times: stage.times, audit_id: lastId });
+                    const sameOrder = auditList.filter(x => { return x.audit_order === existAudit.audit_order });
+                    if (sameOrder.length === 1) {
+                        const updateData = [];
+                        auditList.forEach(x => {
+                            if (x.audit_order <= existAudit.audit_order) return;
+                            updateData.push({id: x.id, audit_order: x.audit_order - 1});
+                        });
+                        if (updateData.length > 0) {
+                            await transaction.updateRows(updateData);
+                        }
+                        order = order - 1;
+                    }
                 }
 
                 // 添加终审

+ 1 - 1
app/service/settle_audit.js

@@ -437,7 +437,7 @@ module.exports = app => {
                 });
                 await this.ctx.service.noticeAgain.stopNoticeAgain(transaction, this.tableName, selfAudit.id);
                 let auditStatus;
-                if (settle.curAuditors.length === 1 || selfAudit.audit_type !== auditType.key.and) {
+                if (settle.curAuditors.length === 1 || selfAudit.audit_type === auditType.key.or) {
                     // 或签更新他人审批状态
                     if (selfAudit.audit_type === auditType.key.or) {
                         const updateOther = [];

+ 16 - 0
app/service/shenpi_audit.js

@@ -54,6 +54,13 @@ module.exports = app => {
             return await this.db.queryOne(sql, sqlParam);
         }
 
+        async getUnionAudit(tid, type, audit_order) {
+            const sql = 'SELECT sp.id, sp.audit_id, sp.audit_ledger_id, pa.name, pa.company FROM ?? AS sp LEFT JOIN ?? AS pa ON sp.audit_id = pa.id' +
+                ' WHERE sp.tid = ? AND sp.sp_type = ? AND sp.sp_status = ? AND audit_order = ?';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tid, type, shenpiConst.sp_status.gdspl, audit_order];
+            return await this.db.query(sql, sqlParam);
+        }
+
         async getAuditList(tid, type, status, group_id = 0) {
             const sql = 'SELECT sp.id, sp.audit_id, sp.audit_type, pa.name FROM ?? AS sp LEFT JOIN ?? AS pa ON sp.audit_id = pa.id' +
                 ' WHERE sp.tid = ? AND sp.sp_type = ? AND sp.sp_status = ? AND sp.sp_group = ? ORDER BY sp.audit_order ASC, sp.id ASC';
@@ -480,6 +487,15 @@ module.exports = app => {
                 shenpiList.splice(yBIndex, 1);
             }
         }
+
+        async saveUnionAudit(tid, data) {
+            const orgData = await this.getAllDataByCondition({ where: { id: data.map(x => { return x.id; }) } });
+            for (const od of orgData) {
+                if (od.tid !== tid) throw '保存数据错误,请刷新页面后重试';
+            }
+            const updateData = data.map(x => { return { id: x.id, audit_ledger_id: x.audit_ledger_id }; });
+            if (updateData.length > 0) await this.db.updateRows(this.tableName, updateData);
+        }
     }
 
     return ShenpiAudit;

+ 2 - 1
app/service/stage.js

@@ -89,6 +89,7 @@ module.exports = app => {
                 name: stage.user.name, role: stage.user.role, company: stage.user.company
             }]);
             stage.finalAuditorIds = stage.userGroups[stage.userGroups.length - 1].map(x => { return x.aid; });
+            stage.relaAuditor = stage.auditors.find(x => { return x.aid === accountId });
 
             stage.assists = await this.service.stageAuditAss.getData(stage); // 全部协同人
             stage.assists = stage.assists.filter(x => {
@@ -234,7 +235,7 @@ module.exports = app => {
                     const flowAssists = stage.auditAssists.filter(x => { return stage.flowAuditorIds.indexOf(x.user_id) >= 0; });
                     if (flowAssists.find(x => { return x.confirm; })) return; //当前流程存在协审人确认时,不可撤回
                     if (stage.curAuditorIds.indexOf(accountId) < 0 && stage.flowAuditorIds.indexOf(accountId) >= 0) {
-                        stage.cancancel = 5; // 会签未全部审批通过时,审批人撤回审批通过
+                        stage.cancancel = 5; // 会签/协同 未全部审批通过时,审批人撤回审批通过
                         return;
                     }
 

+ 111 - 19
app/service/stage_audit.js

@@ -79,7 +79,7 @@ module.exports = app => {
 
         async getLastestAuditor(stageId, times, status) {
             const sql =
-                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.audit_type, la.audit_order,' +
+                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.audit_type, la.audit_order, la.audit_ledger_id,' +
                 '    la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
                 '    FROM ' + this.tableName + ' AS la' +
                 '    Left Join ' + this.ctx.service.projectAccount.tableName + ' AS pa ON la.`aid` = pa.`id`' +
@@ -91,7 +91,7 @@ module.exports = app => {
 
         async getLastestAuditors(stageId, times, status) {
             const sql =
-                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.audit_type, la.audit_order,' +
+                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.audit_type, la.audit_order, la.audit_ledger_id,' +
                 '    la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
                 '    FROM ' + this.tableName + ' AS la' +
                 '    Left Join ' + this.ctx.service.projectAccount.tableName + ' AS pa ON la.`aid` = pa.`id`' +
@@ -112,7 +112,7 @@ module.exports = app => {
          * @return {Promise<*>}
          */
         async getAuditors(stageId, times = 1, order_sort = 'asc') {
-            const sql = 'SELECT la.id, la.aid, la.times, la.order, la.status, la.opinion, la.begin_time, la.end_time, la.audit_type, la.audit_order,' +
+            const sql = 'SELECT la.id, la.aid, la.times, la.order, la.status, la.opinion, la.begin_time, la.end_time, la.audit_type, la.audit_order, la.audit_ledger_id,' +
                 '    pa.name, pa.company, pa.role, pa.mobile, pa.telephone, pa.sign_path' +
                 `  FROM ${this.tableName} la LEFT JOIN ${this.ctx.service.projectAccount.tableName} pa ON la.aid = pa.id` +
                 '  WHERE la.sid = ? AND la.times = ?' +
@@ -160,7 +160,7 @@ module.exports = app => {
          */
         async getCurAuditor(stageId, times = 1) {
             const sql =
-                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, la.audit_type, la.audit_order ' +
+                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, la.audit_type, la.audit_order, la.audit_ledger_id ' +
                 '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
                 '  WHERE la.`sid` = ? and la.`status` = ? and la.`times` = ?';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, auditConst.status.checking, times];
@@ -169,7 +169,7 @@ module.exports = app => {
 
         async getCurAuditors(stageId, times = 1) {
             const sql =
-                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, la.audit_type, la.audit_order ' +
+                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, la.audit_type, la.audit_order, la.audit_ledger_id ' +
                 '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
                 '  WHERE la.`sid` = ? and la.`status` = ? and la.`times` = ?';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, auditConst.status.checking, times];
@@ -478,7 +478,7 @@ module.exports = app => {
                     sf_tp: sfPay.tp,
                 };
                 this.ctx.stage.tp_history.push({ times, order: selfAudit.order, ...stageTp });
-                if (audits.length === 1 || selfAudit.audit_type !== auditType.key.and) {
+                if (audits.length === 1 || selfAudit.audit_type === auditType.key.or) {
                     // 或签更新他人审批状态
                     if (selfAudit.audit_type === auditType.key.or) {
                         const updateOther = [];
@@ -653,7 +653,7 @@ module.exports = app => {
                return {
                    aid: x.aid, tid: selfAudit.tid, sid: selfAudit.sid,
                    times: times + 1, order: x.audit_order, status: auditConst.status.uncheck,
-                   audit_type: x.audit_type, audit_order: x.audit_order,
+                   audit_type: x.audit_type, audit_order: x.audit_order, audit_ledger_id: x.audit_ledger_id,
                }
             });
 
@@ -862,6 +862,7 @@ module.exports = app => {
                         times: x.times, order: selfAudit.order + 1,
                         status: auditConst.status.checking, begin_time: time,
                         audit_type: x.audit_type, audit_order: x.audit_order,
+                        audit_ledger_id: x.audit_ledger_id,
                     });
                 });
                 // 获取ids值
@@ -877,6 +878,7 @@ module.exports = app => {
                         times: x.times, order: selfAudit.order + 2,
                         status: auditConst.status.uncheck,
                         audit_type: x.audit_type, audit_order: x.audit_order,
+                        audit_ledger_id: x.audit_ledger_id,
                     });
                 });
                 await transaction.insert(this.tableName, newFlowAuditors);
@@ -1018,7 +1020,7 @@ module.exports = app => {
                         times: x.times, order: x.order + 1,
                         status: !selfAudit || x.aid === selfAudit.aid ? auditConst.status.checkAgain : auditConst.status.checkSkip,
                         begin_time: time, end_time: time, opinion: '',
-                        audit_type: x.audit_type, audit_order: x.audit_order,
+                        audit_type: x.audit_type, audit_order: x.audit_order, audit_ledger_id: x.audit_ledger_id,
                     });
                 });
                 audits.forEach(x => {
@@ -1027,7 +1029,7 @@ module.exports = app => {
                         times: x.times, order: x.order + 2,
                         status: auditConst.status.checking,
                         begin_time: time, end_time: time, opinion: '',
-                        audit_type: x.audit_type, audit_order: x.audit_order,
+                        audit_type: x.audit_type, audit_order: x.audit_order, audit_ledger_id: x.audit_ledger_id,
                     });
                 });
                 await transaction.insert(this.tableName, checkAgainAuditors);
@@ -1239,7 +1241,7 @@ module.exports = app => {
                         times: x.times, order: x.order + 1,
                         status: x.aid === selfAuditor.aid ? auditConst.status.checkCancel : auditConst.status.checkSkip,
                         begin_time: time, end_time: time, opinion: '',
-                        audit_type: x.audit_type, audit_order: x.audit_order,
+                        audit_type: x.audit_type, audit_order: x.audit_order, audit_ledger_id: x.audit_ledger_id,
                     });
                 });
                 stage.preAuditors.forEach(x => {
@@ -1248,7 +1250,7 @@ module.exports = app => {
                         times: x.times, order: x.order + 2,
                         status: auditConst.status.checking,
                         begin_time: time, end_time: time, opinion: '',
-                        audit_type: x.audit_type, audit_order: x.audit_order,
+                        audit_type: x.audit_type, audit_order: x.audit_order, audit_ledger_id: x.audit_ledger_id,
                     });
                 });
                 await transaction.insert(this.tableName, [...checkCancelAuditors, ...checkingAuditors]);
@@ -1338,7 +1340,7 @@ module.exports = app => {
                         times: x.times, order: x.order + 1,
                         status: x.aid === selfAuditor.aid ? auditConst.status.checkCancel : auditConst.status.checkSkip,
                         begin_time: time, end_time: time, opinion: '',
-                        audit_type: x.audit_type, audit_order: x.audit_order,
+                        audit_type: x.audit_type, audit_order: x.audit_order, audit_ledger_id: x.audit_ledger_id,
                     });
                 });
                 await transaction.insert(this.tableName, newAuditors);
@@ -1429,7 +1431,7 @@ module.exports = app => {
                         times: x.times, order: x.order + 1,
                         status: x.aid === selfAuditor.aid ? auditConst.status.checkCancel : auditConst.status.checkSkip,
                         begin_time: time, end_time: time, opinion: '',
-                        audit_type: x.audit_type, audit_order: x.audit_order,
+                        audit_type: x.audit_type, audit_order: x.audit_order, audit_ledger_id: x.audit_ledger_id,
                     });
                 });
                 stage.preAuditors.forEach(x => {
@@ -1438,7 +1440,7 @@ module.exports = app => {
                         times: x.times, order: x.order + 2,
                         status: auditConst.status.checking,
                         begin_time: time, end_time: time, opinion: '',
-                        audit_type: x.audit_type, audit_order: x.audit_order,
+                        audit_type: x.audit_type, audit_order: x.audit_order, audit_ledger_id: x.audit_ledger_id,
                     });
                 });
                 await transaction.insert(this.tableName, [...checkCancelAuditors, ...checkingAuditors]);
@@ -1675,7 +1677,7 @@ module.exports = app => {
             //     '  WHERE la.`sid` = ? and la.`times` = ? and la.`is_old` = 0 GROUP BY la.`aid` ORDER BY la.`order`';
             // const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, times];
             const sql =
-                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order`, la.`status`, la.audit_type, la.audit_order' +
+                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order`, la.`status`, la.audit_type, la.audit_order, la.audit_ledger_id' +
                 ' FROM (SELECT `aid`, max(`order`) `order` FROM ?? WHERE `sid` = ? and `times` = ? and `is_old` = ? GROUP BY aid) sa' +
                 ' LEFT JOIN ?? la ON sa.`aid` = la.`aid` AND sa.`order` = la.`order`' +
                 ' Left JOIN ?? AS pa On la.`aid` = pa.`id` WHERE la.`sid` = ? and la.`times` = ? and la.`is_old` = ? order BY la.`order`';
@@ -1724,6 +1726,7 @@ module.exports = app => {
                     status: auditConst.status.uncheck,
                     audit_type: a.audit_type,
                     audit_order: a.audit_order,
+                    audit_ledger_id: a.audit_ledger_id,
                 };
                 newAuditors.push(na);
             }
@@ -1783,7 +1786,7 @@ module.exports = app => {
                     cur = await this.db.queryOne(`SELECT * From ${this.tableName} where sid = ? AND times = ? AND status = ? ORDER By times DESC, ` + '`order` DESC', [stageId, times, status]);
                     if (!cur) return [];
 
-                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order`, la.audit_order, la.audit_type ' +
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order`, la.audit_order, la.audit_type, la.audit_ledger_id ' +
                         '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
                         '  WHERE la.`sid` = ? and la.`order` = ? and times = ?';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, cur.order, times];
@@ -1793,7 +1796,7 @@ module.exports = app => {
                     cur = await this.db.queryOne(`SELECT * From ${this.tableName} where sid = ? AND times = ? AND status = ? ORDER By times DESC, ` + '`order` DESC', [stageId, parseInt(times) - 1, status]);
                     if (!cur) return [];
 
-                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order`, la.audit_order, la.audit_type ' +
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order`, la.audit_order, la.audit_type, la.audit_ledger_id ' +
                         '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
                         '  WHERE la.`sid` = ? and la.`order` = ? and la.`times` = ?';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, cur.order, parseInt(times) - 1];
@@ -1895,7 +1898,7 @@ module.exports = app => {
                     await this._timesDelete(this.ctx.stage.id, nowTimes, transaction);
                 }
                 // 添加上一次审批人
-                const sql = 'SELECT `tid`, `sid`, `aid`, `order`, `audit_type`, `audit_order` FROM ?? WHERE `sid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
+                const sql = 'SELECT `tid`, `sid`, `aid`, `order`, `audit_type`, `audit_order`, `audit_ledger_id` FROM ?? WHERE `sid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
                 const sqlParam = [this.tableName, this.ctx.stage.id, nowTimes - 1];
                 const auditors = await this.db.query(sql, sqlParam);
                 let order = 1;
@@ -1977,6 +1980,7 @@ module.exports = app => {
                         tid: stage.tid, sid: stage.id, aid: auditor.audit_id,
                         times: stage.times, order: auditor.audit_order, status: auditConst.status.uncheck,
                         audit_type: auditor.audit_type, audit_order: auditor.audit_order,
+                        audit_ledger_id: auditor.audit_type === auditType.key.union ? auditor.audit_ledger_id : '',
                     });
                 }
                 if(newAuditors.length > 0) await transaction.insert(this.tableName, newAuditors);
@@ -2026,7 +2030,7 @@ module.exports = app => {
         async getFinalAuditGroup(stageId, times) {
             const sql =
                 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, pa.`sign_path`,' +
-                '  la.`times`, la.`sid`, la.`audit_order`, la.`audit_type`, Max(la.`order`) as max_order ' +
+                '  la.`times`, la.`sid`, la.`audit_order`, la.`audit_type`, la.`audit_ledger_id`, Max(la.`order`) as max_order ' +
                 '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
                 '  WHERE la.`sid` = ? and la.`times` = ? GROUP BY la.`aid` ORDER BY la.`order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, times];
@@ -2246,6 +2250,94 @@ module.exports = app => {
             });
             return result;
         }
+
+        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, confirm: 1 } });
+            locking.forEach(x => {
+                x.locked_ledger_id = x.ass_ledger_id.split(',');
+            });
+            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, confirm: 1 } });
+            // 前审批人数据,与被锁定数据相关数据确认状态保留
+            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 ? 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, o: uid, confirm: 1 } });
+            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, user_id: keepUid, confirm: 1 } });
+            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 ? 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) {
+            if (transaction) {
+                await transaction.update(this.tableName, { locked: 0 }, { where: { sid: stage.id, times: stage.times, aid: uid } });
+            } else {
+                await this.db.update(this.tableName, { locked: 0 }, { where: { sid: stage.id, times: stage.times, aid: uid } });
+            }
+        }
+
+        async getLockedId(stage) {
+            return await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.times, audit_locked: 1 } });
+        }
     }
 
     return StageAudit;

+ 1 - 1
app/service/stage_audit_ass.js

@@ -52,7 +52,7 @@ module.exports = app => {
             const accountStageAssDatas = [];
             for (const as of accountStageAssData) {
                 const s = this._.find(lastStageList, { tid: as.tid });
-                if (s.id === as.sid) {
+                if (s && s.id === as.sid) {
                     const asaIndex = accountStageAssDatas.findIndex(x => { return x.sid === as.sid; });
                     if (asaIndex === -1 || (asaIndex >= 0 && as.times > accountStageAssDatas[asaIndex].times)) {
                         if (asaIndex >= 0 && as.times > accountStageAssDatas[asaIndex].times) accountStageAssDatas.splice(asaIndex, 1);

+ 13 - 11
app/service/tender_cache.js

@@ -115,7 +115,7 @@ module.exports = app => {
             await transaction.insert(this.tableName, data);
         }
 
-        async updateLedgerCache4Start(transaction, tid, status, audit, tp) {
+        async updateLedgerCache4Start(transaction, tid, status, auditors, tp) {
             const orgCache = await this.getDataById(tid);
             const preInfo = JSON.parse(orgCache.ledger_flow_cur_info);
             preInfo.time = new Date();
@@ -123,12 +123,12 @@ module.exports = app => {
                 id: tid, ledger_status: status,
                 ledger_flow_pre_uid: orgCache.ledger_flow_cur_uid, ledger_flow_pre_info: JSON.stringify(preInfo),
             };
-            if (audit) {
-                const info = await this.ctx.service.projectAccount.getAccountCacheData(audit.audit_id);
-                info.order = audit.audit_order;
-                info.status = status;
-                data.ledger_flow_cur_uid = audit.audit_id;
-                data.ledger_flow_cur_info = JSON.stringify(info);
+            if (auditors.length > 0) {
+                const auditIds = auditors.map(x => { return x.audit_id; });
+                const curInfo = await this.ctx.service.projectAccount.getAccountCacheDatas(auditIds,
+                    {audit_type: auditors[0].audit_type, audit_order: 1, status});
+                data.ledger_flow_cur_uid = auditIds.join(',');
+                data.ledger_flow_cur_info = JSON.stringify(curInfo);
             }
             if (tp) data.ledger_tp = JSON.stringify(tp);
             await transaction.update(this.tableName, data);
@@ -143,10 +143,12 @@ module.exports = app => {
                 id: tid, ledger_status: status,
                 ledger_flow_pre_uid: orgCache.ledger_flow_cur_uid || orgCache.ledger_flow_pre_uid, ledger_flow_pre_info: JSON.stringify(preInfo),
             };
-            if (next) {
-                const info = await this.ctx.service.projectAccount.getAccountCacheData(next.audit_id);
-                info.status = checkType === auditConst.ledger.status.checkNo ? status : auditConst.ledger.status.uncheck;
-                data.ledger_flow_cur_uid = next.audit_id;
+            if (next && next.length > 0) {
+                const auditIds = next.map(x => { return x.audit_id; });
+                const infoStatus = checkType === auditConst.ledger.status.checkNo ? status : auditConst.ledger.status.uncheck;
+                const info = await this.ctx.service.projectAccount.getAccountCacheDatas(auditIds,
+                    {audit_type: next[0].audit_type, audit_order: next[0].audit_order, status: infoStatus});
+                data.ledger_flow_cur_uid = auditIds.join(',');
                 data.ledger_flow_cur_info = JSON.stringify(info);
             } else {
                 data.ledger_flow_cur_uid = 0;

+ 0 - 19
app/view/change/apply_information.ejs

@@ -165,27 +165,8 @@
                                 <th>操作</th>
                             </tr>
                             </thead>
-                            <tbody>
-                            <!--<tr>-->
-                            <!--<td colspan="5"><button type="button" class="btn btn-primary btn-sm"  data-toggle="modal" data-target="#upload-fj">上传附件</button></td>-->
-                            <!--</tr>-->
                             <tbody id="file-content">
                             </tbody>
-                            <!--<tr>-->
-                            <!--<td>1</td>-->
-                            <!--<td>XXX设计图纸</td>-->
-                            <!--<td>仁温书</td>-->
-                            <!--<td>2021-12-09 16:58:47</td>-->
-                            <!--<td><a href="#" class="mr-2"><i class="fa fa-download"></i></a><a href="#" class="text-danger"><i class="fa fa-remove"></i></a></td>-->
-                            <!--</tr>-->
-                            <!--<tr>-->
-                            <!--<td>1</td>-->
-                            <!--<td>XXX资料说明</td>-->
-                            <!--<td>仁温书</td>-->
-                            <!--<td>2021-12-09 16:58:47</td>-->
-                            <!--<td><a href="#" class="mr-2"><i class="fa fa-download"></i></a><a href="#" class="text-danger"><i class="fa fa-remove"></i></a></td>-->
-                            <!--</tr>-->
-                            </tbody>
                         </table>
                     </div>
                 </div>

+ 0 - 19
app/view/change/plan_information.ejs

@@ -162,27 +162,8 @@
                                 <th>操作</th>
                             </tr>
                             </thead>
-                            <tbody>
-                            <!--<tr>-->
-                            <!--<td colspan="5"><button type="button" class="btn btn-primary btn-sm"  data-toggle="modal" data-target="#upload-fj">上传附件</button></td>-->
-                            <!--</tr>-->
                             <tbody id="file-content">
                             </tbody>
-                            <!--<tr>-->
-                            <!--<td>1</td>-->
-                            <!--<td>XXX设计图纸</td>-->
-                            <!--<td>仁温书</td>-->
-                            <!--<td>2021-12-09 16:58:47</td>-->
-                            <!--<td><a href="#" class="mr-2"><i class="fa fa-download"></i></a><a href="#" class="text-danger"><i class="fa fa-remove"></i></a></td>-->
-                            <!--</tr>-->
-                            <!--<tr>-->
-                            <!--<td>1</td>-->
-                            <!--<td>XXX资料说明</td>-->
-                            <!--<td>仁温书</td>-->
-                            <!--<td>2021-12-09 16:58:47</td>-->
-                            <!--<td><a href="#" class="mr-2"><i class="fa fa-download"></i></a><a href="#" class="text-danger"><i class="fa fa-remove"></i></a></td>-->
-                            <!--</tr>-->
-                            </tbody>
                         </table>
                     </div>
                 </div>

+ 0 - 19
app/view/change/project_information.ejs

@@ -137,27 +137,8 @@
                             <th>操作</th>
                         </tr>
                         </thead>
-                        <tbody>
-                        <!--<tr>-->
-                            <!--<td colspan="5"><button type="button" class="btn btn-primary btn-sm"  data-toggle="modal" data-target="#upload-fj">上传附件</button></td>-->
-                        <!--</tr>-->
                         <tbody id="file-content">
                         </tbody>
-                        <!--<tr>-->
-                            <!--<td>1</td>-->
-                            <!--<td>XXX设计图纸</td>-->
-                            <!--<td>仁温书</td>-->
-                            <!--<td>2021-12-09 16:58:47</td>-->
-                            <!--<td><a href="#" class="mr-2"><i class="fa fa-download"></i></a><a href="#" class="text-danger"><i class="fa fa-remove"></i></a></td>-->
-                        <!--</tr>-->
-                        <!--<tr>-->
-                            <!--<td>1</td>-->
-                            <!--<td>XXX资料说明</td>-->
-                            <!--<td>仁温书</td>-->
-                            <!--<td>2021-12-09 16:58:47</td>-->
-                            <!--<td><a href="#" class="mr-2"><i class="fa fa-download"></i></a><a href="#" class="text-danger"><i class="fa fa-remove"></i></a></td>-->
-                        <!--</tr>-->
-                        </tbody>
                     </table>
                 </div>
             </div>

+ 3 - 3
app/view/file/file.ejs

@@ -49,7 +49,7 @@
                             </span>
                             </div>
                         </div>
-                        <table class="table table-bordered">
+                        <table class="table table-hover table-bordered">
                             <thead>
                             <tr class="text-center">
                                 <th width="60px">选择</th>
@@ -73,8 +73,8 @@
                             <div class="input-group input-group-sm pb-1">
                                 <div class="input-group-prepend">
                                     <select class="input-group-text" id="search-filter">
-                                        <option value="cur">当前节点</option>
-                                        <option value="all">全部节点</option>
+                                        <option value="cur">当前分类</option>
+                                        <option value="all">全部分类</option>
                                     </select>
                                 </div>
                                 <input id="search-keyword" type="text" class="form-control" autocomplete="off" placeholder="输入文件名称查找" aria-label="Recipient\'s username" aria-describedby="button-addon2">

+ 1 - 1
app/view/layout/menu.ejs

@@ -11,7 +11,7 @@
             <% } %>
             <% for (const index in ctx.menuList) { %>
             <% if (ctx.menuList[index].display === undefined || !ctx.menuList[index].display) { continue } %>
-            <li <% if(ctx.controllerName === index || (ctx.controllerName === 'list' && index === 'tender')) { %>class="active"<% } %>>
+            <li <% if(ctx.controllerName === index || (ctx.controllerName === 'list' && index === 'tender') || (ctx.menuList[index].url && ctx.menuList[index].url === ctx.menu.url)) { %>class="active"<% } %>>
                 <a href="<%- (index === 'tender' ? ctx.curListUrl : ctx.menuList[index].url) %>" id="<%- 'nav_' + index%>" data-toggle="tooltip" data-placement="right" title="" data-original-title="<%- ctx.menuList[index].name %>">
                     <i class="fa <%- ctx.menuList[index].icon %>"></i>
                     <% if (ctx.menuList[index].caption) { %>

+ 221 - 248
app/view/ledger/audit_modal.ejs

@@ -1,4 +1,4 @@
-<% if (tender.ledger_status === auditConst.status.checking && curAuditor.audit_id === ctx.session.sessionUser.accountId) { %>
+<% if (tender.ledger_status === auditConst.status.checking && tender.curAuditorIds.indexOf(ctx.session.sessionUser.accountId) >= 0) { %>
 <!--审批通过-->
 <div class="modal fade sp-location-list" id="sp-done" data-backdrop="static">
     <div class="modal-dialog modal-lg" role="document">
@@ -12,173 +12,160 @@
                         <% if(tender.ledger_status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) { %>
                             <a class="sp-list-item" href="#sub-sp2" data-toggle="modal" data-target="#sub-sp2" id="hideSp">修改审批流程</a>
                         <% } %>
-                        <div class="card mt-3">
-                            <ul class="list-group list-group-flush auditors-list">
-                                <% auditors.forEach((item, idx) => { %>
+                        <div class="card modal-height-500 mt-3 scroll-y">
+                            <ul class="list-group list-group-flush auditors-list" id="auditors-list">
+                                <% tender.userGroups.forEach((group, idx) => { %>
                                 <% if (idx === 0) { %>
-                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
-                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
-                                    <small class="text-muted"><%- item.role %></small>
-                                    <span class="pull-right">原报</span>
+                                <li class="list-group-item d-flex justify-content-between align-items-center">
+                                    <span class="mr-1"><i class="fa fa fa-play-circle fa-rotate-90"></i></span>
+                                    <span class="text-muted">
+                                        <% for (const u of group) { %>
+                                        <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.audit_id %>"><%- u.name %></small>
+                                        <% } %>
+                                    </span>
+                                    <span class="badge badge-light badge-pill ml-auto"><small>原报</small></span>
                                 </li>
-                                <% } else if(idx === auditors.length -1 && idx !== 0) { %>
-                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
-                                    <i class="fa fa fa-stop-circle"></i> <%- item.name %>
-                                    <small class="text-muted"><%- item.role %></small>
-                                    <span class="pull-right">终审</span>
+                                <% } else if(idx === tender.userGroups.length -1 && idx !== 0) { %>
+                                <li class="list-group-item d-flex justify-content-between align-items-center">
+                                    <span class="mr-1"><i class="fa fa fa-stop-circle"></i></span>
+                                    <span class="text-muted">
+                                        <% for (const u of group) { %>
+                                        <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.audit_id %>"><%- u.name %></small>
+                                        <% } %>
+                                    </span>
+                                    <div class="d-flex ml-auto">
+                                        <% if (group[0].audit_type !== auditType.key.common) { %>
+                                        <span class="badge badge-pill badge-<%-  auditType.info[group[0].audit_type].class %> p-1"><small><%- auditType.info[group[0].audit_type].short %></small></span>
+                                        <% } %>
+                                        <span class="badge badge-light badge-pill"><small>终审</small></span>
+                                    </div>
                                 </li>
                                 <% } else {%>
-                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
-                                    <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
-                                    <small class="text-muted"><%- item.role %></small>
-                                    <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
+                                <li class="list-group-item d-flex justify-content-between align-items-center">
+                                    <span class="mr-1"><i class="fa fa-chevron-circle-down"></i></span>
+                                    <span class="text-muted">
+                                        <% for (const u of group) { %>
+                                        <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.audit_id %>"><%- u.name %></small>
+                                        <% } %>
+                                    </span>
+                                    <div class="d-flex ml-auto">
+                                        <% if (group[0].audit_type !== auditType.key.common) { %>
+                                        <span class="badge badge-pill badge-<%- auditType.info[group[0].audit_type].class %> p-1"><small><%- auditType.info[group[0].audit_type].short %></small></span>
+                                        <% } %>
+                                        <span class="badge badge-light badge-pill"><small><%= ctx.helper.transFormToChinese(idx) %>审</small></span>
+                                    </div>
                                 </li>
                                 <% } %>
                                 <% }) %>
                             </ul>
                         </div>
                     </div>
-                    <div class="col-8 modal-height-500" style="overflow: auto">
-                        <% auditHistory.forEach((auditors, idx) => { %>
+                    <div class="col-8 modal-height-500 scroll-y">
+                        <% tender.auditHistory.forEach((his, idx) => { %>
                             <!-- 展开/收起历史流程 -->
-                        <% if(idx === auditHistory.length - 1 && auditHistory.length !== 1) { %>
+                        <% if(idx === tender.auditHistory.length - 1 && tender.auditHistory.length !== 1) { %>
                             <div class="text-right">
                                 <a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a>
                             </div>
                             <% } %>
-                        <div class="<%- idx < auditHistory.length - 1 ? 'fold-card' : '' %>">
+                        <div class="<%- idx < tender.auditHistory.length - 1 ? 'fold-card' : '' %>">
                             <div class="text-center text-muted"><%- idx+1 %>#</div>
-                            <ul class="timeline-list list-unstyled mt-2 <% if (idx === auditHistory.length - 1) { %>last-auditor-list<% } %>">
-                                <% auditors.forEach((auditor, index) => { %>
+                            <ul class="timeline-list list-unstyled mt-2 <% if (idx === tender.auditHistory.length - 1 && tender.auditHistory.length > 1) { %>last-auditor-list<% } %>">
+                                <% his.forEach((group, index) => { %>
                                 <% if (index === 0) { %>
                                 <li class="timeline-list-item pb-2">
                                     <div class="timeline-item-date">
-                                        <%- ctx.helper.formatDate(auditor.begin_time) %>
+                                        <%- group.beginYear %>
+                                        <span><%- group.beginDate %></span>
+                                        <span><%- group.beginTime %></span>
                                     </div>
                                     <div class="timeline-item-tail"></div>
-                                    <div class="timeline-item-icon bg-success text-light">
-                                        <i class="fa fa-caret-down"></i>
-                                    </div>
+                                    <div class="timeline-item-icon bg-success text-light"><i class="fa fa-caret-down"></i></div>
                                     <div class="timeline-item-content">
-                                        <div class="card">
-                                            <div class="card-body p-3">
-                                                <div class="card-text">
-                                                    <p class="mb-1"><span
-                                                            class="h5"><%- user.name %></span><span
-                                                            class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
-                                                    </p>
-                                                    <p class="text-muted mb-0"><%- user.role %></p>
-                                                </div>
-                                            </div>
+                                        <div class="py-1">
+                                            <span class="text-black-50">原报</span>
+                                            <span class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
                                         </div>
-                                    </div>
-                                </li>
-                                <li class="timeline-list-item pb-2 <% if (auditor.status === auditConst.status.uncheck && idx === auditHistory.length - 1) { %>is_uncheck<% } %>">
-                                    <div class="timeline-item-date">
-                                        <%- ctx.helper.formatDate(auditor.end_time) %>
-                                    </div>
-                                    <% if(index < auditors.length - 1) { %>
-                                    <div class="timeline-item-tail"></div>
-                                    <% } %>
-                                    <% if(auditor.status === auditConst.status.checked) { %>
-                                    <div class="timeline-item-icon bg-success text-light">
-                                        <i class="fa fa-check"></i>
-                                    </div>
-                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
-                                    <div class="timeline-item-icon bg-warning text-light">
-                                        <i class="fa fa-level-up"></i>
-                                    </div>
-                                    <% } else if(auditor.status === auditConst.status.checking) { %>
-                                    <div class="timeline-item-icon bg-warning text-light">
-                                        <i class="fa fa-ellipsis-h"></i>
-                                    </div>
-                                    <% } else {%>
-                                    <div class="timeline-item-icon bg-secondary text-light">
-                                    </div>
-                                    <% } %>
-                                    <div class="timeline-item-content">
                                         <div class="card">
-                                            <div class="card-body p-3">
-                                                <div class="card-text">
-                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
-                                                            class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
-                                                    </p>
-                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
+                                            <div class="card-body px-3 py-0">
+                                                <div class="card-text p-2 py-3 row">
+                                                    <div class="col">
+                                                        <span class="h6"><%- tender.user.name %></span>
+                                                        <span class="text-muted ml-1"><%- tender.user.role %></span>
+                                                    </div>
+                                                    <div class="col">
+                                                        <span class="pull-right text-success"><i class="fa fa-check-circle"></i></span>
+                                                    </div>
                                                 </div>
                                             </div>
-                                            <!--审批意见-->
-                                            <% if(auditor.status !== auditConst.status.uncheck) { %>
-                                            <div class="card-body p-3 border-top">
-                                                <% if (tender.ledger_times === idx + 1 && auditor.status === auditConst.status.checking) { %>
-                                                <label>审批意见<b class="text-danger">*</b></label>
-                                                <textarea class="form-control form-control-sm"
-                                                    name="opinion">同意</textarea>
-                                                <% } else { %>
-                                                <p style="margin: 0;"><%- auditor.opinion %></p>
-                                                <% } %>
-                                            </div>
-                                            <% } %>
                                         </div>
                                     </div>
                                 </li>
-                                <% } else {%>
-                                <li class="timeline-list-item pb-2 <% if (auditor.status === auditConst.status.uncheck && idx === auditHistory.length - 1) { %>is_uncheck<% } %>">
+                                <% } %>
+                                <li class="timeline-list-item pb-2 <% if (group.status === auditConst.status.uncheck && idx === tender.auditHistory.length - 1 && tender.auditHistory.length > 1) { %>is_uncheck<% } %>">
+                                    <% if (group.endYear) { %>
                                     <div class="timeline-item-date">
-                                        <%- ctx.helper.formatDate(auditor.end_time) %>
+                                        <%- group.endYear %>
+                                        <span><%- group.endDate %></span>
+                                        <span><%- group.endTime %></span>
                                     </div>
-                                    <% if(index < auditors.length - 1) { %>
+                                    <% } %>
+                                    <% if (index < his.length - 1) { %>
                                     <div class="timeline-item-tail"></div>
                                     <% } %>
-                                    <% if(auditor.status === auditConst.status.checked) { %>
-                                    <div class="timeline-item-icon bg-success text-light">
-                                        <i class="fa fa-check"></i>
-                                    </div>
-                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
-                                    <div class="timeline-item-icon bg-warning text-light">
-                                        <i class="fa fa-level-up"></i>
-                                    </div>
-                                    <% } else if(auditor.status === auditConst.status.checking) { %>
-                                    <div class="timeline-item-icon bg-warning text-light">
-                                        <i class="fa fa-ellipsis-h"></i>
-                                    </div>
+                                    <% if (group.status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light"><i class="fa fa-check"></i></div>
+                                    <% } else if (group.status === auditConst.status.checkNo || group.status === auditConst.status.checkNoPre || group.status === auditConst.status.checkCancel) { %>
+                                    <div class="timeline-item-icon bg-warning text-light"><i class="fa fa-level-up"></i></div>
+                                    <% } else if (group.status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light"><i class="fa fa-ellipsis-h"></i></div>
                                     <% } else { %>
-                                    <div class="timeline-item-icon bg-secondary text-light">
-                                    </div>
+                                    <div class="timeline-item-icon bg-secondary text-light"></div>
                                     <% } %>
                                     <div class="timeline-item-content">
+                                        <div class="py-1">
+                                                <span class="text-black-50">
+                                                    <%- (!group.is_final ? group.audit_order : '终') %>审
+                                                    <% if (group.audit_type !== auditType.key.common) { %><span class="text-<%- auditType.info[group.audit_type].class %> "><%- auditType.info[group.audit_type].long %></span><% } %>
+                                                </span>
+                                            <% if (group.status !== auditConst.status.uncheck) { %>
+                                            <span class="pull-right <%- auditConst.statusClass[group.status] %>"><%- auditConst.statusString[group.status] %></span>
+                                            <% } %>
+                                        </div>
                                         <div class="card">
-                                            <div class="card-body p-3">
-                                                <div class="card-text">
-                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span>
-                                                        <span class="pull-right <%- auditConst.statusClass[auditor.status] %>">
-                                                            <%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
-                                                            <%- auditor.status === auditConst.status.checkNo ? user.name : '' %>
-                                                            <%- auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : '' %>
-                                                        </span>
-                                                    </p>
-                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
+                                            <div class="card-body px-3 py-0">
+                                                <% for (const [i, auditor] of group.auditors.entries()) { %>
+                                                <div class="card-text p-2 py-3 row <%- ( i > 0 ? 'border-top' : '') %>">
+                                                    <div class="col">
+                                                        <span class="h6"><%- auditor.name %></span>
+                                                        <span class="text-muted ml-1"><%- auditor.role %></span>
+                                                    </div>
+                                                    <div class="col">
+                                                        <% if (auditor.status === auditConst.status.checked) { %>
+                                                        <span class="pull-right text-success"><i class="fa fa-check-circle"></i></span>
+                                                        <% } if (auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre || auditor.status === auditConst.status.checkCancel) { %>
+                                                        <span class="pull-right text-warning"><i class="fa fa-share-square fa-rotate-270"></i></span>
+                                                        <% } else if (auditor.status === auditConst.status.checking) { %>
+                                                        <span class="pull-right text-warning"><i class="fa fa-commenting"></i></span>
+                                                        <% } %>
+                                                    </div>
+                                                    <% if (auditor.status !== auditConst.status.uncheck && auditor.opinion) { %>
+                                                    <div class="col-12 py-1 bg-light"><i class="fa fa-commenting-o mr-1"></i><%- auditor.opinion%></div>
+                                                    <% } %>
+                                                    <% if (auditor.status === auditConst.status.checking && auditor.audit_id === ctx.session.sessionUser.accountId) { %>
+                                                    <div class="col-12 py-1 bg-light">
+                                                        <textarea class="form-control form-control-sm" name="opinion">同意</textarea>
+                                                    </div>
+                                                    <% } %>
                                                 </div>
-                                            </div>
-                                            <!--审批意见-->
-                                            <% if(auditor.status !== auditConst.status.uncheck) { %>
-                                            <div class="card-body p-3 border-top">
-                                                <% if (tender.ledger_times === idx + 1 && auditor.status === auditConst.status.checking) { %>
-                                                <label>审批意见<b class="text-danger">*</b></label>
-                                                <textarea class="form-control form-control-sm"
-                                                    name="opinion">同意</textarea>
-                                                <% } else { %>
-                                                <p style="margin: 0;"><%- auditor.opinion %></p>
                                                 <% } %>
                                             </div>
-                                            <% } %>
                                         </div>
                                     </div>
                                 </li>
-                                <% } %>
                                 <% }) %>
                             </ul>
                         </div>
-
                         <% }) %>
                     </div>
                 </div>
@@ -206,171 +193,157 @@
                         <% if(tender.ledger_status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) { %>
                             <a class="sp-list-item" href="#sub-sp2" data-toggle="modal" data-target="#sub-sp2" id="hideSp">修改审批流程</a>
                         <% } %>
-                        <div class="card mt-3">
-                            <ul class="list-group list-group-flush auditors-list">
-                                <% auditors.forEach((item, idx) => { %>
+                        <div class="card modal-height-500 mt-3 scroll-y" style="overflow: auto">
+                            <ul class="list-group list-group-flush auditors-list" id="auditors-list">
+                                <% tender.userGroups.forEach((item, idx) => { %>
                                 <% if (idx === 0) { %>
-                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
-                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
-                                    <small class="text-muted"><%- item.role %></small>
-                                    <span class="pull-right">原报</span>
+                                <li class="list-group-item d-flex justify-content-between align-items-center">
+                                    <span class="mr-1"><i class="fa fa fa-play-circle fa-rotate-90"></i></span>
+                                    <span class="text-muted">
+                                        <% for (const u of item) { %>
+                                        <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.audit_id %>"><%- u.name %></small>
+                                        <% } %>
+                                    </span>
+                                    <span class="badge badge-light badge-pill ml-auto"><small>原报</small></span>
                                 </li>
-                                <% } else if(idx === auditors.length -1 && idx !== 0) { %>
-                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
-                                    <i class="fa fa fa-stop-circle"></i> <%- item.name %>
-                                    <small class="text-muted"><%- item.role %></small>
-                                    <span class="pull-right">终审</span>
+                                <% } else if(idx === tender.userGroups.length -1 && idx !== 0) { %>
+                                <li class="list-group-item d-flex justify-content-between align-items-center">
+                                    <span class="mr-1"><i class="fa fa fa-stop-circle"></i></span>
+                                    <span class="text-muted">
+                                        <% for (const u of item) { %>
+                                        <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.audit_id %>"><%- u.name %></small>
+                                        <% } %>
+                                    </span>
+                                    <div class="d-flex ml-auto">
+                                        <% if (item[0].audit_type !== auditType.key.common) { %>
+                                        <span class="badge badge-pill badge-<%-  auditType.info[item[0].audit_type].class %> p-1"><small><%- auditType.info[item[0].audit_type].short %></small></span>
+                                        <% } %>
+                                        <span class="badge badge-light badge-pill"><small>终审</small></span>
+                                    </div>
                                 </li>
                                 <% } else {%>
-                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
-                                    <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
-                                    <small class="text-muted"><%- item.role %></small>
-                                    <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
+                                <li class="list-group-item d-flex justify-content-between align-items-center">
+                                    <span class="mr-1"><i class="fa fa-chevron-circle-down"></i></span>
+                                    <span class="text-muted">
+                                        <% for (const u of item) { %>
+                                        <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.audit_id %>"><%- u.name %></small>
+                                        <% } %>
+                                    </span>
+                                    <div class="d-flex ml-auto">
+                                        <% if (item[0].audit_type !== auditType.key.common) { %>
+                                        <span class="badge badge-pill badge-<%- auditType.info[item[0].audit_type].class %> p-1"><small><%- auditType.info[item[0].audit_type].short %></small></span>
+                                        <% } %>
+                                        <span class="badge badge-light badge-pill"><small><%= ctx.helper.transFormToChinese(idx) %>审</small></span>
+                                    </div>
                                 </li>
                                 <% } %>
                                 <% }) %>
                             </ul>
                         </div>
                     </div>
+
                     <div class="col-8 modal-height-500" style="overflow: auto">
-                        <% auditHistory.forEach((auditors, idx) => { %>
-                            <!-- 展开/收起历史流程 -->
-                        <% if(idx === auditHistory.length - 1 && auditHistory.length !== 1) { %>
-                            <div class="text-right">
-                                <a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a>
-                            </div>
-                            <% } %>
-                        <div class="<%- idx < auditHistory.length - 1 ? 'fold-card' : '' %>">
+                        <% tender.auditHistory.forEach((his, idx) => { %>
+                        <!-- 展开/收起历史流程 -->
+                        <% if(idx === tender.auditHistory.length - 1 && tender.auditHistory.length !== 1) { %>
+                        <div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
+                                                   data-idx="<%- idx + 1 %>">展开历史审批流程</a></div>
+                        <% } %>
+                        <div class="<%- idx < tender.auditHistory.length - 1 ? 'fold-card' : '' %>">
                             <div class="text-center text-muted"><%- idx+1 %>#</div>
-                            <ul class="timeline-list list-unstyled mt-2 <% if (idx === auditHistory.length - 1) { %>last-auditor-list<% } %>">
-                                <% auditors.forEach((auditor, index) => { %>
+                            <ul class="timeline-list list-unstyled mt-2 <% if (idx === tender.auditHistory.length - 1 && tender.auditHistory.length !== 1) { %>last-auditor-list<% } %>">
+                                <% his.forEach((group, index) => { %>
                                 <% if (index === 0) { %>
                                 <li class="timeline-list-item pb-2">
                                     <div class="timeline-item-date">
-                                        <%- ctx.helper.formatDate(auditor.begin_time) %>
+                                        <%- group.beginYear %>
+                                        <span><%- group.beginDate %></span>
+                                        <span><%- group.beginTime %></span>
                                     </div>
                                     <div class="timeline-item-tail"></div>
-                                    <div class="timeline-item-icon bg-success text-light">
-                                        <i class="fa fa-caret-down"></i>
-                                    </div>
+                                    <div class="timeline-item-icon bg-success text-light"><i class="fa fa-caret-down"></i></div>
                                     <div class="timeline-item-content">
-                                        <div class="card">
-                                            <div class="card-body p-3">
-                                                <div class="card-text">
-                                                    <p class="mb-1"><span
-                                                            class="h5"><%- user.name %></span><span
-                                                            class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
-                                                    </p>
-                                                    <p class="text-muted mb-0"><%- user.role %></p>
-                                                </div>
-                                            </div>
+                                        <div class="py-1">
+                                            <span class="text-black-50">原报</span>
+                                            <span class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
                                         </div>
-                                    </div>
-                                </li>
-                                <li class="timeline-list-item pb-2 <% if (auditor.status === auditConst.status.uncheck && idx === auditHistory.length - 1) { %>is_uncheck<% } %>">
-                                    <div class="timeline-item-date">
-                                        <%- ctx.helper.formatDate(auditor.end_time) %>
-                                    </div>
-                                    <% if(index < auditors.length - 1) { %>
-                                    <div class="timeline-item-tail"></div>
-                                    <% } %>
-                                    <% if(auditor.status === auditConst.status.checked) { %>
-                                    <div class="timeline-item-icon bg-success text-light">
-                                        <i class="fa fa-check"></i>
-                                    </div>
-                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
-                                    <div class="timeline-item-icon bg-warning text-light">
-                                        <i class="fa fa-level-up"></i>
-                                    </div>
-                                    <% } else if(auditor.status === auditConst.status.checking) { %>
-                                    <div class="timeline-item-icon bg-warning text-light">
-                                        <i class="fa fa-ellipsis-h"></i>
-                                    </div>
-                                    <% } else {%>
-                                    <div class="timeline-item-icon bg-secondary text-light">
-                                    </div>
-                                    <% } %>
-                                    <div class="timeline-item-content">
                                         <div class="card">
-                                            <div class="card-body p-3">
-                                                <div class="card-text">
-                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
-                                                            class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
-                                                    </p>
-                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
+                                            <div class="card-body px-3 py-0">
+                                                <div class="card-text p-2 py-3 row">
+                                                    <div class="col">
+                                                        <span class="h6"><%- tender.user.name %></span>
+                                                        <span class="text-muted ml-1"><%- tender.user.role %></span>
+                                                    </div>
+                                                    <div class="col">
+                                                        <span class="pull-right text-success"><i class="fa fa-check-circle"></i></span>
+                                                    </div>
                                                 </div>
                                             </div>
-
-                                            <!--审批意见-->
-                                            <% if(auditor.times === tender.ledger_times && auditor.status !== auditConst.status.uncheck) { %>
-                                            <div class="card-body p-3 border-top">
-                                                <% if (tender.ledger_times === idx + 1 && auditor.status === auditConst.status.checking) { %>
-                                                <label>审批意见<b class="text-danger">*</b></label>
-                                                <textarea class="form-control form-control-sm"
-                                                    name="opinion">不同意</textarea>
-                                                <% } else if(auditor.status === auditConst.status.checked){ %>
-                                                <p style="margin: 0;"><%- auditor.opinion %></p>
-                                                <% } %>
-                                            </div>
-                                            <% } %>
                                         </div>
                                     </div>
                                 </li>
-                                <% } else {%>
-                                <li class="timeline-list-item pb-2 <% if (auditor.status === auditConst.status.uncheck && idx === auditHistory.length - 1) { %>is_uncheck<% } %>">
+                                <% } %>
+                                <li class="timeline-list-item pb-2 <% if (group.status === auditConst.status.uncheck && idx === tender.auditHistory.length - 1 && tender.auditHistory.length !== 1) { %>is_uncheck<% } %>">
+                                    <% if (his.endYear) { %>
                                     <div class="timeline-item-date">
-                                        <%- ctx.helper.formatDate(auditor.end_time) %>
+                                        <%- group.endYear %>
+                                        <span><%- group.endDate %></span>
+                                        <span><%- group.endTime %></span>
                                     </div>
-                                    <% if(index < auditors.length - 1) { %>
+                                    <% } %>
+                                    <% if (index < his.length - 1) { %>
                                     <div class="timeline-item-tail"></div>
                                     <% } %>
-                                    <% if(auditor.status === auditConst.status.checked) { %>
-                                    <div class="timeline-item-icon bg-success text-light">
-                                        <i class="fa fa-check"></i>
-                                    </div>
-                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
-                                    <div class="timeline-item-icon bg-warning text-light">
-                                        <i class="fa fa-level-up"></i>
-                                    </div>
-                                    <% } else if(auditor.status === auditConst.status.checking) { %>
-                                    <div class="timeline-item-icon bg-warning text-light">
-                                        <i class="fa fa-ellipsis-h"></i>
-                                    </div>
+                                    <% if (group.status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light"><i class="fa fa-check"></i></div>
+                                    <% } else if (group.status === auditConst.status.checkNo || group.status === auditConst.status.checkNoPre || group.status === auditConst.status.checkCancel) { %>
+                                    <div class="timeline-item-icon bg-warning text-light"><i class="fa fa-level-up"></i></div>
+                                    <% } else if (group.status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light"><i class="fa fa-ellipsis-h"></i></div>
                                     <% } else { %>
-                                    <div class="timeline-item-icon bg-secondary text-light">
-                                    </div>
+                                    <div class="timeline-item-icon bg-secondary text-light"></div>
                                     <% } %>
                                     <div class="timeline-item-content">
+                                        <div class="py-1">
+                                                <span class="text-black-50">
+                                                    <%- (!group.is_final ? group.audit_order : '终') %>审
+                                                    <% if (group.audit_type !== auditType.key.common) { %><span class="text-<%- auditType.info[group.audit_type].class %> "><%- auditType.info[group.audit_type].long %></span><% } %>
+                                                </span>
+                                            <% if (group.status !== auditConst.status.uncheck) { %>
+                                            <span class="pull-right <%- auditConst.statusClass[group.status] %>"><%- auditConst.statusString[group.status] %></span>
+                                            <% } %>
+                                        </div>
                                         <div class="card">
-                                            <div class="card-body p-3">
-                                                <div class="card-text">
-                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span>
-                                                        <span
-                                                            class="pull-right
-                                                                            <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
-                                                            <%- auditor.status === auditConst.status.checkNo ? user.name : '' %>
-                                                            <%- auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : '' %>
-                                                        </span>
-                                                    </p>
-                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
+                                            <div class="card-body px-3 py-0">
+                                                <% for (const [i, auditor] of group.auditors.entries()) { %>
+                                                <div class="card-text p-2 py-3 row <%- ( i > 0 ? 'border-top' : '') %>">
+                                                    <div class="col">
+                                                        <span class="h6"><%- auditor.name %></span>
+                                                        <span class="text-muted ml-1"><%- auditor.role %></span>
+                                                    </div>
+                                                    <div class="col">
+                                                        <% if (auditor.status === auditConst.status.checked) { %>
+                                                        <span class="pull-right text-success"><i class="fa fa-check-circle"></i></span>
+                                                        <% } if (auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre || auditor.status === auditConst.status.checkCancel) { %>
+                                                        <span class="pull-right text-warning"><i class="fa fa-share-square fa-rotate-270"></i></span>
+                                                        <% } else if (auditor.status === auditConst.status.checking) { %>
+                                                        <span class="pull-right text-warning"><i class="fa fa-commenting"></i></span>
+                                                        <% } %>
+                                                    </div>
+                                                    <% if (auditor.status !== auditConst.status.uncheck && auditor.opinion) { %>
+                                                    <div class="col-12 py-1 bg-light"><i class="fa fa-commenting-o mr-1"></i><%- auditor.opinion%></div>
+                                                    <% } %>
+                                                    <% if (auditor.status === auditConst.status.checking && auditor.audit_id === ctx.session.sessionUser.accountId) { %>
+                                                    <div class="col-12 py-1 bg-light">
+                                                        <textarea class="form-control form-control-sm" name="opinion">不同意</textarea>
+                                                    </div>
+                                                    <% } %>
                                                 </div>
-                                            </div>
-                                            <!--审批意见-->
-                                            <% if(auditor.times === tender.ledger_times && auditor.status !== auditConst.status.uncheck) { %>
-                                            <div class="card-body p-3 border-top">
-                                                <% if (tender.ledger_times === idx + 1 && auditor.status === auditConst.status.checking) { %>
-                                                <label>审批意见<b class="text-danger">*</b></label>
-                                                <textarea class="form-control form-control-sm"
-                                                    name="opinion">不同意</textarea>
-                                                <% } else { %>
-                                                <p style="margin: 0;"><%- auditor.opinion %></p>
                                                 <% } %>
                                             </div>
-                                            <% } %>
                                         </div>
                                     </div>
                                 </li>
-                                <% } %>
                                 <% }) %>
                             </ul>
                         </div>

+ 3 - 3
app/view/ledger/explode.ejs

@@ -52,7 +52,7 @@
                 <a class="btn btn-sm btn-primary mr-1" id="ledger-check2" href="javascript: void(0);">数据检查</a>
                 <% if (tender.ledger_status === auditConst.status.checkNo) { %>
                     <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-dark sp-list-btn">审批退回</a>
-                <% } else if (tender.ledger_status === auditConst.status.checking && curAuditor.audit_id !== ctx.session.sessionUser.accountId) { %>
+                <% } else if (tender.ledger_status === auditConst.status.checking && tender.curAuditorIds.indexOf(ctx.session.sessionUser.accountId) < 0) { %>
                     <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-dark">审批中</a>
                 <% } else if (tender.ledger_status === auditConst.status.checked) { %>
                     <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm sp-list-btn">审批完成</a>
@@ -65,11 +65,11 @@
                     <% } %>
                 <% } %>
 
-                <% if (tender.ledger_status === auditConst.status.checking && curAuditor.audit_id === ctx.session.sessionUser.accountId) { %>
+                <% if (tender.ledger_status === auditConst.status.checking && tender.curAuditorIds.indexOf(ctx.session.sessionUser.accountId) >= 0) { %>
                 <a href="#sp-done" data-toggle="modal" data-target="#sp-done" class="btn btn-success btn-sm mr-1">审批通过</a>
                 <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm mr-1">审批退回</a>
                 <% }%>
-                <% if (tender.ledger_status === auditConst.status.checked && auditors[auditors.length - 1].audit_id === ctx.session.sessionUser.accountId && !tender.hasStage && !tender.hasRevise) { %>
+                <% if (tender.ledger_status === auditConst.status.checked && tender.finalAuditorIds.indexOf(ctx.session.sessionUser.accountId) >= 0 && !tender.hasStage && !tender.hasRevise) { %>
                 <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-back" class="btn btn-warning btn-sm mr-1">重新审批</a>
                 <% }%>
             </div>

+ 122 - 121
app/view/ledger/explode_modal.ejs

@@ -159,14 +159,26 @@
                     </div>
                     <div class="modal-height-500" style="overflow: auto">
                     <ul class="list-group list-group-flush" id="auditors">
-                        <% for (let i = 0, iLen = auditorList.length; i < iLen; i++) { %>
-                        <li class="list-group-item" auditorId="<%- auditorList[i].audit_id %>">
-                            <% if (ctx.tender.info.shenpi.ledger === shenpiConst.sp_status.sqspr ||
-                                    (ctx.tender.info.shenpi.ledger === shenpiConst.sp_status.gdzs && i+1 !== iLen)) { %>
-                            <a href="javascript: void(0)" class="text-danger pull-right">移除</a>
-                            <% } %>
-                            <span><%- auditorList[i].audit_order %> <%- auditorList[i].name %></span>
-                            <small class="text-muted"><%- auditorList[i].role %></small>
+                        <% for (let i = 0, iLen = tender.auditorGroups.length; i < iLen; i++) { %>
+                        <li class="list-group-item d-flex" auditorId="<%- tender.auditorGroups[i][0].audit_id %>">
+                            <div class="col-auto"><%- i+1 %></div>
+                            <div class="col">
+                                <% for (const auditor of tender.auditorGroups[i]) { %>
+                                <div class="d-inline-block mx-1" auditorId="<%- auditor.audit_id %>">
+                                    <i class="fa fa-user text-muted"></i> <%- auditor.name %> <small class="text-muted"><%- auditor.role %></small>
+                                </div>
+                                <% } %>
+                            </div>
+                            <div class="col-auto">
+                                <% if (tender.auditorGroups[i][0].audit_type !== auditType.key.common) { %>
+                                <span class="badge badge-pill badge-<%- auditType.info[tender.auditorGroups[i][0].audit_type].class %> badge-bg-small"><small><%- auditType.info[tender.auditorGroups[i][0].audit_type].long%></small></span>
+                                <% } %>
+                                <% if (ctx.tender.info.shenpi.ledger === shenpiConst.sp_status.sqspr ||
+                                        (ctx.tender.info.shenpi.ledger === shenpiConst.sp_status.gdzs && i+1 !== iLen)) { %>
+                                <a href="javascript: void(0)" class="text-danger pull-right">移除</a>
+                                <% } %>
+                            </div>
+
                         </li>
                         <% } %>
                     </ul>
@@ -193,32 +205,53 @@
             <div class="modal-body">
                 <div class="row">
                     <div class="col-4">
-                        <% if(tender.ledger_status === auditConst.status.checkNo) { %>
+                        <% if(tender.ledger_status === auditConst.status.checkNo && tender.user_id === ctx.session.sessionUser.accountId) { %>
                             <a class="sp-list-item" href="#sub-sp" data-toggle="modal" data-target="#sub-sp" id="hideSp">修改审批流程</a>
                         <% } else if(tender.ledger_status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) { %>
-                            <a class="sp-list-item" href="#sub-sp2" data-toggle="modal" data-target="#sub-sp2"
-                               id="hideSp">修改审批流程</a>
+                            <a class="sp-list-item" href="#sub-sp2" data-toggle="modal" data-target="#sub-sp2" id="hideSp">修改审批流程</a>
                         <% } %>
-                        <div class="card mt-3">
+                        <div class="card modal-height-500 mt-3" style="overflow: auto">
                             <ul class="list-group list-group-flush auditors-list" id="auditors-list">
-                                <% auditors.forEach((item, idx) => { %>
+                                <% tender.userGroups.forEach((item, idx) => { %>
                                 <% if (idx === 0) { %>
-                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
-                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
-                                    <small class="text-muted"><%- item.role %></small>
-                                    <span class="pull-right">原报</span>
+                                <li class="list-group-item d-flex justify-content-between align-items-center">
+                                    <span class="mr-1"><i class="fa fa fa-play-circle fa-rotate-90"></i></span>
+                                    <span class="text-muted">
+                                        <% for (const u of item) { %>
+                                        <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.audit_id %>"><%- u.name %></small>
+                                        <% } %>
+                                    </span>
+                                    <span class="badge badge-light badge-pill ml-auto"><small>原报</small></span>
                                 </li>
-                                <% } else if(idx === auditors.length -1 && idx !== 0) { %>
-                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
-                                    <i class="fa fa fa-stop-circle"></i> <%- item.name %>
-                                    <small class="text-muted"><%- item.role %></small>
-                                    <span class="pull-right">终审</span>
+                                <% } else if(idx === tender.userGroups.length -1 && idx !== 0) { %>
+                                <li class="list-group-item d-flex justify-content-between align-items-center">
+                                    <span class="mr-1"><i class="fa fa fa-stop-circle"></i></span>
+                                    <span class="text-muted">
+                                        <% for (const u of item) { %>
+                                        <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.audit_id %>"><%- u.name %></small>
+                                        <% } %>
+                                    </span>
+                                    <div class="d-flex ml-auto">
+                                        <% if (item[0].audit_type !== auditType.key.common) { %>
+                                        <span class="badge badge-pill badge-<%-  auditType.info[item[0].audit_type].class %> p-1"><small><%- auditType.info[item[0].audit_type].short %></small></span>
+                                        <% } %>
+                                        <span class="badge badge-light badge-pill"><small>终审</small></span>
+                                    </div>
                                 </li>
                                 <% } else {%>
-                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
-                                    <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
-                                    <small class="text-muted"><%- item.role %></small>
-                                    <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
+                                <li class="list-group-item d-flex justify-content-between align-items-center">
+                                    <span class="mr-1"><i class="fa fa-chevron-circle-down"></i></span>
+                                    <span class="text-muted">
+                                        <% for (const u of item) { %>
+                                        <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.audit_id %>"><%- u.name %></small>
+                                        <% } %>
+                                    </span>
+                                    <div class="d-flex ml-auto">
+                                        <% if (item[0].audit_type !== auditType.key.common) { %>
+                                        <span class="badge badge-pill badge-<%- auditType.info[item[0].audit_type].class %> p-1"><small><%- auditType.info[item[0].audit_type].short %></small></span>
+                                        <% } %>
+                                        <span class="badge badge-light badge-pill"><small><%= ctx.helper.transFormToChinese(idx) %>审</small></span>
+                                    </div>
                                 </li>
                                 <% } %>
                                 <% }) %>
@@ -226,132 +259,101 @@
                         </div>
                     </div>
                     <div class="col-8 modal-height-500" style="overflow: auto">
-                        <% auditHistory.forEach((auditors, idx) => { %>
+                        <% tender.auditHistory.forEach((his, idx) => { %>
                             <!-- 展开/收起历史流程 -->
-                        <% if(idx === auditHistory.length - 1 && auditHistory.length !== 1) { %>
+                        <% if(idx === tender.auditHistory.length - 1 && tender.auditHistory.length !== 1) { %>
                             <div class="text-right">
                                 <a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a>
                             </div>
                         <% } %>
-                        <div class="<%- idx < auditHistory.length - 1 ? 'fold-card' : '' %>">
+                        <div class="<%- idx < tender.auditHistory.length - 1 ? 'fold-card' : '' %>">
                             <div class="text-center text-muted"><%- idx+1 %>#</div>
-                            <ul class="timeline-list list-unstyled mt-2 <% if (idx === auditHistory.length - 1) { %>last-auditor-list<% } %>">
-                                <% auditors.forEach((auditor, index) => { %>
+                            <ul class="timeline-list list-unstyled mt-2 <% if (idx === tender.auditHistory.length - 1) { %>last-auditor-list<% } %>">
+                                <% his.forEach((group, index) => { %>
                                 <% if (index === 0) { %>
                                 <li class="timeline-list-item pb-2">
                                     <div class="timeline-item-date">
-                                        <%- ctx.helper.formatDate(auditor.begin_time) %>
+                                        <%- group.beginYear %>
+                                        <span><%- group.beginDate %></span>
+                                        <span><%- group.beginTime %></span>
                                     </div>
                                     <div class="timeline-item-tail"></div>
-                                    <div class="timeline-item-icon bg-success text-light">
-                                        <i class="fa fa-caret-down"></i>
-                                    </div>
+                                    <div class="timeline-item-icon bg-success text-light"><i class="fa fa-caret-down"></i></div>
                                     <div class="timeline-item-content">
-                                        <div class="card">
-                                            <div class="card-body p-3">
-                                                <div class="card-text">
-                                                    <p class="mb-1"><span
-                                                            class="h5"><%- user.name %></span><span
-                                                            class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
-                                                    </p>
-                                                    <p class="text-muted mb-0"><%- user.role %></p>
-                                                </div>
-                                            </div>
+                                        <div class="py-1">
+                                            <span class="text-black-50">原报</span>
+                                            <span class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
                                         </div>
-                                    </div>
-                                </li>
-                                <li class="timeline-list-item pb-2 <% if (auditor.status === auditConst.status.uncheck && idx === auditHistory.length - 1) { %>is_uncheck<% } %>">
-                                    <div class="timeline-item-date">
-                                        <%- ctx.helper.formatDate(auditor.end_time) %>
-                                    </div>
-                                    <% if(index < auditors.length - 1) { %>
-                                    <div class="timeline-item-tail"></div>
-                                    <% } %>
-                                    <% if(auditor.status === auditConst.status.checked) { %>
-                                    <div class="timeline-item-icon bg-success text-light">
-                                        <i class="fa fa-check"></i>
-                                    </div>
-                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
-                                    <div class="timeline-item-icon bg-warning text-light">
-                                        <i class="fa fa-level-up"></i>
-                                    </div>
-                                    <% } else if(auditor.status === auditConst.status.checking) { %>
-                                    <div class="timeline-item-icon bg-warning text-light">
-                                        <i class="fa fa-ellipsis-h"></i>
-                                    </div>
-                                    <% } else {%>
-                                    <div class="timeline-item-icon bg-secondary text-light">
-                                    </div>
-                                    <% } %>
-                                    <div class="timeline-item-content">
                                         <div class="card">
-                                            <div class="card-body p-3">
-                                                <div class="card-text">
-                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
-                                                            class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
-                                                    </p>
-                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
+                                            <div class="card-body px-3 py-0">
+                                                <div class="card-text p-2 py-3 row">
+                                                    <div class="col">
+                                                        <span class="h6"><%- tender.user.name %></span>
+                                                        <span class="text-muted ml-1"><%- tender.user.role %></span>
+                                                    </div>
+                                                    <div class="col">
+                                                        <span class="pull-right text-success"><i class="fa fa-check-circle"></i></span>
+                                                    </div>
                                                 </div>
                                             </div>
-
-                                            <!--审批意见-->
-                                            <% if (auditor.opinion) { %>
-                                            <div class="card-body p-3 border-top">
-                                                <p style="margin: 0;"><%- auditor.opinion %></p>
-                                            </div>
-                                            <% } %>
                                         </div>
                                     </div>
                                 </li>
-                                <% } else {%>
-                                <li class="timeline-list-item pb-2 <% if (auditor.status === auditConst.status.uncheck && idx === auditHistory.length - 1) { %>is_uncheck<% } %>">
+                                <% } %>
+                                <li class="timeline-list-item pb-2 <% if (group.status === auditConst.status.uncheck && idx === tender.auditHistory.length - 1 && tender.auditHistory.length !== 1) { %>is_uncheck<% } %>">
+                                    <% if (group.endYear) { %>
                                     <div class="timeline-item-date">
-                                        <%- ctx.helper.formatDate(auditor.end_time) %>
+                                        <%- group.endYear %>
+                                        <span><%- group.endDate %></span>
+                                        <span><%- group.endTime %></span>
                                     </div>
-                                    <% if(index < auditors.length - 1) { %>
+                                    <% } %>
+                                    <% if (index < his.length - 1) { %>
                                     <div class="timeline-item-tail"></div>
                                     <% } %>
-                                    <% if(auditor.status === auditConst.status.checked) { %>
-                                    <div class="timeline-item-icon bg-success text-light">
-                                        <i class="fa fa-check"></i>
-                                    </div>
-                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
-                                    <div class="timeline-item-icon bg-warning text-light">
-                                        <i class="fa fa-level-up"></i>
-                                    </div>
-                                    <% } else if(auditor.status === auditConst.status.checking) { %>
-                                    <div class="timeline-item-icon bg-warning text-light">
-                                        <i class="fa fa-ellipsis-h"></i>
-                                    </div>
+                                    <% if (group.status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light"><i class="fa fa-check"></i></div>
+                                    <% } else if (group.status === auditConst.status.checkNo || group.status === auditConst.status.checkNoPre || group.status === auditConst.status.checkCancel) { %>
+                                    <div class="timeline-item-icon bg-warning text-light"><i class="fa fa-level-up"></i></div>
+                                    <% } else if (group.status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light"><i class="fa fa-ellipsis-h"></i></div>
                                     <% } else { %>
-                                    <div class="timeline-item-icon bg-secondary text-light">
-                                    </div>
+                                    <div class="timeline-item-icon bg-secondary text-light"></div>
                                     <% } %>
                                     <div class="timeline-item-content">
-                                        <div class="card">
-                                            <div class="card-body p-3">
-                                                <div class="card-text">
-                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span>
-                                                        <span
-                                                            class="pull-right
-                                                                            <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
-                                                            <%- auditor.status === auditConst.status.checkNo ? user.name : '' %>
-                                                            <%- auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : '' %>
+                                        <div class="py-1">
+                                                        <span class="text-black-50">
+                                                            <%- (!group.is_final ? group.audit_order : '终') %>审
+                                                            <% if (group.audit_type !== auditType.key.common) { %><span class="text-<%- auditType.info[group.audit_type].class %> "><%- auditType.info[group.audit_type].long %></span><% } %>
                                                         </span>
-                                                    </p>
-                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
+                                            <% if (group.status !== auditConst.status.uncheck) { %>
+                                            <span class="pull-right <%- auditConst.statusClass[group.status] %>"><%- auditConst.statusString[group.status] %></span>
+                                            <% } %>
+                                        </div>
+                                        <div class="card">
+                                            <div class="card-body px-3 py-0">
+                                                <% for (const [i, auditor] of group.auditors.entries()) { %>
+                                                <div class="card-text p-2 py-3 row <%- ( i > 0 ? 'border-top' : '') %>">
+                                                    <div class="col">
+                                                        <span class="h6"><%- auditor.name %></span>
+                                                        <span class="text-muted ml-1"><%- auditor.role %></span>
+                                                    </div>
+                                                    <div class="col">
+                                                        <% if (auditor.status === auditConst.status.checked) { %>
+                                                        <span class="pull-right text-success"><i class="fa fa-check-circle"></i></span>
+                                                        <% } else if (auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre || auditor.status === auditConst.status.checkCancel) { %>
+                                                        <span class="pull-right text-warning"><i class="fa fa-share-square fa-rotate-270"></i></span>
+                                                        <% } %>
+                                                    </div>
+                                                    <% if (auditor.opinion) { %>
+                                                    <div class="col-12 py-1 bg-light"><i class="fa fa-commenting-o mr-1"></i><%- auditor.opinion%></div>
+                                                    <% } %>
                                                 </div>
+                                                <% } %>
                                             </div>
-                                            <!--审批意见-->
-                                            <% if (auditor.opinion) { %>
-                                            <div class="card-body p-3 border-top">
-                                                <p style="margin: 0;"><%- auditor.opinion %></p>
-                                            </div>
-                                            <% } %>
                                         </div>
                                     </div>
                                 </li>
-                                <% } %>
                                 <% }) %>
                             </ul>
                         </div>
@@ -472,7 +474,6 @@
 </div>
 <% } %>
 
-<% console.log(ctx.session.sessionUser.accountId, ctx.tender.data.user_id, ctx.session.sessionUser.is_admin) %>
 <% if ((ctx.session.sessionUser.accountId === ctx.tender.data.user_id || ctx.session.sessionUser.is_admin) && (ctx.tender.data.ledger_status === auditConst.status.uncheck || ctx.tender.data.ledger_status === auditConst.status.checkNo)) { %>
     <script>
         const shenpi_status = <%- ctx.tender.info.shenpi.ledger %>;

+ 1 - 1
app/view/revise/info.ejs

@@ -58,7 +58,7 @@
                 <% } %>
                 <% if (revise.status === audit.status.checking) { %>
                 <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-dark mr-1">审批中</a>
-                    <% if (curAuditor.audit_id === ctx.session.sessionUser.accountId) { %>
+                    <% if (revise.curAuditorIds.indexOf(ctx.session.sessionUser.accountId) >= 0) { %>
                     <a href="#sp-done" data-toggle="modal" data-target="#sp-done" class="btn btn-success btn-sm mr-1">审批通过</a>
                     <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm mr-1">审批退回</a>
                     <% } %>

文件差異過大導致無法顯示
+ 403 - 485
app/view/revise/info_modal.ejs


文件差異過大導致無法顯示
+ 123 - 123
app/view/tender/detail.ejs


+ 1 - 1
app/view/tender/modal.ejs

@@ -446,7 +446,7 @@
                 auditConst2 = JSON.parse('<%- JSON.stringify(auditConst.material) %>');
             }
             postData(url, data, function (result) {
-                if (type === 'stage') {
+                if (type === 'stage' || type === 'ledger') {
                     loadStageHistory(result, auditConst2);
                 } else {
                     loadHistory(result, auditConst2);

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

@@ -57,8 +57,9 @@
                                             <span class="col-auto"><%- ctx.helper.transFormToChinese(i+1) %>审</span>
                                             <span class="col-7 spr-span">
                                                 <span class="d-inline-block">
-                                                    <select class="form-control form-control-sm audit-type-key" data-type="<%- auditGroup[0].audit_type %>" <% if (['stage', 'change'].indexOf(sp.code) === -1 ) { %> style="display: none;" <% } %>>
+                                                    <select class="form-control form-control-sm audit-type-key" data-type="<%- auditGroup[0].audit_type %>">
                                                         <% for (const at of auditType.types) { %>
+                                                        <% if (at.valid && at.valid.indexOf(sp.code) < 0) continue; %>
                                                         <option value="<%- at.value %>" <% if (auditGroup[0].audit_type === at.value) { %>selected<%} %>><%- at.name %></option>
                                                         <% } %>
                                                     </select>
@@ -105,6 +106,9 @@
                                                 </div>
                                                 </span>
                                                 <% } %>
+                                                <% if (auditGroup[0].audit_type === auditType.key.union) { %>
+                                                <button class="btn btn-outline-primary btn-sm" sp_type="<%- sp.code %>" audit_order="<%- auditGroup[0].audit_order %>" name="union-set">协同设置</button>
+                                                <% } %>
                                             </span>
                                         </li>
                                         <% } %>

+ 32 - 0
app/view/tender/shenpi_modal.ejs

@@ -166,6 +166,38 @@
         </div>
     </div>
 </div>
+<div class="modal fade" id="union" data-backdrop="static">
+    <div class="modal-dialog modal-xl" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">协同审批</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-6">
+                        <div class="modal-height-500" style="overflow: auto;">
+                            <table class="table table-hover table-bordered">
+                                <thead class="text-center" >
+                                <tr><th>姓名</th><th>单位</th><th>协同处理</th></tr>
+                                </thead>
+                                <tbody id="union_table">
+                                </tbody>
+                            </table>
+                        </div>
+                    </div>
+                    <div class="col-6">
+                        <div class="modal-height-500" style="overflow: auto;" id="union-spread">
+                        </div>
+                    </div>
+                </div>
+            </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" id="union-ok">保存</button>
+            </div>
+        </div>
+    </div>
+</div>
 <!--存为审批组-->
 <div class="modal fade" id="spzsave" data-backdrop="static">
     <div class="modal-dialog" role="document">

+ 18 - 0
sql/update.sql

@@ -445,6 +445,24 @@ ALTER TABLE `zh_budget_std`
 ADD COLUMN `ht_project_template_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '合同-项目合同模版-id列表(‘,’分隔)' AFTER `zb_bills_id`,
 ADD COLUMN `ht_tender_template_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '合同-标段合同模版-id列表(‘,’分隔)' AFTER `ht_project_template_id`;
 
+ALTER TABLE `zh_shenpi_audit`
+ADD COLUMN `audit_ledger_id` varchar(5000) NOT NULL DEFAULT '' COMMENT '审批台账id' AFTER `audit_order`;
+
+ALTER TABLE `zh_stage_audit`
+ADD COLUMN `audit_ledger_id` varchar(5000) NOT NULL DEFAULT '' COMMENT '审批台账id' AFTER `audit_order`,
+ADD COLUMN `audit_locked` tinyint(1) NOT NULL DEFAULT 0  COMMENT '审批锁定(仅协审用)' AFTER `audit_ledger_id`;
+
+ALTER TABLE `zh_ledger_audit`
+ADD COLUMN `audit_type` tinyint(4) NOT NULL DEFAULT 1  COMMENT '审批类型' AFTER `audit_order`,
+ADD COLUMN `audit_ledger_id` varchar(5000) NOT NULL DEFAULT '' COMMENT '审批台账id' AFTER `audit_id`;
+
+ALTER TABLE `zh_revise_audit`
+ADD COLUMN `audit_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '审批类型' AFTER `audit_order`,
+ADD COLUMN `audit_ledger_id` varchar(5000) NOT NULL DEFAULT '' COMMENT '审批台账id' AFTER `audit_type`;
+
+ALTER TABLE `zh_tender_cache`
+MODIFY COLUMN `ledger_flow_cur_uid` varchar(1000) NOT NULL DEFAULT '0' COMMENT '台账-当前流程人id' AFTER `ledger_status`,
+MODIFY COLUMN `ledger_flow_pre_uid` varchar(1000) NOT NULL DEFAULT '0' COMMENT '台账-上一流程人id' AFTER `ledger_flow_cur_info`;
 
 -- update请放在最后