浏览代码

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

Tony Kang 1 年之前
父节点
当前提交
e208583e69
共有 48 个文件被更改,包括 4055 次插入493 次删除
  1. 72 0
      app/const/audit.js
  2. 313 18
      app/controller/change_controller.js
  3. 84 16
      app/controller/settle_controller.js
  4. 5 3
      app/lib/rpt_data_analysis.js
  5. 10 27
      app/middleware/change_apply_check.js
  6. 10 31
      app/middleware/change_plan_check.js
  7. 13 39
      app/middleware/change_project_check.js
  8. 4 4
      app/public/js/change_apply_information.js
  9. 4 4
      app/public/js/change_plan_information.js
  10. 219 0
      app/public/js/settle_ledger.js
  11. 412 0
      app/public/js/settle_select.js
  12. 12 0
      app/router.js
  13. 36 30
      app/service/change.js
  14. 51 9
      app/service/change_apply.js
  15. 346 3
      app/service/change_apply_audit.js
  16. 53 0
      app/service/change_apply_history.js
  17. 5 4
      app/service/change_audit.js
  18. 51 9
      app/service/change_plan.js
  19. 367 3
      app/service/change_plan_audit.js
  20. 53 0
      app/service/change_plan_history.js
  21. 65 8
      app/service/change_project.js
  22. 395 3
      app/service/change_project_audit.js
  23. 48 0
      app/service/change_project_history.js
  24. 9 9
      app/service/settle.js
  25. 27 0
      app/service/settle_bills.js
  26. 27 0
      app/service/settle_pos.js
  27. 62 0
      app/service/settle_select.js
  28. 9 0
      app/service/stage_change_final.js
  29. 5 6
      app/view/change/apply.ejs
  30. 11 2
      app/view/change/apply_information.ejs
  31. 248 72
      app/view/change/apply_information_modal.ejs
  32. 2 6
      app/view/change/information.ejs
  33. 80 76
      app/view/change/information_modal.ejs
  34. 5 6
      app/view/change/plan.ejs
  35. 11 2
      app/view/change/plan_information.ejs
  36. 215 39
      app/view/change/plan_information_modal.ejs
  37. 1 1
      app/view/change/project.ejs
  38. 11 2
      app/view/change/project_information.ejs
  39. 240 46
      app/view/change/project_information_modal.ejs
  40. 63 0
      app/view/settle/audit_btn.ejs
  41. 157 0
      app/view/settle/index.ejs
  42. 160 0
      app/view/settle/select.ejs
  43. 0 0
      app/view/settle/select_modal.ejs
  44. 3 2
      app/view/settle/settle_menu.ejs
  45. 5 2
      app/view/settle/settle_menu_list.ejs
  46. 40 0
      config/web.js
  47. 23 11
      sql/update.sql
  48. 13 0
      sql/update20231218.sql

+ 72 - 0
app/const/audit.js

