Przeglądaj źródła

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

Tony Kang 1 rok temu
rodzic
commit
355e2e388b
75 zmienionych plików z 4969 dodań i 16150 usunięć
  1. 19 0
      app/const/audit.js
  2. 2 0
      app/const/shenpi.js
  3. 2 0
      app/const/sms_alitemplate.js
  4. 11 0
      app/const/sms_type.js
  5. 2 0
      app/const/spec_3f.js
  6. 1 0
      app/const/tender_info.js
  7. 3 1
      app/const/wechat_template.js
  8. 197 6
      app/controller/change_controller.js
  9. 30 0
      app/controller/dashboard_controller.js
  10. 276 16
      app/controller/settle_controller.js
  11. 3 0
      app/controller/tender_controller.js
  12. 28 17
      app/controller/wap_controller.js
  13. 32 0
      app/controller/wechat_controller.js
  14. 17 6
      app/extend/helper.js
  15. 34 0
      app/lib/common.js
  16. 4 0
      app/lib/ledger.js
  17. 5 1
      app/lib/rpt_data_analysis.js
  18. 300 0
      app/lib/settle.js
  19. 1 1
      app/middleware/settle_check.js
  20. 4 1
      app/middleware/tender_check.js
  21. 1 1
      app/public/js/change_apply_information.js
  22. 3 3
      app/public/js/change_revise.js
  23. 2 2
      app/public/js/material.js
  24. 3 2
      app/public/js/path_tree.bak.js
  25. 3 3
      app/public/js/path_tree.js
  26. 173 20
      app/public/js/settle_ledger.js
  27. 93 10
      app/public/js/settle_select.js
  28. 6 5
      app/public/js/shares/cs_tools.js
  29. 1 1
      app/public/js/shares/new_tag.js
  30. 452 0
      app/public/js/shares/settle_audit.js
  31. 6 4
      app/public/js/shares/sjs_setting.js
  32. 279 0
      app/public/js/shares/tools_att.js
  33. 15 1
      app/router.js
  34. 10 8
      app/service/change.js
  35. 71 5
      app/service/change_apply_audit.js
  36. 1 1
      app/service/change_apply_history.js
  37. 86 0
      app/service/change_audit.js
  38. 87 5
      app/service/change_plan_audit.js
  39. 1 1
      app/service/change_plan_history.js
  40. 1 1
      app/service/change_project.js
  41. 69 5
      app/service/change_project_audit.js
  42. 28 0
      app/service/ledger_att.js
  43. 2 6
      app/service/ledger_tag.js
  44. 1 1
      app/service/message.js
  45. 6 0
      app/service/project_account.js
  46. 10 2
      app/service/report_memory.js
  47. 0 1
      app/service/rpt_stage_sum_memory.js
  48. 74 27
      app/service/settle.js
  49. 574 27
      app/service/settle_audit.js
  50. 187 0
      app/service/settle_audit_ass.js
  51. 2 2
      app/service/settle_select.js
  52. 6 0
      app/service/spec_msg.js
  53. 1 0
      app/service/stage_audit.js
  54. 4 0
      app/service/tender.js
  55. 9 5
      app/view/change/apply_information.ejs
  56. 90 6
      app/view/change/apply_information_modal.ejs
  57. 2 3
      app/view/change/information.ejs
  58. 415 11
      app/view/change/information_modal.ejs
  59. 9 5
      app/view/change/plan_information.ejs
  60. 90 6
      app/view/change/plan_information_modal.ejs
  61. 9 5
      app/view/change/project_information.ejs
  62. 92 8
      app/view/change/project_information_modal.ejs
  63. 867 0
      app/view/settle/audit_modal.ejs
  64. 30 12
      app/view/settle/index.ejs
  65. 3 0
      app/view/settle/modal.ejs
  66. 1 0
      app/view/settle/select.ejs
  67. 1 0
      app/view/settle/select_modal.ejs
  68. 23 0
      app/view/shares/upload_att.ejs
  69. 1 0
      app/view/tender/shenpi.ejs
  70. 74 0
      app/view/wap/msg.ejs
  71. 9 0
      config/config.default.js
  72. 6 0
      config/web.js
  73. 0 15896
      package-lock.json
  74. 1 0
      package.json
  75. 8 0
      sql/update.sql

+ 19 - 0
app/const/audit.js

