Browse Source

台账修订,审批流程相关

MaiXinRong 5 years ago
parent
commit
ee5e45e77c

+ 8 - 0
app/base/base_controller.js

@@ -139,6 +139,14 @@ class BaseController extends Controller {
         }
     }
 
+    postError(error, msg) {
+        if (error.stack) {
+            this.ctx.session.postError = msg;
+        } else {
+            this.ctx.session.postError = error.toString();
+        }
+    }
+
     ajaxErrorBody(error, msg) {
         if (error.stack) {
             return {err: 2, msg: msg, data: null};

+ 21 - 1
app/const/audit.js

@@ -57,7 +57,27 @@ const revise = (function () {
     statusString[status.checking] = '审批中';
     statusString[status.checked] = '完成';
     statusString[status.checkNo] = '退回';
-    return { status, statusString }
+
+    const statusClass = [];
+    statusClass[status.uncheck] = '';
+    statusClass[status.checking] = '';
+    statusClass[status.checked] = 'text-success';
+    statusClass[status.checkNo] = 'text-warning';
+
+    // 标段概况页
+    // 描述文本
+    const auditString = [];
+    auditString[status.uncheck] = '';
+    auditString[status.checking] = '审批中';
+    auditString[status.checked] = '审批完成';
+    auditString[status.checkNo] = '审批退回';
+    // 文字样式
+    const auditStringClass = [];
+    auditStringClass[status.uncheck] = '';
+    auditStringClass[status.checking] = 'text-warning';
+    auditStringClass[status.checked] = 'text-success';
+    auditStringClass[status.checkNo] = 'text-warning';
+    return { status, statusString, statusClass, auditString, auditStringClass }
 })();
 
 // 期审批流程

+ 5 - 0
app/controller/dashboard_controller.js

@@ -24,23 +24,28 @@ module.exports = app => {
             const auditTenders = await ctx.service.ledgerAudit.getAuditTender(ctx.session.sessionUser.accountId);
             const auditStages = await ctx.service.stageAudit.getAuditStage(ctx.session.sessionUser.accountId);
             const auditChanges = await ctx.service.changeAudit.getAuditChange(ctx.session.sessionUser.accountId);
+            const auditRevise = await ctx.service.reviseAudit.getAuditRevise(ctx.session.sessionUser.accountId);
             const projectAccountInfo = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
             const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
             const lastNotice = pa.last_notice ? pa.last_notice : (pa.last_notice === 0 ? new Date() : new Date(pa.last_login * 1000));
             const noticeLedger = await ctx.service.ledgerAudit.getNoticeTender(ctx.session.sessionProject.id, pa.id, lastNotice);
             const noticeStage = await ctx.service.stageAudit.getNoticeStage(ctx.session.sessionProject.id, pa.id, lastNotice);
             const noticeChange = await ctx.service.changeAudit.getNoticeChange(ctx.session.sessionProject.id, pa.id, lastNotice);
+            const noticeRevise = await ctx.service.reviseAudit.getNoticeRevise(ctx.session.sessionProject.id, pa.id, lastNotice);
             const renderData = {
                 auditTenders,
                 auditStages,
                 auditChanges,
+                auditRevise,
                 role: projectAccountInfo.role,
                 acLedger: auditConst.ledger,
                 acStage: auditConst.stage,
                 acChange: auditConst.flow,
+                acRevise: auditConst.revise,
                 noticeLedger,
                 noticeStage,
                 noticeChange,
+                noticeRevise,
             };
             await this.layout('dashboard/index.ejs', renderData);
             await ctx.service.projectAccount.defaultUpdate({

+ 223 - 77
app/controller/revise_controller.js

@@ -14,6 +14,7 @@ const stdDataAddType = {
     next: 3,
 };
 const audit = require('../const/audit');
+const accountGroup = require('../const/account_group').group;
 const tenderMenu = require('../../config/menu').tenderMenu;
 const measureType = require('../const/tender').measureType;
 const spreadConst = require('../const/spread');
@@ -44,7 +45,7 @@ module.exports = app => {
             const stage  = await ctx.service.stage.getLastestStage(ctx.tender.id, true);
             const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
             return (ctx.tender.data.user_id === ctx.session.sessionUser.accountId) &&
-                (ctx.tender.data.ledger_status === audit.ledger.status.checked) &&
+                (ctx.tender.data.ledger_status === audit.revise.status.checked) &&
                 (!stage || stage.status === audit.stage.status.checked) &&
                 (!revise || !revise.valid || revise.status === audit.revise.status.checked);
         }
@@ -195,10 +196,26 @@ module.exports = app => {
         }
 
         async _getDefaultReviseInfoData(ctx, revise) {
-            const reviseBills = await ctx.service.reviseBills.getData(ctx.tender.id);
-            const revisePos = await ctx.service.revisePos.getData(ctx.tender.id, revise.id);
+            const reviseBills = revise.bills_file
+                ? JSON.parse(await fs.readFileSync(this.ctx.app.baseDir + revise.bills_file, 'utf8'))
+                : await ctx.service.reviseBills.getData(ctx.tender.id);
+
+            const revisePos = revise.pos_file
+                ? JSON.parse(await fs.readFileSync(this.ctx.app.baseDir + revise.pos_file, 'utf8'))
+                : await ctx.service.revisePos.getData(ctx.tender.id);
             const [ledgerSpread, posSpread] = this._getSpreadSetting(revise);
             const [stdBills, stdChapters] = await this.ctx.service.valuation.getValuationStdList(ctx.tender.data.valuation);
+            const curAuditor = await ctx.service.reviseAudit.getCurAuditor(revise.id, revise.times);
+            const auditors = await ctx.service.reviseAudit.getAuditors(revise.id, revise.times);
+            console.log(auditors);
+            const user = await ctx.service.projectAccount.getAccountInfoById(revise.uid);
+            const auditHistory = [];
+            if (revise.times > 1) {
+                for (let i = 1; i < revise.times; i++) {
+                    auditHistory.push(await ctx.service.reviseAudit.getAuditors(revise.id, i));
+                }
+            }
+            console.log(auditHistory);
             return {
                 revise: revise, tender: ctx.tender.data,
                 reviseBills, revisePos, ledgerSpread, posSpread, tenderMenu, measureType,
@@ -207,6 +224,10 @@ module.exports = app => {
                 jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.revise),
                 stdBills,
                 stdChapters,
+                curAuditor,
+                auditors,
+                user,
+                auditHistory,
             };
         }
 
@@ -234,41 +255,54 @@ module.exports = app => {
          * @returns {Promise<void>}
          * @private
          */
-        async _getReviseInfo(ctx, revise) {
+        async _getAuditReviseInfo(ctx, revise) {
             const renderData = await this._getDefaultReviseInfoData(ctx, revise);
-            const readOnly = (revise.status === audit.revise.status.checking || revise.status === audit.revise.status.checked || revise.uid !== ctx.session.sessionUser.accountId);
-            if (readOnly) {
-                renderData.ledgerSpread.readOnly = true;
-                renderData.posSpread.readOnly = true;
-            }
-            renderData.readOnly = readOnly;
-            if (!readOnly) {
-                renderData.usedInfo = {};
-                const lastStage = await ctx.service.stage.getLastestStage(ctx.tender.id, true);
-                if (lastStage.status === audit.stage.status.checked) {
-                    const usedPreBills = await ctx.service.stageBillsFinal.getUsedBills(ctx.tender.id, lastStage.order);
-                    for (const b of renderData.reviseBills) {
-                        b.used = usedPreBills.indexOf(b.id) >= 0;
-                    }
-                    const usedPrePos = await ctx.service.stagePosFinal.getUsedPos(ctx.tender.id, lastStage.order);
-                    for (const p of renderData.revisePos) {
-                        p.used = usedPrePos.indexOf(p.id) >= 0;
-                    }
-                } else {
-                    const usedPreBills = lastStage.order > 1 ? await ctx.service.stageBillsFinal.getUsedBills(ctx.tender.id, lastStage.order - 1) : [];
-                    const usedCurBills = await ctx.service.stageBills.getStageUsedBills(ctx.tender.id, lastStage.id);
-                    for (const b of renderData.reviseBills) {
-                        b.used = usedPreBills.indexOf(b.id) >= 0 || usedCurBills.indexOf(b.id) >= 0;
-                    }
-                    const usedPrePos = lastStage.order > 1 ? await ctx.service.stagePosFinal.getUsedPos(ctx.tender.id, lastStage.order - 1) : [];
-                    const usedCurPos = await ctx.service.stageBills.getStageUsedPos(ctx.tender.id, lastStage.id);
-                    for (const p of renderData.revisePos) {
-                        p.used = usedPrePos.indexOf(b.id) >= 0 || usedCurPos.indexOf(p.id) >= 0;
-                    }
+            renderData.readOnly = true;
+            renderData.ledgerSpread.readOnly = true;
+            renderData.posSpread.readOnly = true;
+            renderData.history = false;
+            renderData.historyRevise = [];
+            renderData.curAuditor = await ctx.service.reviseAudit.getCurAuditor(revise.id, revise.times);
+            await this.layout('revise/info.ejs', renderData, 'revise/info_modal.ejs');
+        }
+
+        async _getDraftReviseInfo(ctx, revise) {
+            const renderData = await this._getDefaultReviseInfoData(ctx, revise);
+            // 台账只读、使用数据
+            renderData.readOnly = false;
+            const lastStage = await ctx.service.stage.getLastestStage(ctx.tender.id, true);
+            if (lastStage.status === audit.stage.status.checked) {
+                const usedPreBills = await ctx.service.stageBillsFinal.getUsedBills(ctx.tender.id, lastStage.order);
+                for (const b of renderData.reviseBills) {
+                    b.used = usedPreBills.indexOf(b.id) >= 0;
+                }
+                const usedPrePos = await ctx.service.stagePosFinal.getUsedPos(ctx.tender.id, lastStage.order);
+                for (const p of renderData.revisePos) {
+                    p.used = usedPrePos.indexOf(p.id) >= 0;
+                }
+            } else {
+                const usedPreBills = lastStage.order > 1 ? await ctx.service.stageBillsFinal.getUsedBills(ctx.tender.id, lastStage.order - 1) : [];
+                const usedCurBills = await ctx.service.stageBills.getStageUsedBills(ctx.tender.id, lastStage.id);
+                for (const b of renderData.reviseBills) {
+                    b.used = usedPreBills.indexOf(b.id) >= 0 || usedCurBills.indexOf(b.id) >= 0;
+                }
+                const usedPrePos = lastStage.order > 1 ? await ctx.service.stagePosFinal.getUsedPos(ctx.tender.id, lastStage.order - 1) : [];
+                const usedCurPos = await ctx.service.stageBills.getStageUsedPos(ctx.tender.id, lastStage.id);
+                for (const p of renderData.revisePos) {
+                    p.used = usedPrePos.indexOf(b.id) >= 0 || usedCurPos.indexOf(p.id) >= 0;
                 }
             }
+            // 修订历史
             renderData.history = false;
             renderData.historyRevise = [];
+            // 添加审批人相关
+            renderData.accountGroup = accountGroup;
+            renderData.accountList = await ctx.service.projectAccount.getAllDataByCondition({
+                where: { project_id: ctx.session.sessionProject.id, enable: 1 },
+                columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group'],
+            });
+            renderData.auditorList = await ctx.service.reviseAudit.getAuditors(revise.id, revise.times);
+            renderData.curAuditor = await ctx.service.reviseAudit.getCurAuditor(revise.id, revise.times);
             await this.layout('revise/info.ejs', renderData, 'revise/info_modal.ejs');
         }
 
@@ -280,10 +314,15 @@ module.exports = app => {
         async info(ctx) {
             try {
                 const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
-                if (revise.status === audit.revise.status.checked) {
-                    await this._getHistoryReviseInfo(ctx);
+                if (!revise) throw '台账修订数据有误';
+
+                if (revise.uid === ctx.session.sessionUser.accountId &&
+                    (revise.status === audit.revise.status.uncheck || revise.status === audit.revise.status.checkNo)) {
+                    await this._getDraftReviseInfo(ctx, revise);
+                } else if (revise.status === audit.revise.status.checked) {
+                    await this._getHistoryReviseInfo(ctx, revise);
                 } else {
-                    await this._getReviseInfo(ctx, revise);
+                    await this._getAuditReviseInfo(ctx, revise);
                 }
             } catch (err) {
                 this.log(err);
@@ -291,41 +330,29 @@ module.exports = app => {
             }
         }
 
-        async _checkRevise() {
-            const revise = await this.ctx.service.ledgerRevise.getLastestRevise(this.ctx.tender.id);
-            if (revise.uid !== this.ctx.session.sessionUser.accountId) {
-                throw '修订数据错误';
-            } else if (revise.status === audit.revise.status.checking) {
-                throw '修订审批中,不可修改';
-            } else if (revise.status === audit.revise.status.checked) {
-                throw '修订已完成,如需修订台账,请新增修订';
-            }
-            this.ctx.revise = revise;
-        }
-
         /**
          * 增、删、上下移、升降级
          * @param ctx
          * @returns {Promise<void>}
          */
-        async _billsBase(ctx, type, data) {
+        async _billsBase(revise, type, data) {
             if (isNaN(data.id) || data.id <= 0) throw '数据错误';
             if (type !== 'add') {
                 if (isNaN(data.count) || data.count <= 0) data.count = 1;
             }
             switch (type) {
                 case 'add':
-                    return await ctx.service.reviseBills.addReviseNode(ctx.tender.id, ctx.revise.id, data.id);
+                    return await this.ctx.service.reviseBills.addReviseNode(revise.tid, revise.id, data.id);
                 case 'delete':
-                    return await ctx.service.reviseBills.delete(ctx.tender.id, data.id, data.count);
+                    return await this.ctx.service.reviseBills.delete(revise.tid, data.id, data.count);
                 case 'up-move':
-                    return await ctx.service.reviseBills.upMoveNode(ctx.tender.id, data.id, data.count);
+                    return await this.ctx.service.reviseBills.upMoveNode(revise.tid, data.id, data.count);
                 case 'down-move':
-                    return await ctx.service.reviseBills.downMoveNode(ctx.tender.id, data.id, data.count);
+                    return await this.ctx.service.reviseBills.downMoveNode(revise.tid, data.id, data.count);
                 case 'up-level':
-                    return await ctx.service.reviseBills.upLevelNode(ctx.tender.id, data.id, data.count);
+                    return await this.ctx.service.reviseBills.upLevelNode(revise.tid, data.id, data.count);
                 case 'down-level':
-                    return await ctx.service.reviseBills.downLevelNode(ctx.tender.id, data.id, data.count);
+                    return await this.ctx.service.reviseBills.downLevelNode(revise.tid, data.id, data.count);
             }
         }
         /**
@@ -339,26 +366,26 @@ module.exports = app => {
          * @param ctx
          * @return {Promise<void>}
          */
-        async _batchInsert(ctx, data) {
+        async _batchInsert(revise, data) {
             if ((isNaN(data.id) || data.id <= 0) || !data.batchType) {
                 throw '参数错误';
             }
             switch (data.batchType) {
                 case 'child':
-                    return await ctx.service.reviseBills.batchInsertChild(ctx.tender.id, ctx.revise.id, data.id, data.batchData);
+                    return await this.ctx.service.reviseBills.batchInsertChild(revise.tid, revise.id, data.id, data.batchData);
                 case 'next':
-                    return await ctx.service.reviseBills.batchInsertNext(ctx.tender.id, ctx.revise.id, data.id, data.batchData);
+                    return await this.ctx.service.reviseBills.batchInsertNext(revise.tid, revise.id, data.id, data.batchData);
                 default:
                     throw '参数错误';
             }
         }
-        async _pasteBlock(ctx, data) {
+        async _pasteBlock(revise, data) {
             if ((isNaN(data.id) || data.id <= 0) ||
                 (!data.tid && data.tid <= 0) ||
                 (!data.block || data.block.length <= 0)) throw '参数错误';
             return await ctx.service.revise.pasteBlock(data.tid, data.id, data.block);
         }
-        async _addStd(ctx, data) {
+        async _addStd(revise, data) {
             if ((isNaN(data.id) || data.id <= 0) || !data.stdType || !data.stdNode) throw '参数错误';
             // todo 校验项目是否使用该库的权限
 
@@ -393,28 +420,28 @@ module.exports = app => {
                     throw '未知添加方式';
             }
         }
-        async _addDeal(ctx, data) {
+        async _addDeal(revise, data) {
             if (!data.type || !data.dealBills) throw '数据错误';
             if (data.type === 'child') {
-                return await ctx.service.reviseBills.addChild(ctx.tender.id, data.id, data.dealBills, ctx.revise.id);
+                return await this.ctx.service.reviseBills.addChild(revise.tid, data.id, data.dealBills, revise.id);
             } else if (data.type === 'next') {
-                return await ctx.service.reviseBills.addBillsNode(ctx.tender.id, data.id, data.dealBills, ctx.revise.id);
+                return await this.ctx.service.reviseBills.addBillsNode(revise.tid, data.id, data.dealBills, revise.id);
             } else {
                 throw '数据错误';
             }
         }
-        async _updatePos(ctx, data) {
+        async _updatePos(revise, data) {
             await this.checkMeasureType(measureType.tz.value);
             if (!data.posPostType) throw '数据错误';
             switch (data.posPostType) {
                 case 'add':
-                    return await ctx.service.revisePos.addPos(ctx.tender.id, ctx.revise.id, data.postData);
+                    return await this.ctx.service.revisePos.addPos(revise.tid, revise.id, data.postData);
                 case 'update':
-                    return await ctx.service.revisePos.updatePos(ctx.tender.id, data.postData);
+                    return await this.ctx.service.revisePos.updatePos(revise.tid, data.postData);
                 case 'delete':
-                    return await ctx.service.revisePos.deletePos(ctx.tender.id, data.postData);
+                    return await this.ctx.service.revisePos.deletePos(revise.tid, data.postData);
                 case 'paste':
-                    return await ctx.service.revisePos.pastePosData(ctx.tender.id, ctx.revise.id, data.postData);
+                    return await this.ctx.service.revisePos.pastePosData(revise.tid, revise.id, data.postData);
                 default:
                     throw '未知操作';
             }
@@ -422,12 +449,18 @@ module.exports = app => {
         async update(ctx) {
             try {
                 if (!ctx.tender.data) throw '标段数据错误';
-                await this._checkRevise();
+                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+                if (revise.uid !== ctx.session.sessionUser.accountId) {
+                    throw '修订数据错误';
+                } else if (revise.status === audit.revise.status.checking) {
+                    throw '修订审批中,不可修改';
+                } else if (revise.status === audit.revise.status.checked) {
+                    throw '修订已完成,如需修订台账,请新增修订';
+                }
                 const data = JSON.parse(ctx.request.body.data);
                 if (!data.postType || !data.postData) throw '数据错误';
                 const responseData = {err: 0, msg: '', data: {}};
 
-                console.log(data);
                 switch (data.postType) {
                     case 'add':
                     case 'delete':
@@ -435,25 +468,25 @@ module.exports = app => {
                     case 'down-move':
                     case 'up-level':
                     case 'down-level':
-                        responseData.data = await this._billsBase(ctx, data.postType, data.postData);
+                        responseData.data = await this._billsBase(revise, data.postType, data.postData);
                         break;
                     case 'update':
-                        responseData.data = await this.ctx.service.reviseBills.updateCalc(ctx.tender.id, data.postData);
+                        responseData.data = await this.ctx.service.reviseBills.updateCalc(revise, data.postData);
                         break;
                     case 'batch-insert':
-                        responseData.data = await this._batchInsert(ctx, data.postData);
+                        responseData.data = await this._batchInsert(revise, data.postData);
                         break;
                     case 'add-deal':
-                        responseData.data = await this._addDeal(ctx, data.postData);
+                        responseData.data = await this._addDeal(revise, data.postData);
                         break;
                     case 'add-std':
-                        responseData.data = await this._addStd(ctx, data.postData);
+                        responseData.data = await this._addStd(revise, data.postData);
                         break;
                     case 'paste-block':
-                        responseData.data = await this._pasteBlock(ctx, data.postData);
+                        responseData.data = await this._pasteBlock(revise, data.postData);
                         break;
                     case 'pos':
-                        responseData.data = await this._updatePos(ctx, data);
+                        responseData.data = await this._updatePos(revise, data);
                         break;
                     default:
                         throw '未知操作';
@@ -465,6 +498,119 @@ module.exports = app => {
                 ctx.body = this.ajaxErrorBody(err, '数据错误');
             }
         }
+
+        /**
+         * 新增审批人(Ajax)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async addAuditor(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const id = data.auditorId;
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+                if (!revise) throw '台账修订数据有误';
+                // 检查权限等
+                if (revise.uid !== ctx.session.sessionUser.accountId) throw '您无权添加审核人';
+                if (revise.status === audit.revise.status.checking || revise.status === audit.revise.status.checked) {
+                    throw '当前不允许添加审核人';
+                }
+                // 检查审核人是否已存在
+                const exist = await ctx.service.reviseAudit.getAuditor(revise.id, id, revise.times);
+                if (exist) throw '该审核人已存在,请勿重复添加';
+
+                const result = await ctx.service.reviseAudit.addAuditor(revise, id);
+                if (!result) throw '添加审核人失败';
+
+                const resultData = await ctx.service.reviseAudit.getAuditor(revise.id, id, revise.times);
+                ctx.body = {err: 0, msg: '', data: resultData};
+            } catch (err) {
+                this.log(err, '数据错误');
+                ctx.body = this.ajaxErrorBody(err);
+            }
+        }
+        /**
+         * 移除审批人(Ajax)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async removeAuditor(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                console.log(data);
+                const id = data.auditorId instanceof Number ? data.auditorId : this.app._.toNumber(data.auditorId);
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+                if (!revise) throw '台账修订数据有误';
+
+                const result = await ctx.service.reviseAudit.deleteAuditor(revise, id);
+                if (!result) {
+                    throw '移除审核人失败';
+                }
+
+                const resultData = await ctx.service.reviseAudit.getAuditors(revise.id, revise.times);
+                ctx.body = {err: 0, msg: '', data: resultData};
+            } catch (err) {
+                this.log(err, '数据错误');
+                ctx.body = this.ajaxErrorBody(err);
+            }
+        }
+        /**
+         * 上报(post)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async start(ctx) {
+            try {
+                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+                if (!revise || revise.status === audit.revise.status.checking || revise.status === audit.revise.status.checked) {
+                    throw '台账修订数据有误';
+                }
+                if (revise.uid !== ctx.session.sessionUser.accountId) throw '上报失败';
+
+                await ctx.service.reviseAudit.start(revise, revise.times);
+
+                ctx.redirect('/tender/' + ctx.tender.id + '/revise/info');
+            } catch (err) {
+                this.log(err);
+                this.postError(err, '上报失败');
+                ctx.redirect('/tender/' + ctx.tender.id + '/revise/info');
+            }
+        }
+        /**
+         * 审批(post)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async check(ctx) {
+            try {
+                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+                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 checkType = parseInt(ctx.request.body.checkType);
+                if (!checkType || isNaN(checkType)) throw '提交数据错误';
+
+                await ctx.service.reviseAudit.check(revise, checkType, ctx.request.body.opinion, revise.times);
+
+                ctx.redirect('/tender/' + ctx.tender.id + '/revise/info');
+            } catch (err) {
+                this.log(err);
+                this.postError(err, '审批失败');
+                ctx.redirect('/tender/' + ctx.tender.id + '/revise/info');
+            }
+        }
     }
 
     return ReviseController;

+ 75 - 68
app/public/js/revise.js

@@ -827,43 +827,45 @@ $(document).ready(() => {
             this.spreadSetting = spreadSetting;
             this.spread = SpreadJsObj.createNewSpread(this.obj);
             SpreadJsObj.initSheet(this.spread.getActiveSheet(), this.spreadSetting);
-            this.spread.getActiveSheet().bind(GC.Spread.Sheets.Events.CellDoubleClick, function (e, info) {
-                const stdSheet = info.sheet;
-                const mainSheet = billsSheet;
-                if (!stdSheet.zh_setting || !stdSheet.zh_tree || !mainSheet.zh_tree) { return; }
+            if (!readOnly) {
+                this.spread.getActiveSheet().bind(GC.Spread.Sheets.Events.CellDoubleClick, function (e, info) {
+                    const stdSheet = info.sheet;
+                    const mainSheet = billsSheet;
+                    if (!stdSheet.zh_setting || !stdSheet.zh_tree || !mainSheet.zh_tree) { return; }
 
-                const stdTree = stdSheet.zh_tree;
-                const stdNode = stdTree.nodes[info.row];
-                const mainTree = mainSheet.zh_tree;
-                const sel = mainSheet.getSelections()[0];
-                const mainNode = mainTree.nodes[sel.row];
-                if (!stdNode) return;
+                    const stdTree = stdSheet.zh_tree;
+                    const stdNode = stdTree.nodes[info.row];
+                    const mainTree = mainSheet.zh_tree;
+                    const sel = mainSheet.getSelections()[0];
+                    const mainNode = mainTree.nodes[sel.row];
+                    if (!stdNode) return;
 
-                if (stdType === 'bills') {
-                    if (!(mainNode.b_code && mainNode.b_code !== '') && !mainTree.isLeafXmj(mainNode)) {
-                        toastr.warning('非最底层项目下,不应添加清单');
-                        return;
+                    if (stdType === 'bills') {
+                        if (!(mainNode.b_code && mainNode.b_code !== '') && !mainTree.isLeafXmj(mainNode)) {
+                            toastr.warning('非最底层项目下,不应添加清单');
+                            return;
+                        }
                     }
-                }
 
-                postData(window.location.pathname + '/update', {
-                    postType: 'add-std',
-                    postData: {
-                        id: mainTree.getNodeKey(mainNode),
-                        tender_id: mainNode.tender_id,
-                        stdType: stdType,
-                        stdLibId: stdNode.list_id,
-                        stdNode: stdTree.getNodeKey(stdNode)
-                    }
-                }, function (result) {
-                    const refreshNode = mainTree.loadPostData(result);
-                    billsTreeSpreadObj.refreshTree(mainSheet, refreshNode);
-                    if (sel && refreshNode.create[0]) {
-                        mainSheet.setSelection(mainTree.nodes.indexOf(refreshNode.create[0]), sel.col, sel.rowCount, sel.colCount);
-                    }
-                    billsTreeSpreadObj.refreshOperationValid(mainSheet);
+                    postData(window.location.pathname + '/update', {
+                        postType: 'add-std',
+                        postData: {
+                            id: mainTree.getNodeKey(mainNode),
+                            tender_id: mainNode.tender_id,
+                            stdType: stdType,
+                            stdLibId: stdNode.list_id,
+                            stdNode: stdTree.getNodeKey(stdNode)
+                        }
+                    }, function (result) {
+                        const refreshNode = mainTree.loadPostData(result);
+                        billsTreeSpreadObj.refreshTree(mainSheet, refreshNode);
+                        if (sel && refreshNode.create[0]) {
+                            mainSheet.setSelection(mainTree.nodes.indexOf(refreshNode.create[0]), sel.col, sel.rowCount, sel.colCount);
+                        }
+                        billsTreeSpreadObj.refreshOperationValid(mainSheet);
+                    });
                 });
-            });
+            }
             this.pathTree = createNewPathTree('base', this.treeSetting);
             this.cacheLib = [];
             $('select', selector).change(function () {
@@ -896,41 +898,43 @@ $(document).ready(() => {
             this.spreadSetting = spreadSetting;
             this.spread = SpreadJsObj.createNewSpread(this.obj);
             SpreadJsObj.initSheet(this.spread.getActiveSheet(), this.spreadSetting);
-            this.spread.bind(spreadNS.Events.CellDoubleClick, function (e, info) {
-                const dealSheet = info.sheet;
-                const mainSheet = ledgerSpread.getActiveSheet();
+            if (!readOnly) {
+                this.spread.bind(spreadNS.Events.CellDoubleClick, function (e, info) {
+                    const dealSheet = info.sheet;
+                    const mainSheet = ledgerSpread.getActiveSheet();
 
-                const dealBills = SpreadJsObj.getSelectObject(dealSheet);
-                if (!dealBills) { return; }
-                const mainTree = mainSheet.zh_tree;
-                const mainNode = SpreadJsObj.getSelectObject(mainSheet);
-                if (!mainNode || !mainTree) { return; }
+                    const dealBills = SpreadJsObj.getSelectObject(dealSheet);
+                    if (!dealBills) { return; }
+                    const mainTree = mainSheet.zh_tree;
+                    const mainNode = SpreadJsObj.getSelectObject(mainSheet);
+                    if (!mainNode || !mainTree) { return; }
 
-                if (mainNode.code && mainNode.code !== '' && !mainTree.isLeafXmj(mainNode)) {
-                    toastr.warning('非最底层项目下,不应添加清单');
-                    return;
-                }
+                    if (mainNode.code && mainNode.code !== '' && !mainTree.isLeafXmj(mainNode)) {
+                        toastr.warning('非最底层项目下,不应添加清单');
+                        return;
+                    }
 
-                postData(window.location.pathname + '/update', {
-                    postType: 'add-deal',
-                    postData: {
-                        id: mainNode.ledger_id,
-                        type: mainNode.code ? 'child' : 'next',
-                        dealBills: {
-                            b_code: dealBills.code, name: dealBills.name, unit: dealBills.unit,
-                            unit_price: dealBills.unit_price,
+                    postData(window.location.pathname + '/update', {
+                        postType: 'add-deal',
+                        postData: {
+                            id: mainNode.ledger_id,
+                            type: mainNode.code ? 'child' : 'next',
+                            dealBills: {
+                                b_code: dealBills.code, name: dealBills.name, unit: dealBills.unit,
+                                unit_price: dealBills.unit_price,
+                            }
+                        },
+                    }, function (result) {
+                        const refreshData = mainTree.loadPostData(result);
+                        billsTreeSpreadObj.refreshTree(mainSheet, refreshData);
+                        const sel = mainSheet.getSelections()[0];
+                        if (sel && refreshData.create[0]) {
+                            mainSheet.setSelection(mainTree.nodes.indexOf(refreshData.create[0]), sel.col, sel.rowCount, sel.colCount);
                         }
-                    },
-                }, function (result) {
-                    const refreshData = mainTree.loadPostData(result);
-                    billsTreeSpreadObj.refreshTree(mainSheet, refreshData);
-                    const sel = mainSheet.getSelections()[0];
-                    if (sel && refreshData.create[0]) {
-                        mainSheet.setSelection(mainTree.nodes.indexOf(refreshData.create[0]), sel.col, sel.rowCount, sel.colCount);
-                    }
-                    billsTreeSpreadObj.refreshOperationValid(mainSheet);
+                        billsTreeSpreadObj.refreshOperationValid(mainSheet);
+                    });
                 });
-            });
+            }
             SpreadJsObj.forbiddenSpreadContextMenu(selector, this.spread);
         }
         loadData () {
@@ -1260,11 +1264,13 @@ $(document).ready(() => {
         }
     });
 
-    // 修订详情 保存
-    $('#save').click(function () {
-        const content = $('textarea').val();
-        postData('save', { content: content });
-    });
+    if (!readOnly) {
+        // 修订详情 保存
+        $('#save').click(function () {
+            const content = $('textarea').val();
+            postData('save', { content: content });
+        });
+    }
 
     // 显示层次
     (function (select, sheet) {
@@ -1292,4 +1298,5 @@ $(document).ready(() => {
             }
         });
     })('a[name=showLevel]', billsSheet);
-});
+});
+

+ 10 - 8
app/router.js

@@ -84,7 +84,6 @@ module.exports = app => {
     // 标段协作办公
     app.get('/tender/:id/cooperation', sessionAuth, tenderCheck, 'tenderController.tenderCooperation');
 
-
     // 台账管理相关
     app.get('/tender/:id/ledger', sessionAuth, tenderCheck, 'ledgerController.explode');
     app.post('/tender/:id/ledger/get-children', sessionAuth, tenderCheck, 'ledgerController.getChildren');
@@ -94,6 +93,12 @@ module.exports = app => {
     app.post('/tender/:id/pos', sessionAuth, tenderCheck, 'ledgerController.pos');
     app.post('/tender/:id/pos/update', sessionAuth, tenderCheck, 'ledgerController.posUpdate');
     app.post('/tender/:id/pos/paste', sessionAuth, tenderCheck, 'ledgerController.posPaste');
+    // 台账审批相关
+    app.get('/tender/:id/ledger/audit', sessionAuth, tenderCheck, 'ledgerAuditController.index');
+    app.post('/tender/:id/ledger/audit/add', sessionAuth, tenderCheck, 'ledgerAuditController.add');
+    app.post('/tender/:id/ledger/audit/delete', sessionAuth, tenderCheck, 'ledgerAuditController.remove');
+    app.post('/tender/:id/ledger/audit/start', sessionAuth, tenderCheck, 'ledgerAuditController.start');
+    app.post('/tender/:id/ledger/audit/check', sessionAuth, tenderCheck, 'ledgerAuditController.check');
     // 台账修订
     app.get('/tender/:id/revise', sessionAuth, tenderCheck, 'reviseController.index');
     app.post('/tender/:id/revise/add', sessionAuth, tenderCheck, 'reviseController.add');
@@ -101,13 +106,10 @@ module.exports = app => {
     app.post('/tender/:id/revise/save', sessionAuth, tenderCheck, 'reviseController.save');
     app.get('/tender/:id/revise/info', sessionAuth, tenderCheck, 'reviseController.info');
     app.post('/tender/:id/revise/info/update', sessionAuth, tenderCheck, 'reviseController.update');
-    //app.post('/tender/:id/ledger/revise/audit/', sessionAuth, tenderCheck, 'ledgerController.reviseStatus');
-    // 台账审批相关
-    app.get('/tender/:id/ledger/audit', sessionAuth, tenderCheck, 'ledgerAuditController.index');
-    app.post('/tender/:id/ledger/audit/add', sessionAuth, tenderCheck, 'ledgerAuditController.add');
-    app.post('/tender/:id/ledger/audit/delete', sessionAuth, tenderCheck, 'ledgerAuditController.remove');
-    app.post('/tender/:id/ledger/audit/start', sessionAuth, tenderCheck, 'ledgerAuditController.start');
-    app.post('/tender/:id/ledger/audit/check', sessionAuth, tenderCheck, 'ledgerAuditController.check');
+    app.post('/tender/:id/revise/audit/add', sessionAuth, tenderCheck, 'reviseController.addAuditor');
+    app.post('/tender/:id/revise/audit/remove', sessionAuth, tenderCheck, 'reviseController.removeAuditor');
+    app.post('/tender/:id/revise/audit/start', sessionAuth, tenderCheck, 'reviseController.start');
+    app.post('/tender/:id/revise/audit/check', sessionAuth, tenderCheck, 'reviseController.check');
 
     // 签约清单
     app.post('/tender/:id/deal/get-data', sessionAuth, tenderCheck, 'dealBillsController.getData');

+ 23 - 2
app/service/ledger_revise.js

@@ -102,6 +102,27 @@ module.exports = app => {
             await transaction.query(sql, sqlParam);
         }
 
+        async _initReviseAuditor(transaction, rid, order) {
+            const inTime = new Date();
+            const preRevise = order > 0 ? await this.getDataByCondition({tid: this.ctx.tender.id, corder: order}) : null;
+            const newAuditors = [];
+            const auditors = preRevise
+                ? await this.ctx.service.reviseAudit.getAuditors(preRevise.id, preRevise.ledger_times)
+                : await this.ctx.service.ledgerAudit.getAuditors(this.ctx.tender.id, this.ctx.tender.data.ledger_times);
+            for (const a of auditors) {
+                newAuditors.push({
+                    tender_id: this.ctx.tender.id,
+                    rid: rid,
+                    in_time: inTime,
+                    audit_order: a.audit_order,
+                    audit_id: a.audit_id,
+                    times: 1,
+                    status: audit.status.uncheck,
+                });
+            }
+            await transaction.insert(this.ctx.service.reviseAudit.tableName, newAuditors);
+        }
+
         /**
          * 新增修订
          * @param {Number}tid - 标段id
@@ -109,7 +130,6 @@ module.exports = app => {
          * @returns {Promise<void>}
          */
         async add(tid, uid) {
-
             if (!tid && !uid) {
                 throw '数据错误';
             }
@@ -125,9 +145,10 @@ module.exports = app => {
                     throw '新增台账修订失败';
                 }
                 await transaction.delete(this.ctx.service.reviseBills.tableName, {tender_id: tid});
-                await transaction.delete(this.ctx.service.revisePos.tableName, {tid: tid});
                 await this._initReviseBills(transaction, tid, data.id);
+                await transaction.delete(this.ctx.service.revisePos.tableName, {tid: tid});
                 await this._initRevisePos(transaction, tid, data.id);
+                await this._initReviseAuditor(transaction, data.id, maxOrder);
                 await transaction.commit();
                 return data;
             } catch(err) {

+ 398 - 0
app/service/revise_audit.js

@@ -0,0 +1,398 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const auditConst = require('../const/audit').revise;
+const smsTypeConst = require('../const/sms_type');
+const SMS = require('../lib/sms');
+
+module.exports = app => {
+    class ReviseAudit extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'revise_audit';
+        }
+
+        /**
+         * 获取标段审核人信息
+         *
+         * @param {Number} reviseId - 修订id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<*>}
+         */
+        async getAuditor(reviseId, 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` ' +
+                'FROM ?? AS la, ?? AS pa ' +
+                'WHERE la.`rid` = ? and la.`audit_id` = ? and la.`times` = ?' +
+                '    and la.`audit_id` = pa.`id`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, reviseId, auditorId, times];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        /**
+         * 获取标段审核列表信息
+         *
+         * @param {Number} reviseId - 修订id
+         * @param {Number} times - 第几次审批
+         * @returns {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`';
+            const sqlParam = [reviseId, times];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 获取标段当前审核人
+         *
+         * @param {Number} reviseId - 修订id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<*>}
+         */
+        async getCurAuditor(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` ' +
+                'FROM ?? AS la, ?? AS pa ' +
+                'WHERE la.`rid` = ? and la.`status` = ? and la.`times` = ?' +
+                '    and la.`audit_id` = pa.`id`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, reviseId, auditConst.status.checking, times];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        /**
+         * 获取最新审核顺序
+         *
+         * @param {Number} reviseId - 修订id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<number>}
+         */
+        async getNewOrder(reviseId, times = 1) {
+            const sql = 'SELECT Max(??) As max_order FROM ?? Where `rid` = ? and `times` = ?';
+            const sqlParam = ['audit_order', this.tableName, reviseId, times];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result && result.max_order ? result.max_order + 1 : 1;
+        }
+
+        /**
+         * 新增审核人
+         *
+         * @param {Object} revise - 修订
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<number>}
+         */
+        async addAuditor(revise, auditorId) {
+            const times = revise.times ? revise.times : 1;
+            const newOrder = await this.getNewOrder(revise.id, times);
+            const data = {
+                tender_id: revise.tid,
+                audit_id: auditorId,
+                times: times,
+                audit_order: newOrder,
+                status: auditConst.status.uncheck,
+                rid: revise.id,
+                in_time: new Date(),
+            };
+            const result = await this.db.insert(this.tableName, data);
+            return result.effectRows = 1;
+        }
+
+        /**
+         * 移除审核人时,同步其后审核人order
+         * @param transaction - 事务
+         * @param {Number} reviseId - 修订id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<*>}
+         * @private
+         */
+        async _syncOrderByDelete(transaction, reviseId, order, times) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('rid', {
+                value: this.db.escape(reviseId),
+                operate: '='
+            });
+            this.sqlBuilder.setAndWhere('audit_order', {
+                value: order,
+                operate: '>=',
+            });
+            this.sqlBuilder.setAndWhere('times', {
+                value: times,
+                operate: '=',
+            });
+            this.sqlBuilder.setUpdateData('audit_order', {
+                value: 1,
+                selfOperate: '-',
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+            const data = await transaction.query(sql, sqlParam);
+
+            return data;
+        }
+
+        /**
+         * 移除审核人
+         *
+         * @param {Object} revise - 修订
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<boolean>}
+         */
+        async deleteAuditor(revise, auditorId) {
+            const times = revise.times ? revise.times : 1;
+            const transaction = await this.db.beginTransaction();
+            try {
+                const condition = {rid: revise.id, audit_id: auditorId, times: times};
+                const auditor = await this.getDataByCondition(condition);
+                if (!auditor) {
+                    throw '该审核人不存在';
+                }
+                await this._syncOrderByDelete(transaction, revise.id, auditor.audit_order, times);
+                await transaction.delete(this.tableName, condition);
+                await transaction.commit();
+            } catch(err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
+
+        /**
+         * 拷贝一份台账备份
+         * @param {Object} revise - 修订
+         * @returns {Promise<void>}
+         * @private
+         */
+        async saveLedgerHistoryFile(revise) {
+            const timestamp = Date.parse(new Date());
+
+            const billsHis = 'file/revise/bills' + timestamp + '.json';
+            const bills = await this.ctx.service.reviseBills.getData(revise.tid);
+            await this.ctx.helper.saveBufferFile(JSON.stringify(bills), this.ctx.app.baseDir + billsHis);
+
+            const posHis = 'file/revise/pos' + timestamp + '.json';
+            const pos = await this.ctx.service.revisePos.getData(revise.tid);
+            await this.ctx.helper.saveBufferFile(JSON.stringify(pos), this.ctx.app.baseDir + posHis);
+
+            return [billsHis, posHis];
+        }
+
+        /**
+         * 开始审批
+         *
+         * @param {Object} revise - 修订
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<boolean>}
+         */
+        async start(revise, times = 1) {
+            const audit = await this.getDataByCondition({rid: revise.id, times: times, audit_order: 1});
+            if (!audit) throw '审核人信息错误';
+
+            // 拷贝备份台账数据
+            const [billsHis, posHis] = await this.saveLedgerHistoryFile(revise);
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, {id: audit.id, status: auditConst.status.checking, begin_time: new Date()});
+                await transaction.update(this.ctx.service.ledgerRevise.tableName, {
+                    id: revise.id, status: auditConst.status.checking,
+                    bills_file: billsHis, pos_file: posHis,
+                });
+
+                // 添加短信通知-需要审批提醒功能
+                const smsUser = await this.ctx.service.projectAccount.getDataById(audit.audit_id);
+                if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                    const smsType = JSON.parse(smsUser.sms_type);
+                    if (smsType[smsTypeConst.const.TZ] !== undefined && smsType[smsTypeConst.const.TZ].indexOf(smsTypeConst.judge.approval.toString()) !== -1) {
+                        const sms = new SMS(this.ctx);
+                        const tenderName = await sms.contentChange(this.ctx.tender.data.name);
+                        const content = '【纵横计量支付】' + tenderName + '台账修订需要您审批。';
+                        sms.send(smsUser.auth_mobile, content);
+                    }
+                }
+
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
+
+        async _replaceLedgerByRevise(transaction, revise) {
+            const sqlParam = [revise.tid];
+            await transaction.delete(this.ctx.service.ledger.tableName, {tender_id: revise.tid});
+            const bSql = 'Insert Into ' + this.ctx.service.ledger.tableName +
+                '  (id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
+                '     quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
+                '     sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id)' +
+                '  Select id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
+                '      quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
+                '      sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id' +
+                '  From ' + this.ctx.service.reviseBills.tableName +
+                '  Where `tender_id` = ?';
+            await transaction.query(bSql, sqlParam);
+            await transaction.delete(this.ctx.service.pos.tableName, {tid: revise.tid});
+            const pSql = 'Insert Into ' + this.ctx.service.revisePos.tableName +
+                '  (id, tid, lid, name, drawing_code, quantity, add_stage, add_times, add_user,' +
+                '     sgfh_qty, sjcl_qty, qtcl_qty, crid)' +
+                '  Select id, tid, lid, name, drawing_code, quantity, add_stage, add_times, add_user,' +
+                '     sgfh_qty, sjcl_qty, qtcl_qty, crid' +
+                '  From ' + this.ctx.service.pos.tableName +
+                '  Where `tid` = ?';
+            await transaction.query(pSql, sqlParam);
+        }
+
+        /**
+         * 审批
+         * @param {Object} revise - 修订
+         * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
+         * @param {String} opinion - 审批意见
+         * @param {Number} times - 第几次审批
+         * @returns {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: times, status: auditConst.status.checking});
+            if (!audit) throw '审核数据错误';
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 整理当前流程审核人状态更新
+                const time = new Date();
+                // 更新当前审核流程
+                await transaction.update(this.tableName, {
+                    id: audit.id, status: checkType, opinion: opinion, end_time: time,
+                    bills_file: revise.bills_file, pos_file: revise.pos_file,
+                });
+                if (checkType === auditConst.status.checked) {
+                    const nextAudit = await this.getDataByCondition({rid: revise.id, times: times, audit_order: audit.audit_order + 1});
+                    // 无下一审核人表示,审核结束
+                    if (nextAudit) {
+                        await transaction.update(this.tableName, {id: nextAudit.id, status: auditConst.status.checking, begin_time: time});
+
+                        // 添加短信通知-需要审批提醒功能
+                        const smsUser = await this.ctx.service.projectAccount.getDataById(nextAudit.audit_id);
+                        if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                            const smsType = JSON.parse(smsUser.sms_type);
+                            if (smsType[smsTypeConst.const.TZ] !== undefined && smsType[smsTypeConst.const.TZ].indexOf(smsTypeConst.judge.approval.toString()) !== -1) {
+                                const sms = new SMS(this.ctx);
+                                const tenderName = await sms.contentChange(this.ctx.tender.data.name);
+                                const content = '【纵横计量支付】' + tenderName + '台帐修订需要您审批。';
+                                sms.send(smsUser.auth_mobile, content);
+                            }
+                        }
+                    } else {
+                        // 同步修订信息
+                        await transaction.update(this.ctx.service.ledgerRevise.tableName, {id: revise.id, status: checkType});
+                        // 拷贝修订数据至台账
+                        await this._replaceLedgerByRevise(transaction, revise);
+
+                        // 添加短信通知-审批通过提醒功能
+                        const smsUser = await this.ctx.service.projectAccount.getDataById(revise.uid);
+                        if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                            const smsType = JSON.parse(smsUser.sms_type);
+                            if (smsType[smsTypeConst.const.TZ] !== undefined && smsType[smsTypeConst.const.TZ].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
+                                const sms = new SMS(this.ctx);
+                                const tenderName = await sms.contentChange(this.ctx.tender.data.name);
+                                const content = '【纵横计量支付】' + tenderName + '台账修订审批通过,请登录系统处理。';
+                                sms.send(smsUser.auth_mobile, content);
+                            }
+                        }
+                    }
+                } 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: times},
+                        columns: ['tender_id', 'rid', 'audit_order', 'audit_id']
+                    });
+                    for (const a of auditors) {
+                        a.times = times + 1;
+                        a.status = auditConst.status.uncheck;
+                        a.in_time = time;
+                    }
+                    await transaction.insert(this.tableName, auditors);
+
+                    // 添加短信通知-审批退回提醒功能
+                    const smsUser = await this.ctx.service.projectAccount.getDataById(revise.uid);
+                    if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                        const smsType = JSON.parse(smsUser.sms_type);
+                        if (smsType[smsTypeConst.const.TZ] !== undefined && smsType[smsTypeConst.const.TZ].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
+                            const sms = new SMS(this.ctx);
+                            const tenderName = await sms.contentChange(this.ctx.tender.data.name);
+                            const content = '【纵横计量支付】' + tenderName + '台账修订审批退回,请登录系统处理。';
+                            sms.send(smsUser.auth_mobile, content);
+                        }
+                    }
+                }
+
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 获取审核人需要审核的标段列表
+         *
+         * @param auditorId
+         * @returns {Promise<*>}
+         */
+        async getAuditRevise(auditorId) {
+            const sql = 'SELECT ra.`audit_id`, ra.`times`, ra.`audit_order`, ra.`begin_time`, ra.`end_time`,' +
+                '    r.id, r.corder, r.uid, r.status, r.content,' +
+                '    t.id As t_id, t.`name` As t_name, t.`project_id` As t_pid, t.`type` As t_type, t.`user_id` As t_uid, t.`status` As t_status, ' +
+                '    p.name As audit_name, p.role As audit_role, p.company As audit_company' +
+                '  FROM ' + this.tableName + ' AS ra' +
+                '  Left Join ' + this.ctx.service.ledgerRevise.tableName + ' As r On ra.rid = r.id' +
+                '  Left Join '+ this.ctx.service.tender.tableName +' AS t On r.tid = t.id' +
+                '  Left Join ' + this.ctx.service.projectAccount.tableName + ' As p On ra.audit_id = p.id' +
+                '  WHERE ((ra.`audit_id` = ? and ra.`status` = ?) OR' +
+                '    (r.`uid` = ? and r.`status` = ? and ra.`status` = ? and ra.`times` = (r.`times`-1)))';
+            const sqlParam = [auditorId, auditConst.status.checking, auditorId, auditConst.status.checkNo, auditConst.status.checkNo];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 获取 某时间后 审批进度 更新的台账
+         * @param {Integer} projectId - 项目id
+         * @param {Integer} auditorId - 查询人id
+         * @param {Date} noticeTime - 查询事件
+         * @returns {Promise<*>}
+         */
+        async getNoticeRevise(projectId, auditorId, noticeTime) {
+            const sql = 'SELECT ra.`audit_id`, ra.`times`, ra.`audit_order`, ra.`end_time`, ra.`status`,' +
+                '    r.id, r.corder, r.uid, r.status, r.content, ' +
+                '    t.`id` As t_id, t.`name` As t_name, t.`project_id` As t_pid, t.`type` As t_type, t.`user_id` As t_uid, ' +
+                '    pa.name As `ru_name`, pa.role As `ru_role`, pa.company As `ru_company`' +
+                '  FROM ' + this.tableName + ' As ra' +
+                '  INNER JOIN ' + this.ctx.service.ledgerRevise.tableName + ' As r ON ra.audit_id <> ? and ra.end_time > ?' +
+                '  LEFT JOIN ' + this.ctx.service.tender.tableName + ' As t ON r.`tid` = t.`id` and t.project_id = ?' +
+                '  LEFT JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa ON ra.`audit_id` = pa.`id`' +
+                '  GROUP By t.`id`' +
+                '  ORDER By ra.`end_time`';
+            const sqlParam = [auditorId, noticeTime, projectId];
+            return await this.db.query(sql, sqlParam);
+        }
+    }
+
+    return ReviseAudit;
+};

+ 38 - 0
app/view/dashboard/index.ejs

@@ -29,6 +29,30 @@
                 </li>
                 <% } %>
                 <% } %>
+                <% for (const revise of auditRevise) { %>
+                <li class="media">
+                    <img class="mr-3" src="public/images/avatar.png">
+                    <div class="media-body">
+                        <span class="pull-right text-muted"><%- (revise.end_time ? revise.end_time.toLocaleString() : '') %></span>
+                        <h5 class="mt-0"><%- revise.audit_name %><small class="ml-3 text-muted"><%- revise.audit_role %></small></h5>
+                        <p>
+                            <a href="/tender/<%- revise.t_id %>"><%- revise.t_name %></a>
+                            台账修订(第<%- revise.corder %>次)
+                            <% if (revise.status === acRevise.status.checkNo) { %>
+                            <span style="color: indianred">被退回</span>
+                            <% } %>
+                            需要您
+                            <a href="/tender/<%- revise.t_id %>/revise/info">
+                                <% if (revise.status === acRevise.status.checking) { %>
+                                审批
+                                <% } else if (revise.status === acRevise.status.checkNo) { %>
+                                重新上报
+                                <% } %>
+                            </a>。
+                        </p>
+                    </div>
+                </li>
+                <% } %>
                 <% for (const audit of auditStages) { %>
                 <% if (audit.sstatus !== acStage.status.checkNo) { %>
                 <li class="media">
@@ -103,6 +127,20 @@
                     </div>
                 </li>
                 <% } %>
+                <% for (const nr of noticeRevise) { %>
+                <li class="media">
+                    <img class="mr-3" src="public/images/avatar.png">
+                    <div class="media-body">
+                        <span class="pull-right text-muted"><%- (nr.end_time ? nr.end_time.toLocaleString() : '') %></span>
+                        <h5 class="mt-0"><%- nr.ru_name %><small class="ml-3 text-muted"><%- nr.ru_role %></small></h5>
+                        <p>
+                            <a href="/tender/<%- nr.id %>"><%- nr.name %></a>
+                            <a href="/tender/<%- nr.t_id %>/revise/info">台账修订(第<%- nr.corder %>次)</a>
+                            <%- acRevise.statusString[nr.status]%>。
+                        </p>
+                    </div>
+                </li>
+                <% } %>
                 <% for (const ns of noticeStage) { %>
                 <li class="media">
                     <img class="mr-3" src="public/images/avatar.png">

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

@@ -55,7 +55,7 @@
                         <a href="<%- preUrl + '/revise/info' %>" class="btn btn-primary btn-sm">修订</a>
                         <a href="#remove" data-toggle="modal" data-target="#remove" class="btn btn-secondary btn-sm">作废</a>
                         <% } else if (lr.status === auditConst.status.checking) { %>
-                        <a href="<%- preUrl + '/revise/info' %>" class="btn btn-success btn-sm">审批</a><
+                        <a href="<%- preUrl + '/revise/info' %>" class="btn btn-success btn-sm">审批</a>
                         <% } else if (lr.status === auditConst.status.checkNo) { %>
                         <a href="<%- preUrl + '/revise/info' %>" class="btn btn-primary btn-sm">重新上报</a>
                         <a href="#remove" data-toggle="modal" data-target="#remove" class="btn btn-secondary btn-sm">作废</a>

+ 11 - 7
app/view/revise/info.ejs

@@ -44,19 +44,23 @@
             </div>
             <% } %>
             <div class="ml-auto">
-                <% if (revise.status === audit.status.uncheck) { %>
+                <% if (revise.status === audit.status.uncheck && revise.uid === ctx.session.sessionUser.accountId) { %>
                 <a href="#sub-sp" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm pull-right">上报审批</a>
-                <% } else if (revise.status === audit.status.checkNo) { %>
+                <% } %>
+                <% if (revise.status === audit.status.checkNo) { %>
                 <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm pull-right">审批退回</a>
                     <% if (revise.uid === ctx.session.sessionUser.accountId) { %>
                     <a href="#sub-sp" data-toggle="modal" data-target="#sub-sp2" class="btn btn-primary btn-sm pull-right">重新上报</a>
                     <% } %>
-                <% } else if (revise.status === audit.status.checking) { %>
+                <% } %>
+                <% if (revise.status === audit.status.checking) { %>
                 <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm pull-right text-dark">审批中</a>
-                <!--审批-->
-                <a href="#sp-done" data-toggle="modal" data-target="#sp-done" class="btn btn-success btn-sm pull-right">审批通过</a>
-                <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm pull-right">审批退回</a>
-                <% } else if (revise.status === audit.status.checked) { %>
+                    <% if (curAuditor.audit_id === ctx.session.sessionUser.accountId) { %>
+                    <a href="#sp-done" data-toggle="modal" data-target="#sp-done" class="btn btn-success btn-sm pull-right">审批通过</a>
+                    <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm pull-right">审批退回</a>
+                    <% } %>
+                <% } %>
+                <% if (revise.status === audit.status.checked) { %>
                 <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm pull-right">审批完成</a>
                 <% } %>
             </div>

+ 357 - 99
app/view/revise/info_modal.ejs

@@ -1,4 +1,3 @@
-
 <!--批量添加清单部位-->
 <div class="modal fade" id="batch" data-backdrop="static">
     <div class="modal-dialog modal-lg" role="document">
@@ -30,7 +29,7 @@
         </div>
     </div>
 </div>
-<% if (revise.status === audit.status.uncheck) { %>
+<% if ((revise.status === audit.status.uncheck || revise.status === audit.status.checkNo) && ctx.session.sessionUser.accountId === revise.uid) { %>
 <!--上报审批-->
 <div class="modal fade" id="sub-sp" data-backdrop="static">
     <div class="modal-dialog" role="document">
@@ -40,43 +39,50 @@
             </div>
             <div class="modal-body">
                 <div class="form-group">
-                    <label>搜索审批人</label>
+                    <label>选择审批人</label>
                     <div class="input-group">
-                        <input class="form-control" placeholder="请输入姓名进行检索" type="text">
-                        <div class="input-group-append">
-                            <button class="btn btn-outline-secondary" type="button"><i class="fa fa-search"></i></button>
+                        <div class="input-group-prepend">
+                            <select class="form-control" id="account_group">
+                                <option value="0">所有分组</option>
+                                <% for (const dw in accountGroup) { %>
+                                <option value="<%= dw %>"><%= accountGroup[dw] %></option>
+                                <% } %>
+                            </select>
                         </div>
-                    </div>
-                </div>
-                <div class="card border-primary">
-                    <div class="card-body">
-                        <h5 class="card-title">
-                            <a href="#" class="btn btn-primary btn-sm pull-right">添加</a>张三
-                        </h5>
-                        <h6 class="card-subtitle mb-2 text-muted">监理</h6>
-                        <p class="card-text">XXXXX公司</p>
+                        <select class="form-control" id="account_list">
+                            <option value="0">选择审批人</option>
+                            <% for (const account of accountList) { %>
+                            <option value="<%= account.id %>"><%= account.name %><% if (account.role !== '') { %>(<%= account.role %>)<% } %><% if (account.company !== '') { %> -<%= account.company %><% } %></option>
+                            <% } %>
+                        </select>
                     </div>
                 </div>
                 <div class="card mt-3">
                     <div class="card-header">
                         审批流程
                     </div>
-                    <ul class="list-group list-group-flush">
-                        <li class="list-group-item"><a href="" class="text-danger pull-right">移除</a>1 张三  <small class="text-muted">监理</small></li>
-                        <li class="list-group-item"><a href="" class="text-danger pull-right">移除</a>2 王五 <small class="text-muted">监理</small></li>
-                        <li class="list-group-item"><a href="" class="text-danger pull-right">移除</a>3 李四 <small class="text-muted">监理</small></li>
+                    <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 %>">
+                            <a href="javascript: void(0)" class="text-danger pull-right">移除</a>
+                            <%- auditorList[i].audit_order %> <%- auditorList[i].name %>
+                            <small class="text-muted"><%- auditorList[i].role %></small>
+                        </li>
+                        <% } %>
                     </ul>
                 </div>
             </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-primary">确认上报</button>
+            <form class="modal-footer" method="post" action="/tender/<%- ctx.tender.id %>/revise/audit/start" onsubmit="return checkAuditorFrom()">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
-            </div>
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
+                <button class="btn btn-primary" type="submit">确认上报</button>
+            </form>
         </div>
     </div>
 </div>
-<% } else if (revise.status === audit.status.checkNo) { %>
-<!--重新审批-->
+<% } %>
+<% if (revise.status === audit.status.checkNo && ctx.session.sessionUser.accountId === revise.uid) { %>
+<!--重新上报-->
 <div class="modal fade" id="sub-sp2" data-backdrop="static">
     <div class="modal-dialog" role="document">
         <div class="modal-content">
@@ -84,114 +90,192 @@
                 <h5 class="modal-title">重新上报</h5>
             </div>
             <div class="modal-body">
-                <div class="alert alert-primary" role="alert">
-                    重新上报后,将由 王五 继续审批
-                </div>
-                <div class="card mt-3">
-                    <div class="card-header">
-                        审批流程
+                <div class="row">
+                    <div class="col-4">
+                        <a href="#sub-sp" data-toggle="modal" data-target="#sub-sp" id="hideSp">修改审批流程</a>
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush">
+                                <li class="list-group-item">
+                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- user.name %>  <small class="text-muted"><%- user.role %></small>
+                                </li>
+                            </ul>
+                            <ul class="list-group list-group-flush" id="auditors-list">
+                                <% for (let i = 0; i < auditorList.length; i++) { %>
+                                <li class="list-group-item" data-auditid="<%- auditorList[i].audit_id %>">
+                                    <% if (i < auditorList.length - 1) { %>
+                                    <i class="fa fa-chevron-circle-down"></i> <%- auditorList[i].name %>  <small class="text-muted"><%- auditorList[i].role %></small>
+                                    <% } else {%>
+                                    <i class="fa fa fa-stop-circle"></i> <%- auditorList[i].name %>  <small class="text-muted"><%- auditorList[i].role %></small>
+                                    <% } %>
+                                </li>
+                                <% } %>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto">
+                        <% for (const ah of auditHistory) { %>
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush">
+                                <% for (let iA = 0; iA < ah.length; iA++) { %>
+                                <% if (iA === 0) { %>
+                                <li class="list-group-item">
+                                    <span class="text-success pull-right"><% if (auditHistory.indexOf(ah) > 0) { %>重新<% } %>上报</span>
+                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- user.name %> <small class="text-muted"><%- user.role %></small></h5>
+                                    <p class="card-text"><small class="text-muted"><%- ah[iA].begin_time.toLocaleDateString() %></small></p>
+                                </li>
+                                <li class="list-group-item">
+                                    <% if (ah[iA].status !== audit.status.uncheck) { %>
+                                    <span class="<%- audit.statusClass[ah[iA].status] %> pull-right"><%- audit.statusString[ah[iA].status]%><% if (ah[iA].status === audit.status.checkNo) { %> <%- user.name %><% } %></span>
+                                    <% } %>
+                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down <%- audit.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
+                                    <% if (ah[iA].status === audit.status.checked || ah[iA].status === audit.status.checkNo) { %>
+                                    <p class="card-text mb-1"><%- ah[iA].opinion %></p>
+                                    <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
+                                    <% } %>
+                                </li>
+                                <% } else if (iA === ah.length - 1) { %>
+                                <li class="list-group-item">
+                                    <% if (ah[iA].status !== audit.status.uncheck) { %>
+                                    <span class="<%- audit.statusClass[ah[iA].status] %> pull-right"><%- audit.statusString[ah[iA].status]%><% if (ah[iA].status === audit.status.checkNo) { %> <%- user.name %><% } %></span>
+                                    <% } %>
+                                    <h5 class="card-title"><i class="fa fa-stop-circle <%- audit.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
+                                    <% if (ah[iA].status === audit.status.checked || ah[iA].status === audit.status.checkNo) { %>
+                                    <p class="card-text mb-1"><%- ah[iA].opinion %></p>
+                                    <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
+                                    <% } %>
+                                </li>
+                                <% } else { %>
+                                <li class="list-group-item">
+                                    <% if (ah[iA].status !== audit.status.uncheck) { %>
+                                    <span class="<%- audit.statusClass[ah[iA].status] %> pull-right"><%- audit.statusString[ah[iA].status]%><% if (ah[iA].status === audit.status.checkNo) { %> <%- user.name %><% } %></span>
+                                    <% } %>
+                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down <%- audit.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
+                                    <% if (ah[iA].status === audit.status.checked || ah[iA].status === audit.status.checkNo) { %>
+                                    <p class="card-text mb-1"><%- ah[iA].opinion %></p>
+                                    <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
+                                    <% } %>
+                                </li>
+                                <% } %>
+                                <% } %>
+                            </ul>
+                        </div>
+                        <% } %>
+                        <% if (revise.status === audit.status.checkNo) {%>
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush">
+                                <li class="list-group-item">
+                                    <span class="pull-right">重新上报中</span>
+                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90"></i> <%- user.name %> <small class="text-muted"><%- user.role %></small></h5>
+                                    <p class="card-text"><small class="text-muted"></small></p>
+                                </li>
+                            </ul>
+                            <ul class="list-group list-group-flush" id="auditors-list2">
+                                <% for (let iA = 0; iA < auditorList.length; iA++) { %>
+                                <% if (iA === auditorList.length - 1) { %>
+                                <li class="list-group-item" data-auditid="<%- auditorList[iA].audit_id %>">
+                                    <h5 class="card-title"><i class="fa fa-stop-circle"></i> <%- auditorList[iA].name %> <small class="text-muted"><%- auditorList[iA].role %></small></h5>
+                                </li>
+                                <% } else { %>
+                                <li class="list-group-item" data-auditid="<%- auditorList[iA].audit_id %>">
+                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down"></i> <%- auditorList[iA].name %> <small class="text-muted"><%- auditorList[iA].role %></small></h5>
+                                </li>
+                                <% } %>
+                                <% } %>
+                            </ul>
+                        </div>
+                        <% } %>
                     </div>
-                    <ul class="list-group list-group-flush">
-                        <li class="list-group-item">
-                            <span class="text-success pull-right">审批通过</span>
-                            <h5 class="card-title">1 张三 <small class="text-muted">监理</small></h5>
-                            <p class="card-text">审批意见。2018-01-01</p>
-                        </li>
-                        <li class="list-group-item">
-                            <span class="text-warning pull-right">退回</span>
-                            <h5 class="card-title">2 王五 <small class="text-muted">监理</small></h5>
-                            <p class="card-text"></p>
-                        </li>
-                        <li class="list-group-item">
-                            <h5 class="card-title">3 李四 <small class="text-muted">监理</small></h5>
-                            <p class="card-text"></p>
-                        </li>
-                    </ul>
                 </div>
             </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-primary">确认上报</button>
+            <form class="modal-footer" action="/tender/<%- ctx.tender.id %>/revise/audit/start" method="post" onsubmit="return checkAuditorFrom()">
+                <button type="submit" class="btn btn-primary">确认上报</button>
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
-            </div>
+            </form>
         </div>
     </div>
 </div>
-<% } else if (revise.status === audit.status.checking) { %>
+<% } %>
+<% if (revise.status === audit.status.checking && curAuditor.audit_id === ctx.session.sessionUser.accountId) { %>
 <!--审批通过-->
 <div class="modal fade" id="sp-done" data-backdrop="static">
     <div class="modal-dialog" role="document">
-        <div class="modal-content">
+        <form class="modal-content" action="/tender/<%- ctx.tender.id %>/revise/audit/check" method="post">
             <div class="modal-header">
                 <h5 class="modal-title">审批通过</h5>
             </div>
             <div class="modal-body">
                 <div class="card mt-3">
                     <ul class="list-group list-group-flush">
-                        <li class="list-group-item">
-                            <span class="text-success pull-right">审批通过</span>
-                            <h5 class="card-title">1 张三 <small class="text-muted">监理</small></h5>
-                            <p class="card-text">审批意见。2018-01-01</p>
-                        </li>
-                        <li class="list-group-item">
-                            <span class="text-warning pull-right">审批中</span>
-                            <h5 class="card-title">2 王五 <small class="text-muted">监理</small></h5>
+                        <% for (let i = 0, iLen = auditors.length; i < iLen; i++) { %>
+                        <li class="list-group-item" auditorId="<%- auditors[i].audit_id %>">
+                            <% if (auditors[i].status !== audit.status.uncheck) { %>
+                            <span class="<%- audit.statusClass[auditors[i].status] %> pull-right"><%- audit.statusString[auditors[i].status] %></span>
+                            <% } %>
+                            <h5 class="card-title"><%- auditors[i].audit_order %> <%- auditors[i].name %> <small class="text-muted"><%- auditors[i].role %></small></h5>
+                            <% if (auditors[i].status === audit.status.checked) { %>
+                            <p class="card-text"><%- auditors[i].opinion %> <%- auditors[i].end_time ? auditors[i].end_time.toLocaleString() : '' %></p>
+                            <% } else if (auditors[i].status === audit.status.checking) { %>
                             <div class="form-group">
                                 <label>审批意见<b class="text-danger">*</b></label>
-                                <textarea class="form-control" ></textarea>
+                                <textarea class="form-control" name="opinion">同意</textarea>
                             </div>
+                            <% } %>
                         </li>
-                        <li class="list-group-item">
-                            <h5 class="card-title">3 李四 <small class="text-muted">监理</small></h5>
-                        </li>
+                        <% } %>
                     </ul>
                 </div>
             </div>
             <div class="modal-footer">
-                <button type="button" class="btn btn-success" >确认通过</button>
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+                <input type="hidden" name="checkType" value="<%= audit.status.checked %>" />
+                <button type="submit" class="btn btn-success" >确认通过</button>
             </div>
-        </div>
+        </form>
     </div>
 </div>
 <!--审批退回-->
 <div class="modal fade" id="sp-back" data-backdrop="static">
     <div class="modal-dialog" role="document">
-        <div class="modal-content">
+        <form class="modal-content" action="/tender/<%- ctx.tender.id %>/revise/audit/check" method="post">
             <div class="modal-header">
                 <h5 class="modal-title">审批退回</h5>
             </div>
             <div class="modal-body">
                 <div class="card mt-3">
                     <ul class="list-group list-group-flush">
-                        <li class="list-group-item">
-                            <span class="text-success pull-right">审批通过</span>
-                            <h5 class="card-title">1 张三 <small class="text-muted">监理</small></h5>
-                            <p class="card-text">审批意见。2018-01-01</p>
-                        </li>
-                        <li class="list-group-item">
-                            <span class="text-warning pull-right">审批中</span>
-                            <h5 class="card-title">2 王五 <small class="text-muted">监理</small></h5>
+                        <% for (let i = 0, iLen = auditors.length; i < iLen; i++) { %>
+                        <li class="list-group-item" auditorId="<%- auditors[i].audit_id %>">
+                            <% if (auditors[i].status !== audit.status.uncheck) { %>
+                            <span class="<%- audit.statusClass[auditors[i].status] %> pull-right"><%- audit.statusString[auditors[i].status] %></span>
+                            <% } %>
+                            <h5 class="card-title"><%- auditors[i].audit_order %> <%- auditors[i].name %> <small class="text-muted"><%- auditors[i].role %></small></h5>
+                            <% if (auditors[i].status === audit.status.checked) { %>
+                            <p class="card-text"><%- auditors[i].opinion %> <%- auditors[i].end_time ? auditors[i].end_time.toLocaleString() : '' %></p>
+                            <% } else if (auditors[i].status === audit.status.checking) { %>
                             <div class="form-group">
                                 <label>审批意见<b class="text-danger">*</b></label>
-                                <textarea class="form-control" ></textarea>
+                                <textarea class="form-control" name="opinion">不同意</textarea>
                             </div>
-                            <div class="alert alert-warning">审批退回,将直接退回给上报人。</data-min-view>
-                        </li>
-                        <li class="list-group-item">
-                            <h5 class="card-title">3 李四 <small class="text-muted">监理</small></h5>
+                            <div class="alert alert-warning">审批退回,将直接退回给上报人。</div>
+                            <% } %>
                         </li>
+                        <% } %>
                     </ul>
                 </div>
             </div>
             <div class="modal-footer">
-                <button type="button" class="btn btn-warning" >确认退回</button>
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+                <input type="hidden" name="checkType" value="<%= audit.status.checkNo %>" />
+                <button type="submit" class="btn btn-warning" >确认退回</button>
             </div>
-        </div>
+        </form>
     </div>
 </div>
 <% } %>
-<% if (revise.status !== audit.status.uncheck) { %>
+<% if (revise.status !== audit.status.uncheck || revise.times > 1) { %>
 <!--审批流程/结果-->
 <div class="modal fade" id="sp-list" data-backdrop="static">
     <div class="modal-dialog" role="document">
@@ -200,22 +284,99 @@
                 <h5 class="modal-title">审批流程</h5>
             </div>
             <div class="modal-body">
-                <div class="card mt-3">
-                    <ul class="list-group list-group-flush">
-                        <li class="list-group-item">
-                            <span class="text-success pull-right">审批通过</span>
-                            <h5 class="card-title">1 张三 <small class="text-muted">监理</small></h5>
-                            <p class="card-text">审批意见。2018-01-01</p>
-                        </li>
-                        <li class="list-group-item">
-                            <span class="text-warning pull-right">审批中</span>
-                            <h5 class="card-title">2 王五 <small class="text-muted">监理</small></h5>
-                            <p class="card-text"></p>
-                        </li>
-                        <li class="list-group-item">
-                            <h5 class="card-title">3 李四 <small class="text-muted">监理</small></h5>
-                        </li>
-                    </ul>
+                <div class="row">
+                    <div class="col-4">
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush">
+                                <li class="list-group-item">
+                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- user.name %>  <small class="text-muted"><%- user.role %></small>
+                                </li>
+                                <% for (let i = 0; i < auditors.length; i++) { %>
+                                <li class="list-group-item">
+                                    <% if (i < auditors.length - 1) { %>
+                                    <i class="fa fa-chevron-circle-down"></i> <%- auditors[i].name %>  <small class="text-muted"><%- auditors[i].role %></small>
+                                    <% } else {%>
+                                    <i class="fa fa fa-stop-circle"></i> <%- auditors[i].name %>  <small class="text-muted"><%- auditors[i].role %></small>
+                                    <% } %>
+                                </li>
+                                <% } %>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto">
+                        <% for (const ah of auditHistory) { %>
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush">
+                                <li class="list-group-item">
+                                    <span class="text-success pull-right"><% if (auditHistory.indexOf(ah) > 0) { %>重新<% } %>上报</span>
+                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- user.name %> <small class="text-muted"><%- user.role %></small></h5>
+                                    <p class="card-text"><small class="text-muted"><%- ah[0].begin_time.toLocaleDateString() %></small></p>
+                                </li>
+                                <% for (let iA = 0; iA < ah.length; iA++) { %>
+                                <% if (iA === ah.length - 1) { %>
+                                <li class="list-group-item">
+                                    <% if (ah[iA].status !== audit.status.uncheck) { %>
+                                    <span class="<%- audit.statusClass[ah[iA].status] %> pull-right"><%- audit.statusString[ah[iA].status]%><% if (ah[iA].status === audit.status.checkNo) { %> <%- user.name %><% } %></span>
+                                    <% } %>
+                                    <h5 class="card-title"><i class="fa fa-stop-circle <%- audit.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
+                                    <% if (ah[iA].status === audit.status.checked || ah[iA].status === audit.status.checkNo) { %>
+                                    <p class="card-text mb-1"><%- ah[iA].opinion %></p>
+                                    <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
+                                    <% } %>
+                                </li>
+                                <% } else { %>
+                                <li class="list-group-item">
+                                    <% if (ah[iA].status !== audit.status.uncheck) { %>
+                                    <span class="<%- audit.statusClass[ah[iA].status] %> pull-right"><%- audit.statusString[ah[iA].status]%><% if (ah[iA].status === audit.status.checkNo) { %> <%- user.name %><% } %></span>
+                                    <% } %>
+                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down <%- audit.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
+                                    <% if (ah[iA].status === audit.status.checked || ah[iA].status === audit.status.checkNo) { %>
+                                    <p class="card-text mb-1"><%- ah[iA].opinion %></p>
+                                    <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
+                                    <% } %>
+                                </li>
+                                <% } %>
+                                <% } %>
+                            </ul>
+                        </div>
+                        <% } %>
+                        <% if (revise.status === audit.status.checking || revise.status === audit.status.checked) {%>
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush">
+                                <li class="list-group-item">
+                                    <span class="text-success pull-right"><% if (revise.times > 1) { %>重新<% } %>上报</span>
+                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- user.name %> <small class="text-muted"><%- user.role %></small></h5>
+                                    <p class="card-text"><small class="text-muted"><%- auditors[0].begin_time ? auditors[0].begin_time.toLocaleDateString() : ''%></small></p>
+                                </li>
+                                <% for (let iA = 0; iA < auditors.length; iA++) { %>
+                                <% if (iA === auditors.length - 1) { %>
+                                <li class="list-group-item">
+                                    <% if (auditors[iA].status !== audit.status.uncheck) { %>
+                                    <span class="<%- audit.statusClass[auditors[iA].status] %> pull-right"><%- audit.statusString[auditors[iA].status]%><% if (auditors[iA].status === audit.status.checkNo) { %> <%- user.name %><% } %></span>
+                                    <% } %>
+                                    <h5 class="card-title"><i class="fa fa-stop-circle <%- audit.statusClass[auditors[iA].status] %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small></h5>
+                                    <% if (auditors[iA].status === audit.status.checked || auditors[iA].status === audit.status.checkNo) { %>
+                                    <p class="card-text mb-1"><%- auditors[iA].opinion %></p>
+                                    <p class="card-text"><small class="text-muted"><%- auditors[iA].end_time.toLocaleDateString() %></small></p>
+                                    <% } %>
+                                </li>
+                                <% } else { %>
+                                <li class="list-group-item">
+                                    <% if (auditors[iA].status !== audit.status.uncheck) { %>
+                                    <span class="<%- audit.statusClass[auditors[iA].status] %> pull-right"><%- audit.statusString[auditors[iA].status]%><% if (auditors[iA].status === audit.status.checkNo) { %> <%- user.name %><% } %></span>
+                                    <% } %>
+                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down <%- audit.statusClass[auditors[iA].status] %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small></h5>
+                                    <% if (auditors[iA].status === audit.status.checked || auditors[iA].status === audit.status.checkNo) { %>
+                                    <p class="card-text mb-1"><%- auditors[iA].opinion %></p>
+                                    <p class="card-text"><small class="text-muted"><%- auditors[iA].end_time.toLocaleDateString() %></small></p>
+                                    <% } %>
+                                </li>
+                                <% } %>
+                                <% } %>
+                            </ul>
+                        </div>
+                        <% } %>
+                    </div>
                 </div>
             </div>
             <div class="modal-footer">
@@ -224,4 +385,101 @@
         </div>
     </div>
 </div>
-<% } %>
+<% } %>
+<script>
+    <% if (ctx.session.sessionUser.accountId === revise.uid && (revise.status === audit.status.uncheck || revise.status === audit.status.checkNo)) { %>
+    const accountList = JSON.parse('<%- JSON.stringify(accountList) %>');
+    // 审批人分组选择
+    $('#account_group').change(function () {
+        let account_html = '<option value="0">选择审批人</option>';
+        for (const account of accountList) {
+            if (parseInt($(this).val()) === 0 || parseInt($(this).val()) === account.account_group) {
+                const role = account.role !== '' ? '(' + account.role + ')' : '';
+                const company = account.company !== '' ? ' -' + account.company : '';
+                account_html += '<option value="' + account.id + '">' + account.name + role + company + '</option>';
+            }
+        }
+        $('#account_list').html(account_html);
+    });
+    // 添加到审批流程中
+    $('body').on('change', '#account_list', function () {
+        let id = $(this).val();
+        id = parseInt(id);
+        if (id !== 0) {
+            const data = {
+                auditorId: $(this).val(),
+            };
+            postData('/tender/<%- ctx.tender.id %>/revise/audit/add', data, (data) => {
+                const html = [];
+                html.push('<li class="list-group-item" auditorId="' + data.audit_id + '"><a href="javascript: void(0)" class="text-danger pull-right">移除</a>');
+                html.push('<span>');
+                html.push(data.audit_order + ' ');
+                html.push(data.name + ' ');
+                html.push('</span>');
+                html.push('<small class="text-muted">');
+                html.push(data.role);
+                html.push('</small></li>');
+                $('#auditors').append(html.join(''));
+
+                // 如果是重新上报,添加到重新上报列表中
+                const auditorshtml = [];
+                // 重新上报时。令其它的审批人流程图标转换
+                $('#auditors-list li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
+                // 添加新审批人
+                auditorshtml.push('<li class="list-group-item" data-auditid="' + data.audit_id + '">');
+                auditorshtml.push('<i class="fa fa-stop-circle"></i> ');
+                auditorshtml.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
+                auditorshtml.push('</li>');
+                $('#auditors-list').append(auditorshtml.join(''));
+
+                const auditorshtml2 = [];
+                // 重新上报时。令其它的审批人流程图标转换
+                $('#auditors-list2 li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
+                // 添加新审批人
+                auditorshtml2.push('<li class="list-group-item" data-auditid="' + data.audit_id + '">');
+                auditorshtml2.push('<h5 class="card-title"><i class="fa fa-stop-circle"></i> ');
+                auditorshtml2.push(data.name + ' <small class="text-muted">' + data.role + '</small></h5>');
+                auditorshtml2.push('</li>');
+                $('#auditors-list2').append(auditorshtml2.join(''));
+            });
+        }
+    });
+    $('body').on('click', '#auditors li>a', function () {
+        const li = $(this).parent();
+        const data = {
+            auditorId: parseInt(li.attr('auditorId')),
+        };
+        postData('/tender/<%- ctx.tender.id %>/revise/audit/remove', data, (data2) => {
+            li.remove();
+            for (const a of data2) {
+                const aLi = $('li[auditorId=' + a.audit_id + ']');
+                $('span', aLi).text(a.audit_order + ' ' + a.name + ' ');
+            }
+
+            // 如果是重新上报
+            // 令最后一个图标转换
+            $('#auditors-list li[data-auditid="' + data.auditorId + '"]').remove();
+            if ($('#auditors-list li').length !== 0 && !$('#auditors-list li i').hasClass('fa-stop-circle')) {
+                $('#auditors-list li').eq($('#auditors-list li').length-1).children('i')
+                    .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
+            }
+            $('#auditors-list2 li[data-auditid="' + data.auditorId + '"]').remove();
+            if ($('#auditors-list2 li').length !== 0 && !$('#auditors-list2 li i').hasClass('fa-stop-circle')) {
+                $('#auditors-list2 li').eq($('#auditors-list2 li').length-1).children('i')
+                    .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
+            }
+        });
+    });
+    $('#hideSp').click(function () {
+        $('#sub-sp2').modal('hide');
+    });
+    <% } %>
+
+    // 检查上报情况
+    function checkAuditorFrom () {
+        if ($('#auditors li').length === 0) {
+            toast('请先选择审批人,再上报数据', 'error', 'exclamation-circle');
+            return false;
+        }
+    }
+</script>