@@ -609,6 +609,10 @@ const changeProject = (function() {
         checked: 3, // 审批通过
         checkNo: 4, // 审批终止
         back: 5, // 退回到原报人重新上报
+        checkAgain: 6, // 终审退回  --该状态仅可用于,终审退回时,修改原终审的审批状态,并同时新增一条新的终审审批中记录
+        revise: 7, // 修订变更
+        cancelRevise: 8, // 撤销修订
+        checkCancel: 9, // 撤回
     };
     const statusString = [];
     statusString[status.uncheck] = '待上报';
@@ -616,6 +620,9 @@ const changeProject = (function() {
     statusString[status.checked] = '审批通过';
     statusString[status.checkNo] = '终止';
     statusString[status.back] = '审批退回';
+    statusString[status.revise] = '修订';
+    statusString[status.cancelRevise] = '撤销修订';
+    statusString[status.checkCancel] = '撤回';
 
     const statusClass = [];
     statusClass[status.uncheck] = '';
@@ -623,6 +630,9 @@ const changeProject = (function() {
     statusClass[status.checked] = 'text-success';
     statusClass[status.checkNo] = 'text-danger';
     statusClass[status.back] = 'text-warning';
+    statusClass[status.revise] = 'text-warning';
+    statusClass[status.cancelRevise] = 'text-success';
+    statusClass[status.checkCancel] = 'text-warning';
 
     // 标段概况页
     // 描述文本
@@ -632,6 +642,9 @@ const changeProject = (function() {
     auditString[status.checked] = '审批通过';
     auditString[status.checkNo] = '终止';
     auditString[status.back] = '审批退回';
+    auditString[status.revise] = '修订';
+    auditString[status.cancelRevise] = '撤销修订';
+    auditString[status.checkCancel] = '撤回';
     // 文字样式
     const auditStringClass = [];
     auditStringClass[status.uncheck] = '';
@@ -639,6 +652,9 @@ const changeProject = (function() {
     auditStringClass[status.checked] = 'text-success';
     auditStringClass[status.checkNo] = 'text-danger';
     auditStringClass[status.back] = 'text-warning';
+    auditStringClass[status.revise] = 'text-warning';
+    auditStringClass[status.cancelRevise] = 'text-success';
+    auditStringClass[status.checkCancel] = 'text-warning';
     // 描述文本
     const auditProgress = [];
     auditProgress[status.uncheck] = '草稿';
@@ -646,6 +662,9 @@ const changeProject = (function() {
     auditProgress[status.checked] = '审批通过';
     auditProgress[status.checkNo] = '终止';
     auditProgress[status.back] = '审批退回';
+    auditProgress[status.revise] = '修订中';
+    auditProgress[status.cancelRevise] = '撤销修订';
+    auditProgress[status.checkCancel] = '撤回';
     // 样式
     const auditProgressClass = [];
     auditProgressClass[status.uncheck] = '';
@@ -653,6 +672,9 @@ const changeProject = (function() {
     auditProgressClass[status.checked] = 'text-success';
     auditProgressClass[status.checkNo] = 'text-danger';
     auditProgressClass[status.back] = 'text-warning';
+    auditProgressClass[status.revise] = 'text-warning';
+    auditProgressClass[status.cancelRevise] = 'text-success';
+    auditProgressClass[status.checkCancel] = 'text-warning';
 
     const filter = {
         status: {
@@ -677,6 +699,7 @@ const changeProject = (function() {
     statusButton[status.checked] = '';
     statusButton[status.checkNo] = '';
     statusButton[status.back] = '重新上报';
+    statusButton[status.revise] = '修订';
 
     // 按钮样式
     const statusButtonClass = [];
@@ -685,6 +708,7 @@ const changeProject = (function() {
     statusButtonClass[status.checked] = '';
     statusButtonClass[status.checkNo] = '';
     statusButtonClass[status.back] = 'btn-warning';
+    statusButtonClass[status.revise] = 'btn-warning';
     return { status, statusString, statusClass, auditString, auditStringClass, auditProgress, auditProgressClass, filter, statusButton, statusButtonClass };
 })();
 
@@ -695,18 +719,28 @@ const changeApply = (function() {
         checking: 2, // 待审批|审批中
         checked: 3, // 审批通过
         checkNo: 4, // 退回到原报人重新上报
+        checkAgain: 6,
+        revise: 7, // 修订变更
+        cancelRevise: 8, // 撤销修订
+        checkCancel: 9, // 撤回
     };
     const statusString = [];
     statusString[status.uncheck] = '待上报';
     statusString[status.checking] = '审批中';
     statusString[status.checked] = '审批通过';
     statusString[status.checkNo] = '审批退回';
+    statusString[status.revise] = '修订';
+    statusString[status.cancelRevise] = '撤销修订';
+    statusString[status.checkCancel] = '撤回';
 
     const statusClass = [];
     statusClass[status.uncheck] = '';
     statusClass[status.checking] = 'text-warning';
     statusClass[status.checked] = 'text-success';
     statusClass[status.checkNo] = 'text-warning';
+    statusClass[status.revise] = 'text-warning';
+    statusClass[status.cancelRevise] = 'text-success';
+    statusClass[status.checkCancel] = 'text-warning';
 
     // 标段概况页
     // 描述文本
@@ -715,24 +749,36 @@ const changeApply = (function() {
     auditString[status.checking] = '审批中';
     auditString[status.checked] = '审批通过';
     auditString[status.checkNo] = '审批退回';
+    auditString[status.revise] = '修订';
+    auditString[status.cancelRevise] = '撤销修订';
+    auditString[status.checkCancel] = '撤回';
     // 文字样式
     const auditStringClass = [];
     auditStringClass[status.uncheck] = '';
     auditStringClass[status.checking] = 'text-warning';
     auditStringClass[status.checked] = 'text-success';
     auditStringClass[status.checkNo] = 'text-warning';
+    auditStringClass[status.revise] = 'text-warning';
+    auditStringClass[status.cancelRevise] = 'text-success';
+    auditStringClass[status.checkCancel] = 'text-warning';
     // 描述文本
     const auditProgress = [];
     auditProgress[status.uncheck] = '草稿';
     auditProgress[status.checking] = '审批中';
     auditProgress[status.checked] = '审批通过';
     auditProgress[status.checkNo] = '审批退回';
+    auditProgress[status.revise] = '修订中';
+    auditProgress[status.cancelRevise] = '撤销修订';
+    auditProgress[status.checkCancel] = '撤回';
     // 样式
     const auditProgressClass = [];
     auditProgressClass[status.uncheck] = '';
     auditProgressClass[status.checking] = 'text-warning';
     auditProgressClass[status.checked] = 'text-success';
     auditProgressClass[status.checkNo] = 'text-warning';
+    auditProgressClass[status.revise] = 'text-warning';
+    auditProgressClass[status.cancelRevise] = 'text-success';
+    auditProgressClass[status.checkCancel] = 'text-warning';
 
     const filter = {
         status: {
@@ -756,6 +802,7 @@ const changeApply = (function() {
     statusButton[status.checking] = '审批';
     statusButton[status.checked] = '';
     statusButton[status.checkNo] = '重新上报';
+    statusButton[status.revise] = '修订';
 
     // 按钮样式
     const statusButtonClass = [];
@@ -763,6 +810,7 @@ const changeApply = (function() {
     statusButtonClass[status.checking] = 'btn-success';
     statusButtonClass[status.checked] = '';
     statusButtonClass[status.checkNo] = 'btn-warning';
+    statusButtonClass[status.revise] = 'btn-warning';
     return { status, statusString, statusClass, auditString, auditStringClass, auditProgress, auditProgressClass, filter, statusButton, statusButtonClass };
 })();
 
@@ -773,18 +821,28 @@ const changePlan = (function() {
         checking: 2, // 待审批|审批中
         checked: 3, // 审批通过
         checkNo: 4, // 退回到原报人重新上报
+        checkAgain: 6,
+        revise: 7, // 修订变更
+        cancelRevise: 8, // 撤销修订
+        checkCancel: 9, // 撤回
     };
     const statusString = [];
     statusString[status.uncheck] = '待上报';
     statusString[status.checking] = '审批中';
     statusString[status.checked] = '审批通过';
     statusString[status.checkNo] = '审批退回';
+    statusString[status.revise] = '修订';
+    statusString[status.cancelRevise] = '撤销修订';
+    statusString[status.checkCancel] = '撤回';
 
     const statusClass = [];
     statusClass[status.uncheck] = '';
     statusClass[status.checking] = 'text-warning';
     statusClass[status.checked] = 'text-success';
     statusClass[status.checkNo] = 'text-warning';
+    statusClass[status.revise] = 'text-warning';
+    statusClass[status.cancelRevise] = 'text-success';
+    statusClass[status.checkCancel] = 'text-warning';
 
     // 标段概况页
     // 描述文本
@@ -793,24 +851,36 @@ const changePlan = (function() {
     auditString[status.checking] = '审批中';
     auditString[status.checked] = '审批通过';
     auditString[status.checkNo] = '审批退回';
+    auditString[status.revise] = '修订';
+    auditString[status.cancelRevise] = '撤销修订';
+    auditString[status.checkCancel] = '撤回';
     // 文字样式
     const auditStringClass = [];
     auditStringClass[status.uncheck] = '';
     auditStringClass[status.checking] = 'text-warning';
     auditStringClass[status.checked] = 'text-success';
     auditStringClass[status.checkNo] = 'text-warning';
+    auditStringClass[status.revise] = 'text-warning';
+    auditStringClass[status.cancelRevise] = 'text-success';
+    auditStringClass[status.checkCancel] = 'text-warning';
     // 描述文本
     const auditProgress = [];
     auditProgress[status.uncheck] = '草稿';
     auditProgress[status.checking] = '审批中';
     auditProgress[status.checked] = '审批通过';
     auditProgress[status.checkNo] = '审批退回';
+    auditProgress[status.revise] = '修订中';
+    auditProgress[status.cancelRevise] = '撤销修订';
+    auditProgress[status.checkCancel] = '撤回';
     // 样式
     const auditProgressClass = [];
     auditProgressClass[status.uncheck] = '';
     auditProgressClass[status.checking] = 'text-warning';
     auditProgressClass[status.checked] = 'text-success';
     auditProgressClass[status.checkNo] = 'text-warning';
+    auditProgressClass[status.revise] = 'text-warning';
+    auditProgressClass[status.cancelRevise] = 'text-success';
+    auditProgressClass[status.checkCancel] = 'text-warning';
 
     const filter = {
         status: {
@@ -834,6 +904,7 @@ const changePlan = (function() {
     statusButton[status.checking] = '审批';
     statusButton[status.checked] = '';
     statusButton[status.checkNo] = '重新上报';
+    statusButton[status.revise] = '修订';
 
     // 按钮样式
     const statusButtonClass = [];
@@ -841,6 +912,7 @@ const changePlan = (function() {
     statusButtonClass[status.checking] = 'btn-success';
     statusButtonClass[status.checked] = '';
     statusButtonClass[status.checkNo] = 'btn-warning';
+    statusButtonClass[status.revise] = 'btn-warning';
     return { status, statusString, statusClass, auditString, auditStringClass, auditProgress, auditProgressClass, filter, statusButton, statusButtonClass };
 })();
 

+ 313 - 18
app/controller/change_controller.js

@@ -1540,13 +1540,6 @@ module.exports = app => {
                         throw '验证码不正确!';
                     }
                 }
-
-                // 获取是否已存在调用变更令
-                const changeUsedData = await ctx.service.stageChange.getFinalUsedData(ctx.tender.id, changeData.cid);
-                const stageChangeNum = this.ctx.helper.sum(changeUsedData.map(x => { return Math.abs(x.qty); }));
-                if (stageChangeNum !== 0) {
-                    throw '该变更令已被调用,无法重新审批';
-                }
                 // 重新审批
                 const result = await ctx.service.change.checkAgain(changeData.cid);
                 if (!result) {
@@ -1880,6 +1873,7 @@ module.exports = app => {
                 }
                 ctx.body = responseData;
             } catch (err) {
+                console.log(err);
                 this.log(err);
                 ctx.body = this.ajaxErrorBody(err, '数据错误');
             }
@@ -2192,7 +2186,7 @@ module.exports = app => {
          */
         async _getChangeProjectAuditViewData(ctx) {
             const auditConst = audit.changeProject;
-            const times = ctx.change.status === auditConst.status.back ? ctx.change.times - 1 : ctx.change.times;
+            const times = (ctx.change.status === auditConst.status.back || ctx.change.status === auditConst.status.revise) ? ctx.change.times - 1 : ctx.change.times;
             ctx.change.user = await ctx.service.projectAccount.getAccountInfoById(ctx.change.uid);
             ctx.change.auditHistory = [];
             if (times >= 1) {
@@ -2201,10 +2195,10 @@ module.exports = app => {
                 }
             }
             // 获取审批流程中左边列表
-            ctx.change.auditors2 = ctx.change.status === auditConst.status.back && ctx.change.uid !== ctx.session.sessionUser.accountId ?
+            ctx.change.auditors2 = (ctx.change.status === auditConst.status.back || ctx.change.status === auditConst.status.revise) && ctx.change.uid !== ctx.session.sessionUser.accountId ?
                 await ctx.service.changeProjectAudit.getAuditorsWithOwner(ctx.change.id, times) :
                 await ctx.service.changeProjectAudit.getAuditorsWithOwner(ctx.change.id, ctx.change.times);
-            if (ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.back) {
+            if (ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.back || ctx.change.status === auditConst.status.revise) {
                 ctx.change.auditorList = await ctx.service.changeProjectAudit.getAuditors(ctx.change.id, ctx.change.times);
             }
             ctx.change.xsAuditors = await ctx.service.changeProjectXsAudit.getAuditList(ctx.change.id);
@@ -2247,6 +2241,9 @@ module.exports = app => {
                 const fileList = await ctx.service.changeProjectAtt.getAllChangeProjectAtt(ctx.tender.id, ctx.change.id);
                 await this._getChangeProjectAuditViewData(ctx);
                 const changeClass = await this._getOrUpdateClass(ctx, 'changeProject');
+                // 获取用户人验证手机号
+                const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                const auth_mobile = pa.auth_mobile;
                 // 判断并更新
                 const renderData = {
                     tender,
@@ -2256,6 +2253,7 @@ module.exports = app => {
                     auditConst: audit.changeProject,
                     fileList,
                     whiteList,
+                    authMobile: auth_mobile,
                     returnUrl: this.app._.includes(ctx.request.headers.referer, '/tender/' + ctx.tender.id + '/change/project') ? ctx.request.headers.referer : null,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.change.project_information),
                     preUrl: '/tender/' + ctx.tender.id + '/change/project/' + ctx.change.id + '/information',
@@ -2636,6 +2634,103 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 撤回审批
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async checkProjectAuditCancel(ctx) {
+            try {
+                if (!ctx.change.cancancel) throw '您无权进行该操作';
+
+                await ctx.service.changeProjectAudit.checkCancel(ctx.change);
+                ctx.body = { err: 0, url: ctx.request.header.referer, msg: '' };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err };
+            }
+        }
+
+        /**
+         * 变更立项修订重新上报
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async checkProjectRevise(ctx) {
+            try {
+                if (ctx.change.status !== audit.changeProject.status.checked || ctx.session.sessionUser.accountId !== ctx.change.uid) {
+                    throw '您无权进行该操作';
+                }
+                // 判断是否被变更申请调用了,是则无法发起修订
+                const projectInfo = await ctx.service.changeApply.getDataByCondition({ tid: ctx.tender.id, project_code: ctx.change.code });
+                if (projectInfo) {
+                    throw '该变更立项已被变更申请调用,无法发起修订';
+                }
+                if (ctx.session.sessionUser.loginStatus === 0) {
+                    const code = ctx.request.body.code;
+                    const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                    if (!pa.auth_mobile) {
+                        throw '未绑定手机号';
+                    }
+                    const cacheKey = 'smsCode:' + ctx.session.sessionUser.accountId;
+                    const cacheCode = await app.redis.get(cacheKey);
+                    // console.log(cacheCode);
+                    if (cacheCode === null || code === undefined || cacheCode !== (code + pa.auth_mobile)) {
+                        throw '验证码不正确!';
+                    }
+                }
+
+                // 重新审批
+                const result = await ctx.service.changeProjectAudit.checkRevise(ctx.change);
+                if (!result) {
+                    throw '修订发起失败';
+                }
+                // ctx.redirect('/tender/' + changeData.tid + '/change/' + changeData.cid + '/info');
+                ctx.body = {
+                    err: 0,
+                    url: ctx.request.header.referer,
+                    msg: '',
+                };
+            } catch (err) {
+                console.log(err);
+                // ctx.redirect(ctx.request.header.referer);
+                ctx.body = {
+                    err: 1,
+                    // url: ctx.request.header.referer,
+                    msg: err,
+                };
+            }
+        }
+
+        /**
+         * 变更令撤销修订
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async cancelProjectRevise(ctx) {
+            try {
+                if (!(ctx.change.status === audit.changeProject.status.revise && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.session.sessionUser.accountId === ctx.session.sessionUser.is_admin))) {
+                    throw '您无权进行该操作';
+                }
+                // 重新审批
+                const result = await ctx.service.changeProjectAudit.cancelRevise(ctx.change);
+                if (!result) {
+                    throw '撤销修订失败';
+                }
+                ctx.body = {
+                    err: 0,
+                    url: ctx.request.header.referer,
+                    msg: '',
+                };
+            } catch (err) {
+                console.log(err);
+                ctx.body = {
+                    err: 1,
+                    msg: err,
+                };
+            }
+        }
+
         async _filterChangesApply(ctx, status = 0) {
             const tenderId = ctx.params.id;
             ctx.session.sessionUser.tenderId = tenderId;
@@ -2822,7 +2917,7 @@ module.exports = app => {
          */
         async _getChangeApplyAuditViewData(ctx) {
             const auditConst = audit.changeApply;
-            const times = ctx.change.status === auditConst.status.checkNo ? ctx.change.times - 1 : ctx.change.times;
+            const times = ctx.change.status === auditConst.status.checkNo || ctx.change.status === auditConst.status.revise ? ctx.change.times - 1 : ctx.change.times;
             ctx.change.user = await ctx.service.projectAccount.getAccountInfoById(ctx.change.uid);
             ctx.change.auditHistory = [];
             if (times >= 1) {
@@ -2831,10 +2926,10 @@ module.exports = app => {
                 }
             }
             // 获取审批流程中左边列表
-            ctx.change.auditors2 = ctx.change.status === auditConst.status.checkNo && ctx.change.uid !== ctx.session.sessionUser.accountId ?
+            ctx.change.auditors2 = (ctx.change.status === auditConst.status.checkNo || ctx.change.status === auditConst.status.revise) && ctx.change.uid !== ctx.session.sessionUser.accountId ?
                 await ctx.service.changeApplyAudit.getAuditorsWithOwner(ctx.change.id, times) :
                 await ctx.service.changeApplyAudit.getAuditorsWithOwner(ctx.change.id, ctx.change.times);
-            if (ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.checkNo) {
+            if (ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.checkNo || ctx.change.status === auditConst.status.revise) {
                 ctx.change.auditorList = await ctx.service.changeApplyAudit.getAuditors(ctx.change.id, ctx.change.times);
             }
         }
@@ -2849,6 +2944,9 @@ module.exports = app => {
                 // 获取附件列表
                 const fileList = await ctx.service.changeApplyAtt.getAllChangeApplyAtt(ctx.tender.id, ctx.change.id);
                 const changeClass = await this._getOrUpdateClass(ctx, 'changeApply');
+                // 获取用户人验证手机号
+                const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                const auth_mobile = pa.auth_mobile;
                 const renderData = {
                     tender,
                     change: ctx.change,
@@ -2859,6 +2957,7 @@ module.exports = app => {
                     auditConst: audit.changeApply,
                     fileList,
                     whiteList,
+                    authMobile: auth_mobile,
                     tpUnit: ctx.change.decimal ? ctx.change.decimal.tp : ctx.tender.info.decimal.tp,
                     upUnit: ctx.change.decimal ? ctx.change.decimal.up : ctx.tender.info.decimal.up,
                     changeUnits: changeConst.units,
@@ -2867,7 +2966,7 @@ module.exports = app => {
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.change.apply_information),
                     preUrl: '/tender/' + ctx.tender.id + '/change/apply/' + ctx.change.id + '/information',
                 };
-                if ((ctx.change.status === audit.changeApply.status.uncheck || ctx.change.status === audit.changeApply.status.checkNo) && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.tender.isTourist)) {
+                if ((ctx.change.status === audit.changeApply.status.uncheck || ctx.change.status === audit.changeApply.status.checkNo || ctx.change.status === audit.changeApply.status.revise) && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.tender.isTourist)) {
                     // data.accountGroup = accountGroup;
                     // 获取所有项目参与者
                     const accountList = await ctx.service.projectAccount.getAllDataByCondition({
@@ -3256,6 +3355,102 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 撤回审批
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async checkApplyAuditCancel(ctx) {
+            try {
+                if (!ctx.change.cancancel) throw '您无权进行该操作';
+
+                await ctx.service.changeApplyAudit.checkCancel(ctx.change);
+                ctx.body = { err: 0, url: ctx.request.header.referer, msg: '' };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err };
+            }
+        }
+
+        /**
+         * 变更申请修订重新上报
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async checkApplyRevise(ctx) {
+            try {
+                if (ctx.change.status !== audit.changeApply.status.checked || ctx.session.sessionUser.accountId !== ctx.change.uid) {
+                    throw '您无权进行该操作';
+                }
+                // 判断是否被变更申请调用了,是则无法发起修订
+                const projectInfo = await ctx.service.changePlan.getDataByCondition({ tid: ctx.tender.id, apply_code: ctx.change.code });
+                if (projectInfo) {
+                    throw '该变更申请已被变更方案调用,无法发起修订';
+                }
+                if (ctx.session.sessionUser.loginStatus === 0) {
+                    const code = ctx.request.body.code;
+                    const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                    if (!pa.auth_mobile) {
+                        throw '未绑定手机号';
+                    }
+                    const cacheKey = 'smsCode:' + ctx.session.sessionUser.accountId;
+                    const cacheCode = await app.redis.get(cacheKey);
+                    // console.log(cacheCode);
+                    if (cacheCode === null || code === undefined || cacheCode !== (code + pa.auth_mobile)) {
+                        throw '验证码不正确!';
+                    }
+                }
+                // 重新审批
+                const result = await ctx.service.changeApplyAudit.checkRevise(ctx.change);
+                if (!result) {
+                    throw '修订发起失败';
+                }
+                // ctx.redirect('/tender/' + changeData.tid + '/change/' + changeData.cid + '/info');
+                ctx.body = {
+                    err: 0,
+                    url: ctx.request.header.referer,
+                    msg: '',
+                };
+            } catch (err) {
+                console.log(err);
+                // ctx.redirect(ctx.request.header.referer);
+                ctx.body = {
+                    err: 1,
+                    // url: ctx.request.header.referer,
+                    msg: err,
+                };
+            }
+        }
+
+        /**
+         * 变更令撤销修订
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async cancelApplyRevise(ctx) {
+            try {
+                if (!(ctx.change.status === audit.changeApply.status.revise && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.session.sessionUser.accountId === ctx.session.sessionUser.is_admin))) {
+                    throw '您无权进行该操作';
+                }
+                // 重新审批
+                const result = await ctx.service.changeApplyAudit.cancelRevise(ctx.change);
+                if (!result) {
+                    throw '撤销修订失败';
+                }
+                ctx.body = {
+                    err: 0,
+                    url: ctx.request.header.referer,
+                    msg: '',
+                };
+            } catch (err) {
+                console.log(err);
+                ctx.body = {
+                    err: 1,
+                    msg: err,
+                };
+            }
+        }
+
         // 变更方案
         async _filterChangesPlan(ctx, status = 0) {
             const tenderId = ctx.params.id;
@@ -3443,7 +3638,7 @@ module.exports = app => {
          */
         async _getChangePlanAuditViewData(ctx) {
             const auditConst = audit.changePlan;
-            const times = ctx.change.status === auditConst.status.checkNo ? ctx.change.times - 1 : ctx.change.times;
+            const times = ctx.change.status === auditConst.status.checkNo || ctx.change.status === auditConst.status.revise ? ctx.change.times - 1 : ctx.change.times;
             ctx.change.user = await ctx.service.projectAccount.getAccountInfoById(ctx.change.uid);
             ctx.change.auditHistory = [];
             if (times >= 1) {
@@ -3452,10 +3647,10 @@ module.exports = app => {
                 }
             }
             // 获取审批流程中左边列表
-            ctx.change.auditors2 = ctx.change.status === auditConst.status.checkNo && ctx.change.uid !== ctx.session.sessionUser.accountId ?
+            ctx.change.auditors2 = (ctx.change.status === auditConst.status.checkNo || ctx.change.status === auditConst.status.revise) && ctx.change.uid !== ctx.session.sessionUser.accountId ?
                 await ctx.service.changePlanAudit.getAuditorsWithOwner(ctx.change.id, times) :
                 await ctx.service.changePlanAudit.getAuditorsWithOwner(ctx.change.id, ctx.change.times);
-            if (ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.checkNo) {
+            if (ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.checkNo || ctx.change.status === auditConst.status.revise) {
                 ctx.change.auditorList = await ctx.service.changePlanAudit.getAuditors(ctx.change.id, ctx.change.times);
             }
         }
@@ -3480,6 +3675,9 @@ module.exports = app => {
                         }
                     }
                 }
+                // 获取用户人验证手机号
+                const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                const auth_mobile = pa.auth_mobile;
                 const changeClass = await this._getOrUpdateClass(ctx, 'changePlan');
                 const renderData = {
                     tender,
@@ -3491,6 +3689,7 @@ module.exports = app => {
                     auditConst: audit.changePlan,
                     fileList,
                     whiteList,
+                    authMobile: auth_mobile,
                     tpUnit: ctx.change.decimal ? ctx.change.decimal.tp : ctx.tender.info.decimal.tp,
                     upUnit: ctx.change.decimal ? ctx.change.decimal.up : ctx.tender.info.decimal.up,
                     changeUnits: changeConst.units,
@@ -3499,7 +3698,7 @@ module.exports = app => {
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.change.plan_information),
                     preUrl: '/tender/' + ctx.tender.id + '/change/plan/' + ctx.change.id + '/information',
                 };
-                if ((ctx.change.status === audit.changePlan.status.uncheck || ctx.change.status === audit.changePlan.status.checkNo) && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.tender.isTourist)) {
+                if ((ctx.change.status === audit.changePlan.status.uncheck || ctx.change.status === audit.changePlan.status.checkNo || ctx.change.status === audit.changePlan.status.revise) && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.tender.isTourist)) {
                     // data.accountGroup = accountGroup;
                     // 获取所有项目参与者
                     const accountList = await ctx.service.projectAccount.getAllDataByCondition({
@@ -3881,6 +4080,102 @@ module.exports = app => {
                 ctx.body = { err: 1, msg: err.toString(), data: null };
             }
         }
+
+        /**
+         * 撤回审批
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async checkPlanAuditCancel(ctx) {
+            try {
+                if (!ctx.change.cancancel) throw '您无权进行该操作';
+
+                await ctx.service.changePlanAudit.checkCancel(ctx.change);
+                ctx.body = { err: 0, url: ctx.request.header.referer, msg: '' };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err };
+            }
+        }
+
+        /**
+         * 变更申请修订重新上报
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async checkPlanRevise(ctx) {
+            try {
+                if (ctx.change.status !== audit.changePlan.status.checked || ctx.session.sessionUser.accountId !== ctx.change.uid) {
+                    throw '您无权进行该操作';
+                }
+                // 判断是否被变更申请调用了,是则无法发起修订
+                const projectInfo = await ctx.service.change.getDataByCondition({ tid: ctx.tender.id, plan_code: ctx.change.code });
+                if (projectInfo) {
+                    throw '该变更方案已被变更令调用,无法发起修订';
+                }
+                if (ctx.session.sessionUser.loginStatus === 0) {
+                    const code = ctx.request.body.code;
+                    const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                    if (!pa.auth_mobile) {
+                        throw '未绑定手机号';
+                    }
+                    const cacheKey = 'smsCode:' + ctx.session.sessionUser.accountId;
+                    const cacheCode = await app.redis.get(cacheKey);
+                    // console.log(cacheCode);
+                    if (cacheCode === null || code === undefined || cacheCode !== (code + pa.auth_mobile)) {
+                        throw '验证码不正确!';
+                    }
+                }
+                // 重新审批
+                const result = await ctx.service.changePlanAudit.checkRevise(ctx.change);
+                if (!result) {
+                    throw '修订发起失败';
+                }
+                // ctx.redirect('/tender/' + changeData.tid + '/change/' + changeData.cid + '/info');
+                ctx.body = {
+                    err: 0,
+                    url: ctx.request.header.referer,
+                    msg: '',
+                };
+            } catch (err) {
+                console.log(err);
+                // ctx.redirect(ctx.request.header.referer);
+                ctx.body = {
+                    err: 1,
+                    // url: ctx.request.header.referer,
+                    msg: err,
+                };
+            }
+        }
+
+        /**
+         * 变更令撤销修订
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async cancelPlanRevise(ctx) {
+            try {
+                if (!(ctx.change.status === audit.changePlan.status.revise && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.session.sessionUser.accountId === ctx.session.sessionUser.is_admin))) {
+                    throw '您无权进行该操作';
+                }
+                // 重新审批
+                const result = await ctx.service.changePlanAudit.cancelRevise(ctx.change);
+                if (!result) {
+                    throw '撤销修订失败';
+                }
+                ctx.body = {
+                    err: 0,
+                    url: ctx.request.header.referer,
+                    msg: '',
+                };
+            } catch (err) {
+                console.log(err);
+                ctx.body = {
+                    err: 1,
+                    msg: err,
+                };
+            }
+        }
     }
 
     return ChangeController;

+ 84 - 16
app/controller/settle_controller.js

@@ -157,8 +157,11 @@ module.exports = app => {
                 settle: ctx.settle,
                 shenpiConst,
                 auditType: auditConst.auditType,
+                thirdParty: {
+                    gxby: ctx.session.sessionProject.gxby_status,
+                    dagl: ctx.session.sessionProject.dagl_status,
+                },
             };
-            data.tenderMenu.back.children[0].url = '/tender/' + ctx.tender.id + '/measure/stage';
             // 是否已验证手机短信
             const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
             data.authMobile = pa.auth_mobile;
@@ -183,14 +186,32 @@ module.exports = app => {
         }
 
         async index(ctx) {
+            const status = auditConst.settle.status;
+            let url = (ctx.settle.audit_status === status.uncheck || ctx.settle.audit_status === status.checkNo) && (ctx.session.sessionUser.is_admin || ctx.session.sessionUser.accountId === ctx.settle.user_id) ? 'select' : 'ledger';
+            ctx.redirect(ctx.url + '/' + url);
+        }
+        async select(ctx) {
             try {
                 await ctx.service.settle.loadAuditViewData(ctx.settle);
                 const renderData = await this._getDefaultRenderData(ctx);
+                renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.settle.select);
+
+                const projectFunInfo = await this.ctx.service.project.getFunRela(ctx.session.sessionProject.id);
+                renderData.whiteList = this.ctx.app.config.multipart.whitelist;
+                await this.layout('settle/select.ejs', renderData, 'settle/select_modal.ejs');
+            } catch(err) {
+                console.log(err);
+                ctx.log(err);
+                ctx.redirect('/tender/' + ctx.tender.id + '/settle');
+            }
+        }
+        async ledger(ctx) {
+            try {
+                await ctx.service.settle.loadAuditViewData(ctx.settle);
+                const renderData = await this._getDefaultRenderData(ctx);
+                renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.settle.ledger);
 
                 const projectFunInfo = await this.ctx.service.project.getFunRela(ctx.session.sessionProject.id);
-                renderData.minusNoValue = projectFunInfo.minusNoValue && ctx.tender.info.fun_rela.stage_change.minusNoValue;
-                [renderData.ledgerSpread, renderData.posSpread] = await spreadSetting.getStageSpreadSetting(ctx, ctx.tender.id,
-                    this.ctx.stage.readOnly || this.ctx.stage.revising, {minusNoValue: renderData.minusNoValue});
                 renderData.whiteList = this.ctx.app.config.multipart.whitelist;
                 await this.layout('settle/index.ejs', renderData, 'settle/modal.ejs');
             } catch(err) {
@@ -200,26 +221,61 @@ module.exports = app => {
             }
         }
 
+        async _loadLatestStage(ctx) {
+            if (ctx.settle.latestStage) return;
+            ctx.settle.latestStage = ctx.settle.final_sid
+                ? await this.ctx.service.stage.getDataById(ctx.settle.final_sid)
+                : await this.ctx.service.stage.getLastestCompleteStage(ctx.tender.id);
+        }
+        async _getStageBillsData(ctx) {
+            this.ledgerColumn = [
+                'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
+                'code', 'b_code', 'name', 'unit', 'unit_price',
+                'quantity', 'total_price', 'memo', 'drawing_code', 'node_type'];
+            const ledgerData = await ctx.service.ledger.getAllDataByCondition({ columns: this.ledgerColumn, where: { tender_id: ctx.tender.id } });
+            const endStageData = await ctx.service.stageBillsFinal.getAllDataByCondition({ where: { sid: ctx.settle.latestStage.id } });
+            this.ctx.helper.assignRelaData(ledgerData, [
+                { data: endStageData, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'qc_minus_qty'], prefix: 'end_', relaId: 'lid' },
+            ]);
+            return ledgerData;
+        }
+        async _getStagePosData(ctx) {
+            this.posColumn = ['id', 'tid', 'lid', 'name', 'position', 'porder', 'quantity', 'add_stage_order', 'drawing_code'];
+            const posData = await ctx.service.pos.getAllDataByCondition({ columns: this.posColumn, where: { tid: ctx.tender.id } });
+            const endStageData = await ctx.service.stagePosFinal.getAllDataByCondition({ where: { sid: ctx.settle.latestStage.id } });
+            this.ctx.helper.assignRelaData(posData, [
+                { data: endStageData, fields: ['contract_qty', 'qc_qty', 'qc_minus_qty'], prefix: 'end_', relaId: 'pid' },
+            ]);
+            return posData;
+        }
         async _loadSettleDataByKey(ctx, key, hpack) {
             switch (key) {
                 case 'stageBills':
-                    if (!ctx.settle.latestStage) ctx.settle.latestStage = await this.ctx.service.stage.getLastestCompleteStage(ctx.tender.id);
+                    await this._loadLatestStage(ctx);
                     const bills = await this._getStageBillsData(ctx);
-                    return hpack ? [this.ctx.helper.hpackArr(bills), 'stageBills'] : [bills, ''];
+                    return hpack ? [ctx.helper.hpackArr(bills), 'stageBills'] : [bills, ''];
                 case 'stagePos':
-                    if (!ctx.settle.latestStage) ctx.settle.latestStage = await this.ctx.service.stage.getLastestCompleteStage(ctx.tender.id);
+                    await this._loadLatestStage(ctx);
                     const pos = await this._getStagePosData(ctx);
-                    return hpack ? [this.ctx.helper.hpackArr(pos), 'stagePos'] : [pos, ''];
+                    return hpack ? [ctx.helper.hpackArr(pos), 'stagePos'] : [pos, ''];
+                case 'settleChange':
+                    await this._loadLatestStage(ctx);
+                    const settleChange = await ctx.service.stageChangeFinal.getUnSettleChangeData(ctx.settle.latestStage);
+                    return hpack ? [ctx.helper.hpackArr(settleChange), key] : [settleChange, ''];
                 case 'settleBills':
-                    return ctx.service.settleBills.getAllDataByCondition({ where: { settle_id: ctx.settle.id } });
+                    const settleBills = await ctx.service.settleBills.getAllDataByCondition({ where: { settle_id: ctx.settle.id } });
+                    return hpack ? [ctx.helper.hpackArr(settleBills), 'settleBills'] : [settleBills, ''];
                 case 'settlePos':
-                    return ctx.service.settlePos.getAllDataByCondition({ where: { settle_id: ctx.settle.id } });
+                    const settlePos = await ctx.service.settlePos.getAllDataByCondition({ where: { settle_id: ctx.settle.id } });
+                    return hpack ? [ctx.helper.hpackArr(settlePos), 'settlePos'] : [settlePos, ''];
                 case 'settleSelect':
-                    return ctx.service.settleSelect.getAllDataByCondition({ where: { settle_id: ctx.settle.id } });
-                    break;
+                    const settleSelect = await ctx.service.settleSelect.getAllDataByCondition({ where: { settle_id: ctx.settle.id } });
+                    return [settleSelect, ''];
                 case 'tag':
-                    return await ctx.service.ledgerTag.getDatas(ctx.tender.id, ctx.stage.id, ctx.settle.id);
-                    break;
+                    const tag = await ctx.service.ledgerTag.getDatas(ctx.tender.id, -1, ctx.settle.id);
+                    return [tag, ''];
+                default:
+                    return [null, ''];
             }
         }
 
@@ -230,7 +286,7 @@ module.exports = app => {
                 const responseData = { err: 0, msg: '', data: {}, hpack: [] };
                 const hpack = true;
                 for (const f of filter) {
-                    const [relaData, hpackKey] = await this._loadSettleDataByKey(f, hpack);
+                    const [relaData, hpackKey] = await this._loadSettleDataByKey(ctx, f, hpack);
                     responseData.data[f] = relaData;
                     if (hpackKey) responseData.hpack.push(hpackKey);
                 }
@@ -238,7 +294,19 @@ module.exports = app => {
                 ctx.body = responseData;
             } catch (err) {
                 ctx.log(err);
-                ctx.body = { err: 1, msg: err.toString(), data: null };
+                ctx.ajaxErrorBody(err, '获取结算数据出错');
+            }
+        }
+
+        async updateSelect(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.add && !data.del) throw '提交数据错误';
+                const result = await ctx.service.settleSelect.updateData(data);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '提交结算数据出错');
             }
         }
 

+ 5 - 3
app/lib/rpt_data_analysis.js

@@ -1798,14 +1798,16 @@ const treeFilter = {
     defaultSetting: {
         table: 'mem_stage_bills',
         type: 'match/filter',
-        subType: 'all/posterity',
+        subType: 'all/posterity/leaf',
         condition: [
             { field: 'name', type: 'str', value: '建筑项目管理费', isPart: true },
         ],
     },
     _checkPath(matchPath, full_path) {
         for (const mp of matchPath) {
-            if (full_path.indexOf(mp) === 0) return true;
+            if (full_path.indexOf(mp.match + '-') === 0) return true;
+            if (mp.type === 'all' && full_path === mp.match) return true;
+
         }
         return false;
     },
@@ -1824,7 +1826,7 @@ const treeFilter = {
             if (this._checkPath(matchPath, d.full_path)) continue;
 
             if (valueCheck.checkData(ctx, d, options.condition)) {
-                matchPath.push(d.full_path + (options.subType === 'posterity' ? '-' : ''));
+                matchPath.push({ match: d.full_path, type: options.subType });
             }
         }
         data[options.table] = fData.filter(x => {

+ 10 - 27
app/middleware/change_apply_check.js

@@ -50,39 +50,22 @@ module.exports = options => {
                 //     change.readOnly = change.status !== status.uncheck && change.status !== status.back;
                 // }
                 change.curTimes = change.times;
-                if (change.status === status.uncheck || change.status === status.checkNo) {
-                    change.curOrder = 0;
-                } else if (change.status === status.checked) {
-                    change.curOrder = _.max(_.map(change.auditors, 'order'));
-                } else {
-                    change.curOrder = change.curAuditor.aid === accountId ? change.curAuditor.order : change.curAuditor.order - 1;
-                }
                 change.filePermission = true;
             } else if (this.tender.isTourist) {
                 change.curTimes = change.times;
-                if (change.status === status.uncheck || change.status === status.checkNo) {
-                    change.curOrder = 0;
-                } else if (change.status === status.checked) {
-                    change.curOrder = _.max(_.map(change.auditors, 'order'));
-                } else {
-                    change.curOrder = change.curAuditor.order;
-                }
                 change.filePermission = this.tender.touristPermission.file || auditorIds.indexOf(accountId) !== -1;
             } else if (auditorIds.indexOf(accountId) !== -1) { // 审批人
                 if (change.status === status.uncheck) {
                     throw '您无权查看该数据';
                 }
                 // change.readOnly = change.status !== status.checking || accountId !== change.curAuditor.aid;
-                change.curTimes = change.status === status.checkNo ? change.times - 1 : change.times;
-                if (change.status === status.checked) {
-                    change.curOrder = _.max(_.map(change.auditors, 'order'));
-                } else if (change.status === status.checkNo) {
-                    const audit = this.service.changeApplyAudit.getDataByCondition({
-                        caid: change.id, times: change.times, status: status.checkNo,
-                    });
-                    change.curOrder = audit.order;
-                } else {
-                    change.curOrder = accountId === change.curAuditor.aid ? change.curAuditor.order : change.curAuditor.order - 1;
+                change.curTimes = change.status === status.checkNo || change.status === status.revise ? change.times - 1 : change.times;
+                change.filePermission = true;
+            } else if ((change.status === status.checkNo || change.status === status.revise) && change.uid !== accountId) {
+                const preAuditors = yield this.service.changeApplyAudit.getAuditors(change.id, change.times - 1);
+                const preAuditorIds = _.map(preAuditors, 'aid');
+                if (preAuditorIds.indexOf(accountId) === -1) {
+                    throw '您无权查看该数据';
                 }
                 change.filePermission = true;
             } else if (shareIds.indexOf(accountId) !== -1) { // 分享人
@@ -90,16 +73,16 @@ module.exports = options => {
                     throw '您无权查看该数据';
                 }
                 // change.readOnly = true;
-                change.curTimes = change.status === status.checkNo ? change.times - 1 : change.times;
-                change.curOrder = change.status === status.checked ? _.max(_.map(change.auditors, 'order')) : change.curAuditor.order - 1;
+                change.curTimes = change.status === status.checkNo || change.status === status.revise ? change.times - 1 : change.times;
                 change.filePermission = false;
             } else { // 其他不可见
                 throw '您无权查看该数据';
             }
             // 调差的readOnly 指表格和页面只能看不能改,和审批无关
-            change.readOnly = !((change.status === status.uncheck || change.status === status.checkNo) && accountId === change.uid);
+            change.readOnly = !((change.status === status.uncheck || change.status === status.checkNo || change.status === status.revise) && accountId === change.uid);
             change.shenpiPower = change.status === status.checking && change.curAuditor.aid === accountId;
             this.change = change;
+            yield this.service.changeApply.doCheckChangeCanCancel(this.change);
             yield next;
         } catch (err) {
             console.log(err);

+ 10 - 31
app/middleware/change_plan_check.js

@@ -44,29 +44,10 @@ module.exports = options => {
                 auditorIds = _.map(change.auditors, 'aid'),
                 shareIds = [];
             if (accountId === change.uid) { // 原报
-                // if (change.curAuditor) {
-                //     change.readOnly = change.status === status.checking && change.curAuditor.user_id === accountId;
-                // } else {
-                //     change.readOnly = change.status !== status.uncheck && change.status !== status.back;
-                // }
                 change.curTimes = change.times;
-                if (change.status === status.uncheck || change.status === status.checkNo) {
-                    change.curOrder = 0;
-                } else if (change.status === status.checked) {
-                    change.curOrder = _.max(_.map(change.auditors, 'order'));
-                } else {
-                    change.curOrder = change.curAuditor.aid === accountId ? change.curAuditor.order : change.curAuditor.order - 1;
-                }
                 change.filePermission = true;
             } else if (this.tender.isTourist) {
                 change.curTimes = change.times;
-                if (change.status === status.uncheck || change.status === status.checkNo) {
-                    change.curOrder = 0;
-                } else if (change.status === status.checked) {
-                    change.curOrder = _.max(_.map(change.auditors, 'order'));
-                } else {
-                    change.curOrder = change.curAuditor.order;
-                }
                 change.filePermission = this.tender.touristPermission.file || auditorIds.indexOf(accountId) !== -1;
             } else if (auditorIds.indexOf(accountId) !== -1) { // 审批人
                 if (change.status === status.uncheck) {
@@ -74,15 +55,12 @@ module.exports = options => {
                 }
                 // change.readOnly = change.status !== status.checking || accountId !== change.curAuditor.aid;
                 change.curTimes = change.status === status.checkNo ? change.times - 1 : change.times;
-                if (change.status === status.checked) {
-                    change.curOrder = _.max(_.map(change.auditors, 'order'));
-                } else if (change.status === status.checkNo) {
-                    const audit = this.service.changePlanAudit.getDataByCondition({
-                        cpid: change.id, times: change.times, status: status.checkNo,
-                    });
-                    change.curOrder = audit.order;
-                } else {
-                    change.curOrder = accountId === change.curAuditor.aid ? change.curAuditor.order : change.curAuditor.order - 1;
+                change.filePermission = true;
+            } else if ((change.status === status.checkNo || change.status === status.revise) && change.uid !== accountId) {
+                const preAuditors = yield this.service.changePlanAudit.getAuditors(change.id, change.times - 1);
+                const preAuditorIds = _.map(preAuditors, 'aid');
+                if (preAuditorIds.indexOf(accountId) === -1) {
+                    throw '您无权查看该数据';
                 }
                 change.filePermission = true;
             } else if (shareIds.indexOf(accountId) !== -1) { // 分享人
@@ -90,16 +68,17 @@ module.exports = options => {
                     throw '您无权查看该数据';
                 }
                 // change.readOnly = true;
-                change.curTimes = change.status === status.checkNo ? change.times - 1 : change.times;
-                change.curOrder = change.status === status.checked ? _.max(_.map(change.auditors, 'order')) : change.curAuditor.order - 1;
+                change.curTimes = change.status === status.checkNo || change.status === status.revise ? change.times - 1 : change.times;
                 change.filePermission = false;
             } else { // 其他不可见
                 throw '您无权查看该数据';
             }
             // 调差的readOnly 指表格和页面只能看不能改,和审批无关
-            change.readOnly = !((change.status === status.uncheck || change.status === status.checkNo) && accountId === change.uid);
+            change.readOnly = !((change.status === status.uncheck || change.status === status.checkNo || change.status === status.revise) && accountId === change.uid);
             change.shenpiPower = change.status === status.checking && change.curAuditor.aid === accountId;
             this.change = change;
+            yield this.service.changePlan.doCheckChangeCanCancel(this.change);
+            console.log(this.change.cancancel);
             yield next;
         } catch (err) {
             console.log(err);

+ 13 - 39
app/middleware/change_project_check.js

@@ -44,63 +44,37 @@ module.exports = options => {
                 xsAuditorIds = _.map(change.xsAuditors, 'aid'),
                 shareIds = [];
             if (accountId === change.uid) { // 原报
-                // if (change.curAuditor) {
-                //     change.readOnly = change.status === status.checking && change.curAuditor.user_id === accountId;
-                // } else {
-                //     change.readOnly = change.status !== status.uncheck && change.status !== status.back;
-                // }
                 change.curTimes = change.times;
-                if (change.status === status.uncheck || change.status === status.back || change.status === status.checkNo) {
-                    change.curOrder = 0;
-                } else if (change.status === status.checked) {
-                    change.curOrder = _.max(_.map(change.auditors, 'order'));
-                } else {
-                    change.curOrder = change.curAuditor.aid === accountId ? change.curAuditor.order : change.curAuditor.order - 1;
-                }
                 change.filePermission = true;
-            } else if (this.tender.isTourist) {
-                change.curTimes = change.times;
-                if (change.status === status.uncheck || change.status === status.back || change.status === status.checkNo) {
-                    change.curOrder = 0;
-                } else if (change.status === status.checked) {
-                    change.curOrder = _.max(_.map(change.auditors, 'order'));
-                } else {
-                    change.curOrder = change.curAuditor.order;
-                }
-                change.filePermission = this.tender.touristPermission.file || auditorIds.indexOf(accountId) !== -1;
             } else if (auditorIds.indexOf(accountId) !== -1 || xsAuditorIds.indexOf(accountId) !== -1) { // 审批人或者协审人
                 if (change.status === status.uncheck) {
                     throw '您无权查看该数据';
                 }
-                // change.readOnly = change.status !== status.checking || accountId !== change.curAuditor.aid;
-                change.curTimes = change.status === status.back ? change.times - 1 : change.times;
-                if (change.status === status.checked) {
-                    change.curOrder = _.max(_.map(change.auditors, 'order'));
-                } else if (change.status === status.back) {
-                    const audit = this.service.changeProjectAudit.getDataByCondition({
-                        cpid: change.id, times: change.times, status: status.back,
-                    });
-                    change.curOrder = audit.order;
-                } else if (change.status === status.checkNo) {
-                    change.curOrder = 0;
-                } else {
-                    change.curOrder = accountId === change.curAuditor.aid ? change.curAuditor.order : change.curAuditor.order - 1;
+                change.curTimes = change.status === status.back || change.status === status.revise ? change.times - 1 : change.times;
+                change.filePermission = true;
+            } else if ((change.status === status.back || change.status === status.revise) && change.uid !== accountId) {
+                const preAuditors = yield this.service.changeProjectAudit.getAuditors(change.id, change.times - 1);
+                const preAuditorIds = _.map(preAuditors, 'aid');
+                if (preAuditorIds.indexOf(accountId) === -1) {
+                    throw '您无权查看该数据';
                 }
                 change.filePermission = true;
             } else if (shareIds.indexOf(accountId) !== -1) { // 分享人
                 if (change.status === status.uncheck) {
                     throw '您无权查看该数据';
                 }
-                // change.readOnly = true;
-                change.curTimes = change.status === status.back ? change.times - 1 : change.times;
-                change.curOrder = change.status === status.checked ? _.max(_.map(change.auditors, 'order')) : (change.status !== status.checkNo ? change.curAuditor.order - 1 : 0);
+                change.curTimes = change.status === status.back || change.status === status.revise ? change.times - 1 : change.times;
                 change.filePermission = false;
+            } else if (this.tender.isTourist) {
+                change.curTimes = change.times;
+                change.filePermission = this.tender.touristPermission.file || auditorIds.indexOf(accountId) !== -1;
             } else { // 其他不可见
                 throw '您无权查看该数据';
             }
             // 调差的readOnly 指表格和页面只能看不能改,和审批无关
-            change.readOnly = !((change.status === status.uncheck || change.status === status.back) && accountId === change.uid);
+            change.readOnly = !((change.status === status.uncheck || change.status === status.back || change.status === status.revise) && accountId === change.uid);
             this.change = change;
+            yield this.service.changeProject.doCheckChangeCanCancel(this.change);
             yield next;
         } catch (err) {
             console.log(err);

+ 4 - 4
app/public/js/change_apply_information.js

@@ -320,7 +320,7 @@ $(document).ready(() => {
                 const select = type === 'update' ? SpreadJsObj.getSelectObject(info.sheet) : {unit: ''};
                 const col = info.sheet.zh_setting.cols[info.col];
                 // 未改变值则不提交
-                let validText = is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : '');
+                let validText = (col.type === 'Number' || col.field === 'new_up') && is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : '');
                 const orgValue = type === 'update' ? select[col.field] : '';
                 if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -402,7 +402,7 @@ $(document).ready(() => {
                         // cLData[colSetting.field] = trimInvalidChar(info.sheet.getText(curRow, curCol));
 
                         let validText = info.sheet.getText(curRow, curCol);
-                        validText = is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : '');
+                        validText = (colSetting.type === 'Number' || colSetting.field === 'new_up') && is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : '');
                         const orgValue = curRow >= sortData.length ? '' : sortData[curRow][colSetting.field];
                         if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
                             sameCol++;
@@ -664,7 +664,7 @@ $(document).ready(() => {
                 const select = SpreadJsObj.getSelectObject(info.sheet);
                 const col = info.sheet.zh_setting.cols[info.col];
                 // 未改变值则不提交
-                let validText = is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : '');
+                let validText = info.editingText ? trimInvalidChar(info.editingText) : '';
                 const orgValue = select[col.field];
                 if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -713,7 +713,7 @@ $(document).ready(() => {
                     if (!colSetting) continue;
 
                     let validText = info.sheet.getText(curRow, curCol);
-                    validText = is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : null);
+                    validText = validText ? trimInvalidChar(validText) : null;
                     const orgValue = sortData[curRow][colSetting.field];
                     if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
                         sameCol++;

+ 4 - 4
app/public/js/change_plan_information.js

@@ -383,7 +383,7 @@ $(document).ready(() => {
                     return;
                 }
                 // 未改变值则不提交
-                let validText = is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : '');
+                let validText = (col.type === 'Number' || col.field === 'new_up') && is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : '');
                 const orgValue = type === 'update' ? select[col.field] : '';
                 if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -468,7 +468,7 @@ $(document).ready(() => {
                         // cLData[colSetting.field] = trimInvalidChar(info.sheet.getText(curRow, curCol));
 
                         let validText = info.sheet.getText(curRow, curCol);
-                        validText = is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : '');
+                        validText = (colSetting.type === 'Number' || colSetting.field === 'new_up') && is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : '');
                         const orgValue = curRow >= sortData.length ? '' : sortData[curRow][colSetting.field];
                         if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
                             sameCol++;
@@ -765,7 +765,7 @@ $(document).ready(() => {
                 const select = SpreadJsObj.getSelectObject(info.sheet);
                 const col = info.sheet.zh_setting.cols[info.col];
                 // 未改变值则不提交
-                let validText = is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : '');
+                let validText = col.type === 'Number' && is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : '');
                 const orgValue = select[col.field];
                 if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -833,7 +833,7 @@ $(document).ready(() => {
                     if (!colSetting) continue;
 
                     let validText = info.sheet.getText(curRow, curCol);
-                    validText = is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : null);
+                    validText = colSetting.type === 'Number' && is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : null);
                     const orgValue = sortData[curRow][colSetting.field];
                     if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
                         sameCol++;

+ 219 - 0
app/public/js/settle_ledger.js

@@ -0,0 +1,219 @@
+function getGxbyText(data) {
+    const def = thirdParty.gxby.find(function (x) {
+        return x.value === data.gxby_status;
+    });
+    return def ? def.name : '';
+}
+function getDaglText(data) {
+    const def = thirdParty.dagl.find(function (x) {
+        return x.value === data.dagl_status;
+    });
+    return def ? def.name : '';
+}
+
+const ckBillsSpread = window.location.pathname + '-billsSelect';
+
+$(document).ready(() => {
+    autoFlashHeight();
+
+    let searchLedger;
+    const settleTreeSetting = {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        keys: ['id', 'tender_id', 'ledger_id'],
+        stageId: 'id',
+        autoExpand: 3,
+        markExpandKey: 'settle-select-expand',
+        markExpandSubKey: window.location.pathname.split('/')[2],
+        calcFields: ['total_price', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp', 'end_correct_tp'],
+        calcFun: function(node) {
+            if (!node.children || node.children.length === 0) {
+                node.end_gather_qty = ZhCalc.add(node.end_contract_qty, node.end_qc_qty);
+                if (node.end_contract_qty) {
+                    node.end_correct_tp = ZhCalc.add(node.end_qc_tp, ZhCalc.mul(node.end_contract_qty, node.unit_price, tenderInfo.decimal.tp));
+                } else {
+                    node.end_correct_tp = node.end_gather_tp;
+                }
+            }
+            node.end_gather_tp = ZhCalc.add(node.end_contract_tp, node.end_qc_tp);
+            node.end_gather_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_tp), 100, 2);
+            node.end_correct_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_tp), 100, 2);
+        }
+    };
+    const settleTree = createNewPathTree('stage', settleTreeSetting);
+    const settlePosSetting = {
+        id: 'id', ledgerId: 'lid',
+        calcFun: function(pos) {
+            pos.end_gather_qty = ZhCalc.add(pos.end_contract_qty, pos.end_qc_qty);
+            pos.sum = ZhCalc.add(pos.end_qc_qty, pos.quantity);
+            pos.end_gather_percent = ZhCalc.mul(ZhCalc.div(pos.end_gather_qty, pos.sum), 100, 2);
+        }
+    };
+    const settlePos = new StagePosData(settlePosSetting);
+
+    const slSpread = SpreadJsObj.createNewSpread($('#settle-bills')[0]);
+    const slSheet = slSpread.getActiveSheet();
+    slSheet.frozenColumnCount(billsSpreadSetting.cols.findIndex(x => { return x.field === 'total_price'; }) + 1);
+    slSheet.options.frozenlineColor = '#93b5e4';
+    const ratioCol = billsSpreadSetting.cols.find(x => {return x.field === 'end_final_1_percent' || x.field === 'end_correct_1_percent'});
+    if (ratioCol) ratioCol.field = tenderInfo.display.stage.correct ? 'end_correct_1_percent' : 'end_final_1_percent';
+    billsSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
+        if (!data) return defaultColor;
+        if (data.children && data.children.length > 0) return defaultColor;
+
+        if (col.field === 'gxby') {
+            const def = thirdParty.gxby.find(function (x) {
+                return x.value === data.gxby_status;
+            });
+            if (def && def.color) return def.color;
+        } else if (col.field === 'dagl') {
+            const def = thirdParty.dagl.find(function (x) {
+                return x.value === data.dagl_status;
+            });
+            if (def && def.color) return def.color;
+        }
+    };
+    sjsSettingObj.setFxTreeStyle(billsSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    sjsSettingObj.set3FCols(billsSpreadSetting.cols, [
+        {field: 'gxby', getValue: getGxbyText, url_field: 'gxby_url'},
+        {field: 'dagl', getValue: getDaglText, url_field: 'dagl_url'},
+    ]);
+    SpreadJsObj.initSheet(slSheet, billsSpreadSetting);
+
+    const spSpread = SpreadJsObj.createNewSpread($('#settle-pos')[0]);
+    const spSheet = spSpread.getActiveSheet();
+    spSheet.frozenColumnCount(posSpreadSetting.cols.findIndex(x => { return x.field === 'total_price'; }) + 1);
+    spSheet.options.frozenlineColor = '#93b5e4';
+    posSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
+        if (!data) return defaultColor;
+
+        if (col.field === 'gxby') {
+            const def = thirdParty.gxby.find(function (x) {
+                return x.value === data.gxby_status;
+            });
+            if (def && def.color) return def.color;
+        } else if (col.field === 'dagl') {
+            const def = thirdParty.dagl.find(function (x) {
+                return x.value === data.dagl_status;
+            });
+            if (def && def.color) return def.color;
+        }
+    };
+    sjsSettingObj.set3FCols(posSpreadSetting.cols, [
+        {field: 'gxby', getValue: getGxbyText, url_field: 'gxby_url'},
+        {field: 'dagl', getValue: getDaglText, url_field: 'dagl_url'},
+    ]);
+    SpreadJsObj.initSheet(spSheet, posSpreadSetting);
+
+    const settleBillsObj = {
+        loadRelaData: function() {
+            SpreadJsObj.saveTopAndSelect(slSheet, ckBillsSpread);
+            SpreadJsObj.resetTopAndSelect(spSheet);
+            settlePosObj.loadCurPosData();
+        },
+        selectionChanged: function(e, info) {
+            if (!info.oldSelections || !info.oldSelections[0] || info.newSelections[0].row !== info.oldSelections[0].row) {
+                settleBillsObj.loadRelaData();
+            }
+        },
+        topRowChanged(e, info) {
+            SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
+        },
+    };
+    slSpread.bind(spreadNS.Events.SelectionChanged, settleBillsObj.selectionChanged);
+    slSpread.bind(spreadNS.Events.TopRowChanged, settleBillsObj.topRowChanged);
+
+    const settlePosObj = {
+        loadCurPosData: function() {
+            const billsNode = SpreadJsObj.getSelectObject(slSheet);
+            if (billsNode) {
+                spSheet.zh_setting.readOnly = readOnly;
+                const posRange = settlePos.getLedgerPos(billsNode.id) || [];
+                SpreadJsObj.loadSheetData(spSheet, SpreadJsObj.DataType.Data, posRange, readOnly);
+            } else {
+                spSheet.zh_setting.readOnly = true;
+                SpreadJsObj.loadSheetData(spSheet, SpreadJsObj.DataType.Data, [], true);
+            }
+        }
+    };
+
+    postData('load', {filter: 'settleBills;settlePos;tag'}, function(result) {
+        settleTree.loadDatas(result.settleBills);
+        treeCalc.calculateAll(settleTree);
+        settlePos.loadDatas(result.settlePos);
+        settlePos.calculateAll();
+
+        SpreadJsObj.loadSheetData(slSheet, SpreadJsObj.DataType.Tree, settleTree);
+        SpreadJsObj.loadTopAndSelect(slSpread.getActiveSheet(), ckBillsSpread);
+        settlePosObj.loadCurPosData();
+    });
+
+    // 展开收起工具栏
+    $('a', '.right-nav').bind('click', function () {
+        const tab = $(this), tabPanel = $(tab.attr('content'));
+        if (!tab.hasClass('active')) {
+            $('a', '.side-menu').removeClass('active');
+            $('.tab-content .tab-select-show').removeClass('active');
+            tab.addClass('active');
+            tabPanel.addClass('active');
+            showSideTools(tab.hasClass('active'));
+            if (tab.attr('content') === '#search' && !searchLedger) {
+                searchLedger = $.billsSearch({
+                    selector: '#search',
+                    searchSpread: slSpread,
+                    searchOver: true,
+                    searchEmpty: true,
+                    resultSpreadSetting: {
+                        cols: [
+                            {title: '项目节编号', field: 'code', hAlign: 0, width: 90, formatter: '@'},
+                            {title: '清单编号', field: 'b_code', hAlign: 0, width: 80, formatter: '@'},
+                            {title: '名称', field: 'name', width: 150, hAlign: 0, formatter: '@'},
+                            {title: '单位', field: 'unit', width: 50, hAlign: 1, formatter: '@'},
+                            {title: '单价', field: 'unit_price', hAlign: 2, width: 50},
+                            {title: '数量', field: 'quantity', hAlign: 2, width: 50},
+                        ],
+                        emptyRows: 0,
+                        headRows: 1,
+                        headRowHeight: [32],
+                        headColWidth: [30],
+                        defaultRowHeight: 21,
+                        headerFont: '12px 微软雅黑',
+                        font: '12px 微软雅黑',
+                        selectedBackColor: '#fffacd',
+                        readOnly: true,
+                    },
+                    afterLocated: function () {
+                        settlePosObj.loadCurPosData();
+                    },
+                });
+                searchLedger.spread.refresh();
+            }
+        } else {
+            tab.removeClass('active');
+            tabPanel.removeClass('active');
+            showSideTools(tab.hasClass('active'));
+        }
+        slSpread.refresh();
+        spSpread.refresh();
+    });
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+});

+ 412 - 0
app/public/js/settle_select.js

@@ -0,0 +1,412 @@
+function getGxbyText(data) {
+    const def = thirdParty.gxby.find(function (x) {
+        return x.value === data.gxby_status;
+    });
+    return def ? def.name : '';
+}
+function getDaglText(data) {
+    const def = thirdParty.dagl.find(function (x) {
+        return x.value === data.dagl_status;
+    });
+    return def ? def.name : '';
+}
+
+const ckBillsSpread = window.location.pathname + '-billsSelect';
+$(document).ready(() => {
+    autoFlashHeight();
+
+    let searchLedger;
+    const settleTreeSetting = {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        keys: ['id', 'tender_id', 'ledger_id'],
+        stageId: 'id',
+        autoExpand: 3,
+        markExpandKey: 'settle-select-expand',
+        markExpandSubKey: window.location.pathname.split('/')[2],
+        calcFields: ['total_price', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp', 'end_correct_tp'],
+        calcFun: function(node) {
+            if (!node.children || node.children.length === 0) {
+                node.end_gather_qty = ZhCalc.add(node.end_contract_qty, node.end_qc_qty);
+                if (node.end_contract_qty) {
+                    node.end_correct_tp = ZhCalc.add(node.end_qc_tp, ZhCalc.mul(node.end_contract_qty, node.unit_price, tenderInfo.decimal.tp));
+                } else {
+                    node.end_correct_tp = node.end_gather_tp;
+                }
+            }
+            node.end_gather_tp = ZhCalc.add(node.end_contract_tp, node.end_qc_tp);
+            node.end_gather_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_tp), 100, 2);
+            node.end_correct_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_tp), 100, 2);
+        }
+    };
+    const settleTree = createNewPathTree('stage', settleTreeSetting);
+    const settlePosSetting = {
+        id: 'id', ledgerId: 'lid',
+        calcFun: function(pos) {
+            pos.end_gather_qty = ZhCalc.add(pos.end_contract_qty, pos.end_qc_qty);
+            pos.sum = ZhCalc.add(pos.end_qc_qty, pos.quantity);
+            pos.end_gather_percent = ZhCalc.mul(ZhCalc.div(pos.end_gather_qty, pos.sum), 100, 2);
+        }
+    };
+    const settlePos = new StagePosData(settlePosSetting);
+
+    const slSpread = SpreadJsObj.createNewSpread($('#settle-bills')[0]);
+    const slSheet = slSpread.getActiveSheet();
+    slSheet.frozenColumnCount(billsSpreadSetting.cols.findIndex(x => { return x.field === 'total_price'; }) + 1);
+    slSheet.options.frozenlineColor = '#93b5e4';
+    const ratioCol = billsSpreadSetting.cols.find(x => {return x.field === 'end_final_1_percent' || x.field === 'end_correct_1_percent'});
+    if (ratioCol) ratioCol.field = tenderInfo.display.stage.correct ? 'end_correct_1_percent' : 'end_final_1_percent';
+    billsSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
+        if (!data) return defaultColor;
+        if (data.children && data.children.length > 0) return defaultColor;
+
+        if (col.field === 'gxby') {
+            const def = thirdParty.gxby.find(function (x) {
+                return x.value === data.gxby_status;
+            });
+            if (def && def.color) return def.color;
+        } else if (col.field === 'dagl') {
+            const def = thirdParty.dagl.find(function (x) {
+                return x.value === data.dagl_status;
+            });
+            if (def && def.color) return def.color;
+        }
+    };
+    sjsSettingObj.setFxTreeStyle(billsSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    sjsSettingObj.set3FCols(billsSpreadSetting.cols, [
+        {field: 'gxby', getValue: getGxbyText, url_field: 'gxby_url'},
+        {field: 'dagl', getValue: getDaglText, url_field: 'dagl_url'},
+    ]);
+    SpreadJsObj.initSheet(slSheet, billsSpreadSetting);
+
+    const spSpread = SpreadJsObj.createNewSpread($('#settle-pos')[0]);
+    const spSheet = spSpread.getActiveSheet();
+    spSheet.frozenColumnCount(posSpreadSetting.cols.findIndex(x => { return x.field === 'total_price'; }) + 1);
+    spSheet.options.frozenlineColor = '#93b5e4';
+    posSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
+        if (!data) return defaultColor;
+
+        if (col.field === 'gxby') {
+            const def = thirdParty.gxby.find(function (x) {
+                return x.value === data.gxby_status;
+            });
+            if (def && def.color) return def.color;
+        } else if (col.field === 'dagl') {
+            const def = thirdParty.dagl.find(function (x) {
+                return x.value === data.dagl_status;
+            });
+            if (def && def.color) return def.color;
+        }
+    };
+    sjsSettingObj.set3FCols(posSpreadSetting.cols, [
+        {field: 'gxby', getValue: getGxbyText, url_field: 'gxby_url'},
+        {field: 'dagl', getValue: getDaglText, url_field: 'dagl_url'},
+    ]);
+    SpreadJsObj.initSheet(spSheet, posSpreadSetting);
+
+    // 0: 可结算,1: 合同未完成,2:
+    const settleCheck = {
+        _analysisPos(pos) {
+            pos.undoneDeal = pos.quantity ? !checkZero(ZhCalc.div(pos.end_contract_qty, pos.quantity)) : false;
+            pos.undone = pos.undoneDeal || pos.undoneChange;
+        },
+        _analysisNode(node) {
+            if (node.undoneDeal === undefined) node.undoneDeal = false;
+            if (node.undoneChange === undefined) node.undoneChange = false;
+            if (node.children && node.children.length > 0) {
+                for (const child of node.children) {
+                    this._analysisNode(child);
+                    if (child.undoneDeal) node.undoneDeal = true;
+                    if (child.undoneChange) node.undoneChange = true;
+                }
+                node.undone = !!node.undoneDeal || !!node.undoneChange;
+            } else {
+                const posRange = settlePos.getLedgerPos(node.id);
+                if (posRange && posRange.length > 0) {
+                    for (const pos of posRange) {
+                        this._analysisPos(pos);
+                        if (pos.undoneDeal) node.undoneDeal = true;
+                        if (pos.undoneChange) node.undoneChange = true;
+                    }
+                } else {
+                    node.undoneDeal = node.end_contract_qty ? !checkZero(ZhCalc.div(node.end_contract_qty, node.quantity)) : false;
+                    node.undone = !!node.undoneDeal || !!node.undoneChange;
+                }
+            }
+        },
+        init() {
+            for (const node of settleTree.children) {
+                this._analysisNode(node);
+            }
+        }
+    };
+
+    const settleBillsObj = {
+        loadRelaData: function() {
+            SpreadJsObj.saveTopAndSelect(slSheet, ckBillsSpread);
+            SpreadJsObj.resetTopAndSelect(spSheet);
+            settlePosObj.loadCurPosData();
+        },
+        selectionChanged: function(e, info) {
+            if (!info.oldSelections || !info.oldSelections[0] || info.newSelections[0].row !== info.oldSelections[0].row) {
+                settleBillsObj.loadRelaData();
+            }
+        },
+        topRowChanged(e, info) {
+            SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
+        },
+        buttonClicked: function(e, info) {
+            if (!info.sheet.zh_setting) return;
+
+            const col = info.sheet.zh_setting.cols[info.col];
+            if (col.field !== 'selected') return;
+
+            const node = SpreadJsObj.getSelectObject(info.sheet);
+            if (node.undone && !node.selected) {
+                let msg = '选择的节点不可结算';
+                if (node.undoneDeal) msg = msg + ',合同未计量完';
+                if (node.undoneChange) msg = msg + ',变更令未调用完';
+                toastr.warning(msg);
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                return;
+            }
+            if (!node.selected) {
+                const parents = settleTree.getAllParents(node);
+                for (const p of parents) {
+                    if (p.selected) {
+                        toastr.warning(`父项${p.code || ''}以勾选,勿需重复勾选子项`);
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                }
+            }
+
+            const update = {};
+            if (!node.selected) {
+                update.add = [{ lid: node.id }];
+                const posterity = settleTree.getPosterity(node);
+                for (const p of posterity) {
+                    if (p.selected) {
+                        if (!update.del) update.del = [];
+                        update.del.push({ lid: node.id });
+                    }
+                    if (!p.children || p.children.length === 0) {
+                        const posRange = settlePos.getLedgerPos(p.id);
+                        for (const p of posRange) {
+                            if (p.selected) {
+                                if (!update.del) update.del = [];
+                                update.del.push({ pid: p.id });
+                            }
+                        }
+                    }
+                }
+            } else {
+                update.del = [{ lid: node.id }];
+            }
+            postData(window.location.pathname + '/update', update, result => {
+                node.selected = !node.selected;
+                if (result.del) {
+                    let refreshRow = [], refreshPos = false;
+                    for (const d of result.del) {
+                        if (d.lid) {
+                            const sbi = settleTree.nodes.findIndex(x => { return x.id === d.lid; });
+                            settleTree.nodes[sbi].selected = false;
+                            refreshRow.push(sbi);
+                        } else if (d.pid) {
+                            const sp = settlePos.getPos(d.pid);
+                            if (sp) {
+                                sp.selected = false;
+                                if (sp.lid === node.id) refreshPos = false;
+                            }
+                        }
+                    }
+                    if (refreshRow.length > 0) SpreadJsObj.reLoadRowsData(info.sheet, refreshRow);
+                    if (refreshPos) settlePosObj.loadCurPosData();
+                }
+            }, () => {
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+            });
+        },
+    };
+    slSpread.bind(spreadNS.Events.SelectionChanged, settleBillsObj.selectionChanged);
+    slSpread.bind(spreadNS.Events.TopRowChanged, settleBillsObj.topRowChanged);
+    if (!readOnly) {
+        slSpread.bind(spreadNS.Events.ButtonClicked, settleBillsObj.buttonClicked);
+    }
+
+    const settlePosObj = {
+        loadCurPosData: function() {
+            const billsNode = SpreadJsObj.getSelectObject(slSheet);
+            if (billsNode) {
+                spSheet.zh_setting.readOnly = readOnly;
+                const posRange = settlePos.getLedgerPos(billsNode.id) || [];
+                SpreadJsObj.loadSheetData(spSheet, SpreadJsObj.DataType.Data, posRange, readOnly);
+                if (posRange.length > 0) SpreadJsObj.locateData(spSheet, posRange[0]);
+            } else {
+                spSheet.zh_setting.readOnly = true;
+                SpreadJsObj.loadSheetData(spSheet, SpreadJsObj.DataType.Data, [], true);
+            }
+        },
+        buttonClicked: function(e, info) {
+            if (!info.sheet.zh_setting) return;
+
+            const col = info.sheet.zh_setting.cols[info.col];
+            if (col.field !== 'selected') return;
+
+            const node = SpreadJsObj.getSelectObject(info.sheet);
+            if (node.undone && !node.selected) {
+                let msg = '选择的节点不可结算';
+                if (node.undoneDeal) msg = msg + ',合同未计量完';
+                if (node.undoneChange) msg = msg + ',变更令未调用完';
+                toastr.warning(msg);
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                return;
+            }
+            if (!node.selected) {
+                const billsNode = settleTree.nodes.find(x => { return x.id === node.lid });
+                const parents = settleTree.getAllParents(billsNode);
+                for (const p of parents) {
+                    if (p.selected) {
+                        toastr.warning(`所属清单的父项${p.code || ''}以勾选,勿需重复勾选计量单元`);
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                }
+            }
+
+            const update = {};
+            if (!node.selected) {
+                update.add = [{ lid: node.id }];
+            } else {
+                update.del = [{ lid: node.id }];
+            }
+            postData(window.location.pathname + '/update', update, result => {
+                node.selected = !node.selected;
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+            }, () => {
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+            });
+        },
+    };
+    if (!readOnly) {
+        spSpread.bind(spreadNS.Events.ButtonClicked, settlePosObj.buttonClicked);
+    }
+
+    postData('load', {filter: 'stageBills;stagePos;settleSelect;tag;settleChange'}, function(result) {
+        for (const select of result.settleSelect) {
+            if (select.pid) {
+                const sp = result.stagePos.find(x => { return x.id === select.pid });
+                if (sp) sp.selected = true;
+            } else {
+                const sb = result.stageBills.find(x => { return x.id === select.lid });
+                if (sb) sb.selected = true;
+            }
+        }
+        for (const change of result.settleChange) {
+           const sb = result.stageBills.find(x => { return x.id === change.gcl_id });
+           if (sb) sb.undoneChange = true;
+           if (change.mx_id) {
+               const sp = result.stagePos.find(x => { return x.id === change.mx_id });
+               if (sp) sp.undoneChange = true;
+           }
+        }
+        settleTree.loadDatas(result.stageBills);
+        treeCalc.calculateAll(settleTree);
+        settlePos.loadDatas(result.stagePos);
+        settlePos.calculateAll();
+        settleCheck.init();
+
+        SpreadJsObj.loadSheetData(slSheet, SpreadJsObj.DataType.Tree, settleTree);
+        SpreadJsObj.loadTopAndSelect(slSpread.getActiveSheet(), ckBillsSpread);
+        settlePosObj.loadCurPosData();
+    });
+
+    // 展开收起工具栏
+    $('a', '.right-nav').bind('click', function () {
+        const tab = $(this), tabPanel = $(tab.attr('content'));
+        if (!tab.hasClass('active')) {
+            $('a', '.side-menu').removeClass('active');
+            $('.tab-content .tab-select-show').removeClass('active');
+            tab.addClass('active');
+            tabPanel.addClass('active');
+            showSideTools(tab.hasClass('active'));
+            if (tab.attr('content') === '#search' && !searchLedger) {
+                searchLedger = $.billsSearch({
+                    selector: '#search',
+                    searchSpread: slSpread,
+                    searchOver: true,
+                    searchEmpty: true,
+                    resultSpreadSetting: {
+                        cols: [
+                            {title: '项目节编号', field: 'code', hAlign: 0, width: 90, formatter: '@'},
+                            {title: '清单编号', field: 'b_code', hAlign: 0, width: 80, formatter: '@'},
+                            {title: '名称', field: 'name', width: 150, hAlign: 0, formatter: '@'},
+                            {title: '单位', field: 'unit', width: 50, hAlign: 1, formatter: '@'},
+                            {title: '单价', field: 'unit_price', hAlign: 2, width: 50},
+                            {title: '数量', field: 'quantity', hAlign: 2, width: 50},
+                        ],
+                        emptyRows: 0,
+                        headRows: 1,
+                        headRowHeight: [32],
+                        headColWidth: [30],
+                        defaultRowHeight: 21,
+                        headerFont: '12px 微软雅黑',
+                        font: '12px 微软雅黑',
+                        selectedBackColor: '#fffacd',
+                        readOnly: true,
+                    },
+                    afterLocated: function () {
+                        settlePosObj.loadCurPosData();
+                    },
+                });
+                searchLedger.spread.refresh();
+            }
+        } else {
+            tab.removeClass('active');
+            tabPanel.removeClass('active');
+            showSideTools(tab.hasClass('active'));
+        }
+        slSpread.refresh();
+        spSpread.refresh();
+    });
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+    // 加载上下窗口resizer
+    $.divResizer({
+        select: '#main-resize',
+        callback: function () {
+            slSpread.refresh();
+            let bcontent = $(".bcontent-wrap") ? $(".bcontent-wrap").height() : 0;
+            $(".sp-wrap").height(bcontent-30);
+            spSpread.refresh();
+            window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
+        }
+    });
+    // 工具栏resizer
+    $.divResizer({
+        select: '#right-spr',
+        callback: function () {
+            slSpread.refresh();
+            spSpread.refresh();
+            window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
+        }
+    });
+});

+ 12 - 0
app/router.js

@@ -429,7 +429,10 @@ module.exports = app => {
     app.post('/tender/:id/settle/delete', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, 'settleController.delete');
     // 结算期
     app.get('/tender/:id/settle/:sorder', sessionAuth, tenderCheck, uncheckTenderCheck, settleCheck, 'settleController.index');
+    app.get('/tender/:id/settle/:sorder/select', sessionAuth, tenderCheck, uncheckTenderCheck, settleCheck, 'settleController.select');
+    app.get('/tender/:id/settle/:sorder/ledger', sessionAuth, tenderCheck, uncheckTenderCheck, settleCheck, 'settleController.ledger');
     app.post('/tender/:id/settle/:sorder/load', sessionAuth, tenderCheck, uncheckTenderCheck, settleCheck, 'settleController.loadSettleData');
+    app.post('/tender/:id/settle/:sorder/select/update', sessionAuth, tenderCheck, uncheckTenderCheck, settleCheck, 'settleController.updateSelect');
     // 结算汇总
     app.get('/tender/:id/settle/gather', sessionAuth, tenderCheck, uncheckTenderCheck, 'settleController.gather');
     app.get('/tender/:id/settle/gather/load', sessionAuth, tenderCheck, uncheckTenderCheck, 'settleController.loadGatherData');
@@ -542,6 +545,9 @@ module.exports = app => {
     app.post('/tender/:id/change/project/:cpid/information/audit/check', sessionAuth, tenderCheck, uncheckTenderCheck, changeProjectCheck, tenderBuildCheck, 'changeController.checkProjectAudit');
     app.post('/tender/:id/change/project/:cpid/information/xsaudit/add', sessionAuth, tenderCheck, uncheckTenderCheck, changeProjectCheck, 'changeController.addProjectXsAudit');
     app.post('/tender/:id/change/project/:cpid/information/xsaudit/delete', sessionAuth, tenderCheck, uncheckTenderCheck, changeProjectCheck, 'changeController.deleteProjectXsAudit');
+    app.post('/tender/:id/change/project/cancel/audit', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, changeProjectCheck, 'changeController.checkProjectAuditCancel');
+    app.post('/tender/:id/change/project/check/revise', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, changeProjectCheck, 'changeController.checkProjectRevise');
+    app.post('/tender/:id/change/project/cancel/revise', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, changeProjectCheck, 'changeController.cancelProjectRevise');
     // 变更申请
     app.get('/tender/:id/change/apply', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.apply');
     app.get('/tender/:id/change/apply/status/:status', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.applyStatus');
@@ -558,6 +564,9 @@ module.exports = app => {
     app.post('/tender/:id/change/apply/:caid/information/audit/check', sessionAuth, tenderCheck, uncheckTenderCheck, changeApplyCheck, tenderBuildCheck, 'changeController.checkApplyAudit');
     app.get('/tender/:id/change/apply/:caid/information/notice', sessionAuth, tenderCheck, uncheckTenderCheck, changeApplyCheck, 'changeController.applyInformationNotice');
     app.post('/tender/:id/change/apply/:caid/information/list/save', sessionAuth, tenderCheck, uncheckTenderCheck, changeApplyCheck, 'changeController.saveApplyListsData');
+    app.post('/tender/:id/change/apply/cancel/audit', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, changeApplyCheck, 'changeController.checkApplyAuditCancel');
+    app.post('/tender/:id/change/apply/check/revise', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, changeApplyCheck, 'changeController.checkApplyRevise');
+    app.post('/tender/:id/change/apply/cancel/revise', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, changeApplyCheck, 'changeController.cancelApplyRevise');
     // 变更方案
     app.get('/tender/:id/change/plan', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.plan');
     app.get('/tender/:id/change/plan/status/:status', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.planStatus');
@@ -574,6 +583,9 @@ module.exports = app => {
     app.post('/tender/:id/change/plan/:cpid/information/audit/start', sessionAuth, tenderCheck, uncheckTenderCheck, changePlanCheck, tenderBuildCheck, 'changeController.startPlanAudit');
     app.post('/tender/:id/change/plan/:cpid/information/audit/check', sessionAuth, tenderCheck, uncheckTenderCheck, changePlanCheck, tenderBuildCheck, 'changeController.checkPlanAudit');
     app.post('/tender/:id/change/plan/:cpid/information/list/save', sessionAuth, tenderCheck, uncheckTenderCheck, changePlanCheck, 'changeController.savePlanListsData');
+    app.post('/tender/:id/change/plan/cancel/audit', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, changePlanCheck, 'changeController.checkPlanAuditCancel');
+    app.post('/tender/:id/change/plan/check/revise', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, changePlanCheck, 'changeController.checkPlanRevise');
+    app.post('/tender/:id/change/plan/cancel/revise', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, changePlanCheck, 'changeController.cancelPlanRevise');
     // 材料调差
     app.get('/tender/:id/measure/material', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.index');
     app.post('/tender/:id/measure/material/add', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, 'materialController.add');

+ 36 - 30
app/service/change.js

@@ -1630,13 +1630,15 @@ module.exports = app => {
          */
         async checkAgain(cid) {
             // 初始化事务
+            const time = new Date();
             this.transaction = await this.db.beginTransaction();
             let result = false;
             try {
                 const changeInfo = await this.getDataByCondition({ cid });
 
                 // 获取终审
-                const auditInfo = (
+                const zsAudit = await this.ctx.service.changeAudit.getAuditorByStatus(cid, changeInfo.times, audit.flow.auditStatus.checked);
+                const lastAudit = (
                     await this.ctx.service.changeAudit.getAllDataByCondition({
                         where: { cid },
                         orders: [['usort', 'desc']],
@@ -1644,39 +1646,40 @@ module.exports = app => {
                         offset: 0,
                     })
                 )[0];
-                let usort = auditInfo.usort + 1;
+                let usort = lastAudit.usort + 1;
+                const insert_audit_array = [];
 
                 // 新增2个审批状态到审批列表中
-                const insert_audit1 = {
-                    tid: auditInfo.tid,
-                    cid: auditInfo.cid,
-                    uid: auditInfo.uid,
-                    name: auditInfo.name,
-                    jobs: auditInfo.jobs,
-                    company: auditInfo.company,
-                    times: auditInfo.times,
-                    usite: auditInfo.usite,
+                insert_audit_array.push({
+                    tid: changeInfo.tid,
+                    cid: changeInfo.cid,
+                    uid: zsAudit.uid,
+                    name: zsAudit.name,
+                    jobs: zsAudit.jobs,
+                    company: zsAudit.company,
+                    times: zsAudit.times,
+                    usite: zsAudit.usite,
                     usort,
-                    sin_time: new Date(),
+                    sin_time: time,
                     status: audit.flow.auditStatus.checkAgain,
-                };
-                await this.transaction.insert(this.ctx.service.changeAudit.tableName, insert_audit1);
+                    sdesc: '',
+                });
                 usort++;
                 // 新增2个审批人到审批列表中
-                const insert_audit2 = {
-                    tid: auditInfo.tid,
-                    cid: auditInfo.cid,
-                    uid: auditInfo.uid,
-                    name: auditInfo.name,
-                    jobs: auditInfo.jobs,
-                    company: auditInfo.company,
-                    times: auditInfo.times,
-                    usite: auditInfo.usite,
+                insert_audit_array.push({
+                    tid: changeInfo.tid,
+                    cid: changeInfo.cid,
+                    uid: zsAudit.uid,
+                    name: zsAudit.name,
+                    jobs: zsAudit.jobs,
+                    company: zsAudit.company,
+                    times: zsAudit.times,
+                    usite: zsAudit.usite,
                     usort,
                     status: audit.flow.auditStatus.checking,
-                    sin_time: new Date(),
-                };
-                await this.transaction.insert(this.ctx.service.changeAudit.tableName, insert_audit2);
+                    sin_time: time,
+                });
+                await this.transaction.insert(this.ctx.service.changeAudit.tableName, insert_audit_array);
 
                 // 审批列表数据也要回退
                 let total_price = 0;
@@ -1684,6 +1687,7 @@ module.exports = app => {
                 const changeList = await this.ctx.service.changeAuditList.getAllDataByCondition({
                     where: { cid: changeInfo.cid },
                 });
+                const updateChangeList = [];
                 for (const cl of changeList) {
                     const audit_amount = cl.audit_amount.split(',');
                     const last_amount = audit_amount[audit_amount.length - 1];
@@ -1692,16 +1696,18 @@ module.exports = app => {
                         id: cl.id,
                         audit_amount: audit_amount.join(','),
                         samount: '',
+                        spamount: parseFloat(last_amount),
                     };
                     total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), tp_decimal));
-                    await this.transaction.update(this.ctx.service.changeAuditList.tableName, list_update);
+                    updateChangeList.push(list_update);
                 }
+                if (updateChangeList.length > 0) await this.transaction.updateRows(this.ctx.service.changeAuditList.tableName, updateChangeList);
 
                 // 设置变更令审批中
                 const change_update = {
                     p_code: null,
                     status: audit.flow.status.checking,
-                    cin_time: Date.parse(new Date()) / 1000,
+                    cin_time: Date.parse(time) / 1000,
                     sin_time: null,
                     total_price,
                 };
@@ -1718,7 +1724,7 @@ module.exports = app => {
                 const shenpiUrl = await this.ctx.helper.urlToShort(
                     this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + changeInfo.tid + '/change/' + changeInfo.cid + '/information#shenpi'
                 );
-                await this.ctx.helper.sendAliSms(auditInfo.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), SmsAliConst.template.change_check, {
+                await this.ctx.helper.sendAliSms(zsAudit.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), SmsAliConst.template.change_check, {
                     biangeng: code,
                     code: shenpiUrl,
                 });
@@ -1731,7 +1737,7 @@ module.exports = app => {
                     code: this.ctx.session.sessionProject.code,
                     c_name: changeInfo.name,
                 };
-                await this.ctx.helper.sendWechat(auditInfo.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
+                await this.ctx.helper.sendWechat(zsAudit.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
 
             } catch (error) {
                 await this.transaction.rollback();

+ 51 - 9
app/service/change_apply.js

@@ -133,14 +133,14 @@ module.exports = app => {
                         ];
                         break;
                     case 1: // 待处理(你的)
-                        sql = 'SELECT a.*, p.name as account_name FROM ?? as a LEFT JOIN ?? AS p On a.notice_uid = p.id WHERE a.tid = ? AND (a.id in(SELECT b.caid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ?)))';
-                        sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, this.ctx.service.changeApplyAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.checkNo];
+                        sql = 'SELECT a.*, p.name as account_name FROM ?? as a LEFT JOIN ?? AS p On a.notice_uid = p.id WHERE a.tid = ? AND (a.id in(SELECT b.caid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)))';
+                        sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, this.ctx.service.changeApplyAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.checkNo, audit.status.revise];
                         break;
                     case 5: // 待上报(所有的)PS:取未上报,退回,修订的变更令
                         sql =
                             'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.notice_uid = p.id WHERE ' +
                             // 'a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.caid) AND ' +
-                            'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ?)';
+                            'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)';
                         sqlParam = [
                             this.tableName,
                             this.ctx.service.projectAccount.tableName,
@@ -149,6 +149,7 @@ module.exports = app => {
                             tenderId,
                             audit.status.uncheck,
                             audit.status.checkNo,
+                            audit.status.revise,
                         ];
                         break;
                     case 2: // 进行中(所有的)
@@ -220,15 +221,15 @@ module.exports = app => {
                     //     uid: this.ctx.session.sessionUser.accountId,
                     //     status: 2,
                     // });
-                    const sql6 = 'SELECT count(*) AS count FROM ?? as a WHERE a.tid = ? AND (a.id in(SELECT b.caid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ?)))';
-                    const sqlParam6 = [this.tableName, tenderId, this.ctx.service.changeApplyAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.checkNo];
+                    const sql6 = 'SELECT count(*) AS count FROM ?? as a WHERE a.tid = ? AND (a.id in(SELECT b.caid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)))';
+                    const sqlParam6 = [this.tableName, tenderId, this.ctx.service.changeApplyAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.checkNo, audit.status.revise];
                     const result6 = await this.db.query(sql6, sqlParam6);
                     return result6[0].count;
                 case 5: // 待上报(所有的)PS:取未上报,退回的变更立项
                     const sql2 =
                         'SELECT count(*) AS count FROM ?? AS a WHERE ' +
                         // 'a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.caid) ' +
-                        'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ?)';
+                        'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)';
                     const sqlParam2 = [
                         this.tableName,
                         // this.ctx.service.changeApplyAudit.tableName,
@@ -236,6 +237,7 @@ module.exports = app => {
                         tenderId,
                         audit.status.uncheck,
                         audit.status.checkNo,
+                        audit.status.revise,
                     ];
                     const result2 = await this.db.query(sql2, sqlParam2);
                     return result2[0].count;
@@ -292,15 +294,15 @@ module.exports = app => {
                     //     uid: this.ctx.session.sessionUser.accountId,
                     //     status: 2,
                     // });
-                    const sql6 = 'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? as a WHERE a.tid = ? AND (a.id in(SELECT b.caid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ?)))';
-                    const sqlParam6 = [this.tableName, tenderId, this.ctx.service.changeApplyAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.checkNo];
+                    const sql6 = 'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? as a WHERE a.tid = ? AND (a.id in(SELECT b.caid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)))';
+                    const sqlParam6 = [this.tableName, tenderId, this.ctx.service.changeApplyAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.checkNo, audit.status.revise];
                     const result6 = await this.db.query(sql6, sqlParam6);
                     return result6[0].total_price ? result6[0].total_price : 0;
                 case 5: // 待上报(所有的)PS:取未上报,退回的变更立项
                     const sql2 =
                         'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? AS a WHERE ' +
                         // 'a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.caid) ' +
-                        'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ?)';
+                        'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)';
                     const sqlParam2 = [
                         this.tableName,
                         // this.ctx.service.changePlanAudit.tableName,
@@ -308,6 +310,7 @@ module.exports = app => {
                         tenderId,
                         audit.status.uncheck,
                         audit.status.checkNo,
+                        audit.status.revise,
                     ];
                     const result2 = await this.db.query(sql2, sqlParam2);
                     return result2[0].total_price ? result2[0].total_price : 0;
@@ -387,6 +390,8 @@ module.exports = app => {
                 await this.transaction.delete(this.ctx.service.changeApplyAtt.tableName, { caid: id });
                 // 最后删除变更令
                 await this.transaction.delete(this.tableName, { id });
+                // 删除history
+                await this.transaction.delete(this.ctx.service.changeApplyHistory.tableName, { caid: id });
                 // 记录删除日志
                 await this.ctx.service.projectLog.addProjectLog(this.transaction, projectLogConst.type.changeApply, projectLogConst.status.delete, changeInfo.code);
                 await this.transaction.commit();
@@ -397,6 +402,43 @@ module.exports = app => {
             }
             return result;
         }
+
+        async doCheckChangeCanCancel(change) {
+            // 获取当前审批人的上一个审批人,判断是否是当前登录人,并赋予撤回功能,(当审批人存在有审批过时,上一人不允许再撤回)
+            const status = audit.status;
+            const accountId = this.ctx.session.sessionUser.accountId;
+            const auditors = change.auditors;
+            change.cancancel = 0;
+            if (change.status !== status.checked && change.status !== status.uncheck && change.status !== status.revise) {
+                if (change.status !== status.checkNo) {
+                    // 找出当前操作人上一个审批人,包括审批完成的和退回上一个审批人的,同时当前操作人为第一人时,就是则为原报
+                    const onAuditor = this._.find(auditors, function(item) {
+                        return item.aid === change.curAuditor.aid && item.status === status.checking;
+                    });
+                    const preAudit = onAuditor.order !== 1 ? this._.find(auditors, { order: onAuditor.order - 1 }) : false;
+                    const preAid = preAudit ? (preAudit.status !== status.checkAgain ? preAudit.aid : false) : change.uid;// 已发起重审无法撤回
+                    if (onAuditor && onAuditor.aid === preAid && preAudit.status === status.checkCancel) {
+                        return;// 不可以多次撤回
+                    } else if (preAid === accountId && preAid !== change.uid) {
+                        if (preAudit.status === status.checked) {
+                            change.cancancel = 2;// 审批人撤回审批通过
+                        }
+                        // else if (preAudit.status === status.checkNoPre) {
+                        //     change.cancancel = 3;// 审批人撤回审批退回上一人
+                        // }
+                        change.preAudit = preAudit;
+                    } else if (preAid === accountId && preAid === change.uid) {
+                        change.cancancel = 1;// 原报撤回
+                    }
+                } else {
+                    const lastAuditors = await this.service.changeApplyAudit.getAuditors(change.id, change.times - 1);
+                    const onAuditor = this._.findLast(lastAuditors, { status: status.checkNo });
+                    if (onAuditor && onAuditor.aid === accountId) {
+                        change.cancancel = 4;// 审批人撤回退回原报
+                    }
+                }
+            }
+        }
     }
 
     return ChangeApply;

+ 346 - 3
app/service/change_apply_audit.js

@@ -75,8 +75,8 @@ module.exports = app => {
         async getAuditGroupByList(changeId, times) {
             const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`caid`, la.`aid`, la.`order` ' +
                 '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
-                '  WHERE la.`caid` = ? and la.`times` = ? GROUP BY la.`aid` ORDER BY la.`order`';
-            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, changeId, times];
+                '  WHERE la.`caid` = ? and la.`times` = ? and la.`status` != ? and la.`status` != ? GROUP BY la.`aid` ORDER BY la.`order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, changeId, times, auditConst.status.revise, auditConst.status.cancelRevise];
             return await this.db.query(sql, sqlParam);
         }
 
@@ -95,6 +95,7 @@ module.exports = app => {
             switch (status) {
                 case auditConst.status.checking :
                 case auditConst.status.checked :
+                case auditConst.status.revise :
                     sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`caid`, la.`aid`, la.`order` ' +
                         '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
                         '  WHERE la.`caid` = ? and la.`status` = ? ' +
@@ -117,6 +118,12 @@ module.exports = app => {
             return auditor;
         }
 
+        async getLastAudit(caid, times, transaction = null) {
+            const sql = 'SELECT * FROM ?? WHERE `caid` = ? AND `times` = ? ORDER BY `order` DESC';
+            const sqlParam = [this.tableName, caid, times];
+            return transaction ? await transaction.queryOne(sql, sqlParam) : await this.db.queryOne(sql, sqlParam);
+        }
+
         /**
          * 获取审核人流程列表(包括原报)
          * @param {Number} materialId 调差id
@@ -279,7 +286,7 @@ module.exports = app => {
                     c_name: this.ctx.change.name,
                 };
                 await this.ctx.helper.sendWechat(audit.aid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
-
+                await transaction.delete(this.ctx.service.changeProjectHistory.tableName, { caid: caId });
                 // todo 更新标段tender状态 ?
                 await transaction.commit();
             } catch (err) {
@@ -457,6 +464,7 @@ module.exports = app => {
 
         async _checkNo(pid, caId, checkData, times) {
             const time = new Date();
+            const changeData = await this.ctx.service.changeApply.getDataById(caId);
             // 整理当前流程审核人状态更新
             const audit = await this.getDataByCondition({ caid: caId, times, status: auditConst.status.checking });
             if (!audit) {
@@ -504,6 +512,12 @@ module.exports = app => {
                     c_name: this.ctx.change.name,
                 };
                 await this.ctx.helper.sendWechat(users, smsTypeConst.const.BG, smsTypeConst.judge.result.toString(), wxConst.template.change, wechatData);
+                // 生成内容保存表至zh_change_project_history中,用于撤回
+                // 回退spamount值数据
+                const changeList = await this.ctx.service.changeApplyList.getAllDataByCondition({
+                    where: { caid: caId },
+                });
+                await this.ctx.service.changeApplyHistory.saveHistory(transaction, changeData, changeList);
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
@@ -545,6 +559,335 @@ module.exports = app => {
             const sqlParam = [tenderId];
             return this.db.query(sql, sqlParam);
         }
+
+        /**
+         * 审批撤回
+         * @param {Number} stageId - 标段id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<void>}
+         */
+        async checkCancel(change) {
+
+            // 分3种情况,根据ctx.cancancel值判断:
+            // 1.原报发起撤回,当前流程删除,并回到待上报
+            // 2.审批人撤回审批通过,增加流程,并回到它审批中
+            // 4.审批人撤回退回原报操作,删除新增的审批流,增加流程,回滚到它审批中
+            switch (change.cancancel) {
+                case 1: await this._userCheckCancel(change); break;
+                case 2: await this._auditCheckCancel(change); break;
+                case 4: await this._auditCheckCancelNo(change); break;
+                default: throw '不可撤回,请刷新页面重试';
+            }
+        }
+        /**
+         * 原报撤回,直接改动审批人状态
+         * 如果存在审批人数据,将其改为原报流程数据,但保留原提交人
+         *
+         * 一审 1 A checking  ->  A uncheck status改   pay/jl:删0(jl为增量数据,只删重复部分) 1->0 删1
+         * ...
+         *
+         * @param stage
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _userCheckCancel(change) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 整理当前流程审核人状态更新
+                const curAudit = await this.getDataByCondition({ caid: change.id, times: change.times, status: auditConst.status.checking });
+                // 审批人变成待审批状态
+                await transaction.update(this.tableName, {
+                    id: curAudit.id,
+                    status: auditConst.status.uncheck,
+                    begin_time: null,
+                    opinion: null,
+                });
+                // 变成待上报状态
+                await transaction.update(this.ctx.service.changeApply.tableName, {
+                    id: change.id,
+                    status: change.times === 1 ? auditConst.status.uncheck : auditConst.status.checkNo,
+                });
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+
+        }
+        /**
+         * 审批人撤回审批通过,插入两条数据
+         *
+         * 一审 1 A checked             一审 1 A checked
+         * 二审 2 B checked   pre ->    二审 2 B checked
+         * 三审 3 C checking  cur       二审 3 B checkCancel  增                增extra_his     增tp_his
+         * 四审 4 D uncheck             二审 4 B checking     增                增pay_cur
+         *                             三审 5 C uncheck      order、status改
+         *                             四审 6 D uncheck      order改
+         *
+         * @param stage
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _auditCheckCancel(change) {
+            const time = new Date();
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 整理当前流程审核人状态更新
+                const curAudit = await this.getDataByCondition({ caid: change.id, times: change.times, status: auditConst.status.checking });
+                const preAudit = change.preAudit;
+                if (!curAudit || curAudit.order <= 1 || !preAudit) {
+                    throw '撤回用户数据错误';
+                }
+                // 顺移其后审核人流程顺序
+                const sql = 'UPDATE ' + this.tableName + ' SET `order` = `order` + 2 WHERE caid = ? AND times = ? AND `order` > ?';
+                await transaction.query(sql, [change.id, change.times, curAudit.order]);
+                // 当前审批人2次添加至流程中
+                const newAuditors = [];
+                // 先入撤回记录
+                newAuditors.push({
+                    tid: change.tid,
+                    caid: change.id,
+                    aid: preAudit.aid,
+                    times: change.times,
+                    order: curAudit.order,
+                    status: auditConst.status.checkCancel,
+                    begin_time: time,
+                    end_time: time,
+                    opinion: '',
+                });
+                newAuditors.push({
+                    tid: change.tid,
+                    caid: change.id,
+                    aid: preAudit.aid,
+                    times: change.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 });
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 审批人撤回审批退回原报
+         *
+         * 1# 一审 1 A checked              1# 一审 1 A checked
+         *    二审 2 B checkNo   pre   ->      二审 2 B checkNo
+         *    三审 3 C uncheck                 二审 3 B checkCancel    增       pay: 2#0 -> 1#3   jl: 2#0 -> 1#3   增tp_his   增extra_his
+         *                                    二审 4 B checking       增       pay: 2#0 -> 1#4
+         *                                    三审 5 C uncheck        order改
+         *
+         * 2# 一审 1 A uncheck              2#                        删       pay: 2#0删   jl: 2#0删
+         *    二审 2 B uncheck
+         *    三审 3 C uncheck
+         *
+         * @param stage
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _auditCheckCancelNo(change) {
+
+            const time = new Date();
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // const curAudit = await this.getDataByCondition({ cpid: change.id, times: change.times - 1, status: auditConst.status.back });
+                const curAudit = await this.getAuditorByStatus(change.id, auditConst.status.checkNo, change.times);
+                // 整理上一个流程审核人状态更新
+                // 顺移其后审核人流程顺序
+                const sql = 'UPDATE ' + this.tableName + ' SET `order` = `order` + 2 WHERE caid = ? AND times = ? AND `order` > ?';
+                await transaction.query(sql, [change.id, change.times - 1, curAudit.order]);
+                // 当前审批人2次添加至流程中
+                const newAuditors = [];
+                newAuditors.push({
+                    tid: change.tid,
+                    caid: change.id,
+                    aid: curAudit.aid,
+                    times: curAudit.times,
+                    order: curAudit.order + 1,
+                    status: auditConst.status.checkCancel,
+                    begin_time: time,
+                    end_time: time,
+                    opinion: '',
+                });
+                newAuditors.push({
+                    tid: change.tid,
+                    caid: change.id,
+                    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, { caid: change.id, times: change.times });
+                // 回退数据
+                await this.ctx.service.changeApplyHistory.returnHistory(transaction, change.id);
+                await transaction.delete(this.ctx.service.changeApplyHistory.tableName, { caid: change.id });
+                // // 设置变更立项为审批中
+                // await transaction.update(this.ctx.service.changeProject.tableName, {
+                //     id: change.id,
+                //     time: change.times - 1,
+                //     status: auditConst.status.checking,
+                // });
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 重新审批变更令
+         * @param { string } cid - 查询的清单
+         * @return {Promise<*>} - 可用的变更令列表
+         */
+        async checkRevise(change) {
+            const time = new Date();
+            // 初始化事务
+            const transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                const pid = this.ctx.session.sessionProject.id;
+                // 获取审核人列表
+                const sql = 'SELECT `tid`, `caid`, `aid`, `order` FROM ?? WHERE `caid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
+                const sqlParam = [this.tableName, change.id, change.times];
+                const auditors = await this.db.query(sql, sqlParam);
+                // 添加到消息推送表
+                const noticeContent = await this.getNoticeContent(pid, change.tid, change.id, this.ctx.session.sessionUser.accountId, '发起修订');
+                const records = [];
+                auditors.forEach(auditor => {
+                    records.push({
+                        pid,
+                        type: pushType.changeApply,
+                        uid: auditor.aid,
+                        status: auditConst.status.revise,
+                        content: noticeContent,
+                    });
+                });
+                await transaction.insert('zh_notice', records);
+
+                // 获取当前次数审批人列表
+                const auditList = await this.getAuditGroupByList(change.id, change.times);
+                const lastAudit = await this.getLastAudit(change.id, change.times);
+                const insert_audit_array = [];
+                // 新增一个发起修订状态到审批流程中
+                const revise_audit = {
+                    tid: change.tid,
+                    caid: change.id,
+                    aid: change.uid,
+                    times: change.times,
+                    order: lastAudit.order + 1,
+                    status: auditConst.status.revise,
+                    begin_time: time,
+                    end_time: time,
+                    opinion: '',
+                };
+                insert_audit_array.push(revise_audit);
+                // 新增新一次的审批人列表
+                let order = 1;
+                for (const al of auditList) {
+                    const insert_audit = {
+                        tid: change.tid,
+                        caid: change.id,
+                        aid: al.aid,
+                        times: change.times + 1,
+                        order,
+                        status: auditConst.status.uncheck,
+                    };
+                    insert_audit_array.push(insert_audit);
+                    order++;
+                }
+                await transaction.insert(this.tableName, insert_audit_array);
+                // 生成内容保存表至zh_change_history中,用于撤销修订回退
+                const changeData = await transaction.get(this.ctx.service.changeApply.tableName, { id: change.id });
+                const changeList = await this.ctx.service.changeApplyList.getAllDataByCondition({
+                    where: { caid: change.id },
+                });
+                await this.ctx.service.changeApplyHistory.saveHistory(transaction, changeData, changeList);
+                // 设置变更立项修订状态
+                await transaction.update(this.ctx.service.changeApply.tableName, {
+                    id: change.id,
+                    notice_code: null,
+                    notice_uid: null,
+                    decimal: null,
+                    status: auditConst.status.revise,
+                    times: change.times + 1,
+                });
+
+                await transaction.commit();
+                result = true;
+            } catch (error) {
+                console.log(error);
+                await transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
+
+        /**
+         * 撤销修订变更令
+         * @param { string } cid - 查询的清单
+         * @return {Promise<*>} - 可用的变更令列表
+         */
+        async cancelRevise(change) {
+            const time = new Date();
+            // 初始化事务
+            const transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                const pid = this.ctx.session.sessionProject.id;
+                // 获取审核人列表
+                const sql = 'SELECT `tid`, `caid`, `aid`, `order` FROM ?? WHERE `caid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
+                const sqlParam = [this.tableName, change.id, change.times - 1];
+                const auditors = await this.db.query(sql, sqlParam);
+                // 添加到消息推送表
+                const noticeContent = await this.getNoticeContent(pid, change.tid, change.id, this.ctx.session.sessionUser.accountId, '撤销修订');
+                const records = [];
+                auditors.forEach(auditor => {
+                    records.push({
+                        pid,
+                        type: pushType.changeApply,
+                        uid: auditor.aid,
+                        status: auditConst.status.cancelRevise,
+                        content: noticeContent,
+                    });
+                });
+                await transaction.insert('zh_notice', records);
+                const lastAudit = await this.getLastAudit(change.id, change.times - 1);
+                // 新增一个撤销修订状态到审批流程中
+                const revise_audit = {
+                    tid: change.tid,
+                    caid: change.id,
+                    aid: this.ctx.session.sessionUser.accountId,
+                    times: change.times - 1,
+                    order: lastAudit.order + 1,
+                    status: auditConst.status.cancelRevise,
+                    begin_time: time,
+                    end_time: time,
+                    opinion: '',
+                };
+                await transaction.insert(this.ctx.service.changeApplyAudit.tableName, revise_audit);
+                await transaction.delete(this.ctx.service.changeApplyAudit.tableName, { caid: change.id, times: change.times });
+                await this.ctx.service.changeApplyHistory.returnHistory(transaction, change.id);
+                await transaction.delete(this.ctx.service.changeApplyHistory.tableName, { caid: change.id });
+                await transaction.commit();
+                result = true;
+            } catch (error) {
+                console.log(error);
+                await transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
     }
 
     return ChangeApplyAudit;

+ 53 - 0
app/service/change_apply_history.js

@@ -0,0 +1,53 @@
+'use strict';
+
+/**
+ * 变更新增部位插入记录表
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+
+    class ChangeApplyHistory extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'change_apply_history';
+        }
+
+        async saveHistory(transaction, data, list) {
+            await transaction.insert(this.tableName, {
+                tid: data.tid,
+                caid: data.id,
+                info_json: JSON.stringify(data),
+                list_json: JSON.stringify(list),
+            });
+        }
+
+        async returnHistory(transaction, caid) {
+            const data = await transaction.get(this.tableName, { caid });
+            if (!data) throw '撤销前数据不存在,无法撤销';
+            const change_update = {};
+            const oldInfo = JSON.parse(data.info_json);
+            for (const key in oldInfo) {
+                if (key !== 'in_time') {
+                    change_update[key] = oldInfo[key];
+                }
+            }
+            await transaction.update(this.ctx.service.changeApply.tableName, change_update);
+            const oldList = JSON.parse(data.list_json);
+            // 先删后插
+            await transaction.delete(this.ctx.service.changeApplyList.tableName, { caid });
+            await transaction.insert(this.ctx.service.changeApplyList.tableName, oldList);
+        }
+    }
+
+    return ChangeApplyHistory;
+};

+ 5 - 4
app/service/change_audit.js

@@ -926,13 +926,14 @@ module.exports = app => {
                 const tp_decimal = change.tp_decimal ? change.tp_decimal : this.ctx.tender.info.decimal.tp;
                 const updateList = [];
                 for (const cl of changeList) {
-                    const audit_amount = cl.audit_amount.split(',');
-                    const last_amount = audit_amount[audit_amount.length - 1] ? audit_amount[audit_amount.length - 1] : 0;
+                    const audit_amount = cl.audit_amount !== '' ? cl.audit_amount.split(',') : [];
+                    // const last_amount = audit_amount[audit_amount.length - 1] ? audit_amount[audit_amount.length - 1] : 0;
+                    audit_amount.push(cl.spamount);
                     const list_update = {
                         id: cl.id,
-                        spamount: parseFloat(last_amount),
+                        audit_amount: audit_amount.join(','),
                     };
-                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), tp_decimal));
+                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, parseFloat(cl.spamount), tp_decimal));
                     updateList.push(list_update);
                 }
                 if (updateList.length > 0) await transaction.updateRows(this.ctx.service.changeAuditList.tableName, updateList);

+ 51 - 9
app/service/change_plan.js

@@ -154,14 +154,14 @@ module.exports = app => {
                         ];
                         break;
                     case 1: // 待处理(你的)
-                        sql = 'SELECT a.*, p.name as account_name FROM ?? as a LEFT JOIN ?? AS p On a.uid = p.id WHERE a.tid = ? AND (a.id in(SELECT b.cpid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ?)))';
-                        sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, this.ctx.service.changePlanAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.checkNo];
+                        sql = 'SELECT a.*, p.name as account_name FROM ?? as a LEFT JOIN ?? AS p On a.uid = p.id WHERE a.tid = ? AND (a.id in(SELECT b.cpid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)))';
+                        sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, this.ctx.service.changePlanAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.checkNo, audit.status.revise];
                         break;
                     case 5: // 待上报(所有的)PS:取未上报,退回,修订的变更令
                         sql =
                             'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE ' +
                             // 'a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) AND ' +
-                            'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ?)';
+                            'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)';
                         sqlParam = [
                             this.tableName,
                             this.ctx.service.projectAccount.tableName,
@@ -170,6 +170,7 @@ module.exports = app => {
                             tenderId,
                             audit.status.uncheck,
                             audit.status.checkNo,
+                            audit.status.revise,
                         ];
                         break;
                     case 2: // 进行中(所有的)
@@ -241,15 +242,15 @@ module.exports = app => {
                     //     uid: this.ctx.session.sessionUser.accountId,
                     //     status: 2,
                     // });
-                    const sql6 = 'SELECT count(*) AS count FROM ?? as a WHERE a.tid = ? AND (a.id in(SELECT b.cpid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ?)))';
-                    const sqlParam6 = [this.tableName, tenderId, this.ctx.service.changePlanAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.checkNo];
+                    const sql6 = 'SELECT count(*) AS count FROM ?? as a WHERE a.tid = ? AND (a.id in(SELECT b.cpid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)))';
+                    const sqlParam6 = [this.tableName, tenderId, this.ctx.service.changePlanAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.checkNo, audit.status.revise];
                     const result6 = await this.db.query(sql6, sqlParam6);
                     return result6[0].count;
                 case 5: // 待上报(所有的)PS:取未上报,退回的变更立项
                     const sql2 =
                         'SELECT count(*) AS count FROM ?? AS a WHERE ' +
                         // 'a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) ' +
-                        'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ?)';
+                        'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)';
                     const sqlParam2 = [
                         this.tableName,
                         // this.ctx.service.changePlanAudit.tableName,
@@ -257,6 +258,7 @@ module.exports = app => {
                         tenderId,
                         audit.status.uncheck,
                         audit.status.checkNo,
+                        audit.status.revise,
                     ];
                     const result2 = await this.db.query(sql2, sqlParam2);
                     return result2[0].count;
@@ -313,15 +315,15 @@ module.exports = app => {
                     //     uid: this.ctx.session.sessionUser.accountId,
                     //     status: 2,
                     // });
-                    const sql6 = 'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? as a WHERE a.tid = ? AND (a.id in(SELECT b.cpid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ?)))';
-                    const sqlParam6 = [this.tableName, tenderId, this.ctx.service.changePlanAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.checkNo];
+                    const sql6 = 'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? as a WHERE a.tid = ? AND (a.id in(SELECT b.cpid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)))';
+                    const sqlParam6 = [this.tableName, tenderId, this.ctx.service.changePlanAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.checkNo, audit.status.revise];
                     const result6 = await this.db.query(sql6, sqlParam6);
                     return result6[0].total_price ? result6[0].total_price : 0;
                 case 5: // 待上报(所有的)PS:取未上报,退回的变更立项
                     const sql2 =
                         'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? AS a WHERE ' +
                         // 'a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) ' +
-                        'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ?)';
+                        'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)';
                     const sqlParam2 = [
                         this.tableName,
                         // this.ctx.service.changePlanAudit.tableName,
@@ -329,6 +331,7 @@ module.exports = app => {
                         tenderId,
                         audit.status.uncheck,
                         audit.status.checkNo,
+                        audit.status.revise,
                     ];
                     const result2 = await this.db.query(sql2, sqlParam2);
                     return result2[0].total_price ? result2[0].total_price : 0;
@@ -410,6 +413,8 @@ module.exports = app => {
                 await this.transaction.delete(this.ctx.service.changePlanAtt.tableName, { cpid: id });
                 // 最后删除变更令
                 await this.transaction.delete(this.tableName, { id });
+                // 删除history
+                await this.transaction.delete(this.ctx.service.changePlanHistory.tableName, { cpid: id });
                 // 记录删除日志
                 await this.ctx.service.projectLog.addProjectLog(this.transaction, projectLogConst.type.changePlan, projectLogConst.status.delete, changeInfo.code);
                 await this.transaction.commit();
@@ -420,6 +425,43 @@ module.exports = app => {
             }
             return result;
         }
+
+        async doCheckChangeCanCancel(change) {
+            // 获取当前审批人的上一个审批人,判断是否是当前登录人,并赋予撤回功能,(当审批人存在有审批过时,上一人不允许再撤回)
+            const status = audit.status;
+            const accountId = this.ctx.session.sessionUser.accountId;
+            const auditors = change.auditors;
+            change.cancancel = 0;
+            if (change.status !== status.checked && change.status !== status.uncheck && change.status !== status.revise) {
+                if (change.status !== status.checkNo) {
+                    // 找出当前操作人上一个审批人,包括审批完成的和退回上一个审批人的,同时当前操作人为第一人时,就是则为原报
+                    const onAuditor = this._.find(auditors, function(item) {
+                        return item.aid === change.curAuditor.aid && item.status === status.checking;
+                    });
+                    const preAudit = onAuditor.order !== 1 ? this._.find(auditors, { order: onAuditor.order - 1 }) : false;
+                    const preAid = preAudit ? (preAudit.status !== status.checkAgain ? preAudit.aid : false) : change.uid;// 已发起重审无法撤回
+                    if (onAuditor && onAuditor.aid === preAid && preAudit.status === status.checkCancel) {
+                        return;// 不可以多次撤回
+                    } else if (preAid === accountId && preAid !== change.uid) {
+                        if (preAudit.status === status.checked) {
+                            change.cancancel = 2;// 审批人撤回审批通过
+                        }
+                        // else if (preAudit.status === status.checkNoPre) {
+                        //     change.cancancel = 3;// 审批人撤回审批退回上一人
+                        // }
+                        change.preAudit = preAudit;
+                    } else if (preAid === accountId && preAid === change.uid) {
+                        change.cancancel = 1;// 原报撤回
+                    }
+                } else {
+                    const lastAuditors = await this.service.changePlanAudit.getAuditors(change.id, change.times - 1);
+                    const onAuditor = this._.findLast(lastAuditors, { status: status.checkNo });
+                    if (onAuditor && onAuditor.aid === accountId) {
+                        change.cancancel = 4;// 审批人撤回退回原报
+                    }
+                }
+            }
+        }
     }
 
     return ChangePlan;

+ 367 - 3
app/service/change_plan_audit.js

@@ -75,8 +75,8 @@ module.exports = app => {
         async getAuditGroupByList(changeId, times) {
             const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`cpid`, la.`aid`, la.`order` ' +
                 '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
-                '  WHERE la.`cpid` = ? and la.`times` = ? GROUP BY la.`aid` ORDER BY la.`order`';
-            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, changeId, times];
+                '  WHERE la.`cpid` = ? and la.`times` = ? and la.`status` != ? and la.`status` != ? GROUP BY la.`aid` ORDER BY la.`order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, changeId, times, auditConst.status.revise, auditConst.status.cancelRevise];
             return await this.db.query(sql, sqlParam);
         }
 
@@ -95,6 +95,7 @@ module.exports = app => {
             switch (status) {
                 case auditConst.status.checking :
                 case auditConst.status.checked :
+                case auditConst.status.revise :
                     sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`cpid`, la.`aid`, la.`order`, la.`status` ' +
                         '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
                         '  WHERE la.`cpid` = ? and la.`status` = ? ' +
@@ -117,6 +118,12 @@ module.exports = app => {
             return auditor;
         }
 
+        async getLastAudit(cpid, times, transaction = null) {
+            const sql = 'SELECT * FROM ?? WHERE `cpid` = ? AND `times` = ? ORDER BY `order` DESC';
+            const sqlParam = [this.tableName, cpid, times];
+            return transaction ? await transaction.queryOne(sql, sqlParam) : await this.db.queryOne(sql, sqlParam);
+        }
+
         /**
          * 获取审核人流程列表(包括原报)
          * @param {Number} materialId 调差id
@@ -283,7 +290,7 @@ module.exports = app => {
                     c_name: this.ctx.change.name,
                 };
                 await this.ctx.helper.sendWechat(audit.aid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
-
+                await transaction.delete(this.ctx.service.changeProjectHistory.tableName, { cpid: cpId });
                 // todo 更新标段tender状态 ?
                 await transaction.commit();
             } catch (err) {
@@ -470,6 +477,7 @@ module.exports = app => {
 
         async _checkNo(pid, cpId, checkData, times) {
             const time = new Date();
+            const changeData = await this.ctx.service.changePlan.getDataById(cpId);
             // 整理当前流程审核人状态更新
             const audit = await this.getDataByCondition({ cpid: cpId, times, status: auditConst.status.checking });
             if (!audit) {
@@ -518,6 +526,12 @@ module.exports = app => {
                     c_name: this.ctx.change.name,
                 };
                 await this.ctx.helper.sendWechat(users, smsTypeConst.const.BG, smsTypeConst.judge.result.toString(), wxConst.template.change, wechatData);
+                // 生成内容保存表至zh_change_project_history中,用于撤回
+                // 回退spamount值数据
+                const changeList = await this.ctx.service.changePlanList.getAllDataByCondition({
+                    where: { cpid: cpId },
+                });
+                await this.ctx.service.changePlanHistory.saveHistory(transaction, changeData, changeList);
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
@@ -587,6 +601,356 @@ module.exports = app => {
             ];
             return await this.db.query(sql, sqlParam);
         }
+
+        /**
+         * 审批撤回
+         * @param {Number} stageId - 标段id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<void>}
+         */
+        async checkCancel(change) {
+
+            // 分3种情况,根据ctx.cancancel值判断:
+            // 1.原报发起撤回,当前流程删除,并回到待上报
+            // 2.审批人撤回审批通过,增加流程,并回到它审批中
+            // 4.审批人撤回退回原报操作,删除新增的审批流,增加流程,回滚到它审批中
+            switch (change.cancancel) {
+                case 1: await this._userCheckCancel(change); break;
+                case 2: await this._auditCheckCancel(change); break;
+                case 4: await this._auditCheckCancelNo(change); break;
+                default: throw '不可撤回,请刷新页面重试';
+            }
+        }
+        /**
+         * 原报撤回,直接改动审批人状态
+         * 如果存在审批人数据,将其改为原报流程数据,但保留原提交人
+         *
+         * 一审 1 A checking  ->  A uncheck status改   pay/jl:删0(jl为增量数据,只删重复部分) 1->0 删1
+         * ...
+         *
+         * @param stage
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _userCheckCancel(change) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 整理当前流程审核人状态更新
+                const curAudit = await this.getDataByCondition({ cpid: change.id, times: change.times, status: auditConst.status.checking });
+                // 审批人变成待审批状态
+                await transaction.update(this.tableName, {
+                    id: curAudit.id,
+                    status: auditConst.status.uncheck,
+                    begin_time: null,
+                    opinion: null,
+                });
+                // 清单审批值删除并重算变更金额
+                await this.ctx.service.changePlanList.delAuditAmount(transaction, change.id);
+                // 变成待上报状态
+                await transaction.update(this.ctx.service.changePlan.tableName, {
+                    id: change.id,
+                    status: change.times === 1 ? auditConst.status.uncheck : auditConst.status.checkNo,
+                });
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+
+        }
+        /**
+         * 审批人撤回审批通过,插入两条数据
+         *
+         * 一审 1 A checked             一审 1 A checked
+         * 二审 2 B checked   pre ->    二审 2 B checked
+         * 三审 3 C checking  cur       二审 3 B checkCancel  增                增extra_his     增tp_his
+         * 四审 4 D uncheck             二审 4 B checking     增                增pay_cur
+         *                             三审 5 C uncheck      order、status改
+         *                             四审 6 D uncheck      order改
+         *
+         * @param stage
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _auditCheckCancel(change) {
+            const time = new Date();
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 整理当前流程审核人状态更新
+                const curAudit = await this.getDataByCondition({ cpid: change.id, times: change.times, status: auditConst.status.checking });
+                const preAudit = change.preAudit;
+                if (!curAudit || curAudit.order <= 1 || !preAudit) {
+                    throw '撤回用户数据错误';
+                }
+                // 顺移其后审核人流程顺序
+                const sql = 'UPDATE ' + this.tableName + ' SET `order` = `order` + 2 WHERE cpid = ? AND times = ? AND `order` > ?';
+                await transaction.query(sql, [change.id, change.times, curAudit.order]);
+                // 当前审批人2次添加至流程中
+                const newAuditors = [];
+                // 先入撤回记录
+                newAuditors.push({
+                    tid: change.tid,
+                    cpid: change.id,
+                    aid: preAudit.aid,
+                    times: change.times,
+                    order: curAudit.order,
+                    status: auditConst.status.checkCancel,
+                    begin_time: time,
+                    end_time: time,
+                    opinion: '',
+                });
+                newAuditors.push({
+                    tid: change.tid,
+                    cpid: change.id,
+                    aid: preAudit.aid,
+                    times: change.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 });
+                // 清除上一人的值并调整spamount值
+                const updateList = [];
+                const changeList = await this.ctx.service.changePlanList.getAllDataByCondition({
+                    where: { cpid: change.id },
+                });
+                for (const cl of changeList) {
+                    const audit_amount = cl.audit_amount.split(',');
+                    const last_amount = audit_amount[audit_amount.length - 1] ? audit_amount[audit_amount.length - 1] : 0;
+                    audit_amount.splice(-1, 1);
+                    const list_update = {
+                        id: cl.id,
+                        audit_amount: audit_amount.join(','),
+                        spamount: parseFloat(last_amount),
+                    };
+                    updateList.push(list_update);
+                }
+                if (updateList.length > 0) await transaction.updateRows(this.ctx.service.changePlanList.tableName, updateList);
+                // 更新total_price
+                await this.ctx.service.changePlanList.calcCamountSum(transaction);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 审批人撤回审批退回原报
+         *
+         * 1# 一审 1 A checked              1# 一审 1 A checked
+         *    二审 2 B checkNo   pre   ->      二审 2 B checkNo
+         *    三审 3 C uncheck                 二审 3 B checkCancel    增       pay: 2#0 -> 1#3   jl: 2#0 -> 1#3   增tp_his   增extra_his
+         *                                    二审 4 B checking       增       pay: 2#0 -> 1#4
+         *                                    三审 5 C uncheck        order改
+         *
+         * 2# 一审 1 A uncheck              2#                        删       pay: 2#0删   jl: 2#0删
+         *    二审 2 B uncheck
+         *    三审 3 C uncheck
+         *
+         * @param stage
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _auditCheckCancelNo(change) {
+
+            const time = new Date();
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // const curAudit = await this.getDataByCondition({ cpid: change.id, times: change.times - 1, status: auditConst.status.back });
+                const curAudit = await this.getAuditorByStatus(change.id, auditConst.status.checkNo, change.times);
+                // 整理上一个流程审核人状态更新
+                // 顺移其后审核人流程顺序
+                const sql = 'UPDATE ' + this.tableName + ' SET `order` = `order` + 2 WHERE cpid = ? AND times = ? AND `order` > ?';
+                await transaction.query(sql, [change.id, change.times - 1, curAudit.order]);
+                // 当前审批人2次添加至流程中
+                const newAuditors = [];
+                newAuditors.push({
+                    tid: change.tid,
+                    cpid: change.id,
+                    aid: curAudit.aid,
+                    times: curAudit.times,
+                    order: curAudit.order + 1,
+                    status: auditConst.status.checkCancel,
+                    begin_time: time,
+                    end_time: time,
+                    opinion: '',
+                });
+                newAuditors.push({
+                    tid: change.tid,
+                    cpid: change.id,
+                    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, { cpid: change.id, times: change.times });
+                // 回退数据
+                await this.ctx.service.changePlanHistory.returnHistory(transaction, change.id);
+                await transaction.delete(this.ctx.service.changePlanHistory.tableName, { cpid: change.id });
+                // // 设置变更立项为审批中
+                // await transaction.update(this.ctx.service.changeProject.tableName, {
+                //     id: change.id,
+                //     time: change.times - 1,
+                //     status: auditConst.status.checking,
+                // });
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 重新审批变更令
+         * @param { string } cid - 查询的清单
+         * @return {Promise<*>} - 可用的变更令列表
+         */
+        async checkRevise(change) {
+            const time = new Date();
+            // 初始化事务
+            const transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                const pid = this.ctx.session.sessionProject.id;
+                // 获取审核人列表
+                const sql = 'SELECT `tid`, `cpid`, `aid`, `order` FROM ?? WHERE `cpid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
+                const sqlParam = [this.tableName, change.id, change.times];
+                const auditors = await this.db.query(sql, sqlParam);
+                // 添加到消息推送表
+                const noticeContent = await this.getNoticeContent(pid, change.tid, change.id, this.ctx.session.sessionUser.accountId, '发起修订');
+                const records = [];
+                auditors.forEach(auditor => {
+                    records.push({
+                        pid,
+                        type: pushType.changePlan,
+                        uid: auditor.aid,
+                        status: auditConst.status.revise,
+                        content: noticeContent,
+                    });
+                });
+                await transaction.insert('zh_notice', records);
+
+                // 获取当前次数审批人列表
+                const auditList = await this.getAuditGroupByList(change.id, change.times);
+                const lastAudit = await this.getLastAudit(change.id, change.times);
+                const insert_audit_array = [];
+                // 新增一个发起修订状态到审批流程中
+                const revise_audit = {
+                    tid: change.tid,
+                    cpid: change.id,
+                    aid: change.uid,
+                    times: change.times,
+                    order: lastAudit.order + 1,
+                    status: auditConst.status.revise,
+                    begin_time: time,
+                    end_time: time,
+                    opinion: '',
+                };
+                insert_audit_array.push(revise_audit);
+                // 新增新一次的审批人列表
+                let order = 1;
+                for (const al of auditList) {
+                    const insert_audit = {
+                        tid: change.tid,
+                        cpid: change.id,
+                        aid: al.aid,
+                        times: change.times + 1,
+                        order,
+                        status: auditConst.status.uncheck,
+                    };
+                    insert_audit_array.push(insert_audit);
+                    order++;
+                }
+                await transaction.insert(this.tableName, insert_audit_array);
+                // 生成内容保存表至zh_change_history中,用于撤销修订回退
+                const changeData = await transaction.get(this.ctx.service.changePlan.tableName, { id: change.id });
+                const changeList = await this.ctx.service.changePlanList.getAllDataByCondition({
+                    where: { cpid: change.id },
+                });
+                await this.ctx.service.changePlanHistory.saveHistory(transaction, changeData, changeList);
+                // 清单审批值删除并重算变更金额
+                await this.ctx.service.changePlanList.delAuditAmount(transaction, change.id);
+                // 设置变更立项修订状态
+                await transaction.update(this.ctx.service.changePlan.tableName, {
+                    id: change.id,
+                    decimal: null,
+                    status: auditConst.status.revise,
+                    times: change.times + 1,
+                });
+
+                await transaction.commit();
+                result = true;
+            } catch (error) {
+                console.log(error);
+                await transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
+
+        /**
+         * 撤销修订变更令
+         * @param { string } cid - 查询的清单
+         * @return {Promise<*>} - 可用的变更令列表
+         */
+        async cancelRevise(change) {
+            const time = new Date();
+            // 初始化事务
+            const transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                const pid = this.ctx.session.sessionProject.id;
+                // 获取审核人列表
+                const sql = 'SELECT `tid`, `cpid`, `aid`, `order` FROM ?? WHERE `cpid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
+                const sqlParam = [this.tableName, change.id, change.times - 1];
+                const auditors = await this.db.query(sql, sqlParam);
+                // 添加到消息推送表
+                const noticeContent = await this.getNoticeContent(pid, change.tid, change.id, this.ctx.session.sessionUser.accountId, '撤销修订');
+                const records = [];
+                auditors.forEach(auditor => {
+                    records.push({
+                        pid,
+                        type: pushType.changePlan,
+                        uid: auditor.aid,
+                        status: auditConst.status.cancelRevise,
+                        content: noticeContent,
+                    });
+                });
+                await transaction.insert('zh_notice', records);
+                const lastAudit = await this.getLastAudit(change.id, change.times - 1);
+                // 新增一个撤销修订状态到审批流程中
+                const revise_audit = {
+                    tid: change.tid,
+                    cpid: change.id,
+                    aid: this.ctx.session.sessionUser.accountId,
+                    times: change.times - 1,
+                    order: lastAudit.order + 1,
+                    status: auditConst.status.cancelRevise,
+                    begin_time: time,
+                    end_time: time,
+                    opinion: '',
+                };
+                await transaction.insert(this.ctx.service.changePlanAudit.tableName, revise_audit);
+                await transaction.delete(this.ctx.service.changePlanAudit.tableName, { cpid: change.id, times: change.times });
+                await this.ctx.service.changePlanHistory.returnHistory(transaction, change.id);
+                await transaction.delete(this.ctx.service.changePlanHistory.tableName, { cpid: change.id });
+                await transaction.commit();
+                result = true;
+            } catch (error) {
+                console.log(error);
+                await transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
     }
     return ChangePlanAudit;
 };

+ 53 - 0
app/service/change_plan_history.js

@@ -0,0 +1,53 @@
+'use strict';
+
+/**
+ * 变更新增部位插入记录表
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+
+    class ChangePlanHistory extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'change_plan_history';
+        }
+
+        async saveHistory(transaction, data, list) {
+            await transaction.insert(this.tableName, {
+                tid: data.tid,
+                cpid: data.id,
+                info_json: JSON.stringify(data),
+                list_json: JSON.stringify(list),
+            });
+        }
+
+        async returnHistory(transaction, cpid) {
+            const data = await transaction.get(this.tableName, { cpid });
+            if (!data) throw '撤销前数据不存在,无法撤销';
+            const change_update = {};
+            const oldInfo = JSON.parse(data.info_json);
+            for (const key in oldInfo) {
+                if (key !== 'in_time') {
+                    change_update[key] = oldInfo[key];
+                }
+            }
+            await transaction.update(this.ctx.service.changePlan.tableName, change_update);
+            const oldList = JSON.parse(data.list_json);
+            // 先删后插
+            await transaction.delete(this.ctx.service.changePlanList.tableName, { cpid });
+            await transaction.insert(this.ctx.service.changePlanList.tableName, oldList);
+        }
+    }
+
+    return ChangePlanHistory;
+};

+ 65 - 8
app/service/change_project.js

@@ -110,8 +110,10 @@ module.exports = app => {
                 switch (status) {
                     case 0: // 包含你的所有变更立项
                         sql =
-                            'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE a.tid = ? AND ' +
-                            '(a.uid = ? OR (a.status != ? AND (a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) OR a.id IN (SELECT c.cpid FROM ?? AS c WHERE c.aid = ? AND c.tid = ?))) OR a.status = ? )';
+                            'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE a.tid = ? AND' +
+                            ' (a.uid = ? OR (a.status != ? AND (a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid)' +
+                            ' OR a.id IN (SELECT c.cpid FROM ?? AS c WHERE c.aid = ? AND c.tid = ?)))' +
+                            ' OR a.status = ? )';
                         sqlParam = [
                             this.tableName,
                             this.ctx.service.projectAccount.tableName,
@@ -127,14 +129,22 @@ module.exports = app => {
                         ];
                         break;
                     case 1: // 待处理(你的)
-                        sql = 'SELECT a.*, p.name as account_name FROM ?? as a LEFT JOIN ?? AS p On a.uid = p.id WHERE a.tid = ? AND (a.id in(SELECT b.cpid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ?)))';
-                        sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, this.ctx.service.changeProjectAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.back];
+                        sql = 'SELECT a.*, p.name as account_name FROM ?? as a LEFT JOIN ?? AS p On a.uid = p.id WHERE' +
+                            ' a.tid = ? AND (a.id in(SELECT b.cpid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?)' +
+                            ' OR (a.uid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)))';
+                        sqlParam = [this.tableName,
+                            this.ctx.service.projectAccount.tableName,
+                            tenderId, this.ctx.service.changeProjectAudit.tableName,
+                            tenderId, this.ctx.session.sessionUser.accountId,
+                            audit.status.checking, this.ctx.session.sessionUser.accountId,
+                            audit.status.uncheck, audit.status.back, audit.status.revise,
+                        ];
                         break;
                     case 5: // 待上报(所有的)PS:取未上报,退回,修订的变更令
                         sql =
                             'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE ' +
                             // 'a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) AND ' +
-                            'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ?)';
+                            'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)';
                         sqlParam = [
                             this.tableName,
                             this.ctx.service.projectAccount.tableName,
@@ -143,6 +153,7 @@ module.exports = app => {
                             tenderId,
                             audit.status.uncheck,
                             audit.status.back,
+                            audit.status.revise,
                         ];
                         break;
                     case 2: // 进行中(所有的)
@@ -245,15 +256,15 @@ module.exports = app => {
                     //     uid: this.ctx.session.sessionUser.accountId,
                     //     status: 2,
                     // });
-                    const sql6 = 'SELECT count(*) AS count FROM ?? as a WHERE a.tid = ? AND (a.id in(SELECT b.cpid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ?)))';
-                    const sqlParam6 = [this.tableName, tenderId, this.ctx.service.changeProjectAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.back];
+                    const sql6 = 'SELECT count(*) AS count FROM ?? as a WHERE a.tid = ? AND (a.id in(SELECT b.cpid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)))';
+                    const sqlParam6 = [this.tableName, tenderId, this.ctx.service.changeProjectAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.back, audit.status.revise];
                     const result6 = await this.db.query(sql6, sqlParam6);
                     return result6[0].count;
                 case 5: // 待上报(所有的)PS:取未上报,退回的变更立项
                     const sql2 =
                         'SELECT count(*) AS count FROM ?? AS a WHERE ' +
                         // 'a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) ' +
-                        'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ?)';
+                        'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ? OR a.status = ?)';
                     const sqlParam2 = [
                         this.tableName,
                         // this.ctx.service.changeProjectAudit.tableName,
@@ -261,6 +272,7 @@ module.exports = app => {
                         tenderId,
                         audit.status.uncheck,
                         audit.status.back,
+                        audit.status.revise,
                     ];
                     const result2 = await this.db.query(sql2, sqlParam2);
                     return result2[0].count;
@@ -346,6 +358,8 @@ module.exports = app => {
                 // }
                 // 最后删除变更令
                 await this.transaction.delete(this.tableName, { id });
+                // 删除history
+                await this.transaction.delete(this.ctx.service.changeProjectHistory.tableName, { cpid: id });
                 // 记录删除日志
                 await this.ctx.service.projectLog.addProjectLog(this.transaction, projectLogConst.type.changeProject, projectLogConst.status.delete, changeInfo.code);
                 await this.transaction.commit();
@@ -356,6 +370,49 @@ module.exports = app => {
             }
             return result;
         }
+
+        async doCheckChangeCanCancel(change) {
+            // 获取当前审批人的上一个审批人,判断是否是当前登录人,并赋予撤回功能,(当审批人存在有审批过时,上一人不允许再撤回)
+            const status = audit.status;
+            const accountId = this.ctx.session.sessionUser.accountId;
+            const auditors = change.auditors;
+            change.cancancel = 0;
+            if (change.status !== status.checked && change.status !== status.uncheck && change.status !== status.revise) {
+                if (change.status === status.back) {
+                    const lastAuditors = await this.service.changeProjectAudit.getAuditors(change.id, change.times - 1);
+                    const onAuditor = this._.findLast(lastAuditors, { status: status.back });
+                    if (onAuditor && onAuditor.aid === accountId) {
+                        change.cancancel = 4;// 审批人撤回退回原报
+                    }
+                } else if (change.status === status.checkNo) {
+                    const onAuditor = this._.find(auditors, function(item) {
+                        return item.status === status.checkNo;
+                    });
+                    if (onAuditor && onAuditor.aid === accountId) {
+                        change.cancancel = 3; // 审批人撤回审批终止
+                    }
+                } else {
+                    // 找出当前操作人上一个审批人,包括审批完成的和退回上一个审批人的,同时当前操作人为第一人时,就是则为原报
+                    const onAuditor = this._.find(auditors, function(item) {
+                        return item.aid === change.curAuditor.aid && item.status === status.checking;
+                    });
+
+                    const preAudit = onAuditor.order > 1 ? this._.find(auditors, { order: onAuditor.order - 1 }) : false;
+                    const preAid = preAudit ? (preAudit.status !== status.checkAgain ? preAudit.aid : false) : change.uid;
+                    // console.log(onAuditor, preAudit, auditors);
+                    if (onAuditor.aid === preAid && preAudit.status === status.checkCancel) {
+                        return;// 不可以多次撤回
+                    } else if (preAid === accountId && preAid !== change.uid) {
+                        if (preAudit.status === status.checked) {
+                            change.cancancel = 2;// 审批人撤回审批通过
+                        }
+                        change.preAudit = preAudit;
+                    } else if (preAid === accountId && preAid === change.uid) {
+                        change.cancancel = 1;// 原报撤回
+                    }
+                }
+            }
+        }
     }
 
     return ChangeProject;

+ 395 - 3
app/service/change_project_audit.js

@@ -75,8 +75,8 @@ module.exports = app => {
         async getAuditGroupByList(changeId, times) {
             const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`cpid`, la.`aid`, la.`order` ' +
                 '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
-                '  WHERE la.`cpid` = ? and la.`times` = ? GROUP BY la.`aid` ORDER BY la.`order`';
-            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, changeId, times];
+                '  WHERE la.`cpid` = ? and la.`times` = ? and la.`status` != ? and la.`status` != ? GROUP BY la.`aid` ORDER BY la.`order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, changeId, times, auditConst.status.revise, auditConst.status.cancelRevise];
             return await this.db.query(sql, sqlParam);
         }
 
@@ -96,6 +96,8 @@ module.exports = app => {
                 case auditConst.status.checking :
                 case auditConst.status.checked :
                 case auditConst.status.checkNo :
+                case auditConst.status.revise :
+                case auditConst.status.cancelRevise :
                     sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`cpid`, la.`aid`, la.`order` ' +
                         '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
                         '  WHERE la.`cpid` = ? and la.`status` = ? ' +
@@ -118,6 +120,12 @@ module.exports = app => {
             return auditor;
         }
 
+        async getLastAudit(cpid, times, transaction = null) {
+            const sql = 'SELECT * FROM ?? WHERE `cpid` = ? AND `times` = ? ORDER BY `order` DESC';
+            const sqlParam = [this.tableName, cpid, times];
+            return transaction ? await transaction.queryOne(sql, sqlParam) : await this.db.queryOne(sql, sqlParam);
+        }
+
         /**
          * 获取审核人流程列表(包括原报)
          * @param {Number} materialId 调差id
@@ -280,7 +288,7 @@ module.exports = app => {
                     c_name: this.ctx.change.name,
                 };
                 await this.ctx.helper.sendWechat(audit.aid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
-
+                await transaction.delete(this.ctx.service.changeProjectHistory.tableName, { cpid: cpId });
                 // todo 更新标段tender状态 ?
                 await transaction.commit();
             } catch (err) {
@@ -458,6 +466,7 @@ module.exports = app => {
 
         async _back(pid, cpId, checkData, times) {
             const time = new Date();
+            const changeData = await this.ctx.service.changeProject.getDataById(cpId);
             // 整理当前流程审核人状态更新
             const audit = await this.getDataByCondition({ cpid: cpId, times, status: auditConst.status.checking });
             if (!audit) {
@@ -500,6 +509,8 @@ module.exports = app => {
                     c_name: this.ctx.change.name,
                 };
                 await this.ctx.helper.sendWechat(users, smsTypeConst.const.BG, smsTypeConst.judge.result.toString(), wxConst.template.change, wechatData);
+                // 生成内容保存表至zh_change_project_history中,用于撤回
+                await this.ctx.service.changeProjectHistory.saveHistory(transaction, changeData);
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
@@ -590,6 +601,387 @@ module.exports = app => {
             const sqlParam = [tenderId];
             return this.db.query(sql, sqlParam);
         }
+
+        /**
+         * 审批撤回
+         * @param {Number} stageId - 标段id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<void>}
+         */
+        async checkCancel(change) {
+
+            // 分4种情况,根据ctx.cancancel值判断:
+            // 1.原报发起撤回,当前流程删除,并回到待上报
+            // 2.审批人撤回审批通过,增加流程,并回到它审批中
+            // 3.审批人撤回审批终止,增加流程,并回到它审批中,并更新立项状态为审批中
+            // 4.审批人撤回退回原报操作,删除新增的审批流,增加流程,回滚到它审批中
+            switch (change.cancancel) {
+                case 1: await this._userCheckCancel(change); break;
+                case 2: await this._auditCheckCancel(change); break;
+                case 3: await this._auditCheckCancelStop(change); break;
+                case 4: await this._auditCheckCancelNo(change); break;
+                default: throw '不可撤回,请刷新页面重试';
+            }
+        }
+        /**
+         * 原报撤回,直接改动审批人状态
+         * 如果存在审批人数据,将其改为原报流程数据,但保留原提交人
+         *
+         * 一审 1 A checking  ->  A uncheck status改   pay/jl:删0(jl为增量数据,只删重复部分) 1->0 删1
+         * ...
+         *
+         * @param stage
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _userCheckCancel(change) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 整理当前流程审核人状态更新
+                const curAudit = await this.getDataByCondition({ cpid: change.id, times: change.times, status: auditConst.status.checking });
+                // 审批人变成待审批状态
+                await transaction.update(this.tableName, {
+                    id: curAudit.id,
+                    status: auditConst.status.uncheck,
+                    begin_time: null,
+                    opinion: null,
+                });
+                // 变成待上报状态
+                await transaction.update(this.ctx.service.changeProject.tableName, {
+                    id: change.id,
+                    status: change.times === 1 ? auditConst.status.uncheck : auditConst.status.back,
+                });
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+
+        }
+        /**
+         * 审批人撤回审批通过,插入两条数据
+         *
+         * 一审 1 A checked             一审 1 A checked
+         * 二审 2 B checked   pre ->    二审 2 B checked
+         * 三审 3 C checking  cur       二审 3 B checkCancel  增                增extra_his     增tp_his
+         * 四审 4 D uncheck             二审 4 B checking     增                增pay_cur
+         *                             三审 5 C uncheck      order、status改
+         *                             四审 6 D uncheck      order改
+         *
+         * @param stage
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _auditCheckCancel(change) {
+            const time = new Date();
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 整理当前流程审核人状态更新
+                const curAudit = await this.getDataByCondition({ cpid: change.id, times: change.times, status: auditConst.status.checking });
+                const preAudit = change.preAudit;
+                if (!curAudit || curAudit.order <= 1 || !preAudit) {
+                    throw '撤回用户数据错误';
+                }
+                // 顺移其后审核人流程顺序
+                const sql = 'UPDATE ' + this.tableName + ' SET `order` = `order` + 2 WHERE cpid = ? AND times = ? AND `order` > ?';
+                await transaction.query(sql, [change.id, change.times, curAudit.order]);
+                // 当前审批人2次添加至流程中
+                const newAuditors = [];
+                // 先入撤回记录
+                newAuditors.push({
+                    tid: change.tid,
+                    cpid: change.id,
+                    aid: preAudit.aid,
+                    times: change.times,
+                    order: curAudit.order,
+                    status: auditConst.status.checkCancel,
+                    begin_time: time,
+                    end_time: time,
+                    opinion: '',
+                });
+                newAuditors.push({
+                    tid: change.tid,
+                    cpid: change.id,
+                    aid: preAudit.aid,
+                    times: change.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 });
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 审批人撤回审批终止,插入两条数据
+         *
+         * @param stage
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _auditCheckCancelStop(change) {
+            const time = new Date();
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // const curAudit = await this.getDataByCondition({ cpid: change.id, times: change.times, status: auditConst.status.checkNo });
+                const curAudit = await this.getAuditorByStatus(change.id, auditConst.status.checkNo);
+                if (!curAudit) {
+                    throw '撤回用户数据错误';
+                }
+                // 顺移其后审核人流程顺序
+                const sql = 'UPDATE ' + this.tableName + ' SET `order` = `order` + 2 WHERE cpid = ? AND times = ? AND `order` > ?';
+                await transaction.query(sql, [change.id, change.times, curAudit.order]);
+                // 当前审批人2次添加至流程中
+                const newAuditors = [];
+                // 先入撤回记录
+                newAuditors.push({
+                    tid: change.tid,
+                    cpid: change.id,
+                    aid: curAudit.aid,
+                    times: change.times,
+                    order: curAudit.order + 1,
+                    status: auditConst.status.checkCancel,
+                    begin_time: time,
+                    end_time: time,
+                    opinion: '',
+                });
+                newAuditors.push({
+                    tid: change.tid,
+                    cpid: change.id,
+                    aid: curAudit.aid,
+                    times: change.times,
+                    order: curAudit.order + 2,
+                    status: auditConst.status.checking,
+                    begin_time: time,
+                });
+                await transaction.insert(this.tableName, newAuditors);
+                await transaction.update(this.ctx.service.changeProject.tableName, {
+                    id: change.id,
+                    status: auditConst.status.checking,
+                });
+
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 审批人撤回审批退回原报
+         *
+         * 1# 一审 1 A checked              1# 一审 1 A checked
+         *    二审 2 B checkNo   pre   ->      二审 2 B checkNo
+         *    三审 3 C uncheck                 二审 3 B checkCancel    增       pay: 2#0 -> 1#3   jl: 2#0 -> 1#3   增tp_his   增extra_his
+         *                                    二审 4 B checking       增       pay: 2#0 -> 1#4
+         *                                    三审 5 C uncheck        order改
+         *
+         * 2# 一审 1 A uncheck              2#                        删       pay: 2#0删   jl: 2#0删
+         *    二审 2 B uncheck
+         *    三审 3 C uncheck
+         *
+         * @param stage
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _auditCheckCancelNo(change) {
+
+            const time = new Date();
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // const curAudit = await this.getDataByCondition({ cpid: change.id, times: change.times - 1, status: auditConst.status.back });
+                const curAudit = await this.getAuditorByStatus(change.id, auditConst.status.back, change.times);
+                // 整理上一个流程审核人状态更新
+                // 顺移其后审核人流程顺序
+                const sql = 'UPDATE ' + this.tableName + ' SET `order` = `order` + 2 WHERE cpid = ? AND times = ? AND `order` > ?';
+                await transaction.query(sql, [change.id, change.times - 1, curAudit.order]);
+                // 当前审批人2次添加至流程中
+                const newAuditors = [];
+                newAuditors.push({
+                    tid: change.tid,
+                    cpid: change.id,
+                    aid: curAudit.aid,
+                    times: curAudit.times,
+                    order: curAudit.order + 1,
+                    status: auditConst.status.checkCancel,
+                    begin_time: time,
+                    end_time: time,
+                    opinion: '',
+                });
+                newAuditors.push({
+                    tid: change.tid,
+                    cpid: change.id,
+                    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, { cpid: change.id, times: change.times });
+                // 回退数据
+                await this.ctx.service.changeProjectHistory.returnHistory(transaction, change.id);
+                await transaction.delete(this.ctx.service.changeProjectHistory.tableName, { cpid: change.id });
+                // // 设置变更立项为审批中
+                // await transaction.update(this.ctx.service.changeProject.tableName, {
+                //     id: change.id,
+                //     time: change.times - 1,
+                //     status: auditConst.status.checking,
+                // });
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 重新审批变更令
+         * @param { string } cid - 查询的清单
+         * @return {Promise<*>} - 可用的变更令列表
+         */
+        async checkRevise(change) {
+            const time = new Date();
+            // 初始化事务
+            const transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                const pid = this.ctx.session.sessionProject.id;
+                // 获取审核人列表
+                const sql = 'SELECT `tid`, `cpid`, `aid`, `order` FROM ?? WHERE `cpid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
+                const sqlParam = [this.tableName, change.id, change.times];
+                const auditors = await this.db.query(sql, sqlParam);
+                // 添加到消息推送表
+                const noticeContent = await this.getNoticeContent(pid, change.tid, change.id, this.ctx.session.sessionUser.accountId, '发起修订');
+                const records = [];
+                auditors.forEach(auditor => {
+                    records.push({
+                        pid,
+                        type: pushType.changeProject,
+                        uid: auditor.aid,
+                        status: auditConst.status.revise,
+                        content: noticeContent,
+                    });
+                });
+                await transaction.insert('zh_notice', records);
+
+                // 获取当前次数审批人列表
+                const auditList = await this.getAuditGroupByList(change.id, change.times);
+                const lastAudit = await this.getLastAudit(change.id, change.times);
+                const insert_audit_array = [];
+                // 新增一个发起修订状态到审批流程中
+                const revise_audit = {
+                    tid: change.tid,
+                    cpid: change.id,
+                    aid: change.uid,
+                    times: change.times,
+                    order: lastAudit.order + 1,
+                    status: auditConst.status.revise,
+                    begin_time: time,
+                    end_time: time,
+                    opinion: '',
+                };
+                insert_audit_array.push(revise_audit);
+                // 新增新一次的审批人列表
+                let order = 1;
+                for (const al of auditList) {
+                    const insert_audit = {
+                        tid: change.tid,
+                        cpid: change.id,
+                        aid: al.aid,
+                        times: change.times + 1,
+                        order,
+                        status: auditConst.status.uncheck,
+                    };
+                    insert_audit_array.push(insert_audit);
+                    order++;
+                }
+                await transaction.insert(this.tableName, insert_audit_array);
+                // 生成内容保存表至zh_change_history中,用于撤销修订回退
+                const changeData = await transaction.get(this.ctx.service.changeProject.tableName, { id: change.id });
+                await this.ctx.service.changeProjectHistory.saveHistory(transaction, changeData);
+                // 设置变更立项修订状态
+                await transaction.update(this.ctx.service.changeProject.tableName, {
+                    id: change.id,
+                    status: auditConst.status.revise,
+                    times: change.times + 1,
+                });
+
+                await transaction.commit();
+                result = true;
+            } catch (error) {
+                console.log(error);
+                await transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
+
+        /**
+         * 撤销修订变更令
+         * @param { string } cid - 查询的清单
+         * @return {Promise<*>} - 可用的变更令列表
+         */
+        async cancelRevise(change) {
+            const time = new Date();
+            // 初始化事务
+            const transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                const pid = this.ctx.session.sessionProject.id;
+                // 获取审核人列表
+                const sql = 'SELECT `tid`, `cpid`, `aid`, `order` FROM ?? WHERE `cpid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
+                const sqlParam = [this.tableName, change.id, change.times - 1];
+                const auditors = await this.db.query(sql, sqlParam);
+                // 添加到消息推送表
+                const noticeContent = await this.getNoticeContent(pid, change.tid, change.id, this.ctx.session.sessionUser.accountId, '撤销修订');
+                const records = [];
+                auditors.forEach(auditor => {
+                    records.push({
+                        pid,
+                        type: pushType.changeProject,
+                        uid: auditor.aid,
+                        status: auditConst.status.cancelRevise,
+                        content: noticeContent,
+                    });
+                });
+                await transaction.insert('zh_notice', records);
+                const lastAudit = await this.getLastAudit(change.id, change.times - 1);
+                // 新增一个撤销修订状态到审批流程中
+                const revise_audit = {
+                    tid: change.tid,
+                    cpid: change.id,
+                    aid: this.ctx.session.sessionUser.accountId,
+                    times: change.times - 1,
+                    order: lastAudit.order + 1,
+                    status: auditConst.status.cancelRevise,
+                    begin_time: time,
+                    end_time: time,
+                    opinion: '',
+                };
+                await transaction.insert(this.ctx.service.changeProjectAudit.tableName, revise_audit);
+                await transaction.delete(this.ctx.service.changeProjectAudit.tableName, { cpid: change.id, times: change.times });
+                await this.ctx.service.changeProjectHistory.returnHistory(transaction, change.id);
+                await transaction.delete(this.ctx.service.changeProjectHistory.tableName, { cpid: change.id });
+                await transaction.commit();
+                result = true;
+            } catch (error) {
+                console.log(error);
+                await transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
     }
 
     return ChangeProjectAudit;

+ 48 - 0
app/service/change_project_history.js

@@ -0,0 +1,48 @@
+'use strict';
+
+/**
+ * 变更新增部位插入记录表
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+
+    class ChangeProjectHistory extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'change_project_history';
+        }
+
+        async saveHistory(transaction, data) {
+            await transaction.insert(this.tableName, {
+                tid: data.tid,
+                cpid: data.id,
+                info_json: JSON.stringify(data),
+            });
+        }
+
+        async returnHistory(transaction, cpid) {
+            const data = await transaction.get(this.tableName, { cpid });
+            if (!data) throw '撤销前数据不存在,无法撤销';
+            const change_update = {};
+            const oldInfo = JSON.parse(data.info_json);
+            for (const key in oldInfo) {
+                if (key !== 'in_time') {
+                    change_update[key] = oldInfo[key];
+                }
+            }
+            await transaction.update(this.ctx.service.changeProject.tableName, change_update);
+        }
+    }
+
+    return ChangeProjectHistory;
+};

+ 9 - 9
app/service/settle.js

@@ -176,7 +176,7 @@ module.exports = app => {
             settle.curAssistsIds = this._.map(settle.curAssists, 'ass_user_id');
             settle.relaAssists = settle.assists.filter(x => { return x.user_id === accountId }); // 登录人的协同人
             // 当前参与人Id
-            settle.userIds = settle.status === settle.uncheck // 当前流程下全部参与人id
+            settle.userIds = settle.audit_status === settle.uncheck // 当前流程下全部参与人id
                 ? [settle.user_id, ...settle.userAssistIds]
                 : [settle.user_id, ...settle.userAssistIds, ...settle.auditorIds, ...settle.auditAssistIds];
         }
@@ -187,7 +187,7 @@ module.exports = app => {
             if (!settle.user) settle.user = await this.ctx.service.projectAccount.getAccountInfoById(settle.user_id);
             settle.auditHistory = await this.ctx.service.settleAudit.getAuditorHistory(settle.id, times);
             // 获取审批流程中左边列表
-            if (settle.status === auditConst.settle.status.checkNo && settle.user_id !== this.ctx.session.sessionUser.accountId) {
+            if (settle.audit_status === auditConst.settle.status.checkNo && settle.user_id !== this.ctx.session.sessionUser.accountId) {
                 const auditors = await this.ctx.service.settleAudit.getAuditors(settle.id, settle.audit_times - 1); // 全部参与的审批人
                 const auditorGroups = this.ctx.helper.groupAuditors(auditors);
                 settle.hisUserGroup = this.ctx.helper.groupAuditorsUniq(auditorGroups);
@@ -212,10 +212,10 @@ module.exports = app => {
             settle.cancancel = 0;
             // 获取当前审批人的上一个审批人,判断是否是当前登录人,并赋予撤回功能,(当审批人存在有审批过时,上一人不允许再撤回)
             const status = auditConst.settle.status;
-            if (settle.status === status.checked || settle.status === status.uncheck) return;
+            if (settle.audit_status === status.checked || settle.audit_status === status.uncheck) return;
 
             const accountId = this.ctx.session.sessionUser.accountId;
-            if (settle.status !== status.checkNo) {
+            if (settle.audit_status !== status.checkNo) {
                 // 找出当前操作人上一个审批人,包括审批完成的和退回上一个审批人的,同时当前操作人为第一人时,就是则为原报
                 if (settle.flowAuditors.find(x => { return x.status !== status.checking}) && settle.flowAuditorIds.indexOf(accountId) < 0) return; // 当前流程存在审批人审批通过时,不可撤回
                 const flowAssists = settle.auditAssists.filter(x => { return settle.flowAuditorIds.indexOf(x.user_id) >= 0; });
@@ -261,21 +261,21 @@ module.exports = app => {
             const accountId = this.ctx.session.sessionUser.accountId;
             const shareIds = [];
             // 是否只读
-            if (settle.status === status.uncheck || settle.status === status.checkNo) {
+            if (settle.audit_status === status.uncheck || settle.audit_status === status.checkNo) {
                 settle.readOnly = accountId !== settle.user_id && settle.userAssistIds.indexOf(accountId) < 0;
             } else {
                 settle.readOnly = true;
             }
             // 读取数据相关
-            settle.curTimes = settle.status === status.checkNo && settle.readOnly ? settle.times - 1 : settle.times;
+            settle.curTimes = settle.audit_status === status.checkNo && settle.readOnly ? settle.times - 1 : settle.times;
             // 协作人相关
 
-            if (settle.status === status.uncheck) {
+            if (settle.audit_status === status.uncheck) {
                 if (!settle.readOnly) {
                     settle.assist = settle.userAssists.find(x => { return x.ass_user_id === accountId; });
                 }
                 settle.curTimes = settle.times;
-            } else if (settle.status === status.checkNo) {
+            } else if (settle.audit_status === status.checkNo) {
                 if (!settle.readOnly) {
                     settle.assist = settle.userAssists.find(x => { return x.ass_user_id === accountId; });
                     settle.curTimes = settle.times;
@@ -300,7 +300,7 @@ module.exports = app => {
                 settle.filePermission = true;
             } else {
                 if (shareIds.indexOf(accountId) !== -1 || (permission !== null && permission.tender !== undefined && permission.tender.indexOf('2') !== -1)) {// 分享人
-                    if (settle.status === status.uncheck) throw '您无权查看该数据';
+                    if (settle.audit_status === status.uncheck) throw '您无权查看该数据';
                     settle.filePermission = false;
                 } else if (this.ctx.tender.isTourist || this.ctx.session.sessionUser.is_admin) {
                     settle.filePermission = this.ctx.tender.touristPermission.file || settle.auditorIds.indexOf(accountId) !== -1;

+ 27 - 0
app/service/settle_bills.js

@@ -0,0 +1,27 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class SettlePos extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.depart = 10;
+            this.tableName = 'settle_bills';
+        }
+    }
+
+    return SettlePos;
+};

+ 27 - 0
app/service/settle_pos.js

@@ -0,0 +1,27 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class SettlePos extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.depart = 20;
+            this.tableName = 'settle_pos';
+        }
+    }
+
+    return SettlePos;
+};

+ 62 - 0
app/service/settle_select.js

@@ -0,0 +1,62 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class SettleSelect extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'settle_select';
+        }
+
+        async updateData(data) {
+            const result = {};
+            const conn = await this.db.beginTransaction();
+            try {
+                if (data.del && data.del.length > 0) {
+                    const lid = [], pid = [];
+                    for (const d of data.del) {
+                        if (d.lid) lid.push(d.lid);
+                        if (d.pid) pid.push(d.pid);
+                    }
+                    const delLibData = await this.getAllDataByCondition({ settle_id: this.ctx.settle.id, lid });
+                    const delPidData = await this.getAllDataByCondition({ settle_id: this.ctx.settle.id, pid });
+                    if (delLibData.length + delPidData.length !== data.del.length) throw '提交数据错误';
+                    const deleteData = [...delLibData, ...delPidData];
+                    await conn.delete(this.tableName, { id: deleteData.map(x => { return x.id }) });
+                    result.del = deleteData;
+                }
+                if (data.add && data.add.length > 0) {
+                    const addData = [];
+                    for (const d of data.add) {
+                        addData.push({
+                            tid: this.ctx.settle.tid, settle_id: this.ctx.settle.id, settle_order: this.ctx.settle.settle_order,
+                            lid: d.lid || '', pid: d.pid || '', user_id: this.ctx.session.sessionUser.accountId,
+                        });
+                    }
+                    await conn.insert(this.tableName, addData);
+                    result.add = addData;
+                }
+                await conn.commit();
+            } catch (err) {
+                await conn.rollback();
+                throw err;
+            }
+            return result;
+        }
+    }
+
+    return SettleSelect;
+};

+ 9 - 0
app/service/stage_change_final.js

@@ -148,6 +148,15 @@ module.exports = app => {
                 '  WHERE scf.tid = ? And scf.sorder <= ?';
             return await this.db.query(sql, [tid, stageOrder]);
         }
+
+        async getUnSettleChangeData(stage) {
+            const sql = 'SELECT cal.cid, cal.gcl_id, cal.mx_id, cal.code, cal.name, cal.unit, cal.unit_price, cal.checked_amount, scf.used_qty' +
+                `  FROM ${this.ctx.service.changeAuditList.tableName} cal ` +
+                `  LEFT JOIN (SELECT cbid, sum(qty) as used_qty FROM ${this.tableName} WHERE tid = ? and sorder <= ? GROUP BY cbid) scf ON cal.id = scf.cbid` +
+                '  WHERE cal.tid = ?';
+            const changeFinal = await this.db.query(sql, [stage.tid, stage.order, stage.tid]);
+            return changeFinal.filter(x => { return x.checked_amount > x.used_qty && !! x.gcl_id; });
+        }
     }
 
     return StageChangeFinal;

+ 5 - 6
app/view/change/apply.ejs

@@ -82,7 +82,7 @@
                             <td><% if (c.notice_code) { %><a href="/tender/<%- tender.id %>/change/apply/<%- c.id %>/information/notice"><%- c.notice_code %></a><% } %></td>
                             <td><%- c.account_name %></td>
                             <td>
-                                <% if ((c.status === auditConst.status.uncheck || c.status === auditConst.status.checkNo) && c.uid === ctx.session.sessionUser.accountId) { %>
+                                <% if ((c.status === auditConst.status.uncheck || c.status === auditConst.status.checkNo || c.status === auditConst.status.revise) && c.uid === ctx.session.sessionUser.accountId) { %>
                                     <a href="/tender/<%- tender.id %>/change/apply/<%- c.id %>/information" class="btn <%- auditConst.statusButtonClass[c.status] %> btn-sm"><%- auditConst.statusButton[c.status] %></a>
                                 <% } else if (c.status === auditConst.status.checking && c.curAuditor && c.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
                                     <a href="/tender/<%- tender.id %>/change/apply/<%- c.id %>/information" class="btn <%- auditConst.statusButtonClass[c.status] %> btn-sm"><%- auditConst.statusButton[c.status] %></a>
@@ -90,13 +90,12 @@
                                     <span class="<%- auditConst.auditProgressClass[c.status] %>"><%- auditConst.auditProgress[c.status] %></span>
                                 <% } %>
                             </td>
-                            <% if (c.status === auditConst.status.uncheck) { %>
                             <td>
-                                待上报
+                                <% if (c.status !== auditConst.status.uncheck) { %>
+                                    <%- c.curAuditor ? c.curAuditor.name : '' %>-<%- c.curAuditor ? c.curAuditor.role : '' %>
+                                <% } %>
+                                <span class="<%- auditConst.statusClass[c.status] %>"><%- auditConst.statusString[c.status] %></span>
                             </td>
-                            <% } else { %>
-                            <td><%- c.curAuditor.name %>-<%- c.curAuditor.role %> <span class="<%- auditConst.statusClass[c.status] %>"><%- auditConst.statusString[c.status] %></span></td>
-                            <% } %>
                             <td>
                                 <% if (c.uid === uid && (c.status === auditConst.status.uncheck || c.status === auditConst.status.checkNo)) { %><a href="#del-bg" caid="<%= c.id %>" data-toggle="modal" data-target="#del-bg" class="btn btn-outline-danger btn-sm delete-caid-modal">删除</a><% } %>
                             </td>

+ 11 - 2
app/view/change/apply_information.ejs

@@ -17,6 +17,9 @@
                 <% } %>
             </div>
             <div class="ml-auto" id="sp-btn">
+                <% if (ctx.change.cancancel) { %>
+                    <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-cancel" class="btn btn-danger btn-sm mr-2">撤回</a>
+                <% } %>
                 <% if (ctx.change.status === auditConst.status.uncheck) { %>
                     <% if (ctx.session.sessionUser.accountId === ctx.change.uid) { %>
                         <a id="sub-sp-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm">上报审批</a>
@@ -31,9 +34,15 @@
                         <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm">审批中</a>
                     <% } %>
                 <% } else if (ctx.change.status === auditConst.status.checked) { %>
+                    <% if (ctx.session.sessionUser.accountId === ctx.change.uid) { %>
+                        <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-revise" class="btn btn-warning btn-sm mr-2">修订变更</a>
+                    <% } %>
                     <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-success btn-sm">审批完成</a>
-                <% } else if (ctx.change.status === auditConst.status.checkNo) { %>
-                    <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-muted sp-list-btn">审批退回</a>
+                <% } else if (ctx.change.status === auditConst.status.checkNo || ctx.change.status === auditConst.status.revise) { %>
+                    <% if (ctx.change.status === auditConst.status.revise && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.session.sessionUser.is_admin)) { %>
+                        <a href="#sub-revoke" data-toggle="modal" data-target="#sub-revoke" class="btn btn-warning btn-sm mr-2">撤销修订</a>
+                    <% } %>
+                    <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-muted sp-list-btn">审批<% if (ctx.change.status === auditConst.status.checkNo) { %>退回<% } else { %>修订<% } %></a>
                     <% if (ctx.session.sessionUser.accountId === ctx.change.uid) { %>
                         <a href="#sp-list" data-type="show" data-toggle="modal" data-target="#sp-list"  class="btn btn-primary btn-sm sp-list-btn">重新上报</a>
                     <% } %>

+ 248 - 72
app/view/change/apply_information_modal.ejs

@@ -30,7 +30,7 @@
         </div>
     </div>
 </div>
-<% if ((ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.checkNo) && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.tender.isTourist)) { %>
+<% if ((ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.checkNo || ctx.change.status === auditConst.status.revise) && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.tender.isTourist)) { %>
     <!--上报审批-->
     <div class="modal fade" id="sub-sp" data-backdrop="static">
         <div class="modal-dialog" role="document">
@@ -146,6 +146,31 @@
             </div>
         </div>
     </div>
+    <% if (ctx.change.status === auditConst.status.revise && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.session.sessionUser.is_admin)) { %>
+        <!--撤销修订-->
+        <div class="modal fade" id="sub-revoke"  role="dialog" aria-labelledby="myModalLabel">
+            <div class="modal-dialog" role="document">
+                <form id="reviseForm" class="modal-content" method="post" action="/tender/<%- tender.id %>/change/apply/cancel/revise" onsubmit="return false;">
+                    <div class="modal-header">
+                        <h5 class="modal-title" id="myModalLabel">撤销修订</h5>
+                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                            <span aria-hidden="true">&times;</span>
+                        </button>
+                    </div>
+                    <div class="modal-body">
+                        <h5>撤销修订,所有修改的数据将全部会被还原。</h5>
+                        <h5>确认撤销修订?</h5>
+                    </div>
+                    <div class="modal-footer">
+                        <input type="hidden" name="caid" value="<%= ctx.change.id %>">
+                        <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                        <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                        <button type="button" id="cancel-revise-btn" class="btn btn-primary btn-sm">确认</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+    <% } %>
 <% } %>
 
 <!--审批流程/结果-->
@@ -153,12 +178,12 @@
     <div class="modal-dialog modal-lg" role="document">
         <div class="modal-content">
             <div class="modal-header">
-                <h5 class="modal-title"><%- ctx.change.status !== auditConst.status.checkNo ? '审批流程' : '重新上报' %></h5>
+                <h5 class="modal-title"><%- ctx.change.status === auditConst.status.checkNo || ctx.change.status === auditConst.status.revise ? '重新上报' : '审批流程' %></h5>
             </div>
             <div class="modal-body">
                 <div class="row">
                     <div class="col-4">
-                        <% if(ctx.change.status === auditConst.status.checkNo) { %>
+                        <% if(ctx.change.status === auditConst.status.checkNo || ctx.change.status === auditConst.status.revise) { %>
                             <a class="sp-list-item" href="#sub-sp" data-toggle="modal" data-target="#sub-sp" id="hideSp">修改审批流程</a>
                         <% } %>
                         <div class="card mt-3">
@@ -229,11 +254,11 @@
                                                 <% if(index < auditors.length - 1) { %>
                                                     <div class="timeline-item-tail"></div>
                                                 <% } %>
-                                                <% if(auditor.status === auditConst.status.checked) { %>
+                                                <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                     <div class="timeline-item-icon bg-success text-light">
                                                         <i class="fa fa-check"></i>
                                                     </div>
-                                                <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                     <div class="timeline-item-icon bg-warning text-light">
                                                         <i class="fa fa-level-up"></i>
                                                     </div>
@@ -250,7 +275,7 @@
                                                         <div class="card-body p-3">
                                                             <div class="card-text">
                                                                 <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
-                                                                            class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                            class="pull-right <%- auditConst.auditStringClass[auditor.status] %>"><%- auditConst.auditString[auditor.status] %></span>
                                                                 </p>
                                                                 <p class="text-muted mb-0"><%- auditor.role %></p>
                                                             </div>
@@ -273,11 +298,11 @@
                                                 <% if(index < auditors.length - 1) { %>
                                                     <div class="timeline-item-tail"></div>
                                                 <% } %>
-                                                <% if(auditor.status === auditConst.status.checked) { %>
+                                                <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                     <div class="timeline-item-icon bg-success text-light">
                                                         <i class="fa fa-check"></i>
                                                     </div>
-                                                <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                     <div class="timeline-item-icon bg-warning text-light">
                                                         <i class="fa fa-level-up"></i>
                                                     </div>
@@ -296,7 +321,7 @@
                                                                 <p class="mb-1"><span class="h5"><%- auditor.name %></span>
                                                                     <span
                                                                             class="pull-right
-                                                                            <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                            <%- auditConst.auditStringClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.auditString[auditor.status] : ''%>
                                                                         <%- auditor.status === auditConst.status.checkNo ? ctx.change.user.name : '' %>
                                                         </span>
                                                                 </p>
@@ -411,11 +436,11 @@
                                                         <% if(index < auditors.length - 1) { %>
                                                             <div class="timeline-item-tail"></div>
                                                         <% } %>
-                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                        <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -432,30 +457,32 @@
                                                                 <div class="card-body p-3">
                                                                     <div class="card-text">
                                                                         <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
-                                                                                    class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                                    class="pull-right <%- auditConst.auditStringClass[auditor.status] %>"><%- auditConst.auditString[auditor.status] %></span>
                                                                         </p>
                                                                         <p class="text-muted mb-0"><%- auditor.role %></p>
                                                                     </div>
                                                                 </div>
                                                                 <!--审批意见-->
                                                                 <% if(auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
                                                                     <div class="card-body p-3 border-top">
-                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
-                                                                            <label>审批意见<b class="text-danger">*</b></label>
-                                                                            <textarea class="form-control form-control-sm"
-                                                                                      name="opinion">同意</textarea>
-                                                                            <% if (auditors[auditors.length - 1].aid === auditor.aid) { %>
-                                                                                <!--终审填写批复编号-->
-                                                                                <div class="form-group mt-3">
-                                                                                    <label>变更通知书<b class="text-danger">*&nbsp;</b></label>
-                                                                                    <input class="form-control form-control-sm" value="BGTZ-<%- change.code %>" name="notice_code" type="text">
-                                                                                    <input type="hidden" name="notice_uid" value="<%- auditor.aid %>">
-                                                                                </div>
-                                                                            <% } %>
-                                                                        <% } else { %>
-                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        <label>审批意见<b class="text-danger">*</b></label>
+                                                                        <textarea class="form-control form-control-sm"
+                                                                                  name="opinion">同意</textarea>
+                                                                        <% if (auditors[auditors.length - 1].aid === auditor.aid) { %>
+                                                                            <!--终审填写批复编号-->
+                                                                            <div class="form-group mt-3">
+                                                                                <label>变更通知书<b class="text-danger">*&nbsp;</b></label>
+                                                                                <input class="form-control form-control-sm" value="BGTZ-<%- change.code %>" name="notice_code" type="text">
+                                                                                <input type="hidden" name="notice_uid" value="<%- auditor.aid %>">
+                                                                            </div>
                                                                         <% } %>
                                                                     </div>
+                                                                    <% } else if (auditor.opinion) { %>
+                                                                        <div class="card-body p-3 border-top">
+                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        </div>
+                                                                    <% } %>
                                                                 <% } %>
                                                             </div>
                                                         </div>
@@ -468,11 +495,11 @@
                                                         <% if(index < auditors.length - 1) { %>
                                                             <div class="timeline-item-tail"></div>
                                                         <% } %>
-                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                        <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -491,7 +518,7 @@
                                                                         <p class="mb-1"><span class="h5"><%- auditor.name %></span>
                                                                             <span
                                                                                     class="pull-right
-                                                                                    <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                                    <%- auditConst.auditStringClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.auditString[auditor.status] : ''%>
                                                                                 <%- auditor.status === auditConst.status.checkNo ? ctx.change.user.name : '' %>
                                                                 </span>
                                                                         </p>
@@ -500,23 +527,25 @@
                                                                 </div>
                                                                 <!--审批意见-->
                                                                 <% if(auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
                                                                     <div class="card-body p-3 border-top">
-                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
-                                                                            <label>审批意见<b class="text-danger">*</b></label>
-                                                                            <textarea class="form-control form-control-sm"
-                                                                                      name="opinion">同意</textarea>
-                                                                            <% if (auditors[auditors.length - 1].aid === auditor.aid) { %>
-                                                                            <!--终审填写批复编号-->
-                                                                            <div class="form-group mt-3">
-                                                                                <label>变更通知书<b class="text-danger">*&nbsp;</b></label>
-                                                                                <input class="form-control form-control-sm" value="BGTZ-<%- change.code %>" name="notice_code" type="text">
-                                                                                <input type="hidden" name="notice_uid" value="<%- auditor.aid %>">
-                                                                            </div>
-                                                                            <% } %>
-                                                                        <% } else { %>
-                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        <label>审批意见<b class="text-danger">*</b></label>
+                                                                        <textarea class="form-control form-control-sm"
+                                                                                  name="opinion">同意</textarea>
+                                                                        <% if (auditors[auditors.length - 1].aid === auditor.aid) { %>
+                                                                        <!--终审填写批复编号-->
+                                                                        <div class="form-group mt-3">
+                                                                            <label>变更通知书<b class="text-danger">*&nbsp;</b></label>
+                                                                            <input class="form-control form-control-sm" value="BGTZ-<%- change.code %>" name="notice_code" type="text">
+                                                                            <input type="hidden" name="notice_uid" value="<%- auditor.aid %>">
+                                                                        </div>
                                                                         <% } %>
                                                                     </div>
+                                                                    <% } else if (auditor.opinion) { %>
+                                                                        <div class="card-body p-3 border-top">
+                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        </div>
+                                                                    <% } %>
                                                                 <% } %>
                                                             </div>
                                                         </div>
@@ -617,11 +646,11 @@
                                                         <% if(index < auditors.length - 1) { %>
                                                             <div class="timeline-item-tail"></div>
                                                         <% } %>
-                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                        <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -638,7 +667,7 @@
                                                                 <div class="card-body p-3">
                                                                     <div class="card-text">
                                                                         <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
-                                                                                    class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                                    class="pull-right <%- auditConst.auditStringClass[auditor.status] %>"><%- auditConst.auditString[auditor.status] %></span>
                                                                         </p>
                                                                         <p class="text-muted mb-0"><%- auditor.role %></p>
                                                                     </div>
@@ -646,8 +675,8 @@
 
                                                                 <!--审批意见-->
                                                                 <% if(auditor.times === ctx.change.times && auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
                                                                     <div class="card-body p-3 border-top">
-                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
                                                                             <label>审批意见<b class="text-danger">*</b></label>
                                                                             <textarea class="form-control form-control-sm"
                                                                                       name="opinion">不同意</textarea>
@@ -662,10 +691,12 @@
                                                                                     </div>
                                                                                 </div>
                                                                             <% } %>
-                                                                        <% } else if(auditor.status === auditConst.status.checked){ %>
-                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
-                                                                        <% } %>
                                                                     </div>
+                                                                    <% } else if(auditor.opinion) { %>
+                                                                        <div class="card-body p-3 border-top">
+                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        </div>
+                                                                    <% } %>
                                                                 <% } %>
                                                             </div>
                                                         </div>
@@ -678,11 +709,11 @@
                                                         <% if(index < auditors.length - 1) { %>
                                                             <div class="timeline-item-tail"></div>
                                                         <% } %>
-                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                        <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -701,7 +732,7 @@
                                                                         <p class="mb-1"><span class="h5"><%- auditor.name %></span>
                                                                             <span
                                                                                     class="pull-right
-                                                                                    <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                                    <%- auditConst.auditStringClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.auditString[auditor.status] : ''%>
                                                                                 <%- auditor.status === auditConst.status.checkNo ? ctx.change.user.name : '' %>
                                                                 </span>
                                                                         </p>
@@ -710,28 +741,28 @@
                                                                 </div>
                                                                 <!--审批意见-->
                                                                 <% if(auditor.times === ctx.change.times && auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
                                                                     <div class="card-body p-3 border-top">
-                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
-                                                                            <label>审批意见<b class="text-danger">*</b></label>
-                                                                            <textarea class="form-control form-control-sm"
-                                                                                      name="opinion">不同意</textarea>
-                                                                            <% if (ctx.change.curAuditor.aid === auditor.aid ) { %>
-                                                                                <div id="reject-process" class="alert alert-warning"
-                                                                                     style="margin-top: 15px;">
-                                                                                    <div class="form-check form-check-inline">
-                                                                                        <input class="form-check-input" type="radio" name="checkType"
-                                                                                               id="inlineRadio1" value="<%- auditConst.status.checkNo %>" checked>
-                                                                                        <label class="form-check-label" for="inlineRadio1">退回原报
-                                                                                            <%- ctx.change.user.name %></label>
-                                                                                    </div>
+                                                                        <label>审批意见<b class="text-danger">*</b></label>
+                                                                        <textarea class="form-control form-control-sm"
+                                                                                  name="opinion">不同意</textarea>
+                                                                        <% if (ctx.change.curAuditor.aid === auditor.aid ) { %>
+                                                                            <div id="reject-process" class="alert alert-warning"
+                                                                                 style="margin-top: 15px;">
+                                                                                <div class="form-check form-check-inline">
+                                                                                    <input class="form-check-input" type="radio" name="checkType"
+                                                                                           id="inlineRadio1" value="<%- auditConst.status.checkNo %>" checked>
+                                                                                    <label class="form-check-label" for="inlineRadio1">退回原报
+                                                                                        <%- ctx.change.user.name %></label>
                                                                                 </div>
-                                                                            <% } %>
-                                                                        <% } else { %>
-                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                            </div>
                                                                         <% } %>
-
                                                                     </div>
-
+                                                                    <% } else { %>
+                                                                        <div class="card-body p-3 border-top">
+                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        </div>
+                                                                    <% } %>
                                                                 <% } %>
                                                             </div>
                                                         </div>
@@ -755,13 +786,87 @@
         </div>
     <% } %>
 <% } %>
+<% if (ctx.change.status === auditConst.status.checked && ctx.session.sessionUser.accountId === ctx.change.uid) { %>
+    <% if (!authMobile && ctx.session.sessionUser.loginStatus === 0) { %>
+        <!--原报修订变更-->
+        <div class="modal fade" id="sp-down-revise" 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>
+                        <h5>您目前还没设置认证手机,请先设置。</h5>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                        <a href="/profile/sms" class="btn btn-sm btn-primary">去设置</a>
+                    </div>
+                </div>
+            </div>
+        </div>
+    <% } else { %>
+        <!--修订变更-->
+        <div class="modal fade" id="sp-down-revise" data-backdrop="static">
+            <div class="modal-dialog" role="document">
+                <form id="reviseForm" class="modal-content" method="post" action="/tender/<%- tender.id %>/change/apply/check/revise" onsubmit="return false;">
+                    <div class="modal-header">
+                        <h5 class="modal-title">修订变更</h5>
+                    </div>
+                    <div class="modal-body">
+                        <h5>确认需要修订变更「<%= change.code %>」?</h5>
+                        <% if (ctx.session.sessionUser.loginStatus === 0) { %>
+                            <div class="form-group">
+                                <label>修订需要验证码确认,验证码将发送至尾号<%- authMobile.slice(-4) %>的手机</label>
+                                <div class="input-group input-group-sm mb-3">
+                                    <input class="form-control" type="text" readonly="readonly" name="code" placeholder="输入短信中的6位验证码" />
+                                    <div class="input-group-append">
+                                        <button class="btn btn-outline-secondary" type="button" id="get-code">获取验证码</button>
+                                    </div>
+                                </div>
+                            </div>
+                        <% } %>
+                    </div>
+                    <div class="modal-footer">
+                        <input type="hidden" name="cpid" value="<%= ctx.change.id %>">
+                        <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                        <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                        <button type="button" id="re-shenpi-btn2" class="btn btn-warning btn-sm" <% if (ctx.session.sessionUser.loginStatus === 0) { %>disabled<% } %>>确定修订</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+    <% } %>
+<% } %>
+<% if (ctx.change.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>
+<% } %>
 <% if (ctx.session.sessionUser.accountId === ctx.change.uid && (ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.checkNo)) { %>
     <script>
         const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
         const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
     </script>
 <% } %>
-<script>const cur_uid = parseInt('<%- ctx.session.sessionUser.accountId %>');</script>
+<script>
+    const cur_uid = parseInt('<%- ctx.session.sessionUser.accountId %>');
+    const csrf = '<%= ctx.csrf %>';
+</script>
 <script>
     $('.sp-location-list').on('shown.bs.modal', function () {
         const scrollBox = $(this).find('div[class="col-8 modal-height-500"]');
@@ -807,4 +912,75 @@
             $('.modal-title').text('重新上报')
         }
     });
+
+    $('#re-shenpi-btn2').click(function () {
+        const data = {
+            caid: parseInt('<%- ctx.change.id %>'),
+        };
+        <% if (ctx.session.sessionUser.loginStatus === 0) { %>
+        const code = $("#reviseForm input[name='code']").val();
+        if ($(this).hasClass('disabled')) {
+            return false;
+        }
+        if (code.length < 6) {
+            // alert('请填写正确的验证码');
+            toast('请填写正确的验证码', 'error');
+            return false;
+        }
+        data.code = code;
+        <% } %>
+        $.ajax({
+            url: '/tender/<%- tender.id %>/change/apply/check/revise?_csrf_j=' + csrf,
+            type: 'post',
+            data: data,
+            dataTye: 'json',
+            success: function(response) {
+                if (response.err === 0) {
+                    window.location.href = response.url;
+                } else {
+                    toastr.error(response.msg);
+                }
+            }
+        });
+    })
+
+    $('#cancel-revise-btn').click(function () {
+        const data = {
+            caid: parseInt('<%- ctx.change.id %>'),
+        };
+        $.ajax({
+            url: '/tender/<%- tender.id %>/change/apply/cancel/revise?_csrf_j=' + csrf,
+            type: 'post',
+            data: data,
+            dataTye: 'json',
+            success: function(response) {
+                if (response.err === 0) {
+                    window.location.href = response.url;
+                } else {
+                    toastr.error(response.msg);
+                }
+            }
+        });
+    })
+
+    <% if (ctx.change && ctx.change.cancancel) { %>
+    $("#cancel-shenpi-btn").click(function () {
+        const data = {
+            caid: parseInt('<%- ctx.change.id %>'),
+        };
+        $.ajax({
+            url: '/tender/<%- tender.id %>/change/apply/cancel/audit?_csrf_j=' + csrf,
+            type: 'post',
+            data: data,
+            dataTye: 'json',
+            success: function (response) {
+                if (response.err === 0) {
+                    window.location.href = response.url;
+                } else {
+                    toastr.error(response.msg);
+                }
+            }
+        });
+    });
+    <% } %>
 </script>

+ 2 - 6
app/view/change/information.ejs

@@ -105,12 +105,8 @@
                         <% } %>
                     <% } %>
                     <% if (auditStatus === 4 && ctx.session.sessionUser.accountId === auditList[auditList.length-1].uid) { %>
-                        <% if (stageChangeNum === 0) { %>
-                            <!--重新审批-->
-                            <!--<a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-back" class="btn btn-warning btn-sm">重新审批</a>-->
-                        <% } else { %>
-                            <!--<button class="btn btn-outline-secondary btn-sm" data-toggle="tooltip" data-placement="bottom" title="已被调用">重新审批</button>-->
-                        <% } %>
+                        <!--重新审批-->
+                        <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-back" class="btn btn-warning btn-sm">重新审批</a>
                     <% } %>
                     <% if (auditStatus === 4 && ctx.session.sessionUser.accountId === change.uid) { %>
                         <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-revise" class="btn btn-warning btn-sm">修订变更</a>

+ 80 - 76
app/view/change/information_modal.ejs

@@ -704,8 +704,8 @@
                                                         </div>
                                                         <!--审批意见-->
                                                         <% if(auditor.status !== auditConst.auditStatus.uncheck) { %>
+                                                            <% if (auditor.status === auditConst.auditStatus.checking) { %>
                                                             <div class="card-body p-3 border-top">
-                                                                <% if (auditor.status === auditConst.auditStatus.checking) { %>
                                                                     <label>审批意见<b class="text-danger">*</b></label>
                                                                     <textarea class="form-control form-control-sm"
                                                                               name="sdesc">同意</textarea>
@@ -719,10 +719,12 @@
                                                                     <% } else { %>
                                                                         <input type="hidden" name="audit_next_id" value="<%= auditList3[index+1].id %>">
                                                                     <% } %>
-                                                                <% } else { %>
-                                                                    <p style="margin: 0;"><%- auditor.sdesc %></p>
-                                                                <% } %>
                                                             </div>
+                                                            <% } else if (auditor.sdesc) { %>
+                                                            <div class="card-body p-3 border-top">
+                                                                <p style="margin: 0;"><%- auditor.sdesc %></p>
+                                                            </div>
+                                                            <% } %>
                                                         <% } %>
                                                     </div>
                                                 </div>
@@ -941,33 +943,35 @@
                                                         </div>
                                                         <!--审批意见-->
                                                         <% if(auditor.status !== auditConst.auditStatus.uncheck) { %>
+                                                            <% if (auditor.status === auditConst.auditStatus.checking) { %>
                                                             <div class="card-body p-3 border-top">
-                                                                <% if (auditor.status === auditConst.auditStatus.checking) { %>
-                                                                    <label>审批意见<b class="text-danger">*</b></label>
-                                                                    <textarea class="form-control form-control-sm"
-                                                                              name="sdesc">不同意</textarea>
-                                                                    <input type="hidden" name="audit_id" value="<%= auditor.id %>">
-                                                                    <div id="change-back-content" class="alert alert-warning"
-                                                                         style="margin-top: 15px;">
+                                                                <label>审批意见<b class="text-danger">*</b></label>
+                                                                <textarea class="form-control form-control-sm"
+                                                                          name="sdesc">不同意</textarea>
+                                                                <input type="hidden" name="audit_id" value="<%= auditor.id %>">
+                                                                <div id="change-back-content" class="alert alert-warning"
+                                                                     style="margin-top: 15px;">
+                                                                    <div class="form-check form-check-inline">
+                                                                        <input class="form-check-input" type="radio" name="status"
+                                                                               id="change-back" value="<%- auditConst.auditStatus.back %>">
+                                                                        <label class="form-check-label" for="change-back">退回原报 <%= auditList3[0].name %></label>
+                                                                    </div>
+                                                                    <% if (auditor.usite !== 1) { %>
                                                                         <div class="form-check form-check-inline">
                                                                             <input class="form-check-input" type="radio" name="status"
-                                                                                   id="change-back" value="<%- auditConst.auditStatus.back %>">
-                                                                            <label class="form-check-label" for="change-back">退回原报 <%= auditList3[0].name %></label>
+                                                                                   id="chagne-backnew"
+                                                                                   value="<%- auditConst.auditStatus.backnew %>">
+                                                                            <label class="form-check-label" for="chagne-backnew">退回上一审批人 <%= auditList2[auditor.usite-1].name %></label>
+                                                                            <input type="hidden" name="audit_last_id" value="<%= auditList2[auditor.usite-1].id %>">
                                                                         </div>
-                                                                        <% if (auditor.usite !== 1) { %>
-                                                                            <div class="form-check form-check-inline">
-                                                                                <input class="form-check-input" type="radio" name="status"
-                                                                                       id="chagne-backnew"
-                                                                                       value="<%- auditConst.auditStatus.backnew %>">
-                                                                                <label class="form-check-label" for="chagne-backnew">退回上一审批人 <%= auditList2[auditor.usite-1].name %></label>
-                                                                                <input type="hidden" name="audit_last_id" value="<%= auditList2[auditor.usite-1].id %>">
-                                                                            </div>
-                                                                        <% } %>
-                                                                    </div>
-                                                                <% } else if(auditor.status === auditConst.auditStatus.checked){ %>
-                                                                    <p style="margin: 0;"><%- auditor.sdesc %></p>
-                                                                <% } %>
+                                                                    <% } %>
+                                                                </div>
                                                             </div>
+                                                            <% } else if(auditor.sdesc) { %>
+                                                                <div class="card-body p-3 border-top">
+                                                                    <p style="margin: 0;"><%- auditor.sdesc %></p>
+                                                                </div>
+                                                            <% } %>
                                                         <% } %>
                                                     </div>
                                                 </div>
@@ -1012,57 +1016,57 @@
         </div>
     </div>
 </div>
-<% if (auditStatus === 4 && ctx.session.sessionUser.accountId === auditList[auditList.length-1].uid && stageChangeNum === 0) { %>
+<% if (auditStatus === 4 && ctx.session.sessionUser.accountId === auditList[auditList.length-1].uid) { %>
     <% if (!authMobile && ctx.session.sessionUser.loginStatus === 0) { %>
-        <!--&lt;!&ndash;终审重新审批&ndash;&gt;-->
-        <!--<div class="modal fade" id="sp-down-back" 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>-->
-        <!--<h5>您目前还没设置认证手机,请先设置。</h5>-->
-        <!--</div>-->
-        <!--<div class="modal-footer">-->
-        <!--<button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>-->
-        <!--<a href="/profile/sms" class="btn btn-sm btn-primary">去设置</a>-->
-        <!--</div>-->
-        <!--</div>-->
-        <!--</div>-->
-        <!--</div>-->
-    <!--<% } else { %>-->
-    <!--&lt;!&ndash;重新审批&ndash;&gt;-->
-    <!--<div class="modal fade" id="sp-down-back" data-backdrop="static">-->
-    <!--<div class="modal-dialog" role="document">-->
-    <!--<form id="againForm" class="modal-content" method="post" action="/tender/<%- tender.id %>/change/check/again" onsubmit="return false;">-->
-    <!--<div class="modal-header">-->
-    <!--<h5 class="modal-title">重新审批</h5>-->
-    <!--</div>-->
-    <!--<div class="modal-body">-->
-    <!--<h5>确认由「终审-<%= auditList[auditList.length-1].name %>」重新审批「<%= change.code %>」?</h5>-->
-    <!--<% if (ctx.session.sessionUser.loginStatus === 0) { %>-->
-    <!--<div class="form-group">-->
-    <!--<label>重审需要验证码确认,验证码将发送至尾号<%- authMobile.slice(-4) %>的手机</label>-->
-    <!--<div class="input-group input-group-sm mb-3">-->
-    <!--<input class="form-control" type="text" readonly="readonly" name="code" placeholder="输入短信中的6位验证码" />-->
-    <!--<div class="input-group-append">-->
-    <!--<button class="btn btn-outline-secondary" type="button" id="get-code">获取验证码</button>-->
-    <!--</div>-->
-    <!--</div>-->
-    <!--</div>-->
-    <!--<% } %>-->
-    <!--</div>-->
-    <!--<div class="modal-footer">-->
-    <!--<input type="hidden" name="cid" value="<%= change.cid %>">-->
-    <!--<input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />-->
-    <!--<button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>-->
-    <!--<button type="button" id="re-shenpi-btn" class="btn btn-warning btn-sm" <% if (ctx.session.sessionUser.loginStatus === 0) { %>disabled<% } %>>确定重审</button>-->
-    <!--</div>-->
-    <!--</form>-->
-    <!--</div>-->
-    <!--</div>-->
+        <!--终审重新审批-->
+        <div class="modal fade" id="sp-down-back" 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>
+                        <h5>您目前还没设置认证手机,请先设置。</h5>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                        <a href="/profile/sms" class="btn btn-sm btn-primary">去设置</a>
+                    </div>
+                </div>
+            </div>
+        </div>
+    <% } else { %>
+        <!--重新审批-->
+        <div class="modal fade" id="sp-down-back" data-backdrop="static">
+            <div class="modal-dialog" role="document">
+                <form id="againForm" class="modal-content" method="post" action="/tender/<%- tender.id %>/change/check/again" onsubmit="return false;">
+                    <div class="modal-header">
+                        <h5 class="modal-title">重新审批</h5>
+                    </div>
+                    <div class="modal-body">
+                        <h5>确认由「终审-<%= auditList[auditList.length-1].name %>」重新审批「<%= change.code %>」?</h5>
+                        <% if (ctx.session.sessionUser.loginStatus === 0) { %>
+                            <div class="form-group">
+                                <label>重审需要验证码确认,验证码将发送至尾号<%- authMobile.slice(-4) %>的手机</label>
+                                <div class="input-group input-group-sm mb-3">
+                                    <input class="form-control" type="text" readonly="readonly" name="code" placeholder="输入短信中的6位验证码" />
+                                    <div class="input-group-append">
+                                        <button class="btn btn-outline-secondary" type="button" id="get-code">获取验证码</button>
+                                    </div>
+                                </div>
+                            </div>
+                        <% } %>
+                    </div>
+                    <div class="modal-footer">
+                        <input type="hidden" name="cid" value="<%= change.cid %>">
+                        <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                        <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                        <button type="button" id="re-shenpi-btn" class="btn btn-warning btn-sm" <% if (ctx.session.sessionUser.loginStatus === 0) { %>disabled<% } %>>确定重审</button>
+                    </div>
+                </form>
+            </div>
+        </div>
     <% } %>
 <% } %>
 <% if (auditStatus === 4 && ctx.session.sessionUser.accountId === change.uid) { %>

+ 5 - 6
app/view/change/plan.ejs

@@ -81,7 +81,7 @@
                             <td style="text-align: right"><%- c.total_price %></td>
                             <td><%- c.apply_code %></td>
                             <td>
-                                <% if ((c.status === auditConst.status.uncheck || c.status === auditConst.status.checkNo) && c.uid === ctx.session.sessionUser.accountId) { %>
+                                <% if ((c.status === auditConst.status.uncheck || c.status === auditConst.status.checkNo || c.status === auditConst.status.revise) && c.uid === ctx.session.sessionUser.accountId) { %>
                                     <a href="/tender/<%- tender.id %>/change/plan/<%- c.id %>/information" class="btn <%- auditConst.statusButtonClass[c.status] %> btn-sm"><%- auditConst.statusButton[c.status] %></a>
                                 <% } else if (c.status === auditConst.status.checking && c.curAuditor && c.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
                                     <a href="/tender/<%- tender.id %>/change/plan/<%- c.id %>/information" class="btn <%- auditConst.statusButtonClass[c.status] %> btn-sm"><%- auditConst.statusButton[c.status] %></a>
@@ -89,13 +89,12 @@
                                     <span class="<%- auditConst.auditProgressClass[c.status] %>"><%- auditConst.auditProgress[c.status] %></span>
                                 <% } %>
                             </td>
-                            <% if (c.status === auditConst.status.uncheck) { %>
                             <td>
-                                待上报
+                                <% if (c.status !== auditConst.status.uncheck) { %>
+                                    <%- c.curAuditor.name %>-<%- c.curAuditor.role %>
+                                <% } %>
+                                <span class="<%- auditConst.statusClass[c.status] %>"><%- auditConst.statusString[c.status] %></span>
                             </td>
-                            <% } else { %>
-                            <td><%- c.curAuditor.name %>-<%- c.curAuditor.role %> <span class="<%- auditConst.statusClass[c.status] %>"><%- auditConst.statusString[c.status] %></span></td>
-                            <% } %>
                             <td>
                                 <% if (c.uid === uid && (c.status === auditConst.status.uncheck || c.status === auditConst.status.checkNo)) { %><a href="#del-bg" cpid="<%= c.id %>" data-toggle="modal" data-target="#del-bg" class="btn btn-outline-danger btn-sm delete-cpid-modal">删除</a><% } %>
                             </td>

+ 11 - 2
app/view/change/plan_information.ejs

@@ -17,6 +17,9 @@
                 <% } %>
             </div>
             <div class="ml-auto" id="sp-btn">
+                <% if (ctx.change.cancancel) { %>
+                    <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-cancel" class="btn btn-danger btn-sm mr-2">撤回</a>
+                <% } %>
                 <% if (ctx.change.status === auditConst.status.uncheck) { %>
                     <% if (ctx.session.sessionUser.accountId === ctx.change.uid) { %>
                         <a id="sub-sp-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm">上报审批</a>
@@ -31,9 +34,15 @@
                         <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm">审批中</a>
                     <% } %>
                 <% } else if (ctx.change.status === auditConst.status.checked) { %>
+                    <% if (ctx.session.sessionUser.accountId === ctx.change.uid) { %>
+                        <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-revise" class="btn btn-warning btn-sm mr-2">修订变更</a>
+                    <% } %>
                     <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-success btn-sm">审批完成</a>
-                <% } else if (ctx.change.status === auditConst.status.checkNo) { %>
-                    <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-muted sp-list-btn">审批退回</a>
+                <% } else if (ctx.change.status === auditConst.status.checkNo || ctx.change.status === auditConst.status.revise) { %>
+                    <% if (ctx.change.status === auditConst.status.revise && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.session.sessionUser.is_admin)) { %>
+                        <a href="#sub-revoke" data-toggle="modal" data-target="#sub-revoke" class="btn btn-warning btn-sm mr-2">撤销修订</a>
+                    <% } %>
+                    <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-muted sp-list-btn">审批<% if (ctx.change.status === auditConst.status.checkNo) { %>退回<% } else { %>修订<% } %></a>
                     <% if (ctx.session.sessionUser.accountId === ctx.change.uid) { %>
                         <a href="#sp-list" data-type="show" data-toggle="modal" data-target="#sp-list"  class="btn btn-primary btn-sm sp-list-btn">重新上报</a>
                     <% } %>

+ 215 - 39
app/view/change/plan_information_modal.ejs

@@ -22,7 +22,7 @@
         </div>
     </div>
 </div>
-<% if ((ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.checkNo) && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.tender.isTourist)) { %>
+<% if ((ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.checkNo || ctx.change.status === auditConst.status.revise) && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.tender.isTourist)) { %>
     <!--上报审批-->
     <div class="modal fade" id="sub-sp" data-backdrop="static">
         <div class="modal-dialog" role="document">
@@ -138,6 +138,31 @@
             </div>
         </div>
     </div>
+    <% if (ctx.change.status === auditConst.status.revise && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.session.sessionUser.is_admin)) { %>
+        <!--撤销修订-->
+        <div class="modal fade" id="sub-revoke"  role="dialog" aria-labelledby="myModalLabel">
+            <div class="modal-dialog" role="document">
+                <form id="reviseForm" class="modal-content" method="post" action="/tender/<%- tender.id %>/change/plan/cancel/revise" onsubmit="return false;">
+                    <div class="modal-header">
+                        <h5 class="modal-title" id="myModalLabel">撤销修订</h5>
+                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                            <span aria-hidden="true">&times;</span>
+                        </button>
+                    </div>
+                    <div class="modal-body">
+                        <h5>撤销修订,所有修改的数据将全部会被还原。</h5>
+                        <h5>确认撤销修订?</h5>
+                    </div>
+                    <div class="modal-footer">
+                        <input type="hidden" name="cpid" value="<%= ctx.change.id %>">
+                        <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                        <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                        <button type="button" id="cancel-revise-btn" class="btn btn-primary btn-sm">确认</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+    <% } %>
 <% } %>
 
 <!--审批流程/结果-->
@@ -145,12 +170,12 @@
     <div class="modal-dialog modal-lg" role="document">
         <div class="modal-content">
             <div class="modal-header">
-                <h5 class="modal-title"><%- ctx.change.status !== auditConst.status.checkNo ? '审批流程' : '重新上报' %></h5>
+                <h5 class="modal-title"><%- ctx.change.status === auditConst.status.checkNo || ctx.change.status === auditConst.status.revise ? '重新上报' : '审批流程' %></h5>
             </div>
             <div class="modal-body">
                 <div class="row">
                     <div class="col-4">
-                        <% if(ctx.change.status === auditConst.status.checkNo) { %>
+                        <% if(ctx.change.status === auditConst.status.checkNo || ctx.change.status === auditConst.status.revise) { %>
                             <a class="sp-list-item" href="#sub-sp" data-toggle="modal" data-target="#sub-sp" id="hideSp">修改审批流程</a>
                         <% } %>
                         <div class="card mt-3">
@@ -221,11 +246,11 @@
                                                 <% if(index < auditors.length - 1) { %>
                                                     <div class="timeline-item-tail"></div>
                                                 <% } %>
-                                                <% if(auditor.status === auditConst.status.checked) { %>
+                                                <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                     <div class="timeline-item-icon bg-success text-light">
                                                         <i class="fa fa-check"></i>
                                                     </div>
-                                                <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                     <div class="timeline-item-icon bg-warning text-light">
                                                         <i class="fa fa-level-up"></i>
                                                     </div>
@@ -242,7 +267,7 @@
                                                         <div class="card-body p-3">
                                                             <div class="card-text">
                                                                 <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
-                                                                            class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                            class="pull-right <%- auditConst.auditStringClass[auditor.status] %>"><%- auditConst.auditString[auditor.status] %></span>
                                                                 </p>
                                                                 <p class="text-muted mb-0"><%- auditor.role %></p>
                                                             </div>
@@ -265,11 +290,11 @@
                                                 <% if(index < auditors.length - 1) { %>
                                                     <div class="timeline-item-tail"></div>
                                                 <% } %>
-                                                <% if(auditor.status === auditConst.status.checked) { %>
+                                                <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                     <div class="timeline-item-icon bg-success text-light">
                                                         <i class="fa fa-check"></i>
                                                     </div>
-                                                <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                     <div class="timeline-item-icon bg-warning text-light">
                                                         <i class="fa fa-level-up"></i>
                                                     </div>
@@ -288,7 +313,7 @@
                                                                 <p class="mb-1"><span class="h5"><%- auditor.name %></span>
                                                                     <span
                                                                             class="pull-right
-                                                                            <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                            <%- auditConst.auditStringClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.auditString[auditor.status] : ''%>
                                                                         <%- auditor.status === auditConst.status.checkNo ? ctx.change.user.name : '' %>
                                                         </span>
                                                                 </p>
@@ -316,7 +341,7 @@
             <form class="modal-footer" method="post" action="<%- preUrl %>/audit/start" onsubmit="return checkAuditorFrom()">
                 <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
                 <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-                <% if(ctx.change.status === auditConst.status.checkNo && ctx.session.sessionUser.accountId === ctx.change.uid) { %>
+                <% if((ctx.change.status === auditConst.status.checkNo || ctx.change.status === auditConst.status.revise) && ctx.session.sessionUser.accountId === ctx.change.uid) { %>
                     <button class="btn btn-primary btn-sm sp-list-item" type="submit">确认上报</button>
                 <% } %>
             </form>
@@ -403,11 +428,11 @@
                                                         <% if(index < auditors.length - 1) { %>
                                                             <div class="timeline-item-tail"></div>
                                                         <% } %>
-                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                        <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -424,22 +449,24 @@
                                                                 <div class="card-body p-3">
                                                                     <div class="card-text">
                                                                         <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
-                                                                                    class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                                    class="pull-right <%- auditConst.auditStringClass[auditor.status] %>"><%- auditConst.auditString[auditor.status] %></span>
                                                                         </p>
                                                                         <p class="text-muted mb-0"><%- auditor.role %></p>
                                                                     </div>
                                                                 </div>
                                                                 <!--审批意见-->
                                                                 <% if(auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
                                                                     <div class="card-body p-3 border-top">
-                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
                                                                             <label>审批意见<b class="text-danger">*</b></label>
                                                                             <textarea class="form-control form-control-sm"
                                                                                       name="opinion">同意</textarea>
-                                                                        <% } else { %>
-                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
-                                                                        <% } %>
                                                                     </div>
+                                                                    <% } else if (auditor.opinion) { %>
+                                                                        <div class="card-body p-3 border-top">
+                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        </div>
+                                                                    <% } %>
                                                                 <% } %>
                                                             </div>
                                                         </div>
@@ -452,11 +479,11 @@
                                                         <% if(index < auditors.length - 1) { %>
                                                             <div class="timeline-item-tail"></div>
                                                         <% } %>
-                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                        <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -475,7 +502,7 @@
                                                                         <p class="mb-1"><span class="h5"><%- auditor.name %></span>
                                                                             <span
                                                                                     class="pull-right
-                                                                                    <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                                    <%- auditConst.auditStringClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.auditString[auditor.status] : ''%>
                                                                                 <%- auditor.status === auditConst.status.checkNo ? ctx.change.user.name : '' %>
                                                                 </span>
                                                                         </p>
@@ -484,15 +511,17 @@
                                                                 </div>
                                                                 <!--审批意见-->
                                                                 <% if(auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
                                                                     <div class="card-body p-3 border-top">
-                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
                                                                             <label>审批意见<b class="text-danger">*</b></label>
                                                                             <textarea class="form-control form-control-sm"
                                                                                       name="opinion">同意</textarea>
-                                                                        <% } else { %>
-                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
-                                                                        <% } %>
                                                                     </div>
+                                                                    <% } else if (auditor.opinion) { %>
+                                                                    <div class="card-body p-3 border-top">
+                                                                        <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                    </div>
+                                                                    <% } %>
                                                                 <% } %>
                                                             </div>
                                                         </div>
@@ -593,11 +622,11 @@
                                                         <% if(index < auditors.length - 1) { %>
                                                             <div class="timeline-item-tail"></div>
                                                         <% } %>
-                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                        <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -622,8 +651,8 @@
 
                                                                 <!--审批意见-->
                                                                 <% if(auditor.times === ctx.change.times && auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
                                                                     <div class="card-body p-3 border-top">
-                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
                                                                             <label>审批意见<b class="text-danger">*</b></label>
                                                                             <textarea class="form-control form-control-sm"
                                                                                       name="opinion">不同意</textarea>
@@ -638,10 +667,12 @@
                                                                                     </div>
                                                                                 </div>
                                                                             <% } %>
-                                                                        <% } else if(auditor.status === auditConst.status.checked){ %>
-                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
-                                                                        <% } %>
                                                                     </div>
+                                                                    <% } else if(auditor.opinion) { %>
+                                                                    <div class="card-body p-3 border-top">
+                                                                        <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                    </div>
+                                                                    <% } %>
                                                                 <% } %>
                                                             </div>
                                                         </div>
@@ -654,11 +685,11 @@
                                                         <% if(index < auditors.length - 1) { %>
                                                             <div class="timeline-item-tail"></div>
                                                         <% } %>
-                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                        <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -686,8 +717,8 @@
                                                                 </div>
                                                                 <!--审批意见-->
                                                                 <% if(auditor.times === ctx.change.times && auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
                                                                     <div class="card-body p-3 border-top">
-                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
                                                                             <label>审批意见<b class="text-danger">*</b></label>
                                                                             <textarea class="form-control form-control-sm"
                                                                                       name="opinion">不同意</textarea>
@@ -702,12 +733,12 @@
                                                                                     </div>
                                                                                 </div>
                                                                             <% } %>
-                                                                        <% } else { %>
-                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
-                                                                        <% } %>
-
                                                                     </div>
-
+                                                                    <% } else if(auditor.opinion) { %>
+                                                                    <div class="card-body p-3 border-top">
+                                                                        <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                    </div>
+                                                                    <% } %>
                                                                 <% } %>
                                                             </div>
                                                         </div>
@@ -731,13 +762,87 @@
         </div>
     <% } %>
 <% } %>
+<% if (ctx.change.status === auditConst.status.checked && ctx.session.sessionUser.accountId === ctx.change.uid) { %>
+    <% if (!authMobile && ctx.session.sessionUser.loginStatus === 0) { %>
+        <!--原报修订变更-->
+        <div class="modal fade" id="sp-down-revise" 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>
+                        <h5>您目前还没设置认证手机,请先设置。</h5>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                        <a href="/profile/sms" class="btn btn-sm btn-primary">去设置</a>
+                    </div>
+                </div>
+            </div>
+        </div>
+    <% } else { %>
+        <!--修订变更-->
+        <div class="modal fade" id="sp-down-revise" data-backdrop="static">
+            <div class="modal-dialog" role="document">
+                <form id="reviseForm" class="modal-content" method="post" action="/tender/<%- tender.id %>/change/plan/check/revise" onsubmit="return false;">
+                    <div class="modal-header">
+                        <h5 class="modal-title">修订变更</h5>
+                    </div>
+                    <div class="modal-body">
+                        <h5>确认需要修订变更「<%= change.code %>」?</h5>
+                        <% if (ctx.session.sessionUser.loginStatus === 0) { %>
+                            <div class="form-group">
+                                <label>修订需要验证码确认,验证码将发送至尾号<%- authMobile.slice(-4) %>的手机</label>
+                                <div class="input-group input-group-sm mb-3">
+                                    <input class="form-control" type="text" readonly="readonly" name="code" placeholder="输入短信中的6位验证码" />
+                                    <div class="input-group-append">
+                                        <button class="btn btn-outline-secondary" type="button" id="get-code">获取验证码</button>
+                                    </div>
+                                </div>
+                            </div>
+                        <% } %>
+                    </div>
+                    <div class="modal-footer">
+                        <input type="hidden" name="cpid" value="<%= ctx.change.id %>">
+                        <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                        <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                        <button type="button" id="re-shenpi-btn2" class="btn btn-warning btn-sm" <% if (ctx.session.sessionUser.loginStatus === 0) { %>disabled<% } %>>确定修订</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+    <% } %>
+<% } %>
+<% if (ctx.change.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>
+<% } %>
 <% if (ctx.session.sessionUser.accountId === ctx.change.uid && (ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.checkNo)) { %>
     <script>
         const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
         const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
     </script>
 <% } %>
-<script>const cur_uid = parseInt('<%- ctx.session.sessionUser.accountId %>');</script>
+<script>
+    const cur_uid = parseInt('<%- ctx.session.sessionUser.accountId %>');
+    const csrf = '<%= ctx.csrf %>';
+</script>
 <script>
     $('.sp-location-list').on('shown.bs.modal', function () {
         const scrollBox = $(this).find('div[class="col-8 modal-height-500"]');
@@ -783,4 +888,75 @@
             $('.modal-title').text('重新上报')
         }
     });
+
+    $('#re-shenpi-btn2').click(function () {
+        const data = {
+            cpid: parseInt('<%- ctx.change.id %>'),
+        };
+        <% if (ctx.session.sessionUser.loginStatus === 0) { %>
+        const code = $("#reviseForm input[name='code']").val();
+        if ($(this).hasClass('disabled')) {
+            return false;
+        }
+        if (code.length < 6) {
+            // alert('请填写正确的验证码');
+            toast('请填写正确的验证码', 'error');
+            return false;
+        }
+        data.code = code;
+        <% } %>
+        $.ajax({
+            url: '/tender/<%- tender.id %>/change/plan/check/revise?_csrf_j=' + csrf,
+            type: 'post',
+            data: data,
+            dataTye: 'json',
+            success: function(response) {
+                if (response.err === 0) {
+                    window.location.href = response.url;
+                } else {
+                    toastr.error(response.msg);
+                }
+            }
+        });
+    })
+
+    $('#cancel-revise-btn').click(function () {
+        const data = {
+            cpid: parseInt('<%- ctx.change.id %>'),
+        };
+        $.ajax({
+            url: '/tender/<%- tender.id %>/change/plan/cancel/revise?_csrf_j=' + csrf,
+            type: 'post',
+            data: data,
+            dataTye: 'json',
+            success: function(response) {
+                if (response.err === 0) {
+                    window.location.href = response.url;
+                } else {
+                    toastr.error(response.msg);
+                }
+            }
+        });
+    })
+
+    <% if (ctx.change && ctx.change.cancancel) { %>
+    $("#cancel-shenpi-btn").click(function () {
+        const data = {
+            cpid: parseInt('<%- ctx.change.id %>'),
+        };
+        $.ajax({
+            url: '/tender/<%- tender.id %>/change/plan/cancel/audit?_csrf_j=' + csrf,
+            type: 'post',
+            data: data,
+            dataTye: 'json',
+            success: function (response) {
+                if (response.err === 0) {
+                    window.location.href = response.url;
+                } else {
+                    toastr.error(response.msg);
+                }
+            }
+        });
+    });
+    <% } %>
 </script>

+ 1 - 1
app/view/change/project.ejs

@@ -76,7 +76,7 @@
                         <tr><td><a href="/tender/<%- tender.id %>/change/project/<%- c.id %>/information"><%- c.code %></a></td>
                             <td><%- c.name %></td><td><%- c.account_name %></td><td><%- changeConst.project_type[c.type] %></td><td><%- ctx.helper.formatFullDate(c.in_time) %></td>
                             <td>
-                                <% if ((c.status === auditConst.status.uncheck || c.status === auditConst.status.back) && c.uid === ctx.session.sessionUser.accountId) { %>
+                                <% if ((c.status === auditConst.status.uncheck || c.status === auditConst.status.back || c.status === auditConst.status.revise) && c.uid === ctx.session.sessionUser.accountId) { %>
                                     <a href="/tender/<%- tender.id %>/change/project/<%- c.id %>/information" class="btn <%- auditConst.statusButtonClass[c.status] %> btn-sm"><%- auditConst.statusButton[c.status] %></a>
                                 <% } else if (c.status === auditConst.status.checking && c.curAuditor && c.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
                                     <a href="/tender/<%- tender.id %>/change/project/<%- c.id %>/information" class="btn <%- auditConst.statusButtonClass[c.status] %> btn-sm"><%- auditConst.statusButton[c.status] %></a>

+ 11 - 2
app/view/change/project_information.ejs

@@ -13,6 +13,9 @@
             </div>
             <div class="ml-auto" id="sp-btn">
                 <a href="#xieshen" data-toggle="modal" data-target="#xieshen" class="btn btn-sm btn-primary mr-2">添加协审</a>
+                <% if (ctx.change.cancancel) { %>
+                    <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-cancel" class="btn btn-danger btn-sm mr-2">撤回</a>
+                <% } %>
                 <% if (ctx.change.status === auditConst.status.uncheck) { %>
                     <% if (ctx.session.sessionUser.accountId === ctx.change.uid) { %>
                         <a id="sub-sp-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm">上报审批</a>
@@ -28,9 +31,15 @@
                         <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm">审批中</a>
                     <% } %>
                 <% } else if (ctx.change.status === auditConst.status.checked) { %>
+                    <% if (ctx.session.sessionUser.accountId === ctx.change.uid) { %>
+                        <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-revise" class="btn btn-warning btn-sm mr-2">修订变更</a>
+                    <% } %>
                     <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm">审批完成</a>
-                <% } else if (ctx.change.status === auditConst.status.back) { %>
-                    <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-muted sp-list-btn">审批退回</a>
+                <% } else if (ctx.change.status === auditConst.status.back || ctx.change.status === auditConst.status.revise) { %>
+                    <% if (ctx.change.status === auditConst.status.revise && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.session.sessionUser.is_admin)) { %>
+                        <a href="#sub-revoke" data-toggle="modal" data-target="#sub-revoke" class="btn btn-warning btn-sm mr-2">撤销修订</a>
+                    <% } %>
+                    <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-muted sp-list-btn">审批<% if (ctx.change.status === auditConst.status.back) { %>退回<% } else { %>修订<% } %></a>
                     <% if (ctx.session.sessionUser.accountId === ctx.change.uid) { %>
                         <a href="#sp-list" data-type="show" data-toggle="modal" data-target="#sp-list"  class="btn btn-primary btn-sm sp-list-btn">重新上报</a>
                     <% } %>

+ 240 - 46
app/view/change/project_information_modal.ejs

@@ -20,7 +20,7 @@
         </div>
     </div>
 </div>
-<% if ((ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.back) && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.tender.isTourist)) { %>
+<% if ((ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.back || ctx.change.status === auditConst.status.revise) && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.tender.isTourist)) { %>
     <!--上报审批-->
     <div class="modal fade" id="sub-sp" data-backdrop="static">
         <div class="modal-dialog" role="document">
@@ -86,6 +86,31 @@
             </div>
         </div>
     </div>
+    <% if (ctx.change.status === auditConst.status.revise && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.session.sessionUser.is_admin)) { %>
+        <!--撤销修订-->
+        <div class="modal fade" id="sub-revoke"  role="dialog" aria-labelledby="myModalLabel">
+            <div class="modal-dialog" role="document">
+                <form id="reviseForm" class="modal-content" method="post" action="/tender/<%- tender.id %>/change/project/cancel/revise" onsubmit="return false;">
+                    <div class="modal-header">
+                        <h5 class="modal-title" id="myModalLabel">撤销修订</h5>
+                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                            <span aria-hidden="true">&times;</span>
+                        </button>
+                    </div>
+                    <div class="modal-body">
+                        <h5>撤销修订,所有修改的数据将全部会被还原。</h5>
+                        <h5>确认撤销修订?</h5>
+                    </div>
+                    <div class="modal-footer">
+                        <input type="hidden" name="cpid" value="<%= ctx.change.id %>">
+                        <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                        <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                        <button type="button" id="cancel-revise-btn" class="btn btn-primary btn-sm">确认</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+    <% } %>
 <% } %>
 
 <!--审批流程/结果-->
@@ -93,12 +118,12 @@
     <div class="modal-dialog modal-lg" role="document">
         <div class="modal-content">
             <div class="modal-header">
-                <h5 class="modal-title"><%- ctx.change.status !== auditConst.status.back ? '审批流程' : '重新上报' %></h5>
+                <h5 class="modal-title"><%- ctx.change.status === auditConst.status.back || ctx.change.status === auditConst.status.revise ? '重新上报' : '审批流程' %></h5>
             </div>
             <div class="modal-body">
                 <div class="row">
                     <div class="col-4">
-                        <% if(ctx.change.status === auditConst.status.back) { %>
+                        <% if(ctx.change.status === auditConst.status.back || ctx.change.status === auditConst.status.revise) { %>
                             <a class="sp-list-item" href="#sub-sp" data-toggle="modal" data-target="#sub-sp" id="hideSp">修改审批流程</a>
                         <% } %>
                         <div class="card mt-3">
@@ -169,11 +194,11 @@
                                                 <% if(index < auditors.length - 1) { %>
                                                     <div class="timeline-item-tail"></div>
                                                 <% } %>
-                                                <% if(auditor.status === auditConst.status.checked) { %>
+                                                <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                     <div class="timeline-item-icon bg-success text-light">
                                                         <i class="fa fa-check"></i>
                                                     </div>
-                                                <% } else if(auditor.status === auditConst.status.back) {%>
+                                                <% } else if(auditor.status === auditConst.status.back || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                     <div class="timeline-item-icon bg-warning text-light">
                                                         <i class="fa fa-level-up"></i>
                                                     </div>
@@ -183,7 +208,7 @@
                                                     </div>
                                                 <% } else if(auditor.status === auditConst.status.checkNo) { %>
                                                     <div class="timeline-item-icon bg-danger text-light">
-                                                        <i class="fa fa-ellipsis-h"></i>
+                                                        <i class="fa fa-stop"></i>
                                                     </div>
                                                 <% } else {%>
                                                     <div class="timeline-item-icon bg-secondary text-light">
@@ -194,7 +219,7 @@
                                                         <div class="card-body p-3">
                                                             <div class="card-text">
                                                                 <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
-                                                                            class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                            class="pull-right <%- auditConst.auditStringClass[auditor.status] %>"><%- auditConst.auditString[auditor.status] %></span>
                                                                 </p>
                                                                 <p class="text-muted mb-0"><%- auditor.role %></p>
                                                             </div>
@@ -217,11 +242,11 @@
                                                 <% if(index < auditors.length - 1) { %>
                                                     <div class="timeline-item-tail"></div>
                                                 <% } %>
-                                                <% if(auditor.status === auditConst.status.checked) { %>
+                                                <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                     <div class="timeline-item-icon bg-success text-light">
                                                         <i class="fa fa-check"></i>
                                                     </div>
-                                                <% } else if(auditor.status === auditConst.status.back) {%>
+                                                <% } else if(auditor.status === auditConst.status.back || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                     <div class="timeline-item-icon bg-warning text-light">
                                                         <i class="fa fa-level-up"></i>
                                                     </div>
@@ -231,7 +256,7 @@
                                                     </div>
                                                 <% } else if(auditor.status === auditConst.status.checkNo) { %>
                                                     <div class="timeline-item-icon bg-danger text-light">
-                                                        <i class="fa fa-ellipsis-h"></i>
+                                                        <i class="fa fa-stop"></i>
                                                     </div>
                                                 <% } else { %>
                                                     <div class="timeline-item-icon bg-secondary text-light">
@@ -244,7 +269,7 @@
                                                                 <p class="mb-1"><span class="h5"><%- auditor.name %></span>
                                                                     <span
                                                                             class="pull-right
-                                                                            <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                            <%- auditConst.auditStringClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.auditString[auditor.status] : ''%>
                                                                         <%- auditor.status === auditConst.status.back ? ctx.change.user.name : '' %>
                                                         </span>
                                                                 </p>
@@ -272,7 +297,7 @@
             <form class="modal-footer" method="post" action="<%- preUrl %>/audit/start" onsubmit="return checkAuditorFrom()">
                 <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
                 <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-                <% if(ctx.change.status === auditConst.status.back && ctx.session.sessionUser.accountId === ctx.change.uid) { %>
+                <% if((ctx.change.status === auditConst.status.back || ctx.change.status === auditConst.status.revise) && ctx.session.sessionUser.accountId === ctx.change.uid) { %>
                     <button class="btn btn-primary btn-sm sp-list-item" type="submit">确认上报</button>
                 <% } %>
             </form>
@@ -359,11 +384,11 @@
                                                         <% if(index < auditors.length - 1) { %>
                                                             <div class="timeline-item-tail"></div>
                                                         <% } %>
-                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                        <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.back) {%>
+                                                        <% } else if(auditor.status === auditConst.status.back || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -371,6 +396,10 @@
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-ellipsis-h"></i>
                                                             </div>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo) { %>
+                                                            <div class="timeline-item-icon bg-danger text-light">
+                                                                <i class="fa fa-stop"></i>
+                                                            </div>
                                                         <% } else {%>
                                                             <div class="timeline-item-icon bg-secondary text-light">
                                                             </div>
@@ -380,22 +409,26 @@
                                                                 <div class="card-body p-3">
                                                                     <div class="card-text">
                                                                         <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
-                                                                                    class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                                    class="pull-right <%- auditConst.auditString[auditor.status] %>"><%- auditConst.auditString[auditor.status] %></span>
                                                                         </p>
                                                                         <p class="text-muted mb-0"><%- auditor.role %></p>
                                                                     </div>
                                                                 </div>
                                                                 <!--审批意见-->
                                                                 <% if(auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
                                                                     <div class="card-body p-3 border-top">
                                                                         <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
                                                                             <label>审批意见<b class="text-danger">*</b></label>
                                                                             <textarea class="form-control form-control-sm"
                                                                                       name="opinion">同意</textarea>
-                                                                        <% } else { %>
-                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
                                                                         <% } %>
                                                                     </div>
+                                                                    <% } else if (auditor.opinion) { %>
+                                                                    <div class="card-body p-3 border-top">
+                                                                        <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                    </div>
+                                                                    <% } %>
                                                                 <% } %>
                                                             </div>
                                                         </div>
@@ -408,11 +441,11 @@
                                                         <% if(index < auditors.length - 1) { %>
                                                             <div class="timeline-item-tail"></div>
                                                         <% } %>
-                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                        <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.back) {%>
+                                                        <% } else if(auditor.status === auditConst.status.back || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -420,6 +453,10 @@
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-ellipsis-h"></i>
                                                             </div>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo) { %>
+                                                            <div class="timeline-item-icon bg-danger text-light">
+                                                                <i class="fa fa-stop"></i>
+                                                            </div>
                                                         <% } else { %>
                                                             <div class="timeline-item-icon bg-secondary text-light">
                                                             </div>
@@ -431,7 +468,7 @@
                                                                         <p class="mb-1"><span class="h5"><%- auditor.name %></span>
                                                                             <span
                                                                                     class="pull-right
-                                                                                    <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                                    <%- auditConst.auditStringClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.auditString[auditor.status] : ''%>
                                                                                 <%- auditor.status === auditConst.status.back ? ctx.change.user.name : '' %>
                                                                 </span>
                                                                         </p>
@@ -440,15 +477,19 @@
                                                                 </div>
                                                                 <!--审批意见-->
                                                                 <% if(auditor.status !== auditConst.status.uncheck) { %>
-                                                                    <div class="card-body p-3 border-top">
-                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
-                                                                            <label>审批意见<b class="text-danger">*</b></label>
-                                                                            <textarea class="form-control form-control-sm"
-                                                                                      name="opinion">同意</textarea>
-                                                                        <% } else { %>
+                                                                    <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                                        <div class="card-body p-3 border-top">
+                                                                            <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                                                <label>审批意见<b class="text-danger">*</b></label>
+                                                                                <textarea class="form-control form-control-sm"
+                                                                                          name="opinion">同意</textarea>
+                                                                            <% } %>
+                                                                        </div>
+                                                                    <% } else if (auditor.opinion) { %>
+                                                                        <div class="card-body p-3 border-top">
                                                                             <p style="margin: 0;"><%- auditor.opinion %></p>
-                                                                        <% } %>
-                                                                    </div>
+                                                                        </div>
+                                                                    <% } %>
                                                                 <% } %>
                                                             </div>
                                                         </div>
@@ -549,11 +590,11 @@
                                                         <% if(index < auditors.length - 1) { %>
                                                             <div class="timeline-item-tail"></div>
                                                         <% } %>
-                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                        <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.back) {%>
+                                                        <% } else if(auditor.status === auditConst.status.back || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -561,6 +602,10 @@
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-ellipsis-h"></i>
                                                             </div>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo) { %>
+                                                            <div class="timeline-item-icon bg-danger text-light">
+                                                                <i class="fa fa-stop"></i>
+                                                            </div>
                                                         <% } else {%>
                                                             <div class="timeline-item-icon bg-secondary text-light">
                                                             </div>
@@ -570,7 +615,7 @@
                                                                 <div class="card-body p-3">
                                                                     <div class="card-text">
                                                                         <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
-                                                                                    class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                                    class="pull-right <%- auditConst.auditStringClass[auditor.status] %>"><%- auditConst.auditString[auditor.status] %></span>
                                                                         </p>
                                                                         <p class="text-muted mb-0"><%- auditor.role %></p>
                                                                     </div>
@@ -578,8 +623,8 @@
 
                                                                 <!--审批意见-->
                                                                 <% if(auditor.times === ctx.change.times && auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
                                                                     <div class="card-body p-3 border-top">
-                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
                                                                             <label>审批意见<b class="text-danger">*</b></label>
                                                                             <textarea class="form-control form-control-sm"
                                                                                       name="opinion">不同意</textarea>
@@ -594,10 +639,12 @@
                                                                                     </div>
                                                                                 </div>
                                                                             <% } %>
-                                                                        <% } else if(auditor.status === auditConst.status.checked){ %>
-                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
-                                                                        <% } %>
                                                                     </div>
+                                                                    <% } else if(auditor.opinion) { %>
+                                                                    <div class="card-body p-3 border-top">
+                                                                        <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                    </div>
+                                                                    <% } %>
                                                                 <% } %>
                                                             </div>
                                                         </div>
@@ -610,11 +657,11 @@
                                                         <% if(index < auditors.length - 1) { %>
                                                             <div class="timeline-item-tail"></div>
                                                         <% } %>
-                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                        <% if(auditor.status === auditConst.status.checked || auditor.status === auditConst.status.cancelRevise) { %>
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.back) {%>
+                                                        <% } else if(auditor.status === auditConst.status.back || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -622,6 +669,10 @@
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-ellipsis-h"></i>
                                                             </div>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo) { %>
+                                                            <div class="timeline-item-icon bg-danger text-light">
+                                                                <i class="fa fa-stop"></i>
+                                                            </div>
                                                         <% } else { %>
                                                             <div class="timeline-item-icon bg-secondary text-light">
                                                             </div>
@@ -633,7 +684,7 @@
                                                                         <p class="mb-1"><span class="h5"><%- auditor.name %></span>
                                                                             <span
                                                                                     class="pull-right
-                                                                                    <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                                    <%- auditConst.auditStringClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.auditString[auditor.status] : ''%>
                                                                                 <%- auditor.status === auditConst.status.back ? ctx.change.user.name : '' %>
                                                                 </span>
                                                                         </p>
@@ -642,12 +693,12 @@
                                                                 </div>
                                                                 <!--审批意见-->
                                                                 <% if(auditor.times === ctx.change.times && auditor.status !== auditConst.status.uncheck) { %>
-                                                                    <div class="card-body p-3 border-top">
-                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                                    <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                                        <div class="card-body p-3 border-top">
                                                                             <label>审批意见<b class="text-danger">*</b></label>
                                                                             <textarea class="form-control form-control-sm"
                                                                                       name="opinion">不同意</textarea>
-                                                                            <% if (ctx.change.curAuditor.aid === auditor.aid ) { %>
+                                                                            <% if (ctx.change.curAuditor.aid === auditor.aid) { %>
                                                                                 <div id="reject-process" class="alert alert-warning"
                                                                                      style="margin-top: 15px;">
                                                                                     <div class="form-check form-check-inline">
@@ -658,12 +709,12 @@
                                                                                     </div>
                                                                                 </div>
                                                                             <% } %>
-                                                                        <% } else { %>
+                                                                        </div>
+                                                                    <% } else if(auditor.opinion) { %>
+                                                                        <div class="card-body p-3 border-top">
                                                                             <p style="margin: 0;"><%- auditor.opinion %></p>
-                                                                        <% } %>
-
-                                                                    </div>
-
+                                                                        </div>
+                                                                    <% } %>
                                                                 <% } %>
                                                             </div>
                                                         </div>
@@ -776,10 +827,82 @@
         </div>
     </div>
 </div>
+<% if (ctx.change.status === auditConst.status.checked && ctx.session.sessionUser.accountId === ctx.change.uid) { %>
+    <% if (!authMobile && ctx.session.sessionUser.loginStatus === 0) { %>
+        <!--原报修订变更-->
+        <div class="modal fade" id="sp-down-revise" 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>
+                        <h5>您目前还没设置认证手机,请先设置。</h5>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                        <a href="/profile/sms" class="btn btn-sm btn-primary">去设置</a>
+                    </div>
+                </div>
+            </div>
+        </div>
+    <% } else { %>
+        <!--修订变更-->
+        <div class="modal fade" id="sp-down-revise" data-backdrop="static">
+            <div class="modal-dialog" role="document">
+                <form id="reviseForm" class="modal-content" method="post" action="/tender/<%- tender.id %>/change/project/check/revise" onsubmit="return false;">
+                    <div class="modal-header">
+                        <h5 class="modal-title">修订变更</h5>
+                    </div>
+                    <div class="modal-body">
+                        <h5>确认需要修订变更「<%= change.code %>」?</h5>
+                        <% if (ctx.session.sessionUser.loginStatus === 0) { %>
+                            <div class="form-group">
+                                <label>修订需要验证码确认,验证码将发送至尾号<%- authMobile.slice(-4) %>的手机</label>
+                                <div class="input-group input-group-sm mb-3">
+                                    <input class="form-control" type="text" readonly="readonly" name="code" placeholder="输入短信中的6位验证码" />
+                                    <div class="input-group-append">
+                                        <button class="btn btn-outline-secondary" type="button" id="get-code">获取验证码</button>
+                                    </div>
+                                </div>
+                            </div>
+                        <% } %>
+                    </div>
+                    <div class="modal-footer">
+                        <input type="hidden" name="cpid" value="<%= ctx.change.id %>">
+                        <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                        <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                        <button type="button" id="re-shenpi-btn2" class="btn btn-warning btn-sm" <% if (ctx.session.sessionUser.loginStatus === 0) { %>disabled<% } %>>确定修订</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+    <% } %>
+<% } %>
+<% if (ctx.change.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>
+<% } %>
 <script>
     const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
     const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
     const cur_uid = parseInt('<%- ctx.session.sessionUser.accountId %>');
+    const csrf = '<%= ctx.csrf %>';
     $('.sp-location-list').on('shown.bs.modal', function () {
         const scrollBox = $(this).find('div[class="col-8 modal-height-500"]');
         const bdiv = (scrollBox.offset() && scrollBox.offset().top) || 0;
@@ -824,4 +947,75 @@
             $('.modal-title').text('重新上报')
         }
     });
+
+    $('#re-shenpi-btn2').click(function () {
+        const data = {
+            cpid: parseInt('<%- ctx.change.id %>'),
+        };
+        <% if (ctx.session.sessionUser.loginStatus === 0) { %>
+        const code = $("#reviseForm input[name='code']").val();
+        if ($(this).hasClass('disabled')) {
+            return false;
+        }
+        if (code.length < 6) {
+            // alert('请填写正确的验证码');
+            toast('请填写正确的验证码', 'error');
+            return false;
+        }
+        data.code = code;
+        <% } %>
+        $.ajax({
+            url: '/tender/<%- tender.id %>/change/project/check/revise?_csrf_j=' + csrf,
+            type: 'post',
+            data: data,
+            dataTye: 'json',
+            success: function(response) {
+                if (response.err === 0) {
+                    window.location.href = response.url;
+                } else {
+                    toastr.error(response.msg);
+                }
+            }
+        });
+    })
+
+    $('#cancel-revise-btn').click(function () {
+        const data = {
+            cpid: parseInt('<%- ctx.change.id %>'),
+        };
+        $.ajax({
+            url: '/tender/<%- tender.id %>/change/project/cancel/revise?_csrf_j=' + csrf,
+            type: 'post',
+            data: data,
+            dataTye: 'json',
+            success: function(response) {
+                if (response.err === 0) {
+                    window.location.href = response.url;
+                } else {
+                    toastr.error(response.msg);
+                }
+            }
+        });
+    })
+
+    <% if (ctx.change && ctx.change.cancancel) { %>
+    $("#cancel-shenpi-btn").click(function () {
+        const data = {
+            cpid: parseInt('<%- ctx.change.id %>'),
+        };
+        $.ajax({
+            url: '/tender/<%- tender.id %>/change/project/cancel/audit?_csrf_j=' + csrf,
+            type: 'post',
+            data: data,
+            dataTye: 'json',
+            success: function (response) {
+                if (response.err === 0) {
+                    window.location.href = response.url;
+                } else {
+                    toastr.error(response.msg);
+                }
+            }
+        });
+    });
+    <% } %>
 </script>

+ 63 - 0
app/view/settle/audit_btn.ejs

@@ -0,0 +1,63 @@
+<div class="contarl-box">
+    <% if (ctx.settle.audit_status === auditConst.status.uncheck) { %>
+        <% if (ctx.session.sessionUser.accountId === ctx.settle.user_id) { %>
+            <a id="sub-sp-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm btn-block">上报审批</a>
+            <% if (ctx.settle.order === ctx.settle.highOrder) { %>
+                <a href="#del-qi" data-toggle="modal" data-target="#del-qi" class="btn btn-outline-danger btn-sm btn-block mt-5">删除本期</a>
+            <% } %>
+        <% } else { %>
+            <a id="sub-sp-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sub-sp" class="btn btn-outline-secondary btn-sm btn-block">上报中</a>
+        <% } %>
+
+    <% } %>
+
+    <% if (ctx.settle.audit_status === auditConst.status.checking) { %>
+        <% if (ctx.settle.curAuditorIds.indexOf(ctx.session.sessionUser.accountId) >= 0) { %>
+            <a id="sp-done-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sp-done" class="btn btn-success btn-sm btn-block">审批通过</a>
+            <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm btn-block">审批退回</a>
+        <% } else { %>
+            <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm btn-block">审批中</a>
+        <% } %>
+    <% } %>
+
+    <% if (ctx.settle.audit_status === auditConst.status.checked) { %>
+        <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm btn-block sp-list-btn">审批完成</a>
+        <% if (ctx.settle.finalAuditorIds && ctx.settle.finalAuditorIds.indexOf(ctx.session.sessionUser.accountId) >= 0 && ctx.settle.order === ctx.settle.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.settle.audit_status === auditConst.status.checkNo) { %>
+        <a href="#sp-list"  data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm btn-block text-muted sp-list-btn">审批退回</a>
+        <% if (ctx.session.sessionUser.accountId === ctx.settle.user_id) { %>
+            <a href="#sp-list" data-type="show" data-toggle="modal" data-target="#sp-list"  class="btn btn-primary btn-sm btn-block sp-list-btn">重新上报</a>
+            <% if (ctx.settle.order === ctx.settle.highOrder) { %>
+                <a href="#del-qi" data-toggle="modal" data-target="#del-qi" class="btn btn-outline-danger btn-sm btn-block mt-5">删除本期</a>
+            <% } %>
+        <% } %>
+    <% } %>
+
+    <% if (ctx.settle.audit_status === auditConst.status.checkNoPre) { %>
+        <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm btn-block text-muted">审批退回</a>
+        <% if (ctx.settle.curAuditorIds.indexOf(ctx.session.sessionUser.accountId) >= 0) { %>
+            <a id="sp-done-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sp-done" class="btn btn-success btn-sm btn-block">审批通过</a>
+            <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm btn-block">审批退回</a>
+        <% } %>
+    <% } %>
+
+    <% if (ctx.settle.cancancel) { %>
+        <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-cancel" class="btn btn-danger btn-sm btn-block">撤回</a>
+    <% } %>
+</div>
+<script>
+    $('.sp-list-btn').click(function () {
+        const type = $(this).data('type')
+        if (type === 'hide') {
+            $('.sp-list-item').hide()
+            $('.modal-title').text('审批流程')
+        } else {
+            $('.sp-list-item').show()
+            $('.modal-title').text('重新上报')
+        }
+    });
+</script>

+ 157 - 0
app/view/settle/index.ejs

@@ -0,0 +1,157 @@
+<% include ./settle_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./settle_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <div class="dropdown">
+                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                            <i class="fa fa-list-ol"></i> 显示层级
+                        </button>
+                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                            <a class="dropdown-item" name="showLevel" tag="1" href="javascript: void(0);">第一层</a>
+                            <a class="dropdown-item" name="showLevel" tag="2" href="javascript: void(0);">第二层</a>
+                            <a class="dropdown-item" name="showLevel" tag="3" href="javascript: void(0);">第三层</a>
+                            <a class="dropdown-item" name="showLevel" tag="4" href="javascript: void(0);">第四层</a>
+                            <a class="dropdown-item" name="showLevel" tag="5" href="javascript: void(0);">第五层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascript: void(0);">最底层</a>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="ml-auto">
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap pr-46">
+        <div class="c-header p-0">
+        </div>
+        <div class="row w-100 sub-content">
+            <div id="left-view" class="c-body" style="width: 100%">
+                <!--上部分-->
+                <div class="sjs-height-1" id="settle-ledger">
+                </div>
+                <!--下部分-->
+                <div class="bcontent-wrap" id="main-bottom">
+                    <div id="main-resize" class="resize-y" r-Type="height" div1="#settle-bills" div2="#main-bottom" store-id="settle-main" store-version="1.0.0" min="100"></div>
+                    <div class="bc-bar mb-1">
+                        <ul class="nav nav-tabs">
+                            <li class="nav-item">
+                                <a class="nav-link active" href="#">计量单元</a>
+                            </li>
+                        </ul>
+                    </div>
+                    <div class="sp-wrap" id="settle-pos">
+                    </div>
+                </div>
+            </div>
+            <div id="right-view" class="c-body" style="display: none; width: 33%;">
+                <div class="resize-x" id="right-spr" r-Type="width" div1="#left-view" div2="#right-view" title="调整大小" a-type="percent"><!--调整左右高度条--></div>
+                <div class="tab-content">
+                    <!--查找定位-->
+                    <div id="search" class="tab-pane tab-select-show">
+                        <div class="sjs-bar-1">
+                            <div class="input-group input-group-sm pb-1">
+                                <div class="input-group-prepend">
+                                    <div class="input-group-text">
+                                        <input type="radio" name="searchType" id="over"> 超计
+                                    </div>
+                                    <div class="input-group-text">
+                                        <input type="radio" name="searchType" id="empty"> 漏计
+                                    </div>
+                                </div>
+                                <input type="text" class="form-control form-control-sm" placeholder="可查找 项目节编号 / 清单编号 /名称" id="keyword">
+                                <div class="input-group-append">
+                                    <button class="btn btn-outline-secondary btn-sm" type="button" id="searchLedger">搜索</button>
+                                </div>
+                            </div>
+                        </div>
+                        <div id="search-result" class="sjs-sh-1">
+                        </div>
+                    </div>
+                    <div id="bills-tag" class="tab-pane tab-select-show">
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="side-menu">
+            <!--右侧菜单-->
+            <ul class="nav flex-column right-nav">
+                <li class="nav-item">
+                    <a class="nav-link" content="#search" href="javascript: void(0);">查找定位</a>
+                </li>
+                <li class="nav-item">
+                    <a class="nav-link" content="#bills-tag" href="javascript: void(0);">书签</a>
+                </li>
+                </li>
+            </ul>
+        </div>
+    </div>
+</div>
+<script>
+    const readOnly = <%- settle.readOnly %>;
+    const tenderInfo = JSON.parse(unescape('<%- escape(JSON.stringify(ctx.tender.info)) %>'));
+    const thousandth = <%- ctx.tender.info.display.thousandth %>;
+    const thirdParty = JSON.parse('<%- JSON.stringify(thirdParty) %>');
+    const billsSpreadSetting = {
+        cols: [
+            {title: '结算状态', colSpan: '1', rowSpan: '2', field: 'settle_status', hAlign: 1, width: 60, formatter: '@', readOnly: true},
+            {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 145, formatter: '@', readOnly: true, cellType: 'tree'},
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 70, formatter: '@', readOnly: true},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', readOnly: true},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: true, cellType: 'unit'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '本期合同结算|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_contract_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '本期数量变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_qc_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_qc_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '本期完成结算|数量', colSpan: '3|1', rowSpan: '1|1', field: 'end_gather_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|完成率(%)', colSpan: '1', rowSpan: '|1', field: 'end_final_1_percent', hAlign: 2, width: 80, readOnly: true, type: 'Number'},
+            {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
+            {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip', readOnly: true},
+            <% if (ctx.session.sessionProject.gxby) { %>
+            {title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            <% } %>
+            <% if (ctx.session.sessionProject.dagl) { %>
+            {title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            <% } %>
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        frozenColCount: 5,
+        frozenLineColor: '#93b5e4',
+    };
+    const posSpreadSetting = {
+        cols: [
+            {title: '选择', colSpan: '1', rowSpan: '2', field: 'selected', hAlign: 0, width: 145, formatter: '@'},
+            {title: '结算状态', colSpan: '1', rowSpan: '2', field: 'settle_status', hAlign: 1, width: 60, formatter: '@', readOnly: true},
+            {title: '计量单元', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 180, formatter: '@', readOnly: true},
+            {title: '位置', colSpan: '1', rowSpan: '2', field: 'position', hAlign: 0, width: 60, formatter: '@', readOnly: true},
+            {title: '累计计量|合同', colSpan: '3|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '|数量变更', colSpan: '|1', rowSpan: '|1', field: 'end_qc_qty', hAlign: 2, width: 80, type: 'Number', readOnly: true},
+            {title: '|完成', colSpan: '|1', rowSpan: '|1', field: 'end_gather_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
+            <% if (ctx.session.sessionProject.gxby) { %>
+            {title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            <% } %>
+            <% if (ctx.session.sessionProject.dagl) { %>
+            {title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            <% } %>
+        ],
+        emptyRows: 20,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        headColWidth: [30],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+    };
+</script>

+ 160 - 0
app/view/settle/select.ejs

@@ -0,0 +1,160 @@
+<% include ./settle_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./settle_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <div class="dropdown">
+                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                            <i class="fa fa-list-ol"></i> 显示层级
+                        </button>
+                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                            <a class="dropdown-item" name="showLevel" tag="1" href="javascript: void(0);">第一层</a>
+                            <a class="dropdown-item" name="showLevel" tag="2" href="javascript: void(0);">第二层</a>
+                            <a class="dropdown-item" name="showLevel" tag="3" href="javascript: void(0);">第三层</a>
+                            <a class="dropdown-item" name="showLevel" tag="4" href="javascript: void(0);">第四层</a>
+                            <a class="dropdown-item" name="showLevel" tag="5" href="javascript: void(0);">第五层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascript: void(0);">最底层</a>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="ml-auto">
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap pr-46">
+        <div class="c-header p-0">
+        </div>
+        <div class="row w-100 sub-content">
+            <div id="left-view" class="c-body" style="width: 100%">
+                <!--上部分-->
+                <div class="sjs-height-1" id="settle-bills">
+                </div>
+                <!--下部分-->
+                <div class="bcontent-wrap" id="main-bottom">
+                    <div id="main-resize" class="resize-y" r-Type="height" div1="#settle-bills" div2="#main-bottom" store-id="settle-main" store-version="1.0.0" min="100"></div>
+                    <div class="bc-bar mb-1">
+                        <ul class="nav nav-tabs">
+                            <li class="nav-item">
+                                <a class="nav-link active" href="#">计量单元</a>
+                            </li>
+                        </ul>
+                    </div>
+                    <div class="sp-wrap" id="settle-pos">
+                    </div>
+                </div>
+            </div>
+            <div id="right-view" class="c-body" style="display: none; width: 33%;">
+                <div class="resize-x" id="right-spr" r-Type="width" div1="#left-view" div2="#right-view" title="调整大小" a-type="percent"><!--调整左右高度条--></div>
+                <div class="tab-content">
+                    <!--查找定位-->
+                    <div id="search" class="tab-pane tab-select-show">
+                        <div class="sjs-bar-1">
+                            <div class="input-group input-group-sm pb-1">
+                                <div class="input-group-prepend">
+                                    <div class="input-group-text">
+                                        <input type="radio" name="searchType" id="over"> 超计
+                                    </div>
+                                    <div class="input-group-text">
+                                        <input type="radio" name="searchType" id="empty"> 漏计
+                                    </div>
+                                </div>
+                                <input type="text" class="form-control form-control-sm" placeholder="可查找 项目节编号 / 清单编号 /名称" id="keyword">
+                                <div class="input-group-append">
+                                    <button class="btn btn-outline-secondary btn-sm" type="button" id="searchLedger">搜索</button>
+                                </div>
+                            </div>
+                        </div>
+                        <div id="search-result" class="sjs-sh-1">
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="side-menu">
+            <!--右侧菜单-->
+            <ul class="nav flex-column right-nav">
+                <li class="nav-item">
+                    <a class="nav-link" content="#search" href="javascript: void(0);">查找定位</a>
+                </li>
+            </ul>
+        </div>
+    </div>
+    <div style="display: none">
+        <img src="/public/images/ellipsis_horizontal.png" id="ellipsis-icon" />
+        <img src="/public/images/icon-ok.png" id="icon-ok" />
+        <img src="/public/images/file_clip.png" id="rela-file-icon">
+        <img src="/public/images/file_clip_hover.png" id="rela-file-hover">
+    </div>
+</div>
+<script>
+    const readOnly = <%- settle.readOnly %>;
+    const tenderInfo = JSON.parse(unescape('<%- escape(JSON.stringify(ctx.tender.info)) %>'));
+    const thousandth = <%- ctx.tender.info.display.thousandth %>;
+    const thirdParty = JSON.parse('<%- JSON.stringify(thirdParty) %>');
+    const billsSpreadSetting = {
+        cols: [
+            {title: '选择', colSpan: '1', rowSpan: '2', field: 'selected', hAlign: 1, width: 30, formatter: '@', cellType: 'checkbox'},
+            {title: '结算状态', colSpan: '1', rowSpan: '2', field: 'settle_status', hAlign: 1, width: 60, formatter: '@', readOnly: true},
+            {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 145, formatter: '@', readOnly: true, cellType: 'tree'},
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 70, formatter: '@', readOnly: true},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', readOnly: true},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: true, cellType: 'unit'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '累计合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_contract_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '累计数量变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_qc_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_qc_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '累计完成计量|数量', colSpan: '3|1', rowSpan: '1|1', field: 'end_gather_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|完成率(%)', colSpan: '1', rowSpan: '|1', field: 'end_final_1_percent', hAlign: 2, width: 80, readOnly: true, type: 'Number'},
+            {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
+            {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip', readOnly: true},
+            <% if (ctx.session.sessionProject.gxby) { %>
+            {title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            <% } %>
+            <% if (ctx.session.sessionProject.dagl) { %>
+            {title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            <% } %>
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        frozenColCount: 5,
+        frozenLineColor: '#93b5e4',
+        readOnly,
+    };
+    const posSpreadSetting = {
+        cols: [
+            {title: '选择', colSpan: '1', rowSpan: '2', field: 'selected', hAlign: 0, width: 30, formatter: '@', cellType: 'checkbox'},
+            {title: '结算状态', colSpan: '1', rowSpan: '2', field: 'settle_status', hAlign: 1, width: 60, formatter: '@', readOnly: true},
+            {title: '计量单元', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 180, formatter: '@', readOnly: true},
+            {title: '位置', colSpan: '1', rowSpan: '2', field: 'position', hAlign: 0, width: 60, formatter: '@', readOnly: true},
+            {title: '累计计量|合同', colSpan: '3|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '|数量变更', colSpan: '|1', rowSpan: '|1', field: 'end_qc_qty', hAlign: 2, width: 80, type: 'Number', readOnly: true},
+            {title: '|完成', colSpan: '|1', rowSpan: '|1', field: 'end_gather_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
+            <% if (ctx.session.sessionProject.gxby) { %>
+            {title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            <% } %>
+            <% if (ctx.session.sessionProject.dagl) { %>
+            {title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            <% } %>
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        headColWidth: [30],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly,
+    };
+</script>

+ 0 - 0
app/view/settle/select_modal.ejs


+ 3 - 2
app/view/settle/settle_menu.ejs

@@ -1,9 +1,10 @@
 <div class="panel-sidebar" id="sub-menu">
-    <div class="sidebar-title" data-toggle="tooltip" data-placement="right" data-original-title="第<%- ctx.stage.order%>期 - 关联台账">
-        关联台账
+    <div class="sidebar-title" data-toggle="tooltip" data-placement="right" data-original-title="<%- ctx.tender.data.name %>">
+        <%- (ctx.tender.data.name.length > 15 ? ctx.tender.data.name.substring(0,15) + '...' : ctx.tender.data.name) %>
     </div>
     <div class="scrollbar-auto">
         <% include ./settle_menu_list.ejs %>
+        <% include ./audit_btn.ejs %>
         <div class="side-fold"><a href="javascript: void(0)" data-toggle="tooltip" data-placement="top" data-original-title="折叠侧栏" id="to-mini-menu"><i class="fa fa-upload fa-rotate-270"></i></a></div>
     </div>
     <script>

+ 5 - 2
app/view/settle/settle_menu_list.ejs

@@ -1,3 +1,6 @@
 <nav-menu title="返回" url="/tender/<%= ctx.tender.id %>/settle" tclass="text-primary" ml="1" icon="fa-chevron-left"></nav-menu>
-<nav-menu title="结算列表" url="/tender/<%= ctx.tender.id %>/settle/<%= ctx.settle.settle_order%>" ml="3" active="<%= (ctx.url.indexOf(ctx.settle.settle.order) > 0 ? 1 : -1) %>"></nav-menu>
-<nav-menu title="汇总台账" url="/tender/<%= ctx.tender.id %>/settle/gather" ml="3" active="<%= (ctx.url.indexOf('gather') > 0 ? 1 : -1) %>"></nav-menu>
+<% if (ctx.session.sessionUser.is_admin || ctx.session.sessionUser.accountId === ctx.settle.user_id) { %>
+<nav-menu title="结算部位" url="/tender/<%= ctx.tender.id %>/settle/<%= ctx.settle.settle_order%>/select" ml="3" active="<%= (ctx.url.indexOf('select') > 0 ? 1 : -1) %>"></nav-menu>
+<% } %>
+<nav-menu title="结算台账" url="/tender/<%= ctx.tender.id %>/settle/<%= ctx.settle.settle_order%>/ledger" ml="3" active="<%= (ctx.url.indexOf('ledger') > 0 ? 1 : -1) %>"></nav-menu>
+<nav-menu title="结算报表" url="/tender/<%= ctx.tender.id %>/settle/<%= ctx.settle.settle_order%>/report" ml="3" active="<%= (ctx.url.indexOf('report') > 0 ? 1 : -1) %>"></nav-menu>

+ 40 - 0
config/web.js

@@ -1346,6 +1346,46 @@ const JsFiles = {
                 ],
                 mergeFile: 'settle_list',
             },
+            select: {
+                files: [
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/spreadjs/sheets/v11/interop/gc.spread.excelio.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/shares/cs_tools.js',
+                    '/public/js/component/menu.js',
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/settle_select.js',
+
+                ],
+                mergeFile: 'settle_select',
+            },
+            ledger: {
+                files: [
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/spreadjs/sheets/v11/interop/gc.spread.excelio.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/shares/cs_tools.js',
+                    '/public/js/component/menu.js',
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/settle_ledger.js',
+
+                ],
+                mergeFile: 'settle',
+            }
         }
     },
 };

+ 23 - 11
sql/update.sql

@@ -1,13 +1,25 @@
-ALTER TABLE `zh_change_audit_list`
-ADD COLUMN `checked_amount` decimal(30, 8) NULL DEFAULT 0 COMMENT '审批变更后数量(整型)' AFTER `samount`,
-ADD COLUMN `checked_price` decimal(30, 8) NULL DEFAULT 0 COMMENT '审批变更后金额(整型)' AFTER `checked_amount`;
+CREATE TABLE `zh_change_project_history` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `cpid` int(11) NOT NULL COMMENT '变更立项id',
+  `info_json` json DEFAULT NULL COMMENT '内容json值',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='变更立项内容临时保存表,用于修订撤销与撤回';
 
-UPDATE `zh_change_audit_list` SET `checked_amount` = CONVERT(`samount`, DECIMAL) WHERE `samount` != '';
+CREATE TABLE `zh_change_apply_history` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `caid` int(11) NOT NULL COMMENT '变更申请id',
+  `info_json` json DEFAULT NULL COMMENT '内容json值',
+  `list_json` json DEFAULT NULL COMMENT '清单json值',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='变更申请内容临时保存表,用于修订撤销与撤回';
 
-ALTER TABLE `zh_sub_project_info`
-ADD COLUMN `reply_land`  varchar(20) NOT NULL DEFAULT '' COMMENT '批复用地' AFTER `quake_peak_value`,
-ADD COLUMN `occupy_land`  varchar(20) NOT NULL DEFAULT '' COMMENT '永久占用土地' AFTER `reply_land`,
-ADD COLUMN `demolish_building`  varchar(20) NOT NULL DEFAULT '' COMMENT '实际拆迁房屋' AFTER `occupy_land`;
-
-ALTER TABLE `zh_payment_detail`
-ADD COLUMN `bills_decimal`  varchar(100) NOT NULL DEFAULT '{"tp":2, "up": 2, "qty": 3}' COMMENT '小数位数' AFTER `in_time`;
+CREATE TABLE `zh_change_plan_history` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `cpid` int(11) NOT NULL COMMENT '变更方案id',
+  `info_json` json DEFAULT NULL COMMENT '内容json值',
+  `list_json` json DEFAULT NULL COMMENT '清单json值',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='变更方案内容临时保存表,用于修订撤销与撤回';

+ 13 - 0
sql/update20231218.sql

@@ -0,0 +1,13 @@
+ALTER TABLE `zh_change_audit_list`
+ADD COLUMN `checked_amount` decimal(30, 8) NULL DEFAULT 0 COMMENT '审批变更后数量(整型)' AFTER `samount`,
+ADD COLUMN `checked_price` decimal(30, 8) NULL DEFAULT 0 COMMENT '审批变更后金额(整型)' AFTER `checked_amount`;
+
+UPDATE `zh_change_audit_list` SET `checked_amount` = CONVERT(`samount`, DECIMAL) WHERE `samount` != '';
+
+ALTER TABLE `zh_sub_project_info`
+ADD COLUMN `reply_land`  varchar(20) NOT NULL DEFAULT '' COMMENT '批复用地' AFTER `quake_peak_value`,
+ADD COLUMN `occupy_land`  varchar(20) NOT NULL DEFAULT '' COMMENT '永久占用土地' AFTER `reply_land`,
+ADD COLUMN `demolish_building`  varchar(20) NOT NULL DEFAULT '' COMMENT '实际拆迁房屋' AFTER `occupy_land`;
+
+ALTER TABLE `zh_payment_detail`
+ADD COLUMN `decimal`  varchar(100) NOT NULL DEFAULT '{"tp":2, "up": 2, "qty": 3}' COMMENT '小数位数' AFTER `in_time`;