@@ -620,6 +620,7 @@ const changeProject = (function() {
     statusString[status.checked] = '审批通过';
     statusString[status.checkNo] = '终止';
     statusString[status.back] = '审批退回';
+    statusString[status.checkAgain] = '重新审批';
     statusString[status.revise] = '修订';
     statusString[status.cancelRevise] = '撤销修订';
     statusString[status.checkCancel] = '撤回';
@@ -630,6 +631,7 @@ const changeProject = (function() {
     statusClass[status.checked] = 'text-success';
     statusClass[status.checkNo] = 'text-danger';
     statusClass[status.back] = 'text-warning';
+    statusClass[status.checkAgain] = 'text-warning';
     statusClass[status.revise] = 'text-warning';
     statusClass[status.cancelRevise] = 'text-success';
     statusClass[status.checkCancel] = 'text-warning';
@@ -642,6 +644,7 @@ const changeProject = (function() {
     auditString[status.checked] = '审批通过';
     auditString[status.checkNo] = '终止';
     auditString[status.back] = '审批退回';
+    auditString[status.checkAgain] = '重新审批';
     auditString[status.revise] = '修订';
     auditString[status.cancelRevise] = '撤销修订';
     auditString[status.checkCancel] = '撤回';
@@ -652,6 +655,7 @@ const changeProject = (function() {
     auditStringClass[status.checked] = 'text-success';
     auditStringClass[status.checkNo] = 'text-danger';
     auditStringClass[status.back] = 'text-warning';
+    auditStringClass[status.checkAgain] = 'text-warning';
     auditStringClass[status.revise] = 'text-warning';
     auditStringClass[status.cancelRevise] = 'text-success';
     auditStringClass[status.checkCancel] = 'text-warning';
@@ -662,6 +666,7 @@ const changeProject = (function() {
     auditProgress[status.checked] = '审批通过';
     auditProgress[status.checkNo] = '终止';
     auditProgress[status.back] = '审批退回';
+    auditProgress[status.checkAgain] = '重新审批';
     auditProgress[status.revise] = '修订中';
     auditProgress[status.cancelRevise] = '撤销修订';
     auditProgress[status.checkCancel] = '撤回';
@@ -672,6 +677,7 @@ const changeProject = (function() {
     auditProgressClass[status.checked] = 'text-success';
     auditProgressClass[status.checkNo] = 'text-danger';
     auditProgressClass[status.back] = 'text-warning';
+    auditProgressClass[status.checkAgain] = 'text-warning';
     auditProgressClass[status.revise] = 'text-warning';
     auditProgressClass[status.cancelRevise] = 'text-success';
     auditProgressClass[status.checkCancel] = 'text-warning';
@@ -729,6 +735,7 @@ const changeApply = (function() {
     statusString[status.checking] = '审批中';
     statusString[status.checked] = '审批通过';
     statusString[status.checkNo] = '审批退回';
+    statusString[status.checkAgain] = '重新审批';
     statusString[status.revise] = '修订';
     statusString[status.cancelRevise] = '撤销修订';
     statusString[status.checkCancel] = '撤回';
@@ -738,6 +745,7 @@ const changeApply = (function() {
     statusClass[status.checking] = 'text-warning';
     statusClass[status.checked] = 'text-success';
     statusClass[status.checkNo] = 'text-warning';
+    statusClass[status.checkAgain] = 'text-warning';
     statusClass[status.revise] = 'text-warning';
     statusClass[status.cancelRevise] = 'text-success';
     statusClass[status.checkCancel] = 'text-warning';
@@ -749,6 +757,7 @@ const changeApply = (function() {
     auditString[status.checking] = '审批中';
     auditString[status.checked] = '审批通过';
     auditString[status.checkNo] = '审批退回';
+    auditString[status.checkAgain] = '重新审批';
     auditString[status.revise] = '修订';
     auditString[status.cancelRevise] = '撤销修订';
     auditString[status.checkCancel] = '撤回';
@@ -758,6 +767,7 @@ const changeApply = (function() {
     auditStringClass[status.checking] = 'text-warning';
     auditStringClass[status.checked] = 'text-success';
     auditStringClass[status.checkNo] = 'text-warning';
+    auditStringClass[status.checkAgain] = 'text-warning';
     auditStringClass[status.revise] = 'text-warning';
     auditStringClass[status.cancelRevise] = 'text-success';
     auditStringClass[status.checkCancel] = 'text-warning';
@@ -767,6 +777,7 @@ const changeApply = (function() {
     auditProgress[status.checking] = '审批中';
     auditProgress[status.checked] = '审批通过';
     auditProgress[status.checkNo] = '审批退回';
+    auditProgress[status.checkAgain] = '重新审批';
     auditProgress[status.revise] = '修订中';
     auditProgress[status.cancelRevise] = '撤销修订';
     auditProgress[status.checkCancel] = '撤回';
@@ -776,6 +787,7 @@ const changeApply = (function() {
     auditProgressClass[status.checking] = 'text-warning';
     auditProgressClass[status.checked] = 'text-success';
     auditProgressClass[status.checkNo] = 'text-warning';
+    auditProgressClass[status.checkAgain] = 'text-warning';
     auditProgressClass[status.revise] = 'text-warning';
     auditProgressClass[status.cancelRevise] = 'text-success';
     auditProgressClass[status.checkCancel] = 'text-warning';
@@ -831,6 +843,7 @@ const changePlan = (function() {
     statusString[status.checking] = '审批中';
     statusString[status.checked] = '审批通过';
     statusString[status.checkNo] = '审批退回';
+    statusString[status.checkAgain] = '重新审批';
     statusString[status.revise] = '修订';
     statusString[status.cancelRevise] = '撤销修订';
     statusString[status.checkCancel] = '撤回';
@@ -840,6 +853,7 @@ const changePlan = (function() {
     statusClass[status.checking] = 'text-warning';
     statusClass[status.checked] = 'text-success';
     statusClass[status.checkNo] = 'text-warning';
+    statusClass[status.checkAgain] = 'text-warning';
     statusClass[status.revise] = 'text-warning';
     statusClass[status.cancelRevise] = 'text-success';
     statusClass[status.checkCancel] = 'text-warning';
@@ -851,6 +865,7 @@ const changePlan = (function() {
     auditString[status.checking] = '审批中';
     auditString[status.checked] = '审批通过';
     auditString[status.checkNo] = '审批退回';
+    auditString[status.checkAgain] = '重新审批';
     auditString[status.revise] = '修订';
     auditString[status.cancelRevise] = '撤销修订';
     auditString[status.checkCancel] = '撤回';
@@ -860,6 +875,7 @@ const changePlan = (function() {
     auditStringClass[status.checking] = 'text-warning';
     auditStringClass[status.checked] = 'text-success';
     auditStringClass[status.checkNo] = 'text-warning';
+    auditStringClass[status.checkAgain] = 'text-warning';
     auditStringClass[status.revise] = 'text-warning';
     auditStringClass[status.cancelRevise] = 'text-success';
     auditStringClass[status.checkCancel] = 'text-warning';
@@ -869,6 +885,7 @@ const changePlan = (function() {
     auditProgress[status.checking] = '审批中';
     auditProgress[status.checked] = '审批通过';
     auditProgress[status.checkNo] = '审批退回';
+    auditProgress[status.checkAgain] = '重新审批';
     auditProgress[status.revise] = '修订中';
     auditProgress[status.cancelRevise] = '撤销修订';
     auditProgress[status.checkCancel] = '撤回';
@@ -878,6 +895,7 @@ const changePlan = (function() {
     auditProgressClass[status.checking] = 'text-warning';
     auditProgressClass[status.checked] = 'text-success';
     auditProgressClass[status.checkNo] = 'text-warning';
+    auditProgressClass[status.checkAgain] = 'text-warning';
     auditProgressClass[status.revise] = 'text-warning';
     auditProgressClass[status.cancelRevise] = 'text-success';
     auditProgressClass[status.checkCancel] = 'text-warning';
@@ -927,6 +945,7 @@ const pushType = {
     changeProject: 7,
     changeApply: 8,
     changePlan: 9,
+    settle: 10,
 };
 
 module.exports = {

+ 2 - 0
app/const/shenpi.js

@@ -13,6 +13,7 @@ const sp_type = {
     ledger: 2,
     revise: 3,
     stage: 4,
+    settle: 7,
     change: 5,
     material: 6,
 };
@@ -29,6 +30,7 @@ const sp_lc = [
     { code: 'ledger', type: sp_type.ledger, name: '台账审批' },
     { code: 'revise', type: sp_type.revise, name: '台账修订' },
     { code: 'stage', type: sp_type.stage, name: '计量期审批' },
+    { code: 'settle', type: sp_type.settle, name: '结算期审批' },
     { code: 'change', type: sp_type.change, name: '工程变更审批' },
     { code: 'material', type: sp_type.material, name: '材料调差审批' },
 ];

+ 2 - 0
app/const/sms_alitemplate.js

@@ -25,6 +25,8 @@ const smsTemplate = {
     revise_result: 'SMS_193140537', // 项目:${project},标段:${number},台账修订审批${status},请登录系统处理。
     revise_result2: 'SMS_192985611', // 项目:${project},标段:${number},台账修订审批${status}。
     revise_report: 'SMS_192985614', // 项目:${project},标段:${number},台账修订已上报。
+    // todo 结算期短信通知
+    // settle_check: 'SMS_193244645', // 项目:${project},标段:${number},第${qi}期,需要您审批。在线审批https://scn.ink/${code}
 };
 const smsStatus = {
     back: '退回',

+ 11 - 0
app/const/sms_type.js

@@ -14,6 +14,7 @@ const smsConst = {
     XD: 'XD',
     TC: 'TC',
     YFK: 'YFK',
+    JS: 'JS',
 };
 const judgeConst = {
     approval: 1,
@@ -80,6 +81,16 @@ const smsType = {
             { title: '审批结果', value: 2 },
         ],
     },
+    JS: {
+        name: '结算审批',
+        path: '',
+        wechat: true,
+        sms: true,
+        children: [
+            { title: '需要我审批', value: 1 },
+            { title: '审批结果', value: 2 },
+        ],
+    },
 };
 
 module.exports = {

+ 2 - 0
app/const/spec_3f.js

@@ -12,6 +12,8 @@ const pushTiming = [
     { value:'ledger.checked', name: '台账-审批通过' },
     { value:'stage.checked', name: '期-审批通过' },
     { value:'stage.flow', name: '期-上报/审批' },
+    { value:'settle.checked', name: '结算期-审批通过' },
+    { value:'settle.flow', name: '结算期-上报/审批' },
     { value:'revise.checked', name: '台账修订-审批通过' },
     { value:'change.checked', name: '变更令-审批通过' },
     { value:'report.file', name: '报表-推送归档' },

+ 1 - 0
app/const/tender_info.js

@@ -178,6 +178,7 @@ const defaultInfo = {
         ledger: 1,
         revise: 1,
         stage: 1,
+        settle: 1,
         change: 1,
         material: 1,
     },

+ 3 - 1
app/const/wechat_template.js

@@ -14,6 +14,7 @@ const template = {
     revise: 3,
     material: 4,
     advance: 5,
+    settle: 6,
 };
 const templateId = {
     stage: '5vU3WmR90yDajbs4LWIWH4OQhunYlS1HXTiesIGxrsk',
@@ -22,6 +23,7 @@ const templateId = {
     revise: '9Ul3KFxvYQGfT6wbGGjqqnGR0zHtx0BHKs9sXj4Ii44',
     material: 'Y9ov80rev3LHcoM2hOQE6jrK_5xZsqE-PZ0Z6HS9VGA',
     advance: 'rgZHkyiLzrqaSGnQ2nSCCrOdUz2RJJZ_JA34L_MnQik',
+    settle: '5vU3WmR90yDajbs4LWIWH4OQhunYlS1HXTiesIGxrsk', // todo 暂使用计量期模板
 };
 const status = {
     check: '待审批',
@@ -42,7 +44,7 @@ const changeType = {
     project: '变更立项',
     apply: '变更申请',
     plan: '变更方案',
-}
+};
 
 module.exports = {
     template,

+ 197 - 6
app/controller/change_controller.js

@@ -634,6 +634,10 @@ module.exports = app => {
                 const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
                 const auth_mobile = pa.auth_mobile;
                 const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
+
+                if (change && change.status !== audit.flow.status.checked && ctx.session.sessionUser.is_admin) {
+                    change.auditors2 = await ctx.service.changeAudit.getListGroupByWithoutYB(change.cid, change.times);
+                }
                 const renderData = {
                     tender,
                     change,
@@ -680,11 +684,7 @@ module.exports = app => {
                 }
                 renderData.changeUsedData = useChangeUsedData;
                 renderData.stageChangeNum = this.ctx.helper.sum(changeUsedData.map(x => { return Math.abs(x.qty); }));
-
-                // 根据auditStatus状态获取的不同的数据
-                if (auditStatus === 1 || auditStatus === 2 || auditStatus === 9) {
-                    renderData.changeUnits = changeConst.units;
-                    // renderData.accountGroup = accountGroup;
+                if (auditStatus === 1 || auditStatus === 2 || auditStatus === 9 || ctx.session.sessionUser.is_admin) {
                     // 获取所有项目参与者
                     const accountList = await ctx.service.projectAccount.getAllDataByCondition({
                         where: { project_id: ctx.session.sessionProject.id, enable: 1 },
@@ -695,6 +695,10 @@ module.exports = app => {
                         const groupList = accountList.filter(item1 => item1.company === item.name);
                         return { groupName: item.name, groupList };
                     });
+                }
+                // 根据auditStatus状态获取的不同的数据
+                if (auditStatus === 1 || auditStatus === 2 || auditStatus === 9) {
+                    renderData.changeUnits = changeConst.units;
                     // 重新上报获取审批流程
                     if (auditStatus === 2 || auditStatus === 9) {
                         const auditList2 = await ctx.service.changeAudit.getListByBack(change.cid, change.times);
@@ -1165,6 +1169,11 @@ module.exports = app => {
                     throw '变更令数据错误';
                 }
                 const status = parseInt(ctx.request.body.status);
+                // 判断是否到你审批,如果不是则无法审批
+                const curAuditor = await ctx.service.changeAudit.getCurAuditor(changeData.cid, changeData.times);
+                if (!curAuditor || (curAuditor && curAuditor.uid !== ctx.session.sessionUser.accountId)) {
+                    throw '该变更令当前您无权操作';
+                }
                 let result = false;
                 const pid = this.ctx.session.sessionProject.id;
                 switch (status) {
@@ -1188,6 +1197,7 @@ module.exports = app => {
                 ctx.redirect(ctx.request.header.referer);
             } catch (err) {
                 console.log(err);
+                ctx.session.postError = err.toString();
                 ctx.redirect(ctx.request.header.referer);
             }
         }
@@ -1523,7 +1533,7 @@ module.exports = app => {
                     throw '变更令数据错误';
                 }
                 // 获取终审
-                const auditInfo = (await this.ctx.service.changeAudit.getAllDataByCondition({ where: { cid: changeData.cid }, orders: [['usort', 'desc']], limit: 1, offset: 0 }))[0];
+                const auditInfo = await ctx.service.changeAudit.getAuditorByStatus(changeData.cid, changeData.times, audit.flow.status.checked);
                 if (changeData.status !== audit.flow.status.checked || ctx.session.sessionUser.accountId !== auditInfo.uid) {
                     throw '您无权进行该操作';
                 }
@@ -1651,6 +1661,31 @@ module.exports = app => {
             }
         }
 
+        async saveAudit(ctx) {
+            try {
+                // if (ctx.change.revising) {
+                //     throw '台账修订中,请勿修改提交期数据';
+                // }
+                const data = JSON.parse(ctx.request.body.data);
+                if (ctx.session.sessionUser.is_admin && ctx.change.status !== audit.flow.status.checked) {
+                    await ctx.service.changeAudit.saveAudit(ctx.change.cid, ctx.change.times, data);
+                    const auditors = await ctx.service.changeAudit.getListGroupByTimes(ctx.change.cid, ctx.change.times);
+                    ctx.body = { err: 0, msg: '', data: auditors };
+                } else {
+                    throw '您无权进行该操作';
+                }
+            } catch (err) {
+                this.log(err);
+                // ctx.session.postError = err.toString();
+                // ctx.redirect(ctx.request.header.referer);
+                ctx.body = {
+                    err: 1,
+                    // url: ctx.request.header.referer,
+                    msg: err,
+                };
+            }
+        }
+
         /**
          * 获取变更清单
          * @param ctx
@@ -2652,6 +2687,58 @@ module.exports = app => {
         }
 
         /**
+         * 变更立项重新审批
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async checkProjectAgain(ctx) {
+            try {
+                // 获取终审
+                const auditInfo = await this.ctx.service.changeProjectAudit.getAuditorByStatus(ctx.change.id, audit.changeProject.status.checked);
+                if (ctx.change.status !== audit.changeProject.status.checked || ctx.session.sessionUser.accountId !== auditInfo.aid) {
+                    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.checkAgain(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}
@@ -3373,6 +3460,58 @@ module.exports = app => {
         }
 
         /**
+         * 变更申请重新审批
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async checkApplyAgain(ctx) {
+            try {
+                // 获取终审
+                const auditInfo = await this.ctx.service.changeApplyAudit.getAuditorByStatus(ctx.change.id, audit.changeApply.status.checked);
+                if (ctx.change.status !== audit.changeApply.status.checked || ctx.session.sessionUser.accountId !== auditInfo.aid) {
+                    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.checkAgain(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}
@@ -4099,6 +4238,58 @@ module.exports = app => {
         }
 
         /**
+         * 变更申请重新审批
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async checkPlanAgain(ctx) {
+            try {
+                // 获取终审
+                const auditInfo = await this.ctx.service.changePlanAudit.getAuditorByStatus(ctx.change.id, audit.changePlan.status.checked);
+                if (ctx.change.status !== audit.changePlan.status.checked || ctx.session.sessionUser.accountId !== auditInfo.aid) {
+                    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.checkAgain(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}

+ 30 - 0
app/controller/dashboard_controller.js

@@ -12,6 +12,7 @@ const auditConst = require('../const/audit');
 const officeList = require('../const/cld_office').list;
 const maintainConst = require('../const/maintain');
 const typeColMap = require('../const/advance').typeColMap;
+const moment = require('moment');
 
 module.exports = app => {
 
@@ -207,6 +208,35 @@ module.exports = app => {
                 ctx.helper.validate(rule);
                 const result = await ctx.service.message.save(id, ctx.request.body, ctx.session.sessionUser, ctx.session.sessionProject.id);
                 if (result) {
+                    // 新增的项目通知会发送微信模版消息通知客户
+                    if (id === 0) {
+                        // 获取该项目所有的openid,发送信息
+                        const wechats = await ctx.service.projectAccount.getOpenIdListByPid(ctx.session.sessionProject.id);
+                        if (wechats.length > 0) {
+                            const msgInfo = await ctx.service.message.getDataById(result);
+                            const projectData = await ctx.service.project.getDataById(ctx.session.sessionProject.id);
+                            // 绑定成功通知
+                            const templateId = 'VKUo4us4lt2dQY0EaaJxcui2jkjmriN3A0K7i4kpZwY';
+                            const url = ctx.protocol + '://' + ctx.host + '/wap/dashboard/msg/' + msgInfo.id;
+                            const msgData = {
+                                thing21: {
+                                    value: projectData.code,
+                                },
+                                thing2: {
+                                    value: ctx.helper.contentChange(msgInfo.title),
+                                },
+                                thing8: {
+                                    value: msgInfo.creator,
+                                },
+                                time3: {
+                                    value: moment(msgInfo.release_time * 1000).format('YYYY-MM-DD'),
+                                },
+                            };
+                            for (const wx of wechats) {
+                                await app.wechat.api.sendTemplate(wx.wx_openid, templateId, url, '', msgData);
+                            }
+                        }
+                    }
                     ctx.redirect('/dashboard/msg/list');
                 }
             } catch (error) {

+ 276 - 16
app/controller/settle_controller.js

@@ -17,6 +17,9 @@ const measureType = tenderConst.measureType;
 const spreadConst = require('../const/spread');
 const spreadSetting = require('../lib/spread_setting');
 
+const path = require('path');
+const sendToWormhole = require('stream-wormhole');
+
 module.exports = app => {
 
     class SettleController extends app.BaseController {
@@ -53,12 +56,12 @@ module.exports = app => {
                     if (s.status === auditConst.settle.status.uncheck) {
                         s.curAuditors = [];
                     } else if (s.status === auditConst.settle.status.checkNo) {
-                        s.curAuditors = await ctx.service.settleAudit.getAuditorsByStatus(sid, s.status, s.times - 1);
+                        s.curAuditors = await ctx.service.settleAudit.getAuditorsByStatus(sid, s.status, s.audit_times - 1);
                     } else {
-                        s.curAuditors = await ctx.service.settleAudit.getAuditorsByStatus(s.id, s.status, s.times);
+                        s.curAuditors = await ctx.service.settleAudit.getAuditorsByStatus(s.id, s.status, s.audit_times);
                     }
                     if (s.status === auditConst.settle.status.checkNoPre) {
-                        s.curAuditorsPre = await ctx.service.stageAudit.getAuditorsByStatus(s.id, auditConst.settle.status.checking, s.times);
+                        s.curAuditorsPre = await ctx.service.settleAudit.getAuditorsByStatus(s.id, auditConst.settle.status.checking, s.audit_times);
                     }
                 }
                 renderData.checkedStageCount = await ctx.service.stage.count({ tid: ctx.tender.id, status: auditConst.stage.status.checked });
@@ -166,11 +169,6 @@ module.exports = app => {
             const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
             data.authMobile = pa.auth_mobile;
 
-            const loadAccount = ctx.session.sessionUser.is_admin
-                ? true
-                : ctx.session.sessionUser.accountId === ctx.settle.user_id && ([auditConst.settle.status.uncheck, auditConst.settle.status.checkNo].indexOf(ctx.settle.audit_status) >= 0);
-            if (!loadAccount) return data;
-
             // 获取所有项目参与者
             const accountList = await ctx.service.projectAccount.getAllDataByCondition({
                 where: { project_id: ctx.session.sessionProject.id, enable: 1 },
@@ -200,7 +198,6 @@ module.exports = app => {
                 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');
             }
@@ -215,12 +212,15 @@ module.exports = app => {
                 renderData.whiteList = this.ctx.app.config.multipart.whitelist;
                 await this.layout('settle/index.ejs', renderData, 'settle/modal.ejs');
             } catch(err) {
-                console.log(err);
                 ctx.log(err);
                 ctx.redirect('/tender/' + ctx.tender.id + '/settle');
             }
         }
 
+        checkNeedPretreadData(ctx) {
+            return ctx.settle.audit_status === auditConst.settle.status.uncheck || (ctx.settle.audit_status === auditConst.settle.status.checkNo && !ctx.settle.readOnly);
+        }
+
         async _loadLatestStage(ctx) {
             if (ctx.settle.latestStage) return;
             ctx.settle.latestStage = ctx.settle.final_sid
@@ -232,22 +232,67 @@ module.exports = app => {
                 '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'];
+            this.ledgerExtraColumn = [];
+            if (this.ctx.session.sessionProject.gxby) this.ledgerExtraColumn.push('gxby_status', 'gxby_url', 'gxby_limit');
+            if (this.ctx.session.sessionProject.dagl) this.ledgerExtraColumn.push('dagl_status', 'dagl_url', 'dagl_limit');
             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 } });
+            const extraData = this.ledgerExtraColumn.length > 0 ? await ctx.service.ledgerExtra.getData(ctx.tender.id, this.ledgerExtraColumn) : [];
             this.ctx.helper.assignRelaData(ledgerData, [
                 { data: endStageData, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'qc_minus_qty'], prefix: 'end_', relaId: 'lid' },
+                { data: extraData, fields: this.ledgerExtraColumn, prefix: '', relaId: 'id' },
             ]);
             return ledgerData;
         }
         async _getStagePosData(ctx) {
             this.posColumn = ['id', 'tid', 'lid', 'name', 'position', 'porder', 'quantity', 'add_stage_order', 'drawing_code'];
+            this.posExtraColumn = [];
+            if (this.ctx.session.sessionProject.gxby) this.posExtraColumn.push('gxby_status', 'gxby_url', 'gxby_limit');
+            if (this.ctx.session.sessionProject.dagl) this.posExtraColumn.push('dagl_status', 'dagl_url', 'dagl_limit');
             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 } });
+            const extraData = this.posExtraColumn.length > 0 ? await ctx.service.posExtra.getData(ctx.tender.id, this.posExtraColumn) : [];
             this.ctx.helper.assignRelaData(posData, [
                 { data: endStageData, fields: ['contract_qty', 'qc_qty', 'qc_minus_qty'], prefix: 'end_', relaId: 'pid' },
+                { data: extraData, fields: this.posExtraColumn, prefix: '', relaId: 'id'},
             ]);
             return posData;
         }
+        async _loadPretreatSettleData(ctx) {
+            if (this.checkNeedPretreadData(ctx)) {
+                this.pretreadSettle = {};
+                const Pretreat = require('../lib/settle');
+                const pretreadObj = new Pretreat(ctx);
+                [this.pretreadSettle.settleBills, this.pretreadSettle.settlePos] = await pretreadObj.doSettle(this.ctx.settle);
+            }
+        }
+        async _loadSettleBills(ctx) {
+            const billsData = this.checkNeedPretreadData(ctx)
+                ? this.pretreadSettle.settleBills
+                : await ctx.service.settleBills.getAllDataByCondition({ where: { settle_id: ctx.settle.id } });
+
+            this.ledgerExtraColumn = [];
+            if (this.ctx.session.sessionProject.gxby) this.ledgerExtraColumn.push('gxby_status', 'gxby_url', 'gxby_limit');
+            if (this.ctx.session.sessionProject.dagl) this.ledgerExtraColumn.push('dagl_status', 'dagl_url', 'dagl_limit');
+            const extraData = this.ledgerExtraColumn.length > 0 ? await ctx.service.ledgerExtra.getData(ctx.tender.id, this.ledgerExtraColumn) : [];
+            this.ctx.helper.assignRelaData(billsData, [
+                { data: extraData, fields: this.ledgerExtraColumn, prefix: '', relaId: 'id'},
+            ], 'lid');
+            return billsData;
+        }
+        async _loadSettlePos(ctx) {
+            const posData = this.checkNeedPretreadData(ctx)
+                ? this.pretreadSettle.settlePos
+                : await ctx.service.settlePos.getAllDataByCondition({ where: { settle_id: ctx.settle.id } });
+            this.posExtraColumn = [];
+            if (this.ctx.session.sessionProject.gxby) this.posExtraColumn.push('gxby_status', 'gxby_url', 'gxby_limit');
+            if (this.ctx.session.sessionProject.dagl) this.posExtraColumn.push('dagl_status', 'dagl_url', 'dagl_limit');
+            const extraData = this.posExtraColumn.length > 0 ? await ctx.service.posExtra.getData(ctx.tender.id, this.posExtraColumn) : [];
+            this.ctx.helper.assignRelaData(posData, [
+                { data: extraData, fields: this.posExtraColumn, prefix: '', relaId: 'id'},
+            ], 'pid');
+            return posData;
+        }
         async _loadSettleDataByKey(ctx, key, hpack) {
             switch (key) {
                 case 'stageBills':
@@ -263,10 +308,12 @@ module.exports = app => {
                     const settleChange = await ctx.service.stageChangeFinal.getUnSettleChangeData(ctx.settle.latestStage);
                     return hpack ? [ctx.helper.hpackArr(settleChange), key] : [settleChange, ''];
                 case 'settleBills':
-                    const settleBills = await ctx.service.settleBills.getAllDataByCondition({ where: { settle_id: ctx.settle.id } });
+                    await this._loadPretreatSettleData(ctx);
+                    const settleBills = await this._loadSettleBills(ctx);
                     return hpack ? [ctx.helper.hpackArr(settleBills), 'settleBills'] : [settleBills, ''];
                 case 'settlePos':
-                    const settlePos = await ctx.service.settlePos.getAllDataByCondition({ where: { settle_id: ctx.settle.id } });
+                    await this._loadPretreatSettleData(ctx);
+                    const settlePos = await this._loadSettlePos(ctx);
                     return hpack ? [ctx.helper.hpackArr(settlePos), 'settlePos'] : [settlePos, ''];
                 case 'settleSelect':
                     const settleSelect = await ctx.service.settleSelect.getAllDataByCondition({ where: { settle_id: ctx.settle.id } });
@@ -274,11 +321,13 @@ module.exports = app => {
                 case 'tag':
                     const tag = await ctx.service.ledgerTag.getDatas(ctx.tender.id, -1, ctx.settle.id);
                     return [tag, ''];
+                case 'att':
+                    const att = await ctx.service.ledgerAtt.getViewData(ctx.tender.id, ctx.settle.id);
+                    return [att, ''];
                 default:
                     return [null, ''];
             }
         }
-
         async loadSettleData(ctx) {
             try {
                 const data = JSON.parse(ctx.request.body.data);
@@ -298,6 +347,15 @@ module.exports = app => {
             }
         }
 
+        _checkSettleCanModify(ctx, isCheck = false) {
+            // 检查登录用户,是否可操作
+            if (ctx.settle.readOnly) {
+                if (!isCheck || !ctx.settle.canCheck) throw '该结算期当前您无权操作';
+            }
+            // if (ctx.settle.assist && ctx.settle.assist.confirm) {
+            //     throw '协同数据已确认,如需修改,请撤销上报或重新审批';
+            // }
+        }
         async updateSelect(ctx) {
             try {
                 const data = JSON.parse(ctx.request.body.data);
@@ -310,18 +368,220 @@ module.exports = app => {
             }
         }
 
+        async uploadFile(ctx) {
+            let stream;
+            try {
+                const parts = ctx.multipart({ autoFields: true });
+                const files = [];
+                let index = 0;
+                const extra_upload = ctx.settle.audit_status === auditConst.settle.status.checked;
+                stream = await parts();
+                while (stream) {
+                    // 判断用户是否选择上传文件
+                    if (!stream.filename) {
+                        throw '请选择上传的文件!';
+                    }
+                    const fileInfo = path.parse(stream.filename);
+                    const create_time = Date.parse(new Date()) / 1000;
+                    const filepath = `${ctx.session.sessionProject.id}/${ctx.tender.id}/settle/${ctx.settle.settle_order}/att_${create_time + index.toString() + fileInfo.ext}`;
+
+                    await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + filepath, stream);
+
+                    if (stream) await sendToWormhole(stream);
+
+                    // 保存数据到att表
+                    const fileData = {
+                        tid: ctx.tender.id,
+                        settle_id: ctx.settle.id,
+                        settle_order: ctx.settle.settle_order,
+                        in_time: create_time,
+                        filename: fileInfo.name,
+                        fileext: fileInfo.ext,
+                        filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size,
+                        filepath,
+                        extra_upload,
+                    };
+                    const result = await ctx.service.ledgerAtt.save(parts.field, fileData, ctx.session.sessionUser.accountId);
+                    if (!result) throw '保存数据失败';
+                    const attData = await ctx.service.ledgerAtt.getViewDataByFid(result.insertId);
+                    files.length !== 0 ? files.unshift(attData) : files.push(attData);
+                    ++index;
+                    if (Array.isArray(parts.field.size) && index < parts.field.size.length) {
+                        stream = await parts();
+                    } else {
+                        stream = undefined;
+                    }
+                }
+                ctx.body = { err: 0, msg: '', data: files };
+            } catch (err) {
+                ctx.log(err);
+                // 失败需要消耗掉stream 以防卡死
+                if (stream) await sendToWormhole(stream);
+                ctx.ajaxErrorBody(err, '上传文件失败');
+            }
+        }
+        async deleteFile(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const fileInfo = await ctx.service.ledgerAtt.getDataById(data.id);
+                if (!fileInfo || !Object.keys(fileInfo).length) throw '该文件不存在';
+                if (!fileInfo.extra_upload && ctx.settle.status === auditConst.settle.status.checked) throw '无权限删除';
+
+                if (fileInfo !== undefined && fileInfo !== '') {
+                    // 先删除文件
+                    await ctx.app.fujianOss.delete(ctx.app.config.fujianOssFolder + fileInfo.filepath);
+                    // 再删除数据库
+                    await ctx.service.ledgerAtt.deleteById(data.id);
+                } else {
+                    throw '不存在该文件';
+                }
+                ctx.body = { err: 0, msg: '', data: null}
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '删除文件失败');
+            }
+        }
+        async saveFile(ctx) {
+            let stream;
+            try {
+                stream = await ctx.getFileStream({ requireFile: false });
+                let fileData = {};
+                if (stream.filename !== undefined) {
+                    const create_time = Date.parse(new Date()) / 1000;
+                    const fileInfo = path.parse(stream.filename);
+                    const filepath = `${ctx.session.sessionProject.id}/${ctx.tender.id}/settle/${ctx.settle.settle_order}/att_${create_time + fileInfo.ext}`;
+
+                    // 保存文件
+                    await ctx.oss.put(ctx.app.config.fujianOssFolder + filepath, stream);
+                    // 保存数据到att表
+                    fileData = {
+                        filesize: stream.fields.size,
+                        filepath,
+                    };
+                }
+                const org = await ctx.service.ledgerAtt.getDataById(stream.fields.id);
+                const result = await ctx.service.ledgerAtt.updateByID(stream.fields, fileData);
+                if (!result) throw '保存数据失败';
+                // 删除原附件
+                await ctx.app.fujianOss.delete(ctx.app.config.fujianOssFolder + org.filepath);
+                const attData = await ctx.service.ledgerAtt.getViewDataByFid(stream.fields.id);
+                ctx.body = { err: 0, msg: '', data: attData };
+                responseData.data = attData;
+            } catch (err) {
+                ctx.log(err);
+                // 失败需要消耗掉stream 以防卡死
+                if (stream) await sendToWormhole(stream);
+                ctx.ajaxErrorBody(err, '保存数据失败');
+            }
+        }
+
+        /**
+         * 添加审批人
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async addAudit(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const id = this.app._.toInteger(data.auditorId);
+                if (isNaN(id) || id <= 0) throw '参数错误';
+
+                // 检查权限等
+                if (ctx.settle.user_id !== ctx.session.sessionUser.accountId) throw '您无权添加审核人';
+                if (ctx.settle.audit_status !== auditConst.settle.status.uncheck && ctx.settle.audit_status !== auditConst.settle.status.checkNo) {
+                    throw '当前不允许添加审核人';
+                }
+
+                // 检查审核人是否已存在
+                const exist = await ctx.service.settleAudit.getDataByCondition({ settle_id: ctx.settle.id, audit_times: ctx.settle.audit_times, audit_id: id });
+                if (exist) throw '该审核人已存在,请勿重复添加';
+
+                const auditorInfo = await this.ctx.service.projectAccount.getDataById(id);
+                if (!auditorInfo) throw '添加的审批人不存在';
+
+                const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition({ tid: ctx.tender.id, sp_type: shenpiConst.sp_type.settle, sp_status: shenpiConst.sp_status.gdzs });
+                const is_gdzs = shenpiInfo && ctx.tender.info.shenpi.settle === shenpiConst.sp_status.gdzs ? 1 : 0;
+                const result = await ctx.service.settleAudit.addAuditor(ctx.settle.id, auditorInfo, ctx.settle.audit_times, is_gdzs);
+                if (!result) throw '添加审核人失败';
+
+                const auditors = await ctx.service.settleAudit.getAuditorGroup(ctx.settle.id, ctx.settle.audit_times);
+                ctx.body = { err: 0, msg: '', data: auditors };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+        /**
+         * 移除审批人
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async deleteAudit(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const id = data.auditorId instanceof Number ? data.auditorId : this.app._.toNumber(data.auditorId);
+                if (isNaN(id) || id <= 0) throw '参数错误';
+
+                const result = await ctx.service.settleAudit.deleteAuditor(ctx.settle.id, id, ctx.settle.audit_times);
+                if (!result) throw '移除审核人失败';
+
+                const auditors = await ctx.service.settleAudit.getAuditors(ctx.settle.id, ctx.settle.audit_times);
+                ctx.body = { err: 0, msg: '', data: auditors };
+            } catch (err) {
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+        async _checkSettleSelect(ctx) {
+            const Check = require('../lib/settle');
+            const checkObj = new Check(ctx);
+            const result = await checkObj.checkSettle(ctx.settle);
+            if (!result) throw '您勾选了不可结算的数据,请检查后再上报';
+        }
+        async auditStart(ctx) {
+            try {
+                if (ctx.settle.user_id !== ctx.session.sessionUser.accountId) throw '您无权上报该期数据';
+                if (ctx.settle.audit_status !== auditConst.settle.status.uncheck && ctx.settle.audit_status !== auditConst.settle.status.checkNo) throw '该期数据当前无法上报';
+                await this._checkSettleSelect(ctx);
+
+                await ctx.service.settleAudit.start(ctx.settle);
+                //ctx.redirect('/tender/' + ctx.tender.id + '/settle/' + newSettle.settle_order);
+                ctx.redirect(ctx.request.header.referer);
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '上报失败');
+                ctx.redirect(`/tender/${ctx.settle.tid}/settle/${ctx.settle.settle_order}/select`);
+            }
+        }
+        async auditCheck(ctx) {
+            try {
+                if (!ctx.settle || (ctx.settle.audit_status !== auditConst.settle.status.checking && ctx.settle.audit_status !== auditConst.settle.status.checkNoPre)) {
+                    throw '当前期数据有误';
+                }
+                if (ctx.settle.curAuditorIds.indexOf(ctx.session.sessionUser.accountId) < 0) {
+                    throw '您无权进行该操作';
+                }
+
+                const checkType = parseInt(ctx.request.body.checkType);
+                const opinion = ctx.request.body.opinion.replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' ');
+                await ctx.service.settleAudit.check(ctx.settle, checkType, opinion);
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '审批失败');
+            }
+            ctx.redirect(ctx.request.header.referer);
+        }
+
         async loadGatherData(ctx) {
             try {
                 const settle = await this.ctx.service.settle.getLatestCompleteSettle(ctx.tender.id);
-                const bills = await this.ctx.service.settleBillsFinal.getDataByCondition({ where: { settle_id: settle.id }});
-                const pos = await this.ctx.service.settlePosFinal.getDataByCondition({ where: { settle_id: settle.id }});
+                const bills = await this.ctx.service.settleBills.getDataByCondition({ where: { settle_id: settle.id }});
+                const pos = await this.ctx.service.settlePos.getDataByCondition({ where: { settle_id: settle.id }});
                 ctx.body = { err: 0, msg: '', data: { bills, pos } };
             } catch(err) {
                 ctx.log(err);
                 ctx.ajaxErrorBody(err, '获取结算汇总数据错误');
             }
         }
-
         async gather(ctx) {
             try {
                 const renderData = {

+ 3 - 0
app/controller/tender_controller.js

@@ -1137,6 +1137,9 @@ module.exports = app => {
                 } else if (ctx.revise) {
                     const isAuditor = ctx.revise.reviseUsers.indexOf(this.ctx.session.sessionUser.accountId) >= 0;
                     if (!isAuditor && !isValidTourist) throw '您无权进行该操作';
+                } else if (ctx.settle) {
+                    const isAuditor = ctx.settle.userIds.indexOf(this.ctx.session.sessionUser.accountId) >= 0;
+                    if (!isAuditor && !isValidTourist) throw '您无权进行该操作';
                 } else {
                     const isAuditor = ctx.tender.ledgerUsers.indexOf(this.ctx.session.sessionUser.accountId) >= 0;
                     if (!isAuditor && !isValidTourist) throw '您无权进行该操作';

+ 28 - 17
app/controller/wap_controller.js

@@ -174,23 +174,7 @@ module.exports = app => {
 
                 const tenderList = await this.ctx.service.tender.getBuildList('', userPermission);
                 for (const t of tenderList) {
-                    if (t.user_id === this.ctx.session.sessionUser.accountId && (
-                        t.ledger_status === auditConst.ledger.status.checkNo || t.ledger_status === auditConst.ledger.status.uncheck)) {
-                        const sum = await this.ctx.service.ledger.addUp({tender_id: t.id/*, is_leaf: true*/});
-                        t.total_price = sum.total_price;
-                        t.deal_tp = sum.deal_tp;
-                    }
-                    if (t.ledger_status === auditConst.ledger.status.checked) {
-                        t.lastStage = await this.ctx.service.stage.getLastestStage(t.id, true);
-
-                        if (!t.lastStage) continue;
-                        if (t.lastStage.status === auditConst.stage.status.uncheck && t.lastStage.user_id !== this.ctx.session.sessionUser.accountId) {
-                            t.lastStage = await this.ctx.service.stage.getLastestStage(t.id);
-                        }
-
-                        if (!t.lastStage) continue;
-                        await this.ctx.service.stage.checkStageGatherData(t.lastStage);
-                    }
+                    await this.ctx.service.tenderCache.loadTenderCache(t, this.ctx.session.sessionUser.accountId);
                 }
                 const categoryData = await this.ctx.service.category.getAllCategory(this.ctx.session.sessionProject.id);
                 const valuations = await this.ctx.service.valuation.getProjectValidValuation(this.ctx.session.sessionProject.id);
@@ -536,6 +520,11 @@ module.exports = app => {
                 if (!changeData) {
                     throw '变更令数据错误';
                 }
+                // 判断是否到你审批,如果不是则无法审批
+                const curAuditor = await ctx.service.changeAudit.getCurAuditor(changeData.cid, changeData.times);
+                if (!curAuditor || (curAuditor && curAuditor.uid !== ctx.session.sessionUser.accountId)) {
+                    throw '该变更令当前您无权操作';
+                }
                 const status = parseInt(ctx.request.body.status);
                 const pid = this.ctx.session.sessionProject.id;
                 let result = false;
@@ -805,6 +794,28 @@ module.exports = app => {
                 }
             }
         }
+
+        async msg(ctx) {
+            try {
+                const msgId = parseInt(ctx.params.id) || 0;
+                if (!msgId) {
+                    throw '参数有误';
+                }
+                const msgInfo = await ctx.service.message.getDataById(msgId);
+                if (!msgInfo) {
+                    throw '项目通知不存在';
+                }
+                const renderData = {
+                    msgInfo,
+                    moment,
+                };
+                await ctx.render('wap/msg.ejs', renderData);
+            } catch (error) {
+                console.log(error);
+                this.log(error);
+                ctx.redirect('/wap/dashboard');
+            }
+        }
     }
 
     return WapController;

+ 32 - 0
app/controller/wechat_controller.js

@@ -17,6 +17,7 @@ const maintainConst = require('../const/maintain');
 const wxConst = require('../const/wechat_template.js');
 const smsTypeConst = require('../const/sms_type');
 const wxWork = require('../lib/wx_work');
+const { parseStringXml } = require('../lib/common');
 
 module.exports = app => {
     class WechatController extends app.BaseController {
@@ -48,6 +49,37 @@ module.exports = app => {
             }
         }
         /**
+         * 微信自动回复
+         *
+         * @param {Object} ctx - egg全局页面
+         * @return {void}
+         */
+        async replyMessage(ctx) {
+            const xml = ctx.request.body;
+            try {
+                const {
+                    createTime, msgType, toUserName, toFromName, event, msgContent,
+                } = await parseStringXml(xml);
+                let body = '';
+                if (msgType === 'text') {
+                    const reply_msg = '如有问题需要咨询,请电话联系0756-3850888;或添加企业QQ:800003850。';
+                    body = `
+            <xml>
+                <ToUserName><![CDATA[${toFromName}]]></ToUserName>
+                <FromUserName><![CDATA[${toUserName}]]></FromUserName>
+                <CreateTime>${createTime}</CreateTime>
+                <MsgType><![CDATA[text]]></MsgType>
+                <Content><![CDATA[${reply_msg}]]></Content>
+                <MediaId><![CDATA[media_id]]></MediaId>
+            </xml>
+        `;
+                }
+                ctx.body = body;
+            } catch (e) {
+                console.log(e);
+            }
+        }
+        /**
          * 微信登录验证
          *
          * @param {Object} ctx - egg全局页面

+ 17 - 6
app/extend/helper.js

@@ -839,7 +839,7 @@ module.exports = {
      * @param {Array} main - 主数据
      * @param {Array[]}rela - 相关数据 {data, fields, prefix, relaId}
      */
-    assignRelaData(main, rela) {
+    assignRelaData(main, rela, mainKey = 'id') {
         const index = {},
             indexPre = 'id_';
         const loadFields = function(datas, fields, prefix, relaId) {
@@ -856,7 +856,7 @@ module.exports = {
             }
         };
         for (const m of main) {
-            index[indexPre + m.id] = m;
+            index[indexPre + m[mainKey]] = m;
             for (const r of rela) {
                 if (r.defaultData) _.assignIn(m, r.defaultData);
             }
@@ -1616,11 +1616,15 @@ module.exports = {
         });
         const Group = [];
         for (const a of auditors) {
-            if (a[orderField] > 0) {
-                if (Group[a[orderField] - 1]) {
-                    Group[a[orderField] - 1].push(a);
+            if (a[orderField] !== undefined) {
+                if (Group.length === 0) {
+                    Group.push([a])
                 } else {
-                    Group.push([a]);
+                    if (Group[Group.length-1][0][orderField] === a[orderField]) {
+                        Group[Group.length-1].push(a);
+                    } else {
+                        Group.push([a]);
+                    }
                 }
             } else {
                 Group.push([a]);
@@ -1676,4 +1680,11 @@ module.exports = {
         qtys.qc_minus_qty = qtys.qc_minus_qty ? qtys.qc_minus_qty : null;
         return qtys;
     },
+    contentChange(content) {
+        let str = content.replace(/【/g, '(');
+        str = str.replace(/】/g, ')');
+        str = str.replace(/工程款/g, '***');
+        str = str.length > 20 ? str.substring(0, 17) + '...' : str;
+        return str;
+    },
 };

+ 34 - 0
app/lib/common.js

@@ -0,0 +1,34 @@
+'use strict';
+
+const { parseString } = require('xml2js');
+class Common {
+    parseStringXml(xml) {
+        return new Promise((resolve, reject) => {
+            parseString(xml, async (err, result) => {
+                if (!err) {
+                    const xmlData = result.xml;
+                    const { MsgType, ToUserName, FromUserName, Event, Content } = xmlData;
+                    const createTime = Date.parse(new Date());
+                    const msgType = MsgType[0]; // 消息类型,event
+                    const toUserName = ToUserName[0]; // 开发人员微信号
+                    const toFromName = FromUserName[0]; // 发送方帐号(一个OpenID)
+                    const event = Event ? Event[0] : ''; // 事件类型,subscribe(订阅)、unsubscribe(取消订阅)
+                    const msgContent = Content ? Content[0] : ''; // 消息内容
+                    resolve({
+                        createTime,
+                        msgType,
+                        toUserName,
+                        toFromName,
+                        event,
+                        msgContent,
+                    });
+                } else {
+                    console.log('err******', err);
+                    reject(new Error('解析失败'));
+                }
+            });
+        });
+    }
+}
+
+module.exports = new Common();

+ 4 - 0
app/lib/ledger.js

@@ -618,6 +618,10 @@ class pos {
         }
     }
 
+    getPos(id) {
+        return this.items[itemsPre + id];
+    }
+
     getLedgerPosKey() {
         const result = [];
         for (const prop in this.ledgerPos) {

+ 5 - 1
app/lib/rpt_data_analysis.js

@@ -49,7 +49,7 @@ const valueCheck = {
                 return true;
         }
     },
-    _checkString(ctx, value, condition) {
+    _checkStringFit(ctx, value, condition) {
         switch (condition.operate) {
             case '=':
                 return value === condition.value;
@@ -61,6 +61,10 @@ const valueCheck = {
                 return true;
         }
     },
+    _checkString(ctx, value, condition) {
+        const result = this._checkStringFit(ctx, value, condition);
+        return condition.opp ? !result : result;
+    },
     loadTypeFun(condition) {
         for (const c of condition) {
             c.fun = this._typeFun[c.type];

+ 300 - 0
app/lib/settle.js

@@ -0,0 +1,300 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const Ledger = require('./ledger');
+
+class Settle {
+    constructor (ctx) {
+        this.ctx = ctx;
+    }
+
+    async _loadLatestStageData() {
+        this.settle.latestStage = await this.ctx.service.stage.getLastestCompleteStage(this.settle.tid);
+        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 this.ctx.service.ledger.getAllDataByCondition({ columns: this.ledgerColumn, where: { tender_id: this.settle.tid } });
+        const endLedgerData = await this.ctx.service.stageBillsFinal.getAllDataByCondition({ where: { sid: this.settle.latestStage.id } });
+        this.ctx.helper.assignRelaData(ledgerData, [
+            { data: endLedgerData, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'qc_minus_qty'], prefix: 'settle_', relaId: 'lid' },
+        ]);
+
+        this.posColumn = ['id', 'tid', 'lid', 'name', 'position', 'porder', 'quantity', 'add_stage_order', 'drawing_code'];
+        const posData = await this.ctx.service.pos.getAllDataByCondition({ columns: this.posColumn, where: { tid: this.settle.tid } });
+        const endPosData = await this.ctx.service.stagePosFinal.getAllDataByCondition({ where: { sid: this.settle.latestStage.id } });
+        this.ctx.helper.assignRelaData(posData, [
+            { data: endPosData, fields: ['contract_qty', 'qc_qty', 'qc_minus_qty'], prefix: 'settle_', relaId: 'pid' },
+        ]);
+
+        this.stageTree = new Ledger.billsTree(this.ctx, {
+            id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1,
+            calcFields: ['total_price', 'settle_gather_tp', 'settle_contract_tp', 'settle_qc_tp']
+        });
+        this.stageTree.loadDatas(ledgerData);
+        this.stageTree.calculateAll();
+
+        this.stagePos = new Ledger.pos({ id: 'id', ledgerId: 'lid' });
+        this.stagePos.loadDatas(posData);
+    }
+
+    async _loadSettleSelect() {
+        const select = await this.ctx.service.settleSelect.getAllDataByCondition({ where: { settle_id: this.settle.id } });
+        for (const s of select) {
+            if (s.pid) {
+                const sp = this.stagePos.getPos(s.pid);
+                if (!sp) continue;
+                sp.is_settle = true;
+                sp.selected = true;
+                const sb = this.stageTree.nodes.find(x => { return x.id === sp.lid });
+                sb.is_settle = true;
+                const parents = this.stageTree.getAllParents(sb);
+                parents.forEach(p => { p.is_settle = true; });
+            }
+            if (s.lid) {
+                const sb = this.stageTree.nodes.find(x => { return x.id === s.lid });
+                sb.is_settle = true;
+                sb.selected = true;
+                const parents = this.stageTree.getAllParents(sb);
+                parents.forEach(p => { p.is_settle = true; });
+                const posterity = this.stageTree.getPosterity(sb);
+                for (const p of posterity) {
+                    p.is_settle = true;
+                    const pos = this.stagePos.getLedgerPos(p.id);
+                    if (pos && pos.length > 0) pos.forEach(x => { x.is_settle = true; });
+                }
+            }
+        }
+    }
+
+    async _loadStageChange() {
+        this.stageChange = await this.ctx.service.stageChangeFinal.getUnSettleChangeData(this.settle.latestStage);
+        for (const change of this.stageChange) {
+            const sb = this.stageTree.datas.find(x => { return x.id === change.gcl_id });
+            if (sb) sb.undoneChange = true;
+            if (change.mx_id) {
+                const sp = this.stagePos.getPos(change.mx_id);
+                if (sp) sp.undoneChange = true;
+            }
+        }
+    }
+
+    async _loadPreSettle() {
+        if (this.settle.settle_order <= 1) return;
+        const prePos = await this.ctx.service.settlePos.getAllDataByCondition({ where: { tid: this.settle.tid, settle_order: this.settle.settle_order - 1 } });
+        for (const pp of prePos) {
+            const sp = this.stagePos.getPos(pp.pid);
+            if (sp) {
+                sp.pre_settle = true;
+                sp.pre_contract_qty = pp.end_contract_qty;
+                sp.pre_qc_qty = pp.end_qc_qty;
+                sp.pre_qc_minus_qty = pp.pre_qc_minus_qty;
+                sp.settle_status = pp.settle_status;
+                sp.settle_done_order = pp.settle_done_order;
+            }
+        }
+        const preBills = await this.ctx.service.settleBills.getAllDataByCondition({ where: { tid: this.settle.tid, settle_order: this.settle.settle_order - 1 } });
+        for (const pb of preBills) {
+            const sb = this.stageTree.nodes.find(x => { return x.id === pb.lid });
+            if (sb) {
+                sb.pre_settle = true;
+                sb.pre_contract_qty = pb.pre_contract_qty;
+                sb.pre_contract_tp = pb.pre_contract_tp;
+                sb.pre_qc_qty = pb.pre_qc_qty;
+                sb.pre_qc_tp = pb.pre_qc_tp;
+                sb.pre_qc_minus_qty = pb.pre_qc_minus_qty;
+                sb.settle_status = pb.settle_status;
+                sb.settle_done_order = pb.settle_done_order;
+            }
+        }
+    }
+
+    calculateSettle() {
+        const helper = this.ctx.helper;
+        const settle = this.settle;
+        this.stagePos.calculateAll(function(p) {
+            if (p.is_settle || !p.pre_settle) {
+                p.cur_contract_qty = p.settle_contract_qty;
+                p.cur_qc_qty = p.settle_qc_qty;
+                p.cur_qc_minus_qty = p.settle_qc_minus_qty;
+            }
+            if (p.is_settle || p.pre_settle) {
+                p.end_contract_qty = helper.add(p.cur_contract_qty, p.pre_contract_qty);
+                p.end_qc_qty = helper.add(p.cur_qc_qty, p.pre_qc_qty);
+                p.end_qc_minus_qty = helper.add(p.cur_qc_minus_qty, p.pre_qc_minus_qty);
+            }
+            if (helper.numEqual(p.end_contract_qty, p.quantity)) {
+                p.settle_status = 2;
+                p.settle_done_order = p.settle_done_order || settle.settle_order;
+            }
+        });
+        const self = this, decimal = this.ctx.tender.info.decimal;
+        this.stageTree.calculateAll(function(b) {
+            if (b.children && b.children.length > 0) return;
+
+            if (b.is_settle) {
+                const posRange = self.stagePos.getLedgerPos(b.id);
+                if (posRange && posRange.length > 0) {
+                    posRange.forEach(p => {
+                        b.cur_contract_qty = helper.add(b.cur_contract_qty, p.cur_contract_qty);
+                        b.cur_qc_qty = helper.add(b.cur_qc_qty, p.cur_qc_qty);
+                        b.cur_qc_minus_qty = helper.add(b.cur_qc_minus_qty, p.cur_qc_minus_qty);
+                    });
+                } else {
+                    if (!b.pre_settle) {
+                        b.cur_contract_qty = b.settle_contract_qty;
+                        b.cur_qc_qty = b.settle_qc_qty;
+                        b.cur_qc_minus_qty = b.settle_qc_minus_qty;
+                    }
+                }
+                b.cur_contract_tp = helper.mul(b.unit_price, b.cur_contract_qty, decimal.tp);
+                b.cur_qc_tp = helper.mul(b.unit_price, b.cur_qc_qty, decimal.tp);
+            }
+            if (b.is_settle || b.pre_settle) {
+                b.end_contract_qty = helper.add(b.cur_contract_qty, b.pre_contract_qty);
+                b.end_contract_tp = helper.add(b.cur_contract_tp, b.pre_contract_tp);
+                b.end_qc_qty = helper.add(b.cur_qc_qty, b.pre_qc_qty);
+                b.end_qc_tp = helper.add(b.cur_qc_tp, b.pre_qc_tp);
+                b.end_qc_minus_qty = helper.add(b.cur_qc_minus_qty, b.pre_qc_minus_qty);
+            }
+            if (b.is_tp) {
+                if (helper.numEqual(b.end_contract_tp, b.total_price)) {
+                    b.settle_status = 2;
+                    b.settle_done_order = b.settle_done_order || settle.settle_order;
+                } else {
+                    b.settle_status = 1;
+                }
+            } else {
+                if (helper.numEqual(b.end_contract_qty, b.quantity)) {
+                    b.settle_status = 2;
+                    b.settle_done_order = b.settle_done_order || settle.settle_order;
+                } else {
+                    b.settle_status = 1;
+                }
+            }
+        })
+    }
+
+    getSettleData() {
+        const settleBills = [];
+        const sum = {};
+        for (const node of this.stageTree.nodes) {
+            if (!node.is_settle && !node.pre_settle) continue;
+            settleBills.push({
+                tid: this.settle.tid, settle_id: this.settle.id, settle_order: this.settle.settle_order,
+                lid: node.id, tree_id: node.ledger_id, tree_pid: node.ledger_pid, tree_full_path: node.full_path,
+                tree_is_leaf: node.is_leaf, tree_level: node.level, tree_order: node.order,
+                code: node.code || '', b_code: node.b_code || '', name: node.name || '', unit: node.unit || '',
+                unit_price: node.unit_price || 0, quantity: node.quantity || 0, total_price: node.total_price || 0,
+                drawing_code: node.drawing_code || '', memo: node.memo || '', node_type: node.node_type || 0,
+                cur_contract_qty: node.cur_contract_qty || 0, cur_contract_tp: node.cur_contract_tp || 0,
+                cur_qc_qty: node.cur_qc_qty || 0, cur_qc_tp: node.cur_qc_tp || 0, cur_qc_minus_qty: node.cur_qc_minus_qty || 0,
+                pre_contract_qty: node.pre_contract_qty || 0, pre_contract_tp: node.pre_contract_tp || 0,
+                pre_qc_qty: node.pre_qc_qty || 0, pre_qc_tp: node.pre_qc_tp || 0, pre_qc_minus_qty: node.pre_qc_minus_qty || 0,
+                end_contract_qty: node.end_contract_qty || 0, end_contract_tp: node.end_contract_tp || 0,
+                end_qc_qty: node.end_qc_qty || 0, end_qc_tp: node.end_qc_tp || 0, end_qc_minus_qty: node.end_qc_minus_qty || 0,
+                is_settle: node.is_settle ? 1 : 0, pre_settle: node.pre_settle ? 1 : 0,
+                settle_status: node.settle_status || 0, settle_done_order: node.settle_done_order || 0,
+            });
+            sum.contract_tp = this.ctx.helper.add(sum.contract_tp, node.cur_contract_tp);
+            sum.qc_tp = this.ctx.helper.add(sum.qc_tp, node.cur_qc_tp);
+        }
+        sum.tp = this.ctx.helper.add(sum.contract_tp, sum.qc_tp);
+        const settlePos = [];
+        for (const pos of this.stagePos.datas) {
+            if (!pos.is_settle && !pos.pre_settle) continue;
+            settlePos.push({
+                tid: this.settle.tid, settle_id: this.settle.id, settle_order: this.settle.settle_order,
+                lid: pos.lid, pid: pos.id,
+                name: pos.name || '', drawing_code: pos.drawing_code || '', position: pos.position || '', porder: pos.porder || 1,
+                cur_contract_qty: pos.cur_contract_qty || 0, cur_qc_qty: pos.cur_qc_qty || 0, cur_qc_minus_qty: pos.cur_qc_minus_qty || 0,
+                pre_contract_qty: pos.pre_contract_qty || 0, pre_qc_qty: pos.pre_qc_qty || 0, pre_qc_minus_qty: pos.pre_qc_minus_qty || 0,
+                end_contract_qty: pos.end_contract_qty || 0, end_qc_qty: pos.end_qc_qty || 0, end_qc_minus_qty: pos.end_qc_minus_qty || 0,
+                is_settle: pos.is_settle ? 1 : 0, pre_settle: pos.pre_settle ? 1 : 0,
+                settle_status: pos.settle_status || 0, settle_done_order: pos.settle_done_order || 0,
+            });
+        }
+        return [settleBills, settlePos, sum];
+    }
+
+    async doSettle(settle) {
+        this.settle = settle;
+
+        await this._loadLatestStageData(settle);
+        await this._loadSettleSelect();
+        await this._loadPreSettle();
+        this.calculateSettle();
+        return this.getSettleData();
+    }
+
+    _initPosUndone(pos) {
+        pos.undoneDeal = pos.quantity ? !this.ctx.helper.numEqual(pos.settle_contract_qty, pos.quantity) : false;
+        pos.undone = !!pos.undoneDeal || !!pos.undoneChange;
+    }
+
+    _initNodeUndone(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._initNodeUndone(child);
+                if (child.undoneDeal) node.undoneDeal = true;
+                if (child.undoneChange) node.undoneChange = true;
+            }
+            node.undone = !!node.undoneDeal || !!node.undoneChange;
+        } else {
+            const posRange = this.stagePos.getLedgerPos(node.id);
+            if (posRange && posRange.length > 0) {
+                for (const pos of posRange) {
+                    this._initPosUndone(pos);
+                    if (pos.undoneDeal) node.undoneDeal = true;
+                    if (pos.undoneChange) node.undoneChange = true;
+                }
+                node.undone = !!node.undoneDeal || !!node.undoneChange;
+            } else {
+                node.undoneDeal = node.settle_contract_qty
+                    ? !this.ctx.helper.numEqual(node.settle_contract_qty, node.quantity)
+                    : !this.ctx.helper.numEqual(node.settle_contract_tp, node.total_price);
+                node.undone = !!node.undoneDeal || !!node.undoneChange;
+            }
+        }
+    }
+
+    _checkNodeError(node) {
+        const billsError = !!node.selected && node.undone;
+        if (billsError) return billsError;
+        const posRange = !node.children || node.children.length === 0 ? (this.stagePos.getLedgerPos(node.id) || []) : [];
+        for (const pos of posRange) {
+            if (!!pos.selected && pos.undone) return true;
+        }
+        return false;
+    }
+
+    checkAllSelect() {
+        for (const node of this.stageTree.children) {
+            this._initNodeUndone(node);
+        }
+        this.error = [];
+        for (const node of this.stageTree.nodes) {
+            if (this._checkNodeError(node)) this.error.push(node);
+        }
+        return this.error.length === 0;
+    }
+
+    async checkSettle(settle) {
+        this.settle = settle;
+        await this._loadLatestStageData(settle);
+        await this._loadSettleSelect();
+        await this._loadStageChange();
+        return this.checkAllSelect();
+    }
+}
+
+module.exports = Settle;

+ 1 - 1
app/middleware/settle_check.js

@@ -27,7 +27,7 @@ module.exports = options => {
             yield this.service.settle.doCheckSettle(settle);
             // 获取最新的期数
             settle.highOrder = yield this.service.settle.count({ tid: this.tender.id });
-
+            yield this.service.settle.checkSettleShenpi(settle);
             this.settle = settle;
             yield next;
         } catch (err) {

+ 4 - 1
app/middleware/tender_check.js

@@ -64,6 +64,8 @@ module.exports = options => {
                 return x.user_id === tender.data.user_id || stageAuditorsId.indexOf(x.user_id) >= 0;
             });
             const auditAssistsId = this.helper._.map(auditAssists, 'ass_user_id');
+            const settleAuditors = yield this.service.settleAudit.getAllAuditors(tender.id);
+            const settleAuditorsId = this.helper._.map(settleAuditors, 'audit_id');
             const changeAuditors = yield this.service.changeAudit.getAllAuditors(tender.id);
             const changeAuditorsId = this.helper._.map(changeAuditors, 'uid');
             const reviseAuditors = yield this.service.reviseAudit.getAllAuditors(tender.id);
@@ -86,7 +88,8 @@ module.exports = options => {
             tender.touristPermission = yield this.service.tenderTourist.getTouristPermission(isTenderTourist);
             if (auditorsId.indexOf(accountId) === -1 && tender.data.user_id !== accountId &&
                 (tenderPermission === null || tenderPermission === undefined || tenderPermission.indexOf('2') === -1) &&
-                stageAuditorsId.indexOf(accountId) === -1 && auditAssistsId.indexOf(accountId) === -1 && changeAuditorsId.indexOf(accountId) === -1 &&
+                stageAuditorsId.indexOf(accountId) === -1 && auditAssistsId.indexOf(accountId) === -1 &&
+                settleAuditorsId.indexOf(accountId) === -1 && changeAuditorsId.indexOf(accountId) === -1 &&
                 reviseAuditorsId.indexOf(accountId) === -1 && materialAuditorsId.indexOf(accountId) === -1 &&
                 changeProjectAuditorsId.indexOf(accountId) === -1 && changeProjectXsAuditorsId.indexOf(accountId) === -1 &&
                 changeApplyAuditorsId.indexOf(accountId) === -1 && changePlanAuditorsId.indexOf(accountId) === -1 &&

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

@@ -769,7 +769,7 @@ $(document).ready(() => {
         // 数组去重
         dealBillList = result.dealBills;
         changeListData = gclGatherData;
-        console.log(changeListData, dealBillList);
+        // console.log(changeListData, dealBillList);
 
         SpreadJsObj.initSpreadSettingEvents(changeSpreadSetting, changeCol);
         SpreadJsObj.initSheet(changeSpreadSheet, changeSpreadSetting);

+ 3 - 3
app/public/js/change_revise.js

@@ -610,7 +610,7 @@ $(document).ready(() => {
                 }
                 if (col.field === 'b_code' && newValue) {
                     const ledgerData = _.filter(billsTree.nodes, function (item) {
-                        return item.b_code !== null && item.b_code !== '' && item.ccid === '';
+                        return item.b_code !== null && item.b_code !== '' && !item.formc;
                     });
                     const ledgerOneData = _.find(ledgerData, { b_code: newValue });
                     if (ledgerOneData) {
@@ -735,7 +735,7 @@ $(document).ready(() => {
                     }
                     if (colSetting.field === 'b_code' && value) {
                         const ledgerData = _.filter(billsTree.nodes, function (item) {
-                            return item.b_code !== null && item.b_code !== '' && item.ccid === '';
+                            return item.b_code !== null && item.b_code !== '' && !item.formc;
                         });
                         const ledgerOneData = _.find(ledgerData, { b_code: value });
                         if (ledgerOneData) {
@@ -830,7 +830,7 @@ $(document).ready(() => {
                         }
                         if (colSetting.field === 'b_code' && value) {
                             const ledgerData = _.filter(billsTree.nodes, function (item) {
-                                return item.b_code !== null && item.b_code !== '' && item.ccid === '';
+                                return item.b_code !== null && item.b_code !== '' && !item.formc;
                             });
                             const ledgerOneData = _.find(ledgerData, { b_code: value });
                             if (ledgerOneData) {

+ 2 - 2
app/public/js/material.js

@@ -2140,7 +2140,7 @@ $(document).ready(() => {
     if (getLocalCache('material_month_' + materialID) && !isStageSelf) {
         const tab = $('.right-nav a[content="#month-tab"]'), tabPanel = $(tab.attr('content'));
         $('a', '.side-menu').removeClass('active');
-        $('.tab-content .tab-pane').removeClass('active');
+        $('#right-view .tab-content .tab-pane').removeClass('active');
         tab.addClass('active');
         tabPanel.addClass('active');
         $('#right-view').width(getLocalCache('material_month_' + materialID) + '%');
@@ -2174,7 +2174,7 @@ $(document).ready(() => {
         $('.sjs-material').height($('.sjs-height-1').height() - getObjHeight($('.sjs-bar')));
     }
     function getObjHeight(select) {
-        return select.length > 0 ? select.height() : 0;
+        return select.length > 0 ? select.outerHeight() : 0;
     }
 
     function formatDate(numb, format) {

+ 3 - 2
app/public/js/path_tree.bak.js

@@ -217,9 +217,10 @@ const createNewPathTree = function (setting) {
      * @returns {Array}
      */
     proto.getPosterity = function (node) {
-        const reg = new RegExp('^' + node.full_path + '.');
+        const field = this.setting.fullPath;
+        const reg = new RegExp('^' + node[field] + '.');
         return this.datas.filter(function (x) {
-            return reg.test(x.full_path);
+            return reg.test(x[field]);
         })
     };
     /**

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

@@ -460,8 +460,8 @@ const createNewPathTree = function (type, setting) {
             const parents = [];
             if (!node) return parents;
 
-            if (node.full_path && node.full_path !== '') {
-                const parentIds = node.full_path.split('-');
+            if (node[this.setting.fullPath] && node[this.setting.fullPath] !== '') {
+                const parentIds = node[this.setting.fullPath].split('-');
                 parentIds.length = parentIds.length - 1;
                 for (const id of parentIds) {
                     if (id !== node[this.setting.id]) {
@@ -556,7 +556,7 @@ const createNewPathTree = function (type, setting) {
         getPosterity(node) {
             const self = this;
             let posterity;
-            if (node.full_path !== '') {
+            if (node[self.setting.fullPath] !== '') {
                 const reg = new RegExp('^' + node[self.setting.fullPath] + '-');
                 posterity = this.datas.filter(function (x) {
                     return reg.test(x[self.setting.fullPath]);

+ 173 - 20
app/public/js/settle_ledger.js

@@ -16,40 +16,42 @@ const ckBillsSpread = window.location.pathname + '-billsSelect';
 $(document).ready(() => {
     autoFlashHeight();
 
-    let searchLedger;
+    let searchLedger, settleAtt;
     const settleTreeSetting = {
-        id: 'ledger_id',
-        pid: 'ledger_pid',
-        order: 'order',
-        level: 'level',
+        id: 'tree_id',
+        pid: 'tree_pid',
+        order: 'tree_order',
+        level: 'tree_level',
+        fullPath: 'tree_full_path',
+        isLeaf: 'tree_is_leaf',
         rootId: -1,
-        keys: ['id', 'tender_id', 'ledger_id'],
+        keys: ['id', 'tid', 'tree_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'],
+        calcFields: ['total_price', 'cur_contract_tp', 'cur_qc_tp', 'cur_gather_tp', 'cur_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));
+                node.cur_gather_qty = ZhCalc.add(node.cur_contract_qty, node.cur_qc_qty);
+                if (node.cur_contract_qty) {
+                    node.cur_correct_tp = ZhCalc.add(node.cur_qc_tp, ZhCalc.mul(node.cur_contract_qty, node.unit_price, tenderInfo.decimal.tp));
                 } else {
-                    node.end_correct_tp = node.end_gather_tp;
+                    node.cur_correct_tp = node.cur_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);
+            node.cur_gather_tp = ZhCalc.add(node.cur_contract_tp, node.cur_qc_tp);
+            node.cur_gather_percent = ZhCalc.mul(ZhCalc.div(node.cur_gather_tp, node.cur_final_tp), 100, 2);
+            node.cur_correct_percent = ZhCalc.mul(ZhCalc.div(node.cur_correct_tp, node.cur_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.cur_gather_qty = ZhCalc.add(pos.cur_contract_qty, pos.cur_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);
+            pos.cur_gather_percent = ZhCalc.mul(ZhCalc.div(pos.cur_gather_qty, pos.sum), 100, 2);
         }
     };
     const settlePos = new StagePosData(settlePosSetting);
@@ -58,8 +60,8 @@ $(document).ready(() => {
     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';
+    const ratioCol = billsSpreadSetting.cols.find(x => {return x.field === 'cur_final_1_percent' || x.field === 'cur_correct_1_percent'});
+    if (ratioCol) ratioCol.field = tenderInfo.display.stage.correct ? 'cur_correct_1_percent' : 'cur_final_1_percent';
     billsSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
         if (!data) return defaultColor;
         if (data.children && data.children.length > 0) return defaultColor;
@@ -81,6 +83,39 @@ $(document).ready(() => {
         {field: 'gxby', getValue: getGxbyText, url_field: 'gxby_url'},
         {field: 'dagl', getValue: getDaglText, url_field: 'dagl_url'},
     ]);
+    billsSpreadSetting.headColWidth = [50];
+    billsSpreadSetting.rowHeader = [
+        {
+            rowHeaderType: 'tag',
+            setting: {
+                indent: 16,
+                tagSize: 0.8,
+                tagFont: '8px 微软雅黑',
+                getColor: function (index, data) {
+                    if (!data) return;
+                    return billsTag.getBillsTagsColor(data.lid);
+                },
+                getTagHtml: function (index, data) {
+                    if (!data) return;
+                    const getHtml = function (list) {
+                        if (!list || list.length === 0) return '';
+                        const html = [];
+                        for (const l of list) {
+                            html.push('<div class="row mr-1">');
+                            html.push(`<div class="col-auto pr-1 ${l.tagClass}">`, '<i class="fa fa-tag"></i>', '</div>');
+                            html.push('<div class="col p-0">', '<p>', l.comment, '</p>', '</div>');
+                            html.push('</div>');
+                        }
+                        return html.join('');
+                    };
+                    return getHtml(billsTag.getBillsTagsInfo(data.lid));
+                }
+            },
+        },
+    ];
+    billsSpreadSetting.afterLocate = function (node) {
+        settleAtt.getCurAttHtml(node);
+    };
     SpreadJsObj.initSheet(slSheet, billsSpreadSetting);
 
     const spSpread = SpreadJsObj.createNewSpread($('#settle-pos')[0]);
@@ -112,6 +147,7 @@ $(document).ready(() => {
         loadRelaData: function() {
             SpreadJsObj.saveTopAndSelect(slSheet, ckBillsSpread);
             SpreadJsObj.resetTopAndSelect(spSheet);
+            settleBillsObj.loadRelaAtt();
             settlePosObj.loadCurPosData();
         },
         selectionChanged: function(e, info) {
@@ -122,6 +158,10 @@ $(document).ready(() => {
         topRowChanged(e, info) {
             SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
         },
+        loadRelaAtt() {
+            const node = SpreadJsObj.getSelectObject(slSheet);
+            settleAtt.getCurAttHtml(node);
+        }
     };
     slSpread.bind(spreadNS.Events.SelectionChanged, settleBillsObj.selectionChanged);
     slSpread.bind(spreadNS.Events.TopRowChanged, settleBillsObj.topRowChanged);
@@ -131,7 +171,7 @@ $(document).ready(() => {
             const billsNode = SpreadJsObj.getSelectObject(slSheet);
             if (billsNode) {
                 spSheet.zh_setting.readOnly = readOnly;
-                const posRange = settlePos.getLedgerPos(billsNode.id) || [];
+                const posRange = settlePos.getLedgerPos(billsNode.lid) || [];
                 SpreadJsObj.loadSheetData(spSheet, SpreadJsObj.DataType.Data, posRange, readOnly);
             } else {
                 spSheet.zh_setting.readOnly = true;
@@ -140,15 +180,81 @@ $(document).ready(() => {
         }
     };
 
-    postData('load', {filter: 'settleBills;settlePos;tag'}, function(result) {
+    const billsTag = $.billsTag({
+        selector: '#bills-tag',
+        relaSpread: slSpread,
+        updateUrl: window.location.pathname + '/tag',
+        key: 'lid',
+        treeId: 'tree_id',
+        afterModify: function (nodes) {
+            SpreadJsObj.repaintNodesRowHeader(slSheet, nodes);
+        },
+        afterLocated:  function () {
+            settlePosObj.loadCurPosData();
+        },
+        afterShow: function () {
+            slSpread.refresh();
+            if (spSpread) spSpread.refresh();
+        },
+    });
+    const addTag = newTag({ledgerSheet: slSheet, billsTag, key: 'lid'});
+    $.contextMenu({
+        selector: '#settle-bills',
+        build: function ($trigger, e) {
+            const target = SpreadJsObj.safeRightClickSelection($trigger, e, slSpread);
+            settleBillsObj.loadRelaData();
+            return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+        },
+        items: {
+            tag: {
+                name: '书签',
+                callback: function (key, opt, menu, e) {
+                    const node = SpreadJsObj.getSelectObject(slSheet);
+                    addTag.do(node);
+                },
+                disabled: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(slSheet);
+                    return !node;
+                }
+            }
+        }
+    });
+
+    postData('load', {filter: 'settleBills;settlePos;tag;att'}, function(result) {
         settleTree.loadDatas(result.settleBills);
         treeCalc.calculateAll(settleTree);
         settlePos.loadDatas(result.settlePos);
         settlePos.calculateAll();
 
+        for (const t of result.tag) {
+            t.node = settleTree.nodes.find(x => {return x.lid === t.lid});
+        }
+        billsTag.loadDatas(result.tag);
+
         SpreadJsObj.loadSheetData(slSheet, SpreadJsObj.DataType.Tree, settleTree);
         SpreadJsObj.loadTopAndSelect(slSpread.getActiveSheet(), ckBillsSpread);
         settlePosObj.loadCurPosData();
+
+        for (const r of result.att) {
+            r.node = settleTree.datas.find(x => {return x.lid === r.lid});
+        }
+        settleAtt = $.ledger_att({
+            selector: '#fujian',
+            key: 'lid',
+            uploadUrl: 'file/upload',
+            deleteUrl: 'file/delete',
+            checked: settleComplete,
+            zipName: `${tenderName}-计量台账-第${settleOrder}期-附件.zip`,
+            readOnly: false, // todo fileUploadPermission,
+            locate: function (att) {
+                if (!att) return;
+                SpreadJsObj.locateTreeNode(slSheet, att.node.tree_id, true);
+                settleBillsObj.loadRelaAtt();
+                settlePosObj.loadCurPosData();
+            }
+        });
+        settleAtt.loadDatas(result.att);
+        settleAtt.getCurAttHtml(SpreadJsObj.getSelectObject(slSheet));
     });
 
     // 展开收起工具栏
@@ -216,4 +322,51 @@ $(document).ready(() => {
             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();
+        }
+    });
+
+    // 显示层次
+    (function (select, sheet) {
+        $(select).click(function () {
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            if (!tree) return;
+            setTimeout(() => {
+                showWaitingView();
+                switch (tag) {
+                    case "1":
+                    case "2":
+                    case "3":
+                    case "4":
+                    case "5":
+                        tree.expandByLevel(parseInt(tag));
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "last":
+                        tree.expandByCustom(() => { return true; });
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                }
+                closeWaitingView();
+            }, 100);
+        });
+    })('a[name=showLevel]', slSheet);
 });

+ 93 - 10
app/public/js/settle_select.js

@@ -80,6 +80,25 @@ $(document).ready(() => {
         {field: 'gxby', getValue: getGxbyText, url_field: 'gxby_url'},
         {field: 'dagl', getValue: getDaglText, url_field: 'dagl_url'},
     ]);
+    billsSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
+        if (data) {
+            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;
+            }
+
+            return data.selected && data.undone ? spreadColor.stage.over : defaultColor;
+        } else {
+            return defaultColor;
+        }
+    };
     SpreadJsObj.initSheet(slSheet, billsSpreadSetting);
 
     const spSpread = SpreadJsObj.createNewSpread($('#settle-pos')[0]);
@@ -105,13 +124,31 @@ $(document).ready(() => {
         {field: 'gxby', getValue: getGxbyText, url_field: 'gxby_url'},
         {field: 'dagl', getValue: getDaglText, url_field: 'dagl_url'},
     ]);
+    posSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
+        if (data) {
+            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;
+            }
+
+            return data.selected && data.undone ? spreadColor.stage.over : defaultColor;
+        } else {
+            return defaultColor;
+        }
+    };
     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;
+            pos.undoneDeal = pos.quantity ? !checkZero(ZhCalc.sub(pos.end_contract_qty, pos.quantity)) : false;
+            pos.undone = !!pos.undoneDeal || !!pos.undoneChange;
         },
         _analysisNode(node) {
             if (node.undoneDeal === undefined) node.undoneDeal = false;
@@ -131,8 +168,11 @@ $(document).ready(() => {
                         if (pos.undoneDeal) node.undoneDeal = true;
                         if (pos.undoneChange) node.undoneChange = true;
                     }
+                    node.undone = !!node.undoneDeal || !!node.undoneChange;
                 } else {
-                    node.undoneDeal = node.end_contract_qty ? !checkZero(ZhCalc.div(node.end_contract_qty, node.quantity)) : false;
+                    node.undoneDeal = node.end_contract_qty
+                        ? !checkZero(ZhCalc.sub(node.end_contract_qty, node.quantity))
+                        : !checkZero(ZhCalc.sub(node.end_contract_tp, node.total_price));
                     node.undone = !!node.undoneDeal || !!node.undoneChange;
                 }
             }
@@ -195,10 +235,12 @@ $(document).ready(() => {
                     }
                     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 });
+                        if (posRange && posRange.length > 0) {
+                            for (const p of posRange) {
+                                if (p.selected) {
+                                    if (!update.del) update.del = [];
+                                    update.del.push({ pid: p.id });
+                                }
                             }
                         }
                     }
@@ -279,9 +321,9 @@ $(document).ready(() => {
 
             const update = {};
             if (!node.selected) {
-                update.add = [{ lid: node.id }];
+                update.add = [{ pid: node.id }];
             } else {
-                update.del = [{ lid: node.id }];
+                update.del = [{ pid: node.id }];
             }
             postData(window.location.pathname + '/update', update, result => {
                 node.selected = !node.selected;
@@ -361,6 +403,20 @@ $(document).ready(() => {
                     afterLocated: function () {
                         settlePosObj.loadCurPosData();
                     },
+                    customSearch: [
+                        {
+                            key: 'err', title: '结算错误', valid: true, parent: true,
+                            check: function (node) {
+                                const billsError = !!node.selected && node.undone;
+                                if (billsError) return billsError;
+                                const posRange = !node.children || node.children.length === 0 ? (settlePos.getLedgerPos(node.id) || []) : [];
+                                for (const pos of posRange) {
+                                    if (!!pos.selected && pos.undone) return true;
+                                }
+                                return false;
+                            }
+                        },
+                    ],
                 });
                 searchLedger.spread.refresh();
             }
@@ -409,4 +465,31 @@ $(document).ready(() => {
             window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
         }
     });
+
+    // 显示层次
+    (function (select, sheet) {
+        $(select).click(function () {
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            if (!tree) return;
+            setTimeout(() => {
+                showWaitingView();
+                switch (tag) {
+                    case "1":
+                    case "2":
+                    case "3":
+                    case "4":
+                    case "5":
+                        tree.expandByLevel(parseInt(tag));
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "last":
+                        tree.expandByCustom(() => { return true; });
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                }
+                closeWaitingView();
+            }, 100);
+        });
+    })('a[name=showLevel]', slSheet);
 });

+ 6 - 5
app/public/js/shares/cs_tools.js

@@ -582,7 +582,7 @@ const showSelectTab = function(select, spread, afterShow) {
             const cs = setting.customSearch.find(function (x) {return x.key === key});
             return cs ? cs.check : null;
         };
-        const getParantFun = function (key) {
+        const getParentFun = function (key) {
             const cs = setting.customSearch.find(function (x) {return x.key === key});
             return cs && cs.parent !== undefined ? cs.parent : false;
         };
@@ -592,9 +592,9 @@ const showSelectTab = function(select, spread, afterShow) {
             const checkFun = getCheckFun(key);
             searchResult = [];
             const sortData = SpreadJsObj.getSortData(searchSheet);
-            const parantFun = getParantFun(key);
+            const parentFun = getParentFun(key);
             for (const node of sortData) {
-                if (node.children && node.children.length > 0 && !parantFun) continue;
+                if (node.children && node.children.length > 0 && !parentFun) continue;
                 if (checkFun && checkFun(node)) {
                     if (!keyword ||
                         (node.code && node.code.indexOf(keyword) > -1) ||
@@ -756,6 +756,8 @@ const showSelectTab = function(select, spread, afterShow) {
             {tagClass: 'text-warning', color: '#da9500'},
             {tagClass: 'text-info', color: '#17a2b8'},
         ];
+        if (!setting.key) setting.key = 'id';
+        if (!setting.treeId) setting.treeId = 'ledger_id';
         const obj = $(setting.selector);
         const html = [], pageLength = 15;
         let billsTags = [], classIndexes = [], billsIndexes = {}, curShow = [];
@@ -828,7 +830,6 @@ const showSelectTab = function(select, spread, afterShow) {
         };
 
         const getTagDisplayHtml = function (tag) {
-            console.log(tag);
             const tagClass = classIndexes.find(x => {return x.color === tag.color}) || {};
             const tagHtml = [];
             tagHtml.push('<div name="tag-view">');
@@ -840,7 +841,7 @@ const showSelectTab = function(select, spread, afterShow) {
                 tagHtml.push(`<div class="pull-right"><i class="fa fa-users text-warning" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="所有参与台账审批管理的用户都可以看到这条书签"></i> <span>${tag.u_name}</span></div>`);
             }
             tagHtml.push('<div class="pull-right edit-tag-btn">');
-            const lid = tag.node ? tag.node.ledger_id : -1;
+            const lid = tag.node ? tag.node[setting.treeId] : -1;
             tagHtml.push(`<a class="mr-1" name="bills-tag-locate" href="javascript: void(0);" lid="${lid}"><i class="fa fa-crosshairs"></i> 定位</a>`);
             if (tag.uid === userID && !setting.readOnly) tagHtml.push(`<a href="javascript: void(0);" name="bills-tag-edit" tag-id="${tag.id}"><i class="fa fa-edit"></i> 编辑</a>`);
             tagHtml.push('</div>');

+ 1 - 1
app/public/js/shares/new_tag.js

@@ -18,7 +18,7 @@ const newTag = function (setting) {
         const data = {
             add: {
                 color: $('.active[name=addtag-color]').attr('tag-color'),
-                lid: relaNode.id,
+                lid: setting.key ? relaNode[setting.key] : relaNode.id,
                 share: $('#addtag-share')[0].checked,
                 comment: $('#addtag-content').val(),
             }

+ 452 - 0
app/public/js/shares/settle_audit.js

@@ -0,0 +1,452 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2019/2/27
+ * @version
+ */
+
+// 检查上报情况
+function checkAuditorFrom () {
+    if ($('#auditors li').length === 0) {
+        if(shenpi_status === shenpiConst.sp_status.gdspl) {
+            toastr.error('请联系管理员添加审批人');
+        } else {
+            toastr.error('请先选择审批人,再上报数据');
+        }
+        return false;
+    }
+    $('#hide-all').show();
+    return true;
+}
+// 点击验证码
+function codeSuccess(btn) {
+    let counter = 60;
+    btn.addClass('disabled').text('重新获取 ' + counter + 'S');
+    btn.parent().siblings('input').removeAttr('readonly').attr('placeholder', '输入短信中的6位验证码');
+    const bindBtn = $("#bind-btn");
+    bindBtn.removeClass('btn-secondary disabled').addClass('btn-primary');
+
+    const countDown = setInterval(function() {
+        const countString = counter - 1 <= 0 ? '' : ' ' + (counter - 1) + 'S';
+        // 倒数结束后
+        if (countString === '') {
+            clearInterval(countDown);
+            btn.removeClass('disabled');
+        }
+        const text = '重新获取' + countString;
+        btn.text(text);
+        counter -= 1;
+    }, 1000);
+}
+
+$(document).ready(function () {
+    let timer = null;
+    let oldSearchVal = null;
+
+    // 搜索审批人
+    $('#gr-search').bind('input propertychange', function(e) {
+        oldSearchVal = e.target.value;
+        timer && clearTimeout(timer);
+        timer = setTimeout(() => {
+            const newVal = $('#gr-search').val();
+            let html = '';
+            if (newVal && newVal === oldSearchVal) {
+                accountList.filter(item => item && cur_uid !== item.id && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1))).forEach(item => {
+                    html += `<dd class="border-bottom p-2 mb-0" data-id="${item.id}">
+                        <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><spanclass="ml-auto">${item.mobile || ''}</span></p>
+                        <span class="text-muted">${item.role || ''}</span>
+                    </dd>`;
+                });
+                $('.book-list').empty();
+                $('.book-list').append(html);
+            } else {
+                if (!$('.acc-btn').length) {
+                    accountGroup.forEach((group, idx) => {
+                        if (!group) return;
+                        html += `<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="${idx}" data-type="hide"><i class="fa fa-plus-square"></i></a> ${group.groupName}</dt><div class="dd-content" data-toggleid="${idx}">`;
+                        group.groupList.forEach(item => {
+                            if (item.id !== cur_uid) {
+                                html += `<dd class="border-bottom p-2 mb-0" data-id="${item.id}">
+                                    <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span class="ml-auto">${item.mobile || ''}</span></p>
+                                    <span class="text-muted">${item.role || ''}</span>
+                                </dd>`;
+                            }
+                        });
+                        html += '</div>';
+                    });
+                    $('.book-list').empty();
+                    $('.book-list').append(html);
+                }
+            }
+        }, 400);
+    });
+
+    // 添加审批流程按钮逻辑
+    $('body').on('click', '.book-list dt', function () {
+        const idx = $(this).find('.acc-btn').attr('data-groupid');
+        const type = $(this).find('.acc-btn').attr('data-type');
+        if (type === 'hide') {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).show(() => {
+                $(this).children().find('i').removeClass('fa-plus-square').addClass('fa-minus-square-o');
+                $(this).find('.acc-btn').attr('data-type', 'show');
+
+            })
+        } else {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).hide(() => {
+                $(this).children().find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square');
+                $(this).find('.acc-btn').attr('data-type', 'hide');
+            })
+        }
+        return false
+    });
+
+    // 添加到审批流程
+    $('#book-list').on('click', 'dd', function () {
+        const id = parseInt($(this).data('id'));
+        if (id !== 0) {
+            postData('audit/add', { auditorId: id }, (datas) => {
+                const html = [];
+                // 如果是重新上报,添加到重新上报列表中
+                const auditorshtml = [];
+                for (const [index, data] of datas.entries()) {
+                    if (index !== 0) {
+                        html.push('<li class="list-group-item d-flex" auditorId="'+ data[0].audit_id +'">');
+                        html.push(`<div class="col-auto">${index}</div>`);
+                        html.push('<div class="col">');
+                        for (const auditor of data) {
+                            html.push(`<div class="d-inline-block mx-1"><i class="fa fa-user text-muted"></i> ${auditor.name} <small class="text-muted">${auditor.role}</small></div>`);
+                        }
+                        html.push('</div>');
+                        html.push('<div class="col-auto">');
+                        // todo 添加会签或签时
+                        // html.push('<span class="badge badge-pill badge-primary badge-bg-small"><small></small></span>');
+                        if (shenpi_status === shenpiConst.sp_status.sqspr || (shenpi_status === shenpiConst.sp_status.gdzs && index+1 !== datas.length)) {
+                            html.push('<a href="javascript: void(0)" class="text-danger pull-right">移除</a>');
+                        }
+                        html.push('</div>');
+                        html.push('</li>');
+                    }
+                    auditorshtml.push('<li class="list-group-item" data-auditorid="' + data.audit_id + '">');
+                    auditorshtml.push('<i class="fa ' + (index+1 === datas.length ? 'fa-stop-circle' : 'fa-chevron-circle-down') + '"></i> ');
+                    auditorshtml.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
+                    if (index === 0) {
+                        auditorshtml.push('<span class="pull-right">原报</span>');
+                    } else if (index+1 === datas.length) {
+                        auditorshtml.push('<span class="pull-right">终审</span>');
+                    } else {
+                        auditorshtml.push('<span class="pull-right">'+ transFormToChinese(index) +'审</span>');
+                    }
+                    auditorshtml.push('</li>');
+                }
+                $('#auditors').html(html.join(''));
+                $('#auditors-list').html(auditorshtml.join(''));
+            });
+        }
+    });
+    // 删除审批人
+    $('body').on('click', '#auditors li a', function () {
+        const li = $(this).parents('li');
+        const data = {
+            auditorId: parseInt(li.attr('auditorId')),
+        };
+        postData('audit/delete', data, (result) => {
+            li.remove();
+            for (const rst of result) {
+                const aLi = $('li[auditorId=' + rst.audit_id + ']');
+                $('span', aLi).text(rst.order + ' ' + rst.name + ' ');
+            }
+
+            // 如果是重新上报
+            // 令最后一个图标转换
+            $('#auditors-list li[data-auditorid="' + data.auditorId + '"]').remove();
+            if ($('#auditors-list li').length !== 0 && !$('#auditors-list li i').hasClass('fa-stop-circle')) {
+                $('#auditors-list li').eq($('#auditors-list li').length-1).children('i')
+                    .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
+            }
+            for (let i = 0; i < $('#auditors-list li').length; i++) {
+                $('#auditors-list li').eq(i).find('.pull-right').text(i === 0 ? '原报' : (i+1 === $('#auditors-list li').length ? '终' : transFormToChinese(i)) + '审');
+                // $('#auditors-list2 li').eq(i).find('.pull-right').text((i+1 === $('#auditors-list2 li').length ? '终' : transFormToChinese(i+1)) + '审');
+            }
+        });
+    });
+    // 退回选择修改审批人流程
+    $('#hideSp').click(function () {
+        $('#sp-list').modal('hide');
+    });
+    $('a[f-target]').click(function () {
+        $($(this).attr('f-target')).modal('show');
+    });
+
+    // 多层modal关闭后的滚动bug修复
+    $('#sp-list').on('hidden.bs.modal', function (e) {
+        $(document.body).addClass('modal-open');
+    });
+
+    // 重新审批获取手机验证码
+    // 获取验证码
+    let isPosting = false;
+    $("#get-code").click(function() {
+        if (isPosting) return false;
+
+        const btn = $(this);
+        $.ajax({
+            url: '/profile/code',
+            type: 'post',
+            data: { mobile: authMobile, type: 'shenpi' },
+            dataTye: 'json',
+            error: function() {
+                isPosting = false;
+                let csrfToken = Cookies.get('csrfToken_j');
+                xhr.setRequestHeader('x-csrf-token', csrfToken);
+            },
+            beforeSend: function() {
+                isPosting = true;
+            },
+            success: function(response) {
+                isPosting = false;
+                if (response.err === 0) {
+                    codeSuccess(btn);
+                    $("input[name='code']").removeAttr('readonly');
+                    $("#re-shenpi-btn").removeAttr('disabled');
+                } else {
+                    toast(response.msg, 'error');
+                }
+            }
+        });
+    });
+
+    // 管理员更改审批流程js部分
+    let timer2 = null;
+    let oldSearchVal2 = null;
+    $('body').on('input propertychange', '.gr-search', function(e) {
+        oldSearchVal2 = e.target.value;
+        timer2 && clearTimeout(timer2);
+        timer2 = setTimeout(() => {
+            const newVal = $(this).val();
+            const code = $(this).attr('data-code');
+            let html = '';
+            if (newVal && newVal === oldSearchVal2) {
+                accountList.filter(item => item && item.id !== stage_uid && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1))).forEach(item => {
+                    html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                        <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                class="ml-auto">${item.mobile || ''}</span></p>
+                        <span class="text-muted">${item.role || ''}</span>
+                    </dd>`
+                });
+                $('#' + code + '_dropdownMenu .book-list').empty();
+                $('#' + code + '_dropdownMenu .book-list').append(html);
+            } else {
+                if (!$('#' + code + '_dropdownMenu .acc-btn').length) {
+                    accountGroup.forEach((group, idx) => {
+                        if (!group) return;
+                        html += `<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="${idx}" data-type="hide"><i class="fa fa-plus-square"></i>
+                        </a> ${group.groupName}</dt>
+                        <div class="dd-content" data-toggleid="${idx}">`;
+                        group.groupList.forEach(item => {
+                            if (item.id !== stage_uid) {
+                                html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                                    <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                            class="ml-auto">${item.mobile || ''}</span></p>
+                                    <span class="text-muted">${item.role || ''}</span>
+                                </dd>`;
+                            }
+                        });
+                        html += '</div>';
+                    });
+                    $('#' + code + '_dropdownMenu .book-list').empty();
+                    $('#' + code + '_dropdownMenu .book-list').append(html);
+                }
+            }
+        }, 400);
+    });
+
+    const getAdminEditShenpiListHtml = function(auditGroup) {
+        const html = [];
+        for (const [i, group] of auditGroup.entries()) {
+            if (i === 0) continue;
+            for (const [j, auditor] of group.entries()) {
+                html.push('<tr>');
+                if (j === 0) {
+                    const auditTypeHtml = auditor.type === auditType.key.common ? '' : `<span class="ml-2 badge badge-pill badge-${auditType.info[auditor.audit_type].class} p-1"><small>${auditType.info[auditor.audit_type].short}</small></span>`;
+                    html.push(`<td class="text-left d-flex">${i + '审'}${auditTypeHtml}</td>`);
+                } else {
+                    html.push(`<td class="text-left d-flex"></td>`);
+                }
+                html.push(`<td></span> ${auditor.name} <small class="text-muted">${auditor.role}</small></td>`);
+                html.push(`<td style="text-align: center"><span class="${auditConst.auditStringClass[auditor.audit_status]}">${ auditor.audit_status !== auditConst.status.uncheck ? auditConst.auditString[auditor.audit_status] : '待审批' }</span></td>`);
+                html.push('<td style="text-align: center">');
+                if (auditor.audit_status === auditConst.status.checking && j === group.length - 1) {
+                    html.push('<span class="dropdown mr-2">',
+                        `<a href="javascript: void(0)" class="add-audit" id="${auditor.audit_id}_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">增加</a>`,
+                        makeSelectAudit(auditor.audit_id+'_add', auditor.audit_id, 'add'),'</div>', '</span>');
+                }
+                if (auditor.audit_status === auditConst.status.uncheck) {
+                    if (j === group.length - 1) {
+                        html.push('<span class="dropdown mr-2">',
+                            `<a href="javascript: void(0)" class="add-audit" id="${auditor.audit_id}_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">增加</a>`,
+                            makeSelectAudit(auditor.audit_id+'_add', auditor.audit_id, 'add'),'</div>', '</span>');
+                        if (auditor.audit_type !== auditType.key.common) {
+                            html.push('<span class="dropdown mr-2">',
+                                `<a href="javascript: void(0)" class="add-audit" id="${auditor.audit_id}_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">平级</a>`,
+                                makeSelectAudit(auditor.audit_id+'_add-sibling', auditor.audit_id, 'add-sibling'),'</div>', '</span>');
+                        }
+                    }
+                    html.push('<span class="dropdown mr-2">',
+                        `<a href="javascript: void(0)" class="add-audit" id="${auditor.audit_id}_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">更换</a>`,
+                        makeSelectAudit(auditor.audit_id+'_change', auditor.audit_id, 'change'),'</div>', '</span>');
+                    html.push(`<span class="dropdown">
+                                    <a href="javascript: void(0)" class="text-danger" title="移除" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">移除</a>
+                                    <div class="dropdown-menu">
+                                        <span class="dropdown-item" href="javascript:void(0);">确认移除审批人?</span>
+                                        <div class="dropdown-divider"></div>
+                                        <div class="px-2 py-1 text-center">
+                                            <button class="remove-audit btn btn-sm btn-danger" data-id="${auditor.audit_id}">移除</button>
+                                            <button class="btn btn-sm btn-secondary">取消</button>
+                                        </div>
+                                    </div>
+                                    </span>`);
+                }
+                html.push('</td>');
+                html.push('</tr>');
+            }
+        }
+        return html.join('');
+    };
+
+    $('body').on('click', '#admin-edit-shenpi dl dd', function () {
+        const id = parseInt($(this).attr('data-id'));
+        if (!id) return;
+
+        let this_aid = parseInt($(this).parents('.book-list').attr('data-aid'));
+        let this_operate = $(this).parents('.book-list').attr('data-operate');
+        const user = _.find(accountList, function (item) {
+            return item.id === id;
+        });
+        const auditorIndex = _.findIndex(auditorList, { audit_id: id });
+        if (auditorIndex !== -1) {
+            toastr.warning('该审核人已存在,请勿重复添加');
+            return;
+        }
+        const prop = {
+            operate: this_operate,
+            old_aid: this_aid,
+            new_aid: user.id,
+        };
+        postData('audit/save', prop, (datas) => {
+            $('#admin-edit-shenpi-list').html(getAdminEditShenpiListHtml(datas));
+            changeLiucheng(datas);
+        });
+    });
+
+    // 管理员移除审批人
+    $('body').on('click', '.remove-audit', function () {
+        const id = parseInt($(this).attr('data-id'));
+        const prop = {
+            operate: 'del',
+            old_aid: id,
+        };
+        postData('audit/save', prop, (datas) => {
+            $('#admin-edit-shenpi-list').html(getAdminEditShenpiListHtml(datas));
+            changeLiucheng(datas);
+        });
+    });
+
+    const getAuditTypeText = function (type) {
+        if (type === auditType.key.common) return '';
+        return `<span class="text-${auditType.info[type].class}">${auditType.info[type].long}</span>`;
+    };
+
+    function changeLiucheng(datas) {
+        const auditorshtml = [];
+        let lastAuditorHtml = [];
+        for (const [index,data] of datas.entries()) {
+            auditorshtml.push('<li class="list-group-item d-flex justify-content-between align-items-center">');
+            if (index === 0) {
+                auditorshtml.push('<span class="mr-1"><i class="fa fa fa-play-circle fa-rotate-90"></i></span>');
+            } else if (index+1 === datas.length) {
+                auditorshtml.push('<span class="mr-1"><i class="fa fa fa-stop-circle"></i></span>');
+            } else {
+                auditorshtml.push('<span class="mr-1"><i class="fa fa-chevron-circle-down"></i></span>');
+            }
+            auditorshtml.push('<span class="text-muted">');
+            for (const u of data) {
+                auditorshtml.push(`<small class="d-inline-block text-dark mx-1" title="${u.role}" data-auditorId="${u.audit_id}">${u.name}</small>`);
+            }
+            auditorshtml.push('</span>');
+            auditorshtml.push('<div class="d-flex ml-auto">');
+            if (data[0].audit_type !== auditType.key.common) auditorshtml.push(`<span class="badge badge-pill badge-${auditType.info[data[0].audit_type].class} p-1"><small>${auditType.info[data[0].audit_type].short}</small></span>`);
+            if (index === 0) {
+                auditorshtml.push('<span class="badge badge-light badge-pill ml-auto"><small>原报</small></span>');
+            } else if (index+1 === datas.length) {
+                auditorshtml.push('<span class="badge badge-light badge-pill"><small>终审</small></span>');
+            } else {
+                auditorshtml.push('<span class="badge badge-light badge-pill"><small>'+ transFormToChinese(index) +'审</small></span>');
+            }
+            auditorshtml.push('</div>');
+            auditorshtml.push('</li>');
+            if (data[0].audit_status === sam_auditConst.status.uncheck) {
+                lastAuditorHtml.push('<li class="timeline-list-item pb-2 is_uncheck">');
+                if (index < datas.length - 1) {
+                    lastAuditorHtml.push('<div class="timeline-item-tail"></div>');
+                }
+                lastAuditorHtml.push('<div class="timeline-item-icon bg-secondary text-light"></div>');
+
+                lastAuditorHtml.push('<div class="timeline-item-content">');
+                lastAuditorHtml.push(`<div class="py-1">
+                        <span class="text-black-50">
+                        ${ !index === datas.length - 1 ? data[0].audit_order + '' : '终' }审 ${getAuditTypeText(data[0].audit_type)}
+                        </span>
+                    </div>`);
+                lastAuditorHtml.push('<div class="card"><div class="card-body px-3 py-0">');
+                for (const [i, auditor] of data.entries()) {
+                    lastAuditorHtml.push(`<div class="card-text p-2 py-3 row ${ ( i > 0 ? 'border-top' : '') }">`);
+                    lastAuditorHtml.push(`<div class="col"><span class="h6">${auditor.name}</span><span class="text-muted ml-1">${auditor.role}</span></div>`);
+                    lastAuditorHtml.push('<div class="col">');
+                    lastAuditorHtml.push('</div>');
+                    lastAuditorHtml.push('</div>');
+                }
+                lastAuditorHtml.push('</div></div>');
+                lastAuditorHtml.push('</div>');
+                lastAuditorHtml.push('</li>');
+            }
+        }
+        $('.last-auditor-list .is_uncheck').remove();
+        $('.last-auditor-list').append(lastAuditorHtml.join(''));
+        $('.auditors-list').html(auditorshtml.join(''));
+
+    }
+
+    // 审批流程-选择审批人html 生成
+    function makeSelectAudit(code, aid, status) {
+        let divhtml = '';
+        accountGroup.forEach((group, idx) => {
+            let didivhtml = '';
+            if(group) {
+                group.groupList.forEach(item => {
+                    didivhtml += item.id !== stage_uid ? '<dd class="border-bottom p-2 mb-0 " data-id="' + item.id + '" >\n' +
+                        '<p class="mb-0 d-flex"><span class="text-primary">' + item.name + '</span><span\n' +
+                        '                                                                                class="ml-auto">' + item.mobile + '</span></p>\n' +
+                        '                                                                    <span class="text-muted">' + item.role + '</span>\n' +
+                        '                                                                    </dd>\n' : '';
+                });
+                divhtml += '<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="' + idx + '" data-type="hide"><i class="fa fa-plus-square"></i></a> ' + group.groupName + '</dt>\n' +
+                    '                                                                <div class="dd-content" data-toggleid="' + idx + '">\n' + didivhtml +
+                    '                                                                </div>\n';
+            }
+        });
+        let html = '<div class="dropdown-menu dropdown-menu-right" id="' + code + '_dropdownMenu" aria-labelledby="' + code + '_dropdownMenuButton" style="width:220px">\n' +
+            '                                                        <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"\n' +
+            '                                                                                     placeholder="姓名/手机 检索" autocomplete="off" data-code="' + code + '"></div>\n' +
+            '                                                        <dl class="list-unstyled book-list" data-aid="'+ aid +'" data-operate="'+ status +'">\n' + divhtml +
+            '                                                        </dl>\n' +
+            '                                                    </div>\n' +
+            '                                                </div>\n' +
+            '                                            </span>\n' +
+            '                                        </span>\n' +
+            '                                        </li>';
+        return html;
+    }
+});

+ 6 - 4
app/public/js/shares/sjs_setting.js

@@ -6,17 +6,19 @@ const sjsSettingObj = (function () {
         setting.selectedBackColor = '#fffacd';
         setting.tree = {
             getFont: function (sheet, data, row, col, defaultFont) {
-                if (sheet.zh_tree && data.level === 1) {
-                    return 'bold ' + defaultFont;
+                if (sheet.zh_tree) {
+                    const levelField = sheet.zh_tree.setting.level;
+                    return data[levelField] === 1 ? 'bold ' + defaultFont : defaultFont;
                 } else {
                     return defaultFont;
                 }
             },
             getColor: function (sheet, data, row, col, defaultColor) {
                 if (sheet.zh_tree) {
-                    if (data.level === 2) {
+                    const levelField = sheet.zh_tree.setting.level;
+                    if (data[levelField] === 2) {
                         return '#C4CAFB';
-                    } else if ((!data.b_code || data.b_code === '') && data.level > 2) {
+                    } else if ((!data.b_code || data.b_code === '') && data[levelField] > 2) {
                         return '#DFE8F9';
                     } else {
                         return defaultColor;

+ 279 - 0
app/public/js/shares/tools_att.js

@@ -0,0 +1,279 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+
+(function($){
+    $.ledger_att = function (setting) {
+        if (!setting.selector) return;
+        const obj = $(setting.selector);
+        const pageLength = 20;
+        let curNode = null, curPage = 0;
+        obj.html(
+            '<div class="sjs-bar">\n' +
+            '    <ul class="nav nav-tabs">\n' +
+            '        <li class="nav-item"><a class="nav-link active" data-toggle="tab" href="#att-cur" role="tab" att-type="cur" id="att-cur-button">当前节点</a></li>\n' +
+            '        <li class="nav-item"><a class="nav-link" data-toggle="tab" href="#att-all" role="tab" att-type="all">所有附件</a></li>\n' +
+            '        <li class="nav-item ml-auto pt-1">\n' +
+            '            <a href="javascript:void(0);" id="batch-download" class="btn btn-sm btn-primary" type="curr">批量下载</a>\n' +
+            '            <span id="showPage" style="display: none"><a href="javascript:void(0);" class="page-select ml-3" content="pre"><i class="fa fa-chevron-left"></i></a> <span id="att-cur-page">1</span>/<span id="att-total-page">10</span> <a href="javascript:void(0);" class="page-select mr-3" content="next"><i class="fa fa-chevron-right"></i></a></span>\n' +
+            (setting.readOnly ? '' : '            <a href="#upload" data-toggle="modal" data-target="#upload"  class="btn btn-sm btn-outline-primary ml-3">上传</a>\n') +
+            '        </li>\n' +
+            '    </ul>\n' +
+            '</div>\n' +
+            '<div class="sjs-sh tab-content">\n' +
+            '    <div class="tab-pane active" id="att-cur" style="height: 100%">\n' +
+            '        <div style="overflow:auto; overflow-x:hidden; height: 100%">\n' +
+            '            <div class="mt-1 mb-1" id="att-cur-hint"></div>\n' +
+            '            <table class="table table-sm table-bordered table-hover" style="word-break:break-all; table-layout: fixed">\n' +
+            '                <thead><tr><th width="25"><input type="checkbox" class="check-all-file"><th>文件名</th><th width="80">上传</th><th width="80">操作</th></tr></thead>\n' +
+            '                <tbody id="cur-att-list" class="list-table"></tbody>\n' +
+            '            </table>\n' +
+            '        </div>\n' +
+            '    </div>\n' +
+            '    <div class="tab-pane" id="att-all" style="height: 100%">\n' +
+            '        <div style="overflow:auto; overflow-x:hidden; height: 100%">\n' +
+            '            <table class="table table-sm table-bordered table-hover" style="word-break:break-all; table-layout: fixed">\n' +
+            '                <thead><tr><th width="25"><input type="checkbox" class="check-all-file"></th><th>文件名</th><th width="80">上传</th><th width="80">操作</th></tr></thead>\n' +
+            '                <tbody id="all-att-list" class="list-table"></tbody>\n' +
+            '           </table>\n' +
+            '        </div>\n' +
+            '    </div>\n' +
+            '</div>'
+        );
+        autoFlashHeight();
+        $('#att-cur-button')[0].click();
+
+        let allAtts = [], nodeIndexes = {};
+
+        const getAttHtml = function(att, tipNode = false) {
+            const html = [];
+            html.push('<tr>');
+            html.push(`<td width="25"><input type="checkbox" class="check-file" file-id=${att.id}></td>`);
+            let nodeInfo = '';
+            if (tipNode && att.node) nodeInfo = `${att.node.code || att.node.b_code || ''}/${att.node.name || ''}`;
+            const tipHtml = nodeInfo ? `${nodeInfo}\n${att.in_time}` : att.in_time;// nodeInfo ? `${nodeInfo}<br/>${att.in_time}` : att.in_time;
+            const tipType = 'title='; //'data-toggle="tooltip" data-html="true" data-placement="left" data-original-title=';
+            html.push(`<td><div class="d-flex"><a href="javascript:void(0)" ${tipType}"${tipHtml}" class="pl-0 col-11" file-id=${att.id}>${att.filename}${att.fileext}</a></div></td>`);
+            html.push(`<td>${att.username}</td>`);
+            const canDel = setting.readOnly ? false : att.uid === userID && (!setting.checked || att.extra_upload);
+            html.push('<td width="80">',
+                `<a class="ml-1" href="javascript:void(0)" ${tipType}"定位" name="att-locate" file-id="${att.id}"><i class="fa fa-crosshairs"></i></a>`,
+                att.viewpath ? `<a class="ml-1" href="${att.viewpath}" ${tipType}"预览"  target="_blank"><i class="fa fa-eye"></i></a>` : '',
+                `<a class="ml-1" href="javascript:void(0)" ${tipType}"下载" onclick="AliOss.downloadFile('${att.filepath}', '${att.filename}${att.fileext}')"><i class="fa fa-download"></i></a>`,
+                canDel ? `<a class="ml-1 text-danger" href="javascript:void(0)" name="att-delete" file-id="${att.id}"><i class="fa fa-close" ${tipType}"删除"></i></a>` : '',
+                '</td>');
+            html.push('</tr>');
+            return html.join('');
+        };
+        const refreshCurAttHtml = function () {
+            const html = [];
+            const atts = (nodeIndexes[curNode[setting.key]]) || [];
+            for (const att of atts) {
+                html.push(getAttHtml(att));
+            }
+            $('#cur-att-list').html(html.join());
+            $('[data-toggle="tooltip"]').tooltip();
+        };
+        const refreshAllAttHtml = function () {
+            let curPage = parseInt($('#att-cur-page').text());
+            if (allAtts.length === 0 && curPage !== 0) curPage = 0;
+            if (allAtts.length > 0 && curPage === 0) curPage = 1;
+            $('#att-cur-page').text(curPage);
+            const pageNum = Math.ceil(allAtts.length/pageLength);
+            $('#att-total-page').text(pageNum);
+            const currPageAttData = allAtts.slice((curPage-1)*pageLength, curPage*pageLength);
+            const html = [];
+            for(const att of currPageAttData) {
+                html.push(getAttHtml(att, true));
+            }
+            $('#all-att-list').html(html.join());
+            $('[data-toggle="tooltip"]').tooltip();
+        };
+        const getAllAttHtml = function (page = 1) {
+            curPage = allAtts.length ? page : 0;
+            $('#att-cur-page').text(curPage);
+            refreshAllAttHtml();
+        };
+        const getCurAttHtml = function (node) {
+            curNode = node;
+            $('#att-cur-hint').text(`${curNode.code || curNode.b_code || ''}/${curNode.name || ''}`);
+            refreshCurAttHtml();
+        };
+
+        // 选中行
+        $('body').on('click', '#all-att-list tr', function() {
+            $('#all-att-list tr').removeClass('bg-light');
+            $(this).addClass('bg-light');
+        });
+        $('body').on('click', '#cur-att-list tr', function() {
+            $('#cur-att-list tr').removeClass('bg-light');
+            $(this).addClass('bg-light');
+        });
+        // 切换 当前节点/所有附件
+        $('#fujian .nav-link').on('click', function () {
+            const tabPanel = $(this).attr('att-type');
+            if (tabPanel !== 'all') {
+                $('#showPage').hide();
+                $('#batch-download').prop('type', 'curr');
+            } else {
+                $('#showPage').show();
+                $('#batch-download').prop('type', 'all')
+            }
+        });
+        // 切换页数
+        $('.page-select').on('click', function () {
+            const totalPageNum = parseInt($('#att-total-page').text());
+            const lastPageNum = parseInt($('#att-cur-page').text());
+            const status = $(this).attr('content');
+            if (status === 'pre' && lastPageNum > 1) {
+                getAllAttHtml(lastPageNum-1);
+                $('#showAttachment').hide();
+                $('#att-all .check-all-file').prop('checked', false)
+            } else if (status === 'next' && lastPageNum < totalPageNum) {
+                getAllAttHtml(lastPageNum+1);
+                $('#showAttachment').hide();
+                $('#att-all .check-all-file').prop('checked', false)
+            }
+        });
+        // 批量下载
+        $('#batch-download').click(function() {
+            const self = this;
+            const files = [];
+            const type = $(this).prop('type');
+            let node = '';
+            if (type === 'curr') {
+                node = '#cur-att-list .check-file:checked'
+            } else {
+                node = '#all-att-list .check-file:checked'
+            }
+            $(node).each(function() {
+                const fid = $(this).attr('file-id');
+                const att = allAtts.find(function (item) {
+                    return item.id === parseInt(fid);
+                });
+                att && files.push(att);
+            });
+
+            if (files.length === 0) return;
+
+            $(self).attr('disabled', 'true');
+            AliOss.zipFiles(files, setting.zipName, (fails) => {
+                $(self).removeAttr('disabled');
+                if (fails.length === 0) {
+                    toastr.success('下载成功');
+                } else {
+                    toastr.warning(`下载成功(${fails.length}个文件下载失败)`);
+                }
+            }, () => {
+                $(self).removeAttr('disabled');
+                toastr.error('批量下载失败');
+            });
+        });
+        // 上传附件
+        $('#upload-file-btn').click(function () {
+            const files = $('#upload-file')[0].files;
+            const formData = new FormData();
+            formData.append('lid', curNode[setting.key]);
+            for (const file of files) {
+                if (file === undefined) {
+                    toastr.error('未选择上传文件!');
+                    return false;
+                }
+                const filesize = file.size;
+                if (filesize > 30 * 1024 * 1024) {
+                    toastr.error('存在上传文件大小过大!');
+                    return false;
+                }
+                const fileext = '.' + file.name.toLowerCase().split('.').splice(-1)[0];
+                if (whiteList.indexOf(fileext) === -1) {
+                    toastr.error('只能上传指定格式的附件!');
+                    return false;
+                }
+                formData.append('size', filesize);
+                formData.append('file[]', file);
+            }
+            postDataWithFile(setting.uploadUrl, formData, function (data) {
+                // 插入到attData中
+                data.forEach(d => {
+                    d.node = curNode;
+                    allAtts.push(d);
+                    _addToNodeIndex(d, true);
+                });
+                // 重新生成List
+                refreshAllAttHtml();
+                refreshCurAttHtml();
+
+                $('#upload').modal('hide');
+            }, function () {
+                toastr.error('附件上传失败');
+            });
+            $('#upload-file').val('');
+        });
+        $('body').on('click', 'a[name=att-locate]', function () {
+            const fid = this.getAttribute('file-id');
+            const att = allAtts.find(item => item.id === parseInt(fid));
+            setting.locate && setting.locate(att);
+        });
+        $('body').on('click', 'a[name=att-delete]', function () {
+            const fid = this.getAttribute('file-id');
+            const data = {id: fid};
+            postData(setting.deleteUrl, data, function (result) {
+                // 删除
+                const att_index = allAtts.findIndex(item => { return item.id === parseInt(fid); });
+                const att = allAtts[att_index];
+                allAtts.splice(att_index, 1);
+                const xi = nodeIndexes[att.node[setting.key]];
+                xi.splice(xi.findIndex(x => { return x.id === parseInt(fid); }), 1);
+                // 重新生成List
+                if (allAtts.length === 1) {
+                    getAllAttHtml();
+                } else {
+                    refreshAllAttHtml();
+                }
+                refreshCurAttHtml();
+            });
+        });
+        // 监听附件check是否选中
+        $('.list-table').on('click', '.check-file', function() {
+            const checkedList = $(this).parents('.list-table').children().find('input:checked');
+            const childs = $(this).parents('.list-table').children().length;
+            const checkBox = $(this).parents('.list-table').parent().find('.check-all-file');
+            if (checkedList.length === childs) {
+                checkBox.prop("checked", true);
+            } else {
+                checkBox.prop("checked", false);
+            }
+        });
+        $('.check-all-file').click(function() {
+            const isCheck = $(this).is(':checked');
+            $(this).parents('table').find('.list-table').each(function() {
+                $(this).find('input:checkbox').prop("checked", isCheck);
+            })
+        });
+
+        const _addToNodeIndex = function(att, isTop = false) {
+            const id = att[setting.key];
+            if (!nodeIndexes[id]) {
+                nodeIndexes[id] = [];
+            }
+            let xi = nodeIndexes[id];
+            isTop ? xi.unshift(att) : xi.push(att);
+        };
+        const loadDatas = function (datas) {
+            for (const d of datas) {
+                allAtts.push(d);
+                _addToNodeIndex(d);
+            }
+            getAllAttHtml();
+        };
+
+        return { loadDatas, getCurAttHtml }
+    };
+})(jQuery);

+ 15 - 1
app/router.js

@@ -433,6 +433,14 @@ module.exports = app => {
     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.post('/tender/:id/settle/:sorder/file/upload', sessionAuth, tenderCheck, uncheckTenderCheck, settleCheck, 'settleController.uploadFile');
+    app.post('/tender/:id/settle/:sorder/file/delete', sessionAuth, tenderCheck, uncheckTenderCheck, settleCheck, 'settleController.deleteFile');
+    app.post('/tender/:id/settle/:sorder/file/save', sessionAuth, tenderCheck, uncheckTenderCheck, settleCheck, 'settleController.saveFile');
+    // 结算审批
+    app.post('/tender/:id/settle/:sorder/audit/add', sessionAuth, tenderCheck, uncheckTenderCheck, settleCheck, 'settleController.addAudit');
+    app.post('/tender/:id/settle/:sorder/audit/delete', sessionAuth, tenderCheck, uncheckTenderCheck, settleCheck, 'settleController.deleteAudit');
+    app.post('/tender/:id/settle/:sorder/audit/start', sessionAuth, tenderCheck, uncheckTenderCheck, settleCheck, 'settleController.auditStart');
+    app.post('/tender/:id/settle/:sorder/audit/check', sessionAuth, tenderCheck, uncheckTenderCheck, settleCheck, 'settleController.auditCheck');
     // 结算汇总
     app.get('/tender/:id/settle/gather', sessionAuth, tenderCheck, uncheckTenderCheck, 'settleController.gather');
     app.get('/tender/:id/settle/gather/load', sessionAuth, tenderCheck, uncheckTenderCheck, 'settleController.loadGatherData');
@@ -488,7 +496,6 @@ module.exports = app => {
     app.post('/tender/:id/signReport/post', sessionAuth, tenderCheck, uncheckTenderCheck, 'reportArchiveController.signPost');
     app.post('/tender/:id/signReport/file', sessionAuth, tenderCheck, uncheckTenderCheck, 'reportArchiveController.signFile');
 
-
     // 变更管理
     app.get('/tender/:id/change', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.index');
     app.get('/tender/:id/change/status/:status', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.status');
@@ -526,6 +533,7 @@ module.exports = app => {
     app.post('/tender/:id/change/:cid/information/audit/add', sessionAuth, tenderCheck, uncheckTenderCheck, changeCheck, 'changeController.addAudit');
     app.post('/tender/:id/change/:cid/information/audit/delete', sessionAuth, tenderCheck, uncheckTenderCheck, changeCheck, 'changeController.deleteAudit');
     app.post('/tender/:id/change/cancel/audit', sessionAuth, tenderCheck, uncheckTenderCheck, changeCheck, 'changeController.checkAuditCancel');
+    app.post('/tender/:id/change/:cid/information/audit/save', sessionAuth, tenderCheck, uncheckTenderCheck, changeCheck, 'changeController.saveAudit');
     // 变更新增部位页
     app.get('/tender/:id/change/:cid/information/revise', sessionAuth, tenderCheck, uncheckTenderCheck, changeCheck, 'changeController.reviseInfo');
     app.post('/tender/:id/change/:cid/information/revise/update', sessionAuth, tenderCheck, uncheckTenderCheck, changeCheck, 'changeController.updateRevise');
@@ -548,6 +556,7 @@ module.exports = app => {
     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.post('/tender/:id/change/project/check/again', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, changeProjectCheck, 'changeController.checkProjectAgain');
     // 变更申请
     app.get('/tender/:id/change/apply', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.apply');
     app.get('/tender/:id/change/apply/status/:status', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.applyStatus');
@@ -567,6 +576,7 @@ module.exports = app => {
     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.post('/tender/:id/change/apply/check/again', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, changeApplyCheck, 'changeController.checkApplyAgain');
     // 变更方案
     app.get('/tender/:id/change/plan', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.plan');
     app.get('/tender/:id/change/plan/status/:status', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.planStatus');
@@ -586,6 +596,7 @@ module.exports = app => {
     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.post('/tender/:id/change/plan/check/again', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, changePlanCheck, 'changeController.checkPlanAgain');
     // 材料调差
     app.get('/tender/:id/measure/material', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.index');
     app.post('/tender/:id/measure/material/add', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, 'materialController.add');
@@ -673,6 +684,7 @@ module.exports = app => {
     app.post('/wap/login', 'wapController.login');
     app.get('/wap/logout', 'wapController.logout');
     app.get('/wap/dashboard', sessionAuth, 'wapController.dashboard');
+    app.get('/wap/dashboard/msg/:id', sessionAuth, 'wapController.msg');
     app.get('/wap/list', sessionAuth, 'wapController.list');
     app.get('/wap/tender/:id', sessionAuth, tenderCheck, uncheckTenderCheck, 'wapController.tender');
     app.get('/wap/tender/:id/stage/:order', sessionAuth, tenderCheck, uncheckTenderCheck, 'wapController.stage');
@@ -689,6 +701,7 @@ module.exports = app => {
 
     // 微信
     app.get('/wx', 'wechatController.index');
+    app.post('/wx', 'wechatController.replyMessage');
     app.get('/wx/oauth', 'wechatController.oauth');
     app.get('/wx/bind', wechatAuth, 'wechatController.bind');
     app.post('/wx/bindwx', wechatAuth, 'wechatController.bindwx');
@@ -717,6 +730,7 @@ module.exports = app => {
     app.post('/tender/:id/ledger/tag', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.billsTag');
     app.post('/tender/:id/revise/:rid/info/tag', sessionAuth, tenderCheck, uncheckTenderCheck, reviseCheck, 'tenderController.billsTag');
     app.post('/tender/:id/measure/stage/:order/tag', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'tenderController.billsTag');
+    app.post('/tender/:id/settle/:sorder/ledger/tag', sessionAuth, tenderCheck, uncheckTenderCheck, settleCheck, 'tenderController.billsTag');
 
     // 总分包
     app.post('/tender/:id/ledger/sumLoad', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.sumLoad');

+ 10 - 8
app/service/change.js

@@ -856,10 +856,12 @@ module.exports = app => {
                     });
                 });
                 await this.transaction.insert('zh_notice', records);
-
+                // 判断是否终审方法需要更改了,因为存在修改审批流程的操作,会使判断是否终审有问题
+                const curAudit = await this.ctx.service.changeAudit.getCurAuditor(changeData.cid, changeData.times);
+                const nextAudit = await this.ctx.service.changeAudit.getDataByCondition({ cid: changeData.cid, times: changeData.times, usort: curAudit.usort + 1, status: audit.flow.auditStatus.uncheck });
                 // 设置审批人通过
                 const audit_update = {
-                    id: postData.audit_id,
+                    id: curAudit.id,
                     sdesc: postData.sdesc,
                     status: audit.flow.auditStatus.checked,
                     sin_time: new Date(),
@@ -896,7 +898,7 @@ module.exports = app => {
                         await this.transaction.update(this.ctx.service.changeAuditList.tableName, list_update);
                     }
                 }
-                if (postData.audit_next_id === undefined) {
+                if (!nextAudit) {
                     // 变更令审批完成
                     change_update.status = audit.flow.status.checked;
                     change_update.p_code = postData.p_code;
@@ -935,20 +937,19 @@ module.exports = app => {
                 } else {
                     // 设置下一个审批人为审批状态
                     const nextAudit_update = {
-                        id: postData.audit_next_id,
+                        id: nextAudit.id,
                         status: audit.flow.auditStatus.checking,
                         sin_time: new Date(),
                     };
                     await this.transaction.update(this.ctx.service.changeAudit.tableName, nextAudit_update);
 
                     // 添加短信通知-需要审批提醒功能
-                    const nextAuditData = await this.ctx.service.changeAudit.getDataById(postData.audit_next_id);
                     const sms = new SMS(this.ctx);
                     const code = await sms.contentChange(changeData.code);
                     const shenpiUrl = await this.ctx.helper.urlToShort(
                         this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + changeData.tid + '/change/' + changeData.cid + '/information#shenpi'
                     );
-                    await this.ctx.helper.sendAliSms(nextAuditData.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), SmsAliConst.template.change_check, {
+                    await this.ctx.helper.sendAliSms(nextAudit.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), SmsAliConst.template.change_check, {
                         biangeng: code,
                         code: shenpiUrl,
                     });
@@ -960,7 +961,7 @@ module.exports = app => {
                         code: this.ctx.session.sessionProject.code,
                         c_name: changeData.name,
                     };
-                    await this.ctx.helper.sendWechat(nextAuditData.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
+                    await this.ctx.helper.sendWechat(nextAudit.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
                 }
                 change_update.total_price = total_price;
                 const options = {
@@ -1690,7 +1691,7 @@ module.exports = app => {
                 const updateChangeList = [];
                 for (const cl of changeList) {
                     const audit_amount = cl.audit_amount.split(',');
-                    const last_amount = audit_amount[audit_amount.length - 1];
+                    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,
@@ -1740,6 +1741,7 @@ module.exports = app => {
                 await this.ctx.helper.sendWechat(zsAudit.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
 
             } catch (error) {
+                console.log(error);
                 await this.transaction.rollback();
                 result = false;
             }

+ 71 - 5
app/service/change_apply_audit.js

@@ -286,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 });
+                await transaction.delete(this.ctx.service.changeApplyHistory.tableName, { caid: caId });
                 // todo 更新标段tender状态 ?
                 await transaction.commit();
             } catch (err) {
@@ -470,15 +470,25 @@ module.exports = app => {
             if (!audit) {
                 throw '审核数据错误';
             }
-            const sql = 'SELECT `tid`, `caid`, `aid`, `order` FROM ?? WHERE `caid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
-            const sqlParam = [this.tableName, caId, times];
-            const auditors = await this.db.query(sql, sqlParam);
+            // const sql = 'SELECT `tid`, `caid`, `aid`, `order` FROM ?? WHERE `caid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
+            // const sqlParam = [this.tableName, caId, times];
+            // const auditors = await this.db.query(sql, sqlParam);
+            const auditors = await this.getAuditGroupByList(caId, times);
             let order = 1;
+            const newAuditors = [];
             for (const a of auditors) {
                 a.times = times + 1;
                 a.order = order;
                 a.status = auditConst.status.uncheck;
                 order++;
+                newAuditors.push({
+                    tid: this.ctx.tender.id,
+                    caid: caId,
+                    times: a.times,
+                    order: a.order,
+                    status: a.status,
+                    aid: a.aid,
+                });
             }
             const transaction = await this.db.beginTransaction();
             try {
@@ -496,7 +506,7 @@ module.exports = app => {
                     times: times + 1,
                 });
                 // 拷贝新一次审核流程列表
-                await transaction.insert(this.tableName, auditors);
+                await transaction.insert(this.tableName, newAuditors);
                 // 微信模板通知
                 // const begin_audit = await this.getDataByCondition({
                 //     mid: materialId,
@@ -888,6 +898,62 @@ module.exports = app => {
             }
             return result;
         }
+
+        /**
+         * 重新审批变更申请
+         * @param { string } cid - 查询的清单
+         * @return {Promise<*>} - 可用的变更令列表
+         */
+        async checkAgain(change) {
+            // 初始化事务
+            const time = new Date();
+            const transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                // 获取终审
+                const zsAudit = await this.getAuditorByStatus(change.id, auditConst.status.checked);
+                const lastAudit = await this.getLastAudit(change.id, change.times);
+                const insert_audit_array = [];
+                // 新增2个审批状态到审批列表中
+                insert_audit_array.push({
+                    tid: change.tid,
+                    caid: change.id,
+                    aid: zsAudit.aid,
+                    times: zsAudit.times,
+                    order: lastAudit.order + 1,
+                    status: auditConst.status.checkAgain,
+                    begin_time: time,
+                    end_time: time,
+                    opinion: '',
+                });
+                // 新增2个审批人到审批列表中
+                insert_audit_array.push({
+                    tid: change.tid,
+                    caid: change.id,
+                    aid: zsAudit.aid,
+                    times: zsAudit.times,
+                    order: lastAudit.order + 2,
+                    status: auditConst.status.checking,
+                    begin_time: time,
+                });
+                await transaction.insert(this.tableName, insert_audit_array);
+
+                // 设置变更令审批中
+                await transaction.update(this.ctx.service.changeApply.tableName, {
+                    id: change.id,
+                    status: auditConst.status.checking,
+                    notice_code: null,
+                    notice_uid: null,
+                    decimal: null,
+                });
+                await transaction.commit();
+                result = true;
+            } catch (error) {
+                await transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
     }
 
     return ChangeApplyAudit;

+ 1 - 1
app/service/change_apply_history.js

@@ -45,7 +45,7 @@ module.exports = app => {
             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);
+            if (oldList.length > 0) await transaction.insert(this.ctx.service.changeApplyList.tableName, oldList);
         }
     }
 

+ 86 - 0
app/service/change_audit.js

@@ -227,6 +227,20 @@ module.exports = app => {
             return list;
         }
 
+        /**
+         * 获取不重复列表
+         * @param {Object} cid - 变更令id
+         * @param {int} times - 次数
+         * @return {object} 返回结果
+         */
+        async getListGroupByWithoutYB(cid, times, transaction = null) {
+            const sql = 'SELECT * FROM ?? where cid = ? AND times = ? AND usite != 0 AND usort in (SELECT MAX(usort) FROM ?? WHERE ' +
+                'cid = ? AND times = ? AND usite != 0 GROUP BY usite) ORDER BY usite asc';
+            const sqlParam = [this.tableName, cid, times, this.tableName, cid, times];
+            const list = transaction ? await transaction.query(sql, sqlParam) : await this.db.query(sql, sqlParam);
+            return list;
+        }
+
         async getListOrderByTimes(cid, times) {
             const sql = 'SELECT * FROM ?? WHERE ' +
                 'cid = ? AND times = ? ORDER BY usort';
@@ -1032,6 +1046,78 @@ module.exports = app => {
             const sqlParam = [this.tableName, cid, times, status];
             return transaction ? await transaction.queryOne(sql, sqlParam) : await this.db.queryOne(sql, sqlParam);
         }
+
+        async saveAudit(cid, times, data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const auditors = await this.getListGroupByWithoutYB(cid, times);
+                const now_audit = this._.find(auditors, { uid: data.old_aid });
+                if (data.operate === 'add') {
+                    if (now_audit.status !== auditConst.status.uncheck && now_audit.status !== auditConst.status.checking) {
+                        throw '当前人下无法操作新增';
+                    }
+                    const nowAuditInfo = await this.ctx.service.projectAccount.getDataById(data.new_aid);
+                    const newAudit = {
+                        tid: this.ctx.tender.id,
+                        cid,
+                        uid: data.new_aid,
+                        name: nowAuditInfo.name,
+                        company: nowAuditInfo.company,
+                        jobs: nowAuditInfo.role,
+                        usort: now_audit.usort+1,
+                        usite: now_audit.usite+1,
+                        times: times,
+                        status: auditConst.auditStatus.uncheck,
+                    };
+                    // order+1
+                    await this._syncOrderByDelete(transaction, cid, now_audit.usite+1, now_audit.usort+1, times, '+');
+                    await transaction.insert(this.tableName, newAudit);
+                    // 更新审批流程页数据,如果存在
+                } else if (data.operate === 'del') {
+                    if (now_audit.status !== auditConst.status.uncheck) {
+                        throw '当前人无法操作删除';
+                    }
+                    await transaction.delete(this.tableName, { cid, times, uid: now_audit.uid, usite: now_audit.usite });
+                    await this._syncOrderByDelete(transaction, cid, now_audit.usite, now_audit.usort, times);
+                    // 旧的更新为is_old为1
+                    // await transaction.update(this.tableName, { is_old: 1 }, {
+                    //     where: {
+                    //         sid: stageId,
+                    //         times,
+                    //         aid: data.old_aid,
+                    //     }
+                    // });
+                } else if (data.operate === 'change') {
+                    const nowAudit = await this.getDataByCondition({ cid, times, uid: now_audit.uid, usite: now_audit.usite });
+                    if (now_audit.status !== auditConst.status.uncheck || !nowAudit) {
+                        throw '当前人无法操作替换';
+                    }
+                    const nowAuditInfo = await this.ctx.service.projectAccount.getDataById(data.new_aid);
+                    nowAudit.uid = data.new_aid;
+                    nowAudit.name = nowAuditInfo.name;
+                    nowAudit.company = nowAuditInfo.company;
+                    nowAudit.jobs = nowAuditInfo.role;
+                    await transaction.update(this.tableName, nowAudit);
+                    // 旧的更新为is_old为1
+                    // await transaction.update(this.tableName, { is_old: 1 }, {
+                    //     where: {
+                    //         sid: stageId,
+                    //         times,
+                    //         aid: data.old_aid,
+                    //     }
+                    // });
+                }
+                if (this.ctx.tender.info.shenpi.change === shenpiConst.sp_status.gdspl || this.ctx.tender.info.shenpi.change === shenpiConst.sp_status.gdzs) {
+                    const newAuditors = await this.getListGroupByWithoutYB(cid, times, transaction);
+                    await this.ctx.service.shenpiAudit.updateAuditList(transaction, this.ctx.tender.id, this.ctx.tender.info.shenpi.change, shenpiConst.sp_type.change, this._.map(newAuditors, 'uid'));
+                }
+                // 更新到审批流程方法
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
     }
 
     return ChangeAudit;

+ 87 - 5
app/service/change_plan_audit.js

@@ -290,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 });
+                await transaction.delete(this.ctx.service.changePlanHistory.tableName, { cpid: cpId });
                 // todo 更新标段tender状态 ?
                 await transaction.commit();
             } catch (err) {
@@ -483,15 +483,25 @@ module.exports = app => {
             if (!audit) {
                 throw '审核数据错误';
             }
-            const sql = 'SELECT `tid`, `cpid`, `aid`, `order` FROM ?? WHERE `cpid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
-            const sqlParam = [this.tableName, cpId, times];
-            const auditors = await this.db.query(sql, sqlParam);
+            // const sql = 'SELECT `tid`, `cpid`, `aid`, `order` FROM ?? WHERE `cpid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
+            // const sqlParam = [this.tableName, cpId, times];
+            // const auditors = await this.db.query(sql, sqlParam);
+            const auditors = await this.getAuditGroupByList(cpId, times);
             let order = 1;
+            const newAuditors = [];
             for (const a of auditors) {
                 a.times = times + 1;
                 a.order = order;
                 a.status = auditConst.status.uncheck;
                 order++;
+                newAuditors.push({
+                    tid: this.ctx.tender.id,
+                    cpid: cpId,
+                    times: a.times,
+                    order: a.order,
+                    status: a.status,
+                    aid: a.aid,
+                });
             }
             const transaction = await this.db.beginTransaction();
             try {
@@ -509,7 +519,7 @@ module.exports = app => {
                     times: times + 1,
                 });
                 // 拷贝新一次审核流程列表
-                await transaction.insert(this.tableName, auditors);
+                await transaction.insert(this.tableName, newAuditors);
                 // 清单审批值删除并重算变更金额
                 await this.ctx.service.changePlanList.delAuditAmount(transaction, cpId);
                 // 微信模板通知
@@ -951,6 +961,78 @@ module.exports = app => {
             }
             return result;
         }
+
+        /**
+         * 重新审批变更方案
+         * @param { string } cid - 查询的清单
+         * @return {Promise<*>} - 可用的变更令列表
+         */
+        async checkAgain(change) {
+            // 初始化事务
+            const time = new Date();
+            const transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                // 获取终审
+                const zsAudit = await this.getAuditorByStatus(change.id, auditConst.status.checked);
+                const lastAudit = await this.getLastAudit(change.id, change.times);
+                const insert_audit_array = [];
+                // 新增2个审批状态到审批列表中
+                insert_audit_array.push({
+                    tid: change.tid,
+                    cpid: change.id,
+                    aid: zsAudit.aid,
+                    times: zsAudit.times,
+                    order: lastAudit.order + 1,
+                    status: auditConst.status.checkAgain,
+                    begin_time: time,
+                    end_time: time,
+                    opinion: '',
+                });
+                // 新增2个审批人到审批列表中
+                insert_audit_array.push({
+                    tid: change.tid,
+                    cpid: change.id,
+                    aid: zsAudit.aid,
+                    times: zsAudit.times,
+                    order: lastAudit.order + 2,
+                    status: auditConst.status.checking,
+                    begin_time: time,
+                });
+                await transaction.insert(this.tableName, insert_audit_array);
+
+                // 设置变更令审批中
+                await transaction.update(this.ctx.service.changePlan.tableName, {
+                    id: change.id,
+                    status: auditConst.status.checking,
+                });
+                // 清除上一人的值并调整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();
+                result = true;
+            } catch (error) {
+                await transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
     }
     return ChangePlanAudit;
 };

+ 1 - 1
app/service/change_plan_history.js

@@ -45,7 +45,7 @@ module.exports = app => {
             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);
+            if (oldList.length > 0) await transaction.insert(this.ctx.service.changePlanList.tableName, oldList);
         }
     }
 

+ 1 - 1
app/service/change_project.js

@@ -385,7 +385,7 @@ module.exports = app => {
                         change.cancancel = 4;// 审批人撤回退回原报
                     }
                 } else if (change.status === status.checkNo) {
-                    const onAuditor = this._.find(auditors, function(item) {
+                    const onAuditor = this._.findLast(auditors, function(item) {
                         return item.status === status.checkNo;
                     });
                     if (onAuditor && onAuditor.aid === accountId) {

+ 69 - 5
app/service/change_project_audit.js

@@ -73,7 +73,7 @@ module.exports = app => {
          * @return {Promise<*>}
          */
         async getAuditGroupByList(changeId, times) {
-            const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`cpid`, la.`aid`, la.`order` ' +
+            const sql = 'SELECT 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` = ? 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];
@@ -472,15 +472,26 @@ module.exports = app => {
             if (!audit) {
                 throw '审核数据错误';
             }
-            const sql = 'SELECT `tid`, `cpid`, `aid`, `order` FROM ?? WHERE `cpid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
-            const sqlParam = [this.tableName, cpId, times];
-            const auditors = await this.db.query(sql, sqlParam);
+            // 需要剔除原报可能存在修订进入流程的情况
+            // const sql = 'SELECT `tid`, `cpid`, `aid`, `order` FROM ?? WHERE `cpid` = ? and `times` = ? and `status` != ? AND `status` != ? GROUP BY `aid` ORDER BY `id` ASC';
+            // const sqlParam = [this.tableName, cpId, times];
+            // const auditors = await this.db.query(sql, sqlParam);
+            const auditors = await this.getAuditGroupByList(cpId, times);
             let order = 1;
+            const newAuditors = [];
             for (const a of auditors) {
                 a.times = times + 1;
                 a.order = order;
                 a.status = auditConst.status.uncheck;
                 order++;
+                newAuditors.push({
+                    tid: this.ctx.tender.id,
+                    cpid: cpId,
+                    times: a.times,
+                    order: a.order,
+                    status: a.status,
+                    aid: a.aid,
+                });
             }
             const transaction = await this.db.beginTransaction();
             try {
@@ -498,7 +509,7 @@ module.exports = app => {
                     times: times + 1,
                 });
                 // 拷贝新一次审核流程列表
-                await transaction.insert(this.tableName, auditors);
+                await transaction.insert(this.tableName, newAuditors);
                 // 微信模板通知
                 const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), this.ctx.change.uid));
                 const wechatData = {
@@ -982,6 +993,59 @@ module.exports = app => {
             }
             return result;
         }
+
+        /**
+         * 重新审批变更立项
+         * @param { string } cid - 查询的清单
+         * @return {Promise<*>} - 可用的变更令列表
+         */
+        async checkAgain(change) {
+            // 初始化事务
+            const time = new Date();
+            const transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                // 获取终审
+                const zsAudit = await this.getAuditorByStatus(change.id, auditConst.status.checked);
+                const lastAudit = await this.getLastAudit(change.id, change.times);
+                const insert_audit_array = [];
+                // 新增2个审批状态到审批列表中
+                insert_audit_array.push({
+                    tid: change.tid,
+                    cpid: change.id,
+                    aid: zsAudit.aid,
+                    times: zsAudit.times,
+                    order: lastAudit.order + 1,
+                    status: auditConst.status.checkAgain,
+                    begin_time: time,
+                    end_time: time,
+                    opinion: '',
+                });
+                // 新增2个审批人到审批列表中
+                insert_audit_array.push({
+                    tid: change.tid,
+                    cpid: change.id,
+                    aid: zsAudit.aid,
+                    times: zsAudit.times,
+                    order: lastAudit.order + 2,
+                    status: auditConst.status.checking,
+                    begin_time: time,
+                });
+                await transaction.insert(this.tableName, insert_audit_array);
+
+                // 设置变更令审批中
+                await transaction.update(this.ctx.service.changeProject.tableName, {
+                    id: change.id,
+                    status: auditConst.status.checking,
+                });
+                await transaction.commit();
+                result = true;
+            } catch (error) {
+                await transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
     }
 
     return ChangeProjectAudit;

+ 28 - 0
app/service/ledger_att.js

@@ -144,6 +144,34 @@ module.exports = app => {
                 });
             });
         }
+
+        async getViewDataByFid(id) {
+            const sql = 'SELECT att.id, att.lid, att.uid, att.filepath, att.filename, att.fileext, att.filesize, att.extra_upload, att.remark, att.in_time,' +
+                ' pa.name as `username`' +
+                ' FROM ' + this.tableName + ' att Left Join ' + this.ctx.service.projectAccount.tableName + ' pa On pa.id = att.uid' +
+                ' WHERE att.id = ?';
+            const result = await this.db.query(sql, [id]);
+            for (const r of result) {
+                r.filepath = this.ctx.app.config.fujianOssPath + r.filepath;
+                if (this.ctx.helper.canPreview(r.fileext)) r.viewpath = r.filepath;
+                r.in_time = this.ctx.moment(r.in_time * 1000).format('YYYY-MM-DD');
+            }
+            return result[0];
+        }
+
+        async getViewData(tid, settle_id = -1) {
+            const sql = 'SELECT att.id, att.lid, att.uid, att.filepath, att.filename, att.fileext, att.filesize, att.extra_upload, att.remark, att.in_time,' +
+                ' pa.name as `username`' +
+                ' FROM ' + this.tableName + ' att Left Join ' + this.ctx.service.projectAccount.tableName + ' pa On pa.id = att.uid' +
+                ' WHERE att.tid = ? AND att.settle_id = ? ORDER BY att.id DESC';
+            const result = await this.db.query(sql, [tid, settle_id]);
+            for (const r of result) {
+                r.filepath = this.ctx.app.config.fujianOssPath + r.filepath;
+                if (this.ctx.helper.canPreview(r.fileext)) r.viewpath = r.filepath;
+                r.in_time = this.ctx.moment(r.in_time * 1000).format('YYYY-MM-DD');
+            }
+            return result;
+        }
     }
     return LedgerAtt;
 };

+ 2 - 6
app/service/ledger_tag.js

@@ -33,12 +33,8 @@ module.exports = app => {
         async getDatas(tid, sid = -1, settleId = -1) {
             const sql = 'SELECT la.id, la.uid, la.lid, la.share, la.color, la.comment, pa.name as u_name FROM ' + this.tableName + ' la ' +
                 '  LEFT JOIN ' + this.ctx.service.projectAccount.tableName + ' pa ON la.uid = pa.id' +
-                '  WHERE la.tid = ? and la.sid = ? and (la.uid = ? or la.share) ORDER BY la.create_time DESC';
-            return await this.db.query(sql, [tid, sid, this.ctx.session.sessionUser.accountId]);
-            // const sql = 'SELECT la.id, la.uid, la.lid, la.share, la.color, la.comment, pa.name as u_name FROM ' + this.tableName + ' la ' +
-            //     '  LEFT JOIN ' + this.ctx.service.projectAccount.tableName + ' pa ON la.uid = pa.id' +
-            //     '  WHERE la.tid = ? and la.sid = ? and la.settle_id = ? and (la.uid = ? or la.share) ORDER BY la.create_time DESC';
-            // return await this.db.query(sql, [tid, sid, settleId, this.ctx.session.sessionUser.accountId]);
+                '  WHERE la.tid = ? and la.sid = ? and la.settle_id = ? and (la.uid = ? or la.share) ORDER BY la.create_time DESC';
+            return await this.db.query(sql, [tid, sid, settleId, this.ctx.session.sessionUser.accountId]);
         }
 
         /**

+ 1 - 1
app/service/message.js

@@ -115,7 +115,7 @@ module.exports = app => {
             const operate = id === 0 ? await this.db.insert(this.tableName, data) :
                 await this.db.update(this.tableName, data);
 
-            const result = operate.affectedRows > 0;
+            const result = operate.affectedRows > 0 ? operate.insertId : false;
 
             return result;
         }

+ 6 - 0
app/service/project_account.js

@@ -936,6 +936,12 @@ module.exports = app => {
             const result = await this.getDataById(id);
             return result ? result.self_category_level : '';
         }
+
+        async getOpenIdListByPid(pid) {
+            const sql = 'SELECT `wx_openid` FROM ?? WHERE `project_id` = ? AND `wx_openid` is not null';
+            const param = [this.tableName, pid];
+            return await this.db.query(sql, param);
+        }
     }
 
     return ProjectAccount;

+ 10 - 2
app/service/report_memory.js

@@ -1546,6 +1546,11 @@ module.exports = app => {
 
                 return x.create_time - y.create_time;
             });
+            const helper = this.ctx.helper;
+            result.forEach(x => {
+                x.PaidPercent = x.Price ? helper.mul(helper.div(x.Paid, x.Price, 4), 100) : 0;
+                x.Tree_PaidPercent = x.Tree_ContractPrice ? helper.mul(helper.div(x.Tree_ContractsPaid, x.Tree_ContractPrice, 4), 100) : 0;
+            });
             return result;
         }
 
@@ -1585,8 +1590,11 @@ module.exports = app => {
                 }
                 return sum;
             };
-            sortSumChildren(root, result, ['ContractPrice', 'ContractReturned', 'ContractsPaid']);
-            result.forEach(x => { delete x.children });
+            sortSumChildren(root, result, ['ContractPrice', 'ContractReturned', 'ContractsPaid', 'ContractDeductionTotal']);
+            result.forEach(x => {
+                x.PaidPercent = x.ContractPrice ? helper.mul(helper.div(x.ContractsPaid, x.ContractPrice, 4), 100) : 0;
+                delete x.children
+            });
             return result;
         }
     }

+ 0 - 1
app/service/rpt_stage_sum_memory.js

@@ -151,7 +151,6 @@ module.exports = app => {
                 order: [['order', 'ASC']],
             });
 
-            const gsSetting = JSON.parse(gsDefine.setting);
             for (const s of gsCustom.stages) {
                 const stage = await this.db.get(this.ctx.service.stage.tableName, { tid: this.ctx.tender.id, order: s });
                 if (!stage) continue;

+ 74 - 27
app/service/settle.js

@@ -10,6 +10,7 @@
 
 const auditConst = require('../const/audit');
 const projectLogConst = require('../const/project_log');
+const shenpiConst = require('../const/shenpi');
 
 module.exports = app => {
     class Settle extends app.BaseService {
@@ -36,7 +37,7 @@ module.exports = app => {
             });
             if (settles.length !== 0 && !this.ctx.session.sessionUser.is_admin) {
                 const last = settles[0];
-                if (last.status === auditConst.settle.status.uncheck && !this.ctx.tender.isTourist) {
+                if (last.audit_status === auditConst.settle.status.uncheck && !this.ctx.tender.isTourist) {
                     if (last.user_id !== this.ctx.session.sessionUser.accountId) {
                         settles.splice(0, 1);
                     }
@@ -44,7 +45,6 @@ module.exports = app => {
             }
             return settles;
         }
-
         async getLatestCompleteSettle(tenderId) {
             const settles = await this.getAllDataByCondition({
                 where: { tid: tenderId, status: auditConst.settle.status.checked },
@@ -67,7 +67,7 @@ module.exports = app => {
                 order: ['order'],
             });
             const pre = settles[settles.length - 1];
-            if (settles.length > 0 && pre.status !== auditConst.settle.status.checked) {
+            if (settles.length > 0 && pre.audit_status !== auditConst.settle.status.checked) {
                 throw '上一期未审批通过,请等待上一期审批通过后,再新增数据';
             }
             const checkedStage = await this.ctx.service.stage.getLastestCompleteStage(tenderId);
@@ -109,7 +109,6 @@ module.exports = app => {
 
             return newSettle;
         }
-
         /**
          * 编辑结算期
          *
@@ -125,7 +124,6 @@ module.exports = app => {
                 settle_period: period,
             }, { where: { tid: tenderId, settle_order: order } });
         }
-
         /**
          * 删除结算期
          * @param id
@@ -154,14 +152,14 @@ module.exports = app => {
 
             settle.user = await this.ctx.service.projectAccount.getAccountInfoById(settle.user_id);
             settle.auditors = await this.ctx.service.settleAudit.getAuditors(settle.id, settle.audit_times); // 全部参与的审批人
-            settle.auditorIds = this._.map(settle.auditors, 'aid');
-            settle.curAuditors = settle.auditors.filter(x => { return x.status === status.checking; }); // 当前流程中审批中的审批人
-            settle.curAuditorIds = this._.map(settle.curAuditors, 'aid');
-            settle.flowAuditors = settle.curAuditors.length > 0 ? settle.auditors.filter(x => { return x.order === settle.curAuditors[0].order; }) : []; // 当前流程中参与的审批人(包含会签时,审批通过的人)
-            settle.flowAuditorIds = this._.map(settle.flowAuditors, 'aid');
+            settle.auditorIds = this._.map(settle.auditors, 'audit_id');
+            settle.curAuditors = settle.auditors.filter(x => { return x.audit_status === status.checking; }); // 当前流程中审批中的审批人
+            settle.curAuditorIds = this._.map(settle.curAuditors, 'audit_id');
+            settle.flowAuditors = settle.curAuditors.length > 0 ? settle.auditors.filter(x => { return x.active_order === settle.curAuditors[0].active_order; }) : []; // 当前流程中参与的审批人(包含会签时,审批通过的人)
+            settle.flowAuditorIds = this._.map(settle.flowAuditors, 'audit_id');
             settle.auditorGroups = this.ctx.helper.groupAuditors(settle.auditors);
             settle.userGroups = this.ctx.helper.groupAuditorsUniq(settle.auditorGroups);
-            settle.finalAuditorIds = settle.userGroups[settle.userGroups.length - 1].map(x => { return x.aid; });
+            settle.finalAuditorIds = settle.userGroups[settle.userGroups.length - 1].map(x => { return x.audit_id; });
 
             // 协作相关
             settle.assists = []; //await this.service.settleAuditAss.getData(settle); // 全部协同人
@@ -180,12 +178,11 @@ module.exports = app => {
                 ? [settle.user_id, ...settle.userAssistIds]
                 : [settle.user_id, ...settle.userAssistIds, ...settle.auditorIds, ...settle.auditAssistIds];
         }
-
         async loadAuditViewData(settle) {
-            const times = settle.audit_status === auditConst.settle.status.checkNo ? settle.audit_times - 1 : settle.audit_times;
+            const auditTimes = settle.audit_status === auditConst.settle.status.checkNo ? settle.audit_times - 1 : settle.audit_times;
 
             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);
+            settle.auditHistory = await this.ctx.service.settleAudit.getAuditorHistory(settle.id, auditTimes);
             // 获取审批流程中左边列表
             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); // 全部参与的审批人
@@ -217,7 +214,7 @@ module.exports = app => {
             const accountId = this.ctx.session.sessionUser.accountId;
             if (settle.audit_status !== status.checkNo) {
                 // 找出当前操作人上一个审批人,包括审批完成的和退回上一个审批人的,同时当前操作人为第一人时,就是则为原报
-                if (settle.flowAuditors.find(x => { return x.status !== status.checking}) && settle.flowAuditorIds.indexOf(accountId) < 0) return; // 当前流程存在审批人审批通过时,不可撤回
+                if (settle.flowAuditors.find(x => { return x.audit_status !== status.checking}) && settle.flowAuditorIds.indexOf(accountId) < 0) return; // 当前流程存在审批人审批通过时,不可撤回
                 const flowAssists = settle.auditAssists.filter(x => { return settle.flowAuditorIds.indexOf(x.user_id) >= 0; });
                 if (flowAssists.find(x => { return x.confirm; })) return; //当前流程存在协审人确认时,不可撤回
                 if (settle.curAuditorIds.indexOf(accountId) < 0 && settle.flowAuditorIds.indexOf(accountId) >= 0) {
@@ -226,16 +223,16 @@ module.exports = app => {
                 }
 
                 const preAuditors = settle.curAuditors[0] && settle.curAuditors[0].order !== 1 ? settle.auditors.filter(x => { return x.order === settle.curAuditors[0].order - 1; }) : [];
-                const preAuditorCheckAgain = preAuditors.find(pa => { return pa.status === status.checkAgain; });
-                const preAuditorCheckCancel = preAuditors.find(pa => { return pa.status === status.checkCancel; });
+                const preAuditorCheckAgain = preAuditors.find(pa => { return pa.audit_status === status.checkAgain; });
+                const preAuditorCheckCancel = preAuditors.find(pa => { return pa.audit_status === status.checkCancel; });
                 const preAuditorHasOld = preAuditors.find(pa => { return pa.is_old === 1; });
-                const preAuditorIds = (preAuditorCheckAgain ? [] : preAuditors.map(x => { return x.aid })); // 重审不可撤回
+                const preAuditorIds = (preAuditorCheckAgain ? [] : preAuditors.map(x => { return x.audit_id })); // 重审不可撤回
                 if ((this._.isEqual(settle.flowAuditorIds, preAuditorIds) && preAuditorCheckCancel) || preAuditorHasOld) {
                     return; // 不可以多次撤回
                 }
 
-                const preAuditChecked = preAuditors.find(pa => { return pa.status === status.checked && pa.aid === accountId; });
-                const preAuditCheckNoPre = preAuditors.find(pa => { return pa.status === status.checkNoPre && pa.aid === accountId; });
+                const preAuditChecked = preAuditors.find(pa => { return pa.audit_status === status.checked && pa.audit_id === accountId; });
+                const preAuditCheckNoPre = preAuditors.find(pa => { return pa.audit_status === status.checkNoPre && pa.audit_id === accountId; });
                 if (preAuditorIds.indexOf(accountId) >= 0) {
                     if (preAuditChecked) {
                         settle.cancancel = 2;// 审批人撤回审批通过
@@ -247,9 +244,9 @@ module.exports = app => {
                     settle.cancancel = 1;// 原报撤回
                 }
             } else {
-                const lastAuditors = await this.service.settleAudit.getAuditors(settle.id, settle.times - 1);
-                const onAuditor = _.findLast(lastAuditors, { status: status.checkNo });
-                if (onAuditor.aid === accountId) {
+                const lastAuditors = await this.ctx.service.settleAudit.getAuditors(settle.id, settle.audit_times - 1);
+                const onAuditor = this._.findLast(lastAuditors, { audit_status: status.checkNo });
+                if (onAuditor.audit_id === accountId) {
                     settle.cancancel = 4;// 审批人撤回退回原报
                     settle.preAuditors = lastAuditors.filter(x => { return x.order === onAuditor.order });
                 }
@@ -267,18 +264,18 @@ module.exports = app => {
                 settle.readOnly = true;
             }
             // 读取数据相关
-            settle.curTimes = settle.audit_status === status.checkNo && settle.readOnly ? settle.times - 1 : settle.times;
+            settle.curTimes = settle.audit_status === status.checkNo && settle.readOnly ? settle.audit_times - 1 : settle.audit_times;
             // 协作人相关
 
             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;
+                settle.curTimes = settle.audit_times;
             } 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;
+                    settle.curTimes = settle.audit_times;
                 }
             } else {
                 const ass = settle.auditAssists.find(x => { return settle.flowAuditorIds.indexOf(x.user_id) >= 0 && x.ass_user_id === accountId; });
@@ -286,7 +283,7 @@ module.exports = app => {
                     settle.assist = ass;
                 }
                 if (!settle.readOnly) {
-                    settle.readOnly = !_.isEqual(settle.flowAuditorIds, settle.curAuditorIds);
+                    settle.readOnly = !this._.isEqual(settle.flowAuditorIds, settle.curAuditorIds);
                     settle.canCheck = true;
                 }
             }
@@ -317,6 +314,56 @@ module.exports = app => {
             // 可否撤回,是哪一种撤回
             await this._doCheckSettleCanCancel(settle);
         }
+        async checkSettleShenpi(settle) {
+            const status = auditConst.settle.status;
+            const info = this.ctx.tender.info;
+            const shenpi_status = info.shenpi.settle;
+            if ((settle.audit_status === status.uncheck || settle.audit_status === status.checkNo) && shenpi_status !== shenpiConst.sp_status.sqspr) {
+                // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
+                const auditList = await this.ctx.service.settleAudit.getAllDataByCondition({ where: { settle_id: settle.id, audit_times: settle.audit_times }, orders: [['audit_order', 'asc']] });
+                auditList.shift();
+                if (shenpi_status === shenpiConst.sp_status.gdspl) {
+                    const shenpiList = await this.ctx.service.shenpiAudit.getAllDataByCondition({ where: { tid: settle.tid, sp_type: shenpiConst.sp_type.settle, sp_status: shenpi_status } });
+                    // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
+                    let sameAudit = auditList.length === shenpiList.length;
+                    if (sameAudit) {
+                        for (const audit of auditList) {
+                            const shenpi = shenpiList.find(x => { return x.audit_id === audit.audit_id; });
+                            if (!shenpi || shenpi.audit_order !== audit.audit_order || shenpi.audit_type !== audit.audit_type) {
+                                sameAudit = false;
+                                break;
+                            }
+                        }
+                    }
+                    if (!sameAudit) {
+                        await this.ctx.service.settleAudit.updateNewAuditList(settle, shenpiList);
+                        await this.loadRelaUser(settle);
+                    }
+                } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
+                    const shenpiInfo = await this.ctx.service.shenpiAudit.getDataByCondition({ tid: settle.tid, sp_type: shenpiConst.sp_type.settle, sp_status: shenpi_status });
+                    // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
+                    const lastAuditors = auditList.filter(x => { x.order === auditList.order; });
+                    if (shenpiInfo && (lastAuditors.length === 0 || (lastAuditors.length > 1 || shenpiInfo.audit_id !== lastAuditors[0].audit_id))) {
+                        await this.ctx.service.settleAudit.updateLastAudit(settle, auditList, shenpiInfo.audit_id);
+                        await this.loadRelaUser(settle);
+                    } else if (!shenpiInfo) {
+                        // 不存在终审人的状态下这里恢复为授权审批人
+                        this.tender.info.shenpi.settle = shenpiConst.sp_status.sqspr;
+                    }
+                }
+            }
+        }
+
+        async doSettle(transaction, settle) {
+            const SettleObj = require('../lib/settle');
+            const settleObj = new SettleObj(this.ctx);
+            const [settleBills, settlePos, settleSum] = await settleObj.doSettle(settle);
+            await transaction.delete(this.ctx.service.settleBills.tableName, { settle_id: settle.id });
+            await transaction.insert(this.ctx.service.settleBills.tableName, settleBills);
+            await transaction.delete(this.ctx.service.settlePos.tableName, { settle_id: settle.id });
+            await transaction.insert(this.ctx.service.settlePos.tableName, settlePos);
+            return settleSum;
+        }
     }
 
     return Settle;

+ 574 - 27
app/service/settle_audit.js

@@ -10,6 +10,9 @@
 
 const auditConst = require('../const/audit');
 const auditType = auditConst.auditType;
+const pushType = auditConst.pushType;
+const smsTypeConst = require('../const/sms_type');
+const wxConst = require('../const/wechat_template');
 
 module.exports = app => {
     class SettleAudit extends app.BaseService {
@@ -24,43 +27,44 @@ module.exports = app => {
             this.tableName = 'settle_audit';
         }
 
-        async getAuditors(settleId, times) {
-            return await this.getAllDataByCondition({ where: { settle_id: settleId, audit_times: times } }); // 全部参与的审批人
+        // ***** 查询审批人相关
+        // 获取全部参与人
+        async getAuditors(settleId, auditTimes) {
+            return await this.getAllDataByCondition({ where: { settle_id: settleId, audit_times: auditTimes } }); // 全部参与的审批人
         }
-
-        async getAuditorGroup(settleId, times) {
-            const auditors = await this.getAuditors(settleId, times); // 全部参与的审批人
+        // 获取全部参与人 分组
+        async getAuditorGroup(settleId, auditTimes) {
+            const auditors = await this.getAuditors(settleId, auditTimes); // 全部参与的审批人
             return this.ctx.helper.groupAuditors(auditors, 'active_order');
         }
-
+        // 获取全部参与人 去重
         async getUniqAuditors(settle) {
             const auditors = await this.getAuditors(settle.id, settle.audit_times); // 全部参与的审批人
             const result = [];
             auditors.forEach(x => {
-                if (result.findIndex(r => { return x.aid === r.aid && x.audit_order === x.audit_order; }) < 0) {
+                if (result.findIndex(r => { return x.audit_id === r.audit_id && x.audit_order === x.audit_order; }) < 0) {
                     result.push(x);
                 }
             });
             return result;
         }
-
-        // 去重
-        async getUniqAuditorsGroup(settleId, times) {
-            const group = await this.getAuditorGroup(settleId, times);
+        // 获取全部参与人 分组 去重
+        async getUniqAuditorsGroup(settleId, auditTimes) {
+            const group = await this.getAuditorGroup(settleId, auditTimes);
             return this.ctx.helper.groupAuditorsUniq(group);
         }
-
-        async getAuditorsByStatus(settleId, status, times) {
-            const cur = await this.db.queryOne(`SELECT * From ${this.tableName} where settle_id = ? AND audit_times = ? AND audit_status = ? ORDER By audit_times DESC, audit_order DESC `, [settleId, times, status]);
+        // 获取某个状态的审批人
+        async getAuditorsByStatus(settleId, auditStatus, auditTimes) {
+            const cur = await this.db.queryOne(`SELECT * From ${this.tableName} where settle_id = ? AND audit_times = ? AND audit_status = ? ORDER By audit_times DESC, audit_order DESC `, [settleId, auditTimes, auditStatus]);
             if (!cur) return [];
 
-            return await this.getAllDataByCondition({ where: { settle_id: settleId, audit_times: times, audit_order: cur.audit_order}});
+            return await this.getAllDataByCondition({ where: { settle_id: settleId, audit_times: auditTimes, audit_order: cur.audit_order}});
         }
-
-        async getAuditorHistory(settleId, times, reverse = false) {
+        // 获取全部审批历史
+        async getAuditorHistory(settleId, auditTimes, reverse = false) {
             const history = [];
-            if (times >= 1) {
-                for (let i = 1; i <= times; i++) {
+            if (auditTimes >= 1) {
+                for (let i = 1; i <= auditTimes; i++) {
                     const auditors = await this.getAuditors(settleId, i);
                     const group = this.ctx.helper.groupAuditors(auditors);
                     const historyGroup = [];
@@ -71,19 +75,17 @@ module.exports = app => {
                             audit_type: g[0].audit_type, audit_order: g[0].audit_order,
                             auditors: g
                         };
-                        if (his.audit_type === auditType.key.common) {
-                            his.name = g[0].name;
-                        } else {
-                            his.name = this.ctx.helper.transFormToChinese(his.audit_order) + '审';
-                        }
                         his.is_final = his.audit_order === max_order;
+                        his.auditName = his.audit_order === 0 ? '原报' : (his.is_final ? '终审' : his.audit_order + '审');
+                        his.auditCnName = his.audit_order === 0 ? '原报' : (his.is_final ? '终审' : this.ctx.helper.transFormToChinese(his.audit_order) + '审');
+                        his.name = his.audit_type === auditType.key.common ? g[0].name : his.auditName;
                         let audit_time;
                         g.forEach(x => {
-                            if (x.status === auditConst.settle.status.checkSkip) return;
-                            if (!his.status || x.status === auditConst.settle.status.checking) his.audit_status = x.audit_status;
+                            if (x.audit_status === auditConst.settle.status.checkSkip) return;
+                            if (!his.audit_status || x.audit_status === auditConst.settle.status.checking) his.audit_status = x.audit_status;
                             if (x.audit_time && (!audit_time || x.audit_time > audit_time)) {
                                 audit_time = x.audit_time;
-                                if (his.status !== auditConst.settle.status.checking) his.audit_status = x.audit_status;
+                                if (his.audit_status !== auditConst.settle.status.checking) his.audit_status = x.audit_status;
                             }
                         });
                         if (audit_time) {
@@ -104,7 +106,125 @@ module.exports = app => {
             }
             return history;
         }
+        async getAllAuditors(tenderId) {
+            const sql =
+                'SELECT sa.audit_id, sa.tid FROM ' + this.tableName + ' sa' +
+                '  LEFT JOIN ' + this.ctx.service.tender.tableName + ' t On sa.tid = t.id' +
+                '  WHERE t.id = ?' +
+                '  GROUP BY sa.audit_id';
+            const sqlParam = [tenderId];
+            return this.db.query(sql, sqlParam);
+        }
+        // ***** 查询审批人相关
 
+        // ***** 修改审批人相关
+        /**
+         * 获取 最新审核顺序
+         *
+         * @param {Number} settleId - 期id
+         * @param {Number} auditTimes - 第几次审批
+         * @return {Promise<number>}
+         */
+        async getNewOrder(settleId, auditTimes = 1) {
+            const sql = `SELECT Max(active_order) As max_order, Max(audit_order) As max_audit_order FROM ${this.tableName} Where settle_id = ? and audit_times = ?`;
+            const result = await this.db.queryOne(sql, [settleId, auditTimes]);
+            return result && result.max_order ? [result.max_order + 1, result.max_audit_order + 1] : [1, 1];
+        }
+        /**
+         * 同步审核人order
+         * @param transaction - 事务
+         * @param {Number} settleId - 结算期id
+         * @param {Number} auditOrder - 审核顺序(实际顺序)
+         * @param {Number} auditTimes - 第几次审批
+         * @param {String} selfOperate - 操作符(+/-)
+         * @return {Promise<*>}
+         * @private
+         */
+        async _syncAuditOrder(transaction, settleId, auditOrder, auditTimes, selfOperate = '-') {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('settle_id', {
+                value: settleId,
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere('active_order', {
+                value: auditOrder,
+                operate: '>=',
+            });
+            this.sqlBuilder.setAndWhere('audit_times', {
+                value: auditTimes,
+                operate: '=',
+            });
+            this.sqlBuilder.setUpdateData('active_order', {
+                value: 1,
+                selfOperate: selfOperate,
+            });
+            this.sqlBuilder.setUpdateData('audit_order', {
+                value: 1,
+                selfOperate: selfOperate,
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+            const data = await transaction.query(sql, sqlParam);
+
+            return data;
+        }
+        /**
+         * 新增审核人
+         *
+         * @param {Number} settleId - 期id
+         * @param {Number} auditor - 审核人
+         * @param {Number} auditTimes - 第几次审批
+         * @param {Number} is_gdzs - 是否固定终审
+         * @return {Promise<number>}
+         */
+        async addAuditor(settleId, auditor, auditTimes = 1, is_gdzs = 0) {
+            let [newOrder, newAuditOrder] = await this.getNewOrder(settleId, auditTimes);
+            // 判断是否存在固定终审,存在则newOrder - 1并使终审order+1
+            newOrder = is_gdzs === 1 ? newOrder - 1 : newOrder;
+            newAuditOrder = is_gdzs === 1 ? newAuditOrder - 1 : newAuditOrder;
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                if (is_gdzs) await this._syncAuditOrder(transaction, settleId, newOrder, auditTimes, '+');
+                const data = {
+                    tid: this.ctx.tender.id, settle_id: settleId, audit_id: auditor.id,
+                    name: auditor.name, company: auditor.company, role: auditor.role, mobile: auditor.mobile,
+                    audit_times: auditTimes, active_order: newOrder, audit_status: auditConst.settle.status.uncheck,
+                    audit_order: newAuditOrder, audit_type: auditType.key.common,
+                };
+                const result = await transaction.insert(this.tableName, data);
+                await transaction.commit();
+                return result.effectRows = 1;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return false;
+        }
+        /**
+         * 移除审核人
+         *
+         * @param {Number} settleId - 期id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} auditTimes - 第几次审批
+         * @return {Promise<boolean>}
+         */
+        async deleteAuditor(settleId, auditorId, auditTimes = 1) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const condition = { settle_id: settleId, audit_id: auditorId, audit_times: auditTimes };
+                const auditor = await this.getDataByCondition(condition);
+                if (!auditor) throw '审批人不存在';
+                // 移除整个流程的人
+                await transaction.delete(this.tableName, { settle_id: settleId, active_order: auditor.active_order, audit_times: auditTimes});
+                await this._syncAuditOrder(transaction, settleId, auditor.active_order, auditTimes);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
+        // 拷贝上一期审批流程
         async copyPreAuditors(transaction, preSettle, newSettle) {
             const auditors = preSettle ? await this.getUniqAuditors(preSettle) : [];
             const newAuditors = [];
@@ -130,7 +250,434 @@ module.exports = app => {
             const result = await transaction.insert(this.tableName, newAuditors);
             if (result.affectedRows !== newAuditors.length) throw '初始化审批流程错误';
         }
+        // 固定审批流-更新
+        async updateNewAuditList(settle, newList) {
+            const newAuditsInfo = await this.ctx.service.projectAccount.getAllDataByCondition({ where: { id: newList.map(x => { return x.audit_id; })} });
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先删除旧的审批流,再添加新的
+                await transaction.query(`DELETE FROM ${this.tableName} where settle_id = ? and audit_times = ? and audit_order > 0`, [settle.id, settle.audit_times]);
+                // todo 协同
+                //await transaction.delete(this.ctx.service.settleAuditAss.tableName, { settle_id: settle.id, audit_times: settle.audit_times });
+                const newAuditors = [];
+                for (const auditor of newList) {
+                    const auditorInfo = newAuditsInfo.find(x => { return x.id === auditor.audit_id; });
+                    if (!auditorInfo) throw '配置的审批人不存在';
+                    newAuditors.push({
+                        tid: settle.tid, settle_id: settle.id, audit_id: auditor.audit_id,
+                        name: auditorInfo.name, company: auditorInfo.company, role: auditorInfo.role, mobile: auditorInfo.mobile,
+                        audit_times: settle.audit_times, active_order: auditor.audit_order, audit_status: auditConst.settle.status.uncheck,
+                        audit_type: auditor.audit_type, audit_order: auditor.audit_order,
+                    });
+                }
+                if(newAuditors.length > 0) await transaction.insert(this.tableName, newAuditors);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+        // 固定终审-更新
+        async updateLastAudit(settle, auditList, lastId) {
+            const lastUser = await this.ctx.service.projectAccount.getDataById(lastId);
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先判断auditList里的aid是否与lastId相同,相同则删除并重新更新order
+                const existAudit = auditList.find(x => { return x.audit_id === lastId });
+                let auditOrder = auditList.length > 0 ? auditList.reduce((rst, a) => { return Math.max(rst, a.active_order)}, 0) + 1 : 1; // 最大值 + 1
+                if (existAudit) {
+                    await transaction.delete(this.tableName, { settle_id: settle.id, audit_times: settle.audit_times, audit_id: lastId });
+                    const sameOrder = auditList.filter(x => { return x.active_order === existAudit.active_order });
+                    if (sameOrder.length === 1) {
+                        const updateData = [];
+                        auditList.forEach(x => {
+                            if (x.active_order <= existAudit.active_order) return;
+                            updateData.push({id: x.id, active_order: x.active_order - 1, audit_order: x.audit_order - 1});
+                        });
+                        if (updateData.length > 0) {
+                            await transaction.updateRows(updateData);
+                        }
+                        auditOrder = auditOrder - 1;
+                    }
+                }
+                // 添加终审
+                const newAuditor = {
+                    tid: settle.tid, settle_id: settle.id, audit_id: lastId,
+                    name: lastUser.name, company: lastUser.company, role: lastUser.role, mobile: lastUser.mobile,
+                    audit_times: settle.audit_times, active_order: auditOrder, audit_status: auditConst.settle.status.uncheck,
+                    audit_type: auditType.key.common, audit_order: auditOrder,
+                };
+                await transaction.insert(this.tableName, newAuditor);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+        // ***** 修改审批人相关
+
+        // ***** 审批操作
+        /**
+         * 用于添加推送所需的content内容
+         * @param {Number} pid 项目id
+         * @param {Number} tid 台账id
+         * @param {Number} sid 期id
+         * @param {Number} uid 审核人id
+         */
+        async _getNoticeContent(pid, tid, sid, uid, opinion = '') {
+            const noticeSql =
+                'SELECT * FROM (SELECT ' +
+                '  t.`id` As `tid`, t.`name`, s.`settle_order` as `order`, pa.`name` As `su_name`, pa.role As `su_role`' +
+                `  FROM (SELECT * FROM ${this.ctx.service.tender.tableName} WHERE id = ? ) As t` +
+                `  LEFT JOIN ${this.ctx.service.settle.tableName} As s On s.id =  ?` +
+                `  LEFT JOIN ${this.ctx.service.projectAccount.tableName} As pa ON pa.id = ?` +
+                '  WHERE  t.`project_id` = ? ) as new_t GROUP BY new_t.`tid`';
+            const noticeSqlParam = [tid, sid, uid, pid];
+            const content = await this.db.query(noticeSql, noticeSqlParam);
+            if (content.length) {
+                content[0].opinion = opinion;
+            }
+            return content.length ? JSON.stringify(content[0]) : '';
+        }
+        async start(settle) {
+            const audits = await this.getAllDataByCondition({ where: { settle_id: settle.id, audit_times: settle.audit_times, audit_order: 1 } });
+            if (audits.length === 0) {
+                if(this.ctx.tender.info.shenpi.settle === shenpiConst.sp_status.gdspl) {
+                    throw '请联系管理员添加审批人';
+                } else {
+                    throw '请先选择审批人,再上报数据';
+                }
+            }
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                const audit_time = new Date();
+                // 更新原报数据
+                await transaction.update(this.tableName, { audit_status: auditConst.settle.status.checked, audit_time }, { where: { settle_id: settle.id, audit_times: settle.audit_times, audit_order: 0 } });
+                // 更新下一审批人数据
+                const updateData = audits.map(x => { return { id: x.id, audit_status: auditConst.settle.status.checking } });
+                await transaction.updateRows(this.tableName, updateData);
+                // 生成结算台账
+                const settleSum = await this.ctx.service.settle.doSettle(transaction, settle);
+                // todo 计算部分缓存汇总数据
+                await transaction.update(this.ctx.service.settle.tableName, {
+                    id: settle.id, audit_status: auditConst.settle.status.checking,
+                    ...settleSum,
+                });
+                // 多人协同,取消下一审批人存在的锁定
+                // await this.ctx.service.settleAuditAss.cancelLock(settle, audits.map(x => { return x.audit_id; }), transaction);
+
+                // todo 添加短信通知-需要审批提醒功能
+                const shenpiUrl = await this.ctx.helper.urlToShort(this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + settle.tid + '/settle/' + settle.settle_order);
+                const auditIds = this._.map(audits, 'audit_id');
+                const users = this._.map(settle.auditAssists.filter(x => { return auditIds.indexOf(x.user_id) >= 0; }), 'ass_user_id');
+                users.push(...auditIds);
+                // await this.ctx.helper.sendAliSms(users, smsTypeConst.const.JS, smsTypeConst.judge.approval.toString(), SmsAliConst.template.settle_check, {
+                //     qi: settle.audit_order,
+                //     code: shenpiUrl,
+                // });
+                // 微信模板通知
+                const wechatData = {
+                    wap_url: shenpiUrl,
+                    qi: settle.settle_order,
+                    status: wxConst.status.check,
+                    tips: wxConst.tips.check,
+                    code: this.ctx.session.sessionProject.code,
+                };
+                await this.ctx.helper.sendWechat(users, smsTypeConst.const.JS, smsTypeConst.judge.approval.toString(), wxConst.template.settle, wechatData);
+
+                // todo 上报/审批 - 检查三方特殊推送
+                // await this.ctx.service.specMsg.addSettleMsg(transaction, this.ctx.session.sessionProject.id, settle, pushOperate.settle.flow);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+        async _checked(settle, opinion) {
+            const accountId = this.ctx.session.sessionUser.accountId;
+            const time = new Date();
+            const selfAudit = settle.curAuditors.find(x => { return x.audit_id === accountId; });
+            if (!selfAudit) throw '当前标段您无权审批';
+            const nextAudits = await this.getAllDataByCondition({ where: { settle_id: settle.id, audit_times: settle.audit_times, active_order: selfAudit.active_order + 1 } });
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 添加通知
+                const noticeContent = await this._getNoticeContent(this.ctx.session.sessionProject.id, settle.tid, settle.id, accountId, opinion);
+                const defaultNoticeRecord = {
+                    pid: this.ctx.session.sessionProject.id,
+                    type: pushType.settle,
+                    status: auditConst.settle.status.checked,
+                    content: noticeContent,
+                };
+                const records = settle.userIds.map(x => {
+                    return { uid: x, ...defaultNoticeRecord };
+                });
+                await transaction.insert('zh_notice', records);
+
+                // 更新本人审批状态
+                await transaction.update(this.tableName, {
+                    id: selfAudit.id,
+                    audit_status: auditConst.settle.status.checked, opinion: opinion,
+                    audit_time: time,
+                });
+                let auditStatus;
+                if (settle.curAuditors.length === 1 || selfAudit.audit_type !== auditType.key.and) {
+                    // 或签更新他人审批状态
+                    if (selfAudit.audit_type === auditType.key.or) {
+                        const updateOther = [];
+                        for (const audit of settle.curAuditors) {
+                            if (audit.audit_id === selfAudit.audit_id) continue;
+                            updateOther.push({
+                                id: audit.id,
+                                audit_status: auditConst.settle.status.checkSkip,
+                                opinion: '',
+                                audit_time: time,
+                            });
+                        }
+                        if (updateOther.length > 0) transaction.updateRows(this.tableName, updateOther);
+                    }
+                    // 无下一审核人表示,审核结束
+                    auditStatus = nextAudits.length > 0 ? auditConst.settle.status.checking : auditConst.settle.status.checked;
+                    if (nextAudits.length > 0) {
+                        // 流程至下一审批人
+                        const updateData = nextAudits.map(x => { return { id: x.id, audit_status: auditConst.settle.status.checking }; });
+                        await transaction.updateRows(this.tableName, updateData);
+                        // todo 多人协同,取消下一审批人存在的锁定
+                        // await this.ctx.service.settleAuditAss.cancelLock(settle, nextAudits.map(x => { return x.audit_id; }), transaction);
+
+                        // todo 添加短信通知-需要审批提醒功能
+                        const shenpiUrl = await this.ctx.helper.urlToShort(this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/settle/' + settle.settle_order);
+                        const nextAuditIds = this._.map(nextAudits, 'audit_id');
+                        const users = this._.map(this._.filter(this.ctx.auditAssists, function (x) { return nextAuditIds.indexOf(x.user_id) >= 0; }), 'ass_user_id');
+                        users.push(...nextAuditIds);
+                        // await this.ctx.helper.sendAliSms(users, smsTypeConst.const.JS, smsTypeConst.judge.approval.toString(), SmsAliConst.template.settle_check, {
+                        //     qi: settle.settle_order,
+                        //     code: shenpiUrl,
+                        // });
+                        // 微信模板通知
+                        const wechatData = {
+                            wap_url: shenpiUrl,
+                            qi: settle.settle_order,
+                            status: wxConst.status.check,
+                            tips: wxConst.tips.check,
+                            code: this.ctx.session.sessionProject.code,
+                        };
+                        await this.ctx.helper.sendWechat(users, smsTypeConst.const.JS, smsTypeConst.judge.approval.toString(), wxConst.template.settle, wechatData);
+                    } else {
+                        // 添加短信通知-审批通过提醒功能
+                        const users = this._.uniq(settle.userIds);
+                        // await this.ctx.helper.sendAliSms(users, smsTypeConst.const.JS, smsTypeConst.judge.result.toString(), SmsAliConst.template.settle_result, {
+                        //     qi: settle.settle_order,
+                        //     status: SmsAliConst.status.success,
+                        // });
 
+                        // 微信模板通知
+                        const shenpiUrl = await this.ctx.helper.urlToShort(this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/settle/' + settle.settle_order);
+                        const wechatData = {
+                            wap_url: shenpiUrl,
+                            qi: settle.settle_order,
+                            status: wxConst.status.success,
+                            tips: wxConst.tips.success,
+                            code: this.ctx.session.sessionProject.code,
+                        };
+                        await this.ctx.helper.sendWechat(users, smsTypeConst.const.JS, smsTypeConst.judge.result.toString(), wxConst.template.settle, wechatData);
+                        // todo 审批通过 - 检查三方特殊推送
+                        // await this.ctx.service.specMsg.addSettleMsg(transaction, this.ctx.session.sessionProject.id, settle, pushOperate.settle.checked);
+                    }
+                    // todo 上报/审批 - 检查三方特殊推送
+                    // await this.ctx.service.specMsg.addSettleMsg(transaction, this.ctx.session.sessionProject.id, settle, pushOperate.settle.flow);
+                } else {
+                    auditStatus = auditConst.settle.status.checking;
+                }
+                // 同步 期信息
+                await transaction.update(this.ctx.service.settle.tableName, { id: settle.id, audit_status: auditStatus });
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+        async _checkNo(settle, opinion) {
+            const pid = this.ctx.session.sessionProject.id;
+            const accountId = this.ctx.session.sessionUser.accountId;
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const selfAudit = settle.curAuditors.find(x => { return x.audit_id === accountId; });
+            if (!selfAudit) throw '当前标段您无权审批';
+            const auditors = await this.getUniqAuditors(settle); // 全部参与的审批人
+            const newAuditors = auditors.map(x => {
+                return {
+                    tid: settle.tid, settle_id: settle.id, audit_id: x.audit_id,
+                    audit_times: settle.audit_times + 1, audit_order: x.audit_order, audit_type: x.audit_type,
+                    active_order: x.audit_order, audit_status: auditConst.settle.status.uncheck,
+                    name: x.name, company: x.company, role: x.role, mobile: x.mobile,
+                }
+            });
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 添加提醒
+                const noticeContent = await this._getNoticeContent(pid, selfAudit.tid, settle.id, selfAudit.audit_id, opinion);
+                const defaultNoticeRecord = { pid, type: pushType.settle, status: auditConst.settle.status.checkNo, content: noticeContent };
+                const records = settle.userIds.map(x => {
+                    return { uid: x, ...defaultNoticeRecord };
+                });
+                await transaction.insert(this.ctx.service.noticePush.tableName, records);
+
+                // 更新审批人状态数据
+                const updateData = [];
+                settle.curAuditors.forEach(x => {
+                    if (x.audit_id === selfAudit.audit_id) {
+                        updateData.push({ id: x.id, audit_status: auditConst.settle.status.checkNo, opinion: opinion, audit_time: time});
+                    } else {
+                        updateData.push({ id: x.id, audit_status: auditConst.settle.status.checkSkip, opinion: '', audit_time: null});
+                    }
+                });
+                await transaction.updateRows(this.tableName, updateData);
+                // 更新 期信息
+                await transaction.update(this.ctx.service.settle.tableName, {
+                    id: settle.id, audit_status: auditConst.settle.status.checkNo, audit_times: settle.audit_times + 1,
+                });
+                // 拷贝新一次审核流程列表
+                await transaction.insert(this.tableName, newAuditors);
+                // todo 锁定本人数据,保留锁定数据相关确认状态
+                // await this.ctx.service.settleAuditAss.lockConfirm4CheckNo(settle, curAuditorIds, auditors, transaction);
+
+                // todo 添加短信通知-审批退回提醒功能
+                const users = this._.uniq(settle.userIds);
+                // await this.ctx.helper.sendAliSms(users, smsTypeConst.const.JS, smsTypeConst.judge.result.toString(), SmsAliConst.template.settle_result, {
+                //     qi: settle.settle_order, status: SmsAliConst.status.back,
+                // });
+                // 微信模板通知
+                const shenpiUrl = await this.ctx.helper.urlToShort(this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + settle.tid + '/settle/' + settle.settle_order);
+                const wechatData = {
+                    wap_url: shenpiUrl,
+                    qi: settle.settle_order,
+                    status: wxConst.status.back,
+                    tips: wxConst.tips.back,
+                    code: this.ctx.session.sessionProject.code,
+                };
+                await this.ctx.helper.sendWechat(users, smsTypeConst.const.JS, smsTypeConst.judge.result.toString(), wxConst.template.settle, wechatData);
+                // todo 上报/审批 - 检查三方特殊推送
+                // await this.ctx.service.specMsg.addSettleMsg(transaction, pid, settle, pushOperate.settle.flow);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+        async _checkNoPre(settle, opinion) {
+            const pid = this.ctx.session.sessionProject.id;
+            const accountId = this.ctx.session.sessionUser.accountId;
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const selfAudit = settle.curAuditors.find(x => { return x.audit_id === accountId; });
+            if (!selfAudit) throw '当前标段您无权审批';
+            const preAuditors = settle.userGroups.find(x => { return x[0].audit_order === selfAudit.audit_order - 1; });
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 添加通知
+                const noticeContent = await this._getNoticeContent(pid, settle.tid, settle.id, selfAudit.audit_id, opinion);
+                const defaultNoticeRecord = {
+                    pid, type: pushType.settle, status: auditConst.settle.status.checkNoPre, content: noticeContent,
+                };
+                const records = settle.userIds.map(x => {
+                    return { uid: x, ...defaultNoticeRecord };
+                });
+                await transaction.insert('zh_notice', records);
+
+                // 更新同一流程所有审批人状态
+                const updateData = [];
+                for (const audit of settle.curAuditors) {
+                    if (audit.audit_id === selfAudit.audit_id) {
+                        updateData.push({
+                            id: audit.id, audit_status: auditConst.settle.status.checkNoPre, opinion, audit_time: time,
+                        });
+                    } else {
+                        updateData.push({
+                            id: audit.id, audit_status: auditConst.settle.status.checkSkip, opinion: '', audit_time: null,
+                        });
+                    }
+                }
+                await transaction.updateRows(this.tableName, updateData);
+
+                // 顺移其后审核人流程顺序
+                const sql = 'UPDATE ' + this.tableName + ' SET `active_order` = `active_order` + 2 WHERE settle_id = ? AND audit_times = ? AND `active_order` > ?';
+                await transaction.query(sql, [settle.id, selfAudit.audit_times, selfAudit.active_order]);
+                // 上一审批人,当前审批人 再次添加至流程
+                const newAuditors = [];
+                preAuditors.forEach(x => {
+                    newAuditors.push({
+                        tid: x.tid, settle_id: x.settle_id, audit_id: x.audit_id,
+                        audit_times: x.audit_times, audit_order: selfAudit.audit_order - 1,
+                        audit_status: auditConst.settle.status.checking,
+                        audit_type: x.audit_type, active_order: selfAudit.active_order + 1,
+                        name: x.name, company: x.company, role: x.role, mobile: x.mobile,
+                    });
+                });
+                settle.flowAuditors.forEach(x => {
+                    newAuditors.push({
+                        tid: x.tid, settle_id: x.settle_id, audit_id: x.audit_id,
+                        audit_times: x.audit_times, active_order: selfAudit.active_order + 2,
+                        audit_status: auditConst.settle.status.uncheck,
+                        audit_type: x.audit_type, audit_order: x.audit_order,
+                        name: x.name, company: x.company, role: x.role, mobile: x.mobile,
+                    });
+                });
+                await transaction.insert(this.tableName, newAuditors);
+
+                const preAuditorIds = preAuditors.map(x => { return x.audit_id; });
+                // todo 锁定本人数据,保留锁定数据相关确认状态
+                // await this.ctx.service.settleAuditAss.lockConfirm4CheckNoPre(settle, settle.curAuditorIds, preAuditorIds, transaction);
+
+                // 同步 期信息
+                await transaction.update(this.ctx.service.settle.tableName, {
+                    id: settle.id, audit_status: auditConst.settle.status.checkNoPre,
+                });
+                const shenpiUrl = await this.ctx.helper.urlToShort(this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + settle.tid + '/settle/' + settle.settle_order);
+                const users = this._.map(settle.auditAssists.filter(x => {return preAuditorIds.indexOf(x.user_id) >= 0 }), 'ass_user_id');
+                users.push(...preAuditorIds);
+                // await this.ctx.helper.sendAliSms(users, smsTypeConst.const.JS, smsTypeConst.judge.approval.toString(), SmsAliConst.template.settle_check, {
+                //     qi: settle.settle_order,
+                //     code: shenpiUrl,
+                // });
+                // 微信模板通知
+                const wechatData = {
+                    wap_url: shenpiUrl,
+                    qi: settle.settle_order,
+                    status: wxConst.status.check,
+                    tips: wxConst.tips.check,
+                    code: this.ctx.session.sessionProject.code,
+                };
+                await this.ctx.helper.sendWechat(users, smsTypeConst.const.JS, smsTypeConst.judge.approval.toString(), wxConst.template.settle, wechatData);
+
+                // todo 上报/审批 - 检查三方特殊推送
+                // await this.ctx.service.specMsg.addSettleMsg(transaction, pid, settle, pushOperate.settle.flow);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+        async check(settle, checkType, opinion = '') {
+            switch (checkType) {
+                case auditConst.settle.status.checked:
+                    await this._checked(settle, opinion);
+                    break;
+                case auditConst.settle.status.checkNo:
+                    await this._checkNo(settle, opinion);
+                    break;
+                case auditConst.settle.status.checkNoPre:
+                    await this._checkNoPre(settle, opinion);
+                    break;
+                default:
+                    throw '无效审批操作';
+            }
+        }
+        // ***** 审批操作
     }
 
     return SettleAudit;

+ 187 - 0
app/service/settle_audit_ass.js

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

+ 2 - 2
app/service/settle_select.js

@@ -31,8 +31,8 @@ module.exports = app => {
                         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 });
+                    const delLibData = lid.length > 0 ? await this.getAllDataByCondition({ where: { settle_id: this.ctx.settle.id, lid } }) : [];
+                    const delPidData = pid.length > 0 ? await this.getAllDataByCondition({ where: { 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 }) });

+ 6 - 0
app/service/spec_msg.js

@@ -59,6 +59,12 @@ module.exports = app => {
             await transaction.insert(this.tableName, { pid, tid: stage.tid, sid: stage.id, timing });
         }
 
+        async addSettleMsg(transaction, pid, settle, timing) {
+            const needMsg = await this.tenderNeedMsg(pid, settle.tid, timing);
+            if (!needMsg) return;
+            await transaction.insert(this.tableName, { pid, tid: settle.tid, settle_id: settle.id, timing });
+        }
+
         async addChangeMsg(transaction, pid, change, timing) {
             const needMsg = await this.tenderNeedMsg(pid, change.tid, timing);
             if (!needMsg) return;

+ 1 - 0
app/service/stage_audit.js

@@ -22,6 +22,7 @@ const measureType = require('../const/tender').measureType;
 const RevisePrice = require('../lib/revise_price');
 const pushOperate = require('../const/spec_3f').pushOperate;
 
+
 module.exports = app => {
     class StageAudit extends app.BaseService {
         /**

+ 4 - 0
app/service/tender.js

@@ -139,6 +139,9 @@ module.exports = app => {
                     // 参与协审
                     '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
                     '        t.id IN ( SELECT sa.`tid` FROM ?? As sa WHERE sa.`ass_user_id` = ? GROUP BY sa.`tid`))' +
+                    // 参与审批 结算期 的标段
+                    '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
+                    '        t.id IN ( SELECT sa.`tid` FROM ?? As sa WHERE sa.`audit_id` = ? GROUP BY sa.`tid`))' +
                     // 参与审批 变更令 的标段
                     '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
                     '        t.id IN ( SELECT ca.`tid` FROM ?? AS ca WHERE ca.`uid` = ? GROUP BY ca.`tid`))' +
@@ -160,6 +163,7 @@ module.exports = app => {
                     this.ctx.service.ledgerAudit.tableName, session.sessionUser.accountId,
                     this.ctx.service.stageAudit.tableName, session.sessionUser.accountId,
                     this.ctx.service.auditAss.tableName, session.sessionUser.accountId,
+                    this.ctx.service.settleAudit.tableName, session.sessionUser.accountId,
                     this.ctx.service.changeAudit.tableName, session.sessionUser.accountId,
                     this.ctx.service.reviseAudit.tableName, session.sessionUser.accountId,
                     this.ctx.service.materialAudit.tableName, session.sessionUser.accountId,

+ 9 - 5
app/view/change/apply_information.ejs

@@ -34,18 +34,22 @@
                         <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) { %>
+                    <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-success btn-sm">审批完成</a>
+                    <% if (ctx.session.sessionUser.accountId === ctx.change.auditors2[ctx.change.auditors2.length-1].aid) { %>
+                        <!--重新审批-->
+                        <a href="#sp-down-back" data-toggle="modal" data-target="#sp-down-back" class="btn btn-warning btn-sm ml-2">重新审批</a>
+                    <% } %>
                     <% 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="javascript: void(0);" data-toggle="modal" data-target="#sp-down-revise" class="btn btn-warning btn-sm ml-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 || 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>
                     <% } %>
+                    <% 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 ml-2">撤销修订</a>
+                    <% } %>
                 <% } %>
             </div>
         </div>

+ 90 - 6
app/view/change/apply_information_modal.ejs

@@ -258,7 +258,7 @@
                                                     <div class="timeline-item-icon bg-success text-light">
                                                         <i class="fa fa-check"></i>
                                                     </div>
-                                                <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                <% } else if(ctx.helper._.includes([auditConst.status.checkNo, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                     <div class="timeline-item-icon bg-warning text-light">
                                                         <i class="fa fa-level-up"></i>
                                                     </div>
@@ -302,7 +302,7 @@
                                                     <div class="timeline-item-icon bg-success text-light">
                                                         <i class="fa fa-check"></i>
                                                     </div>
-                                                <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                <% } else if(ctx.helper._.includes([auditConst.status.checkNo, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                     <div class="timeline-item-icon bg-warning text-light">
                                                         <i class="fa fa-level-up"></i>
                                                     </div>
@@ -440,7 +440,7 @@
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                        <% } else if(ctx.helper._.includes([auditConst.status.checkNo, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -499,7 +499,7 @@
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                        <% } else if(ctx.helper._.includes([auditConst.status.checkNo, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -650,7 +650,7 @@
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                        <% } else if(ctx.helper._.includes([auditConst.status.checkNo, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -713,7 +713,7 @@
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                        <% } else if(ctx.helper._.includes([auditConst.status.checkNo, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -786,6 +786,59 @@
         </div>
     <% } %>
 <% } %>
+<% if (ctx.change.status === auditConst.status.checked && ctx.session.sessionUser.accountId === ctx.change.auditors2[ctx.change.auditors2.length-1].aid) { %>
+    <% if (!authMobile && ctx.session.sessionUser.loginStatus === 0) { %>
+        <!--终审重新审批-->
+        <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/apply/check/again" onsubmit="return false;">
+                    <div class="modal-header">
+                        <h5 class="modal-title">重新审批</h5>
+                    </div>
+                    <div class="modal-body">
+                        <h5>确认由「终审-<%= ctx.change.auditors2[ctx.change.auditors2.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="caid" value="<%= 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-btn" class="btn btn-warning btn-sm" <% if (ctx.session.sessionUser.loginStatus === 0) { %>disabled<% } %>>确定重审</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+    <% } %>
+<% } %>
 <% if (ctx.change.status === auditConst.status.checked && ctx.session.sessionUser.accountId === ctx.change.uid) { %>
     <% if (!authMobile && ctx.session.sessionUser.loginStatus === 0) { %>
         <!--原报修订变更-->
@@ -913,6 +966,37 @@
         }
     });
 
+    $('#re-shenpi-btn').click(function () {
+        const data = {
+            caid: parseInt('<%- ctx.change.id %>'),
+        };
+        <% if (ctx.session.sessionUser.loginStatus === 0) { %>
+        const code = $("#againForm input[name='code']").val();
+        if ($(this).hasClass('disabled')) {
+            return false;
+        }
+        if (code.length < 6) {
+            // alert('请填写正确的验证码');
+            toastr.error('请填写正确的验证码');
+            return false;
+        }
+        data.code = code;
+        <% } %>
+        $.ajax({
+            url: '/tender/<%- tender.id %>/change/apply/check/again?_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);
+                }
+            }
+        });
+    })
+
     $('#re-shenpi-btn2').click(function () {
         const data = {
             caid: parseInt('<%- ctx.change.id %>'),

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

@@ -104,7 +104,7 @@
                         <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm">审批中</a>
                         <% } %>
                     <% } %>
-                    <% if (auditStatus === 4 && ctx.session.sessionUser.accountId === auditList[auditList.length-1].uid) { %>
+                    <% if (auditStatus === 4 && ctx.session.sessionUser.accountId === auditList2[auditList2.length-1].uid) { %>
                         <!--重新审批-->
                         <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-back" class="btn btn-warning btn-sm">重新审批</a>
                     <% } %>
@@ -477,6 +477,7 @@
     const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
     let attData = JSON.parse(unescape('<%- escape(JSON.stringify(attList)) %>'));
     let currPageFileData = [];
+    const change_uid = parseInt('<%- change.uid %>');;
     autoFlashHeight();
     $('a[href="#sub-ap"').click(function() {
         if (parseInt(ledgeStatus) === ledgerConsts.uncheck) {
@@ -512,8 +513,6 @@
     let changeUnits = JSON.parse('<%- JSON.stringify(changeUnits) %>');
     changeUnits = _.map(changeUnits, 'unit');
     changeUnits.push('');
-    const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
-    const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
     const changeLedgerList = JSON.parse(unescape('<%- escape(JSON.stringify(changeLedgerList)) %>'));
     const changePosList = JSON.parse(unescape('<%- escape(JSON.stringify(changePosList)) %>'));
     const shenpi_status = <%- ctx.tender.info.shenpi.change %>;

+ 415 - 11
app/view/change/information_modal.ejs

@@ -388,8 +388,11 @@
                 <div class="modal-body">
                     <div class="row">
                         <div class="col-4 modal-height-500" style="overflow: auto">
+                            <% if(change.status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) { %>
+                                <a href="#sub-sp2" data-toggle="modal" data-category="" data-target="#sub-sp2" id="hideSp">修改审批流程</a>
+                            <% } %>
                             <div class="card mt-3">
-                                <ul class="list-group list-group-flush">
+                                <ul class="list-group list-group-flush auditors-list">
                                     <% for (const [index,a] of auditList2.entries()) { %>
                                         <li class="list-group-item">
                                             <% if (a.usite === 0 ) { %>
@@ -415,7 +418,7 @@
                                 <% } %>
                                 <div class="<%- idx < auditList3.length - 1 ? 'fold-card' : '' %>">
                                     <div class="text-center text-muted"><%- idx+1 %>#</div>
-                                    <ul class="timeline-list list-unstyled mt-2">
+                                    <ul class="timeline-list list-unstyled mt-2 <% if (idx === auditList3.length - 1) { %>last-auditor-list<% } %>">
                                         <% auditors.forEach((auditor, index) => { %>
                                             <% if (index === 0) { %>
                                                 <li class="timeline-list-item pb-2">
@@ -441,7 +444,7 @@
                                                     </div>
                                                 </li>
                                             <% } else {%>
-                                                <li class="timeline-list-item pb-2">
+                                                <li class="timeline-list-item pb-2 <% if (auditor.status === auditConst.auditStatus.uncheck && idx === auditList3.length - 1) { %>is_uncheck<% } %>">
                                                     <div class="timeline-item-date">
                                                         <%- ctx.helper.formatDate(auditor.sin_time) %>
                                                     </div>
@@ -519,8 +522,11 @@
                 <div class="modal-body">
                     <div class="row">
                         <div class="col-4 modal-height-500" style="overflow: auto">
+                            <% if(change.status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) { %>
+                                <a href="#sub-sp2" data-toggle="modal" data-category="" data-target="#sub-sp2" id="hideSp">修改审批流程</a>
+                            <% } %>
                             <div class="card mt-3">
-                                <ul class="list-group list-group-flush">
+                                <ul class="list-group list-group-flush auditors-list">
                                     <% for (const [index,a] of auditList2.entries()) { %>
                                         <% if (a.usite === 0) { %>
                                             <li class="list-group-item">
@@ -634,7 +640,7 @@
                             <% } %>
                             <div class="">
                                 <div class="text-center text-muted"><%- auditList4.length+1 %>#</div>
-                                <ul class="timeline-list list-unstyled mt-2">
+                                <ul class="timeline-list list-unstyled mt-2 last-auditor-list">
                                     <% auditList3.forEach((auditor, index) => { %>
                                         <% if (index === 0) { %>
                                             <li class="timeline-list-item pb-2">
@@ -660,7 +666,7 @@
                                                 </div>
                                             </li>
                                         <% } else {%>
-                                            <li class="timeline-list-item pb-2">
+                                            <li class="timeline-list-item pb-2 <% if (auditor.status === auditConst.auditStatus.uncheck) { %>is_uncheck<% } %>">
                                                 <div class="timeline-item-date">
                                                     <%- ctx.helper.formatDate(auditor.sin_time) %>
                                                 </div>
@@ -757,8 +763,11 @@
                 <div class="modal-body">
                     <div class="row">
                         <div class="col-4 modal-height-500" style="overflow: auto">
+                            <% if(change.status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) { %>
+                                <a href="#sub-sp2" data-toggle="modal" data-category="" data-target="#sub-sp2" id="hideSp">修改审批流程</a>
+                            <% } %>
                             <div class="card mt-3">
-                                <ul class="list-group list-group-flush">
+                                <ul class="list-group list-group-flush auditors-list">
                                     <% for (const [index,a] of auditList2.entries()) { %>
                                         <% if (a.usite === 0) { %>
                                             <li class="list-group-item">
@@ -810,7 +819,7 @@
                                                     </div>
                                                 </li>
                                             <% } else {%>
-                                                <li class="timeline-list-item pb-2">
+                                                <li class="timeline-list-item pb-2 <% if (auditor.status === auditConst.auditStatus.uncheck) { %>is_uncheck<% } %>">
                                                     <div class="timeline-item-date">
                                                         <%- ctx.helper.formatDate(auditor.sin_time) %>
                                                     </div>
@@ -873,7 +882,7 @@
                             <% } %>
                             <div class="">
                                 <div class="text-center text-muted"><%- auditList4.length+1 %>#</div>
-                                <ul class="timeline-list list-unstyled mt-2">
+                                <ul class="timeline-list list-unstyled mt-2 last-auditor-list">
                                     <% auditList3.forEach((auditor, index) => { %>
                                         <% if (index === 0) { %>
                                             <li class="timeline-list-item pb-2">
@@ -899,7 +908,7 @@
                                                 </div>
                                             </li>
                                         <% } else {%>
-                                            <li class="timeline-list-item pb-2">
+                                            <li class="timeline-list-item pb-2 <% if (auditor.status === auditConst.auditStatus.uncheck) { %>is_uncheck<% } %>">
                                                 <div class="timeline-item-date">
                                                     <%- ctx.helper.formatDate(auditor.sin_time) %>
                                                 </div>
@@ -1016,7 +1025,7 @@
         </div>
     </div>
 </div>
-<% if (auditStatus === 4 && ctx.session.sessionUser.accountId === auditList[auditList.length-1].uid) { %>
+<% if (auditStatus === 4 && ctx.session.sessionUser.accountId === auditList2[auditList2.length-1].uid) { %>
     <% if (!authMobile && ctx.session.sessionUser.loginStatus === 0) { %>
         <!--终审重新审批-->
         <div class="modal fade" id="sp-down-back" data-backdrop="static">
@@ -1219,6 +1228,138 @@
         </div>
     </div>
 <% } %>
+<% if (ctx.change && ctx.change.status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) { %>
+    <!--上报审批-->
+    <div class="modal fade" id="sub-sp2" data-backdrop="static">
+        <div class="modal-dialog" style="max-width: 650px" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">修改审批流程</h5>
+                </div>
+                <div class="modal-body">
+                    <div class="card mt-1">
+                        <div class="modal-height-500" style="overflow: auto">
+                            <style>
+                                #admin-edit-shenpi thead th {
+                                    border-bottom: 0;
+                                }
+                                #admin-edit-shenpi td, #admin-edit-shenpi th {
+                                    padding: 0.75rem;
+                                }
+                                #admin-edit-shenpi th {
+                                    background: none;
+                                    color: #212529;
+                                    border-top: 0;
+                                }
+                            </style>
+                            <table class="table table-hover" id="admin-edit-shenpi">
+                                <thead>
+                                <tr class="card-header">
+                                    <th>审批流程</th>
+                                    <th width="80" style="text-align: center">审批状态</th>
+                                    <th width="200" style="text-align: center">操作</th>
+                                </tr>
+                                </thead>
+                                <% for (let i = 0, iLen = change.auditors2.length; i < iLen; i++) { %>
+                                    <tr>
+                                        <td><span class="shenpi-order"><%- i+1 %></span> <%- change.auditors2[i].name %> <small class="text-muted"><%- change.auditors2[i].jobs %></small></td>
+                                        <td style="text-align: center"><span class="<%- auditConst.auditStatusClass[change.auditors2[i].status] %>"><%- change.auditors2[i].status !== auditConst.status.uncheck ? auditConst.auditStatusString[change.auditors2[i].status] : '待审批'  %></span></td>
+                                        <td style="text-align: center">
+                                            <% if (change.auditors2[i].status === auditConst.status.checking) { %>
+                                                <span class="dropdown mr-2">
+                                    <a href="javascript: void(0)" class="add-audit" id="<%- change.auditors2[i].uid %>_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">增加</a>
+                                    <div class="dropdown-menu dropdown-menu-right" id="<%- change.auditors2[i].uid %>_add_dropdownMenu" aria-labelledby="<%- change.auditors2[i].uid %>_add_dropdownMenuButton" style="width:220px">
+                                        <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"
+                                                                     placeholder="姓名/手机 检索" autocomplete="off" data-code="<%- change.auditors2[i].uid %>_add"></div>
+                                        <dl class="list-unstyled book-list" data-aid="<%- change.auditors2[i].uid %>" data-operate="add">
+                                            <% accountGroup.forEach((group, idx) => { %>
+                                                <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>"
+                                                       data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                                                <div class="dd-content" data-toggleid="<%- idx %>">
+                                                    <% group.groupList.forEach(item => { %>
+                                                        <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>">
+                                                            <p class="mb-0 d-flex"><span class="text-primary"><%- item.name %></span><span
+                                                                        class="ml-auto"><%- item.mobile %></span></p>
+                                                            <span class="text-muted"><%- item.role %></span>
+                                                        </dd>
+                                                    <% });%>
+                                                </div>
+                                            <% }) %>
+                                        </dl>
+                                    </div>
+                                    </span>
+                                            <% } %>
+                                            <% if (change.auditors2[i].status === auditConst.status.uncheck) { %>
+                                                <span class="dropdown mr-2">
+                                    <a href="javascript: void(0)" class="add-audit" id="<%- change.auditors2[i].uid %>_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">增加</a>
+                                        <div class="dropdown-menu dropdown-menu-right" id="<%- change.auditors2[i].uid %>_add_dropdownMenu" aria-labelledby="<%- change.auditors2[i].uid %>_add_dropdownMenuButton" style="width:220px">
+                                            <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"
+                                                                         placeholder="姓名/手机 检索" autocomplete="off" data-code="<%- change.auditors2[i].uid %>_add"></div>
+                                            <dl class="list-unstyled book-list" data-aid="<%- change.auditors2[i].uid %>" data-operate="add">
+                                                <% accountGroup.forEach((group, idx) => { %>
+                                                    <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>"
+                                                           data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                                                    <div class="dd-content" data-toggleid="<%- idx %>">
+                                                        <% group.groupList.forEach(item => { %>
+                                                            <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>">
+                                                                <p class="mb-0 d-flex"><span class="text-primary"><%- item.name %></span><span
+                                                                            class="ml-auto"><%- item.mobile %></span></p>
+                                                                <span class="text-muted"><%- item.role %></span>
+                                                            </dd>
+                                                        <% });%>
+                                                    </div>
+                                                <% }) %>
+                                            </dl>
+                                        </div>
+                                    </span>
+                                                <span class="dropdown mr-2">
+                                        <a href="javascript: void(0)" class="change-audit" id="<%- change.auditors2[i].uid %>_change_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">更换</a>
+                                        <div class="dropdown-menu dropdown-menu-right" id="<%- change.auditors2[i].uid %>_change_dropdownMenu" aria-labelledby="<%- change.auditors2[i].uid %>_change_dropdownMenuButton" style="width:220px">
+                                            <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"
+                                                                         placeholder="姓名/手机 检索" autocomplete="off" data-code="<%- change.auditors2[i].uid %>_change"></div>
+                                            <dl class="list-unstyled book-list" data-aid="<%- change.auditors2[i].uid %>" data-operate="change">
+                                                <% accountGroup.forEach((group, idx) => { %>
+                                                    <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>"
+                                                           data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                                                    <div class="dd-content" data-toggleid="<%- idx %>">
+                                                        <% group.groupList.forEach(item => { %>
+                                                            <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>">
+                                                                <p class="mb-0 d-flex"><span class="text-primary"><%- item.name %></span><span
+                                                                            class="ml-auto"><%- item.mobile %></span></p>
+                                                                <span class="text-muted"><%- item.role %></span>
+                                                            </dd>
+                                                        <% });%>
+                                                    </div>
+                                                <% }) %>
+                                            </dl>
+                                        </div>
+                                    </span>
+                                                <span class="dropdown">
+                                    <a href="javascript: void(0)" class="text-danger" title="移除" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">移除</a>
+                                    <div class="dropdown-menu">
+                                        <span class="dropdown-item" href="javascript:void(0);">确认移除审批人?</span>
+                                        <div class="dropdown-divider"></div>
+                                        <div class="px-2 py-1 text-center">
+                                            <button class="remove-audit btn btn-sm btn-danger" data-id="<%- change.auditors2[i].uid %>">移除</button>
+                                            <button class="btn btn-sm btn-secondary">取消</button>
+                                        </div>
+                                    </div>
+                                    </span>
+                                            <% } %>
+                                        </td>
+                                    </tr>
+                                <% } %>
+                            </table>
+                        </div>
+                    </div>
+                </div>
+                <form class="modal-footer">
+                    <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                </form>
+            </div>
+        </div>
+    </div>
+<% } %>
 <script type="text/javascript">
     const csrf = '<%= ctx.csrf %>';
     const authMobile = '<%= authMobile %>';
@@ -1364,3 +1505,266 @@
     });
     <% } %>
 </script>
+<% if (auditStatus === 1 || auditStatus === 2 || auditStatus === 9 || ctx.session.sessionUser.is_admin) { %>
+    <script>
+        const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
+        const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
+    </script>
+<% } %>
+<% if (ctx.change && ctx.change.status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) { %>
+    <script>
+        const auditorList = JSON.parse(unescape('<%- escape(JSON.stringify(change.auditors2)) %>'));
+        $(function () {
+            // 退回选择修改审批人流程
+            $('#hideSp').click(function () {
+                $('#sp-list').modal('hide');
+                $('#sp-done').modal('hide');
+                $('#sp-back').modal('hide');
+            });
+            $('a[f-target]').click(function () {
+                $($(this).attr('f-target')).modal('show');
+            });
+
+            // 多层modal关闭后的滚动bug修复
+            $('#sp-list').on('hidden.bs.modal', function (e) {
+                $(document.body).addClass('modal-open');
+            });
+
+            // 添加审批流程按钮逻辑
+            $('body').on('click', '#admin-edit-shenpi .book-list dt', function () {
+                const idx = $(this).find('.acc-btn').attr('data-groupid')
+                const type = $(this).find('.acc-btn').attr('data-type')
+                if (type === 'hide') {
+                    $(this).parent().find(`div[data-toggleid="${idx}"]`).show(() => {
+                        $(this).children().find('i').removeClass('fa-plus-square').addClass('fa-minus-square-o')
+                        $(this).find('.acc-btn').attr('data-type', 'show')
+
+                    })
+                } else {
+                    $(this).parent().find(`div[data-toggleid="${idx}"]`).hide(() => {
+                        $(this).children().find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square')
+                        $(this).find('.acc-btn').attr('data-type', 'hide')
+                    })
+                }
+                return false
+            })
+
+            // 管理员更改审批流程js部分
+            let timer2 = null;
+            let oldSearchVal2 = null;
+            $('body').on('input propertychange', '#admin-edit-shenpi .gr-search', function(e) {
+                oldSearchVal2 = e.target.value;
+                timer2 && clearTimeout(timer2);
+                timer2 = setTimeout(() => {
+                    const newVal = $(this).val();
+                    const code = $(this).attr('data-code');
+                    let html = '';
+                    if (newVal && newVal === oldSearchVal2) {
+                        accountList.filter(item => item && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1))).forEach(item => {
+                            html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                        <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                class="ml-auto">${item.mobile || ''}</span></p>
+                        <span class="text-muted">${item.role || ''}</span>
+                    </dd>`
+                        });
+                        $('#' + code + '_dropdownMenu .book-list').empty();
+                        $('#' + code + '_dropdownMenu .book-list').append(html);
+                    } else {
+                        if (!$('#' + code + '_dropdownMenu .acc-btn').length) {
+                            accountGroup.forEach((group, idx) => {
+                                if (!group) return;
+                                html += `<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="${idx}" data-type="hide"><i class="fa fa-plus-square"></i>
+                        </a> ${group.groupName}</dt>
+                        <div class="dd-content" data-toggleid="${idx}">`;
+                                group.groupList.forEach(item => {
+                                        html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                                    <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                            class="ml-auto">${item.mobile || ''}</span></p>
+                                    <span class="text-muted">${item.role || ''}</span>
+                                </dd>`;
+                                });
+                                html += '</div>';
+                            });
+                            $('#' + code + '_dropdownMenu .book-list').empty();
+                            $('#' + code + '_dropdownMenu .book-list').append(html);
+                        }
+                    }
+                }, 400);
+            });
+
+            $('body').on('click', '#admin-edit-shenpi dl dd', function () {
+                const id = parseInt($(this).attr('data-id'));
+                if (!id) return;
+
+                let this_aid = parseInt($(this).parents('.book-list').attr('data-aid'));
+                let this_operate = $(this).parents('.book-list').attr('data-operate');
+                const user = _.find(accountList, function (item) {
+                    return item.id === id;
+                });
+                const auditorIndex = _.findIndex(auditorList, { uid: id });
+                if (auditorIndex !== -1) {
+                    toastr.warning('该审核人已存在,请勿重复添加');
+                    return;
+                }
+                const order = parseInt($(this).parents('tr').find('.shenpi-order').text());
+                const curAuditorIndex = _.findIndex(auditorList, { uid: this_aid });
+                const prop = {
+                    operate: this_operate,
+                    old_aid: this_aid,
+                    new_aid: user.id,
+                };
+                postData(window.location.pathname + '/audit/save', prop, (datas) => {
+                    if (this_operate === 'add') {
+                        const addhtml = '<tr>\n' +
+                            `                                <td><span class="shenpi-order">${order+1}</span> ${user.name} <small class="text-muted">${user.role}</small></td>\n` +
+                            `                                <td style="text-align: center"><span class="">待审批</span></td>\n` +
+                            '                                <td style="text-align: center">\n' +
+                            '                                    <span class="dropdown mr-2">\n' +
+                            `                                    <a href="javascript: void(0)" class="add-audit" id="${user.id}_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">增加</a>\n` +
+                            makeSelectAudit(user.id+'_add', user.id, 'add') +
+                            '                                        </div>\n' +
+                            '                                    </span>\n' +
+                            '                                    <span class="dropdown mr-2">\n' +
+                            `                                        <a href="javascript: void(0)" class="change-audit" id="${user.id}_change_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">更换</a>\n` +
+                            makeSelectAudit(user.id+'_change', user.id, 'change') +
+                            '                                    </span>\n' +
+                            '                                    <span class="dropdown">\n' +
+                            '                                    <a href="javascript: void(0)" class="text-danger" title="移除" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">移除</a>\n' +
+                            '                                    <div class="dropdown-menu">\n' +
+                            '                                        <span class="dropdown-item">确认移除审批人?</span>\n' +
+                            '                                        <div class="dropdown-divider"></div>\n' +
+                            '                                        <div class="px-2 py-1 text-center">\n' +
+                            `                                            <button class="remove-audit btn btn-sm btn-danger" data-id="${user.id}">移除</button>\n` +
+                            '                                            <button class="btn btn-sm btn-secondary">取消</button>\n' +
+                            '                                        </div>\n' +
+                            '                                    </div>\n' +
+                            '                                    </span>\n' +
+                            '                                </td>\n' +
+                            '                            </tr>';
+                        $(this).parents('tr').after(addhtml);
+                        auditorList.splice(curAuditorIndex+1, 0, { uid: user.id, company: user.company, name: user.name, role: user.role });
+                        updateOrder(user.id);
+                    } else if (this_operate === 'change') {
+                        const this_user = _.find(auditorList, { uid: this_aid });
+                        this_user.uid = user.id;
+                        this_user.company = user.company;
+                        this_user.role = user.role;
+                        this_user.name = user.name;
+                        auditorList.splice(curAuditorIndex, 1, this_user);
+                        $(this).parents('tr').children('td').eq(0).html(`<span class="shenpi-order">${order}</span> ${user.name} <small class="text-muted">${user.role}</small>`);
+                        // 替换所有aid
+                        $(this).parents('.book-list').attr('data-aid', user.id);
+                        $(this).parents('.dropdown-menu').attr('id', user.id +'_change_dropdownMenu').attr('aria-labelledby', user.id +'_change_dropdownMenuButton');
+                        $(this).parents('.dropdown-menu').children('mb-2').children('input').attr('data-code', user.id +'_change');
+                        $(this).parents('.dropdown-menu').siblings('.change-audit').attr('id', user.id +'_change_dropdownMenuButton');
+                        $(this).parents('td').children('span').eq(0).find('.add-audit').attr('id', user.id +'_add_dropdownMenuButton');
+                        $(this).parents('td').children('span').eq(0).find('.dropdown-menu').attr('id', user.id +'_add_dropdownMenu').attr('aria-labelledby', user.id +'_add_dropdownMenuButton');
+                        $(this).parents('td').children('span').eq(0).find('.dropdown-menu').children('mb-2').children('input').attr('data-code', user.id +'_add');
+                        $(this).parents('td').children('span').eq(0).find('.dropdown-menu').children('.book-list').attr('data-aid', user.id);
+                        $(this).parents('td').children('span').eq(2).find('.remove-audit').attr('data-id', user.id);
+                    }
+                    changeLiucheng(datas);
+                });
+            });
+
+            // 移除审批人
+            $('body').on('click', '#admin-edit-shenpi .remove-audit', function () {
+                const id = parseInt($(this).attr('data-id'));
+                const prop = {
+                    operate: 'del',
+                    old_aid: id,
+                };
+                postData(window.location.pathname + '/audit/save', prop, (datas) => {
+                    updateOrder(id, 0);
+                    const curAuditorIndex = _.findIndex(auditorList, { uid: id });
+                    auditorList.splice(curAuditorIndex, 1);
+                    $(this).parents('tr').remove();
+                    changeLiucheng(datas);
+                });
+            });
+
+            // 比uid大的序号进行调整
+            function updateOrder(uid, num = 1) {
+                const index = _.findIndex(auditorList, { uid });
+                for (let i = index;i < auditorList.length; i++) {
+                    $('#admin-edit-shenpi tbody').children('tr').eq(i).find('.shenpi-order').text(i+num);
+                }
+            }
+
+            function changeLiucheng(datas) {
+                const auditorshtml = [];
+                let lastAuditorHtml = '';
+                for (const [index,data] of datas.entries()) {
+                    auditorshtml.push('<li class="list-group-item" data-auditorid="' + data.uid + '">');
+                    auditorshtml.push('<i class="fa ' + (index+1 === datas.length ? 'fa-stop-circle' : 'fa-chevron-circle-down') + '"></i> ');
+                    auditorshtml.push(data.name + ' <small class="text-muted">' + data.jobs + '</small>');
+                    if (index === 0) {
+                        auditorshtml.push('<span class="pull-right">原报</span>');
+                    } else if (index+1 === datas.length) {
+                        auditorshtml.push('<span class="pull-right">终审</span>');
+                    } else {
+                        auditorshtml.push('<span class="pull-right">'+ transFormToChinese(index) +'审</span>');
+                    }
+                    auditorshtml.push('</li>');
+                    if (data.status === auditConst.status.uncheck) {
+                        lastAuditorHtml += '<li class="timeline-list-item pb-2 is_uncheck">\n' +
+                            '                                            <div class="timeline-item-date">\n' +
+                            '                                                \n' +
+                            '                                            </div>\n' +
+                            '                                            <div class="timeline-item-icon bg-secondary text-light">\n' +
+                            '                                            </div>\n' +
+                            '                                            <div class="timeline-item-content">\n' +
+                            '                                                <div class="card">\n' +
+                            '                                                    <div class="card-body p-3">\n' +
+                            '                                                        <div class="card-text">\n' +
+                            `                                                            <p class="mb-1"><span class="h5">${data.name}</span>\n` +
+                            '                                                                <span class="pull-right ">\n' +
+                            '                                                                </span>\n' +
+                            '                                                            </p>\n' +
+                            `                                                            <p class="text-muted mb-0">${data.jobs}</p>\n` +
+                            '                                                        </div>\n' +
+                            '                                                    </div>\n' +
+                            '                                                </div>\n' +
+                            '                                            </div>\n' +
+                            '                                        </li>';
+                    }
+                }
+                $('.last-auditor-list .is_uncheck').remove();
+                $('.last-auditor-list').append(lastAuditorHtml);
+                $('.auditors-list').html(auditorshtml.join(''));
+
+            }
+
+            // 审批流程-选择审批人html 生成
+            function makeSelectAudit(code, aid, status) {
+                let divhtml = '';
+                accountGroup.forEach((group, idx) => {
+                    let didivhtml = '';
+                    if(group) {
+                        group.groupList.forEach(item => {
+                            didivhtml += '<dd class="border-bottom p-2 mb-0 " data-id="' + item.id + '" >\n' +
+                                '<p class="mb-0 d-flex"><span class="text-primary">' + item.name + '</span><span\n' +
+                                '                                                                                class="ml-auto">' + item.mobile + '</span></p>\n' +
+                                '                                                                    <span class="text-muted">' + item.role + '</span>\n' +
+                                '                                                                    </dd>\n';
+                        });
+                        divhtml += '<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="' + idx + '" data-type="hide"><i class="fa fa-plus-square"></i></a> ' + group.groupName + '</dt>\n' +
+                            '                                                                <div class="dd-content" data-toggleid="' + idx + '">\n' + didivhtml +
+                            '                                                                </div>\n';
+                    }
+                });
+                let html = '<div class="dropdown-menu dropdown-menu-right" id="' + code + '_dropdownMenu" aria-labelledby="' + code + '_dropdownMenuButton" style="width:220px">\n' +
+                    '                                                        <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"\n' +
+                    '                                                                                     placeholder="姓名/手机 检索" autocomplete="off" data-code="' + code + '"></div>\n' +
+                    '                                                        <dl class="list-unstyled book-list" data-aid="'+ aid +'" data-operate="'+ status +'">\n' + divhtml +
+                    '                                                        </dl>\n' +
+                    '                                                    </div>\n' +
+                    '                                                </div>\n' +
+                    '                                            </span>\n' +
+                    '                                        </span>\n' +
+                    '                                        </li>';
+                return html;
+            }
+        });
+    </script>
+<% } %>

+ 9 - 5
app/view/change/plan_information.ejs

@@ -34,18 +34,22 @@
                         <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) { %>
+                    <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-success btn-sm">审批完成</a>
+                    <% if (ctx.session.sessionUser.accountId === ctx.change.auditors2[ctx.change.auditors2.length-1].aid) { %>
+                        <!--重新审批-->
+                        <a href="#sp-down-back" data-toggle="modal" data-target="#sp-down-back" class="btn btn-warning btn-sm ml-2">重新审批</a>
+                    <% } %>
                     <% 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="javascript: void(0);" data-toggle="modal" data-target="#sp-down-revise" class="btn btn-warning btn-sm ml-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 || 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>
                     <% } %>
+                    <% 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 ml-2">撤销修订</a>
+                    <% } %>
                 <% } %>
             </div>
         </div>

+ 90 - 6
app/view/change/plan_information_modal.ejs

@@ -250,7 +250,7 @@
                                                     <div class="timeline-item-icon bg-success text-light">
                                                         <i class="fa fa-check"></i>
                                                     </div>
-                                                <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                <% } else if(ctx.helper._.includes([auditConst.status.checkNo, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                     <div class="timeline-item-icon bg-warning text-light">
                                                         <i class="fa fa-level-up"></i>
                                                     </div>
@@ -294,7 +294,7 @@
                                                     <div class="timeline-item-icon bg-success text-light">
                                                         <i class="fa fa-check"></i>
                                                     </div>
-                                                <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                <% } else if(ctx.helper._.includes([auditConst.status.checkNo, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                     <div class="timeline-item-icon bg-warning text-light">
                                                         <i class="fa fa-level-up"></i>
                                                     </div>
@@ -432,7 +432,7 @@
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                        <% } else if(ctx.helper._.includes([auditConst.status.checkNo, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -483,7 +483,7 @@
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                        <% } else if(ctx.helper._.includes([auditConst.status.checkNo, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -626,7 +626,7 @@
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                        <% } else if(ctx.helper._.includes([auditConst.status.checkNo, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -689,7 +689,7 @@
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                        <% } else if(ctx.helper._.includes([auditConst.status.checkNo, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -762,6 +762,59 @@
         </div>
     <% } %>
 <% } %>
+<% if (ctx.change.status === auditConst.status.checked && ctx.session.sessionUser.accountId === ctx.change.auditors2[ctx.change.auditors2.length-1].aid) { %>
+    <% if (!authMobile && ctx.session.sessionUser.loginStatus === 0) { %>
+        <!--终审重新审批-->
+        <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/plan/check/again" onsubmit="return false;">
+                    <div class="modal-header">
+                        <h5 class="modal-title">重新审批</h5>
+                    </div>
+                    <div class="modal-body">
+                        <h5>确认由「终审-<%= ctx.change.auditors2[ctx.change.auditors2.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="cpid" value="<%= 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-btn" class="btn btn-warning btn-sm" <% if (ctx.session.sessionUser.loginStatus === 0) { %>disabled<% } %>>确定重审</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+    <% } %>
+<% } %>
 <% if (ctx.change.status === auditConst.status.checked && ctx.session.sessionUser.accountId === ctx.change.uid) { %>
     <% if (!authMobile && ctx.session.sessionUser.loginStatus === 0) { %>
         <!--原报修订变更-->
@@ -889,6 +942,37 @@
         }
     });
 
+    $('#re-shenpi-btn').click(function () {
+        const data = {
+            cpid: parseInt('<%- ctx.change.id %>'),
+        };
+        <% if (ctx.session.sessionUser.loginStatus === 0) { %>
+        const code = $("#againForm input[name='code']").val();
+        if ($(this).hasClass('disabled')) {
+            return false;
+        }
+        if (code.length < 6) {
+            // alert('请填写正确的验证码');
+            toastr.error('请填写正确的验证码');
+            return false;
+        }
+        data.code = code;
+        <% } %>
+        $.ajax({
+            url: '/tender/<%- tender.id %>/change/plan/check/again?_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);
+                }
+            }
+        });
+    })
+
     $('#re-shenpi-btn2').click(function () {
         const data = {
             cpid: parseInt('<%- ctx.change.id %>'),

+ 9 - 5
app/view/change/project_information.ejs

@@ -31,18 +31,22 @@
                         <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) { %>
+                    <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm">审批完成</a>
+                    <% if (ctx.session.sessionUser.accountId === ctx.change.auditors2[ctx.change.auditors2.length-1].aid) { %>
+                        <!--重新审批-->
+                        <a href="#sp-down-back" data-toggle="modal" data-target="#sp-down-back" class="btn btn-warning btn-sm ml-2">重新审批</a>
+                    <% } %>
                     <% 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-down-revise" data-toggle="modal" data-target="#sp-down-revise" class="btn btn-warning btn-sm ml-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 || 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>
                     <% } %>
+                    <% 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 ml-2">撤销修订</a>
+                    <% } %>
                 <% } else if (ctx.change.status === auditConst.status.checkNo) { %>
                     <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-danger btn-sm">审批终止</a>
                 <% } %>

+ 92 - 8
app/view/change/project_information_modal.ejs

@@ -198,7 +198,7 @@
                                                     <div class="timeline-item-icon bg-success text-light">
                                                         <i class="fa fa-check"></i>
                                                     </div>
-                                                <% } else if(auditor.status === auditConst.status.back || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                <% } else if(ctx.helper._.includes([auditConst.status.back, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                     <div class="timeline-item-icon bg-warning text-light">
                                                         <i class="fa fa-level-up"></i>
                                                     </div>
@@ -246,7 +246,7 @@
                                                     <div class="timeline-item-icon bg-success text-light">
                                                         <i class="fa fa-check"></i>
                                                     </div>
-                                                <% } else if(auditor.status === auditConst.status.back || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                <% } else if(ctx.helper._.includes([auditConst.status.back, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                     <div class="timeline-item-icon bg-warning text-light">
                                                         <i class="fa fa-level-up"></i>
                                                     </div>
@@ -388,7 +388,7 @@
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.back || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                        <% } else if(ctx.helper._.includes([auditConst.status.back, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -409,7 +409,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.auditString[auditor.status] %>"><%- auditConst.auditString[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>
@@ -445,7 +445,7 @@
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.back || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                        <% } else if(ctx.helper._.includes([auditConst.status.back, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -594,7 +594,7 @@
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.back || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                        <% } else if(ctx.helper._.includes([auditConst.status.back, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -661,7 +661,7 @@
                                                             <div class="timeline-item-icon bg-success text-light">
                                                                 <i class="fa fa-check"></i>
                                                             </div>
-                                                        <% } else if(auditor.status === auditConst.status.back || auditor.status === auditConst.status.revise || auditor.status === auditConst.status.checkCancel) {%>
+                                                        <% } else if(ctx.helper._.includes([auditConst.status.back, auditConst.status.revise, auditConst.status.checkCancel, auditConst.status.checkAgain], auditor.status)) {%>
                                                             <div class="timeline-item-icon bg-warning text-light">
                                                                 <i class="fa fa-level-up"></i>
                                                             </div>
@@ -827,6 +827,59 @@
         </div>
     </div>
 </div>
+<% if (ctx.change.status === auditConst.status.checked && ctx.session.sessionUser.accountId === ctx.change.auditors2[ctx.change.auditors2.length-1].aid) { %>
+    <% if (!authMobile && ctx.session.sessionUser.loginStatus === 0) { %>
+        <!--终审重新审批-->
+        <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/project/check/again" onsubmit="return false;">
+                    <div class="modal-header">
+                        <h5 class="modal-title">重新审批</h5>
+                    </div>
+                    <div class="modal-body">
+                        <h5>确认由「终审-<%= ctx.change.auditors2[ctx.change.auditors2.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="cpid" value="<%= 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-btn" class="btn btn-warning btn-sm" <% if (ctx.session.sessionUser.loginStatus === 0) { %>disabled<% } %>>确定重审</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+    <% } %>
+<% } %>
 <% if (ctx.change.status === auditConst.status.checked && ctx.session.sessionUser.accountId === ctx.change.uid) { %>
     <% if (!authMobile && ctx.session.sessionUser.loginStatus === 0) { %>
         <!--原报修订变更-->
@@ -948,6 +1001,37 @@
         }
     });
 
+    $('#re-shenpi-btn').click(function () {
+        const data = {
+            cpid: parseInt('<%- ctx.change.id %>'),
+        };
+        <% if (ctx.session.sessionUser.loginStatus === 0) { %>
+        const code = $("#againForm input[name='code']").val();
+        if ($(this).hasClass('disabled')) {
+            return false;
+        }
+        if (code.length < 6) {
+            // alert('请填写正确的验证码');
+            toastr.error('请填写正确的验证码');
+            return false;
+        }
+        data.code = code;
+        <% } %>
+        $.ajax({
+            url: '/tender/<%- tender.id %>/change/project/check/again?_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);
+                }
+            }
+        });
+    })
+
     $('#re-shenpi-btn2').click(function () {
         const data = {
             cpid: parseInt('<%- ctx.change.id %>'),
@@ -959,7 +1043,7 @@
         }
         if (code.length < 6) {
             // alert('请填写正确的验证码');
-            toast('请填写正确的验证码', 'error');
+            toastr.error('请填写正确的验证码');
             return false;
         }
         data.code = code;

+ 867 - 0
app/view/settle/audit_modal.ejs

@@ -0,0 +1,867 @@
+<% if (ctx.settle && (ctx.settle.audit_status === auditConst.status.uncheck || ctx.settle.audit_status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ctx.settle.user_id) { %>
+<!--上报审批-->
+<div class="modal fade" id="sub-sp" 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">
+                <div class="dropdown text-right">
+                    <% if (ctx.tender.info.shenpi.settle !== shenpiConst.sp_status.gdspl && ctx.session.sessionUser.accountId === ctx.settle.user_id) { %>
+                    <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="dropdownMenuButton"
+                            data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                        添加审批流程
+                    </button>
+                    <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton" style="width:220px">
+                        <div class="mb-2 p-2"><input class="form-control form-control-sm" placeholder="姓名/手机 检索"
+                                                     id="gr-search" autocomplete="off"></div>
+                        <dl class="list-unstyled book-list" id="book-list">
+                            <% accountGroup.forEach((group, idx) => { %>
+                            <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>"
+                                   data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                            <div class="dd-content" data-toggleid="<%- idx %>">
+                                <% group.groupList.forEach(item => { %>
+                                <% if (item.id !== ctx.session.sessionUser.accountId) { %>
+                                <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>">
+                                    <p class="mb-0 d-flex"><span class="text-primary"><%- item.name %></span><span
+                                                class="ml-auto"><%- item.mobile %></span></p>
+                                    <span class="text-muted"><%- item.role %></span>
+                                </dd>
+                                <% } %>
+                                <% });%>
+                            </div>
+                            <% }) %>
+                        </dl>
+                    </div>
+                    <% } %>
+                </div>
+                <div class="card mt-3">
+                    <div class="card-header">
+                        审批流程
+                    </div>
+                    <div class="modal-height-500" style="overflow: auto">
+                        <ul class="list-group list-group-flush" id="auditors">
+                            <% for (let i = 0, iLen = ctx.settle.auditorGroups.length; i < iLen; i++) { %>
+                            <% if (ctx.settle.auditorGroups[i][0].audit_order === 0) continue; %>
+                            <li class="list-group-item d-flex" auditorId="<%- ctx.settle.auditorGroups[i][0].audit_id %>">
+                                <div class="col-auto"><%- ctx.settle.auditorGroups[i][0].audit_order %></div>
+                                <div class="col">
+                                    <% for (const auditor of ctx.settle.auditorGroups[i]) { %>
+                                    <div class="d-inline-block mx-1" auditorId="<%- auditor.audit_id %>">
+                                        <i class="fa fa-user text-muted"></i> <%- auditor.name %> <small class="text-muted"><%- auditor.role %></small>
+                                    </div>
+                                    <% } %>
+                                </div>
+                                <div class="col-auto">
+                                    <% if (ctx.settle.auditorGroups[i][0].audit_type !== auditType.key.common) { %>
+                                    <span class="badge badge-pill badge-<%- auditType.info[ctx.settle.auditorGroups[i][0].audit_type].class %> badge-bg-small"><small><%- auditType.info[ctx.settle.auditorGroups[i][0].audit_type].long%></small></span>
+                                    <% } %>
+                                    <% if ((ctx.tender.info.shenpi.settle === shenpiConst.sp_status.sqspr ||
+                                                    (ctx.tender.info.shenpi.settle === shenpiConst.sp_status.gdzs && i+1 !== iLen)) && ctx.session.sessionUser.accountId === ctx.settle.user_id && !ctx.tender.isTourist) { %>
+                                    <a href="javascript: void(0)" class="text-danger pull-right">移除</a>
+                                    <% } %>
+                                </div>
+                            </li>
+                            <% } %>
+                        </ul>
+                    </div>
+                </div>
+            </div>
+            <form class="modal-footer" method="post" action="/tender/<%- ctx.tender.id %>/settle/<%- ctx.settle.settle_order %>/audit/start" name="settle-start">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
+                <% if (ctx.session.sessionUser.accountId === ctx.settle.user_id) { %>
+                <button class="btn btn-primary btn-sm" type="submit">确认上报</button>
+                <% } %>
+            </form>
+        </div>
+    </div>
+</div>
+<% } %>
+<% if(ctx.settle && (ctx.settle.audit_status !== auditConst.status.uncheck)) { %>
+<!--审批流程/结果-->
+<div class="modal fade" id="sp-list" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">审批流程</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-4">
+                        <% if(ctx.settle.audit_status === auditConst.status.checkNo && ctx.session.sessionUser.accountId === ctx.settle.user_id) { %>
+                        <a class="sp-list-item" href="#sub-sp" data-toggle="modal" data-target="#sub-sp"
+                           id="hideSp">修改审批流程</a>
+                        <% } else if(ctx.settle.audit_status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) { %>
+                        <a class="sp-list-item" href="#sub-sp2" data-toggle="modal" data-target="#sub-sp2"
+                           id="hideSp">修改审批流程</a>
+                        <% } %>
+                        <div class="card modal-height-500 mt-3" style="overflow: auto">
+                            <ul class="list-group list-group-flush auditors-list" id="auditors-list">
+                                <% ctx.settle.userGroups.forEach((item, idx) => { %>
+                                <% if (idx === 0) { %>
+                                <li class="list-group-item d-flex justify-content-between align-items-center">
+                                    <span class="mr-1"><i class="fa fa fa-play-circle fa-rotate-90"></i></span>
+                                    <span class="text-muted">
+                                        <% for (const u of item) { %>
+                                        <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.audit_id %>"><%- u.name %></small>
+                                        <% } %>
+                                    </span>
+                                    <span class="badge badge-light badge-pill ml-auto"><small>原报</small></span>
+                                </li>
+                                <% } else if(idx === ctx.settle.userGroups.length -1 && idx !== 0) { %>
+                                <li class="list-group-item d-flex justify-content-between align-items-center">
+                                    <span class="mr-1"><i class="fa fa fa-stop-circle"></i></span>
+                                    <span class="text-muted">
+                                        <% for (const u of item) { %>
+                                        <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.audit_id %>"><%- u.name %></small>
+                                        <% } %>
+                                    </span>
+                                    <div class="d-flex ml-auto">
+                                        <% if (item[0].audit_type !== auditType.key.common) { %>
+                                        <span class="badge badge-pill badge-<%-  auditType.info[item[0].audit_type].class %> p-1"><small><%- auditType.info[item[0].audit_type].short %></small></span>
+                                        <% } %>
+                                        <span class="badge badge-light badge-pill"><small>终审</small></span>
+                                    </div>
+                                </li>
+                                <% } else {%>
+                                <li class="list-group-item d-flex justify-content-between align-items-center">
+                                    <span class="mr-1"><i class="fa fa-chevron-circle-down"></i></span>
+                                    <span class="text-muted">
+                                        <% for (const u of item) { %>
+                                        <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.audit_id %>"><%- u.name %></small>
+                                        <% } %>
+                                    </span>
+                                    <div class="d-flex ml-auto">
+                                        <% if (item[0].audit_type !== auditType.key.common) { %>
+                                        <span class="badge badge-pill badge-<%- auditType.info[item[0].audit_type].class %> p-1"><small><%- auditType.info[item[0].audit_type].short %></small></span>
+                                        <% } %>
+                                        <span class="badge badge-light badge-pill"><small><%= ctx.helper.transFormToChinese(idx) %>审</small></span>
+                                    </div>
+                                </li>
+                                <% } %>
+                                <% }) %>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto">
+                        <% ctx.settle.auditHistory.forEach((his, idx) => { %>
+                        <!-- 展开/收起历史流程 -->
+                        <% if(idx === ctx.settle.auditHistory.length - 1 && ctx.settle.auditHistory.length !== 1) { %>
+                        <div class="text-right">
+                            <a href="javascript: void(0);" id="fold-btn" data-target="show" >展开历史审批流程</a>
+                        </div>
+                        <% } %>
+                        <div class="<%- idx < ctx.settle.auditHistory.length - 1 ? 'fold-card' : '' %>">
+                            <div class="text-center text-muted"><%- idx+1 %>#</div>
+                            <ul class="timeline-list list-unstyled mt-2 <% if (idx === ctx.settle.auditHistory.length - 1 && ctx.settle.auditHistory.length !== 1) { %>last-auditor-list<% } %>">
+                                <% his.forEach((group, index) => { %>
+                                <li class="timeline-list-item pb-2 <% if (group.audit_status === auditConst.status.uncheck && idx === ctx.settle.auditHistory.length - 1 && ctx.settle.auditHistory.length !== 1) { %>is_uncheck<% } %>">
+                                    <% if (group.auditYear) { %>
+                                    <div class="timeline-item-date">
+                                        <%- group.auditYear %>
+                                        <span><%- group.auditDate %></span>
+                                        <span><%- group.auditTime %></span>
+                                    </div>
+                                    <% } %>
+                                    <% if (index < his.length - 1) { %>
+                                    <div class="timeline-item-tail"></div>
+                                    <% } %>
+                                    <% if (group.audit_order === 0) { %>
+                                    <div class="timeline-item-icon bg-success text-light"><i class="fa fa-caret-down"></i></div>
+                                    <% } else if (group.audit_status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light"><i class="fa fa-check"></i></div>
+                                    <% } else if (group.audit_status === auditConst.status.checkNo || group.audit_status === auditConst.status.checkNoPre || group.audit_status === auditConst.status.checkCancel) { %>
+                                    <div class="timeline-item-icon bg-warning text-light"><i class="fa fa-level-up"></i></div>
+                                    <% } else if (group.audit_status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light"><i class="fa fa-ellipsis-h"></i></div>
+                                    <% } else { %>
+                                    <div class="timeline-item-icon bg-secondary text-light"></div>
+                                    <% } %>
+                                    <div class="timeline-item-content">
+                                        <div class="py-1">
+                                            <span class="text-black-50">
+                                                <%- group.auditName %>
+                                                <% if (group.audit_type !== auditType.key.common) { %><span class="text-<%- auditType.info[group.audit_type].class %> "><%- auditType.info[group.audit_type].long %></span><% } %>
+                                            </span>
+                                            <% if (group.audit_order === 0) { %>
+                                            <span class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                            <% } else if (group.audit_status !== auditConst.status.uncheck) { %>
+                                            <span class="pull-right <%- auditConst.statusClass[group.audit_status] %>"><%- auditConst.statusString[group.audit_status] %></span>
+                                            <% } %>
+                                        </div>
+                                        <div class="card">
+                                            <div class="card-body px-3 py-0">
+                                                <% for (const [i, auditor] of group.auditors.entries()) { %>
+                                                <div class="card-text p-2 py-3 row <%- ( i > 0 ? 'border-top' : '') %>">
+                                                    <div class="col">
+                                                        <span class="h6"><%- auditor.name %></span>
+                                                        <span class="text-muted ml-1"><%- auditor.role %></span>
+                                                    </div>
+                                                    <div class="col">
+                                                        <% if (auditor.audit_status === auditConst.status.checked) { %>
+                                                        <span class="pull-right text-success"><i class="fa fa-check-circle"></i></span>
+                                                        <% } if (auditor.audit_status === auditConst.status.checkNo || auditor.audit_status === auditConst.status.checkNoPre || auditor.audit_status === auditConst.status.checkCancel) { %>
+                                                        <span class="pull-right text-warning"><i class="fa fa-share-square fa-rotate-270"></i></span>
+                                                        <% } %>
+                                                    </div>
+                                                    <% if (auditor.opinion) { %>
+                                                    <div class="col-12 py-1 bg-light"><i class="fa fa-commenting-o mr-1"></i><%- auditor.opinion%></div>
+                                                    <% } %>
+                                                </div>
+                                                <% } %>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </li>
+                                <% }) %>
+                            </ul>
+                        </div>
+                        <% }) %>
+                    </div>
+                </div>
+            </div>
+            <form class="modal-footer" method="post" action="audit/start" name="settle-start">
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <% if(ctx.settle.audit_status === auditConst.status.checkNo && ctx.session.sessionUser.accountId === ctx.settle.user_id) { %>
+                <button class="btn btn-primary btn-sm sp-list-item" type="submit">确认上报</button>
+                <% } %>
+            </form>
+        </div>
+    </div>
+</div>
+<% } %>
+<% if (ctx.settle && (ctx.settle.audit_status === auditConst.status.checking || ctx.settle.audit_status === auditConst.status.checkNoPre) && ctx.settle.curAuditorIds.indexOf(ctx.session.sessionUser.accountId) >= 0) { %>
+<!--审批通过-->
+<div class="modal fade sp-location-list" id="sp-done" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <form class="modal-content" action="audit/check" method="post" id="audit-check">
+            <div class="modal-header">
+                <h5 class="modal-title">审批通过</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-4">
+                        <% if(ctx.settle.audit_status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) { %>
+                        <a class="sp-list-item" href="#sub-sp2" data-toggle="modal" data-target="#sub-sp2" id="hideSp">修改审批流程</a>
+                        <% } %>
+                        <div class="card modal-height-500 mt-3" style="overflow: auto">
+                            <ul class="list-group list-group-flush auditors-list">
+                                <% ctx.settle.userGroups.forEach((item, idx) => { %>
+                                <li class="list-group-item d-flex justify-content-between align-items-center">
+                                    <% if (idx === 0) { %>
+                                    <span class="mr-1"><i class="fa fa fa-play-circle fa-rotate-90"></i></span>
+                                    <% } else if (idx === ctx.settle.userGroups.length -1 && idx !== 0) { %>
+                                    <span class="mr-1"><i class="fa fa fa-stop-circle"></i></span>
+                                    <% } else { %>
+                                    <span class="mr-1"><i class="fa fa-chevron-circle-down"></i></span>
+                                    <% } %>
+                                    <span class="text-muted">
+                                        <% for (const u of item) { %>
+                                        <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.audit_id %>"><%- u.name %></small>
+                                        <% } %>
+                                    </span>
+                                    <div class="d-flex ml-auto">
+                                        <% if (item[0].audit_type !== auditType.key.common) { %>
+                                        <span class="badge badge-pill badge-<%-  auditType.info[item[0].audit_type].class %> p-1"><small><%- auditType.info[item[0].audit_type].short %></small></span>
+                                        <% } %>
+                                        <span class="badge badge-light badge-pill"><small><%- item.auditName %></small></span>
+                                    </div>
+                                </li>
+                                <% }) %>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto">
+                        <% ctx.settle.auditHistory.forEach((his, idx) => { %>
+                        <!-- 展开/收起历史流程 -->
+                        <% if(idx === ctx.settle.auditHistory.length - 1 && ctx.settle.auditHistory.length !== 1) { %>
+                        <div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a></div>
+                        <% } %>
+                        <div class="<%- idx < ctx.settle.auditHistory.length - 1 ? 'fold-card' : '' %>">
+                            <div class="text-center text-muted"><%- idx+1 %>#</div>
+                            <ul class="timeline-list list-unstyled mt-2 <% if (idx === ctx.settle.auditHistory.length - 1 && ctx.settle.auditHistory.length !== 1) { %>last-auditor-list<% } %>">
+                                <% his.forEach((group, index) => { %>
+                                <li class="timeline-list-item pb-2 <% if (group.audit_status === auditConst.status.uncheck && idx === ctx.settle.auditHistory.length - 1 && ctx.settle.auditHistory.length !== 1) { %>is_uncheck<% } %>">
+                                    <% if (group.auditYear) { %>
+                                    <div class="timeline-item-date">
+                                        <%- group.auditYear %>
+                                        <span><%- group.auditDate %></span>
+                                        <span><%- group.auditTime %></span>
+                                    </div>
+                                    <% } %>
+                                    <% if (index < his.length - 1) { %>
+                                    <div class="timeline-item-tail"></div>
+                                    <% } %>
+                                    <% if (group.audit_order === 0) { %>
+                                    <div class="timeline-item-icon bg-success text-light"><i class="fa fa-caret-down"></i></div>
+                                    <% } else if (group.audit_status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light"><i class="fa fa-check"></i></div>
+                                    <% } else if (group.audit_status === auditConst.status.checkNo || group.audit_status === auditConst.status.checkNoPre || group.audit_status === auditConst.status.checkCancel) { %>
+                                    <div class="timeline-item-icon bg-warning text-light"><i class="fa fa-level-up"></i></div>
+                                    <% } else if (group.audit_status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light"><i class="fa fa-ellipsis-h"></i></div>
+                                    <% } else { %>
+                                    <div class="timeline-item-icon bg-secondary text-light"></div>
+                                    <% } %>
+                                    <div class="timeline-item-content">
+                                        <div class="py-1">
+                                            <span class="text-black-50">
+                                                <%- group.auditName %>
+                                                <% if (group.audit_type !== auditType.key.common) { %><span class="text-<%- auditType.info[group.audit_type].class %> "><%- auditType.info[group.audit_type].long %></span><% } %>
+                                            </span>
+                                            <% if (group.audit_order === 0) { %>
+                                            <span class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                            <% } else if (group.audit_status !== auditConst.status.uncheck) { %>
+                                            <span class="pull-right <%- auditConst.statusClass[group.audit_status] %>"><%- auditConst.statusString[group.audit_status] %></span>
+                                            <% } %>
+                                        </div>
+                                        <div class="card">
+                                            <div class="card-body px-3 py-0">
+                                                <% for (const [i, auditor] of group.auditors.entries()) { %>
+                                                <div class="card-text p-2 py-3 row <%- ( i > 0 ? 'border-top' : '') %>">
+                                                    <div class="col">
+                                                        <span class="h6"><%- auditor.name %></span>
+                                                        <span class="text-muted ml-1"><%- auditor.role %></span>
+                                                    </div>
+                                                    <div class="col">
+                                                        <% if (auditor.audit_status === auditConst.status.checked) { %>
+                                                        <span class="pull-right text-success"><i class="fa fa-check-circle"></i></span>
+                                                        <% } if (auditor.audit_status === auditConst.status.checkNo || auditor.audit_status === auditConst.status.checkNoPre || auditor.audit_status === auditConst.status.checkCancel) { %>
+                                                        <span class="pull-right text-warning"><i class="fa fa-share-square fa-rotate-270"></i></span>
+                                                        <% } else if (auditor.audit_status === auditConst.status.checking) { %>
+                                                        <span class="pull-right text-warning"><i class="fa fa-commenting"></i></span>
+                                                        <% } %>
+                                                    </div>
+                                                    <% if (auditor.audit_status !== auditConst.status.uncheck && auditor.opinion) { %>
+                                                    <div class="col-12 py-1 bg-light"><i class="fa fa-commenting-o mr-1"></i><%- auditor.opinion%></div>
+                                                    <% } %>
+                                                    <% if (auditor.audit_status === auditConst.status.checking && auditor.audit_id === ctx.session.sessionUser.accountId) { %>
+                                                    <div class="col-12 py-1 bg-light">
+                                                        <textarea class="form-control form-control-sm" name="opinion">同意</textarea>
+                                                    </div>
+                                                    <% } %>
+                                                </div>
+                                                <% } %>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </li>
+                                <% }) %>
+                            </ul>
+                        </div>
+                        <% }) %>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                <input type="hidden" name="checkType" value="<%= auditConst.status.checked %>" />
+                <button type="submit" class="btn btn-success btn-sm">确认通过</button>
+            </div>
+        </form>
+    </div>
+</div>
+<!--审批退回-->
+<div class="modal fade sp-location-list" id="sp-back" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <form class="modal-content modal-lg" action="audit/check" method="post" id="audit-check-no">
+            <div class="modal-header">
+                <h5 class="modal-title">审批退回</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-4">
+                        <% if(ctx.settle.audit_status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) { %>
+                        <a class="sp-list-item" href="#sub-sp2" data-toggle="modal" data-target="#sub-sp2" id="hideSp">修改审批流程</a>
+                        <% } %>
+                        <div class="card modal-height-500 mt-3" style="overflow: auto">
+                            <ul class="list-group list-group-flush auditors-list">
+                                <% ctx.settle.userGroups.forEach((item, idx) => { %>
+                                <li class="list-group-item d-flex justify-content-between align-items-center">
+                                    <% if (idx === 0) { %>
+                                    <span class="mr-1"><i class="fa fa fa-play-circle fa-rotate-90"></i></span>
+                                    <% } else if (idx === ctx.settle.userGroups.length -1 && idx !== 0) { %>
+                                    <span class="mr-1"><i class="fa fa fa-stop-circle"></i></span>
+                                    <% } else { %>
+                                    <span class="mr-1"><i class="fa fa-chevron-circle-down"></i></span>
+                                    <% } %>
+                                    <span class="text-muted">
+                                        <% for (const u of item) { %>
+                                        <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.audit_id %>"><%- u.name %></small>
+                                        <% } %>
+                                    </span>
+                                    <div class="d-flex ml-auto">
+                                        <% if (item[0].audit_type !== auditType.key.common) { %>
+                                        <span class="badge badge-pill badge-<%-  auditType.info[item[0].audit_type].class %> p-1"><small><%- auditType.info[item[0].audit_type].short %></small></span>
+                                        <% } %>
+                                        <span class="badge badge-light badge-pill"><small><%- item.auditName %></small></span>
+                                    </div>
+                                </li>
+                                <% }) %>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto">
+                        <% ctx.settle.auditHistory.forEach((his, idx) => { %>
+                        <!-- 展开/收起历史流程 -->
+                        <% if(idx === ctx.settle.auditHistory.length - 1 && ctx.settle.auditHistory.length !== 1) { %>
+                        <div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show" data-idx="<%- idx + 1 %>">展开历史审批流程</a></div>
+                        <% } %>
+                        <div class="<%- idx < ctx.settle.auditHistory.length - 1 ? 'fold-card' : '' %>">
+                            <div class="text-center text-muted"><%- idx+1 %>#</div>
+                            <ul class="timeline-list list-unstyled mt-2 <% if (idx === ctx.settle.auditHistory.length - 1 && ctx.settle.auditHistory.length !== 1) { %>last-auditor-list<% } %>">
+                                <% his.forEach((group, index) => { %>
+                                <li class="timeline-list-item pb-2 <% if (group.audit_status === auditConst.status.uncheck && idx === ctx.settle.auditHistory.length - 1 && ctx.settle.auditHistory.length !== 1) { %>is_uncheck<% } %>">
+                                    <% if (group.auditYear) { %>
+                                    <div class="timeline-item-date">
+                                        <%- group.auditYear %>
+                                        <span><%- group.auditDate %></span>
+                                        <span><%- group.auditTime %></span>
+                                    </div>
+                                    <% } %>
+                                    <% if (index < his.length - 1) { %>
+                                    <div class="timeline-item-tail"></div>
+                                    <% } %>
+                                    <% if (group.audit_order === 0) { %>
+                                    <div class="timeline-item-icon bg-success text-light"><i class="fa fa-caret-down"></i></div>
+                                    <% } else if (group.audit_status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light"><i class="fa fa-check"></i></div>
+                                    <% } else if (group.audit_status === auditConst.status.checkNo || group.audit_status === auditConst.status.checkNoPre || group.audit_status === auditConst.status.checkCancel) { %>
+                                    <div class="timeline-item-icon bg-warning text-light"><i class="fa fa-level-up"></i></div>
+                                    <% } else if (group.audit_status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light"><i class="fa fa-ellipsis-h"></i></div>
+                                    <% } else { %>
+                                    <div class="timeline-item-icon bg-secondary text-light"></div>
+                                    <% } %>
+                                    <div class="timeline-item-content">
+                                        <div class="py-1">
+                                            <span class="text-black-50">
+                                                <%- group.auditName %>
+                                                <% if (group.audit_type !== auditType.key.common) { %><span class="text-<%- auditType.info[group.audit_type].class %> "><%- auditType.info[group.audit_type].long %></span><% } %>
+                                            </span>
+                                            <% if (group.audit_order === 0) { %>
+                                            <span class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                            <% } else if (group.audit_status !== auditConst.status.uncheck) { %>
+                                            <span class="pull-right <%- auditConst.statusClass[group.audit_status] %>"><%- auditConst.statusString[group.audit_status] %></span>
+                                            <% } %>
+                                        </div>
+                                        <div class="card">
+                                            <div class="card-body px-3 py-0">
+                                                <% for (const [i, auditor] of group.auditors.entries()) { %>
+                                                <div class="card-text p-2 py-3 row <%- ( i > 0 ? 'border-top' : '') %>">
+                                                    <div class="col">
+                                                        <span class="h6"><%- auditor.name %></span>
+                                                        <span class="text-muted ml-1"><%- auditor.role %></span>
+                                                    </div>
+                                                    <div class="col">
+                                                        <% if (auditor.audit_status === auditConst.status.checked) { %>
+                                                        <span class="pull-right text-success"><i class="fa fa-check-circle"></i></span>
+                                                        <% } if (auditor.audit_status === auditConst.status.checkNo || auditor.audit_status === auditConst.status.checkNoPre || auditor.audit_status === auditConst.status.checkCancel) { %>
+                                                        <span class="pull-right text-warning"><i class="fa fa-share-square fa-rotate-270"></i></span>
+                                                        <% } else if (auditor.audit_status === auditConst.status.checking) { %>
+                                                        <span class="pull-right text-warning"><i class="fa fa-commenting"></i></span>
+                                                        <% } %>
+                                                    </div>
+                                                    <% if (auditor.audit_status !== auditConst.status.uncheck && auditor.opinion) { %>
+                                                    <div class="col-12 py-1 bg-light"><i class="fa fa-commenting-o mr-1"></i><%- auditor.opinion%></div>
+                                                    <% } %>
+                                                    <% if (auditor.audit_status === auditConst.status.checking && auditor.audit_id === ctx.session.sessionUser.accountId) { %>
+                                                    <div class="col-12 py-1 bg-light">
+                                                        <textarea class="form-control form-control-sm" name="opinion">不同意</textarea>
+                                                        <div id="reject-process" class="alert alert-warning mt-1 mb-0 p-2">
+                                                            <div class="form-check form-check-inline">
+                                                                <input class="form-check-input" type="radio" name="checkType" id="inlineRadio1" value="<%- auditConst.status.checkNo %>">
+                                                                <label class="form-check-label" for="inlineRadio1">退回原报 <%- ctx.settle.user.name %></label>
+                                                            </div>
+                                                            <% if (auditor.audit_order > 1) { %>
+                                                            <div class="form-check form-check-inline">
+                                                                <input class="form-check-input" type="radio" name="checkType" id="inlineRadio2" value="<%- auditConst.status.checkNoPre %>">
+                                                                <label class="form-check-label" for="inlineRadio2">退回上一审批人
+                                                                    <% const pre = his.find(x => { return x.audit_order === auditor.audit_order - 1}); %>
+                                                                    <%- ( pre ? pre.name : '') %>
+                                                                </label>
+                                                            </div>
+                                                            <% } %>
+                                                        </div>
+                                                    </div>
+                                                    <% } %>
+                                                </div>
+                                                <% } %>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </li>
+                                <% }) %>
+                            </ul>
+                        </div>
+                        <% }) %>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                <button type="submit" class="btn btn-warning btn-sm">确认退回</button>
+            </div>
+        </form>
+    </div>
+</div>
+<% } %>
+<% if (ctx.settle && ctx.settle.user_id === ctx.session.sessionUser.accountId && ctx.settle.settle_order === ctx.settle.highOrder && (ctx.settle.audit_status === auditConst.status.checkNo || ctx.settle.audit_status === auditConst.status.uncheck)) { %>
+<div class="modal fade" id="del-qi" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <form class="modal-content" action='/tender/<%= ctx.tender.id %>/settle/delete' method="post">
+            <div class="modal-header">
+                <h5 class="modal-title">删除期</h5>
+            </div>
+            <div class="modal-body">
+                <h5>确认删除「第<%= ctx.settle.order %>期」?</h5>
+                <h5>删除后,数据无法恢复,请谨慎操作。</h5>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" name="stage_id" value="<%= ctx.settle.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="submit" class="btn btn-danger btn-sm">确定删除</button>
+            </div>
+        </form>
+    </div>
+</div>
+<% } %>
+<% if (ctx.settle && ctx.settle.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.settle && ctx.settle.audit_status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) { %>
+<!--上报审批-->
+<div class="modal fade" id="sub-sp2" data-backdrop="static">
+    <div class="modal-dialog" style="max-width: 650px" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">修改审批流程</h5>
+            </div>
+            <div class="modal-body">
+                <div class="card mt-1">
+                    <div class="modal-height-500" style="overflow: auto">
+                        <style>
+                            #admin-edit-shenpi thead th {
+                                border-bottom: 0;
+                            }
+                            #admin-edit-shenpi td, #admin-edit-shenpi th {
+                                padding: 0.75rem;
+                            }
+                            #admin-edit-shenpi th {
+                                background: none;
+                                color: #212529;
+                                border-top: 0;
+                            }
+                        </style>
+                        <table class="table table-hover" id="admin-edit-shenpi">
+                            <thead>
+                            <tr class="card-header text-center">
+                                <th width="100px">审批流程</th>
+                                <th>审批人员</th>
+                                <th width="80" style="text-align: center">审批状态</th>
+                                <th width="200" style="text-align: center">操作</th>
+                            </tr>
+                            </thead>
+                            <tbody id="admin-edit-shenpi-list">
+                            <% for (const [i, group] of ctx.settle.userGroups.entries()) { %>
+                            <% if (i === 0) continue; %>
+                            <% for (const [j, auditor] of group.entries()) { %>
+                            <tr>
+                                <td class="text-left d-flex">
+                                    <% if (j === 0) { %>
+                                    <%- i + '审' %>
+                                    <% if (auditor.audit_type !== auditType.key.common) { %>
+                                    <span class="ml-2 badge badge-pill badge-<%-  auditType.info[auditor.audit_type].class %> p-1"><small><%- auditType.info[auditor.audit_type].short %></small></span>
+                                    <% } %>
+                                    <% } %>
+                                </td>
+                                <td></span> <%- auditor.name %> <small class="text-muted"><%- auditor.role %></small></td>
+                                <td style="text-align: center"><span class="<%- auditConst.auditStringClass[auditor.audit_status] %>"><%- auditor.audit_status !== auditConst.status.uncheck ? auditConst.auditString[auditor.audit_status] : '待审批'  %></span></td>
+                                <td style="text-align: center">
+                                    <% if (auditor.audit_status === auditConst.status.checking && j === group.length - 1) { %>
+                                    <span class="dropdown mr-2">
+                                    <a href="javascript: void(0)" class="add-audit" id="<%- auditor.audit_id %>_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">增加</a>
+                                    <div class="dropdown-menu dropdown-menu-right" id="<%- auditor.audit_id %>_add_dropdownMenu" aria-labelledby="<%- auditor.audit_id %>_add_dropdownMenuButton" style="width:220px">
+                                        <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"
+                                                                     placeholder="姓名/手机 检索" autocomplete="off" data-code="<%- auditor.audit_id %>_add"></div>
+                                        <dl class="list-unstyled book-list" data-aid="<%- auditor.audit_id %>" data-operate="add">
+                                            <% accountGroup.forEach((group, idx) => { %>
+                                            <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>"
+                                                   data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                                                <div class="dd-content" data-toggleid="<%- idx %>">
+                                                    <% group.groupList.forEach(item => { %>
+                                                    <% if (item.id !== ctx.settle.user_id) { %>
+                                                    <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>">
+                                                                <p class="mb-0 d-flex"><span class="text-primary"><%- item.name %></span><span
+                                                                            class="ml-auto"><%- item.mobile %></span></p>
+                                                                <span class="text-muted"><%- item.role %></span>
+                                                            </dd>
+                                                    <% } %>
+                                                    <% });%>
+                                                </div>
+                                            <% }) %>
+                                        </dl>
+                                    </div>
+                                    </span>
+                                    <% } %>
+                                    <% if (auditor.audit_status === auditConst.status.uncheck) { %>
+                                    <% if (j === group.length - 1) { %>
+                                    <span class="dropdown mr-2">
+                                    <a href="javascript: void(0)" class="add-audit" id="<%- auditor.audit_id %>_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">增加</a>
+                                        <div class="dropdown-menu dropdown-menu-right" id="<%- auditor.audit_id %>_add_dropdownMenu" aria-labelledby="<%- auditor.audit_id %>_add_dropdownMenuButton" style="width:220px">
+                                            <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"
+                                                                         placeholder="姓名/手机 检索" autocomplete="off" data-code="<%- auditor.audit_id %>_add"></div>
+                                            <dl class="list-unstyled book-list" data-aid="<%- auditor.audit_id %>" data-operate="add">
+                                                <% accountGroup.forEach((group, idx) => { %>
+                                                <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>"
+                                                       data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                                                    <div class="dd-content" data-toggleid="<%- idx %>">
+                                                        <% group.groupList.forEach(item => { %>
+                                                        <% if (item.id !== ctx.settle.user_id) { %>
+                                                        <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>">
+                                                                    <p class="mb-0 d-flex"><span class="text-primary"><%- item.name %></span><span
+                                                                                class="ml-auto"><%- item.mobile %></span></p>
+                                                                    <span class="text-muted"><%- item.role %></span>
+                                                                </dd>
+                                                        <% } %>
+                                                        <% });%>
+                                                    </div>
+                                                <% }) %>
+                                            </dl>
+                                        </div>
+                                    </span>
+                                    <% if (auditor.audit_type !== auditType.key.common) { %>
+                                    <span class="dropdown mr-2">
+                                    <a href="javascript: void(0)" class="add-audit" id="<%- auditor.audit_id %>_add-sibling_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">平级</a>
+                                        <div class="dropdown-menu dropdown-menu-right" id="<%- auditor.audit_id %>_add-sibling_dropdownMenu" aria-labelledby="<%- auditor.audit_id %>_add-sibling_dropdownMenuButton" style="width:220px">
+                                            <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"
+                                                                         placeholder="姓名/手机 检索" autocomplete="off" data-code="<%- auditor.audit_id %>_add-sibling"></div>
+                                            <dl class="list-unstyled book-list" data-aid="<%- auditor.audit_id %>" data-operate="add-sibling">
+                                                <% accountGroup.forEach((group, idx) => { %>
+                                                <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>"
+                                                       data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                                                    <div class="dd-content" data-toggleid="<%- idx %>">
+                                                        <% group.groupList.forEach(item => { %>
+                                                        <% if (item.id !== ctx.settle.user_id) { %>
+                                                        <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>">
+                                                                    <p class="mb-0 d-flex"><span class="text-primary"><%- item.name %></span><span
+                                                                                class="ml-auto"><%- item.mobile %></span></p>
+                                                                    <span class="text-muted"><%- item.role %></span>
+                                                                </dd>
+                                                        <% } %>
+                                                        <% });%>
+                                                    </div>
+                                                <% }) %>
+                                            </dl>
+                                        </div>
+                                    </span>
+                                    <% } %>
+                                    <% } %>
+                                    <span class="dropdown mr-2">
+                                        <a href="javascript: void(0)" class="change-audit" id="<%- auditor.audit_id %>_change_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">更换</a>
+                                        <div class="dropdown-menu dropdown-menu-right" id="<%- auditor.audit_id %>_change_dropdownMenu" aria-labelledby="<%- auditor.audit_id %>_change_dropdownMenuButton" style="width:220px">
+                                            <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"
+                                                                         placeholder="姓名/手机 检索" autocomplete="off" data-code="<%- auditor.audit_id %>_change"></div>
+                                            <dl class="list-unstyled book-list" data-aid="<%- auditor.audit_id %>" data-operate="change">
+                                                <% accountGroup.forEach((group, idx) => { %>
+                                                <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>"
+                                                       data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                                                    <div class="dd-content" data-toggleid="<%- idx %>">
+                                                        <% group.groupList.forEach(item => { %>
+                                                        <% if (item.id !== ctx.settle.user_id) { %>
+                                                        <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>">
+                                                                    <p class="mb-0 d-flex"><span class="text-primary"><%- item.name %></span><span
+                                                                                class="ml-auto"><%- item.mobile %></span></p>
+                                                                    <span class="text-muted"><%- item.role %></span>
+                                                                </dd>
+                                                        <% } %>
+                                                        <% });%>
+                                                    </div>
+                                                <% }) %>
+                                            </dl>
+                                        </div>
+                                    </span>
+                                    <span class="dropdown">
+                                    <a href="javascript: void(0)" class="text-danger" title="移除" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">移除</a>
+                                    <div class="dropdown-menu">
+                                        <span class="dropdown-item" href="javascript:void(0);">确认移除审批人?</span>
+                                        <div class="dropdown-divider"></div>
+                                        <div class="px-2 py-1 text-center">
+                                            <button class="remove-audit btn btn-sm btn-danger" data-id="<%- auditor.audit_id %>">移除</button>
+                                            <button class="btn btn-sm btn-secondary">取消</button>
+                                        </div>
+                                    </div>
+                                    </span>
+                                    <% } %>
+                                </td>
+                            </tr>
+                            <% } %>
+                            <% } %>
+                            </tbody>
+                        </table>
+                    </div>
+                </div>
+            </div>
+            <form class="modal-footer">
+                <div class="mr-auto text-warning">
+                    <span class="mr-3">增加:后级审核人</span>
+                    <span class="">平级:会签/或签增加平级审核人</span>
+                </div>
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+            </form>
+        </div>
+    </div>
+</div>
+<% } %>
+<script type="text/javascript">
+    const csrf = '<%= ctx.csrf %>';
+    const authMobile = '<%= authMobile %>';
+    const auditType = JSON.parse('<%- JSON.stringify(auditType) %>');
+    const shenpi_status = <%- ctx.tender.info.shenpi.settle %>;
+    const shenpiConst =  JSON.parse('<%- JSON.stringify(shenpiConst) %>');
+    const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
+    const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
+    const cur_tenderid = parseInt('<%- ctx.tender.id %>');
+    const settle = parseInt('<%= ctx.settle.user_id %>');
+    const auditorList = JSON.parse(unescape('<%- escape(JSON.stringify(ctx.settle.userGroups || [])) %>'));
+</script>
+<script>
+    $('[name=settle-start]').submit(function (e) {
+        if (checkAuditorFrom()) {
+            $(this).parent().parent().parent().modal('hide');
+            $('#hide-all').hide();
+        } else {
+            return false;
+        }
+    });
+    $('#audit-check').submit(function (e) {
+        const checkType = parseInt($('[name=checkType]').val());
+        const data = {
+            opinion: $(`${'#sp-done'}`).find('[name=opinion]').val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' '),
+            checkType,
+        };
+        $('#sp-done').modal('hide');
+        checkType && dataChecker.checkAndPost(this.action, data);
+        $('#hide-all').hide();
+        return false;
+    });
+    $('#audit-check-no').submit(function (e) {
+        const checkType = parseInt($('[name=checkType]:checked').val());
+        if ($('#warning-text').length) $('#warning-text').remove();
+        if (!checkType && !$('#warning-text').length) {
+            $('#reject-process').prepend('<p id="warning-text" style="color: red; margin: 0;">请选择退回流程</p>');
+            return false;
+        }
+        $('#hide-all').hide();
+        // const data = {
+        //     opinion: $(`${'#sp-back'}`).find('[name=opinion]').val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' '),
+        //     checkType,
+        // };
+    });
+
+    $('.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;
+        scrollBox.scrollTop(0);
+        const hdiv = divSearch($(this).find('textarea')) ? $(this).find('textarea') : null;
+        const hdheight = hdiv ? hdiv.parents('.timeline-item-content').offset().top : null;
+        if (hdiv && scrollBox.length && scrollBox[0].scrollHeight > 200 && hdheight - bdiv > 200) {
+            scrollBox.scrollTop(hdheight - bdiv);
+        }
+    });
+    function divSearch(div) {
+        if (div.length > 0) {
+            return true;
+        }
+        return false;
+    }
+
+    // 展开历史审核记录
+    $('.modal-body #fold-btn').click(function () {
+        const type = $(this).data('target')
+        const auditCard = $(this).parent().parent()
+        if (type === 'show') {
+            $(this).data('target', 'hide')
+            auditCard.find('.fold-card').slideDown('swing', () => {
+                auditCard.find('#fold-btn').text('收起历史审核记录')
+            })
+        } else {
+            $(this).data('target', 'show')
+            auditCard.find('.fold-card').slideUp('swing', () => {
+                auditCard.find('#fold-btn').text('展开历史审核记录')
+            })
+        }
+    });
+
+    // 重新审批按钮
+    $("#re-shenpi-btn").click(function () {
+        const data = {
+        };
+        <% if (ctx.session.sessionUser.loginStatus === 0) { %>
+        const code = $("#sp-down-back input[name='code']").val();
+        if ($(this).hasClass('disabled')) {
+            return false;
+        }
+        if (code.length < 6) {
+            // alert('请填写正确的验证码');
+            toast('请填写正确的验证码', 'error');
+            return false;
+        }
+        data.code = code;
+        <% } %>
+        $.ajax({
+            url: '<%- preUrl %>/audit/check/again',
+            type: 'get',
+            data: data,
+            dataTye: 'json',
+            success: function (response) {
+                if (response.err === 0) {
+                    window.location.href = response.url;
+                } else {
+                    toast(response.msg, 'error');
+                }
+            }
+        });
+    });
+
+    <% if (ctx.settle && ctx.settle.cancancel) { %>
+    $("#cancel-shenpi-btn").click(function () {
+        const data = {
+        };
+        $.ajax({
+            url: '<%- preUrl %>/audit/check/cancel',
+            type: 'get',
+            data: data,
+            dataTye: 'json',
+            success: function (response) {
+                if (response.err === 0) {
+                    window.location.href = response.url;
+                } else {
+                    toast(response.msg, 'error');
+                }
+            }
+        });
+    });
+    <% } %>
+</script>

+ 30 - 12
app/view/settle/index.ejs

@@ -30,7 +30,7 @@
         <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 class="sjs-height-1" id="settle-bills">
                 </div>
                 <!--下部分-->
                 <div class="bcontent-wrap" id="main-bottom">
@@ -72,6 +72,9 @@
                     </div>
                     <div id="bills-tag" class="tab-pane tab-select-show">
                     </div>
+                    <!--附件-->
+                    <div id="fujian" class="tab-pane tab-select-show">
+                    </div>
                 </div>
             </div>
         </div>
@@ -84,13 +87,25 @@
                 <li class="nav-item">
                     <a class="nav-link" content="#bills-tag" href="javascript: void(0);">书签</a>
                 </li>
+                <li class="nav-item">
+                    <a class="nav-link" content="#fujian" href="javascript: void(0);">附件</a>
                 </li>
             </ul>
         </div>
     </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>
 <script>
     const readOnly = <%- settle.readOnly %>;
+    const auditConst = JSON.parse(JSON.stringify('<%- auditConst %>'));
+    const tenderName = '<%- ctx.tender.name %>';
+    const settleOrder = <%- ctx.settle.settle_order %>;
+    const settleComplete = <%- (ctx.settle.audit_status === auditConst.status.checked ? 1 : 0 )%>;
     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) %>');
@@ -104,13 +119,13 @@
             {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: '2|1', rowSpan: '1|1', field: 'cur_contract_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'cur_contract_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '本期数量变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'cur_qc_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'cur_qc_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '本期完成结算|数量', colSpan: '3|1', rowSpan: '1|1', field: 'cur_gather_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'cur_gather_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|完成率(%)', colSpan: '1', rowSpan: '|1', field: 'cur_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) { %>
@@ -128,6 +143,7 @@
         font: '12px 微软雅黑',
         frozenColCount: 5,
         frozenLineColor: '#93b5e4',
+        readOnly,
     };
     const posSpreadSetting = {
         cols: [
@@ -135,9 +151,10 @@
             {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: 'quantity', hAlign: 2, width: 60, formatter: '@', readOnly: true},
+            {title: '本期结算|合同', colSpan: '3|1', rowSpan: '1|1', field: 'cur_contract_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '|数量变更', colSpan: '|1', rowSpan: '|1', field: 'cur_qc_qty', hAlign: 2, width: 80, type: 'Number', readOnly: true},
+            {title: '|完成', colSpan: '|1', rowSpan: '|1', field: 'cur_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},
@@ -146,12 +163,13 @@
             {title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true},
             <% } %>
         ],
-        emptyRows: 20,
+        emptyRows: 0,
         headRows: 2,
         headRowHeight: [25, 25],
         headColWidth: [30],
         defaultRowHeight: 21,
         headerFont: '12px 微软雅黑',
         font: '12px 微软雅黑',
+        readOnly,
     };
 </script>

+ 3 - 0
app/view/settle/modal.ejs

@@ -0,0 +1,3 @@
+<% include ../shares/new_tag_modal.ejs %>
+<% include ../shares/upload_att.ejs %>
+<% include ./audit_modal.ejs %>

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

@@ -137,6 +137,7 @@
             {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: '1', rowSpan: '2', field: 'quantity', hAlign: 2, 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},

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

@@ -0,0 +1 @@
+<% include ./audit_modal.ejs %>

+ 23 - 0
app/view/shares/upload_att.ejs

@@ -0,0 +1,23 @@
+<!--上传附件-->
+<div class="modal fade" id="upload" 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">
+                <div class="form-group">
+                    <label for="formGroupExampleInput">大小限制:30MB,支持<span data-toggle="tooltip" data-placement="bottom" title="" data-original-title="doc,docx,xls,xlsx,ppt,pptx,pdf">office等文档格式</span>、<span data-toggle="tooltip" data-placement="bottom" title="" data-original-title="jpg,png,bmp">图片格式</span>、<span data-toggle="tooltip" data-placement="bottom" title="" data-original-title="rar,zip">压缩包格式</span></label>
+                    <input type="file" class="" id="upload-file" multiple>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary btn-sm" id="upload-file-btn">确认</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const whiteList = JSON.parse('<%- JSON.stringify(ctx.app.config.multipart.whitelist) %>');
+</script>

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

@@ -30,6 +30,7 @@
                                             <% } %>
                                         </div>
                                     </div>
+                                    <% if (!shenpi.sp_status_list[sp.status]) console.log(sp) %>
                                     <div class="alert alert-warning"><%- shenpi.sp_status_list[sp.status].name %>:<%- shenpi.sp_status_list[sp.status].msg %></div>
                                     <div class="lc-show">
                                     <% if (sp.status === shenpi.sp_status.gdspl) { %>

+ 74 - 0
app/view/wap/msg.ejs

@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
+    <meta http-equiv="x-ua-compatible" content="ie=edge">
+    <title>项目通知-计量支付</title>
+    <link rel="stylesheet" href="/public/css/bootstrap/bootstrap.min.css">
+    <link rel="stylesheet" href="/public/css/wap/main.css">
+    <link rel="stylesheet" href="/public/css/toast.css">
+    <link rel="stylesheet" href="/public/css/font-awesome/font-awesome.min.css">
+    <link rel="stylesheet" href="/public/css/toastr.css">
+    <link rel="shortcut icon" href="/public/images/favicon.ico">
+    <style>
+        body {
+            padding: 0;
+        }
+    </style>
+</head>
+<body>
+<div class="container">
+    <!--顶部-->
+    <nav class="fixed-top bg-dark">
+        <div class="my-2 d-flex justify-content-between">
+            <span class="text-white ml-3">项目通知</span>
+            <div class="mr-3">
+                <div class="dropdown">
+                    <button class="btn btn-sm btn-light dropdown-toggle" type="button" data-toggle="dropdown">
+                        <%- ctx.session.sessionUser.name.substr(ctx.session.sessionUser.name.length > 2 ? ctx.session.sessionUser.name.length - 2 : 0) %>
+                    </button>
+                    <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                        <a class="dropdown-item" href="/wap/logout">退出登录</a>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </nav>
+    <!--待审批期列表-->
+    <div class="py-6">
+        <div>
+            <h4 class="text-center" id="title"><%= msgInfo.title %></h4>
+            <p class="text-center text-muted">
+                <span id="creator"><%- msgInfo.creator %></span> 发布于 <span id="release_time"><%= moment(msgInfo.release_time*1000).format('YYYY-MM-DD') %></span>
+            </p>
+            <!--内容开始-->
+            <div class="msg-content border-top-1 pt-3" id="content">
+                <%- msgInfo.content %>
+            </div>
+        </div>
+    </div>
+    <!--底栏菜单-->
+    <nav class="fixed-bottom navbar-dark bg-light border-top">
+        <ul class="nav nav-fill my-2">
+            <li class="nav-item">
+                <a class="nav-link active show-loading" href="/wap/dashboard"><i class="fa fa-check-square-o"></i> 待审批</a>
+            </li>
+            <li class="nav-item">
+                <a class="nav-link text-muted show-loading" href="/wap/list"><i class="fa fa-list-ul"></i> 项目</a>
+            </li>
+        </ul>
+    </nav>
+</div>
+
+<!-- JS. -->
+<script src="/public/js/jquery/jquery-3.2.1.min.js"></script>
+<script src="/public/js/popper/popper.min.js"></script>
+<script src="/public/js/bootstrap/bootstrap.min.js"></script>
+<script src="/public/js/cookies.js"></script>
+<script src="/public/js/toastr.min.js"></script>
+<script src="/public/js/moment/moment.min.js"></script>
+<script src="/public/js/wap/global.js"></script>
+</body>
+
+</html>

+ 9 - 0
config/config.default.js

@@ -179,6 +179,15 @@ module.exports = appInfo => {
     config.bodyParser = {
         jsonLimit: '10mb',
         formLimit: '10mb',
+        queryString: {
+            arrayLimit: 100,
+            depth: 5,
+            parameterLimit: 1000,
+        },
+        enableTypes: ['json', 'form', 'text'],
+        extendTypes: {
+            text: ['text/xml', 'application/xml'],
+        },
     };
 
     config.etag = {

+ 6 - 0
config/web.js

@@ -1361,6 +1361,7 @@ const JsFiles = {
                     '/public/js/shares/sjs_setting.js',
                     '/public/js/zh_calc.js',
                     '/public/js/path_tree.js',
+                    '/public/js/shares/settle_audit.js',
                     '/public/js/settle_select.js',
 
                 ],
@@ -1368,19 +1369,24 @@ const JsFiles = {
             },
             ledger: {
                 files: [
+                    '/public/js/axios/axios.min.js', '/public/js/file-saver/FileSaver.js', '/public/js/js-xlsx/jszip.min.js',
                     '/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/ali_oss.js',
                     '/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/shares/new_tag.js',
+                    '/public/js/shares/tools_att.js',
                     '/public/js/zh_calc.js',
                     '/public/js/path_tree.js',
+                    '/public/js/shares/settle_audit.js',
                     '/public/js/settle_ledger.js',
 
                 ],

Plik diff jest za duży
+ 0 - 15896
package-lock.json


+ 1 - 0
package.json

@@ -48,6 +48,7 @@
         "uglify-es": "^3.3.9",
         "uglify-js": "^3.3.27",
         "uuid": "^3.2.1",
+        "xml2js": "^0.6.2",
         "xmlreader": "^0.2.3",
         "zlib": "^1.0.5"
     },

+ 8 - 0
sql/update.sql

@@ -23,3 +23,11 @@ CREATE TABLE `zh_change_plan_history` (
   `list_json` json DEFAULT NULL COMMENT '清单json值',
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='变更方案内容临时保存表,用于修订撤销与撤回';
+
+ALTER TABLE `zh_ledger_tag`
+ADD COLUMN `settle_id`  int(11) NOT NULL DEFAULT -1 AFTER `sorder`,
+ADD COLUMN `settle_order`  tinyint(4) NOT NULL DEFAULT -1 AFTER `settle_id`;
+
+ALTER TABLE `zh_ledger_attachment`
+ADD COLUMN `settle_id`  int(11) NOT NULL DEFAULT -1 COMMENT '结算id' AFTER `tid`,
+ADD COLUMN `settle_order`  tinyint(4) NOT NULL DEFAULT -1 COMMENT '结算期序号' AFTER `settle_id`;