laiguoran 2 лет назад
Родитель
Сommit
bfb408bb6f

+ 11 - 0
app/const/audit.js

@@ -115,6 +115,7 @@ const stage = (function() {
         checkNo: 4, // 审批退回原报
         checkNoPre: 5, // 审批退回上一人
         checkAgain: 6, // 重新审批 // 该状态仅可用于,终审退回时,修改原终审的审批状态,并同时新增一条新的终审审批中记录
+        checkCancel: 7, // 撤回 // 该状态为上一审批人可发起,回到它到审批阶段,并同时新增一条新的审批中记录
     };
 
     // 流程状态提示
@@ -125,6 +126,7 @@ const stage = (function() {
     statusString[status.checkNo] = '审批退回';
     statusString[status.checkNoPre] = '审批退回';
     statusString[status.checkAgain] = '重新审批';
+    statusString[status.checkCancel] = '撤回';
 
     // 流程状态样式
     const statusClass = [];
@@ -134,6 +136,7 @@ const stage = (function() {
     statusClass[status.checkNo] = 'text-warning';
     statusClass[status.checkNoPre] = 'text-warning';
     statusClass[status.checkAgain] = 'text-warning';
+    statusClass[status.checkCancel] = 'text-warning';
 
     /**
      * 期列表,审批状态一列
@@ -146,6 +149,7 @@ const stage = (function() {
     statusButton[status.checkNo] = '重新上报';
     statusButton[status.checkNoPre] = '重新审批';
     statusButton[status.checkAgain] = '重新审批';
+    statusButton[status.checkCancel] = '撤回';
     // 按钮样式
     const statusButtonClass = [];
     statusButtonClass[status.uncheck] = 'btn-primary';
@@ -154,6 +158,7 @@ const stage = (function() {
     statusButtonClass[status.checkNo] = 'btn-warning';
     statusButtonClass[status.checkNoPre] = 'btn-warning';
     statusButtonClass[status.checkAgain] = 'btn-warning';
+    statusButtonClass[status.checkCancel] = 'btn-warning';
     // 描述文本
     const auditString = [];
     auditString[status.uncheck] = '';
@@ -162,6 +167,7 @@ const stage = (function() {
     auditString[status.checkNo] = '审批退回';
     auditString[status.checkNoPre] = '审批退回';
     auditString[status.checkAgain] = '重新审批';
+    auditString[status.checkCancel] = '撤回';
     // 文字样式
     const auditStringClass = [];
     auditStringClass[status.uncheck] = '';
@@ -170,6 +176,7 @@ const stage = (function() {
     auditStringClass[status.checkNo] = 'text-warning';
     auditStringClass[status.checkNoPre] = 'text-warning';
     auditStringClass[status.checkAgain] = 'text-warning';
+    auditStringClass[status.checkCancel] = 'text-warning';
     /* ------------------------------------------------------- */
 
     /**
@@ -183,6 +190,7 @@ const stage = (function() {
     auditProgress[status.checkNo] = '审批退回';
     auditProgress[status.checkNoPre] = '审批退回';
     auditProgress[status.checkAgain] = '重新审批';
+    auditProgress[status.checkCancel] = '撤回';
     // 样式
     const auditProgressClass = [];
     auditProgressClass[status.uncheck] = '';
@@ -191,6 +199,7 @@ const stage = (function() {
     auditProgressClass[status.checkNo] = 'text-warning';
     auditProgressClass[status.checkNoPre] = 'text-warning';
     auditProgressClass[status.checkAgain] = 'text-warning';
+    auditProgressClass[status.checkCancel] = 'text-warning';
     /* ------------------------------------------------------- */
 
     const tiStatusString = [];
@@ -200,6 +209,7 @@ const stage = (function() {
     tiStatusString[status.checkNo] = '审批退回';
     tiStatusString[status.checkNoPre] = '审批中';
     tiStatusString[status.checkAgain] = '审批中';
+    tiStatusString[status.checkCancel] = '撤回';
     const tiStatusStringClass = [];
     tiStatusStringClass[status.uncheck] = '';
     tiStatusStringClass[status.checking] = 'text-warning';
@@ -207,6 +217,7 @@ const stage = (function() {
     tiStatusStringClass[status.checkNo] = 'text-warning';
     tiStatusStringClass[status.checkNoPre] = 'text-warning';
     tiStatusStringClass[status.checkAgain] = 'text-warning';
+    tiStatusStringClass[status.checkCancel] = 'text-warning';
     const backType = {
         org: 1,
         pre: 2,

+ 33 - 0
app/controller/stage_controller.js

@@ -1400,6 +1400,39 @@ module.exports = app => {
         }
 
         /**
+         * 撤回审批
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async checkAuditCancel(ctx) {
+            try {
+                if (ctx.stage.revising) {
+                    throw '台账修订中,请勿修改提交期数据';
+                }
+                if (ctx.stage.cancancel) {
+                    await ctx.service.stageAudit.checkCancel(ctx.stage.id, ctx.stage.times);
+                    // ctx.redirect(ctx.request.header.referer);
+                    ctx.body = {
+                        err: 0,
+                        url: ctx.request.header.referer,
+                        msg: '',
+                    };
+                } else {
+                    throw '您无权进行该操作';
+                }
+            } catch (err) {
+                this.log(err);
+                // ctx.session.postError = err.toString();
+                // ctx.redirect(ctx.request.header.referer);
+                ctx.body = {
+                    err: 1,
+                    // url: ctx.request.header.referer,
+                    msg: err,
+                };
+            }
+        }
+
+        /**
          * 清单汇总 页面 (Get)
          * @param ctx
          * @return {Promise<void>}

+ 33 - 1
app/middleware/stage_check.js

@@ -88,9 +88,41 @@ module.exports = options => {
                 stage.readOnly = stage.curAuditor.aid !== accountId && !ass;
                 if (stage.readOnly) stage.assist = ass;
             }
-            if (stage.readOnly)  {
+            if (stage.readOnly) {
                 stage.assist = accountId === stage.user_id || auditorIds.indexOf(accountId) >= 0 ? null : auditAssists.find(x => { return x.ass_user_id === accountId});
             }
+
+            // 获取当前审批人的上一个审批人,判断是否是当前登录人,并赋予撤回功能,(当审批人存在有审批过时,上一人不允许再撤回)
+            stage.cancancel = 0;
+            if (stage.status !== status.checked && stage.status !== status.uncheck) {
+                if (stage.status !== status.checkNo) {
+                    // 找出当前操作人上一个审批人,包括审批完成的和退回上一个审批人的,同时当前操作人为第一人时,就是则为原报
+                    const onAuditor = _.find(stage.auditors, function(item) {
+                        return item.aid === stage.curAuditor.aid && item.status === status.checking;
+                    });
+                    const preAudit = onAuditor.order !== 1 ? _.find(stage.auditors, { order: onAuditor.order - 1 }) : false;
+                    const preAid = preAudit ? (preAudit.status !== status.checkAgain ? preAudit.aid : false) : stage.user_id;// 已发起重审无法撤回
+                    if (onAuditor.aid === preAid && preAudit.status === status.checkCancel) {
+                        stage.cancancel = 0;// 不可以多次撤回
+                    } else if (preAid === accountId && preAid !== stage.user_id) {
+                        if (preAudit.status === status.checked) {
+                            stage.cancancel = 2;// 审批人撤回审批通过
+                        } else if (preAudit.status === status.checkNoPre) {
+                            stage.cancancel = 3;// 审批人撤回审批退回上一人
+                        }
+                        stage.preAudit = preAudit;
+                    } else if (preAid === accountId && preAid === stage.user_id) {
+                        stage.cancancel = 1;// 原报撤回
+                    }
+                } else {
+                    const lastAuditors = yield this.service.stageAudit.getAuditors(stage.id, stage.times - 1);
+                    const onAuditor = _.find(lastAuditors, { status: status.checkNo });
+                    if (onAuditor.aid === accountId) {
+                        stage.cancancel = 4;// 审批人撤回退回原报
+                    }
+                }
+            }
+
             const permission = this.session.sessionUser.permission;
             if (accountId === stage.user_id || userAssistIds.indexOf(accountId) >= 0) { // 原报
                 stage.curTimes = stage.times;

+ 1 - 0
app/router.js

@@ -340,6 +340,7 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/audit/start', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.startAudit');
     app.post('/tender/:id/measure/stage/:order/audit/check', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.checkAudit');
     app.get('/tender/:id/measure/stage/:order/audit/check/again', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.checkAuditAgain');
+    app.get('/tender/:id/measure/stage/:order/audit/check/cancel', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.checkAuditCancel');
 
     // 部位台账
     app.get('/tender/:id/measure/stage/:order/bwtz', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.bwtz');

+ 1 - 1
app/service/material_bills.js

@@ -413,7 +413,7 @@ module.exports = app => {
                     await this.ctx.service.materialStage.updateMtp(transaction, ms_id);
                     result.stageBillsData = await transaction.select(this.ctx.service.materialStageBills.tableName, { where: { mid: this.ctx.material.id } });
                     result.stageData = await transaction.select(this.ctx.service.materialStage.tableName, { where: { mid: this.ctx.material.id } });
-                    result.billsData = await transaction.select(this.tableName, { where: { mid: this.ctx.material.id } });
+                    result.billsData = await transaction.select(this.tableName, { where: { mid: this.ctx.material.id }, orders: [['order', 'asc']] });
                 }
                 result.m_tp = await this.calcMaterialMTp(transaction);
                 await transaction.commit();

+ 315 - 2
app/service/stage_audit.js

@@ -86,10 +86,10 @@ module.exports = app => {
         async getAuditors(stageId, times = 1, order_sort = 'asc') {
             const sql =
                 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, pa.sign_path, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
-                'FROM ?? AS la, ?? AS pa, (SELECT sa.`aid`,(@i:=@i+1) as `sort` FROM (SELECT * FROM ?? ORDER BY `order` ASC) sa, (select @i:=0) as it WHERE sa.`sid` = ? AND sa.`times` = ? GROUP BY sa.`aid`) as g ' +
+                'FROM ?? AS la, ?? AS pa, (SELECT sa.`aid`,(@i:=@i+1) as `sort` FROM (SELECT * FROM ?? WHERE `sid` = ? AND `times` = ? GROUP BY `aid` ORDER BY `order` ASC) sa, (select @i:=0) as it WHERE sa.`sid` = ? AND sa.`times` = ? GROUP BY sa.`aid`) as g ' +
                 'WHERE la.`sid` = ? and la.`times` = ? and la.`aid` = pa.`id` and g.`aid` = la.`aid` order by la.`order` ' +
                 order_sort;
-            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, stageId, times, stageId, times];
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, stageId, times, stageId, times, stageId, times];
             const result = await this.db.query(sql, sqlParam);
             const sql2 = 'SELECT COUNT(a.`aid`) as num FROM (SELECT `aid` FROM ?? WHERE `sid` = ? AND `times` = ? GROUP BY `aid`) as a';
             const sqlParam2 = [this.tableName, stageId, times];
@@ -977,6 +977,319 @@ module.exports = app => {
         }
 
         /**
+         * 审批撤回
+         * @param {Number} stageId - 标段id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<void>}
+         */
+        async checkCancel(stageId, times = 1) {
+            // 分3种情况,根据ctx.cancancel值判断:
+            // 1.原报发起撤回,当前流程删除,并回到待上报
+            // 2.审批人撤回审批通过,增加流程,并回到它审批中
+            // 3.审批人撤回审批退回上一人,并删除退回人,增加流程,并回到它审批中,并更新计量期状态为审批中
+            // 4.审批人撤回退回原报操作,删除新增的审批流,增加流程,回滚到它审批中
+            const transaction = await this.db.beginTransaction();
+            const time = new Date();
+            try {
+                if (this.ctx.stage.cancancel === 1) {
+                    // 原报撤回,判断是否为多次,多次则为退回状态
+                    // 整理当前流程审核人状态更新
+                    const curAudit = await this.getDataByCondition({ sid: stageId, times, status: auditConst.status.checking });
+                    // // 审批人变成待审批状态
+                    await transaction.update(this.tableName, {
+                        id: curAudit.id,
+                        status: auditConst.status.uncheck,
+                        begin_time: null,
+                        opinion: null,
+                    });
+                    const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+                    // 计算并合同支付最终数据
+                    const [yfPay, sfPay] = await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                    this.ctx.stage.tp_history.push({
+                        times,
+                        order: curAudit.order,
+                        contract_tp: tpData.contract_tp,
+                        qc_tp: tpData.qc_tp,
+                        positive_qc_tp: tpData.positive_qc_tp,
+                        negative_qc_tp: tpData.negative_qc_tp,
+                        yf_tp: yfPay.tp,
+                        sf_tp: sfPay.tp,
+                    });
+                    await transaction.update(this.ctx.service.stage.tableName, {
+                        id: stageId,
+                        contract_tp: tpData.contract_tp,
+                        qc_tp: tpData.qc_tp,
+                        positive_qc_tp: tpData.positive_qc_tp,
+                        negative_qc_tp: tpData.negative_qc_tp,
+                        times,
+                        yf_tp: yfPay.tp,
+                        sf_tp: sfPay.tp,
+                        tp_history: JSON.stringify(this.ctx.stage.tp_history),
+                        cache_time_r: this.ctx.stage.cache_time_l,
+                        status: times === 1 ? auditConst.status.uncheck : auditConst.status.checkNo,
+                    });
+                    // 计算该审批人最终数据
+                    await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                    // 复制一份最新数据给下一人
+                    await this.ctx.service.stagePay.deleteAuditStagePays(this.ctx.stage, this.ctx.stage.times, 1, transaction);
+                    await this.ctx.service.stageJgcl.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageBonus.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageOther.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageSafeProd.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageTempLand.updateHistory(this.ctx.stage, transaction);
+                } else if (this.ctx.stage.cancancel === 2) {
+                    const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+                    // 整理当前流程审核人状态更新
+                    const curAudit = await this.getDataByCondition({ sid: stageId, times, status: auditConst.status.checking });
+                    const preAudit = this.ctx.stage.preAudit;
+                    if (!curAudit || curAudit.order <= 1) {
+                        throw '撤回用户数据错误';
+                    }
+                    // 顺移气候审核人流程顺序
+                    this.initSqlBuilder();
+                    this.sqlBuilder.setAndWhere('sid', { value: this.ctx.stage.id, operate: '=' });
+                    this.sqlBuilder.setAndWhere('order', { value: curAudit.order, operate: '>' });
+                    this.sqlBuilder.setUpdateData('order', { value: 2, selfOperate: '+' });
+                    const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+                    const data = await transaction.query(sql, sqlParam);
+                    // 当前审批人2次添加至流程中
+                    const newAuditors = [];
+                    newAuditors.push({
+                        tid: curAudit.tid,
+                        sid: curAudit.sid,
+                        aid: preAudit.aid,
+                        times: curAudit.times,
+                        order: curAudit.order,
+                        status: auditConst.status.checkCancel,
+                        begin_time: time,
+                        end_time: time,
+                        opinion: '',
+                    });
+                    newAuditors.push({
+                        tid: curAudit.tid,
+                        sid: curAudit.sid,
+                        aid: preAudit.aid,
+                        times: curAudit.times,
+                        order: curAudit.order + 1,
+                        status: auditConst.status.checking,
+                        begin_time: time,
+                    });
+                    await transaction.insert(this.tableName, newAuditors);
+                    // 当前审批人变成待审批
+                    await transaction.update(this.tableName, { id: curAudit.id, order: curAudit.order + 2, begin_time: null, status: auditConst.status.uncheck });
+                    // 计算并合同支付最终数据
+                    const [yfPay, sfPay] = await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                    this.ctx.stage.tp_history.push({
+                        times,
+                        order: curAudit.order,
+                        contract_tp: tpData.contract_tp,
+                        qc_tp: tpData.qc_tp,
+                        positive_qc_tp: tpData.positive_qc_tp,
+                        negative_qc_tp: tpData.negative_qc_tp,
+                        yf_tp: yfPay.tp,
+                        sf_tp: sfPay.tp,
+                    });
+                    // 同步 期信息
+                    await transaction.update(this.ctx.service.stage.tableName, {
+                        id: stageId,
+                        contract_tp: tpData.contract_tp,
+                        qc_tp: tpData.qc_tp,
+                        positive_qc_tp: tpData.positive_qc_tp,
+                        negative_qc_tp: tpData.negative_qc_tp,
+                        times,
+                        yf_tp: yfPay.tp,
+                        sf_tp: sfPay.tp,
+                        tp_history: JSON.stringify(this.ctx.stage.tp_history),
+                        cache_time_r: this.ctx.stage.cache_time_l,
+                    });
+
+                    // 计算该审批人最终数据
+                    await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                    // 复制一份最新数据给下一人
+                    await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, curAudit.order + 1, transaction);
+                    await this.ctx.service.stageJgcl.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageBonus.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageOther.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageSafeProd.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageTempLand.updateHistory(this.ctx.stage, transaction);
+
+                    // 锁定本人数据,保留锁定数据相关确认状态
+                    // await this.ctx.service.stageAuditAss.lockConfirm4CheckNoPre(this.ctx.stage, curAudit.aid, preAudit.aid, transaction);
+                } else if (this.ctx.stage.cancancel === 3) {
+                    const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+                    // 整理当前流程审核人状态更新
+                    const curAudit = await this.getDataByCondition({ sid: stageId, times, status: auditConst.status.checking });
+                    const preAudit = this.ctx.stage.preAudit;
+                    if (!curAudit || curAudit.order <= 1) {
+                        throw '撤回用户数据错误';
+                    }
+                    // // 顺移气候审核人流程顺序
+                    // this.initSqlBuilder();
+                    // this.sqlBuilder.setAndWhere('sid', { value: this.ctx.stage.id, operate: '=' });
+                    // this.sqlBuilder.setAndWhere('order', { value: curAudit.order, operate: '>' });
+                    // this.sqlBuilder.setUpdateData('order', { value: 2, selfOperate: '+' });
+                    // const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+                    // const data = await transaction.query(sql, sqlParam);
+                    // 添加撤回人到审批流程中
+                    const newAuditors = [];
+                    newAuditors.push({
+                        tid: curAudit.tid,
+                        sid: curAudit.sid,
+                        aid: preAudit.aid,
+                        times: curAudit.times,
+                        order: curAudit.order,
+                        status: auditConst.status.checkCancel,
+                        begin_time: time,
+                        end_time: time,
+                        opinion: '',
+                    });
+                    await transaction.insert(this.tableName, newAuditors);
+                    // 删除当前审批人
+                    await transaction.delete(this.tableName, { id: curAudit.id });
+                    // 更新上一个人为审批中
+                    await transaction.update(this.tableName, { begin_time: time, status: auditConst.status.checking }, {
+                        where: {
+                            sid: curAudit.sid,
+                            times: curAudit.times,
+                            order: curAudit.order + 1,
+                        }
+                    });
+                    // 修改stage状态为审批中
+                    await transaction.update(this.ctx.service.stage.tableName, { id: this.ctx.stage.id, status: auditConst.status.checking });
+                    // 计算并合同支付最终数据
+                    const [yfPay, sfPay] = await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                    this.ctx.stage.tp_history.push({
+                        times,
+                        order: curAudit.order,
+                        contract_tp: tpData.contract_tp,
+                        qc_tp: tpData.qc_tp,
+                        positive_qc_tp: tpData.positive_qc_tp,
+                        negative_qc_tp: tpData.negative_qc_tp,
+                        yf_tp: yfPay.tp,
+                        sf_tp: sfPay.tp,
+                    });
+                    // 同步 期信息
+                    await transaction.update(this.ctx.service.stage.tableName, {
+                        id: stageId,
+                        contract_tp: tpData.contract_tp,
+                        qc_tp: tpData.qc_tp,
+                        positive_qc_tp: tpData.positive_qc_tp,
+                        negative_qc_tp: tpData.negative_qc_tp,
+                        times,
+                        yf_tp: yfPay.tp,
+                        sf_tp: sfPay.tp,
+                        tp_history: JSON.stringify(this.ctx.stage.tp_history),
+                        cache_time_r: this.ctx.stage.cache_time_l,
+                    });
+
+                    // 计算该审批人最终数据
+                    await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                    // 复制一份最新数据给下一人
+                    await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, curAudit.order + 1, transaction);
+                    await this.ctx.service.stageJgcl.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageBonus.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageOther.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageSafeProd.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageTempLand.updateHistory(this.ctx.stage, transaction);
+
+                    // 锁定本人数据,保留锁定数据相关确认状态
+                    // await this.ctx.service.stageAuditAss.lockConfirm4CheckNoPre(this.ctx.stage, curAudit.aid, preAudit.aid, transaction);
+                } else if (this.ctx.stage.cancancel === 4) {
+                    // 原报撤回,判断是否为多次,多次则为退回状态
+                    // 整理上一个流程审核人状态更新
+                    const curAudit = await this.getDataByCondition({ sid: stageId, times: times - 1, status: auditConst.status.checkNo });
+                    // 顺移气候审核人流程顺序
+                    this.initSqlBuilder();
+                    this.sqlBuilder.setAndWhere('sid', { value: this.ctx.stage.id, operate: '=' });
+                    this.sqlBuilder.setAndWhere('order', { value: curAudit.order, operate: '>' });
+                    this.sqlBuilder.setUpdateData('order', { value: 2, selfOperate: '+' });
+                    const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+                    const data = await transaction.query(sql, sqlParam);
+                    // 当前审批人2次添加至流程中
+                    const newAuditors = [];
+                    newAuditors.push({
+                        tid: curAudit.tid,
+                        sid: curAudit.sid,
+                        aid: curAudit.aid,
+                        times: curAudit.times,
+                        order: curAudit.order + 1,
+                        status: auditConst.status.checkCancel,
+                        begin_time: time,
+                        end_time: time,
+                        opinion: '',
+                    });
+                    newAuditors.push({
+                        tid: curAudit.tid,
+                        sid: curAudit.sid,
+                        aid: curAudit.aid,
+                        times: curAudit.times,
+                        order: curAudit.order + 2,
+                        status: auditConst.status.checking,
+                        begin_time: time,
+                    });
+                    await transaction.insert(this.tableName, newAuditors);
+                    // 删除当前次审批流
+                    await transaction.delete(this.tableName, { sid: stageId, times });
+                    const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+                    // 计算并合同支付最终数据
+                    const [yfPay, sfPay] = await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                    this.ctx.stage.tp_history.push({
+                        times,
+                        order: curAudit.order,
+                        contract_tp: tpData.contract_tp,
+                        qc_tp: tpData.qc_tp,
+                        positive_qc_tp: tpData.positive_qc_tp,
+                        negative_qc_tp: tpData.negative_qc_tp,
+                        yf_tp: yfPay.tp,
+                        sf_tp: sfPay.tp,
+                    });
+                    await transaction.update(this.ctx.service.stage.tableName, {
+                        id: stageId,
+                        contract_tp: tpData.contract_tp,
+                        qc_tp: tpData.qc_tp,
+                        positive_qc_tp: tpData.positive_qc_tp,
+                        negative_qc_tp: tpData.negative_qc_tp,
+                        times: times - 1,
+                        yf_tp: yfPay.tp,
+                        sf_tp: sfPay.tp,
+                        tp_history: JSON.stringify(this.ctx.stage.tp_history),
+                        cache_time_r: this.ctx.stage.cache_time_l,
+                        status: auditConst.status.checking,
+                    });
+                    // 计算该审批人最终数据
+                    await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                    // 复制一份最新数据给下一人
+                    await this.ctx.service.stagePay.deleteAuditStagePays(this.ctx.stage, this.ctx.stage.times, 0, transaction);
+                    await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times - 1, curAudit.order + 1, transaction);
+                    await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times - 1, curAudit.order + 2, transaction);
+                    await this.ctx.service.stageJgcl.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageBonus.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageOther.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageSafeProd.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageTempLand.updateHistory(this.ctx.stage, transaction);
+                }
+                // 上报/审批 - 检查三方特殊推送
+                await this.ctx.service.specMsg.addStageMsg(transaction, this.ctx.session.sessionProject.id, this.ctx.stage, pushOperate.stage.flow);
+                await transaction.commit();
+                // 通知发送 - 第三方更新
+                if (this.ctx.session.sessionProject.custom && syncApiConst.notice_type.indexOf(this.ctx.session.sessionProject.customType) !== -1) {
+                    const base_data = {
+                        tid: this.ctx.tender.id,
+                        sid: stageId,
+                        op: 'update',
+                    };
+                    this.ctx.helper.syncNoticeSend(this.ctx.session.sessionProject.customType, JSON.stringify(base_data));
+                    base_data.op = 'update';
+                    base_data.sid = -1;
+                    this.ctx.helper.syncNoticeSend(this.ctx.session.sessionProject.customType, JSON.stringify(base_data));
+                }
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
          * 获取审核人需要审核的期列表
          *
          * @param auditorId

+ 15 - 0
app/service/stage_pay.js

@@ -298,6 +298,21 @@ module.exports = app => {
             return await transaction.query(sql, sqlParam);
         }
 
+        /**
+         * 删除操作人数据
+         * @param stage - 期数据
+         * @param times - 操作人 该期第几次
+         * @param order - 操作人顺序
+         * @param transaction - 事务
+         * @returns {Promise<*>}
+         */
+        async deleteAuditStagePays(stage, times, order, transaction) {
+            if (!stage || !transaction || !times || order === undefined) {
+                throw '数据错误';
+            }
+            return await transaction.delete(this.tableName, { sid: stage.id, stimes: times, sorder: order });
+        }
+
         async getLastestPayId(id) {
             const info = await this.getDataById(id);
             const sql = 'SELECT SP.* FROM ?? As SP WHERE SP.`sid` = ? AND SP.`pid` = ? ORDER BY SP.`stimes` DESC, SP.`sorder` DESC';

+ 3 - 0
app/view/stage/audit_btn.ejs

@@ -39,6 +39,9 @@
     <% if (ctx.stage.auditors !== undefined && ctx.stage.auditors.length !== 0 && ctx.stage.auditors[ctx.stage.auditors.length-1].aid === ctx.session.sessionUser.accountId && ctx.stage.status === auditConst.status.checked && ctx.stage.order === ctx.stage.highOrder) { %>
         <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-back" class="btn btn-warning btn-sm btn-block">重新审批</a>
     <% } %>
+    <% if (ctx.stage && ctx.stage.cancancel) { %>
+        <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-cancel" class="btn btn-danger btn-sm btn-block">撤回</a>
+    <% } %>
     <% if (ctx.stage.user_id === ctx.session.sessionUser.accountId && ctx.stage.order === ctx.stage.highOrder && (ctx.stage.status === auditConst.status.checkNo || ctx.stage.status === auditConst.status.uncheck)) { %>
         <a href="#del-qi" data-toggle="modal" data-target="#del-qi" class="btn btn-outline-danger btn-sm btn-block mt-5">删除本期</a>
     <% } %>

+ 52 - 15
app/view/stage/audit_modal.ejs

@@ -157,7 +157,7 @@
                                             <div class="timeline-item-icon bg-success text-light">
                                                 <i class="fa fa-check"></i>
                                             </div>
-                                            <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                            <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre || auditor.status === auditConst.status.checkCancel) {%>
                                             <div class="timeline-item-icon bg-warning text-light">
                                                 <i class="fa fa-level-up"></i>
                                             </div>
@@ -201,7 +201,7 @@
                                             <div class="timeline-item-icon bg-success text-light">
                                                 <i class="fa fa-check"></i>
                                             </div>
-                                            <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                            <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre || auditor.status === auditConst.status.checkCancel) {%>
                                             <div class="timeline-item-icon bg-warning text-light">
                                                 <i class="fa fa-level-up"></i>
                                             </div>
@@ -221,7 +221,7 @@
                                                                 <span class="pull-right <%- auditConst.statusClass[auditor.status] %>">
                                                                     <%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
                                                                     <%- auditor.status === auditConst.status.checkNo ? ctx.stage.user.name : '' %>
-                                                                    <%- auditor.status === auditConst.status.checkNoPre ? auditors[index+1].name : '' %>
+                                                                    <%- auditor.status === auditConst.status.checkNoPre && auditor.sort - 1 > 0 ? ctx.helper._.find(auditors, { sort: auditor.sort - 1 }).name : '' %>
                                                                 </span>
                                                             </p>
                                                             <p class="text-muted mb-0"><%- auditor.role %></p>
@@ -340,7 +340,7 @@
                                         <div class="timeline-item-icon bg-success text-light">
                                             <i class="fa fa-check"></i>
                                         </div>
-                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre || auditor.status === auditConst.status.checkCancel) {%>
                                         <div class="timeline-item-icon bg-warning text-light">
                                             <i class="fa fa-level-up"></i>
                                         </div>
@@ -389,7 +389,7 @@
                                         <div class="timeline-item-icon bg-success text-light">
                                             <i class="fa fa-check"></i>
                                         </div>
-                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre || auditor.status === auditConst.status.checkCancel) {%>
                                         <div class="timeline-item-icon bg-warning text-light">
                                             <i class="fa fa-level-up"></i>
                                         </div>
@@ -409,7 +409,7 @@
                                                             <span class="pull-right <%- auditConst.statusClass[auditor.status] %>">
                                                                 <%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
                                                                 <%- auditor.status === auditConst.status.checkNo ? ctx.stage.user.name : '' %>
-                                                                <%- auditor.status === auditConst.status.checkNoPre ? auditors[index+1].name : '' %>
+                                                                <%- auditor.status === auditConst.status.checkNoPre && auditor.sort - 1 > 0 ? ctx.helper._.find(auditors, { sort: auditor.sort - 1 }).name : '' %>
                                                             </span>
                                                         </p>
                                                         <p class="text-muted mb-0"><%- auditor.role %></p>
@@ -486,6 +486,11 @@
                         </div>
                         <div class="col-8 modal-height-500" style="overflow: auto">
                             <% ctx.stage.auditHistory.forEach((auditors, idx) => { %>
+                            <!-- 展开/收起历史流程 -->
+                            <% if(idx === ctx.stage.auditHistory.length - 1 && ctx.stage.auditHistory.length !== 1) { %>
+                                <div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
+                                                           data-idx="<%- idx + 1 %>">展开历史审批流程</a></div>
+                            <% } %>
                             <div class="<%- idx < ctx.stage.auditHistory.length - 1 ? 'fold-card' : '' %>">
                                 <div class="text-center text-muted"><%- idx+1 %>#</div>
                                 <ul class="timeline-list list-unstyled mt-2">
@@ -524,7 +529,7 @@
                                         <div class="timeline-item-icon bg-success text-light">
                                             <i class="fa fa-check"></i>
                                         </div>
-                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre || auditor.status === auditConst.status.checkCancel) {%>
                                         <div class="timeline-item-icon bg-warning text-light">
                                             <i class="fa fa-level-up"></i>
                                         </div>
@@ -600,7 +605,7 @@
                                         <div class="timeline-item-icon bg-success text-light">
                                             <i class="fa fa-check"></i>
                                         </div>
-                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre || auditor.status === auditConst.status.checkCancel) {%>
                                         <div class="timeline-item-icon bg-warning text-light">
                                             <i class="fa fa-level-up"></i>
                                         </div>
@@ -620,7 +625,7 @@
                                                             <span class="pull-right <%- auditConst.statusClass[auditor.status] %>">
                                                                 <%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
                                                                 <%- auditor.status === auditConst.status.checkNo ? ctx.stage.user.name : '' %>
-                                                                <%- auditor.status === auditConst.status.checkNoPre ? auditors[index+1].name : '' %>
+                                                                <%- auditor.status === auditConst.status.checkNoPre && auditor.sort - 1 > 0 ? ctx.helper._.find(auditors, { sort: auditor.sort - 1 }).name : '' %>
                                                             </span>
                                                         </p>
                                                         <p class="text-muted mb-0"><%- auditor.role %></p>
@@ -672,11 +677,6 @@
                                     <% }) %>
                                 </ul>
                             </div>
-                            <!-- 展开/收起历史流程 -->
-                            <% if(idx === ctx.stage.auditHistory.length - 1 && ctx.stage.auditHistory.length !== 1) { %>
-                            <div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
-                                    data-idx="<%- idx + 1 %>">展开历史审批流程</a></div>
-                            <% } %>
                             <% }) %>
                         </div>
                     </div>
@@ -801,7 +801,24 @@
 </div>
 <% } %>
 <% } %>
-
+<% if (ctx.stage && ctx.stage.cancancel) { %>
+<div class="modal fade" id="sp-down-cancel" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">撤回</h5>
+            </div>
+            <div class="modal-body">
+                <h5>撤回后将回退到你的操作状态,并删除下一个流程中发生变化的数据,确定撤回?</h5>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">取消</button>
+                <button type="submit" class="btn btn-danger btn-sm" id="cancel-shenpi-btn">确定撤回</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% } %>
 
 <% include ../shares/check_data_modal.ejs %>
 <script type="text/javascript">
@@ -928,4 +945,24 @@
                 }
             });
     });
+
+    <% if (ctx.stage && ctx.stage.cancancel) { %>
+    $("#cancel-shenpi-btn").click(function () {
+        const data = {
+        };
+        $.ajax({
+            url: '<%- preUrl %>/audit/check/cancel',
+            type: 'get',
+            data: data,
+            dataTye: 'json',
+            success: function (response) {
+                if (response.err === 0) {
+                    window.location.href = response.url;
+                } else {
+                    toast(response.msg, 'error');
+                }
+            }
+        });
+    });
+    <% } %>
 </script>