Przeglądaj źródła

Merge branch 'dev' into uat

MaiXinRong 4 lat temu
rodzic
commit
b944062259
75 zmienionych plików z 6826 dodań i 212 usunięć
  1. 80 0
      app/const/audit.js
  2. 2 0
      app/const/change.js
  3. 3 0
      app/const/code_rule.js
  4. 2 1
      app/const/page_show.js
  5. 2 0
      app/const/project_log.js
  6. 706 6
      app/controller/change_controller.js
  7. 3 0
      app/controller/dashboard_controller.js
  8. 30 1
      app/controller/material_controller.js
  9. 10 0
      app/controller/report_controller.js
  10. 1 0
      app/controller/setting_controller.js
  11. 11 2
      app/controller/tender_controller.js
  12. 19 0
      app/lib/ledger.js
  13. 16 1
      app/lib/rpt_data_analysis.js
  14. 0 1
      app/lib/sum_load.js
  15. 3 1
      app/middleware/change_apply_check.js
  16. 121 0
      app/middleware/change_plan_check.js
  17. 4 1
      app/middleware/tender_check.js
  18. 45 1
      app/public/css/main.css
  19. 1 1
      app/public/js/budget_compare.js
  20. 2 2
      app/public/js/category.js
  21. 12 2
      app/public/js/change.js
  22. 8 0
      app/public/js/change_apply_audit.js
  23. 506 1
      app/public/js/change_apply_information.js
  24. 13 0
      app/public/js/change_information.js
  25. 4 3
      app/public/js/change_information_approval.js
  26. 2 2
      app/public/js/change_information_set.js
  27. 3 3
      app/public/js/change_information_show.js
  28. 322 0
      app/public/js/change_plan.js
  29. 230 0
      app/public/js/change_plan_audit.js
  30. 923 0
      app/public/js/change_plan_information.js
  31. 8 0
      app/public/js/global.js
  32. 42 38
      app/public/js/material_list.js
  33. 39 1
      app/public/js/measure_material.js
  34. 2 2
      app/public/js/shares/sjs_setting.js
  35. 1 0
      app/public/report/js/rpt_change_rela.js
  36. 2 2
      app/public/report/js/rpt_custom.js
  37. 19 0
      app/router.js
  38. 17 1
      app/service/change.js
  39. 73 1
      app/service/change_apply.js
  40. 1 0
      app/service/change_apply_audit.js
  41. 218 0
      app/service/change_apply_list.js
  42. 426 0
      app/service/change_plan.js
  43. 137 0
      app/service/change_plan_att.js
  44. 532 0
      app/service/change_plan_audit.js
  45. 247 0
      app/service/change_plan_list.js
  46. 1 1
      app/service/ledger_history.js
  47. 14 0
      app/service/material.js
  48. 2 1
      app/service/material_list.js
  49. 12 0
      app/service/report.js
  50. 113 2
      app/service/report_memory.js
  51. 1 1
      app/service/rpt_gather_memory.js
  52. 3 1
      app/service/tender.js
  53. 5 2
      app/view/change/apply.ejs
  54. 118 91
      app/view/change/apply_information.ejs
  55. 50 0
      app/view/change/apply_information_modal.ejs
  56. 2 0
      app/view/change/index.ejs
  57. 3 0
      app/view/change/information.ejs
  58. 21 5
      app/view/change/modal.ejs
  59. 125 0
      app/view/change/plan.ejs
  60. 177 0
      app/view/change/plan_information.ejs
  61. 786 0
      app/view/change/plan_information_modal.ejs
  62. 148 0
      app/view/change/plan_modal.ejs
  63. 31 1
      app/view/dashboard/index.ejs
  64. 10 0
      app/view/material/index.ejs
  65. 5 5
      app/view/material/list.ejs
  66. 30 0
      app/view/material/modal.ejs
  67. 1 0
      app/view/report/index.ejs
  68. 14 0
      app/view/setting/fun.ejs
  69. 5 4
      app/view/stage/bwtz.ejs
  70. 29 13
      app/view/tender/detail.ejs
  71. 3 2
      app/view/tender/tender_sub_menu.ejs
  72. 3 2
      app/view/tender/tender_sub_mini_menu.ejs
  73. 137 5
      builder_report_index_define.js
  74. 30 1
      config/web.js
  75. 99 1
      sql/update.sql

+ 80 - 0
app/const/audit.js

@@ -587,6 +587,84 @@ const changeApply = (function() {
     return { status, statusString, statusClass, auditString, auditStringClass, auditProgress, auditProgressClass, filter, statusButton, statusButtonClass };
     return { status, statusString, statusClass, auditString, auditStringClass, auditProgress, auditProgressClass, filter, statusButton, statusButtonClass };
 })();
 })();
 
 
+// 变更方案 审批流程
+const changePlan = (function() {
+    const status = {
+        uncheck: 1, // 待上报
+        checking: 2, // 待审批|审批中
+        checked: 3, // 审批通过
+        checkNo: 4, // 退回到原报人重新上报
+    };
+    const statusString = [];
+    statusString[status.uncheck] = '待上报';
+    statusString[status.checking] = '审批中';
+    statusString[status.checked] = '审批通过';
+    statusString[status.checkNo] = '审批退回';
+
+    const statusClass = [];
+    statusClass[status.uncheck] = '';
+    statusClass[status.checking] = 'text-warning';
+    statusClass[status.checked] = 'text-success';
+    statusClass[status.checkNo] = 'text-warning';
+
+    // 标段概况页
+    // 描述文本
+    const auditString = [];
+    auditString[status.uncheck] = '';
+    auditString[status.checking] = '审批中';
+    auditString[status.checked] = '审批通过';
+    auditString[status.checkNo] = '审批退回';
+    // 文字样式
+    const auditStringClass = [];
+    auditStringClass[status.uncheck] = '';
+    auditStringClass[status.checking] = 'text-warning';
+    auditStringClass[status.checked] = 'text-success';
+    auditStringClass[status.checkNo] = 'text-warning';
+    // 描述文本
+    const auditProgress = [];
+    auditProgress[status.uncheck] = '草稿';
+    auditProgress[status.checking] = '审批中';
+    auditProgress[status.checked] = '审批通过';
+    auditProgress[status.checkNo] = '审批退回';
+    // 样式
+    const auditProgressClass = [];
+    auditProgressClass[status.uncheck] = '';
+    auditProgressClass[status.checking] = 'text-warning';
+    auditProgressClass[status.checked] = 'text-success';
+    auditProgressClass[status.checkNo] = 'text-warning';
+
+    const filter = {
+        status: {
+            pending: 1,
+            uncheck: 5,
+            checking: 2,
+            checked: 3,
+            // checkNo: 4,
+        },
+        statusString: [],
+    };
+    filter.statusString[filter.status.pending] = '待处理';
+    filter.statusString[filter.status.uncheck] = '待上报';
+    filter.statusString[filter.status.checking] = '进行中';
+    filter.statusString[filter.status.checked] = '已通过';
+    // filter.statusString[filter.status.checkNo] = '终止';
+
+    // 按钮
+    const statusButton = [];
+    statusButton[status.uncheck] = '上报';
+    statusButton[status.checking] = '审批';
+    statusButton[status.checked] = '';
+    statusButton[status.checkNo] = '重新上报';
+
+    // 按钮样式
+    const statusButtonClass = [];
+    statusButtonClass[status.uncheck] = 'btn-primary';
+    statusButtonClass[status.checking] = 'btn-success';
+    statusButtonClass[status.checked] = '';
+    statusButtonClass[status.checkNo] = 'btn-warning';
+    return { status, statusString, statusClass, auditString, auditStringClass, auditProgress, auditProgressClass, filter, statusButton, statusButtonClass };
+})();
+
 // 推送类型
 // 推送类型
 const pushType = {
 const pushType = {
     material: 1,
     material: 1,
@@ -597,6 +675,7 @@ const pushType = {
     advance: 6,
     advance: 6,
     changeProject: 7,
     changeProject: 7,
     changeApply: 8,
     changeApply: 8,
+    changePlan: 9,
 };
 };
 
 
 module.exports = {
 module.exports = {
@@ -619,4 +698,5 @@ module.exports = {
     advance,
     advance,
     changeProject,
     changeProject,
     changeApply,
     changeApply,
+    changePlan,
 };
 };

+ 2 - 0
app/const/change.js

@@ -48,6 +48,7 @@ module.exports = {
             value: 5, name: 'E类变更',
             value: 5, name: 'E类变更',
         },
         },
     },
     },
+    className: ['', 'A类变更', 'B类变更', 'C类变更', 'D类变更', 'E类变更'],
     // 变更性质
     // 变更性质
     quality: {
     quality: {
         common: {
         common: {
@@ -60,6 +61,7 @@ module.exports = {
             value: 3, name: '重大设计变更',
             value: 3, name: '重大设计变更',
         },
         },
     },
     },
+    qualityName: ['', '一般设计变更', '较大设计变更', '重大设计变更'],
     // 费用承担方
     // 费用承担方
     charge: {
     charge: {
         partyA: {
         partyA: {

+ 3 - 0
app/const/code_rule.js

@@ -14,6 +14,7 @@ const ruleType = {
     suggestion: 3,
     suggestion: 3,
     will: 4,
     will: 4,
     apply: 5,
     apply: 5,
+    plan: 6,
 };
 };
 const ruleField = [];
 const ruleField = [];
 ruleField[ruleType.measure] = 'm_rule';
 ruleField[ruleType.measure] = 'm_rule';
@@ -21,12 +22,14 @@ ruleField[ruleType.change] = 'c_rule';
 ruleField[ruleType.suggestion] = 'suggestion';
 ruleField[ruleType.suggestion] = 'suggestion';
 ruleField[ruleType.will] = 'will';
 ruleField[ruleType.will] = 'will';
 ruleField[ruleType.apply] = 'apply';
 ruleField[ruleType.apply] = 'apply';
+ruleField[ruleType.plan] = 'plan';
 const ruleString = [];
 const ruleString = [];
 ruleString[ruleType.measure] = 'measure';
 ruleString[ruleType.measure] = 'measure';
 ruleString[ruleType.change] = 'change';
 ruleString[ruleType.change] = 'change';
 ruleString[ruleType.suggestion] = 'suggestion';
 ruleString[ruleType.suggestion] = 'suggestion';
 ruleString[ruleType.will] = 'will';
 ruleString[ruleType.will] = 'will';
 ruleString[ruleType.apply] = 'apply';
 ruleString[ruleType.apply] = 'apply';
+ruleString[ruleType.plan] = 'plan';
 
 
 
 
 // 中间计量编号规则
 // 中间计量编号规则

+ 2 - 1
app/const/page_show.js

@@ -42,8 +42,9 @@ const defaultSetting = {
     close1stStageCheckDealParam: 0,
     close1stStageCheckDealParam: 0,
     openChangeProject: 0,
     openChangeProject: 0,
     openChangeApply: 0,
     openChangeApply: 0,
+    openChangePlan: 0,
     isPreset: 0,
     isPreset: 0,
-    isOnlyChecked: 1
+    isOnlyChecked: 1,
 };
 };
 
 
 
 

+ 2 - 0
app/const/project_log.js

@@ -16,6 +16,7 @@ const type = {
     material: 4,
     material: 4,
     changeProject: 5,
     changeProject: 5,
     changeApply: 6,
     changeApply: 6,
+    changePlan: 7,
 };
 };
 
 
 const type_list = [
 const type_list = [
@@ -26,6 +27,7 @@ const type_list = [
     { code: 'material', type: type.material, name: '材料调差' },
     { code: 'material', type: type.material, name: '材料调差' },
     { code: 'changeProject', type: type.changeProject, name: '变更立项' },
     { code: 'changeProject', type: type.changeProject, name: '变更立项' },
     { code: 'changeApply', type: type.changeApply, name: '变更申请' },
     { code: 'changeApply', type: type.changeApply, name: '变更申请' },
+    { code: 'changePlan', type: type.changePlan, name: '变更方案' },
 ];
 ];
 // 操作状态
 // 操作状态
 const status = {
 const status = {

+ 706 - 6
app/controller/change_controller.js

@@ -138,6 +138,15 @@ module.exports = app => {
                 }
                 }
             }
             }
 
 
+            const changePlanList = await ctx.service.changePlan.getAllDataByCondition({ where: { tid: tender.id, status: audit.changePlan.status.checked } });
+            const allPlanCodes = await ctx.service.change.getAllDataByCondition({
+                columns: ['plan_code'],
+                where: {
+                    tid: tender.id,
+                },
+            });
+            const apLists = allPlanCodes.length > 0 ? ctx.app._.uniq(ctx.app._.map(allPlanCodes, 'plan_code')) : [];
+
             const renderData = {
             const renderData = {
                 uid: ctx.session.sessionUser.accountId,
                 uid: ctx.session.sessionUser.accountId,
                 moment,
                 moment,
@@ -158,6 +167,8 @@ module.exports = app => {
                 tenderMenu: this.menu.tenderMenu,
                 tenderMenu: this.menu.tenderMenu,
                 preUrl: '/tender/' + tenderId,
                 preUrl: '/tender/' + tenderId,
                 tpUnit: ctx.tender.info.decimal.tp,
                 tpUnit: ctx.tender.info.decimal.tp,
+                changePlanList,
+                apLists,
             };
             };
             await this.layout('change/index.ejs', renderData, 'change/modal.ejs');
             await this.layout('change/index.ejs', renderData, 'change/modal.ejs');
         }
         }
@@ -259,11 +270,11 @@ module.exports = app => {
                     throw '当前未打开标段';
                     throw '当前未打开标段';
                 }
                 }
                 const data = JSON.parse(ctx.request.body.data);
                 const data = JSON.parse(ctx.request.body.data);
-                if (!data.code || data.code === '' || !data.name || data.name === '') {
+                if (!data.code || data.code === '') {
                     throw '变更令号不能为空';
                     throw '变更令号不能为空';
                 }
                 }
 
 
-                const change = await ctx.service.change.add(tenderId, ctx.session.sessionUser.accountId, data.code, data.name);
+                const change = await ctx.service.change.add(tenderId, ctx.session.sessionUser.accountId, data.code, data.plan_code, data.name);
 
 
                 ctx.body = { err: 0, msg: '', data: change };
                 ctx.body = { err: 0, msg: '', data: change };
             } catch (err) {
             } catch (err) {
@@ -829,6 +840,9 @@ module.exports = app => {
                         // 取所有工料表
                         // 取所有工料表
                         responseData.data = changeList;
                         responseData.data = changeList;
                         break;
                         break;
+                    case 'update_tp':
+                        await ctx.service.change.saveInfo({ total_price: data.updateData });
+                        break;
                     default: throw '参数有误';
                     default: throw '参数有误';
                 }
                 }
 
 
@@ -2001,7 +2015,7 @@ module.exports = app => {
                 }
                 }
             }
             }
             // 获取审批流程中左边列表
             // 获取审批流程中左边列表
-            ctx.change.auditors2 = ctx.change.status === auditConst.status.back && ctx.change.user_id !== ctx.session.sessionUser.accountId ?
+            ctx.change.auditors2 = ctx.change.status === auditConst.status.back && ctx.change.uid !== ctx.session.sessionUser.accountId ?
                 await ctx.service.changeProjectAudit.getAuditorsWithOwner(ctx.change.id, times) :
                 await ctx.service.changeProjectAudit.getAuditorsWithOwner(ctx.change.id, times) :
                 await ctx.service.changeProjectAudit.getAuditorsWithOwner(ctx.change.id, ctx.change.times);
                 await ctx.service.changeProjectAudit.getAuditorsWithOwner(ctx.change.id, ctx.change.times);
             if (ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.back) {
             if (ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.back) {
@@ -2415,8 +2429,11 @@ module.exports = app => {
             const orders = ctx.query.order ? ctx.query.order : 0;
             const orders = ctx.query.order ? ctx.query.order : 0;
             const changes = await ctx.service.changeApply.getListByStatus(tender.id, status, 1, sorts, orders);
             const changes = await ctx.service.changeApply.getListByStatus(tender.id, status, 1, sorts, orders);
             const total = await ctx.service.changeApply.getCountByStatus(tender.id, status);
             const total = await ctx.service.changeApply.getCountByStatus(tender.id, status);
+            let page_total = 0;
+            const tp = await ctx.service.changeApply.getTp(tender.id, status);
             for (const c of changes) {
             for (const c of changes) {
                 c.curAuditor = await ctx.service.changeApplyAudit.getAuditorByStatus(c.id, c.status, c.times);
                 c.curAuditor = await ctx.service.changeApplyAudit.getAuditorByStatus(c.id, c.status, c.times);
+                page_total = ctx.helper.add(page_total, c.total_price);
             }
             }
             const tender_userInfo = await ctx.service.projectAccount.getDataById(ctx.tender.data.user_id);
             const tender_userInfo = await ctx.service.projectAccount.getDataById(ctx.tender.data.user_id);
             // 分页相关
             // 分页相关
@@ -2491,6 +2508,8 @@ module.exports = app => {
                 changeConst,
                 changeConst,
                 changeProjectList,
                 changeProjectList,
                 pcLists,
                 pcLists,
+                tp,
+                page_total,
                 tenderMenu: this.menu.tenderMenu,
                 tenderMenu: this.menu.tenderMenu,
                 preUrl: '/tender/' + tender.id,
                 preUrl: '/tender/' + tender.id,
                 jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.change.apply),
                 jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.change.apply),
@@ -2589,7 +2608,7 @@ module.exports = app => {
                 }
                 }
             }
             }
             // 获取审批流程中左边列表
             // 获取审批流程中左边列表
-            ctx.change.auditors2 = ctx.change.status === auditConst.status.checkNo && ctx.change.user_id !== ctx.session.sessionUser.accountId ?
+            ctx.change.auditors2 = ctx.change.status === auditConst.status.checkNo && ctx.change.uid !== ctx.session.sessionUser.accountId ?
                 await ctx.service.changeApplyAudit.getAuditorsWithOwner(ctx.change.id, times) :
                 await ctx.service.changeApplyAudit.getAuditorsWithOwner(ctx.change.id, times) :
                 await ctx.service.changeApplyAudit.getAuditorsWithOwner(ctx.change.id, ctx.change.times);
                 await ctx.service.changeApplyAudit.getAuditorsWithOwner(ctx.change.id, ctx.change.times);
             if (ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.checkNo) {
             if (ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.checkNo) {
@@ -2601,17 +2620,25 @@ module.exports = app => {
             try {
             try {
                 const whiteList = this.ctx.app.config.multipart.whitelist;
                 const whiteList = this.ctx.app.config.multipart.whitelist;
                 const tender = await ctx.service.tender.getDataById(ctx.tender.id);
                 const tender = await ctx.service.tender.getDataById(ctx.tender.id);
+                await this._getChangeApplyAuditViewData(ctx);
+                // 获取清单列表
+                const changeList = await ctx.service.changeApplyList.getList(ctx.change.id);
                 // 获取附件列表
                 // 获取附件列表
                 const fileList = await ctx.service.changeApplyAtt.getAllChangeApplyAtt(ctx.tender.id, ctx.change.id);
                 const fileList = await ctx.service.changeApplyAtt.getAllChangeApplyAtt(ctx.tender.id, ctx.change.id);
-                await this._getChangeApplyAuditViewData(ctx);
                 const renderData = {
                 const renderData = {
                     tender,
                     tender,
                     change: ctx.change,
                     change: ctx.change,
+                    listRule: tender.c_apply_list_rule ? JSON.parse(tender.c_apply_list_rule) : { source: 1, rule: ['unit', 'unit_price'] },
+                    changeList,
                     changeConst,
                     changeConst,
                     auditConst: audit.changeApply,
                     auditConst: audit.changeApply,
                     fileList,
                     fileList,
                     whiteList,
                     whiteList,
-                    returnUrl: this.app._.includes(ctx.request.headers.referer, '/tender/' + ctx.tender.id + '/change/apply') ? ctx.request.headers.referer : null,
+                    tpUnit: ctx.change.decimal ? ctx.change.decimal.tp : ctx.tender.info.decimal.tp,
+                    upUnit: ctx.change.decimal ? ctx.change.decimal.up : ctx.tender.info.decimal.up,
+                    changeUnits: changeConst.units,
+                    precision: ctx.change.decimal && ctx.change.decimal.precision ? ctx.change.decimal.precision : ctx.tender.info.precision,
+                    returnUrl: this.app._.includes(ctx.request.headers.referer, '/tender/' + ctx.tender.id + '/change/apply') && !this.app._.includes(ctx.request.headers.referer, '/tender/' + ctx.tender.id + '/change/apply/' + ctx.change.id + '/information') ? ctx.request.headers.referer : null,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.change.apply_information),
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.change.apply_information),
                     preUrl: '/tender/' + ctx.tender.id + '/change/apply/' + ctx.change.id + '/information',
                     preUrl: '/tender/' + ctx.tender.id + '/change/apply/' + ctx.change.id + '/information',
                 };
                 };
@@ -2943,6 +2970,679 @@ module.exports = app => {
                 ctx.redirect(ctx.request.header.referer);
                 ctx.redirect(ctx.request.header.referer);
             }
             }
         }
         }
+
+        /**
+         * 变更清单 - 操作 (Ajax)
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async saveApplyListsData(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = {
+                    err: 0,
+                    msg: '',
+                    data: {},
+                };
+                switch (data.type) {
+                    case 'add':
+                        responseData.data = await ctx.service.changeApplyList.add(data.updateData);
+                        break;
+                    case 'batchadd':
+                        responseData.data = await ctx.service.changeApplyList.batchAdd(data);
+                        break;
+                    case 'del':
+                        await ctx.service.changeApplyList.del(data.ids);
+                        // 取所有工料表
+                        responseData.data = await ctx.service.changeApplyList.getList(ctx.change.id);
+                        break;
+                    case 'update':
+                        // if (data.updateData.code === '' || data.updateData.code === null) {
+                        //     throw '请先输入编号';
+                        // }
+                        await ctx.service.changeApplyList.save(data.updateData);
+                        break;
+                    case 'paste':
+                        await ctx.service.changeApplyList.saveDatas(data.updateData);
+                        // 取所有工料表
+                        responseData.data = await ctx.service.changeApplyList.getList(ctx.change.id);
+                        break;
+                    case 'list_rule':
+                        const result = await ctx.service.tender.saveTenderData(ctx.tender.id, { c_apply_list_rule: data.postData });
+                        if (!result) {
+                            throw '修改失败';
+                        }
+                        break;
+                    // case 'update_tp':
+                    //     await ctx.service.changeApply.saveInfo(ctx.change.id, { name: 'total_price', val: data.updateData });
+                    //     break;
+                    default: throw '参数有误';
+                }
+
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        // 变更方案
+        async _filterChangesPlan(ctx, status = 0) {
+            const tenderId = ctx.params.id;
+            ctx.session.sessionUser.tenderId = tenderId;
+            const tender = await this.service.tender.getDataById(ctx.tender.id);
+            // const tender = ctx.tender;
+            // const tenderList = await this.service.tender.getList();
+
+            const page = ctx.page;
+            const sorts = ctx.query.sort ? ctx.query.sort : 0;
+            const orders = ctx.query.order ? ctx.query.order : 0;
+            const changes = await ctx.service.changePlan.getListByStatus(tender.id, status, 1, sorts, orders);
+            const total = await ctx.service.changePlan.getCountByStatus(tender.id, status);
+            let page_total = 0;
+            const tp = await ctx.service.changePlan.getTp(tender.id, status);
+            for (const c of changes) {
+                c.curAuditor = await ctx.service.changePlanAudit.getAuditorByStatus(c.id, c.status, c.times);
+                page_total = ctx.helper.add(page_total, c.total_price);
+            }
+            const tender_userInfo = await ctx.service.projectAccount.getDataById(ctx.tender.data.user_id);
+            // 分页相关
+            const pageInfo = {
+                page,
+                total: Math.ceil(total / app.config.pageSize),
+                queryData: JSON.stringify(ctx.urlInfo.query),
+            };
+
+            const filter = JSON.parse(JSON.stringify(audit.changePlan.filter));
+            filter.count = [];
+            filter.count[filter.status.pending] = await ctx.service.changePlan.getCountByStatus(tender.id, filter.status.pending);// await ctx.service.change.pendingDatas(tender.id, ctx.session.sessionUser.accountId);
+            filter.count[filter.status.uncheck] = await ctx.service.changePlan.getCountByStatus(tender.id, filter.status.uncheck);// await ctx.service.change.checkingDatas(tender.id, ctx.session.sessionUser.accountId);
+            filter.count[filter.status.checking] = await ctx.service.changePlan.getCountByStatus(tender.id, filter.status.checking);// await ctx.service.change.checkedDatas(tender.id, ctx.session.sessionUser.accountId);
+            filter.count[filter.status.checked] = await ctx.service.changePlan.getCountByStatus(tender.id, filter.status.checked);// await ctx.service.change.pendingDatas(tender.id, ctx.session.sessionUser.accountId);
+            // filter.count[filter.status.checkNo] = await ctx.service.changeApply.getCountByStatus(tender.id, filter.status.checkNo);// await ctx.service.change.pendingDatas(tender.id, ctx.session.sessionUser.accountId);
+            let codeRule = [];
+            let c_connector = '1';
+            let c_rule_first = 1;
+            const rule_type = 'plan';
+            if (tender.c_code_rules) {
+                const c_code_rules = JSON.parse(tender.c_code_rules);
+                codeRule = c_code_rules[rule_type + '_rule'] !== undefined ? c_code_rules[rule_type + '_rule'] : [];
+                c_connector = c_code_rules[rule_type + '_connector'] !== undefined ? c_code_rules[rule_type + '_connector'] : '1';
+                c_rule_first = c_code_rules[rule_type + '_rule_first'] !== undefined ? c_code_rules[rule_type + '_rule_first'] : 1;
+            }
+            for (const rule of codeRule) {
+                switch (rule.rule_type) {
+                    case codeRuleConst.measure.ruleType.dealCode:
+                        rule.preview = ctx.tender.info.deal_info.dealCode;
+                        break;
+                    case codeRuleConst.measure.ruleType.tenderName:
+                        rule.preview = tender.name;
+                        break;
+                    case codeRuleConst.measure.ruleType.inDate:
+                        rule.preview = moment().format('YYYY');
+                        break;
+                    case codeRuleConst.measure.ruleType.text:
+                        rule.preview = rule.text;
+                        break;
+                    case codeRuleConst.measure.ruleType.addNo:
+                        const s = '0000000000';
+                        rule.preview = s.substr(s.length - rule.format);
+                        break;
+                    default: break;
+                }
+            }
+            const changeApplyList = await ctx.service.changeApply.getAllDataByCondition({ where: { tid: tender.id, status: audit.changeApply.status.checked } });
+            const allApplyCodes = await ctx.service.changePlan.getAllDataByCondition({
+                columns: ['apply_code'],
+                where: {
+                    tid: tender.id,
+                },
+            });
+            const acLists = allApplyCodes.length > 0 ? ctx.app._.uniq(ctx.app._.map(allApplyCodes, 'apply_code')) : [];
+            const renderData = {
+                uid: ctx.session.sessionUser.accountId,
+                tender,
+                pageInfo,
+                changes,
+                status,
+                rule_type,
+                codeRule,
+                c_connector,
+                c_rule_first,
+                filter,
+                plan_username: tender_userInfo ? tender_userInfo.name : null,
+                ruleType: codeRuleConst.ruleType[rule_type],
+                dealCode: ctx.tender.info.deal_info.dealCode,
+                auditConst: audit.changePlan,
+                ruleConst: codeRuleConst.measure,
+                changeConst,
+                changeApplyList,
+                acLists,
+                page_total,
+                tp,
+                tenderMenu: this.menu.tenderMenu,
+                preUrl: '/tender/' + tender.id,
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.change.plan),
+            };
+            await this.layout('change/plan.ejs', renderData, 'change/plan_modal.ejs');
+        }
+
+        /**
+         * 变更申请列表 页面 (Get)
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async plan(ctx) {
+            try {
+                await this._filterChangesPlan(ctx);
+            } catch (err) {
+                this.log(err);
+                ctx.redirect('/dashboard');
+            }
+        }
+
+        /**
+         * 变更管理 状态筛选 页面 (Get)
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async planStatus(ctx) {
+            try {
+                const status = parseInt(ctx.params.status);
+                await this._filterChangesPlan(ctx, status);
+            } catch (err) {
+                this.logger.error(err);
+                ctx.redirect('/tender/' + ctx.params.id + '/change/plan');
+            }
+        }
+
+        /**
+         * 新增变更立项 (Post)
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async planAdd(ctx) {
+            try {
+                const tenderId = ctx.params.id;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.code || data.code === '') {
+                    throw '变更方案编号不能为空';
+                }
+
+                const change = await ctx.service.changePlan.add(tenderId, ctx.session.sessionUser.accountId, data.code, data.apply_code, data.name);
+
+                ctx.body = { err: 0, msg: '', data: change };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString() };
+            }
+        }
+
+        /**
+         * 删除变更立项
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async planDelete(ctx) {
+            try {
+                const result = await ctx.service.changePlan.delete(ctx.request.body.cpid);
+                if (!result) {
+                    throw '删除变更方案失败';
+                }
+                ctx.redirect(ctx.request.header.referer);
+            } catch (err) {
+                console.log(err);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 获取审批界面所需的 原报、审批人数据等
+         * @param ctx
+         * @return {Promise<void>}
+         * @private
+         */
+        async _getChangePlanAuditViewData(ctx) {
+            const auditConst = audit.changePlan;
+            const times = ctx.change.status === auditConst.status.checkNo ? ctx.change.times - 1 : ctx.change.times;
+            ctx.change.user = await ctx.service.projectAccount.getAccountInfoById(ctx.change.uid);
+            ctx.change.auditHistory = [];
+            if (times >= 1) {
+                for (let i = 1; i <= times; i++) {
+                    ctx.change.auditHistory.push(await ctx.service.changePlanAudit.getAuditors(ctx.change.id, i));
+                }
+            }
+            // 获取审批流程中左边列表
+            ctx.change.auditors2 = ctx.change.status === auditConst.status.checkNo && ctx.change.uid !== ctx.session.sessionUser.accountId ?
+                await ctx.service.changePlanAudit.getAuditorsWithOwner(ctx.change.id, times) :
+                await ctx.service.changePlanAudit.getAuditorsWithOwner(ctx.change.id, ctx.change.times);
+            if (ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.checkNo) {
+                ctx.change.auditorList = await ctx.service.changePlanAudit.getAuditors(ctx.change.id, ctx.change.times);
+            }
+        }
+
+        async planInformation(ctx) {
+            try {
+                const whiteList = this.ctx.app.config.multipart.whitelist;
+                const tender = await ctx.service.tender.getDataById(ctx.tender.id);
+                await this._getChangePlanAuditViewData(ctx);
+                // 获取附件列表
+                const fileList = await ctx.service.changePlanAtt.getAllChangePlanAtt(ctx.tender.id, ctx.change.id);
+                // 获取清单列表
+                const changeList = await ctx.service.changePlanList.getList(ctx.change.id);
+                if (ctx.change.status === audit.changePlan.status.checking || ctx.change.status === audit.changePlan.status.checked) {
+                    const listAudits = await ctx.service.changePlanAudit.getAuditGroupByList(ctx.change.id, ctx.change.times);
+                    ctx.change.listAudits = listAudits;
+                    for (const cl of changeList) {
+                        const audit_amount = cl.audit_amount !== null && cl.audit_amount !== '' ? cl.audit_amount.split(',') : '';
+                        // 清单表页赋值
+                        for (const [index, au] of listAudits.entries()) {
+                            cl['audit_amount_' + au.aid] = ctx.change.shenpiPower && au.aid === ctx.session.sessionUser.accountId ? cl.spamount : (audit_amount[index] ? parseFloat(audit_amount[index]) : null);
+                        }
+                    }
+                }
+                const renderData = {
+                    tender,
+                    change: ctx.change,
+                    listRule: tender.c_plan_list_rule ? JSON.parse(tender.c_plan_list_rule) : { source: 1, rule: ['unit', 'unit_price'] },
+                    changeList,
+                    changeConst,
+                    auditConst: audit.changePlan,
+                    fileList,
+                    whiteList,
+                    tpUnit: ctx.change.decimal ? ctx.change.decimal.tp : ctx.tender.info.decimal.tp,
+                    upUnit: ctx.change.decimal ? ctx.change.decimal.up : ctx.tender.info.decimal.up,
+                    changeUnits: changeConst.units,
+                    precision: ctx.change.decimal && ctx.change.decimal.precision ? ctx.change.decimal.precision : ctx.tender.info.precision,
+                    returnUrl: this.app._.includes(ctx.request.headers.referer, '/tender/' + ctx.tender.id + '/change/plan') && !this.app._.includes(ctx.request.headers.referer, '/tender/' + ctx.tender.id + '/change/plan/' + ctx.change.id + '/information') ? ctx.request.headers.referer : null,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.change.plan_information),
+                    preUrl: '/tender/' + ctx.tender.id + '/change/plan/' + ctx.change.id + '/information',
+                };
+                if ((ctx.change.status === audit.changePlan.status.uncheck || ctx.change.status === audit.changePlan.status.checkNo) && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.tender.isTourist)) {
+                    // data.accountGroup = accountGroup;
+                    // 获取所有项目参与者
+                    const accountList = await ctx.service.projectAccount.getAllDataByCondition({
+                        where: { project_id: ctx.session.sessionProject.id, enable: 1 },
+                        columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile'],
+                    });
+                    renderData.accountList = accountList;
+                    renderData.accountGroup = accountGroup.map((item, idx) => {
+                        const groupList = accountList.filter(item => item.account_group === idx);
+                        return { groupName: item, groupList };
+                    });
+                }
+                await this.layout('change/plan_information.ejs', renderData, 'change/plan_information_modal.ejs');
+            } catch (err) {
+                this.log(err);
+                ctx.redirect('/tender/' + ctx.params.id + '/change');
+            }
+        }
+
+
+        // 审批相关
+        /**
+         * 添加审批人
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async addPlanAudit(ctx) {
+            try {
+                const auditConst = audit.changePlan;
+                const data = JSON.parse(ctx.request.body.data);
+                const id = this.app._.toInteger(data.auditorId);
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+                // 检查权限等
+                if (ctx.change.uid !== ctx.session.sessionUser.accountId) {
+                    throw '您无权添加审核人';
+                }
+                if (ctx.change.status === auditConst.status.checking || ctx.change.status === auditConst.status.checked) {
+                    throw '当前不允许添加审核人';
+                }
+
+                ctx.change.auditorList = await ctx.service.changePlanAudit.getAuditors(ctx.change.id, ctx.change.times);
+                // 检查审核人是否已存在
+                const exist = this.app._.find(ctx.change.auditorList, { aid: id });
+                if (exist) {
+                    throw '该审核人已存在,请勿重复添加';
+                }
+                // const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition({ tid: ctx.tender.id, sp_type: shenpiConst.sp_type.material, sp_status: shenpiConst.sp_status.gdzs });
+                // const is_gdzs = shenpiInfo && ctx.tender.info.shenpi.material === shenpiConst.sp_status.gdzs ? 1 : 0;
+                const result = await ctx.service.changePlanAudit.addAuditor(ctx.change.id, id, ctx.change.times);
+                if (!result) {
+                    throw '添加审核人失败';
+                }
+
+                const auditors = await ctx.service.changePlanAudit.getAuditorsWithOwner(ctx.change.id, ctx.change.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 deletePlanAudit(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.changePlanAudit.deleteAuditor(ctx.change.id, id, ctx.change.times);
+                if (!result) {
+                    throw '移除审核人失败';
+                }
+
+                const auditors = await ctx.service.changePlanAudit.getAuditors(ctx.change.id, ctx.change.times);
+                ctx.body = { err: 0, msg: '', data: auditors };
+            } catch (err) {
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
+         * 上传附件
+         * @param {*} ctx 上下文
+         */
+        async uploadPlanFile(ctx) {
+            let stream;
+            try {
+                const auditConst = audit.changePlan;
+                // this._checkAdvanceFileCanModify(ctx);
+                const parts = this.ctx.multipart({
+                    autoFields: true,
+                });
+                const files = [];
+                const create_time = Date.parse(new Date()) / 1000;
+                let idx = 0;
+                const extra_upload = ctx.change.status === auditConst.status.checked;
+                while ((stream = await parts()) !== undefined) {
+                    if (!stream.filename) {
+                        // 如果没有传入直接返回
+                        return;
+                    }
+                    const fileInfo = path.parse(stream.filename);
+                    const filepath = `app/public/upload/${this.ctx.tender.id.toString()}/change_plan/fujian_${create_time + idx.toString() + fileInfo.ext}`;
+                    // await ctx.helper.saveStreamFile(stream, path.resolve(this.app.baseDir, 'app', filepath));
+                    await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + filepath, stream);
+                    files.push({ filepath, name: stream.filename, ext: fileInfo.ext });
+                    ++idx;
+                    stream && (await sendToWormhole(stream));
+                }
+                const in_time = new Date();
+                const payload = files.map(file => {
+                    let idx;
+                    if (Array.isArray(parts.field.name)) {
+                        idx = parts.field.name.findIndex(name => name === file.name);
+                    } else {
+                        idx = 'isString';
+                    }
+                    const newFile = {
+                        tid: ctx.tender.id,
+                        cpid: ctx.change.id,
+                        uid: ctx.session.sessionUser.accountId,
+                        filename: file.name,
+                        fileext: file.ext,
+                        filesize: ctx.helper.bytesToSize(idx === 'isString' ? parts.field.size : parts.field.size[idx]),
+                        filepath: file.filepath,
+                        upload_time: in_time,
+                        extra_upload,
+                    };
+                    return newFile;
+                });
+                // 执行文件信息写入数据库
+                await ctx.service.changePlanAtt.saveFileMsgToDb(payload);
+                // 将最新的当前标段的所有文件信息返回
+                const data = await ctx.service.changePlanAtt.getAllChangePlanAtt(ctx.tender.id, ctx.change.id);
+                ctx.body = { err: 0, msg: '', data };
+            } catch (err) {
+                stream && (await sendToWormhole(stream));
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
+         * 删除附件
+         * @param {Ojbect} ctx 上下文
+         */
+        async deletePlanFile(ctx) {
+            try {
+                const { id } = JSON.parse(ctx.request.body.data);
+                const fileInfo = await ctx.service.changePlanAtt.getDataById(id);
+                if (fileInfo || Object.keys(fileInfo).length) {
+                    // 先删除文件
+                    // await fs.unlinkSync(path.resolve(this.app.baseDir, './app', fileInfo.filepath));
+                    await ctx.app.fujianOss.delete(ctx.app.config.fujianOssFolder + fileInfo.filepath);
+                    // 再删除数据库
+                    await ctx.service.changePlanAtt.delete(id);
+                } else {
+                    throw '不存在该文件';
+                }
+                const data = await ctx.service.changePlanAtt.getAllChangePlanAtt(ctx.tender.id, ctx.change.id);
+                ctx.body = { err: 0, msg: '请求成功', data };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
+         * 下载附件
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async downloadPlanFile(ctx) {
+            const id = ctx.params.fid;
+            if (id) {
+                try {
+                    const fileInfo = await ctx.service.changePlanAtt.getDataById(id);
+                    if (fileInfo !== undefined && fileInfo !== '') {
+                        // const fileName = path.join(__dirname, '../', fileInfo.filepath);
+                        // 解决中文无法下载问题
+                        const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
+                        let disposition = '';
+                        if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
+                            disposition = 'attachment; filename=' + encodeURIComponent(fileInfo.filename);
+                        } else if (userAgent.indexOf('firefox') >= 0) {
+                            disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(fileInfo.filename) + '"';
+                        } else {
+                            /* safari等其他非主流浏览器只能自求多福了 */
+                            disposition = 'attachment; filename=' + new Buffer(fileInfo.filename).toString('binary');
+                        }
+                        ctx.response.set({
+                            'Content-Type': 'application/octet-stream',
+                            'Content-Disposition': disposition,
+                            'Content-Length': fileInfo.filesize,
+                        });
+                        // ctx.body = await fs.createReadStream(fileName);
+                        ctx.body = await ctx.helper.ossFileGet(fileInfo.filepath);
+                    } else {
+                        throw '不存在该文件';
+                    }
+                } catch (err) {
+                    this.log(err);
+                    this.setMessage(err.toString(), this.messageType.ERROR);
+                }
+            }
+        }
+
+        async planInformationSave(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (data.name === 'code') {
+                    const info = await ctx.service.changePlan.isRepeat(ctx.change.id, data.val, ctx.tender.id);
+                    if (info) {
+                        throw '该编号已存在';
+                    }
+                }
+                const result = await ctx.service.changePlan.saveInfo(ctx.change.id, data);
+                if (!result) {
+                    throw '修改失败';
+                }
+                ctx.body = { err: 0, msg: '请求成功', data: null };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
+         * 上报和重新上报
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async startPlanAudit(ctx) {
+            try {
+                const auditConst = audit.changePlan;
+                // 检查权限等
+                if (!ctx.change) {
+                    throw '数据错误';
+                }
+                if (ctx.change.uid !== ctx.session.sessionUser.accountId) {
+                    throw '您无权上报该期数据';
+                }
+                if (ctx.change.status === auditConst.status.checking || ctx.change.status === auditConst.status.checked) {
+                    throw '该材料调差期数据当前无法上报';
+                }
+
+                await ctx.service.changePlanAudit.start(ctx.change.id, ctx.change.times);
+
+                ctx.redirect(ctx.request.header.referer);
+            } catch (err) {
+                this.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 审批
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async checkPlanAudit(ctx) {
+            try {
+                const auditConst = audit.changePlan;
+                if (!ctx.change || ctx.change.status !== auditConst.status.checking) {
+                    throw '当前材料调差期数据有误';
+                }
+                if (!ctx.change.curAuditor || ctx.change.curAuditor.aid !== ctx.session.sessionUser.accountId) {
+                    throw '您无权进行该操作';
+                }
+                const data = {
+                    checkType: parseInt(ctx.request.body.checkType),
+                    opinion: ctx.request.body.opinion,
+                };
+                if (!data.checkType || isNaN(data.checkType)) {
+                    throw '提交数据错误';
+                }
+                // if (data.checkType === auditConst.status.checkNo) {
+                //     if (!data.checkType || isNaN(data.checkType)) {
+                //         throw '提交数据错误';
+                //     }
+                // }
+                await ctx.service.changePlanAudit.check(ctx.change.id, data, ctx.change.times);
+
+                ctx.redirect(ctx.request.header.referer);
+            } catch (err) {
+                this.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 变更清单 - 操作 (Ajax)
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async savePlanListsData(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = {
+                    err: 0,
+                    msg: '',
+                    data: {},
+                };
+                switch (data.type) {
+                    case 'add':
+                        responseData.data = await ctx.service.changePlanList.add(data.updateData);
+                        break;
+                    case 'batchadd':
+                        responseData.data = await ctx.service.changePlanList.batchAdd(data);
+                        break;
+                    case 'del':
+                        await ctx.service.changePlanList.del(data.ids);
+                        // 取所有工料表
+                        responseData.data = await ctx.service.changePlanList.getList(ctx.change.id);
+                        break;
+                    case 'update':
+                        // if (data.updateData.code === '' || data.updateData.code === null) {
+                        //     throw '请先输入编号';
+                        // }
+                        await ctx.service.changePlanList.save(data.updateData);
+                        break;
+                    case 'paste':
+                        await ctx.service.changePlanList.saveDatas(data.updateData);
+                        // 取所有工料表
+                        responseData.data = await ctx.service.changePlanList.getList(ctx.change.id);
+                        if (ctx.change.status === audit.changePlan.status.checking || ctx.change.status === audit.changePlan.status.checked) {
+                            const listAudits = await ctx.service.changePlanAudit.getAuditGroupByList(ctx.change.id, ctx.change.times);
+                            for (const cl of responseData.data) {
+                                const audit_amount = cl.audit_amount !== null && cl.audit_amount !== '' ? cl.audit_amount.split(',') : '';
+                                // 清单表页赋值
+                                for (const [index, au] of listAudits.entries()) {
+                                    cl['audit_amount_' + au.aid] = ctx.change.shenpiPower && au.aid === ctx.session.sessionUser.accountId ? cl.spamount : (audit_amount[index] ? parseFloat(audit_amount[index]) : null);
+                                }
+                            }
+                        }
+                        break;
+                    case 'list_rule':
+                        const result = await ctx.service.tender.saveTenderData(ctx.tender.id, { c_plan_list_rule: data.postData });
+                        if (!result) {
+                            throw '修改失败';
+                        }
+                        break;
+                    case 'paste_amount_rows':
+                        await ctx.service.changePlanList.saveDatas(data.updateData);
+                        const changeList = await ctx.service.changePlanList.getList(ctx.change.id);
+                        const listAudits = await ctx.service.changePlanAudit.getAuditGroupByList(ctx.change.id, ctx.change.times);
+                        for (const cl of changeList) {
+                            // 清单表页赋值
+                            const audit_amount = cl.audit_amount !== null && cl.audit_amount !== '' ? cl.audit_amount.split(',') : '';
+                            // 清单表页赋值
+                            for (const [index, au] of listAudits.entries()) {
+                                cl['audit_amount_' + au.aid] = ctx.change.shenpiPower && au.aid === ctx.session.sessionUser.accountId ? cl.spamount : (audit_amount[index] ? parseFloat(audit_amount[index]) : null);
+                            }
+                        }
+                        // 取所有工料表
+                        responseData.data = changeList;
+                        break;
+                    // case 'update_tp':
+                    //     await ctx.service.changePlan.saveInfo(ctx.change.id, { name: 'total_price', val: data.updateData });
+                    //     break;
+                    default: throw '参数有误';
+                }
+
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
     }
     }
 
 
     return ChangeController;
     return ChangeController;

+ 3 - 0
app/controller/dashboard_controller.js

@@ -31,6 +31,7 @@ module.exports = app => {
             const auditAdvance = await ctx.service.advanceAudit.getAuditAdvance(ctx.session.sessionUser.accountId);
             const auditAdvance = await ctx.service.advanceAudit.getAuditAdvance(ctx.session.sessionUser.accountId);
             const auditChangeProject = ctx.session.sessionProject.page_show.openChangeProject ? await ctx.service.changeProjectAudit.getAuditChangeProject(ctx.session.sessionUser.accountId) : [];
             const auditChangeProject = ctx.session.sessionProject.page_show.openChangeProject ? await ctx.service.changeProjectAudit.getAuditChangeProject(ctx.session.sessionUser.accountId) : [];
             const auditChangeApply = ctx.session.sessionProject.page_show.openChangeApply ? await ctx.service.changeApplyAudit.getAuditChangeApply(ctx.session.sessionUser.accountId) : [];
             const auditChangeApply = ctx.session.sessionProject.page_show.openChangeApply ? await ctx.service.changeApplyAudit.getAuditChangeApply(ctx.session.sessionUser.accountId) : [];
+            const auditChangePlan = ctx.session.sessionProject.page_show.openChangePlan ? await ctx.service.changePlanAudit.getAuditChangePlan(ctx.session.sessionUser.accountId) : [];
             const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
             const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
             const noticeList = await ctx.service.noticePush.getNotice(ctx.session.sessionProject.id, pa.id);
             const noticeList = await ctx.service.noticePush.getNotice(ctx.session.sessionProject.id, pa.id);
             const projectData = await ctx.service.project.getDataById(ctx.session.sessionProject.id);
             const projectData = await ctx.service.project.getDataById(ctx.session.sessionProject.id);
@@ -56,6 +57,7 @@ module.exports = app => {
                 auditAdvance,
                 auditAdvance,
                 auditChangeProject,
                 auditChangeProject,
                 auditChangeApply,
                 auditChangeApply,
+                auditChangePlan,
                 role: pa.role,
                 role: pa.role,
                 authMobile: pa.auth_mobile,
                 authMobile: pa.auth_mobile,
                 acLedger: auditConst.ledger,
                 acLedger: auditConst.ledger,
@@ -66,6 +68,7 @@ module.exports = app => {
                 acAdvance: auditConst.advance,
                 acAdvance: auditConst.advance,
                 acChangeProject: auditConst.changeProject,
                 acChangeProject: auditConst.changeProject,
                 acChangeApply: auditConst.changeApply,
                 acChangeApply: auditConst.changeApply,
+                acChangePlan: auditConst.changeApply,
                 noticeList,
                 noticeList,
                 pushType: auditConst.pushType,
                 pushType: auditConst.pushType,
                 projectData,
                 projectData,

+ 30 - 1
app/controller/material_controller.js

@@ -128,6 +128,35 @@ module.exports = app => {
         }
         }
 
 
         /**
         /**
+         * 编辑期(Post)
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async save(ctx) {
+            try {
+                const data = {
+                    mid: ctx.request.body.mid,
+                    period: ctx.request.body.period,
+                };
+                const material = await ctx.service.material.getDataById(data.mid);
+                if (!material) {
+                    throw '提交数据错误';
+                }
+                // if (stage.status === auditConst.status.checked) {
+                //     throw '该计量期当前不可编辑';
+                // }
+                // if (ctx.session.sessionUser.accountId !== stage.user_id) {
+                //     throw '您无权修改该数据';
+                // }
+                await this.ctx.service.material.saveMaterial(data.mid, data.period);
+                ctx.redirect('/tender/' + ctx.tender.id + '/measure/material');
+            } catch (err) {
+                this.log(err);
+                ctx.redirect('/tender/' + ctx.tender.id + '/measure/material');
+            }
+        }
+
+        /**
          * 新增期(Post)
          * 新增期(Post)
          * @param ctx
          * @param ctx
          * @return {Promise<void>}
          * @return {Promise<void>}
@@ -595,7 +624,7 @@ module.exports = app => {
                     msg: '',
                     msg: '',
                     data: {},
                     data: {},
                 };
                 };
-                if (ctx.session.sessionProject.page_show.openMaterialChecklist && (data.type !== 'join' && data.type !== 'notjoin')) {
+                if (ctx.session.sessionProject.page_show.openMaterialChecklist && (data.type !== 'join' && data.type !== 'notjoin' && data.type !== 'useOther')) {
                     throw '清单设置功能已启动,请前往清单设置页操作清单内容';
                     throw '清单设置功能已启动,请前往清单设置页操作清单内容';
                 }
                 }
                 switch (data.type) {
                 switch (data.type) {

+ 10 - 0
app/controller/report_controller.js

@@ -191,6 +191,11 @@ module.exports = app => {
                         reportConst.rptCustomType[JV.NODE_CUS_AUDIT_SELECT], ctx.tender.id, ctx.stage.id);
                         reportConst.rptCustomType[JV.NODE_CUS_AUDIT_SELECT], ctx.tender.id, ctx.stage.id);
                     customSelects.stageFlow = await ctx.service.stageAudit.getAuditGroupByListWithOwner(
                     customSelects.stageFlow = await ctx.service.stageAudit.getAuditGroupByListWithOwner(
                         ctx.stage.stage_id, ctx.stage.stage_times);
                         ctx.stage.stage_id, ctx.stage.stage_times);
+                    stageFlow.forEach(x => {
+                        if (!x) return;
+                        x.visible = (!ctx.session.sessionUser.is_admin && ctx.session.sessionProject.page_show.individualSign === 1)
+                            ? x.aid === ctx.session.sessionUser.accountId : true
+                    });
                 } else {
                 } else {
                     customSelects.audit_select = [];
                     customSelects.audit_select = [];
                 }
                 }
@@ -417,6 +422,11 @@ module.exports = app => {
                 await encodeDummySignatureDataUri(pageRst, this.app.baseDir); // 注意草图数据量问题!
                 await encodeDummySignatureDataUri(pageRst, this.app.baseDir); // 注意草图数据量问题!
             }
             }
             const stageFlow = await ctx.service.stageAudit.getAuditGroupByListWithOwner(params.stage_id, params.stage_times);
             const stageFlow = await ctx.service.stageAudit.getAuditGroupByListWithOwner(params.stage_id, params.stage_times);
+            stageFlow.forEach(x => {
+                if (!x) return;
+                x.visible = (!ctx.session.sessionUser.is_admin && ctx.session.sessionProject.page_show.individualSign === 1)
+                    ? x.aid === ctx.session.sessionUser.accountId : true
+            });
 
 
             // console.log('encodeSignatureDataUri!');
             // console.log('encodeSignatureDataUri!');
             return {
             return {

+ 1 - 0
app/controller/setting_controller.js

@@ -765,6 +765,7 @@ module.exports = app => {
                 if (!result) throw '保存数据失败';
                 if (!result) throw '保存数据失败';
                 this.ctx.session.sessionProject.page_show.openChangeProject = data.openChangeProject ? 1 : 0;
                 this.ctx.session.sessionProject.page_show.openChangeProject = data.openChangeProject ? 1 : 0;
                 this.ctx.session.sessionProject.page_show.openChangeApply = data.openChangeApply ? 1 : 0;
                 this.ctx.session.sessionProject.page_show.openChangeApply = data.openChangeApply ? 1 : 0;
+                this.ctx.session.sessionProject.page_show.openChangePlan = data.openChangePlan ? 1 : 0;
                 this.ctx.session.sessionProject.page_show.openMaterialTax = data.openMaterialTax ? 1 : 0;
                 this.ctx.session.sessionProject.page_show.openMaterialTax = data.openMaterialTax ? 1 : 0;
                 this.ctx.session.sessionProject.page_show.openMaterialChecklist = data.openMaterialChecklist ? 1 : 0;
                 this.ctx.session.sessionProject.page_show.openMaterialChecklist = data.openMaterialChecklist ? 1 : 0;
                 const result2 = await ctx.service.project.updatePageshow(projectId);
                 const result2 = await ctx.service.project.updatePageshow(projectId);

+ 11 - 2
app/controller/tender_controller.js

@@ -425,6 +425,7 @@ module.exports = app => {
                     tender.end_yf_tp = ctx.helper.add(lastStage.yf_tp, lastStage.pre_yf_tp);
                     tender.end_yf_tp = ctx.helper.add(lastStage.yf_tp, lastStage.pre_yf_tp);
                     tender.end_sf_tp = ctx.helper.add(lastStage.sf_tp, lastStage.pre_sf_tp);
                     tender.end_sf_tp = ctx.helper.add(lastStage.sf_tp, lastStage.pre_sf_tp);
                     const change_tp = await ctx.service.change.getChangeTp(tender.id);
                     const change_tp = await ctx.service.change.getChangeTp(tender.id);
+                    tender.change_tp = change_tp;
                     tender.undone_tp = ctx.helper.sub(ctx.helper.sub(ctx.helper.add(tender.total_price, change_tp), tender.end_contract_tp), tender.end_qc_tp);
                     tender.undone_tp = ctx.helper.sub(ctx.helper.sub(ctx.helper.add(tender.total_price, change_tp), tender.end_contract_tp), tender.end_qc_tp);
                     if (lastStage.status === auditConst.stage.status.uncheck) {
                     if (lastStage.status === auditConst.stage.status.uncheck) {
                         const status_name = await this.ctx.service.projectAccount.getAccountInfoById(lastStage.user_id);
                         const status_name = await this.ctx.service.projectAccount.getAccountInfoById(lastStage.user_id);
@@ -1395,6 +1396,14 @@ module.exports = app => {
                                 orders: [['in_time', 'desc']],
                                 orders: [['in_time', 'desc']],
                             });
                             });
                             break;
                             break;
+                        case 'change_plan':
+                            if (ctx.session.sessionProject.page_show.isOnlyChecked) where.status = auditConst.changeApply.status.checked;
+                            responseData.data[f] = await ctx.service.changePlan.getAllDataByCondition({
+                                columns: [ 'id', 'code', 'name', 'selected' ],
+                                where,
+                                orders: [['in_time', 'desc']],
+                            });
+                            break;
                         default:
                         default:
                             throw '未知数据类型';
                             throw '未知数据类型';
                     }
                     }
@@ -1409,11 +1418,11 @@ module.exports = app => {
         async saveRelaData(ctx) {
         async saveRelaData(ctx) {
             try {
             try {
                 const data = JSON.parse(ctx.request.body.data);
                 const data = JSON.parse(ctx.request.body.data);
-                console.log(data);
                 const responseData = { err: 0, msg: '', data: {} };
                 const responseData = { err: 0, msg: '', data: {} };
                 if (data.change) await this.ctx.service.change.updateChangeSelect(data.change);
                 if (data.change) await this.ctx.service.change.updateChangeSelect(data.change);
                 if (data.change_apply) await this.ctx.service.changeApply.defaultUpdateRows(data.change_apply);
                 if (data.change_apply) await this.ctx.service.changeApply.defaultUpdateRows(data.change_apply);
-                if (data.change_project) this.ctx.service.changeProject.defaultUpdateRows(data.change_project);
+                if (data.change_project) await this.ctx.service.changeProject.defaultUpdateRows(data.change_project);
+                if (data.change_plan) await this.ctx.service.changePlan.defaultUpdateRows(data.change_plan);
                 ctx.body = responseData;
                 ctx.body = responseData;
             } catch (err) {
             } catch (err) {
                 ctx.log(err);
                 ctx.log(err);

+ 19 - 0
app/lib/ledger.js

@@ -496,6 +496,25 @@ class gatherTree extends baseTree {
         }
         }
         // todo load Pos Data;
         // todo load Pos Data;
     }
     }
+    resortChildrenByCustom(fun) {
+        for (const n of this.datas) {
+            if (n.children && n.children.length > 1) {
+                n.children.sort(fun);
+                n.children.forEach((x, i) => { x.order = i + 1; });
+            }
+        }
+        this.generateSortNodes();
+    }
+    resortChildrenDefault() {
+        const helper = this.ctx.helper;
+        this.resortChildrenByCustom((x, y) => {
+            const iCode = helper.compareCode(x.code, y.code);
+            if (iCode) return iCode;
+            if (!x.name) return -1;
+            if (!y.name) return 1;
+            return x.name.localeCompare(y.name);
+        })
+    }
 
 
     calculateSum() {
     calculateSum() {
         if (this.setting.calcSum) {
         if (this.setting.calcSum) {

+ 16 - 1
app/lib/rpt_data_analysis.js

@@ -1510,6 +1510,18 @@ const loadCooperationData = {
         return '';
         return '';
 
 
     },
     },
+    _findCooperation(relaId, stageCooperation, auditor) {
+        if (!stageCooperation) return null;
+        if (relaId.length > 0) {
+            for (const id of relaId) {
+                const c = stageCooperation.find(x => { return x.ledger_id == id; });
+                if (c) return c;
+            }
+            return null;
+        }
+        return null;
+
+    },
     _completeSign(auditor) {
     _completeSign(auditor) {
         if (!auditor || !auditor.end_time) return '';
         if (!auditor || !auditor.end_time) return '';
         return auditor.sign_path ? 'public/upload/sign/' + auditor.sign_path : '';
         return auditor.sign_path ? 'public/upload/sign/' + auditor.sign_path : '';
@@ -1537,9 +1549,11 @@ const loadCooperationData = {
             const bills = data.mem_stage_bills.find(x => { return x.id === d.lid; });
             const bills = data.mem_stage_bills.find(x => { return x.id === d.lid; });
             const relaId = bills ? bills.full_path.split('-').reverse() : [];
             const relaId = bills ? bills.full_path.split('-').reverse() : [];
 
 
-            d.cooperation = [];
+            d.cooperation = [], d.company = [];
             for (const [i, sa] of data.stage_audit.entries()) {
             for (const [i, sa] of data.stage_audit.entries()) {
+                const co = this._findCooperation(relaId, stageCooperation[i], sa);
                 d.cooperation.push(this._findSign(relaId, stageCooperation[i], sa));
                 d.cooperation.push(this._findSign(relaId, stageCooperation[i], sa));
+                d.company.push(co ? co.company : '');
             }
             }
 
 
             if (finish) {
             if (finish) {
@@ -1548,6 +1562,7 @@ const loadCooperationData = {
                         d['co_sign' + (i + 1)] = d.cooperation[cs] || this._completeSign(data.stage_audit[cs]);
                         d['co_sign' + (i + 1)] = d.cooperation[cs] || this._completeSign(data.stage_audit[cs]);
                         d['co_opinion' + (i + 1)] = data.stage_audit[cs].opinion || '';
                         d['co_opinion' + (i + 1)] = data.stage_audit[cs].opinion || '';
                         d['co_time' + (i + 1)] = data.stage_audit[cs].end_time;
                         d['co_time' + (i + 1)] = data.stage_audit[cs].end_time;
+                        d['co_company' + (i + 1)] = d.company[cs] || data.stage_audit[cs].name;
                     }
                     }
                 }
                 }
             }
             }

+ 0 - 1
app/lib/sum_load.js

@@ -396,7 +396,6 @@ class gatherStageGclTree extends loadGclBaseTree {
                 }
                 }
             }
             }
         }
         }
-        console.log(result.update.length);
         for (const i of this.items) {
         for (const i of this.items) {
             result.errors.push({ b_code: i.b_code, name: i.name, unit: i.unit, qty: i.contract_qty, type: 'miss' });
             result.errors.push({ b_code: i.b_code, name: i.name, unit: i.unit, qty: i.contract_qty, type: 'miss' });
             if (i.change_detail && i.change_detail.length > 0) {
             if (i.change_detail && i.change_detail.length > 0) {

+ 3 - 1
app/middleware/change_apply_check.js

@@ -8,7 +8,7 @@
  * @version
  * @version
  */
  */
 
 
-const status = require('../const/audit').changeProject.status;
+const status = require('../const/audit').changeApply.status;
 const _ = require('lodash');
 const _ = require('lodash');
 
 
 module.exports = options => {
 module.exports = options => {
@@ -34,6 +34,8 @@ module.exports = options => {
             // 读取原报、审核人数据
             // 读取原报、审核人数据
             change.auditors = yield this.service.changeApplyAudit.getAuditors(change.id, change.times);
             change.auditors = yield this.service.changeApplyAudit.getAuditors(change.id, change.times);
             change.curAuditor = yield this.service.changeApplyAudit.getCurAuditor(change.id, change.times);
             change.curAuditor = yield this.service.changeApplyAudit.getCurAuditor(change.id, change.times);
+            // decimal小数位设置
+            change.decimal = change.decimal ? JSON.parse(change.decimal) : { tp: this.tender.info.decimal.tp, up: this.tender.info.decimal.up, precision: this.tender.info.precision };
 
 
             if (!change) throw '变更令数据有误';
             if (!change) throw '变更令数据有误';
             // 权限相关
             // 权限相关

+ 121 - 0
app/middleware/change_plan_check.js

@@ -0,0 +1,121 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Ellisran
+ * @date 2020/10/15
+ * @version
+ */
+
+const status = require('../const/audit').changePlan.status;
+const _ = require('lodash');
+
+module.exports = options => {
+    /**
+     * 标段校验 中间件
+     * 1. 读取标段数据(包括属性)
+     * 2. 检验用户是否可见标段(不校验具体权限)
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* changePlanCheck(next) {
+        try {
+            // 获取revise
+            if (!this.session.sessionProject.page_show.openChangePlan) {
+                throw '该功能已关闭';
+            }
+            const cpid = this.params.cpid || this.request.body.cpid;
+            if (!cpid) {
+                throw '您访问的变更方案不存在';
+            }
+            const change = yield this.service.changePlan.getDataById(cpid);
+            // 读取原报、审核人数据
+            change.auditors = yield this.service.changePlanAudit.getAuditors(change.id, change.times);
+            change.curAuditor = yield this.service.changePlanAudit.getCurAuditor(change.id, change.times);
+            // decimal小数位设置
+            change.decimal = change.decimal ? JSON.parse(change.decimal) : { tp: this.tender.info.decimal.tp, up: this.tender.info.decimal.up, precision: this.tender.info.precision };
+
+            if (!change) throw '变更令数据有误';
+            // 权限相关
+            // todo 校验权限 (标段参与人、分享)
+            const accountId = this.session.sessionUser.accountId,
+                auditorIds = _.map(change.auditors, 'aid'),
+                shareIds = [];
+            if (accountId === change.uid) { // 原报
+                // if (change.curAuditor) {
+                //     change.readOnly = change.status === status.checking && change.curAuditor.user_id === accountId;
+                // } else {
+                //     change.readOnly = change.status !== status.uncheck && change.status !== status.back;
+                // }
+                change.curTimes = change.times;
+                if (change.status === status.uncheck || change.status === status.checkNo) {
+                    change.curOrder = 0;
+                } else if (change.status === status.checked) {
+                    change.curOrder = _.max(_.map(change.auditors, 'order'));
+                } else {
+                    change.curOrder = change.curAuditor.aid === accountId ? change.curAuditor.order : change.curAuditor.order - 1;
+                }
+                change.filePermission = true;
+            } else if (this.tender.isTourist) {
+                change.curTimes = change.times;
+                if (change.status === status.uncheck || change.status === status.checkNo) {
+                    change.curOrder = 0;
+                } else if (change.status === status.checked) {
+                    change.curOrder = _.max(_.map(change.auditors, 'order'));
+                } else {
+                    change.curOrder = change.curAuditor.order;
+                }
+                change.filePermission = this.tender.touristPermission.file || auditorIds.indexOf(accountId) !== -1;
+            } else if (auditorIds.indexOf(accountId) !== -1) { // 审批人
+                if (change.status === status.uncheck) {
+                    throw '您无权查看该数据';
+                }
+                // change.readOnly = change.status !== status.checking || accountId !== change.curAuditor.aid;
+                change.curTimes = change.status === status.checkNo ? change.times - 1 : change.times;
+                if (change.status === status.checked) {
+                    change.curOrder = _.max(_.map(change.auditors, 'order'));
+                } else if (change.status === status.checkNo) {
+                    const audit = this.service.changePlanAudit.getDataByCondition({
+                        cpid: change.id, times: change.times, status: status.checkNo,
+                    });
+                    change.curOrder = audit.order;
+                } else {
+                    change.curOrder = accountId === change.curAuditor.aid ? change.curAuditor.order : change.curAuditor.order - 1;
+                }
+                change.filePermission = true;
+            } else if (shareIds.indexOf(accountId) !== -1) { // 分享人
+                if (change.status === status.uncheck) {
+                    throw '您无权查看该数据';
+                }
+                // change.readOnly = true;
+                change.curTimes = change.status === status.checkNo ? change.times - 1 : change.times;
+                change.curOrder = change.status === status.checked ? _.max(_.map(change.auditors, 'order')) : change.curAuditor.order - 1;
+                change.filePermission = false;
+            } else { // 其他不可见
+                throw '您无权查看该数据';
+            }
+            // 调差的readOnly 指表格和页面只能看不能改,和审批无关
+            change.readOnly = !((change.status === status.uncheck || change.status === status.checkNo) && accountId === change.uid);
+            change.shenpiPower = change.status === status.checking && change.curAuditor.aid === accountId;
+            this.change = change;
+            yield next;
+        } catch (err) {
+            console.log(err);
+            // 输出错误到日志
+            if (err.stack) {
+                this.logger.error(err);
+            } else {
+                this.getLogger('fail').info(JSON.stringify({
+                    error: err,
+                    project: this.session.sessionProject,
+                    user: this.session.sessionUser,
+                    body: this.session.body,
+                }));
+            }
+            // 重定向值标段管理
+            this.redirect(this.request.headers.referer);
+        }
+    };
+};

+ 4 - 1
app/middleware/tender_check.js

@@ -69,6 +69,8 @@ module.exports = options => {
             const changeProjectXsAuditorsId = this.helper._.map(changeProjectXsAuditors, 'aid');
             const changeProjectXsAuditorsId = this.helper._.map(changeProjectXsAuditors, 'aid');
             const changeApplyAuditors = this.session.sessionProject.page_show.openChangeApply ? yield this.service.changeApplyAudit.getAllAuditors(tender.id) : [];
             const changeApplyAuditors = this.session.sessionProject.page_show.openChangeApply ? yield this.service.changeApplyAudit.getAllAuditors(tender.id) : [];
             const changeApplyAuditorsId = this.helper._.map(changeApplyAuditors, 'aid');
             const changeApplyAuditorsId = this.helper._.map(changeApplyAuditors, 'aid');
+            const changePlanAuditors = this.session.sessionProject.page_show.openChangePlan ? yield this.service.changePlanAudit.getAllAuditors(tender.id) : [];
+            const changePlanAuditorsId = this.helper._.map(changePlanAuditors, 'aid');
             const tenderPermission = this.session.sessionUser.permission ? this.session.sessionUser.permission.tender : null;
             const tenderPermission = this.session.sessionUser.permission ? this.session.sessionUser.permission.tender : null;
             const isTenderTourist = yield this.service.tenderTourist.getDataByCondition({ tid: tender.id, user_id: accountId });
             const isTenderTourist = yield this.service.tenderTourist.getDataByCondition({ tid: tender.id, user_id: accountId });
             // 判断访问人是否具有游客身份
             // 判断访问人是否具有游客身份
@@ -79,7 +81,8 @@ module.exports = options => {
                 (tenderPermission === null || tenderPermission === undefined || tenderPermission.indexOf('2') === -1) &&
                 (tenderPermission === null || tenderPermission === undefined || tenderPermission.indexOf('2') === -1) &&
                 stageAuditorsId.indexOf(accountId) === -1 && changeAuditorsId.indexOf(accountId) === -1 &&
                 stageAuditorsId.indexOf(accountId) === -1 && changeAuditorsId.indexOf(accountId) === -1 &&
                 reviseAuditorsId.indexOf(accountId) === -1 && materialAuditorsId.indexOf(accountId) === -1 &&
                 reviseAuditorsId.indexOf(accountId) === -1 && materialAuditorsId.indexOf(accountId) === -1 &&
-                changeProjectAuditorsId.indexOf(accountId) === -1 && changeProjectXsAuditorsId.indexOf(accountId) === -1 && changeApplyAuditorsId.indexOf(accountId) === -1 &&
+                changeProjectAuditorsId.indexOf(accountId) === -1 && changeProjectXsAuditorsId.indexOf(accountId) === -1 &&
+                changeApplyAuditorsId.indexOf(accountId) === -1 && changePlanAuditorsId.indexOf(accountId) === -1 &&
                 advanceAuditorsId.indexOf(accountId) === -1 && !this.session.sessionUser.is_admin && !isTenderTourist) {
                 advanceAuditorsId.indexOf(accountId) === -1 && !this.session.sessionUser.is_admin && !isTenderTourist) {
                 throw '您无权查看该项目';
                 throw '您无权查看该项目';
             }
             }

+ 45 - 1
app/public/css/main.css

@@ -23,6 +23,10 @@ body {
   color:#333;
   color:#333;
   font-weight: 600
   font-weight: 600
 }
 }
+.nav-padding .nav-link.active, .nav-padding .show > .nav-link{
+  color: #fff;
+  background-color: #007bff;
+}
 .custom-file-sm,.custom-file-sm .custom-file-label{
 .custom-file-sm,.custom-file-sm .custom-file-label{
   height: calc(1.5em + .5rem + 2px);
   height: calc(1.5em + .5rem + 2px);
   padding-top: .25rem;
   padding-top: .25rem;
@@ -725,6 +729,12 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
 .bg-nav .menu-arrow{
 .bg-nav .menu-arrow{
   margin:22px 8px 0 0
   margin:22px 8px 0 0
 }
 }
+.nav-padding{
+  margin-top: 30px;
+}
+.nav-padding a{
+  padding: 0.5rem 1rem;
+}
 .nav-box {
 .nav-box {
   padding-top: 5px
   padding-top: 5px
 }
 }
@@ -832,6 +842,10 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
   height: 150px;
   height: 150px;
   overflow: auto;
   overflow: auto;
 }
 }
+.modal-height-max100{
+  max-height: 100px;
+  overflow: auto;
+}
 .modal-height-max150{
 .modal-height-max150{
   max-height: 150px;
   max-height: 150px;
   overflow: auto;
   overflow: auto;
@@ -1341,7 +1355,7 @@ overflow-y: auto;
   height: 300px;
   height: 300px;
 }
 }
 .card-big-title{
 .card-big-title{
-  font-size: 1.75rem;
+  font-size: 1.23rem;
   margin-bottom: .30rem;
   margin-bottom: .30rem;
   position: relative;
   position: relative;
 }
 }
@@ -1636,31 +1650,61 @@ overflow-y: auto;
   /*animation: change 60s steps(1) infinite;*/
   /*animation: change 60s steps(1) infinite;*/
 }
 }
 .login-new-body.img-1{
 .login-new-body.img-1{
+  width: 100%;
+  height: 100%;
+  /*background: url(bg/bg_001.jpg) no-repeat;*/
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_01.jpg) no-repeat;
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_01.jpg) no-repeat;
+  background-size: 100% 100%;
+  background-size:cover;
+  /*background-size: contain;*/
 }
 }
 .login-new-body.img-2{
 .login-new-body.img-2{
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_02.jpg) no-repeat;
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_02.jpg) no-repeat;
+  background-size: 100% 100%;
+  background-size:cover;
+  /*background-size: contain;*/
 }
 }
 .login-new-body.img-3{
 .login-new-body.img-3{
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_03.jpg) no-repeat;
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_03.jpg) no-repeat;
+  background-size: 100% 100%;
+  background-size:cover;
+  /*background-size: contain;*/
 }
 }
 .login-new-body.img-4{
 .login-new-body.img-4{
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_04.jpg) no-repeat;
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_04.jpg) no-repeat;
+  background-size: 100% 100%;
+  background-size:cover;
+  /*background-size: contain;*/
 }
 }
 .login-new-body.img-5{
 .login-new-body.img-5{
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_05.jpg) no-repeat;
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_05.jpg) no-repeat;
+  background-size: 100% 100%;
+  background-size:cover;
+  /*background-size: contain;*/
 }
 }
 .login-new-body.img-6{
 .login-new-body.img-6{
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_06.jpg) no-repeat;
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_06.jpg) no-repeat;
+  background-size: 100% 100%;
+  background-size:cover;
+  /*background-size: contain;*/
 }
 }
 .login-new-body.img-7{
 .login-new-body.img-7{
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_07.jpg) no-repeat;
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_07.jpg) no-repeat;
+  background-size: 100% 100%;
+  background-size:cover;
+  /*background-size: contain;*/
 }
 }
 .login-new-body.img-8{
 .login-new-body.img-8{
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_08.jpg) no-repeat;
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_08.jpg) no-repeat;
+  background-size: 100% 100%;
+  background-size:cover;
+  /*background-size: contain;*/
 }
 }
 .login-new-body.img-9{
 .login-new-body.img-9{
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_09.jpg) no-repeat;
   background:#192948 url(https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/bg_09.jpg) no-repeat;
+  background-size: 100% 100%;
+  background-size:cover;
+  /*background-size: contain;*/
 }
 }
 /*@keyframes change {
 /*@keyframes change {
   0% {
   0% {

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

@@ -129,7 +129,7 @@ $(document).ready(() => {
             if (iCode) return iCode;
             if (iCode) return iCode;
             if (!x.name) return -1;
             if (!x.name) return -1;
             if (!y.name) return 1;
             if (!y.name) return 1;
-            return x.name.localeCompare(y.name)
+            return x.name.localeCompare(y.name);
         });
         });
         SpreadJsObj.loadSheetData(compareSheet, SpreadJsObj.DataType.Tree, compareTree);
         SpreadJsObj.loadSheetData(compareSheet, SpreadJsObj.DataType.Tree, compareTree);
     });
     });

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

@@ -216,7 +216,7 @@ $(document).ready(() => {
         const name = $('input[name=name]', '#add-cate');
         const name = $('input[name=name]', '#add-cate');
         // const type = $('input[type=radio]:checked', '#add-cate');
         // const type = $('input[type=radio]:checked', '#add-cate');
         const data = {
         const data = {
-            name: name.val(),
+            name: trimInvalidChar(name.val()),
             // type: parseInt(type.val()),
             // type: parseInt(type.val()),
         };
         };
         postData('/setting/category/add', data, function (data) {
         postData('/setting/category/add', data, function (data) {
@@ -276,7 +276,7 @@ $(document).ready(() => {
         // const type = $('input[type=radio]:checked', '#edit-cate');
         // const type = $('input[type=radio]:checked', '#edit-cate');
         const data = {
         const data = {
             id: parseInt($(this).attr('cid')),
             id: parseInt($(this).attr('cid')),
-            name: name.val(),
+            name: trimInvalidChar(name.val()),
             // type: parseInt(type.val()),
             // type: parseInt(type.val()),
         }
         }
         postData('/setting/category/update', data, function (data) {
         postData('/setting/category/update', data, function (data) {

+ 12 - 2
app/public/js/change.js

@@ -227,7 +227,7 @@ $(document).ready(() => {
     // 新增变更令 确认
     // 新增变更令 确认
     $('#addOk').click(function () {
     $('#addOk').click(function () {
         $(this).attr('disabled', true);
         $(this).attr('disabled', true);
-        if ($('#bj-name').val().length === 0) {
+        if (!openChangePlan && $('#bj-name').val().length === 0) {
             $('#bj-name').addClass('is-invalid');
             $('#bj-name').addClass('is-invalid');
             $('#name_error_msg').show();
             $('#name_error_msg').show();
             $('#name_error_msg').text('变更工程名称不能为空。');
             $('#name_error_msg').text('变更工程名称不能为空。');
@@ -238,7 +238,7 @@ $(document).ready(() => {
             }, 2000);
             }, 2000);
             return;
             return;
         }
         }
-        if ($('#bj-name').val().length > 100) {
+        if (!openChangePlan && $('#bj-name').val().length > 100) {
             $('#bj-name').addClass('is-invalid');
             $('#bj-name').addClass('is-invalid');
             $('#name_error_msg').show();
             $('#name_error_msg').show();
             $('#name_error_msg').text('名称超过100个字,请缩减名称。');
             $('#name_error_msg').text('名称超过100个字,请缩减名称。');
@@ -251,6 +251,7 @@ $(document).ready(() => {
         }
         }
         const data = {
         const data = {
             code: $('#bj-code').val(),
             code: $('#bj-code').val(),
+            plan_code: $('#plan-code').val(),
             name: $('#bj-name').val(),
             name: $('#bj-name').val(),
         };
         };
         if (data.code || data.code !== '' || data.name || data.name !== '') {
         if (data.code || data.code !== '' || data.name || data.name !== '') {
@@ -267,6 +268,15 @@ $(document).ready(() => {
         }
         }
     });
     });
 
 
+    $('#plan-code').change(function () {
+        if ($(this).val() === '') {
+            $('#bj-name').val('');
+        } else {
+            const info = _.find(changePlanList, { code: $(this).val() });
+            $('#bj-name').val(info.name);
+        }
+    });
+
     //状态切换
     //状态切换
     $('#status_select a').on('click', function () {
     $('#status_select a').on('click', function () {
        const status = $(this).data('val');
        const status = $(this).data('val');

+ 8 - 0
app/public/js/change_apply_audit.js

@@ -197,6 +197,14 @@ function checkAuditorFrom () {
         toastr.error('变更原因不能为空');
         toastr.error('变更原因不能为空');
         flag = true;
         flag = true;
     }
     }
+    // 判断变更清单是否有空值
+    const nullCodeList = _.filter(changeList, function(item) {
+        return item.code === null || item.code === '';
+    });
+    if (nullCodeList.length > 0) {
+        toastr.error('变更清单都需要设置清单编号才能上报');
+        flag = true;
+    }
     if (flag) {
     if (flag) {
         return false;
         return false;
     }
     }

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

@@ -187,7 +187,492 @@ $(document).ready(() => {
         } else {
         } else {
             $(this).val(change[val_name]);
             $(this).val(change[val_name]);
         }
         }
-    })
+    });
+
+
+    const changeSpread = SpreadJsObj.createNewSpread($('#apply-spread')[0]);
+    const changeSpreadSheet = changeSpread.getActiveSheet();
+    const style1 = new GC.Spread.Sheets.Style();
+    style1.locked = true;
+
+    const changeSpreadSetting = {
+        cols: [
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 110, formatter: '@', readOnly: 'readOnly.isEdit'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 130, formatter: '@', readOnly: 'readOnly.isEdit'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: 'readOnly.isEdit', cellType: 'unit', comboItems: changeUnits, comboEdit: true},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.isEdit', getValue: 'getValue.unit_price'},
+            {title: '原设计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'oamount', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.isEdit', getValue: 'getValue.oamount'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'oa_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, getValue: 'getValue.oa_tp'},
+            {title: '申请变更增(+)减(-)|数量', colSpan: '2|1', rowSpan: '1|1', field: 'camount', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.isEdit', getValue: 'getValue.camount'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'ca_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, getValue: 'getValue.ca_tp'},
+        ],
+        emptyRows: !readOnly ? 3 : 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: readOnly,
+        localCache: {
+            key: 'changes-apply-list-spread',
+            colWidth: true,
+        }
+    };
+
+    console.log(changeList);
+
+    const changeCol = {
+        getValue: {
+            unit_price: function(data) {
+                return ZhCalc.round(data.unit_price, unitPriceUnit);
+            },
+            oa_tp: function (data) {
+                return ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.oamount, findDecimal(data.unit))), totalPriceUnit);
+            },
+            ca_tp: function (data) {
+                return ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.camount, findDecimal(data.unit))), totalPriceUnit);
+            },
+            oamount: function (data) {
+                return ZhCalc.round(data.oamount, findDecimal(data.unit));
+            },
+            camount: function (data) {
+                return ZhCalc.round(data.camount, findDecimal(data.unit));
+            },
+        },
+        readOnly: {
+            isEdit: function (data) {
+                return readOnly;
+            },
+        },
+    };
+    const changeSpreadObj = {
+        makeSjsFooter: function() {
+            // 增加汇总行并设为锁定禁止编辑状态
+            changeSpreadSheet.addRows(changeSpreadSheet.getRowCount(), 1);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 0, '合计');
+            changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
+            changeSpreadObj.countSum();
+        },
+        countSum: function() {
+            const rowCount = changeSpreadSheet.getRowCount();
+            let oSum = 0,
+                cSum = 0;
+            for (let i = 0; i < rowCount - 1; i++) {
+                oSum = ZhCalc.add(oSum, changeSpreadSheet.getValue(i, 5));
+                cSum = ZhCalc.add(cSum, changeSpreadSheet.getValue(i, 7));
+            }
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 5, oSum !== 0 ? oSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 7, cSum !== 0 ? cSum : null);
+        },
+        deletePress: function (sheet) {
+            return;
+        },
+        valueChanged: function (e, info) {
+            // 防止ctrl+z撤销数据
+            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+        }
+    };
+    if (!readOnly) {
+        changeSpreadObj.add = function () {
+            changeSpreadSheet.addRows(changeSpreadSheet.getRowCount() - 1, 1);
+            changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
+            // changeSpreadSheet.setSelection(changeList.length - 1, 0, 1, 1);
+        };
+        changeSpreadObj.batchAdd = function (num) {
+            changeSpreadSheet.addRows(changeSpreadSheet.getRowCount() - 1, parseInt(num));
+            changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
+        };
+        changeSpreadObj.del = function (sheet) {
+            const selection = sheet.getSelections();
+            const row = selection[0].row, count = selection[0].rowCount;
+            const sortData = sheet.zh_data;
+            const ids = [];
+            let emptyRowNum = 0;
+            for (let iRow = 0; iRow < count; iRow++) {
+                if (sortData[iRow + row]) {
+                    ids.push(sortData[iRow + row].id);
+                } else {
+                    emptyRowNum++;
+                }
+            }
+            if (ids.length > 0) {
+                postData(preUrl + '/list/save', {type: 'del', ids}, function (result) {
+                    changeList = result;
+                    SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+                    changeSpreadObj.makeSjsFooter();
+                });
+            } else if (emptyRowNum > 0) {
+                changeSpreadSheet.deleteRows(changeList.length, emptyRowNum);
+                changeSpreadSheet.setSelection(changeList.length - 1, 0, 1, 1);
+            }
+        };
+        changeSpreadObj.editEnded = function (e, info) {
+            if (info.sheet.zh_setting) {
+                const type = SpreadJsObj.getSelectObject(info.sheet) ? 'update' : 'add';
+                const select = type === 'update' ? SpreadJsObj.getSelectObject(info.sheet) : {unit: ''};
+                const col = info.sheet.zh_setting.cols[info.col];
+                // 未改变值则不提交
+                let validText = is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : '');
+                const orgValue = type === 'update' ? select[col.field] : '';
+                if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                if (col.field === 'oa_tp' || col.field === 'ca_tp') {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                // 判断部分值是否输入的是数字判断和数据计算
+                if (col.type === 'Number') {
+                    if (isNaN(validText)) {
+                        toastr.error('不能输入其它非数字类型字符');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    if (col.field === 'unit_price') {
+                        validText = ZhCalc.round(validText, unitPriceUnit);
+                    } else {
+                        validText = ZhCalc.round(validText, findDecimal(select.unit)) || 0;
+                    }
+                }
+                if (col.field === 'unit') {
+                    select.camount = ZhCalc.round(select.camount, findDecimal(validText)) || 0;
+                    select.oamount = ZhCalc.round(select.oamount, findDecimal(validText)) || 0;
+                }
+                select[col.field] = validText;
+
+                console.log(select, type);
+                delete select.waitingLoading;
+
+                // 更新至服务器
+                postData(preUrl + '/list/save', { type, updateData: select }, function (result) {
+                    if(type === 'update') {
+                        changeList.splice(info.row, 1, select);
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        changeSpreadObj.countSum();
+                    } else {
+                        changeList.push(result);
+                        changeSpreadSheet.addRows(changeList.length - 1, 1);
+                        SpreadJsObj.reLoadRowData(changeSpreadSheet, changeList.length - 1);
+                        changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
+                        // changeSpreadSheet.setSelection(changeList.length - 1, 0, 1, 1);
+                    }
+                }, function () {
+                    select[col.field] = orgValue;
+                    if(col.field === 'camount') {
+                        select.spamount = orgValue;
+                    }
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                });
+            }
+        };
+        changeSpreadObj.clipboardPasted  = function(e, info, cellRange) {
+            if (info.sheet.getColumnCount() > info.sheet.zh_setting.cols.length) {
+                info.sheet.setColumnCount(info.sheet.zh_setting.cols.length);
+            }
+            const hint = {
+                cellError: {type: 'error', msg: '粘贴内容超出了表格范围'},
+                numberExpr: {type: 'error', msg: '不能粘贴其它非数字类型字符'},
+            };
+            if (info.sheet.zh_setting) {
+                const sortData = info.sheet.zh_data || [];
+                const range = info.cellRange;
+                const data = [];
+                for (let iRow = 0; iRow < range.rowCount; iRow++) {
+                    let bPaste = true;
+                    const curRow = range.row + iRow;
+                    // const materialData = JSON.parse(JSON.stringify(sortData[curRow]));
+                    const cLData = curRow >= sortData.length ? {unit: ''} : {id: sortData[curRow].id};
+                    const hintRow = range.rowCount > 1 ? curRow : '';
+                    let sameCol = 0;
+                    for (let iCol = 0; iCol < range.colCount; iCol++) {
+                        const curCol = range.col + iCol;
+                        const colSetting = info.sheet.zh_setting.cols[curCol];
+                        if (!colSetting) continue;
+                        // cLData[colSetting.field] = trimInvalidChar(info.sheet.getText(curRow, curCol));
+
+                        let validText = info.sheet.getText(curRow, curCol);
+                        validText = is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : '');
+                        const orgValue = curRow >= sortData.length ? '' : sortData[curRow][colSetting.field];
+                        if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                            sameCol++;
+                            if (range.colCount === sameCol) {
+                                bPaste = false;
+                            }
+                            continue;
+                        }
+
+                        if (colSetting.type === 'Number') {
+                            if (isNaN(validText)) {
+                                // toastMessageUniq(getPasteHint(hint.numberExpr, hintRow));
+                                toastMessageUniq(hint.numberExpr);
+                                bPaste = false;
+                                continue;
+                            }
+                            if (colSetting.field === 'unit_price') {
+                                validText = ZhCalc.round(validText, unitPriceUnit);
+                            } else {
+                                validText = ZhCalc.round(validText, findDecimal(cLData.unit)) || 0;
+                            }
+                        }
+                        let unitdecimal = validText;
+                        if (colSetting.field === 'unit') {
+                            //粘贴内容要为下拉列表里所有的单位,不然为空
+                            if (changeUnits.indexOf(validText) === -1) {
+                                unitdecimal = '';
+                                // validText = null;
+                            }
+                            cLData.camount = curRow >= sortData.length ? 0 : ZhCalc.round(sortData[curRow].camount, findDecimal(unitdecimal)) || 0;
+                            cLData.oamount = curRow >= sortData.length ? 0 : ZhCalc.round(sortData[curRow].oamount, findDecimal(unitdecimal)) || 0;
+                        }
+                        cLData[colSetting.field] = validText;
+
+                    }
+                    if (bPaste) {
+                        delete cLData.oa_tp;
+                        delete cLData.ca_tp;
+                        data.push(cLData);
+                        // rowData.push(curRow);
+                    } else {
+                        SpreadJsObj.reLoadRowData(info.sheet, curRow);
+                    }
+                }
+                if (data.length === 0) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                    return;
+                }
+                console.log(data);
+                // 更新至服务器
+                postData(preUrl + '/list/save', { type:'paste', updateData: data }, function (result) {
+                    changeList = result;
+                    SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+                    changeSpreadObj.makeSjsFooter();
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                    return;
+                });
+            }
+        };
+
+        changeSpreadObj.updateOamount = function () {
+            const dataSource = listRule.source === 1 ? gclGatherData : dealBillList;
+            const updateList = [];
+            for (const c of changeList) {
+                const source = _.find(dataSource, function (item) {
+                    if (((item.b_code && item.b_code === c.code) || (item.code && item.code === c.code)) && item.name === c.name) {
+                        if (listRule.rule.length > 0) {
+                            for(const r of listRule.rule) {
+                                if (item[r] !== c[r]) {
+                                    return false;
+                                }
+                            }
+                        }
+                        return true;
+                    }
+                    return false;
+                });
+                if (source && source.quantity !== c.oamount) {
+                    updateList.push({ id: c.id, oamount: source.quantity });
+                }
+            }
+            console.log(updateList);
+            if(updateList.length > 0) {
+                postData(preUrl + '/list/save', { type:'paste', updateData: updateList }, function (result) {
+                    changeList = result;
+                    SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+                    changeSpreadObj.makeSjsFooter();
+                });
+            }
+        };
+        // changeSpread.bind(spreadNS.Events.CellChanged, changeSpreadObj.cellChanged);
+        changeSpread.bind(spreadNS.Events.EditEnded, changeSpreadObj.editEnded);
+        changeSpread.bind(spreadNS.Events.ClipboardPasted, changeSpreadObj.clipboardPasted);
+        changeSpread.bind(spreadNS.Events.ValueChanged, changeSpreadObj.valueChanged);
+        SpreadJsObj.addDeleteBind(changeSpread, changeSpreadObj.deletePress);
+
+        let batchInsertObj;
+        $.contextMenu.types.batchInsert = function (item, opt, root) {
+            const self = this;
+            if ($.isFunction(item.icon)) {
+                item._icon = item.icon.call(this, this, $t, key, item);
+            } else {
+                if (typeof(item.icon) === 'string' && item.icon.substring(0, 3) === 'fa-') {
+                    // to enable font awesome
+                    item._icon = root.classNames.icon + ' ' + root.classNames.icon + '--fa fa ' + item.icon;
+                } else {
+                    item._icon = root.classNames.icon + ' ' + root.classNames.icon + '-' + item.icon;
+                }
+            }
+            this.addClass(item._icon);
+            const $obj = $('<div>' + item.name + '<input class="text-right ml-1 mr-1" type="tel" max="100" min="1" value="' + item.value + '" style="width: 30px; height: 18px; padding-right: 4px;">行</div>')
+                .appendTo(this);
+            const $input = $obj.find('input');
+            const event = () => {
+                if (self.hasClass('context-menu-disabled')) return;
+                item.batchInsert($input[0], root);
+            };
+            $obj.on('click', event).keypress(function (e) {if (e.keyCode === 13) { event(); }});
+            $input.click((e) => {e.stopPropagation();})
+                .keyup((e) => {if (e.keyCode === 13) item.batchInsert($input[0], root);})
+                .on('input', function () {this.value = this.value.replace(/[^\d]/g, '');});
+        };
+
+        // 右键菜单
+        $.contextMenu({
+            selector: '#apply-spread',
+            build: function ($trigger, e) {
+                const target = SpreadJsObj.safeRightClickSelection($trigger, e, changeSpread);
+                return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
+            },
+            items: {
+                'updateOamount': {
+                    name: '原设计数量读取',
+                    icon: '',
+                    callback: function (key, opt) {
+                        changeSpreadObj.updateOamount(changeSpreadSheet);
+                    },
+                    disabled: function (key, opt) {
+                        const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+                        const sel = changeSpreadSheet.getSelections()[0];
+                        // console.log(select, sel);
+                        if (!readOnly && select && sel.row !== changeSpreadSheet.getRowCount() - 1) {
+                            return false;
+                        } else {
+                            return true;
+                        }
+                    }
+                },
+                sprDel: '------------',
+                'createAdd': {
+                    name: '添加行',
+                    icon: 'fa-sign-in',
+                    callback: function (key, opt) {
+                        changeSpreadObj.add(changeSpreadSheet);
+                    },
+                },
+                'batchInsert': {
+                    name: '批量添加行',
+                    type: 'batchInsert',
+                    value: '2',
+                    icon: 'fa-sign-in',
+                    batchInsert: function (obj, root) {
+                        if (_.toNumber(obj.value) > _.toNumber(obj.max)) {
+                            obj.value = obj.max;
+                            toastr.warning('批量添加不可多于' + obj.max);
+                        } else if(_.toNumber(obj.value) < _.toNumber(obj.min)) {
+                            obj.value = obj.min;
+                            toastr.warning('批量添加不可少于' + obj.min);
+                        } else {
+                            // treeOperationObj.addNode(ledgerSpread.getActiveSheet(), parseInt(obj.value));
+                            changeSpreadObj.batchAdd(obj.value);
+                            root.$menu.trigger('contextmenu:hide');
+                        }
+                    },
+                },
+                'delete': {
+                    name: '删除',
+                    icon: 'fa-remove',
+                    callback: function (key, opt) {
+                        changeSpreadObj.del(changeSpreadSheet);
+                    },
+                    disabled: function (key, opt) {
+                        // const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+                        if (changeSpreadSheet.zh_data) {
+                            const selection = changeSpreadSheet.getSelections();
+                            // return changeSpreadSheet.zh_data.length < selection[0].row + selection[0].rowCount;
+                            return changeSpreadSheet.getRowCount() - 1 < selection[0].row + selection[0].rowCount;
+                        } else {
+                            return true;
+                        }
+                        // const sel = changeSpreadSheet.getSelections()[0];
+                        // // console.log(select, sel);
+                        // if (!readOnly && select && sel.row !== changeSpreadSheet.getRowCount() - 1) {
+                        //     return false;
+                        // } else {
+                        //     return true;
+                        // }
+                    }
+                },
+            }
+        });
+
+        $('#shuliangguize').on('show.bs.modal', function () {
+            $('#shuliangguize input[name="data_source"][value="'+ listRule.source +'"]').prop('checked', true);
+            $('#shuliangguize input[name="data_rule"]').prop('checked', false);
+            for(const r of listRule.rule) {
+                $('#shuliangguize input[name="data_rule"][value="' + r +'"]').prop('checked', true);
+            }
+        });
+
+        // 设置原设计数量读取
+        $('#setListRule').click(function () {
+            const rule = [];
+            $('#shuliangguize input[name="data_rule"]:checked').each(function () {
+                rule.push($(this).val());
+            });
+            const newListRule = {
+                source: parseInt($('#shuliangguize input[name="data_source"]:checked').val()),
+                rule,
+            };
+            postData(preUrl + '/list/save', { type: 'list_rule', postData: JSON.stringify(newListRule) }, function (result) {
+                listRule = newListRule;
+                $('#shuliangguize').modal('hide');
+            });
+        })
+    }
+    let changeListData;
+    let gclGatherData;
+    let dealBillList;
+    const billUrl = window.location.pathname.split('/').slice(0, 4).join('/');
+    postData(billUrl + '/defaultBills', {}, function (result) {
+        gclGatherModel.loadLedgerData(result.bills);
+        gclGatherModel.loadPosData(result.pos);
+
+        gclGatherData = gclGatherModel.gatherGclData();
+        gclGatherData = _.filter(gclGatherData, function (item) {
+            return item.leafXmjs && item.leafXmjs.length !== 0;
+        });
+        // 数组去重
+        dealBillList = result.dealBills;
+        changeListData = gclGatherData;
+        console.log(changeListData, dealBillList);
+
+        SpreadJsObj.initSpreadSettingEvents(changeSpreadSetting, changeCol);
+        SpreadJsObj.initSheet(changeSpreadSheet, changeSpreadSetting);
+        SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+        changeSpreadObj.makeSjsFooter();
+
+        // 计算最新的变更总额和change的total_price是否一致,不一致则更新
+        if (change.status !== auditConst.status.checked) {
+            // let new_tp = 0;
+            const updateArray = [];
+            for (const c of changeList) {
+                const oneUpdate = {};
+                console.log(ZhCalc.round(c.camount, findDecimal(c.unit)), c.camount);
+                if (ZhCalc.round(c.oamount, findDecimal(c.unit)) !== c.oamount) oneUpdate.oamount = ZhCalc.round(c.oamount, findDecimal(c.unit));
+                if (ZhCalc.round(c.camount, findDecimal(c.unit)) !== c.camount) oneUpdate.camount = ZhCalc.round(c.camount, findDecimal(c.unit));
+                if (ZhCalc.round(c.unit_price, unitPriceUnit) !== c.unit_price) oneUpdate.unit_price = ZhCalc.round(c.unit_price, unitPriceUnit);
+                if (!_.isEmpty(oneUpdate)) {
+                    oneUpdate.id = c.id;
+                    updateArray.push(oneUpdate);
+                }
+                // new_tp = ZhCalc.add(new_tp, ZhCalc.round(ZhCalc.mul(ZhCalc.round(c.spamount, findDecimal(c.unit)), ZhCalc.round(c.unit_price, unitPriceUnit)), totalPriceUnit));
+            }
+            console.log(updateArray);
+            if (updateArray.length > 0) {
+                // 更新至服务器
+                postData(preUrl + '/list/save', { type:'paste', updateData: updateArray }, function (result) {
+                    changeList = result;
+                    SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+                    changeSpreadObj.makeSjsFooter();
+                });
+            }
+            // if (change.total_price && change.total_price !== new_tp) {
+            //     postData(preUrl + '/list/save', {type: 'update_tp', updateData: new_tp}, function (result) {
+            //     });
+            // }
+        }
+    });
 });
 });
 
 
 /**
 /**
@@ -211,3 +696,23 @@ function validateFiles(files) {
         return true
         return true
     })
     })
 }
 }
+
+function findDecimal(unit) {
+    let value = precision.other.value;
+    const changeUnits = precision;
+    for (const d in changeUnits) {
+        if (changeUnits[d].unit !== undefined && changeUnits[d].unit === unit) {
+            value = changeUnits[d].value;
+            break;
+        }
+    }
+    return value;
+}
+
+const is_numeric = (value) => {
+    if (typeof(value) === 'object') {
+        return false;
+    } else {
+        return !Number.isNaN(Number(value)) && value.toString().trim() !== '';
+    }
+};

+ 13 - 0
app/public/js/change_information.js

@@ -32,6 +32,19 @@ $(document).ready(() => {
         changeSpreadSheet.setColumnVisible(3,$(this).is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
         changeSpreadSheet.setColumnVisible(3,$(this).is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
     });
     });
 
 
+    // 计算最新的变更总额和change的total_price是否一致,不一致则更新
+    if (changeStatus !== auditConst.status.checked) {
+        let new_tp = 0;
+        for (const c of changeList) {
+            new_tp = ZhCalc.add(new_tp, ZhCalc.round(ZhCalc.mul(ZhCalc.round(c.spamount, findDecimal(c.unit)), ZhCalc.round(c.unit_price, unitPriceUnit)), totalPriceUnit));
+        }
+        console.log(changeTp, new_tp);
+        if (changeTp !== new_tp) {
+            postData(window.location.pathname + '/save', { type:'update_tp', updateData: new_tp }, function (result) {
+            });
+        }
+    }
+
     //tab change
     //tab change
     $('a[data-toggle="tab"]').on('shown.bs.tab', function () {
     $('a[data-toggle="tab"]').on('shown.bs.tab', function () {
         const tab = $(this).data('tab');
         const tab = $(this).data('tab');

+ 4 - 3
app/public/js/change_information_approval.js

@@ -85,10 +85,10 @@ $(document).ready(() => {
                 return ZhCalc.round(data.unit_price, unitPriceUnit);
                 return ZhCalc.round(data.unit_price, unitPriceUnit);
             },
             },
             oa_tp: function (data) {
             oa_tp: function (data) {
-                return ZhCalc.round(ZhCalc.mul(data.unit_price, data.oamount), totalPriceUnit);
+                return ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.oamount, findDecimal(data.unit))), totalPriceUnit);
             },
             },
             ca_tp: function (data) {
             ca_tp: function (data) {
-                return ZhCalc.round(ZhCalc.mul(data.unit_price, data.camount), totalPriceUnit);
+                return ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.camount, findDecimal(data.unit))), totalPriceUnit);
             },
             },
             oamount: function (data) {
             oamount: function (data) {
                 return ZhCalc.round(data.oamount, findDecimal(data.unit));
                 return ZhCalc.round(data.oamount, findDecimal(data.unit));
@@ -110,7 +110,8 @@ $(document).ready(() => {
         setAuditValue: function () {
         setAuditValue: function () {
             for (const c  of changeList) {
             for (const c  of changeList) {
                 for (const j of aidList) {
                 for (const j of aidList) {
-                    c['sa_tp_' + j] = ZhCalc.round(ZhCalc.mul(c['audit_amount_' + j], c.unit_price), totalPriceUnit);
+                    c['audit_amount_' + j] = ZhCalc.round(c['audit_amount_' + j], findDecimal(c.unit));
+                    c['sa_tp_' + j] = ZhCalc.round(ZhCalc.mul(c['audit_amount_' + j], ZhCalc.round(c.unit_price, unitPriceUnit)), totalPriceUnit);
                 }
                 }
             }
             }
             // const rowCount = changeSpreadSheet.getRowCount();
             // const rowCount = changeSpreadSheet.getRowCount();

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

@@ -142,10 +142,10 @@ $(document).ready(() => {
                 return ZhCalc.round(data.unit_price, unitPriceUnit);
                 return ZhCalc.round(data.unit_price, unitPriceUnit);
             },
             },
             oa_tp: function (data) {
             oa_tp: function (data) {
-                return ZhCalc.round(ZhCalc.mul(data.unit_price, data.oamount), totalPriceUnit);
+                return ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.oamount, findDecimal(data.unit))), totalPriceUnit);
             },
             },
             ca_tp: function (data) {
             ca_tp: function (data) {
-                return ZhCalc.round(ZhCalc.mul(data.unit_price, data.camount), totalPriceUnit);
+                return ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.camount, findDecimal(data.unit))), totalPriceUnit);
             },
             },
             oamount: function (data) {
             oamount: function (data) {
                 return ZhCalc.round(data.oamount, findDecimal(data.unit));
                 return ZhCalc.round(data.oamount, findDecimal(data.unit));

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

@@ -73,10 +73,10 @@ $(document).ready(() => {
                 return ZhCalc.round(data.unit_price, unitPriceUnit);
                 return ZhCalc.round(data.unit_price, unitPriceUnit);
             },
             },
             oa_tp: function (data) {
             oa_tp: function (data) {
-                return ZhCalc.round(ZhCalc.mul(data.unit_price, data.oamount), totalPriceUnit);
+                return ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.oamount, findDecimal(data.unit))), totalPriceUnit);
             },
             },
             ca_tp: function (data) {
             ca_tp: function (data) {
-                return ZhCalc.round(ZhCalc.mul(data.unit_price, data.camount), totalPriceUnit);
+                return ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.camount, findDecimal(data.unit))), totalPriceUnit);
             },
             },
             oamount: function (data) {
             oamount: function (data) {
                 return ZhCalc.round(data.oamount, findDecimal(data.unit));
                 return ZhCalc.round(data.oamount, findDecimal(data.unit));
@@ -88,7 +88,7 @@ $(document).ready(() => {
                 return ZhCalc.round(data.samount, findDecimal(data.unit));
                 return ZhCalc.round(data.samount, findDecimal(data.unit));
             },
             },
             sa_tp: function (data) {
             sa_tp: function (data) {
-                return ZhCalc.round(ZhCalc.mul(data.unit_price, data.samount), totalPriceUnit);
+                return ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.samount, findDecimal(data.unit))), totalPriceUnit);
             },
             },
         },
         },
     };
     };

+ 322 - 0
app/public/js/change_plan.js

@@ -0,0 +1,322 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/8/21
+ * @version
+ */
+// 向后端请求中间计量号
+function getNewCode() {
+    postData('/tender/'+ tenderId +'/change/newCode', { type: rulesType }, function (code) {
+        if (code !== '') {
+            $('#bj-code').val(code);
+        }
+    });
+}
+
+class codeRuleSet {
+    constructor (obj) {
+        this.body = obj;
+        // 切换规则组件类型
+        $('.rule-change', obj).change(function () {
+            const codeType = this.selectedIndex-1;
+            if (codeType === ruleConst.ruleType.addNo) {
+                $('#format', obj).show();
+                $('#text', obj).show();
+                $('#text>label', obj).text('起始编号');
+                $('#text>input', obj).val('001');
+                const s = '0000000000' + 1;
+                $('#text>input', obj).val(s.substr(s.length - $('#format>input', obj).val()));
+            } else if (codeType === ruleConst.ruleType.text) {
+                $('#format', obj).hide();
+                $('#text', obj).show();
+                $('#text>label', obj).text('文本');
+                $('#text>input', obj).val('').attr('placeholder', '请在这里输入需要的文本');
+            } else {
+                $('#format', obj).hide();
+                $('#text', obj).hide();
+            }
+        });
+        // 修改编号位数
+        $('#format>input', obj).change(function () {
+            const s = '0000000000' + parseInt($('#text>input', obj).val());
+            $('#text>input', obj).val(s.substr(s.length - $(this).val()));
+        });
+
+        // 修改连接符
+        $('.connector-change', obj).change(function () {
+            const connectorType = this.options[this.selectedIndex].text;
+            const rules = $('span>span', obj), ruleText = [];
+            for (const r of rules) {
+                ruleText.push($.trim(r.innerText));
+            }
+            if (connectorType === '无') {
+                $('#preview', obj).text(ruleText.join(''));
+            } else {
+                $('#preview', obj).text(ruleText.join(connectorType));
+            }
+            connectorRule = this.options[this.selectedIndex].value;
+        });
+
+        // 新增规则组件
+        $('#addRule', obj).click(function () {
+            const codeType = $('select', obj)[1].selectedIndex-1;
+            const rule = {rule_type: codeType}, html = [];
+            let preview;
+            switch (codeType) {
+                case ruleConst.ruleType.dealCode: {
+                    if (dealCode === '') {
+                        toastr.error('当前标段合同编号为空,请选择其他组件。');
+                        return false;
+                    }
+                    preview = dealCode;
+                    break;
+                }
+                case ruleConst.ruleType.tenderName: {
+                    preview = tenderName;
+                    break;
+                }
+                case ruleConst.ruleType.text: {
+                    rule.text = $('#text>input', obj).val();
+                    if (rule.text === '') {
+                        toastr.error('文本内容不允许为空。');
+                        return false;
+                    }
+                    preview = rule.text;
+                    break;
+                }
+                case ruleConst.ruleType.inDate: {
+                    preview = moment().format('YYYY');
+                    break;
+                }
+                case ruleConst.ruleType.addNo: {
+                    rule.format = parseInt($('#format>input', obj).val());
+                    rule.start = parseInt($('#text>input', obj).val());
+                    if ($('#text>input', obj).val().length !== rule.format) {
+                        toastr.error('起始编号位数和自动编号位数不一致。');
+                        return false;
+                    }
+                    const s = '0000000000';
+                    preview = s.substr(s.length - rule.format);
+                    break;
+                }
+                default: {
+                    toastr.error('请选择组件再添加');
+                    return false;
+                }
+            }
+            // 更新规则
+            codeRule.push(rule);
+            // 更新规则显示
+            html.push('<span class="badge badge-light" title="' + ruleConst.ruleString[codeType] + '" rule="' + JSON.stringify(rule) + '">');
+            html.push('<span>' + preview + '</span>');
+            html.push('<a href="javascript: void(0);" class="text-danger" title="移除"><i class="fa fa-remove"></i></a>');
+            html.push('</span>');
+            const part = $('#ruleParts', obj).append(html.join(''));
+            // 更新规则预览
+            const connectorType = connectorRule !== '' && parseInt(connectorRule) !== ruleConst.connectorType.nothing ? ruleConst.connectorString[connectorRule] : '';
+            const previewtext = $.trim($('#preview', obj).text()) === '' ? preview : $.trim($('#preview', obj).text()) + connectorType + preview;
+            $('#preview', obj).text(previewtext);
+        });
+        // 删除规则组件
+        $($('#ruleParts', obj)).on('click', 'a', function () {
+            const index = $('a', obj).index(this);
+            codeRule.splice(index-1, 1);
+            $(this).parent().remove();
+            const rules = $('span>span', obj), ruleText = [];
+            for (const r of rules) {
+                ruleText.push($.trim(r.innerText));
+            }
+            const connectorType = connectorRule !== '' && parseInt(connectorRule) !== ruleConst.connectorType.nothing ? ruleConst.connectorString[connectorRule] : '';
+            $('#preview', obj).text(ruleText.join(connectorType));
+        });
+    }
+}
+
+$(document).ready(() => {
+    // 首次进入设置
+    let showNoNeed = false;
+    if (cRuleFirst) {
+        codeRule = [];
+        showNoNeed = true;
+        $('#setting').modal('show');
+    }
+    // else if ($('#changeList').children.length === 0) {
+    //     $('#add-bj').modal('show');
+    // }
+    // 设置
+    const ruleSet = new codeRuleSet($('div.modal-body', '#setting'));
+    $('#setRule', '#setting').bind('click', function () {
+        const data = {
+            rule: ruleType,
+            type: rulesType,
+            connector: connectorRule,
+            data: JSON.stringify(codeRule),
+        };
+        if (codeRule.length !== 0) {
+            $('#autoCodeShow').show();
+        }
+        postData('/tender/rule', data, function () {
+            if (cRuleFirst && showNoNeed) {
+                $('#changeFirst').click();
+                $('.ml-auto a[href="#add-bj"]').click();
+                // $('#add-bj-modal').modal('show');
+            } else {
+                $('#setting').modal('hide');
+            }
+        });
+    })
+    $('.ml-auto').on('click', 'a', function () {
+        const content = $(this).attr('href');
+        if (content === '#add-bj') {
+            $('#add-bj-modal').modal('show')
+                getNewCode();
+                if ($('#changeList').children.length === 0) {
+                    $('#addCancel').hide();
+                } else {
+                    $('#addCancel').show();
+                }
+                $('#bj-code').removeClass('is-invalid');
+        }
+    });
+
+    // 获取最新可用变更令号
+    $('#autoCode').click(getNewCode);
+    // 新增变更令 确认
+    $('#addOk').click(function () {
+        $(this).attr('disabled', true);
+        if (!openChangeApply && $('#bj-name').val().length === 0) {
+            $('#bj-name').addClass('is-invalid');
+            $('#name_error_msg').show();
+            $('#name_error_msg').text('变更工程名称不能为空。');
+            $(this).attr('disabled', false);
+            setTimeout(function () {
+                $('#bj-name').removeClass('is-invalid');
+                $('#name_error_msg').hide();
+            }, 2000);
+            return;
+        }
+        if (!openChangeApply && $('#bj-name').val().length > 100) {
+            $('#bj-name').addClass('is-invalid');
+            $('#name_error_msg').show();
+            $('#name_error_msg').text('名称超过100个字,请缩减名称。');
+            $(this).attr('disabled', false);
+            setTimeout(function () {
+                $('#bj-name').removeClass('is-invalid');
+                $('#name_error_msg').hide();
+            }, 2000);
+            return;
+        }
+        const data = {
+            code: $('#bj-code').val(),
+            apply_code: $('#apply-code').val(),
+            name: $('#bj-name').val(),
+        };
+        if (data.code || data.code !== '') {
+            postData('/tender/'+ tenderId +'/change/plan/add', data, function (rst) {
+                $('#bj-code').removeClass('is-invalid');
+                $('#mj-add').modal('hide');
+                $(this).attr('disabled', false);
+                window.location.href = '/tender/'+ tenderId +'/change/plan/' + rst.id + '/information';
+            }, function () {
+                $('#mj-code').addClass('is-invalid');
+                $('#mj-Hint').show();
+                $(this).attr('disabled', false);
+            });
+        }
+    });
+
+    $('#apply-code').change(function () {
+        if ($(this).val() === '') {
+            $('#bj-name').val('');
+        } else {
+            const info = _.find(changeApplyList, { code: $(this).val() });
+            $('#bj-name').val(info.name);
+        }
+    });
+
+    //状态切换
+    $('#status_select a').on('click', function () {
+       const status = $(this).data('val');
+       let url = '/tender/'+ tenderId +'/change/plan';
+       if (status !== 0) {
+           url += '/status/'+ status;
+       }
+       let orderSetting = getLocalCache('change-plan-'+ tenderId +'-list-order');
+       if (orderSetting) {
+           const orders = orderSetting.split('|');
+           url += '?sort=' + orders[0] + '&order=' + orders[1];
+       }
+       window.location.href = url;
+    });
+    // 不再显示首次使用
+    $('#changeFirst').click(function () {
+        showNoNeed = false;
+        $('#changeFirst').remove();
+        $('#hide_modal').show();
+        $('#setting').modal('hide');
+        postData('/tender/'+ tenderId +'/rule/first', { type: rulesType }, function () {
+        });
+    });
+
+    // 弹出删除变更框赋值
+    $('.delete-cpid-modal').on('click', function () {
+        $('#delete-cpid').val($(this).attr('cpid'));
+    });
+
+    // 排序初始化
+    let orderSetting = getLocalCache('change-plan-'+ tenderId +'-list-order');
+    if (!orderSetting) orderSetting = 'time|desc';
+    const orders = orderSetting.split('|');
+    $("#sort-radio input[value='"+ orders[0] +"']").prop('checked', true);
+    $("#order-radio input[value='"+ orders[1] +"']").prop('checked', true);
+    if (orders[0] === 'time') {
+        $('#bpaixu').text('排序:创建时间');
+    } else {
+        $('#bpaixu').text('排序:变更方案编号');
+    }
+    // let sortSetting = getLocalCache('change-'+ $('#tenderId').val() +'-list-sort');
+    // if (sortSetting && parseInt(sortSetting) === 1) {
+    //     $('#bpaixu').click();
+    // }
+    // $('#sort-dropdown').on('shown.bs.dropdown', function () {
+    //     setLocalCache('change-'+ $('#tenderId').val() +'-list-sort', 1);
+    // });
+    // $('#sort-dropdown').on('hidden.bs.dropdown', function () {
+    //     setLocalCache('change-'+ $('#tenderId').val() +'-list-sort', 0);
+    // });
+
+    $('#sort-radio input[name="paizhi"]').click(function () {
+        const orderStr = $(this).val() + '|' + $('#order-radio input[name="paixu"]:checked').val();
+        setLocalCache('change-plan-'+ tenderId +'-list-order', orderStr);
+        // setLocalCache('change-'+ $('#tenderId').val() +'-list-sort', 1);
+        const link = window.location.origin + window.location.pathname + '?sort='+ $(this).val() + '&order=' + $('#order-radio input[name="paixu"]:checked').val();
+        window.location.href = link;
+    });
+    $('#order-radio input[name="paixu"]').click(function () {
+        const orderStr = $('#sort-radio input[name="paizhi"]:checked').val() + '|' + $(this).val();
+        setLocalCache('change-plan-'+ tenderId +'-list-order', orderStr);
+        // setLocalCache('change-'+ $('#tenderId').val() +'-list-sort', 1);
+        const link = window.location.origin + window.location.pathname + '?sort='+ $('#sort-radio input[name="paizhi"]:checked').val() + '&order=' + $(this).val();
+        window.location.href = link;
+    })
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+});

+ 230 - 0
app/public/js/change_plan_audit.js

@@ -0,0 +1,230 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2019/2/27
+ * @version
+ */
+
+$(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><span
+                                class="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);
+    })
+
+    // 添加审批流程按钮逻辑
+    $('.book-list').on('click', '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
+    })
+
+    // 添加到审批流程中
+    $('dl').on('click', 'dd', function () {
+        const id = parseInt($(this).data('id'));
+        if (id) {
+            postData(preUrl + '/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" auditorId="'+ data.aid +'">');
+                        html.push('<a href="javascript: void(0)" class="text-danger pull-right">移除</a>');
+                        html.push('<span>');
+                        html.push(data.order + ' ');
+                        html.push(data.name + ' ');
+                        html.push('</span>');
+                        html.push('<small class="text-muted">');
+                        html.push(data.role);
+                        html.push('</small></li>');
+                    }
+                    // 添加新审批人流程修改
+                    auditorshtml.push('<li class="list-group-item" data-auditorid="' + data.aid + '">');
+                    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 li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
+                // for (let i = 0; i < $('#auditors-list li').length; i++) {
+                //     $('#auditors-list li').eq(i).find('.pull-right').text(transFormToChinese(i+1) + '审');
+                //     $('#auditors-list2 li').eq(i).find('.pull-right').text(transFormToChinese(i+1) + '审');
+                // }
+
+                $('#auditors-list').html(auditorshtml.join(''));
+
+                // const auditorshtml2 = [];
+                // // 重新上报时。令其它的审批人流程图标转换
+                // $('#auditors-list2 li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
+                // // 添加新审批人
+                // auditorshtml2.push('<li class="list-group-item" data-auditid="' + data.aid + '">');
+                // auditorshtml2.push('<h5 class="card-title"><i class="fa fa-stop-circle"></i> ');
+                // auditorshtml2.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
+                // auditorshtml2.push('<span class="pull-right">终审</span>');
+                // auditorshtml2.push('</h5></li>');
+                // $('#auditors-list2').append(auditorshtml2.join(''));
+            });
+        }
+    });
+    // 删除审批人
+    $('body').on('click', '#auditors li>a', function () {
+        const li = $(this).parent();
+        const data = {
+            auditorId: parseInt(li.attr('auditorId')),
+        };
+        postData(preUrl +  '/audit/delete', data, (result) => {
+            li.remove();
+            for (const rst of result) {
+                const aLi = $('li[auditorId=' + rst.aid + ']');
+                $('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');
+            }
+            // $('#auditors-list2 li[data-auditid="' + data.auditorId + '"]').remove();
+            // if ($('#auditors-list2 li').length !== 0 && !$('#auditors-list2 li i').hasClass('fa-stop-circle')) {
+            //     $('#auditors-list2 li').eq($('#auditors-list2 li').length-1).children('i')
+            //         .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
+            // }
+            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').eq(i).find('.pull-right').text((i+1 === $('#auditors-list2').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');
+    });
+});
+// 检查上报情况
+function checkAuditorFrom () {
+    if ($('#auditors li').length === 0) {
+        // if(shenpi_status === shenpiConst.sp_status.gdspl) {
+        //     toastr.error('请联系管理员添加审批人');
+        // } else {
+            toastr.error('请先选择审批人,再上报数据');
+        // }
+        return false;
+    }
+    let flag = false;
+    if (change.code === '') {
+        toastr.error('变更方案编号不能为空');
+        flag = true;
+    }
+    if (!change.name) {
+        toastr.error('变更工程名称不能为空');
+        flag = true;
+    }
+    if (!change.reason) {
+        toastr.error('变更原因不能为空');
+        flag = true;
+    }
+    // 判断变更清单是否有空值
+    const nullCodeList = _.filter(changeList, function(item) {
+        return item.code === null || item.code === '';
+    });
+    if (nullCodeList.length > 0) {
+        toastr.error('变更清单都需要设置清单编号才能上报');
+        flag = true;
+    }
+    if (flag) {
+        return false;
+    }
+    $('#hide-all').show();
+}
+// texterea换行
+function auditCheck(i) {
+    // const inlineRadio1 = $('#inlineRadio1:checked').val()
+    // const inlineRadio2 = $('#inlineRadio2:checked').val()
+    const opinion = $('textarea[name="opinion"]').eq(i).val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' ');
+    $('textarea[name="opinion"]').eq(i).val(opinion);
+    // if (i === 1) {
+    //     if (!inlineRadio1 && !inlineRadio2) {
+    //         if (!$('#warning-text').length) {
+    //             $('#reject-process').prepend('<p id="warning-text" style="color: red; margin: 0;">请选择退回流程</p>');
+    //         }
+    //         return false;
+    //     }
+    //     if ($('#warning-text').length) $('#warning-text').remove()
+    // }
+
+    return true;
+}

+ 923 - 0
app/public/js/change_plan_information.js

@@ -0,0 +1,923 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author EllisRan
+ * @date 2022/01/21
+ * @version
+ */
+
+$(document).ready(() => {
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+
+    handleFileList(fileList);
+
+    $('#file-ok').click(function () {
+        const files = Array.from($('#file-modal')[0].files)
+        const valiData = files.map(v => {
+            const ext = v.name.substring(v.name.lastIndexOf('.') + 1)
+            return {
+                size: v.size,
+                ext
+            }
+        });
+        if (validateFiles(valiData)) {
+            if (files.length) {
+                const formData = new FormData();
+                files.forEach(file => {
+                    formData.append('name', file.name);
+                    formData.append('size', file.size);
+                    formData.append('file', file);
+                })
+                postDataWithFile(preUrl + '/file/upload', formData, function (result) {
+                    handleFileList(result);
+                    $('#file-cancel').click();
+                });
+            }
+        }
+    })
+    function handleFileList(files = []) {
+        $('#file-content').empty();
+        // const { uncheck, checkNo } = auditConst.status
+        const newFiles = files.map(file => {
+            let showDel = false;
+            if (file.uid === cur_uid) {
+                // if (!curAuditor) {
+                //     advance.status === uncheck && cur_uid === advance.uid && (showDel = true)
+                //     advance.status === checkNo && cur_uid === advance.uid && (showDel = true)
+                // } else {
+                //     curAuditor.audit_id === cur_uid && (showDel = true)
+                // }
+                if (change.status === auditConst.status.checked) {
+                    showDel = Boolean(file.extra_upload )
+                } else {
+                    showDel = true
+                }
+            }
+            return {...file, showDel}
+        })
+        let html = change.filePermission ? `<tr><td colspan="5"><a href="#addfujian" data-toggle="modal" class="btn btn-primary btn-sm" data-placement="bottom" title="">上传附件</a></td></tr>` : '';
+        newFiles.forEach((file, idx) => {
+            if (file.showDel) {
+                html += `<tr><td>${idx + 1}</td><td><a href="${file.filepath}" target="_blank">${file.filename}</a></td><td>${file.username}</td><td>${moment(file.upload_time).format('YYYY-MM-DD HH:mm:ss')}</td><td><a href="/tender/${file.tid}/change/plan/${file.cpid}/information/file/${file.id}/download" class="mr-2"><i class="fa fa-download"></i></a><a href="javascript: void(0);" class="text-danger file-del" data-id="${file.id}"><i class="fa fa-remove"></i></a></td></tr>`
+            } else {
+                html += `<tr><td width="70">${idx + 1}</td><td><a href="${file.filepath}" target="_blank">${file.filename}</a></td><td>${file.username}</td><td>${moment(file.upload_time).format('YYYY-MM-DD HH:mm:ss')}</td><td><a href="/tender/${file.tid}/change/plan/${file.cpid}/information/file/${file.id}/download" class="mr-2"><i class="fa fa-download"></i></a></td></tr>`
+            }
+        })
+        $('#file-content').append(html);
+    }
+
+    $('#file-content').on('click', 'a', function () {
+        if ($(this).hasClass('file-del')) {
+            const id = $(this).data('id');
+            postData(preUrl + '/file/delete', {id}, (result) => {
+                handleFileList(result);
+            })
+        }
+    });
+
+    // 回车提交
+    $('#plan-table input').on('keypress', function () {
+        if(window.event.keyCode === 13) {
+            $(this).blur();
+        }
+    });
+
+    $('#plan-table input').blur(function () {
+        const val_name = $(this).data('name');
+        let val = _.trim($(this).val()) !== '' ? _.trim($(this).val()) : null;
+        switch(val_name) {
+            case 'code':
+                if(!val) {
+                    toastr.error('方案编号不能为空');
+                    $(this).val(change[val_name]);
+                    return false;
+                }
+                break;
+            case 'name':
+                if(val && val.length > 100) {
+                    toastr.error('名称超过100个字,请缩减名称');
+                    $(this).val(change[val_name]);
+                    return false;
+                }
+                break;
+            default:
+                if(val && val.length > 255) {
+                    toastr.error('超出字段范围,请缩减');
+                    $(this).val(change[val_name]);
+                    return false;
+                }
+                break;
+        }
+        if(change[val_name] !== val) {
+            const _self = $(this);
+            postData(preUrl + '/save', { name: val_name, val}, function (result) {
+                change[val_name] = val;
+                _self.val(change[val_name]);
+                if (val_name === 'code') {
+                    $('#change-plan-code').text(change[val_name]);
+                }
+            }, function () {
+                _self.val(change[val_name]);
+            })
+        } else {
+            $(this).val(change[val_name]);
+        }
+    });
+
+    $('#plan-table textarea').blur(function () {
+        const val_name = $(this).data('name');
+        let val = _.trim($(this).val()) !== '' ? _.trim($(this).val()) : null;
+        if(change[val_name] !== val) {
+            const _self = $(this);
+            postData(preUrl + '/save', { name: val_name, val}, function (result) {
+                change[val_name] = val;
+                _self.val(change[val_name]);
+            }, function () {
+                _self.val(change[val_name]);
+            })
+        } else {
+            $(this).val(change[val_name]);
+        }
+    });
+
+    $('#plan-table select').change(function () {
+        const val_name = $(this).attr('data-name');
+        let val = _.trim($(this).val()) !== '' ? _.trim($(this).val()) : null;
+        if(change[val_name] !== val) {
+            const _self = $(this);
+            postData(preUrl + '/save', { name: val_name, val}, function (result) {
+                change[val_name] = val;
+                _self.val(change[val_name]);
+            }, function () {
+                _self.val(change[val_name]);
+            })
+        } else {
+            $(this).val(change[val_name]);
+        }
+    });
+
+    const changeSpread = SpreadJsObj.createNewSpread($('#plan-spread')[0]);
+    const changeSpreadSheet = changeSpread.getActiveSheet();
+    const style1 = new GC.Spread.Sheets.Style();
+    style1.locked = true;
+
+    const changeSpreadSetting = {
+        cols: [
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 110, formatter: '@', readOnly: 'readOnly.isEdit'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 130, formatter: '@', readOnly: 'readOnly.isEdit'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: 'readOnly.isEdit', cellType: 'unit', comboItems: changeUnits, comboEdit: true},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.isEdit', getValue: 'getValue.unit_price'},
+            {title: '原设计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'oamount', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.isEdit', getValue: 'getValue.oamount'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'oa_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, getValue: 'getValue.oa_tp'},
+            {title: '申请变更增(+)减(-)|数量', colSpan: '2|1', rowSpan: '1|1', field: 'camount', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.isEdit', getValue: 'getValue.camount'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'ca_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, getValue: 'getValue.ca_tp'},
+            {title: '审批后变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'samount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.samount'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sa_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, getValue: 'getValue.sa_tp'},
+        ],
+        emptyRows: !readOnly ? 3 : 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: change.status === auditConst.status.checking ? false : readOnly,
+        localCache: {
+            key: 'changes-plan-list-spread',
+            colWidth: true,
+        }
+    };
+    console.log(changeList, change.listAudits);
+
+    if (change.status === auditConst.status.checking || change.status === auditConst.status.checked) {
+        for (const audit of change.listAudits) {
+            // const userinfo = _.find(auditList2, { 'aid': aid });
+            const newColcount = {
+                title: audit.name + ' 审批|数量',
+                colSpan: '2|1', rowSpan: '1|1',
+                field: 'audit_amount_' + audit.aid,
+                hAlign: 2, width: 60, type: 'Number',
+                readOnly: !(change.shenpiPower && audit.aid === parseInt(cur_uid))
+            };
+            const newColTp = {
+                title: '|金额',
+                colSpan: '|1', rowSpan: '|1',
+                field: 'sa_tp_' + audit.aid,
+                hAlign: 2, width: 80, type: 'Number',
+                readOnly: true
+            };
+            changeSpreadSetting.cols.push(newColcount);
+            changeSpreadSetting.cols.push(newColTp);
+        }
+    }
+
+    const changeCol = {
+        getValue: {
+            unit_price: function(data) {
+                return ZhCalc.round(data.unit_price, unitPriceUnit);
+            },
+            oa_tp: function (data) {
+                return ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.oamount, findDecimal(data.unit))), totalPriceUnit);
+            },
+            ca_tp: function (data) {
+                return ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.camount, findDecimal(data.unit))), totalPriceUnit);
+            },
+            oamount: function (data) {
+                return ZhCalc.round(data.oamount, findDecimal(data.unit));
+            },
+            camount: function (data) {
+                return ZhCalc.round(data.camount, findDecimal(data.unit));
+            },
+            samount: function (data) {
+                return ZhCalc.round(data.samount, findDecimal(data.unit));
+            },
+            sa_tp: function (data) {
+                return ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.samount, findDecimal(data.unit))), totalPriceUnit);
+            },
+        },
+        readOnly: {
+            isEdit: function (data) {
+                return readOnly;
+            },
+        },
+    };
+    const changeSpreadObj = {
+        makeSjsFooter: function() {
+            // 增加汇总行并设为锁定禁止编辑状态
+            changeSpreadSheet.addRows(changeSpreadSheet.getRowCount(), 1);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 0, '合计');
+            changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
+            changeSpreadObj.countSum();
+        },
+        setAuditValue: function () {
+            for (const c of changeList) {
+                for (const audit of change.listAudits) {
+                    c['audit_amount_' + audit.aid] = ZhCalc.round(c['audit_amount_' + audit.aid], findDecimal(c.unit));
+                    c['sa_tp_' + audit.aid] = ZhCalc.round(ZhCalc.mul(c['audit_amount_' + audit.aid], ZhCalc.round(c.unit_price, unitPriceUnit)), totalPriceUnit);
+                }
+            }
+        },
+        setRowValueAndSum: function (data, row, col) {
+            for (const j in change.listAudits) {
+                const sum = ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), parseFloat(changeSpreadSheet.getValue(row, 10 + parseInt(j)*2))), totalPriceUnit);
+                changeSpreadSheet.setValue(row, 11 + j*2, sum !== 0 ? sum : null);
+            }
+            // const sum = ZhCalc.round(ZhCalc.mul(data.unit_price, data.spamount), totalPriceUnit);
+            // changeSpreadSheet.setValue(row, col+1, sum !== 0 ? sum : null);
+            const rowCount = changeSpreadSheet.getRowCount();
+            // 用户的数据合计
+            let audit_sum = 0;
+            for(let i = 0; i < rowCount - 1; i++){
+                audit_sum = ZhCalc.add(audit_sum, changeSpreadSheet.getValue(i, col+1));
+            }
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, col+1, audit_sum !== 0 ? audit_sum : null);
+        },
+        countSum: function() {
+            const rowCount = changeSpreadSheet.getRowCount();
+            let oSum = 0,
+                cSum = 0,
+                sSum = 0;
+            for (let i = 0; i < rowCount - 1; i++) {
+                oSum = ZhCalc.add(oSum, changeSpreadSheet.getValue(i, 5));
+                cSum = ZhCalc.add(cSum, changeSpreadSheet.getValue(i, 7));
+                sSum = ZhCalc.add(sSum, changeSpreadSheet.getValue(i, 9));
+            }
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 5, oSum !== 0 ? oSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 7, cSum !== 0 ? cSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 9, sSum !== 0 ? sSum : null);
+            // 用户的数据合计
+            for (const j in change.listAudits) {
+                let audit_sum = 0;
+                for(let i = 0; i < rowCount - 1; i++){
+                    audit_sum = ZhCalc.add(audit_sum, changeSpreadSheet.getValue(i, 11 + j*2));
+                }
+                changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 11 + j*2, audit_sum !== 0 ? audit_sum : null);
+            }
+        },
+        deletePress: function (sheet) {
+            return;
+        },
+        showHideAudit: function (show = false) {
+            const count = changeSpreadSetting.cols.length;
+            for (let i = 10; i < count; i++) {
+                changeSpreadSheet.setColumnVisible(i, show, GC.Spread.Sheets.SheetArea.viewport);
+            }
+            changeSpreadSheet.setColumnVisible(8, !show, GC.Spread.Sheets.SheetArea.viewport);
+            changeSpreadSheet.setColumnVisible(9, !show, GC.Spread.Sheets.SheetArea.viewport);
+        },
+        valueChanged: function (e, info) {
+            // 防止ctrl+z撤销数据
+            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+        }
+    };
+    if (!readOnly) {
+        changeSpreadObj.add = function () {
+            changeSpreadSheet.addRows(changeSpreadSheet.getRowCount() - 1, 1);
+            changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
+            // changeSpreadSheet.setSelection(changeList.length - 1, 0, 1, 1);
+        };
+        changeSpreadObj.batchAdd = function (num) {
+            changeSpreadSheet.addRows(changeSpreadSheet.getRowCount() - 1, parseInt(num));
+            changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
+        };
+        changeSpreadObj.del = function (sheet) {
+            const selection = sheet.getSelections();
+            const row = selection[0].row, count = selection[0].rowCount;
+            const sortData = sheet.zh_data;
+            const ids = [];
+            let emptyRowNum = 0;
+            for (let iRow = 0; iRow < count; iRow++) {
+                if (sortData[iRow + row]) {
+                    ids.push(sortData[iRow + row].id);
+                } else {
+                    emptyRowNum++;
+                }
+            }
+            if (ids.length > 0) {
+                postData(preUrl + '/list/save', {type: 'del', ids}, function (result) {
+                    changeList = result;
+                    SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+                    changeSpreadObj.makeSjsFooter();
+                });
+            } else if (emptyRowNum > 0) {
+                changeSpreadSheet.deleteRows(changeList.length, emptyRowNum);
+                changeSpreadSheet.setSelection(changeList.length - 1, 0, 1, 1);
+            }
+        };
+        changeSpreadObj.editEnded = function (e, info) {
+            if (info.sheet.zh_setting) {
+                const type = SpreadJsObj.getSelectObject(info.sheet) ? 'update' : 'add';
+                const select = type === 'update' ? SpreadJsObj.getSelectObject(info.sheet) : {unit: ''};
+                const col = info.sheet.zh_setting.cols[info.col];
+                // 未改变值则不提交
+                let validText = is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : '');
+                const orgValue = type === 'update' ? select[col.field] : '';
+                if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                if (col.field === 'oa_tp' || col.field === 'ca_tp') {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                // 判断部分值是否输入的是数字判断和数据计算
+                if (col.type === 'Number') {
+                    if (isNaN(validText)) {
+                        toastr.error('不能输入其它非数字类型字符');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    if (col.field === 'unit_price') {
+                        validText = ZhCalc.round(validText, unitPriceUnit);
+                    } else {
+                        validText = ZhCalc.round(validText, findDecimal(select.unit)) || 0;
+                    }
+                }
+                if (col.field === 'unit') {
+                    select.camount = ZhCalc.round(select.camount, findDecimal(validText)) || 0;
+                    select.oamount = ZhCalc.round(select.oamount, findDecimal(validText)) || 0;
+                }
+                if(col.field === 'camount') {
+                    select.spamount = ZhCalc.round(validText, findDecimal(select.unit)) || 0;
+                }
+                select[col.field] = validText;
+
+                console.log(select, type);
+                delete select.waitingLoading;
+
+                // 更新至服务器
+                postData(preUrl + '/list/save', { type, updateData: select }, function (result) {
+                    if(type === 'update') {
+                        changeList.splice(info.row, 1, select);
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        changeSpreadObj.countSum();
+                    } else {
+                        changeList.push(result);
+                        changeSpreadSheet.addRows(changeList.length - 1, 1);
+                        SpreadJsObj.reLoadRowData(changeSpreadSheet, changeList.length - 1);
+                        changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
+                        // changeSpreadSheet.setSelection(changeList.length - 1, 0, 1, 1);
+                    }
+                }, function () {
+                    select[col.field] = orgValue;
+                    if(col.field === 'camount') {
+                        select.spamount = orgValue;
+                    }
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                });
+            }
+        };
+        changeSpreadObj.clipboardPasted  = function(e, info, cellRange) {
+            if (info.sheet.getColumnCount() > info.sheet.zh_setting.cols.length) {
+                info.sheet.setColumnCount(info.sheet.zh_setting.cols.length);
+            }
+            const hint = {
+                cellError: {type: 'error', msg: '粘贴内容超出了表格范围'},
+                numberExpr: {type: 'error', msg: '不能粘贴其它非数字类型字符'},
+            };
+            if (info.sheet.zh_setting) {
+                const sortData = info.sheet.zh_data || [];
+                const range = info.cellRange;
+                const data = [];
+                for (let iRow = 0; iRow < range.rowCount; iRow++) {
+                    let bPaste = true;
+                    const curRow = range.row + iRow;
+                    // const materialData = JSON.parse(JSON.stringify(sortData[curRow]));
+                    const cLData = curRow >= sortData.length ? {unit: ''} : {id: sortData[curRow].id};
+                    const hintRow = range.rowCount > 1 ? curRow : '';
+                    let sameCol = 0;
+                    for (let iCol = 0; iCol < range.colCount; iCol++) {
+                        const curCol = range.col + iCol;
+                        const colSetting = info.sheet.zh_setting.cols[curCol];
+                        if (!colSetting) continue;
+                        // cLData[colSetting.field] = trimInvalidChar(info.sheet.getText(curRow, curCol));
+
+                        let validText = info.sheet.getText(curRow, curCol);
+                        validText = is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : '');
+                        const orgValue = curRow >= sortData.length ? '' : sortData[curRow][colSetting.field];
+                        if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                            sameCol++;
+                            if (range.colCount === sameCol) {
+                                bPaste = false;
+                            }
+                            continue;
+                        }
+
+                        if (colSetting.type === 'Number') {
+                            if (isNaN(validText)) {
+                                // toastMessageUniq(getPasteHint(hint.numberExpr, hintRow));
+                                toastMessageUniq(hint.numberExpr);
+                                bPaste = false;
+                                continue;
+                            }
+                            if (colSetting.field === 'unit_price') {
+                                validText = ZhCalc.round(validText, unitPriceUnit);
+                            } else {
+                                validText = ZhCalc.round(validText, findDecimal(cLData.unit)) || 0;
+                            }
+                        }
+                        let unitdecimal = validText;
+                        if (colSetting.field === 'unit') {
+                            //粘贴内容要为下拉列表里所有的单位,不然为空
+                            if (changeUnits.indexOf(validText) === -1) {
+                                unitdecimal = '';
+                                // validText = null;
+                            }
+                            cLData.camount = curRow >= sortData.length ? 0 : ZhCalc.round(sortData[curRow].camount, findDecimal(unitdecimal)) || 0;
+                            cLData.oamount = curRow >= sortData.length ? 0 : ZhCalc.round(sortData[curRow].oamount, findDecimal(unitdecimal)) || 0;
+                            cLData.spamount = curRow >= sortData.length ? 0 : ZhCalc.round(sortData[curRow].camount, findDecimal(unitdecimal)) || 0;
+                        }
+                        // sortData[curRow][colSetting.field] = validText;
+                        if (colSetting.field === 'camount') {
+                            cLData.spamount = ZhCalc.round(validText, findDecimal(unitdecimal)) || 0;
+                        }
+                        cLData[colSetting.field] = validText;
+
+                    }
+                    if (bPaste) {
+                        delete cLData.oa_tp;
+                        delete cLData.ca_tp;
+                        data.push(cLData);
+                        // rowData.push(curRow);
+                    } else {
+                        SpreadJsObj.reLoadRowData(info.sheet, curRow);
+                    }
+                }
+                if (data.length === 0) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                    return;
+                }
+                console.log(data);
+                // 更新至服务器
+                postData(preUrl + '/list/save', { type:'paste', updateData: data }, function (result) {
+                    changeList = result;
+                    SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+                    changeSpreadObj.makeSjsFooter();
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                    return;
+                });
+            }
+        };
+
+        changeSpreadObj.updateOamount = function () {
+            const dataSource = listRule.source === 1 ? gclGatherData : dealBillList;
+            const updateList = [];
+            for (const c of changeList) {
+                const source = _.find(dataSource, function (item) {
+                    if (((item.b_code && item.b_code === c.code) || (item.code && item.code === c.code)) && item.name === c.name) {
+                        if (listRule.rule.length > 0) {
+                            for(const r of listRule.rule) {
+                                if (item[r] !== c[r]) {
+                                    return false;
+                                }
+                            }
+                        }
+                        return true;
+                    }
+                    return false;
+                });
+                if (source && source.quantity !== c.oamount) {
+                    updateList.push({ id: c.id, oamount: source.quantity });
+                }
+            }
+            console.log(updateList);
+            if(updateList.length > 0) {
+                postData(preUrl + '/list/save', { type:'paste', updateData: updateList }, function (result) {
+                    changeList = result;
+                    SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+                    changeSpreadObj.makeSjsFooter();
+                });
+            }
+        };
+        // changeSpread.bind(spreadNS.Events.CellChanged, changeSpreadObj.cellChanged);
+        changeSpread.bind(spreadNS.Events.EditEnded, changeSpreadObj.editEnded);
+        changeSpread.bind(spreadNS.Events.ClipboardPasted, changeSpreadObj.clipboardPasted);
+        changeSpread.bind(spreadNS.Events.ValueChanged, changeSpreadObj.valueChanged);
+        SpreadJsObj.addDeleteBind(changeSpread, changeSpreadObj.deletePress);
+
+        let batchInsertObj;
+        $.contextMenu.types.batchInsert = function (item, opt, root) {
+            const self = this;
+            if ($.isFunction(item.icon)) {
+                item._icon = item.icon.call(this, this, $t, key, item);
+            } else {
+                if (typeof(item.icon) === 'string' && item.icon.substring(0, 3) === 'fa-') {
+                    // to enable font awesome
+                    item._icon = root.classNames.icon + ' ' + root.classNames.icon + '--fa fa ' + item.icon;
+                } else {
+                    item._icon = root.classNames.icon + ' ' + root.classNames.icon + '-' + item.icon;
+                }
+            }
+            this.addClass(item._icon);
+            const $obj = $('<div>' + item.name + '<input class="text-right ml-1 mr-1" type="tel" max="100" min="1" value="' + item.value + '" style="width: 30px; height: 18px; padding-right: 4px;">行</div>')
+                .appendTo(this);
+            const $input = $obj.find('input');
+            const event = () => {
+                if (self.hasClass('context-menu-disabled')) return;
+                item.batchInsert($input[0], root);
+            };
+            $obj.on('click', event).keypress(function (e) {if (e.keyCode === 13) { event(); }});
+            $input.click((e) => {e.stopPropagation();})
+                .keyup((e) => {if (e.keyCode === 13) item.batchInsert($input[0], root);})
+                .on('input', function () {this.value = this.value.replace(/[^\d]/g, '');});
+        };
+
+        // 右键菜单
+        $.contextMenu({
+            selector: '#plan-spread',
+            build: function ($trigger, e) {
+                const target = SpreadJsObj.safeRightClickSelection($trigger, e, changeSpread);
+                return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
+            },
+            items: {
+                'updateOamount': {
+                    name: '原设计数量读取',
+                    icon: '',
+                    callback: function (key, opt) {
+                        changeSpreadObj.updateOamount(changeSpreadSheet);
+                    },
+                    disabled: function (key, opt) {
+                        // const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+                        // const sel = changeSpreadSheet.getSelections()[0];
+                        // // console.log(select, sel);
+                        // if (!readOnly && select && sel.row !== changeSpreadSheet.getRowCount() - 1) {
+                        //     return false;
+                        // } else {
+                        //     return true;
+                        // }
+                    }
+                },
+                sprDel: '------------',
+                'createAdd': {
+                    name: '添加行',
+                    icon: 'fa-sign-in',
+                    callback: function (key, opt) {
+                        changeSpreadObj.add(changeSpreadSheet);
+                    },
+                },
+                'batchInsert': {
+                    name: '批量添加行',
+                    type: 'batchInsert',
+                    value: '2',
+                    icon: 'fa-sign-in',
+                    batchInsert: function (obj, root) {
+                        if (_.toNumber(obj.value) > _.toNumber(obj.max)) {
+                            obj.value = obj.max;
+                            toastr.warning('批量添加不可多于' + obj.max);
+                        } else if(_.toNumber(obj.value) < _.toNumber(obj.min)) {
+                            obj.value = obj.min;
+                            toastr.warning('批量添加不可少于' + obj.min);
+                        } else {
+                            // treeOperationObj.addNode(ledgerSpread.getActiveSheet(), parseInt(obj.value));
+                            changeSpreadObj.batchAdd(obj.value);
+                            root.$menu.trigger('contextmenu:hide');
+                        }
+                    },
+                },
+                'delete': {
+                    name: '删除',
+                    icon: 'fa-remove',
+                    callback: function (key, opt) {
+                        changeSpreadObj.del(changeSpreadSheet);
+                    },
+                    disabled: function (key, opt) {
+                        // const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+                        if (changeSpreadSheet.zh_data) {
+                            const selection = changeSpreadSheet.getSelections();
+                            // return changeSpreadSheet.zh_data.length < selection[0].row + selection[0].rowCount;
+                            return changeSpreadSheet.getRowCount() - 1 < selection[0].row + selection[0].rowCount;
+                        } else {
+                            return true;
+                        }
+                        // const sel = changeSpreadSheet.getSelections()[0];
+                        // // console.log(select, sel);
+                        // if (!readOnly && select && sel.row !== changeSpreadSheet.getRowCount() - 1) {
+                        //     return false;
+                        // } else {
+                        //     return true;
+                        // }
+                    }
+                },
+            }
+        });
+
+        $('#shuliangguize').on('show.bs.modal', function () {
+            $('#shuliangguize input[name="data_source"][value="'+ listRule.source +'"]').prop('checked', true);
+            $('#shuliangguize input[name="data_rule"]').prop('checked', false);
+            for(const r of listRule.rule) {
+                $('#shuliangguize input[name="data_rule"][value="' + r +'"]').prop('checked', true);
+            }
+        });
+
+        // 设置原设计数量读取
+        $('#setListRule').click(function () {
+            const rule = [];
+            $('#shuliangguize input[name="data_rule"]:checked').each(function () {
+                rule.push($(this).val());
+            });
+            const newListRule = {
+                source: parseInt($('#shuliangguize input[name="data_source"]:checked').val()),
+                rule,
+            };
+            postData(preUrl + '/list/save', { type: 'list_rule', postData: JSON.stringify(newListRule) }, function (result) {
+                listRule = newListRule;
+                $('#shuliangguize').modal('hide');
+            });
+        })
+    }
+
+    if (change.shenpiPower) {
+        changeSpreadObj.editEnded = function (e, info) {
+            if (info.sheet.zh_setting) {
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                // 未改变值则不提交
+                let validText = is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : '');
+                const orgValue = select[col.field];
+                if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    changeSpreadObj.setRowValueAndSum(select, info.row, info.col);
+                    return;
+                }
+                // 判断部分值是否输入的是数字判断和数据计算
+                if (col.type === 'Number') {
+                    if (isNaN(validText)) {
+                        toastr.error('不能输入其它非数字类型字符');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        changeSpreadObj.setRowValueAndSum(select, info.row, info.col);
+                        return;
+                    }
+                    validText = ZhCalc.round(validText, findDecimal(select.unit)) || 0;
+                    // 判断是否 正数必须大于等于限制值,负数必须小于等于限制值,否则无法更改
+                }
+                select[col.field] = validText;
+                select.spamount = ZhCalc.round(validText, findDecimal(select.unit)) || 0;
+
+                const data = {
+                    id: select.id,
+                    spamount: select.spamount,
+                };
+                console.log(data);
+
+                // 更新至服务器
+                postData(preUrl + '/list/save', { type:'update', updateData: data }, function (result) {
+                    changeList.splice(info.row, 1, select);
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    changeSpreadObj.setRowValueAndSum(select, info.row, info.col);
+                }, function () {
+                    select[col.field] = orgValue;
+                    select.spamount = orgValue;
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    changeSpreadObj.setRowValueAndSum(select, info.row, info.col);
+                });
+            }
+        };
+        changeSpreadObj.clipboardPasted = function(e, info) {
+            const hint = {
+                cellError: {type: 'error', msg: '粘贴内容超出了表格范围'},
+                numberExpr: {type: 'error', msg: '不能粘贴其它非数字类型字符'},
+            };
+            const range = info.cellRange;
+            const sortData = info.sheet.zh_data || [];
+            const data = [];
+            for (let iRow = 0; iRow < range.rowCount; iRow++) {
+                let bPaste = true;
+                const curRow = range.row + iRow;
+                // const materialData = JSON.parse(JSON.stringify(sortData[curRow]));
+                const cLData = { id: sortData[curRow].id };
+                const hintRow = range.rowCount > 1 ? curRow : '';
+                let sameCol = 0;
+                for (let iCol = 0; iCol < range.colCount; iCol++) {
+                    const curCol = range.col + iCol;
+                    const colSetting = info.sheet.zh_setting.cols[curCol];
+                    if (!colSetting) continue;
+
+                    let validText = info.sheet.getText(curRow, curCol);
+                    validText = is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : null);
+                    const orgValue = sortData[curRow][colSetting.field];
+                    if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                        sameCol++;
+                        if (range.colCount === sameCol)  {
+                            bPaste = false;
+                        }
+                        continue;
+                    }
+                    if (colSetting.type === 'Number') {
+                        if (isNaN(validText)) {
+                            toastMessageUniq(hint.numberExpr);
+                            bPaste = false;
+                            continue;
+                        }
+                        validText = ZhCalc.round(validText, findDecimal(sortData[curRow].unit)) || 0;
+                    }
+                    // cLData[colSetting.field] = validText;
+                    sortData[curRow][colSetting.field] = validText;
+                    cLData.spamount = validText;
+                }
+                if (bPaste) {
+                    data.push(cLData);
+                    // rowData.push(curRow);
+                } else {
+                    changeSpreadObj.setAuditValue();
+                    SpreadJsObj.reLoadRowData(info.sheet, curRow);
+                    // SpreadJsObj.reLoadRowData(info.sheet, curRow);
+                }
+            }
+            if (data.length === 0) {
+                // SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                return;
+            }
+            console.log(data);
+            // 更新至服务器
+            postData(preUrl + '/list/save', { type:'paste_amount_rows', updateData: data }, function (result) {
+                changeList = result;
+                changeSpreadObj.setAuditValue();
+                SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+                changeSpreadObj.makeSjsFooter();
+            }, function () {
+                changeSpreadObj.setAuditValue();
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                return;
+            });
+        };
+        changeSpread.bind(spreadNS.Events.EditEnded, changeSpreadObj.editEnded);
+        changeSpread.bind(spreadNS.Events.ClipboardPasted, changeSpreadObj.clipboardPasted);
+        changeSpread.bind(spreadNS.Events.ValueChanged, changeSpreadObj.valueChanged);
+        SpreadJsObj.addDeleteBind(changeSpread, changeSpreadObj.deletePress);
+    }
+
+    let changeListData;
+    let gclGatherData;
+    let dealBillList;
+    const billUrl = window.location.pathname.split('/').slice(0, 4).join('/');
+    postData(billUrl + '/defaultBills', {}, function (result) {
+        gclGatherModel.loadLedgerData(result.bills);
+        gclGatherModel.loadPosData(result.pos);
+
+        gclGatherData = gclGatherModel.gatherGclData();
+        gclGatherData = _.filter(gclGatherData, function (item) {
+            return item.leafXmjs && item.leafXmjs.length !== 0;
+        });
+        // 数组去重
+        dealBillList = result.dealBills;
+        changeListData = gclGatherData;
+        console.log(changeListData, dealBillList);
+        SpreadJsObj.initSpreadSettingEvents(changeSpreadSetting, changeCol);
+        SpreadJsObj.initSheet(changeSpreadSheet, changeSpreadSetting);
+        if (change.status === auditConst.status.checking || change.status === auditConst.status.checked) changeSpreadObj.setAuditValue();
+        SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+        changeSpreadObj.makeSjsFooter();
+        // 计算最新的变更总额和change的total_price是否一致,不一致则更新
+        if (change.status !== auditConst.status.checked) {
+            // let new_tp = 0;
+            const updateArray = [];
+            for (const c of changeList) {
+                const oneUpdate = {};
+                if (ZhCalc.round(c.spamount, findDecimal(c.unit)) !== c.spamount) oneUpdate.spamount = ZhCalc.round(c.spamount, findDecimal(c.unit));
+                if (ZhCalc.round(c.oamount, findDecimal(c.unit)) !== c.oamount) oneUpdate.oamount = ZhCalc.round(c.oamount, findDecimal(c.unit));
+                if (ZhCalc.round(c.camount, findDecimal(c.unit)) !== c.camount) oneUpdate.camount = ZhCalc.round(c.camount, findDecimal(c.unit));
+                if (ZhCalc.round(c.unit_price, unitPriceUnit) !== c.unit_price) oneUpdate.unit_price = ZhCalc.round(c.unit_price, unitPriceUnit);
+                const audit_amount = c.audit_amount ? c.audit_amount.split(',') : null;
+                if (audit_amount) {
+                    for (const i in audit_amount) {
+                        if (ZhCalc.round(parseFloat(audit_amount[i]), findDecimal(c.unit)) !== parseFloat(audit_amount[i])) audit_amount[i] = ZhCalc.round(parseFloat(audit_amount[i]), findDecimal(c.unit));
+                    }
+                    if (c.audit_amount !== audit_amount.join(',')) oneUpdate.audit_amount = audit_amount.join(',');
+                }
+                if (!_.isEmpty(oneUpdate)) {
+                    oneUpdate.id = c.id;
+                    updateArray.push(oneUpdate);
+                }
+                // new_tp = ZhCalc.add(new_tp, ZhCalc.round(ZhCalc.mul(ZhCalc.round(c.spamount, findDecimal(c.unit)), ZhCalc.round(c.unit_price, unitPriceUnit)), totalPriceUnit));
+            }
+            console.log(updateArray);
+            if (updateArray.length > 0) {
+                // 更新至服务器
+                postData(preUrl + '/list/save', { type:'paste', updateData: updateArray }, function (result) {
+                    changeList = result;
+                    if (change.status === auditConst.status.checking) changeSpreadObj.setAuditValue();
+                    SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+                    changeSpreadObj.makeSjsFooter();
+                });
+            }
+            // if (change.total_price && change.total_price !== new_tp) {
+            //     postData(preUrl + '/list/save', {type: 'update_tp', updateData: new_tp}, function (result) {
+            //     });
+            // }
+            if (change.status === auditConst.status.checked) {
+                changeSpreadObj.showHideAudit(false);
+            } else {
+                changeSpreadObj.showHideAudit(true);
+            }
+        }
+
+
+    });
+
+    // 审批流程展示与隐藏
+    $('#show-table-detail').on('click', function (e) {
+        if($(e.target).is('label')){
+            return;
+        }
+        changeSpreadObj.showHideAudit($(this).is(':checked'));
+    });
+});
+
+/**
+ * 校验文件大小、格式
+ * @param {Array} files 文件数组
+ */
+function validateFiles(files) {
+    if (files.length > 10) {
+        toastr.error('至多同时上传10个文件');
+        return false
+    }
+    return files.every(file => {
+        if (file.size > 1024 * 1024 * 30) {
+            toastr.error('文件大小限制为30MB');
+            return false
+        }
+        if (whiteList.indexOf('.' + file.ext) === -1) {
+            toastr.error('请上传正确的格式文件');
+            return false
+        }
+        return true
+    })
+}
+
+function findDecimal(unit) {
+    let value = precision.other.value;
+    const changeUnits = precision;
+    for (const d in changeUnits) {
+        if (changeUnits[d].unit !== undefined && changeUnits[d].unit === unit) {
+            value = changeUnits[d].value;
+            break;
+        }
+    }
+    return value;
+}
+
+const is_numeric = (value) => {
+    if (typeof(value) === 'object') {
+        return false;
+    } else {
+        return !Number.isNaN(Number(value)) && value.toString().trim() !== '';
+    }
+};

+ 8 - 0
app/public/js/global.js

@@ -132,6 +132,14 @@ $(function(){
             $(this).attr('href', $(this).attr('href') + '?sort=' + orders[0] + '&order=' + orders[1]);
             $(this).attr('href', $(this).attr('href') + '?sort=' + orders[0] + '&order=' + orders[1]);
         }
         }
     });
     });
+    $('.change_plan_sort_link').each(function () {
+        const tender_id = $(this).attr('href').split('/')[2];
+        let orderSetting = getLocalCache('change-plan-'+ tender_id +'-list-order');
+        if(orderSetting) {
+            const orders = orderSetting.split('|');
+            $(this).attr('href', $(this).attr('href') + '?sort=' + orders[0] + '&order=' + orders[1]);
+        }
+    });
 
 
     $('#nav_management').click(function(e) {
     $('#nav_management').click(function(e) {
       e.preventDefault()
       e.preventDefault()

+ 42 - 38
app/public/js/material_list.js

@@ -165,6 +165,7 @@ $(document).ready(() => {
             const leafXmjs = gcl.leafXmjs.filter(item => {
             const leafXmjs = gcl.leafXmjs.filter(item => {
                 return item.qc_qty || item.contract_qty
                 return item.qc_qty || item.contract_qty
             });
             });
+            console.log(leafXmjs);
             SpreadJsObj.loadSheetData(leafXmjSpread.getActiveSheet(), SpreadJsObj.DataType.Data, leafXmjs);
             SpreadJsObj.loadSheetData(leafXmjSpread.getActiveSheet(), SpreadJsObj.DataType.Data, leafXmjs);
 
 
             // 对清单调差工料table的单位数量进行改变
             // 对清单调差工料table的单位数量进行改变
@@ -216,6 +217,7 @@ $(document).ready(() => {
         gclGatherData = gclGatherModel.gatherGclData().filter(item => {
         gclGatherData = gclGatherModel.gatherGclData().filter(item => {
             return item.qc_qty || item.contract_qty
             return item.qc_qty || item.contract_qty
         });
         });
+        console.log(gclGatherData);
         if (openMaterialChecklist) {
         if (openMaterialChecklist) {
             // 取交集
             // 取交集
             gclGatherData = _.filter(gclGatherData, function (item) {
             gclGatherData = _.filter(gclGatherData, function (item) {
@@ -913,43 +915,6 @@ $(document).ready(() => {
         materialSpread.bind(spreadNS.Events.EditEnded, materialSpreadObj.editEnded);
         materialSpread.bind(spreadNS.Events.EditEnded, materialSpreadObj.editEnded);
         materialSpread.bind(spreadNS.Events.ClipboardPasted, materialSpreadObj.clipboardPasted);
         materialSpread.bind(spreadNS.Events.ClipboardPasted, materialSpreadObj.clipboardPasted);
         SpreadJsObj.addDeleteBind(materialSpread, materialSpreadObj.deletePress);
         SpreadJsObj.addDeleteBind(materialSpread, materialSpreadObj.deletePress);
-        // 应用调差工料至其他清单明细
-        $('#user_all_material').click(function () {
-            const ledgerSheet = ledgerSpread.getActiveSheet();
-            const ledgerSelect = SpreadJsObj.getSelectObject(ledgerSheet);
-            if (ledgerSelect.leafXmjs.length < 2) {
-                toastr.warning('没有需要应用调差工料的其它清单明细');
-                return false;
-            }
-            const xmjSheet = leafXmjSpread.getActiveSheet();
-            const xmjSelect = SpreadJsObj.getSelectObject(xmjSheet);
-            // 判断需要应用调差工料的清单明细
-            const needAddList = [];
-            for (const xmj of ledgerSelect.leafXmjs) {
-                const notx = findNotJoinLeafXmj(xmj);
-                if (notx === undefined && xmjSelect !== xmj) {
-                    needAddList.push(xmj);
-                }
-            }
-            if (needAddList.length === 0) {
-                toastr.warning('没有需要应用调差工料的其它清单明细');
-                return false;
-            }
-            console.log(needAddList, materialList);
-            // 更新至服务器
-            // postData(window.location.pathname + '/save', { type:'useOther', postData: { addXmj: needAddList, materialBills: materialList } }, function (result) {
-            //     materialListData = result;
-            //     toastr.success('已成功应用');
-            //     calculateJiaCha(gclGatherData);
-            //     const index = gclGatherData.indexOf(ledgerSelect);
-            //     loadLeafXmjData(index);
-            //     const xmjSheet = leafXmjSpread.getActiveSheet();
-            //     const xmjSelect = SpreadJsObj.getSelectObject(xmjSheet);
-            //     const xmjIndex = gclGatherData[index].leafXmjs.indexOf(xmjSelect);
-            //     loadMaterialData(index, xmjIndex);
-            //     SpreadJsObj.reLoadRowData(ledgerSpread.getActiveSheet(), index);
-            // });
-        });
         if (!openMaterialChecklist) {
         if (!openMaterialChecklist) {
             $.contextMenu({
             $.contextMenu({
                 selector: '#material-spread',
                 selector: '#material-spread',
@@ -1005,6 +970,45 @@ $(document).ready(() => {
         }
         }
     }
     }
 
 
+    // 应用调差工料至其他清单明细
+    $('#user_all_material').click(function () {
+        const ledgerSheet = ledgerSpread.getActiveSheet();
+        const ledgerSelect = SpreadJsObj.getSelectObject(ledgerSheet);
+        if (ledgerSelect.leafXmjs.length < 2) {
+            toastr.warning('没有需要应用调差工料的其它清单明细');
+            return false;
+        }
+        const xmjSheet = leafXmjSpread.getActiveSheet();
+        const xmjSelect = SpreadJsObj.getSelectObject(xmjSheet);
+        console.log(ledgerSelect.leafXmjs);
+        // 判断需要应用调差工料的清单明细
+        const needAddList = [];
+        for (const xmj of ledgerSelect.leafXmjs) {
+            const notx = findNotJoinLeafXmj(xmj);
+            if (notx === undefined && xmjSelect !== xmj && xmj.gather_qty && xmj.jiacha === null && xmjSelect.jiacha !== null) {
+                needAddList.push(xmj);
+            }
+        }
+        // if (needAddList.length === 0) {
+        //     toastr.warning('没有需要应用调差工料的其它清单明细');
+        //     return false;
+        // }
+        console.log(needAddList, materialList);
+        // 更新至服务器
+        postData(window.location.pathname + '/save', { type:'useOther', postData: { addXmj: needAddList, materialBills: materialList } }, function (result) {
+            materialListData = result;
+            toastr.success('已成功应用');
+            calculateJiaCha(gclGatherData);
+            const index = gclGatherData.indexOf(ledgerSelect);
+            loadLeafXmjData(index);
+            const xmjSheet = leafXmjSpread.getActiveSheet();
+            const xmjSelect = SpreadJsObj.getSelectObject(xmjSheet);
+            const xmjIndex = gclGatherData[index].leafXmjs.indexOf(xmjSelect);
+            loadMaterialData(index, xmjIndex);
+            SpreadJsObj.reLoadRowData(ledgerSpread.getActiveSheet(), index);
+        });
+    });
+
     // 切换清单行,读取所属项目节数据
     // 切换清单行,读取所属项目节数据
     ledgerSpread.getActiveSheet().bind(spreadNS.Events.SelectionChanged, function (e, info) {
     ledgerSpread.getActiveSheet().bind(spreadNS.Events.SelectionChanged, function (e, info) {
         if (info.oldSelections !== undefined) {
         if (info.oldSelections !== undefined) {
@@ -1117,7 +1121,7 @@ $(document).ready(() => {
             const height = $('.bcontent-wrap').height();
             const height = $('.bcontent-wrap').height();
             setLocalCache('material_list2_' + materialID, height);
             setLocalCache('material_list2_' + materialID, height);
         }
         }
-    });jub
+    });
 
 
     // 展开收起月信息价并浏览器记住本期展开收起
     // 展开收起月信息价并浏览器记住本期展开收起
     $('a', '.right-nav').bind('click', function () {
     $('a', '.right-nav').bind('click', function () {

+ 39 - 1
app/public/js/measure_material.js

@@ -246,5 +246,43 @@ $(function () {
         }
         }
         $(this).text('正在生成新一期数据中,请勿刷新本页!...');
         $(this).text('正在生成新一期数据中,请勿刷新本页!...');
         $(this).parents('form').submit();
         $(this).parents('form').submit();
-    })
+    });
+    let editPeriod = null;
+    $('.edit-material').on('click', function () {
+        const order = parseInt($(this).data('order'));
+        const mid = parseInt($(this).data('mid'));
+        $('#edit-period').val('');
+        $('#edit-name').val('第 ' + order + ' 期');
+        $('#edit-mid').val(mid);
+        const m_period = $(this).data('period');
+        const period = [];
+        for (const p of m_period.split('~')) {
+            if (p && p !== '') {
+                period.push(new Date(p));
+            }
+        }
+        $('#edit-period').val(m_period);
+        editPeriod = !editPeriod ? $('#edit-period').datepicker().data('datepicker') : editPeriod;
+        editPeriod.clear();
+        if (period.length > 0) {
+            editPeriod.selectDate(period);
+        }
+
+    });
 });
 });
+function checkForm () {
+    if ($('#edit-period').val() == '') {
+        toastr.error('请选择调差周期');
+        return false;
+    }
+    // const date = $('#edit-date').val();
+    // const period = $('#edit-period').val();
+    // const startDate = period.split('~')[0];
+    // const endDate = period.split('~')[1];
+    // if (startDate.indexOf(date) === -1 && endDate.indexOf(date) === -1) {
+    //     toastr.error('所选日期与当前月份不匹配,请重新选择');
+    //     $('#edit-period').parents('.form-group').find('.text-danger').remove();
+    //     $('#edit-period').parents('.form-group').append('<small class="text-danger">所选日期与当前月份不匹配,请重新选择</small>');
+    //     return false;
+    // }
+};

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

@@ -72,9 +72,9 @@ const sjsSettingObj = (function () {
             const col = _.find(cols, {field: r.field});
             const col = _.find(cols, {field: r.field});
             if (col) {
             if (col) {
                 col.getValue = r.getValue;
                 col.getValue = r.getValue;
-                col.cellType = 'activeImageBtn';
+                col.cellType = 'imageBtn';
                 col.normalImg = '#rela-file-icon';
                 col.normalImg = '#rela-file-icon';
-                col.indent = 5;
+                col.indent = 12;
                 col.imgAlign = 2;
                 col.imgAlign = 2;
                 col.showImage = function (data) { return data && data[r.url_field]; }
                 col.showImage = function (data) { return data && data[r.url_field]; }
             }
             }

+ 1 - 0
app/public/report/js/rpt_change_rela.js

@@ -3,6 +3,7 @@ const rptChangeRela = (function (){
         change: { title: '请选择变更令', colHeader: ['选择', '变更令号', '工程名称'] },
         change: { title: '请选择变更令', colHeader: ['选择', '变更令号', '工程名称'] },
         change_project: { title: '请选择变更立项', colHeader: ['选择', '变更立项编号', '工程名称'] },
         change_project: { title: '请选择变更立项', colHeader: ['选择', '变更立项编号', '工程名称'] },
         change_apply: { title: '请选择变更申请', colHeader: ['选择', '变更申请编号', '工程名称'] },
         change_apply: { title: '请选择变更申请', colHeader: ['选择', '变更申请编号', '工程名称'] },
+        change_plan: { title: '请选择变更方案', colHeader: ['选择', '变更方案编号', '工程名称'] },
     };
     };
     const data = {};
     const data = {};
     let curType = '';
     let curType = '';

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

@@ -220,7 +220,7 @@ const rptCustomObj = (function () {
         const html = [];
         const html = [];
         html.push('<select style="width: 80%" id="' + id + '" sf-title="' + select.title + '">');
         html.push('<select style="width: 80%" id="' + id + '" sf-title="' + select.title + '">');
         for (const sf of stageFlow) {
         for (const sf of stageFlow) {
-            html.push('<option>' + sf.name + '-' + sf.role +'</option>');
+            html.push(`<option ${sf.visible ? '' : 'style="display: none"'}>${sf.name}-${sf.role}</option>`);
         }
         }
         html.push('</select>');
         html.push('</select>');
         return html.join('');
         return html.join('');
@@ -687,7 +687,7 @@ const rptCustomObj = (function () {
                     return x.id === zTreeOprObj.currentNode.refId;
                     return x.id === zTreeOprObj.currentNode.refId;
                 });
                 });
                 if (material_sum_select) {
                 if (material_sum_select) {
-                    material_sum_select.masterial_sum_select = data[sMaterialSumSelect];
+                    material_sum_select.material_sum_select = data[sMaterialSumSelect];
                 }
                 }
                 $('#material-sum-select-count').html(data[sMaterialSumSelect].materials.length);
                 $('#material-sum-select-count').html(data[sMaterialSumSelect].materials.length);
                 $('#material-sum-select').modal('hide');
                 $('#material-sum-select').modal('hide');

+ 19 - 0
app/router.js

@@ -31,6 +31,8 @@ module.exports = app => {
     const changeProjectCheck = app.middlewares.changeProjectCheck();
     const changeProjectCheck = app.middlewares.changeProjectCheck();
     // 变更申请中间件
     // 变更申请中间件
     const changeApplyCheck = app.middlewares.changeApplyCheck();
     const changeApplyCheck = app.middlewares.changeApplyCheck();
+    // 变更方案中间件
+    const changePlanCheck = app.middlewares.changePlanCheck();
     // 投资进度中间件
     // 投资进度中间件
     const scheduleCheck = app.middlewares.scheduleCheck();
     const scheduleCheck = app.middlewares.scheduleCheck();
     // 修订
     // 修订
@@ -485,12 +487,29 @@ module.exports = app => {
     app.post('/tender/:id/change/apply/:caid/information/audit/start', sessionAuth, tenderCheck, uncheckTenderCheck, changeApplyCheck, 'changeController.startApplyAudit');
     app.post('/tender/:id/change/apply/:caid/information/audit/start', sessionAuth, tenderCheck, uncheckTenderCheck, changeApplyCheck, 'changeController.startApplyAudit');
     app.post('/tender/:id/change/apply/:caid/information/audit/check', sessionAuth, tenderCheck, uncheckTenderCheck, changeApplyCheck, 'changeController.checkApplyAudit');
     app.post('/tender/:id/change/apply/:caid/information/audit/check', sessionAuth, tenderCheck, uncheckTenderCheck, changeApplyCheck, 'changeController.checkApplyAudit');
     app.get('/tender/:id/change/apply/:caid/information/notice', sessionAuth, tenderCheck, uncheckTenderCheck, changeApplyCheck, 'changeController.applyInformationNotice');
     app.get('/tender/:id/change/apply/:caid/information/notice', sessionAuth, tenderCheck, uncheckTenderCheck, changeApplyCheck, 'changeController.applyInformationNotice');
+    app.post('/tender/:id/change/apply/:caid/information/list/save', sessionAuth, tenderCheck, uncheckTenderCheck, changeApplyCheck, 'changeController.saveApplyListsData');
+    // 变更方案
+    app.get('/tender/:id/change/plan', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.plan');
+    app.get('/tender/:id/change/plan/status/:status', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.planStatus');
+    app.post('/tender/:id/change/plan/add', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.planAdd');
+    app.post('/tender/:id/change/plan/delete', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.planDelete');
+    app.get('/tender/:id/change/plan/:cpid/information', sessionAuth, tenderCheck, uncheckTenderCheck, changePlanCheck, 'changeController.planInformation');
+    app.post('/tender/:id/change/plan/:cpid/information/save', sessionAuth, tenderCheck, uncheckTenderCheck, changePlanCheck, 'changeController.planInformationSave');
+    app.post('/tender/:id/change/plan/:cpid/information/file/upload', sessionAuth, tenderCheck, uncheckTenderCheck, changePlanCheck, 'changeController.uploadPlanFile');
+    app.post('/tender/:id/change/plan/:cpid/information/file/delete', sessionAuth, tenderCheck, uncheckTenderCheck, changePlanCheck, 'changeController.deletePlanFile');
+    app.get('/tender/:id/change/plan/:cpid/information/file/:fid/download', sessionAuth, tenderCheck, uncheckTenderCheck, changePlanCheck, 'changeController.downloadPlanFile');
+    app.post('/tender/:id/change/plan/:cpid/information/audit/add', sessionAuth, tenderCheck, uncheckTenderCheck, changePlanCheck, 'changeController.addPlanAudit');
+    app.post('/tender/:id/change/plan/:cpid/information/audit/delete', sessionAuth, tenderCheck, uncheckTenderCheck, changePlanCheck, 'changeController.deletePlanAudit');
+    app.post('/tender/:id/change/plan/:cpid/information/audit/start', sessionAuth, tenderCheck, uncheckTenderCheck, changePlanCheck, 'changeController.startPlanAudit');
+    app.post('/tender/:id/change/plan/:cpid/information/audit/check', sessionAuth, tenderCheck, uncheckTenderCheck, changePlanCheck, 'changeController.checkPlanAudit');
+    app.post('/tender/:id/change/plan/:cpid/information/list/save', sessionAuth, tenderCheck, uncheckTenderCheck, changePlanCheck, 'changeController.savePlanListsData');
     // 材料调差
     // 材料调差
     app.get('/tender/:id/measure/material', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.index');
     app.get('/tender/:id/measure/material', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.index');
     app.post('/tender/:id/measure/material/add', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.add');
     app.post('/tender/:id/measure/material/add', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.add');
     app.post('/tender/:id/measure/material/delete', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.delete');
     app.post('/tender/:id/measure/material/delete', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.delete');
     app.post('/tender/:id/measure/material/auditors', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.materialAuditors');
     app.post('/tender/:id/measure/material/auditors', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.materialAuditors');
     app.post('/tender/:id/measure/material/:order/save/decimal', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.saveDecimal');
     app.post('/tender/:id/measure/material/:order/save/decimal', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.saveDecimal');
+    app.post('/tender/:id/measure/material/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.save');
     // 审批
     // 审批
     app.post('/tender/:id/measure/material/:order/audit/add', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.addAudit');
     app.post('/tender/:id/measure/material/:order/audit/add', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.addAudit');
     app.post('/tender/:id/measure/material/:order/audit/delete', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.deleteAudit');
     app.post('/tender/:id/measure/material/:order/audit/delete', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.deleteAudit');

+ 17 - 1
app/service/change.js

@@ -81,7 +81,7 @@ module.exports = app => {
             }
             }
         }
         }
 
 
-        async add(tenderId, userId, code, name) {
+        async add(tenderId, userId, code, plan_code, name) {
             const sql = 'SELECT COUNT(*) as count FROM ?? WHERE `tid` = ? AND ((`code` = ? AND `status` != ?) OR (`p_code` = ? AND `status` = ?))';
             const sql = 'SELECT COUNT(*) as count FROM ?? WHERE `tid` = ? AND ((`code` = ? AND `status` != ?) OR (`p_code` = ? AND `status` = ?))';
             const sqlParam = [this.tableName, tenderId, code, audit.flow.status.checked, code, audit.flow.status.checked];
             const sqlParam = [this.tableName, tenderId, code, audit.flow.status.checked, code, audit.flow.status.checked];
             const codeCount = await this.db.queryOne(sql, sqlParam);
             const codeCount = await this.db.queryOne(sql, sqlParam);
@@ -105,9 +105,25 @@ module.exports = app => {
                     in_time: new Date(),
                     in_time: new Date(),
                     code,
                     code,
                     name,
                     name,
+                    plan_code,
                     // tp_decimal: this.ctx.tender.info.decimal.tp,
                     // tp_decimal: this.ctx.tender.info.decimal.tp,
                     // up_decimal: this.ctx.tender.info.decimal.up,
                     // up_decimal: this.ctx.tender.info.decimal.up,
                 };
                 };
+                if (plan_code) {
+                    const planInfo = await this.ctx.service.changePlan.getDataByCondition({ tid: tenderId, code: plan_code });
+                    if (planInfo) {
+                        change.org_name = planInfo.org_name;
+                        change.peg = planInfo.peg;
+                        change.org_code = planInfo.new_code;
+                        change.new_code = planInfo.c_new_code;
+                        change.new_name = planInfo.design_name;
+                        const classIndex = planInfo.class && this._.indexOf(changeConst.className, planInfo.class) !== -1 ? this._.indexOf(changeConst.className, planInfo.class) : 0;
+                        change.class = classIndex ? classIndex : changeConst.class.A.value;
+                        const qualityIndex = planInfo.quality && this._.indexOf(changeConst.qualityName, planInfo.quality) !== -1 ? this._.indexOf(changeConst.qualityName, planInfo.quality) : 0;
+                        change.quality = qualityIndex ? qualityIndex : changeConst.quality.common.value;
+                        change.content = planInfo.reason.replace(/[\r\n]/g, '\r\n') + '\r\n' + planInfo.content.replace(/[\r\n]/g, '\r\n');
+                    }
+                }
                 const operate = await this.transaction.insert(this.tableName, change);
                 const operate = await this.transaction.insert(this.tableName, change);
 
 
                 if (operate.affectedRows <= 0) {
                 if (operate.affectedRows <= 0) {

+ 73 - 1
app/service/change_apply.js

@@ -56,7 +56,7 @@ module.exports = app => {
                     quality: changeConst.quality.common.name,
                     quality: changeConst.quality.common.name,
                 };
                 };
                 if (project_code) {
                 if (project_code) {
-                    const projectInfo = await this.ctx.service.changeProject.getDataByCondition({ code: project_code });
+                    const projectInfo = await this.ctx.service.changeProject.getDataByCondition({ tid: tenderId, code: project_code });
                     if (projectInfo) {
                     if (projectInfo) {
                         change.org_name = projectInfo.org_name;
                         change.org_name = projectInfo.org_name;
                         change.peg = projectInfo.peg;
                         change.peg = projectInfo.peg;
@@ -258,6 +258,78 @@ module.exports = app => {
         }
         }
 
 
         /**
         /**
+         * 获取变更方案金额
+         * @param {int} tenderId - 标段id
+         * @param {int} status - 状态
+         * @return {void}
+         */
+        async getTp(tenderId, status) {
+            if (this.ctx.tender.isTourist && status === 0) {
+                const sql5 = 'SELECT SUM(cast (total_price as decimal(18,6))) AS total_price FROM ?? WHERE tid = ?';
+                const sqlParam5 = [this.tableName, tenderId];
+                const result5 = await this.db.query(sql5, sqlParam5);
+                return result5[0].total_price ? result5[0].total_price : 0;
+            }
+            switch (status) {
+                case 0: // 包含你的所有变更令
+                    const sql =
+                        'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? AS a WHERE a.tid = ? AND ' +
+                        '(a.uid = ? OR (a.status != ? AND a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.caid)) OR a.status = ?)';
+                    const sqlParam = [
+                        this.tableName,
+                        tenderId,
+                        this.ctx.session.sessionUser.accountId,
+                        audit.status.uncheck,
+                        this.ctx.service.changeApplyAudit.tableName,
+                        this.ctx.session.sessionUser.accountId,
+                        audit.status.checked,
+                    ];
+                    const result = await this.db.query(sql, sqlParam);
+                    return result[0].total_price ? result[0].total_price : 0;
+                case 1: // 待处理(你的)
+                    // return await this.ctx.service.changeAudit.count({
+                    //     tid: tenderId,
+                    //     uid: this.ctx.session.sessionUser.accountId,
+                    //     status: 2,
+                    // });
+                    const sql6 = 'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? as a WHERE a.tid = ? AND (a.id in(SELECT b.caid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ?)))';
+                    const sqlParam6 = [this.tableName, tenderId, this.ctx.service.changeApplyAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.checkNo];
+                    const result6 = await this.db.query(sql6, sqlParam6);
+                    return result6[0].total_price ? result6[0].total_price : 0;
+                case 5: // 待上报(所有的)PS:取未上报,退回的变更立项
+                    const sql2 =
+                        'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? AS a WHERE ' +
+                        // 'a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.caid) ' +
+                        'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ?)';
+                    const sqlParam2 = [
+                        this.tableName,
+                        // this.ctx.service.changePlanAudit.tableName,
+                        this.ctx.session.sessionUser.accountId,
+                        tenderId,
+                        audit.status.uncheck,
+                        audit.status.checkNo,
+                    ];
+                    const result2 = await this.db.query(sql2, sqlParam2);
+                    return result2[0].total_price ? result2[0].total_price : 0;
+                case 2: // 进行中(所有的)
+                case 4: // 终止(所有的)
+                    const sql3 =
+                        'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? AS a WHERE ' +
+                        'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.caid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.caid))';
+                    const sqlParam3 = [this.tableName, status, tenderId, this.ctx.session.sessionUser.accountId, this.ctx.service.changeApplyAudit.tableName, this.ctx.session.sessionUser.accountId];
+                    const result3 = await this.db.query(sql3, sqlParam3);
+                    return result3[0].total_price ? result3[0].total_price : 0;
+                case 3: // 已完成(所有的)
+                    const sql4 = 'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? WHERE status = ? AND tid = ?';
+                    const sqlParam4 = [this.tableName, status, tenderId];
+                    const result4 = await this.db.query(sql4, sqlParam4);
+                    return result4[0].total_price ? result4[0].total_price : 0;
+                default:
+                    break;
+            }
+        }
+
+        /**
          * 保存变更信息
          * 保存变更信息
          * @param {int} postData - 表单提交的数据
          * @param {int} postData - 表单提交的数据
          * @param {int} tenderId - 标段id
          * @param {int} tenderId - 标段id

+ 1 - 0
app/service/change_apply_audit.js

@@ -410,6 +410,7 @@ module.exports = app => {
                         id: caId, status: checkData.checkType,
                         id: caId, status: checkData.checkType,
                         notice_code: checkData.notice_code,
                         notice_code: checkData.notice_code,
                         notice_uid: checkData.notice_uid,
                         notice_uid: checkData.notice_uid,
+                        decimal: JSON.stringify(this.ctx.change.decimal),
                     });
                     });
 
 
                     // 微信模板通知
                     // 微信模板通知

+ 218 - 0
app/service/change_apply_list.js

@@ -0,0 +1,218 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/8/14
+ * @version
+ */
+
+const audit = require('../const/audit');
+
+module.exports = app => {
+    class ChangeApplyList extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'change_apply_list';
+        }
+
+        /**
+         * 取出变更令清单列表,并按台账清单在前,空白清单在后排序
+         * @return {void}
+         */
+        async getList(caid, transaction = false) {
+            const sql = 'SELECT * FROM ?? WHERE `caid` = ? ORDER BY `id` asc';
+            const sqlParam = [this.tableName, caid];
+            return transaction ? await transaction.query(sql, sqlParam) : await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 添加空白变更清单
+         * @return {void}
+         */
+        async add(data) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+            const insertData = {
+                tid: this.ctx.tender.id,
+                caid: this.ctx.change.id,
+            };
+            const newData = this._.assign(insertData, data);
+            // 新增工料
+            const result = await this.db.insert(this.tableName, newData);
+            if (result.affectedRows === 0) {
+                throw '新增空白清单数据失败';
+            }
+            return await this.getDataById(result.insertId);
+        }
+
+        /**
+         * 批量添加空白变更清单
+         * @return {void}
+         */
+        async batchAdd(data) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+            const num = data.num ? parseInt(data.num) : 0;
+            if (num < 1 || num > 100) {
+                throw '批量添加的空白清单数目不能小于1或大于100';
+            }
+            const insertArray = [];
+            for (let i = 0; i < num; i++) {
+                const insertData = {
+                    tid: this.ctx.tender.id,
+                    caid: this.ctx.change.id,
+                };
+                insertArray.push(insertData);
+            }
+            // 新增工料
+            const result = await this.db.insert(this.tableName, insertArray);
+            if (result.affectedRows !== num) {
+                throw '批量添加空白清单数据失败';
+            }
+            // 获取刚批量添加的所有list
+            for (let j = 0; j < num; j++) {
+                insertArray[j].id = result.insertId + j;
+            }
+            return insertArray;
+        }
+
+        /**
+         * 删除变更清单
+         * @param {int} id 清单id
+         * @return {void}
+         */
+        async del(ids) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 判断是否可删
+                await transaction.delete(this.tableName, { id: ids });
+                // 重新算变更令总额
+                await this.calcCamountSum(transaction);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 修改变更清单
+         * @param {Object} data 工料内容
+         * @param {int} order 期数
+         * @return {void}
+         */
+        async save(data, order) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // const mb_id = data.mb_id;
+                // delete data.mb_id;
+                await transaction.update(this.tableName, data);
+                // await this.calcQuantityByML(transaction, mb_id);
+                await this.calcCamountSum(transaction);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 修改变更清单 复制粘贴
+         * @param {Object} datas 修改内容
+         * @return {void}
+         */
+        async saveDatas(datas) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+            // 判断是否可修改
+            // 判断t_type是否为费用
+            const transaction = await this.db.beginTransaction();
+            try {
+                const insertArray = [];
+                const updateArray = [];
+                for (const d of datas) {
+                    if (d.id) {
+                        updateArray.push(d);
+                    } else {
+                        d.tid = this.ctx.tender.id;
+                        d.caid = this.ctx.change.id;
+                        insertArray.push(d);
+                    }
+                }
+                if (insertArray.length > 0) await transaction.insert(this.tableName, insertArray);
+                if (updateArray.length > 0) await transaction.updateRows(this.tableName, updateArray);
+                await this.calcCamountSum(transaction);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async calcCamountSum(transaction, updateTpDecimal = false) {
+            // const sql = 'SELECT SUM(ROUND(`camount`*`unit_price`, )) as total_price FROM ?? WHERE cid = ?';
+            // const sqlParam = [this.tableName, this.change.cid];
+            // const tp = await transaction.queryOne(sql, sqlParam);
+            // 防止小数位不精确,采用取值计算
+            const sql = 'SELECT unit_price, camount FROM ?? WHERE caid = ?';
+            const sqlParam = [this.tableName, this.ctx.change.id];
+            const changeList = await transaction.query(sql, sqlParam);
+            let total_price = 0;
+            const tp_decimal = this.ctx.change.decimal.tp;
+            for (const cl of changeList) {
+                total_price = this.ctx.helper.accAdd(total_price, this.ctx.helper.mul(cl.unit_price, cl.camount, tp_decimal));
+            }
+            const updateData = {
+                total_price,
+            };
+            // if (updateTpDecimal) {
+            //     updateData.tp_decimal = tp_decimal;
+            // }
+            const options = {
+                where: {
+                    id: this.ctx.change.id,
+                },
+            };
+            await transaction.update(this.ctx.service.changeApply.tableName, updateData, options);
+        }
+
+        /**
+         * 报表用
+         * Tony Kang
+         * @param {tid} tid - 标段id
+         * @return {void}
+         */
+        async getChangeBills(tid, onlyChecked) {
+            const sql = 'SELECT cb.*' +
+                '  FROM ' + this.tableName + ' cb' +
+                '  LEFT JOIN ' + this.ctx.service.changeApply.tableName + ' c ON cb.caid = c.id' +
+                '  WHERE c.tid = ? ' + (onlyChecked ? `and c.status = ${audit.changeApply.status.checked}` : '');
+            const param = [tid];
+            const result = await this.db.query(sql, param);
+            return result;
+        }
+    }
+
+    return ChangeApplyList;
+};

+ 426 - 0
app/service/change_plan.js

@@ -0,0 +1,426 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/8/14
+ * @version
+ */
+
+const audit = require('../const/audit').changePlan;
+// const smsTypeConst = require('../const/sms_type');
+// const SMS = require('../lib/sms');
+// const SmsAliConst = require('../const/sms_alitemplate');
+const wxConst = require('../const/wechat_template');
+const pushType = require('../const/audit').pushType;
+const projectLogConst = require('../const/project_log');
+const codeRuleConst = require('../const/code_rule');
+const changeConst = require('../const/change');
+
+module.exports = app => {
+    class ChangePlan extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'change_plan';
+        }
+
+        async add(tenderId, userId, code, apply_code, name) {
+            const sql = 'SELECT COUNT(*) as count FROM ?? WHERE `tid` = ? AND `code` = ?';
+            const sqlParam = [this.tableName, tenderId, code];
+            const codeCount = await this.db.queryOne(sql, sqlParam);
+            const count = codeCount.count;
+            if (count > 0) {
+                throw '变更方案编号重复';
+            }
+
+            // 初始化事务
+            this.transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                const change = {
+                    tid: tenderId,
+                    uid: userId,
+                    status: audit.status.uncheck,
+                    times: 1,
+                    in_time: new Date(),
+                    code,
+                    apply_code,
+                    name,
+                    quality: changeConst.quality.common.name,
+                };
+                let caid = null;
+                if (apply_code) {
+                    const applyInfo = await this.ctx.service.changeApply.getDataByCondition({ tid: tenderId, code: apply_code });
+                    if (applyInfo) {
+                        caid = applyInfo.id;
+                        change.org_name = applyInfo.org_name;
+                        change.peg = applyInfo.peg;
+                        change.new_code = applyInfo.new_code;
+                        change.c_new_code = applyInfo.c_new_code;
+                        change.design_name = applyInfo.design_name;
+                        change.class = applyInfo.class;
+                        change.quality = applyInfo.quality;
+                        change.reason = applyInfo.reason;
+                        change.content = applyInfo.content;
+                        change.total_price = applyInfo.total_price;
+                    }
+                }
+                const operate = await this.transaction.insert(this.tableName, change);
+
+                if (operate.affectedRows <= 0) {
+                    throw '新建变更令数据失败';
+                }
+                change.id = operate.insertId;
+                // 清单也要同步
+                if (caid) {
+                    const changeList = await this.ctx.service.changeApplyList.getList(caid);
+                    const insertArray = [];
+                    for (const c of changeList) {
+                        insertArray.push({
+                            tid: this.ctx.tender.id,
+                            cpid: operate.insertId,
+                            code: c.code,
+                            name: c.name,
+                            unit: c.unit,
+                            unit_price: c.unit_price,
+                            oamount: c.oamount,
+                            camount: c.camount,
+                            spamount: c.camount,
+                        });
+                    }
+                    if (insertArray.length > 0) await this.transaction.insert(this.ctx.service.changePlanList.tableName, insertArray);
+                }
+                // 先找出标段最近存在的变更令审批人的变更令info
+                const preChangeInfo = await this.getHaveAuditLastInfo(tenderId);
+                if (preChangeInfo) {
+                    // 并把之前存在的变更令审批人添加到zh_change_audit
+                    const auditResult = await this.ctx.service.changePlanAudit.copyPreChangePlanAuditors(this.transaction, preChangeInfo, change);
+                    if (!auditResult) {
+                        throw '复制上一次审批流程失败';
+                    }
+                }
+                result = change;
+                this.transaction.commit();
+            } catch (error) {
+                console.log(error);
+                // 回滚
+                await this.transaction.rollback();
+            }
+
+            return result;
+        }
+
+        async getHaveAuditLastInfo(tenderId) {
+            const sql = 'SELECT a.* FROM ?? as a LEFT JOIN ?? as b ON a.`id` = b.`cpid` WHERE a.`tid` = ? ORDER BY a.`in_time` DESC';
+            const sqlParam = [this.tableName, this.ctx.service.changePlanAudit.tableName, tenderId];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        /**
+         * 获取变更方案列表
+         * @param {int} tenderId - 标段id
+         * @param {int} status - 状态
+         * @param {int} hadlimit - 分页
+         * @return {object} list - 列表
+         */
+        async getListByStatus(tenderId, status = 0, hadlimit = 1, sortBy = '', orderBy = '') {
+            let sql = '';
+            let sqlParam = '';
+            if (this.ctx.tender.isTourist && status === 0) {
+                sql = 'SELECT a.*, p.name as account_name FROM ?? As a LEFT JOIN ?? AS p On a.notice_uid = p.id WHERE a.tid = ?';
+                sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId];
+            } else {
+                switch (status) {
+                    case 0: // 包含你的所有变更方案
+                        sql =
+                            'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE a.tid = ? AND ' +
+                            '(a.uid = ? OR (a.status != ? AND a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid)) OR a.status = ?)';
+                        sqlParam = [
+                            this.tableName,
+                            this.ctx.service.projectAccount.tableName,
+                            tenderId,
+                            this.ctx.session.sessionUser.accountId,
+                            audit.status.uncheck,
+                            this.ctx.service.changePlanAudit.tableName,
+                            this.ctx.session.sessionUser.accountId,
+                            audit.status.checked,
+                        ];
+                        break;
+                    case 1: // 待处理(你的)
+                        sql = 'SELECT a.*, p.name as account_name FROM ?? as a LEFT JOIN ?? AS p On a.uid = p.id WHERE a.tid = ? AND (a.id in(SELECT b.cpid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ?)))';
+                        sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, this.ctx.service.changePlanAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.checkNo];
+                        break;
+                    case 5: // 待上报(所有的)PS:取未上报,退回,修订的变更令
+                        sql =
+                            'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE ' +
+                            // 'a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) AND ' +
+                            'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ?)';
+                        sqlParam = [
+                            this.tableName,
+                            this.ctx.service.projectAccount.tableName,
+                            // this.ctx.service.changePlanAudit.tableName,
+                            this.ctx.session.sessionUser.accountId,
+                            tenderId,
+                            audit.status.uncheck,
+                            audit.status.checkNo,
+                        ];
+                        break;
+                    case 2: // 进行中(所有的)
+                    case 4: // 终止(所有的)
+                        sql =
+                            'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE ' +
+                            'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid))';
+                        sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, status, tenderId, this.ctx.session.sessionUser.accountId, this.ctx.service.changePlanAudit.tableName, this.ctx.session.sessionUser.accountId];
+                        break;
+                    case 3: // 已完成(所有的)
+                        sql = 'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE a.status = ? AND a.tid = ?';
+                        sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, status, tenderId];
+                        break;
+                    default:
+                        break;
+                }
+            }
+            if (sortBy && orderBy) {
+                if (sortBy === 'code') {
+                    sql += ' ORDER BY CHAR_LENGTH(a.code) ' + orderBy + ',convert(a.code using gbk) ' + orderBy;
+                } else {
+                    sql += ' ORDER BY a.in_time ' + orderBy;
+                }
+            } else {
+                sql += ' ORDER BY a.in_time DESC';
+            }
+            if (hadlimit) {
+                const limit = this.app.config.pageSize;
+                const offset = limit * (this.ctx.page - 1);
+                const limitString = offset >= 0 ? offset + ',' + limit : limit;
+                sql += ' LIMIT ' + limitString;
+            }
+            const list = await this.db.query(sql, sqlParam);
+            return list;
+        }
+
+        /**
+         * 获取变更令个数
+         * @param {int} tenderId - 标段id
+         * @param {int} status - 状态
+         * @return {void}
+         */
+        async getCountByStatus(tenderId, status) {
+            if (this.ctx.tender.isTourist && status === 0) {
+                const sql5 = 'SELECT count(*) AS count FROM ?? WHERE tid = ? ORDER BY in_time DESC';
+                const sqlParam5 = [this.tableName, tenderId];
+                const result5 = await this.db.query(sql5, sqlParam5);
+                return result5[0].count;
+            }
+            switch (status) {
+                case 0: // 包含你的所有变更令
+                    const sql =
+                        'SELECT count(*) AS count FROM ?? AS a WHERE a.tid = ? AND ' +
+                        '(a.uid = ? OR (a.status != ? AND a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid)) OR a.status = ?)';
+                    const sqlParam = [
+                        this.tableName,
+                        tenderId,
+                        this.ctx.session.sessionUser.accountId,
+                        audit.status.uncheck,
+                        this.ctx.service.changePlanAudit.tableName,
+                        this.ctx.session.sessionUser.accountId,
+                        audit.status.checked,
+                    ];
+                    const result = await this.db.query(sql, sqlParam);
+                    return result[0].count;
+                case 1: // 待处理(你的)
+                    // return await this.ctx.service.changeAudit.count({
+                    //     tid: tenderId,
+                    //     uid: this.ctx.session.sessionUser.accountId,
+                    //     status: 2,
+                    // });
+                    const sql6 = 'SELECT count(*) AS count FROM ?? as a WHERE a.tid = ? AND (a.id in(SELECT b.cpid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ?)))';
+                    const sqlParam6 = [this.tableName, tenderId, this.ctx.service.changePlanAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.checkNo];
+                    const result6 = await this.db.query(sql6, sqlParam6);
+                    return result6[0].count;
+                case 5: // 待上报(所有的)PS:取未上报,退回的变更立项
+                    const sql2 =
+                        'SELECT count(*) AS count FROM ?? AS a WHERE ' +
+                        // 'a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) ' +
+                        'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ?)';
+                    const sqlParam2 = [
+                        this.tableName,
+                        // this.ctx.service.changePlanAudit.tableName,
+                        this.ctx.session.sessionUser.accountId,
+                        tenderId,
+                        audit.status.uncheck,
+                        audit.status.checkNo,
+                    ];
+                    const result2 = await this.db.query(sql2, sqlParam2);
+                    return result2[0].count;
+                case 2: // 进行中(所有的)
+                case 4: // 终止(所有的)
+                    const sql3 =
+                        'SELECT count(*) AS count FROM ?? AS a WHERE ' +
+                        'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid))';
+                    const sqlParam3 = [this.tableName, status, tenderId, this.ctx.session.sessionUser.accountId, this.ctx.service.changePlanAudit.tableName, this.ctx.session.sessionUser.accountId];
+                    const result3 = await this.db.query(sql3, sqlParam3);
+                    return result3[0].count;
+                case 3: // 已完成(所有的)
+                    const sql4 = 'SELECT count(*) AS count FROM ?? WHERE status = ? AND tid = ?';
+                    const sqlParam4 = [this.tableName, status, tenderId];
+                    const result4 = await this.db.query(sql4, sqlParam4);
+                    return result4[0].count;
+                default:
+                    break;
+            }
+        }
+
+        /**
+         * 获取变更方案金额
+         * @param {int} tenderId - 标段id
+         * @param {int} status - 状态
+         * @return {void}
+         */
+        async getTp(tenderId, status) {
+            if (this.ctx.tender.isTourist && status === 0) {
+                const sql5 = 'SELECT SUM(cast (total_price as decimal(18,6))) AS total_price FROM ?? WHERE tid = ?';
+                const sqlParam5 = [this.tableName, tenderId];
+                const result5 = await this.db.query(sql5, sqlParam5);
+                return result5[0].total_price ? result5[0].total_price : 0;
+            }
+            switch (status) {
+                case 0: // 包含你的所有变更令
+                    const sql =
+                        'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? AS a WHERE a.tid = ? AND ' +
+                        '(a.uid = ? OR (a.status != ? AND a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid)) OR a.status = ?)';
+                    const sqlParam = [
+                        this.tableName,
+                        tenderId,
+                        this.ctx.session.sessionUser.accountId,
+                        audit.status.uncheck,
+                        this.ctx.service.changePlanAudit.tableName,
+                        this.ctx.session.sessionUser.accountId,
+                        audit.status.checked,
+                    ];
+                    const result = await this.db.query(sql, sqlParam);
+                    return result[0].total_price ? result[0].total_price : 0;
+                case 1: // 待处理(你的)
+                    // return await this.ctx.service.changeAudit.count({
+                    //     tid: tenderId,
+                    //     uid: this.ctx.session.sessionUser.accountId,
+                    //     status: 2,
+                    // });
+                    const sql6 = 'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? as a WHERE a.tid = ? AND (a.id in(SELECT b.cpid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ?)))';
+                    const sqlParam6 = [this.tableName, tenderId, this.ctx.service.changePlanAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.checkNo];
+                    const result6 = await this.db.query(sql6, sqlParam6);
+                    return result6[0].total_price ? result6[0].total_price : 0;
+                case 5: // 待上报(所有的)PS:取未上报,退回的变更立项
+                    const sql2 =
+                        'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? AS a WHERE ' +
+                        // 'a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) ' +
+                        'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ?)';
+                    const sqlParam2 = [
+                        this.tableName,
+                        // this.ctx.service.changePlanAudit.tableName,
+                        this.ctx.session.sessionUser.accountId,
+                        tenderId,
+                        audit.status.uncheck,
+                        audit.status.checkNo,
+                    ];
+                    const result2 = await this.db.query(sql2, sqlParam2);
+                    return result2[0].total_price ? result2[0].total_price : 0;
+                case 2: // 进行中(所有的)
+                case 4: // 终止(所有的)
+                    const sql3 =
+                        'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? AS a WHERE ' +
+                        'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid))';
+                    const sqlParam3 = [this.tableName, status, tenderId, this.ctx.session.sessionUser.accountId, this.ctx.service.changePlanAudit.tableName, this.ctx.session.sessionUser.accountId];
+                    const result3 = await this.db.query(sql3, sqlParam3);
+                    return result3[0].total_price ? result3[0].total_price : 0;
+                case 3: // 已完成(所有的)
+                    const sql4 = 'SELECT SUM(cast (a.total_price as decimal(18,6))) AS total_price FROM ?? WHERE status = ? AND tid = ?';
+                    const sqlParam4 = [this.tableName, status, tenderId];
+                    const result4 = await this.db.query(sql4, sqlParam4);
+                    return result4[0].total_price ? result4[0].total_price : 0;
+                default:
+                    break;
+            }
+        }
+
+        /**
+         * 保存变更信息
+         * @param {int} postData - 表单提交的数据
+         * @param {int} tenderId - 标段id
+         * @return {void}
+         */
+        async saveInfo(cpid, postData) {
+            // 初始化事务
+            const transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                const updateData = {
+                    id: cpid,
+                };
+                updateData[postData.name] = postData.val;
+                await transaction.update(this.tableName, updateData);
+                await transaction.commit();
+                result = true;
+            } catch (error) {
+                await transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
+
+        /**
+         * 判断是否有重名的变更立项
+         * @param cpid
+         * @param code
+         * @param tid
+         * @return {Promise<void>}
+         */
+        async isRepeat(cpid, code, tid) {
+            const sql = 'SELECT COUNT(*) as count FROM ?? WHERE `code` = ? AND `id` != ? AND `tid` = ?';
+            const sqlParam = [this.tableName, code, cpid, tid];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result.count !== 0;
+        }
+
+        /**
+         * 查询可用的变更令
+         * @param { string } cid - 查询的清单
+         * @return {Promise<*>} - 可用的变更令列表
+         */
+        async delete(id) {
+            // 初始化事务
+            this.transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                const changeInfo = await this.getDataById(id);
+                // 先删除审批人列表
+                await this.transaction.delete(this.ctx.service.changePlanAudit.tableName, { cpid: id });
+                // 删除清单
+                await this.transaction.delete(this.ctx.service.changePlanList.tableName, { cpid: id });
+                // 再删除附件和附件文件ni zuo
+                const attList = await this.ctx.service.changePlanAtt.getAllDataByCondition({ where: { cpid: id } });
+                await this.ctx.helper.delFiles(attList);
+                await this.transaction.delete(this.ctx.service.changePlanAtt.tableName, { cpid: id });
+                // 最后删除变更令
+                await this.transaction.delete(this.tableName, { id });
+                // 记录删除日志
+                await this.ctx.service.projectLog.addProjectLog(this.transaction, projectLogConst.type.changePlan, projectLogConst.status.delete, changeInfo.code);
+                await this.transaction.commit();
+                result = true;
+            } catch (e) {
+                await this.transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
+    }
+
+    return ChangePlan;
+};

+ 137 - 0
app/service/change_plan_att.js

@@ -0,0 +1,137 @@
+'use strict';
+const archiver = require('archiver');
+const path = require('path');
+const fs = require('fs');
+/**
+ * 附件表 数据模型
+ * @author LanJianRong
+ * @date 2020/6/30
+ * @version
+ */
+
+module.exports = app => {
+    class ChangePlanFile extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'change_plan_attachment';
+        }
+
+        /**
+         * 获取当前标段(期)所有上传的附件
+         * @param {Number} tid 标段id
+         * @param {Number?} mid 期id
+         * @return {Promise<void>} 数据库查询实例
+         */
+        async getAllChangePlanAtt(tid, cpid) {
+            const { ctx } = this;
+            // const where = { tid };
+            // if (cpid) where.cpid = cpid;
+            const sql = 'SELECT a.*,b.name as username FROM ?? as a LEFT JOIN ?? as b ON a.uid = b.id WHERE a.tid = ? AND a.cpid = ? ORDER BY upload_time DESC';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tid, cpid];
+            const result = await this.db.query(sql, sqlParam);
+            // const result = await this.db.select(this.tableName, {
+            //     where,
+            //     orders: [['upload_time', 'desc']],
+            // });
+            // for (const r of result) {
+            //     const userInfo = await this.ctx.service.projectAccount.getDataById(r.uid);
+            //     r.username = userInfo ? userInfo.name : '';
+            // }
+            return result.map(item => {
+                item.orginpath = this.ctx.app.config.fujianOssPath + item.filepath;
+                if (!ctx.helper.canPreview(item.fileext)) {
+                    item.filepath = `/tender/${ctx.tender.id}/change/plan/${item.cpid}/information/file/${item.id}/download`;
+                } else {
+                    item.filepath = this.ctx.app.config.fujianOssPath + item.filepath;
+                }
+                return item;
+            });
+        }
+
+
+        /**
+         * 存储上传的文件信息至数据库
+         * @param {Array} payload 载荷
+         * @return {Promise<void>} 数据库插入执行实例
+         */
+        async saveFileMsgToDb(payload) {
+            return await this.db.insert(this.tableName, payload);
+        }
+
+        /**
+         * 获取单个文件信息
+         * @param {Number} id 文件id
+         * @return {Promise<void>} 数据库查询实例
+         */
+        async getMaterialFileById(id) {
+            return await this.getDataByCondition({ id });
+        }
+
+        /**
+         * 删除附件
+         * @param {Number} id - 附件id
+         * @return {void}
+         */
+        async delete(id) {
+            return await this.deleteById(id);
+        }
+
+        /**
+         * 将文件压缩成zip,并返回zip文件的路径
+         * @param {array} fileIds - 文件数组id
+         * @param {string} zipPath - 压缩文件存储路径
+         * @return {string} 压缩后的zip文件路径
+         */
+        async compressedFile(fileIds, zipPath) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('id', {
+                value: fileIds,
+                operate: 'in',
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const files = await this.db.query(sql, sqlParam);
+            // const paths = files.map(item => {
+            //     return { name: item.filename + item.fileext, path: item.filepath }
+            // })
+            return new Promise((resolve, reject) => {
+                // 每次开一个新的archiver
+                const ziparchiver = archiver('zip');
+                const outputPath = fs.createWriteStream(path.resolve(this.app.baseDir, zipPath));
+                outputPath.on('error', err => {
+                    return reject(err);
+                });
+
+                ziparchiver.pipe(outputPath);
+                files.forEach(item => {
+                    ziparchiver.file(path.resolve(this.app.baseDir, 'app', item.filepath), { name: item.file_name });
+                });
+
+                // 存档警告
+                ziparchiver.on('warning', function(err) {
+                    // if (err.code === 'ENOENT') {
+                    //     console.warn('stat故障和其他非阻塞错误');
+                    // }
+                    return reject(err);
+                });
+
+                // 存档出错
+                ziparchiver.on('error', function(err) {
+                    // console.log(err);
+                    return reject(err);
+                });
+                ziparchiver.finalize();
+                outputPath.on('close', () => {
+                    return resolve(ziparchiver.pointer());
+                });
+            });
+        }
+    }
+    return ChangePlanFile;
+};
+

+ 532 - 0
app/service/change_plan_audit.js

@@ -0,0 +1,532 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/8/14
+ * @version
+ */
+
+const auditConst = require('../const/audit').changePlan;
+const pushType = require('../const/audit').pushType;
+const shenpiConst = require('../const/shenpi');
+const smsTypeConst = require('../const/sms_type');
+const SMS = require('../lib/sms');
+const SmsAliConst = require('../const/sms_alitemplate');
+const wxConst = require('../const/wechat_template');
+
+module.exports = app => {
+    class ChangePlanAudit extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'change_plan_audit';
+        }
+
+        /**
+         * 获取 审核列表信息
+         *
+         * @param {Number} cpId - 变更立项id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<*>}
+         */
+        async getAuditors(cpId, times = 1) {
+            const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
+                'FROM ?? AS la, ?? AS pa, (SELECT `aid`,(@i:=@i+1) as `sort` FROM ??, (select @i:=0) as it WHERE `cpid` = ? AND `times` = ? GROUP BY `aid`) as g ' +
+                'WHERE la.`cpid` = ? and la.`times` = ? and la.`aid` = pa.`id` and g.`aid` = la.`aid` order by la.`order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, cpId, times, cpId, times];
+            const result = await this.db.query(sql, sqlParam);
+            const sql2 = 'SELECT COUNT(a.`aid`) as num FROM (SELECT `aid` FROM ?? WHERE `cpid` = ? AND `times` = ? GROUP BY `aid`) as a';
+            const sqlParam2 = [this.tableName, cpId, times];
+            const count = await this.db.queryOne(sql2, sqlParam2);
+            for (const i in result) {
+                result[i].max_sort = count.num;
+            }
+            return result;
+        }
+
+        /**
+         * 获取 当前审核人
+         *
+         * @param {Number} cpId - 变更立项id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<*>}
+         */
+        async getCurAuditor(cpId, times = 1) {
+            const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+                '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
+                '  WHERE la.`cpid` = ? and la.`status` = ? and la.`times` = ?';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, cpId, auditConst.status.checking, times];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        /**
+         * 获取审核人流程列表
+         *
+         * @param auditorId
+         * @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` ' +
+                '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
+                '  WHERE la.`cpid` = ? and la.`times` = ? GROUP BY la.`aid` ORDER BY la.`order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, changeId, times];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 移除审核人
+         *
+         * @param {Number} materialId - 材料调差期id
+         * @param {Number} status - 期状态
+         * @param {Number} status - 期次数
+         * @return {Promise<boolean>}
+         */
+        async getAuditorByStatus(cpId, status, times = 1) {
+            let auditor = null;
+            let sql = '';
+            let sqlParam = '';
+            switch (status) {
+                case auditConst.status.checking :
+                case auditConst.status.checked :
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`cpid`, la.`aid`, la.`order` ' +
+                        '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
+                        '  WHERE la.`cpid` = ? and la.`status` = ? ' +
+                        '  ORDER BY la.`times` desc, la.`order` desc';
+                    sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, cpId, status];
+                    auditor = await this.db.queryOne(sql, sqlParam);
+                    break;
+                case auditConst.status.checkNo :
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`cpid`, la.`aid`, la.`order` ' +
+                        '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
+                        '  WHERE la.`cpid` = ? and la.`status` = ? and la.`times` = ?' +
+                        '  ORDER BY la.`times` desc, la.`order` desc';
+                    sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, cpId, auditConst.status.checkNo, parseInt(times) - 1];
+                    auditor = await this.db.queryOne(sql, sqlParam);
+                    break;
+                case auditConst.status.uncheck :
+                    break;
+                default:break;
+            }
+            return auditor;
+        }
+
+        /**
+         * 获取审核人流程列表(包括原报)
+         * @param {Number} materialId 调差id
+         * @param {Number} times 审核次数
+         * @return {Promise<Array>} 查询结果集(包括原报)
+         */
+        async getAuditorsWithOwner(cpId, times = 1) {
+            const result = await this.getAuditGroupByList(cpId, times);
+            const sql =
+                'SELECT pa.`id` As aid, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As cpid, 0 As `order`' +
+                '  FROM ' +
+                this.ctx.service.changePlan.tableName +
+                ' As s' +
+                '  LEFT JOIN ' +
+                this.ctx.service.projectAccount.tableName +
+                ' As pa' +
+                '  ON s.uid = pa.id' +
+                '  WHERE s.id = ?';
+            const sqlParam = [times, cpId, cpId];
+            const user = await this.db.queryOne(sql, sqlParam);
+            result.unshift(user);
+            return result;
+        }
+
+        /**
+         * 新增审核人
+         *
+         * @param {Number} cpId - 方案id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<number>}
+         */
+        async addAuditor(cpId, auditorId, times = 1, is_gdzs = 0) {
+            const transaction = await this.db.beginTransaction();
+            let flag = false;
+            try {
+                let newOrder = await this.getNewOrder(cpId, times);
+                // 判断是否存在固定终审,存在则newOrder - 1并使终审order+1
+                newOrder = is_gdzs === 1 ? newOrder - 1 : newOrder;
+                if (is_gdzs) await this._syncOrderByDelete(transaction, cpId, newOrder, times, '+');
+                const data = {
+                    tid: this.ctx.tender.id,
+                    cpid: cpId,
+                    aid: auditorId,
+                    times,
+                    order: newOrder,
+                    status: auditConst.status.uncheck,
+                };
+                const result = await transaction.insert(this.tableName, data);
+                await transaction.commit();
+                flag = result.effectRows = 1;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return flag;
+        }
+
+        /**
+         * 获取 最新审核顺序
+         *
+         * @param {Number} cpId - 方案id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<number>}
+         */
+        async getNewOrder(cpId, times = 1) {
+            const sql = 'SELECT Max(??) As max_order FROM ?? Where `cpid` = ? and `times` = ?';
+            const sqlParam = ['order', this.tableName, cpId, times];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result && result.max_order ? result.max_order + 1 : 1;
+        }
+
+        /**
+         * 移除审核人
+         *
+         * @param {Number} cpId - 变更方案id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<boolean>}
+         */
+        async deleteAuditor(cpId, auditorId, times = 1) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const condition = { cpid: cpId, aid: auditorId, times };
+                const auditor = await this.getDataByCondition(condition);
+                if (!auditor) {
+                    throw '该审核人不存在';
+                }
+                await this._syncOrderByDelete(transaction, cpId, auditor.order, times);
+                await transaction.delete(this.tableName, condition);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
+
+        /**
+         * 移除审核人时,同步其后审核人order
+         * @param transaction - 事务
+         * @param {Number} cpId - 变更方案id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<*>}
+         * @private
+         */
+        async _syncOrderByDelete(transaction, cpId, order, times, selfOperate = '-') {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('cpid', {
+                value: cpId,
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere('order', {
+                value: order,
+                operate: '>=',
+            });
+            this.sqlBuilder.setAndWhere('times', {
+                value: times,
+                operate: '=',
+            });
+            this.sqlBuilder.setUpdateData('order', {
+                value: 1,
+                selfOperate,
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+            const data = await transaction.query(sql, sqlParam);
+
+            return data;
+        }
+
+        /**
+         * 开始审批
+         * @param {Number} cpId - 方案id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<boolean>}
+         */
+        async start(cpId, times = 1) {
+            const audit = await this.getDataByCondition({ cpid: cpId, times, order: 1 });
+            if (!audit) {
+                // if (this.ctx.tender.info.shenpi.material === shenpiConst.sp_status.gdspl) {
+                //     throw '请联系管理员添加审批人';
+                // } else {
+                throw '请先选择审批人,再上报数据';
+                // }
+            }
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, { id: audit.id, status: auditConst.status.checking, begin_time: new Date() });
+                await transaction.update(this.ctx.service.changePlan.tableName, {
+                    id: cpId, status: auditConst.status.checking,
+                });
+                // 微信模板通知
+                // const materialInfo = await this.ctx.service.material.getDataById(materialId);
+                // const wechatData = {
+                //     qi: materialInfo.order,
+                //     status: wxConst.status.check,
+                //     tips: wxConst.tips.check,
+                //     begin_time: Date.parse(new Date()),
+                //     m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, 2), this.ctx.helper.round(materialInfo.ex_tp, 2)),
+                //     hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.rate/100), 2)),
+                // };
+                // await this.ctx.helper.sendWechat(audit.aid, smsTypeConst.const.TC, smsTypeConst.judge.approval.toString(), wxConst.template.material, wechatData);
+
+                // todo 更新标段tender状态 ?
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
+
+        /**
+         * 获取审核人需要审核的期列表
+         *
+         * @param auditorId
+         * @return {Promise<*>}
+         */
+        async getAuditChangePlan(auditorId) {
+            const sql = 'SELECT ma.`aid`, ma.`times`, ma.`order`, ma.`begin_time`, ma.`end_time`, ma.`tid`, ma.`cpid`,' +
+                '    m.`status` As `mstatus`, m.`code` As `mcode`,' +
+                '    t.`name`, t.`project_id`, t.`type`, t.`user_id` ' +
+                '  FROM ?? AS ma, ?? AS m, ?? As t ' +
+                '  WHERE ((ma.`aid` = ? and ma.`status` = ?) OR (m.`uid` = ? and ma.`status` = ? and m.`status` = ? and ma.`times` = (m.`times`-1)))' +
+                '    and ma.`cpid` = m.`id` and ma.`tid` = t.`id` ORDER BY ma.`begin_time` DESC';
+            const sqlParam = [this.tableName, this.ctx.service.changePlan.tableName, this.ctx.service.tender.tableName, auditorId, auditConst.status.checking, auditorId, auditConst.status.checkNo, auditConst.status.checkNo];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 用于添加推送所需的content内容
+         * @param {Number} pid 项目id
+         * @param {Number} tid 台账id
+         * @param {Number} cpId 方案id
+         * @param {Number} uid 审批人id
+         */
+        async getNoticeContent(pid, tid, cpId, uid) {
+            const noticeSql = 'SELECT * FROM (SELECT ' +
+                '  t.`id` As `tid`, ma.`cpid`, m.`code` as `c_code`, t.`name`, pa.`name` As `su_name`, pa.role As `su_role`' +
+                '  FROM (SELECT * FROM ?? WHERE `id` = ? ) As t' +
+                '  LEFT JOIN ?? As m On t.`id` = m.`tid` AND m.`id` = ?' +
+                '  LEFT JOIN ?? As ma ON m.`id` = ma.`cpid`' +
+                '  LEFT JOIN ?? As pa ON pa.`id` = ?' +
+                '  WHERE  t.`project_id` = ? ) as new_t GROUP BY new_t.`tid`';
+            const noticeSqlParam = [this.ctx.service.tender.tableName, tid, this.ctx.service.changePlan.tableName, cpId, this.tableName, this.ctx.service.projectAccount.tableName, uid, pid];
+            const content = await this.db.query(noticeSql, noticeSqlParam);
+            return content.length ? JSON.stringify(content[0]) : '';
+        }
+
+        /**
+         * 审批
+         * @param {Number} cpId - 方案id
+         * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
+         * @param {Number} times - 第几次审批
+         * @return {Promise<void>}
+         */
+        async check(cpId, checkData, times = 1) {
+            if (checkData.checkType !== auditConst.status.checked && checkData.checkType !== auditConst.status.checkNo) {
+                throw '提交数据错误';
+            }
+            const pid = this.ctx.session.sessionProject.id;
+            switch (checkData.checkType) {
+                case auditConst.status.checked:
+                    await this._checked(pid, cpId, checkData, times);
+                    break;
+                case auditConst.status.checkNo:
+                    await this._checkNo(pid, cpId, checkData, times);
+                    break;
+                default:
+                    throw '无效审批操作';
+            }
+        }
+
+        async _checked(pid, cpId, checkData, times) {
+            const time = new Date();
+
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({ cpid: cpId, times, status: auditConst.status.checking });
+            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 nextAudit = await this.getDataByCondition({ cpid: cpId, times, order: audit.order + 1 });
+
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
+
+                // 获取推送必要信息
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, cpId, audit.aid);
+                // 添加推送
+                const records = [{ pid, type: pushType.changePlan, uid: this.ctx.change.uid, status: auditConst.status.checked, content: noticeContent }];
+                auditors.forEach(audit => {
+                    records.push({ pid, type: pushType.changePlan, uid: audit.aid, status: auditConst.status.checked, content: noticeContent });
+                });
+                await transaction.insert('zh_notice', records);
+
+                // 清单审批流程修改的数据插入到audit_amount中,审批完成则最后一位并插入到审批后变更值中
+                await this.ctx.service.changePlanList.insertAuditAmount(transaction, cpId, !nextAudit);
+
+                // 无下一审核人表示,审核结束
+                if (nextAudit) {
+                    // 流程至下一审批人
+                    await transaction.update(this.tableName, { id: nextAudit.id, status: auditConst.status.checking, begin_time: time });
+
+                    // 同步 期信息
+                    await transaction.update(this.ctx.service.changePlan.tableName, {
+                        id: cpId, status: auditConst.status.checking,
+                    });
+
+                    // 微信模板通知
+                    // const wechatData = {
+                    //     qi: materialInfo.order,
+                    //     status: wxConst.status.check,
+                    //     tips: wxConst.tips.check,
+                    //     begin_time: Date.parse(begin_audit.begin_time),
+                    //     m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, 2), this.ctx.helper.round(materialInfo.ex_tp, 2)),
+                    //     hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.rate/100), 2)),
+                    // };
+                    // await this.ctx.helper.sendWechat(nextAudit.aid, smsTypeConst.const.TC, smsTypeConst.judge.approval.toString(), wxConst.template.material, wechatData);
+                } else {
+                    // 本期结束
+                    // 生成截止本期数据 final数据
+                    // 同步 期信息
+                    await transaction.update(this.ctx.service.changePlan.tableName, {
+                        id: cpId, status: checkData.checkType,
+                        decimal: JSON.stringify(this.ctx.change.decimal),
+                    });
+
+                    // 微信模板通知
+                    // const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), materialInfo.user_id));
+                    // const wechatData = {
+                    //     qi: materialInfo.order,
+                    //     status: wxConst.status.success,
+                    //     tips: wxConst.tips.success,
+                    //     begin_time: Date.parse(begin_audit.begin_time),
+                    //     m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, 2), this.ctx.helper.round(materialInfo.ex_tp, 2)),
+                    //     hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.rate/100), 2)),
+                    // };
+                    // await this.ctx.helper.sendWechat(users, smsTypeConst.const.TC, smsTypeConst.judge.result.toString(), wxConst.template.material, wechatData);
+                }
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async _checkNo(pid, cpId, checkData, times) {
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({ cpid: cpId, times, status: auditConst.status.checking });
+            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);
+            let order = 1;
+            for (const a of auditors) {
+                a.times = times + 1;
+                a.order = order;
+                a.status = auditConst.status.uncheck;
+                order++;
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
+                // 添加到消息推送表
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, cpId, audit.aid);
+                const records = [{ pid, type: pushType.changePlan, uid: this.ctx.change.uid, status: auditConst.status.checkNo, content: noticeContent }];
+                auditors.forEach(audit => {
+                    records.push({ pid, type: pushType.changePlan, uid: audit.aid, status: auditConst.status.checkNo, content: noticeContent });
+                });
+                await transaction.insert(this.ctx.service.noticePush.tableName, records);
+                // 同步期信息
+                await transaction.update(this.ctx.service.changePlan.tableName, {
+                    id: cpId, status: checkData.checkType,
+                    times: times + 1,
+                });
+                // 拷贝新一次审核流程列表
+                await transaction.insert(this.tableName, auditors);
+                // 清单审批值删除并重算变更金额
+                await this.ctx.service.changePlanList.delAuditAmount(transaction, cpId);
+                // 微信模板通知
+                // const begin_audit = await this.getDataByCondition({
+                //     mid: materialId,
+                //     order: 1,
+                // });
+                // const materialInfo = await this.ctx.service.material.getDataById(materialId);
+                // const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), materialInfo.user_id));
+                // const wechatData = {
+                //     qi: materialInfo.order,
+                //     status: wxConst.status.back,
+                //     tips: wxConst.tips.back,
+                //     begin_time: Date.parse(begin_audit.begin_time),
+                //     m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, 2), this.ctx.helper.round(materialInfo.ex_tp, 2)),
+                //     hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.rate/100), 2)),
+                // };
+                // await this.ctx.helper.sendWechat(users, smsTypeConst.const.TC, smsTypeConst.judge.result.toString(), wxConst.template.material, wechatData);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 复制上一期的审批人列表给最新一期
+         *
+         * @param transaction - 新增一期的事务
+         * @param {Object} preMaterial - 上一期
+         * @param {Object} newaMaterial - 最新一期
+         * @return {Promise<*>}
+         */
+        async copyPreChangePlanAuditors(transaction, preChange, newChange) {
+            const auditors = await this.getAuditGroupByList(preChange.id, preChange.times);
+            const newAuditors = [];
+            for (const a of auditors) {
+                const na = {
+                    tid: preChange.tid,
+                    cpid: newChange.id,
+                    aid: a.aid,
+                    times: newChange.times,
+                    order: newAuditors.length + 1,
+                    status: auditConst.status.uncheck,
+                };
+                newAuditors.push(na);
+            }
+            const result = newAuditors.length > 0 ? await transaction.insert(this.tableName, newAuditors) : null;
+            return result ? result.affectedRows === auditors.length : true;
+        }
+
+        async getAllAuditors(tenderId) {
+            const sql = 'SELECT ma.aid, ma.tid FROM ' + this.tableName + ' ma' +
+                '  LEFT JOIN ' + this.ctx.service.tender.tableName + ' t On ma.tid = t.id' +
+                '  WHERE t.id = ?' +
+                '  GROUP BY  ma.aid';
+            const sqlParam = [tenderId];
+            return this.db.query(sql, sqlParam);
+        }
+    }
+
+    return ChangePlanAudit;
+};

+ 247 - 0
app/service/change_plan_list.js

@@ -0,0 +1,247 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/8/14
+ * @version
+ */
+
+const audit = require('../const/audit');
+
+module.exports = app => {
+    class ChangePlanList extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'change_plan_list';
+        }
+
+        /**
+         * 取出变更令清单列表,并按台账清单在前,空白清单在后排序
+         * @return {void}
+         */
+        async getList(cpid, transaction = false) {
+            const sql = 'SELECT * FROM ?? WHERE `cpid` = ? ORDER BY `id` asc';
+            const sqlParam = [this.tableName, cpid];
+            return transaction ? await transaction.query(sql, sqlParam) : await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 添加空白变更清单
+         * @return {void}
+         */
+        async add(data) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+            const insertData = {
+                tid: this.ctx.tender.id,
+                cpid: this.ctx.change.id,
+            };
+            const newData = this._.assign(insertData, data);
+            // 新增工料
+            const result = await this.db.insert(this.tableName, newData);
+            if (result.affectedRows === 0) {
+                throw '新增空白清单数据失败';
+            }
+            return await this.getDataById(result.insertId);
+        }
+
+        /**
+         * 批量添加空白变更清单
+         * @return {void}
+         */
+        async batchAdd(data) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+            const num = data.num ? parseInt(data.num) : 0;
+            if (num < 1 || num > 100) {
+                throw '批量添加的空白清单数目不能小于1或大于100';
+            }
+            const insertArray = [];
+            for (let i = 0; i < num; i++) {
+                const insertData = {
+                    tid: this.ctx.tender.id,
+                    cpid: this.ctx.change.id,
+                };
+                insertArray.push(insertData);
+            }
+            // 新增工料
+            const result = await this.db.insert(this.tableName, insertArray);
+            if (result.affectedRows !== num) {
+                throw '批量添加空白清单数据失败';
+            }
+            // 获取刚批量添加的所有list
+            for (let j = 0; j < num; j++) {
+                insertArray[j].id = result.insertId + j;
+            }
+            return insertArray;
+        }
+
+        /**
+         * 删除变更清单
+         * @param {int} id 清单id
+         * @return {void}
+         */
+        async del(ids) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 判断是否可删
+                await transaction.delete(this.tableName, { id: ids });
+                // 重新算变更令总额
+                await this.calcCamountSum(transaction);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 修改变更清单
+         * @param {Object} data 工料内容
+         * @param {int} order 期数
+         * @return {void}
+         */
+        async save(data, order) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // const mb_id = data.mb_id;
+                // delete data.mb_id;
+                await transaction.update(this.tableName, data);
+                // await this.calcQuantityByML(transaction, mb_id);
+                await this.calcCamountSum(transaction);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 修改变更清单 复制粘贴
+         * @param {Object} datas 修改内容
+         * @return {void}
+         */
+        async saveDatas(datas) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+            // 判断是否可修改
+            // 判断t_type是否为费用
+            const transaction = await this.db.beginTransaction();
+            try {
+                const insertArray = [];
+                const updateArray = [];
+                for (const d of datas) {
+                    if (d.id) {
+                        updateArray.push(d);
+                    } else {
+                        d.tid = this.ctx.tender.id;
+                        d.cpid = this.ctx.change.id;
+                        insertArray.push(d);
+                    }
+                }
+                if (insertArray.length > 0) await transaction.insert(this.tableName, insertArray);
+                if (updateArray.length > 0) await transaction.updateRows(this.tableName, updateArray);
+                await this.calcCamountSum(transaction);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async calcCamountSum(transaction, updateTpDecimal = false) {
+            // const sql = 'SELECT SUM(ROUND(`camount`*`unit_price`, )) as total_price FROM ?? WHERE cid = ?';
+            // const sqlParam = [this.tableName, this.change.cid];
+            // const tp = await transaction.queryOne(sql, sqlParam);
+            // 防止小数位不精确,采用取值计算
+            const sql = 'SELECT unit_price, spamount FROM ?? WHERE cpid = ?';
+            const sqlParam = [this.tableName, this.ctx.change.id];
+            const changeList = await transaction.query(sql, sqlParam);
+            let total_price = 0;
+            const tp_decimal = this.ctx.change.decimal.tp;
+            for (const cl of changeList) {
+                total_price = this.ctx.helper.accAdd(total_price, this.ctx.helper.mul(cl.unit_price, cl.spamount, tp_decimal));
+            }
+            const updateData = {
+                total_price,
+            };
+            // if (updateTpDecimal) {
+            //     updateData.tp_decimal = tp_decimal;
+            // }
+            const options = {
+                where: {
+                    id: this.ctx.change.id,
+                },
+            };
+            await transaction.update(this.ctx.service.changePlan.tableName, updateData, options);
+        }
+
+        async insertAuditAmount(transaction, cpId, lastAudit = false) {
+            const changeList = await this.getList(cpId, transaction);
+            const updateArray = [];
+            for (const c of changeList) {
+                const audit_amount = c.audit_amount !== null && c.audit_amount !== '' ? c.audit_amount.split(',') : [];
+                audit_amount.push(c.spamount);
+                updateArray.push({
+                    id: c.id,
+                    audit_amount: audit_amount.join(','),
+                    samount: lastAudit ? c.spamount : null,
+                });
+            }
+            if (updateArray.length > 0) await transaction.updateRows(this.tableName, updateArray);
+        }
+
+        async delAuditAmount(transaction, cpId) {
+            const changeList = await this.getList(cpId, transaction);
+            const updateArray = [];
+            for (const c of changeList) {
+                updateArray.push({
+                    id: c.id,
+                    audit_amount: null,
+                    spamount: c.camount,
+                });
+            }
+            if (updateArray.length > 0) await transaction.updateRows(this.tableName, updateArray);
+            await this.calcCamountSum(transaction);
+        }
+
+        /**
+         * 报表用
+         * Tony Kang
+         * @param {tid} tid - 标段id
+         * @return {void}
+         */
+        async getChangeBills(tid, onlyChecked) {
+            const sql = 'SELECT cb.*' +
+                '  FROM ' + this.tableName + ' cb' +
+                '  LEFT JOIN ' + this.ctx.service.changePlan.tableName + ' c ON cb.cpid = c.id' +
+                '  WHERE c.tid = ? ' + (onlyChecked ? `and c.status = ${audit.changeApply.status.checked}` : '');
+            const param = [tid];
+            const result = await this.db.query(sql, param);
+            return result;
+        }
+    }
+
+    return ChangePlanList;
+};

+ 1 - 1
app/service/ledger_history.js

@@ -29,7 +29,7 @@ module.exports = app => {
          */
          */
         async getLatestHistory(tid) {
         async getLatestHistory(tid) {
             const his = await this.db.select(this.tableName, {
             const his = await this.db.select(this.tableName, {
-                where: { tid },
+                where: { tid, valid: 1 },
                 orders: [['in_time', 'desc']],
                 orders: [['in_time', 'desc']],
                 limit: 1, offset: 0,
                 limit: 1, offset: 0,
             });
             });

+ 14 - 0
app/service/material.js

@@ -139,6 +139,7 @@ module.exports = app => {
             const newMaterial = {
             const newMaterial = {
                 tid: tenderId,
                 tid: tenderId,
                 order,
                 order,
+                period: data.period,
                 in_time: new Date(),
                 in_time: new Date(),
                 times: 1,
                 times: 1,
                 status: auditConst.status.uncheck,
                 status: auditConst.status.uncheck,
@@ -197,6 +198,19 @@ module.exports = app => {
         }
         }
 
 
         /**
         /**
+         * 编辑计量期
+         *
+         * @param {Number} mid - 第N期
+         * @param {String} period - 开始-截止时间
+         * @return {Promise<void>}
+         */
+        async saveMaterial(mid, period) {
+            await this.db.update(this.tableName, {
+                period,
+            }, { where: { id: mid } });
+        }
+
+        /**
          * 删除材料调差期
          * 删除材料调差期
          *
          *
          * @param {Number} id - 期Id
          * @param {Number} id - 期Id

+ 2 - 1
app/service/material_list.js

@@ -160,7 +160,7 @@ module.exports = app => {
                     for (const mb of materialBills) {
                     for (const mb of materialBills) {
                         const newLists = {
                         const newLists = {
                             tid: this.ctx.tender.id,
                             tid: this.ctx.tender.id,
-                            order: this.ctx.material.order,
+                            order: mb.order,
                             mid: this.ctx.material.id,
                             mid: this.ctx.material.id,
                             mb_id: mb.mb_id,
                             mb_id: mb.mb_id,
                             gcl_id: xmj.gcl_id,
                             gcl_id: xmj.gcl_id,
@@ -168,6 +168,7 @@ module.exports = app => {
                             mx_id: xmj.mx_id ? xmj.mx_id : '',
                             mx_id: xmj.mx_id ? xmj.mx_id : '',
                             gather_qty: xmj.gather_qty ? xmj.gather_qty : null,
                             gather_qty: xmj.gather_qty ? xmj.gather_qty : null,
                             quantity: mb.quantity,
                             quantity: mb.quantity,
+                            expr: mb.expr,
                             in_time: new Date(),
                             in_time: new Date(),
                         };
                         };
                         list.push(newLists);
                         list.push(newLists);

+ 12 - 0
app/service/report.js

@@ -281,9 +281,21 @@ module.exports = app => {
                     case 'mem_change_apply':
                     case 'mem_change_apply':
                         rst[filter] = await service.reportMemory.getChangeApplyData(params.tender_id);
                         rst[filter] = await service.reportMemory.getChangeApplyData(params.tender_id);
                         break;
                         break;
+                    case 'mem_change_apply_bills':
+                        rst[filter] = await service.reportMemory.getChangeApplyBillsData(params.tender_id);
+                        break;
                     case 'mem_change_apply_audit':
                     case 'mem_change_apply_audit':
                         rst[filter] = await service.reportMemory.getChangeApplyAuditData(params.tender_id);
                         rst[filter] = await service.reportMemory.getChangeApplyAuditData(params.tender_id);
                         break;
                         break;
+                    case 'mem_change_plan':
+                        rst[filter] = await service.reportMemory.getChangePlanData(params.tender_id);
+                        break;
+                    case 'mem_change_plan_bills':
+                        rst[filter] = await service.reportMemory.getChangePlanBillsData(params.tender_id);
+                        break;
+                    case 'mem_change_plan_audit':
+                        rst[filter] = await service.reportMemory.getChangePlanAuditData(params.tender_id);
+                        break;
                     case 'mem_change_project':
                     case 'mem_change_project':
                         rst[filter] = await service.reportMemory.getChangeProjectData(params.tender_id);
                         rst[filter] = await service.reportMemory.getChangeProjectData(params.tender_id);
                         break;
                         break;

+ 113 - 2
app/service/report_memory.js

@@ -759,6 +759,8 @@ module.exports = app => {
                     d.c_tp = this.ctx.helper.mul(d.c_qty, d.unit_price, decimal.tp);
                     d.c_tp = this.ctx.helper.mul(d.c_qty, d.unit_price, decimal.tp);
                     d.s_qty = d.samount ? parseFloat(d.samount) : 0;
                     d.s_qty = d.samount ? parseFloat(d.samount) : 0;
                     d.s_tp = this.ctx.helper.mul(d.s_qty, d.unit_price, decimal.tp);
                     d.s_tp = this.ctx.helper.mul(d.s_qty, d.unit_price, decimal.tp);
+                    d.sp_qty = d.spamount;
+                    d.sp_tp = this.ctx.helper.mul(d.sp_qty, d.unit_price, decimal.tp);
 
 
                     const auditAmount = d.audit_amount ? d.audit_amount.split(',') : [];
                     const auditAmount = d.audit_amount ? d.audit_amount.split(',') : [];
                     const relaChange = ctx.helper._.find(change, {cid: d.cid});
                     const relaChange = ctx.helper._.find(change, {cid: d.cid});
@@ -840,10 +842,30 @@ module.exports = app => {
 
 
         async _generateChangeApply(tid) {
         async _generateChangeApply(tid) {
             if (!!this.changeApplyData) return;
             if (!!this.changeApplyData) return;
-            this.changeApplyData = {};
+            const helper = this.ctx.helper;
+            const decimal = this.ctx.tender.info.decimal;
             const where = { tid };
             const where = { tid };
             if (this.ctx.session.sessionProject.page_show.isOnlyChecked) where.status = audit.changeApply.status.checked;
             if (this.ctx.session.sessionProject.page_show.isOnlyChecked) where.status = audit.changeApply.status.checked;
-            this.changeApplyData = { data: await this.ctx.service.changeApply.getAllDataByCondition({ where }) };
+            const apply = await this.ctx.service.changeApply.getAllDataByCondition({ where });
+            const bills = await this.ctx.service.changeApplyList.getChangeBills(tid, this.ctx.session.sessionProject.page_show.isOnlyChecked);
+            bills.forEach(x => {
+                const a = apply.find(y => { return y.id === x.caid });
+                const a_decimal = a.decimal ? JSON.stringify(a.decimal) : { tp: decimal.tp, up: decimal.up };
+                x.o_tp = helper.mul(x.oamount, x.unit_price, a_decimal.tp);
+                x.c_tp = helper.mul(x.camount, x.unit_price, a_decimal.tp);
+            });
+            bills.sort(function(a, b ) {
+                const aCIndex = apply.findIndex(function (c) {
+                    return c.id === a.caid;
+                });
+                const bCIndex = apply.findIndex(function (c) {
+                    return c.id === b.caid;
+                });
+                return aCIndex === bCIndex
+                    ? helper.compareCode(a.code, b.code)
+                    : aCIndex - bCIndex;
+            });
+            this.changeApplyData = { data: apply, bills };
         }
         }
 
 
         async _generateChangeApplyAudit() {
         async _generateChangeApplyAudit() {
@@ -866,6 +888,14 @@ module.exports = app => {
             }
             }
         }
         }
 
 
+        async getChangeApplyBillsData(tid, sid, fields) {
+            try {
+                await this._generateChangeApply(tid);
+                return this.changeApplyData.bills;
+            } catch (err) {
+                return [];
+            }
+        }
 
 
         async getChangeApplyAuditData(tid, sid, fields) {
         async getChangeApplyAuditData(tid, sid, fields) {
             try {
             try {
@@ -878,6 +908,87 @@ module.exports = app => {
             }
             }
         }
         }
 
 
+        async _generateChangePlan(tid) {
+            if (!!this.changePlanData) return;
+            const helper = this.ctx.helper;
+            const decimal = this.ctx.tender.info.decimal;
+            this.changePlanData = {};
+            const where = { tid };
+            if (this.ctx.session.sessionProject.page_show.isOnlyChecked) where.status = audit.changeApply.status.checked;
+            const data = await this.ctx.service.changePlan.getAllDataByCondition({ where });
+            const bills = await this.ctx.service.changePlanList.getChangeBills(tid, this.ctx.session.sessionProject.page_show.isOnlyChecked);
+            bills.forEach(x => {
+                const plan = data.find(y => { return y.id === x.cpid });
+                const p_decimal = plan.decimal ? JSON.stringify(plan.decimal) : { tp: decimal.tp, up: decimal.up };
+                x.o_tp = helper.mul(x.oamount, x.unit_price, p_decimal.tp);
+                x.c_tp = helper.mul(x.camount, x.unit_price, p_decimal.tp);
+                x.s_tp = helper.mul(x.samount, x.unit_price, p_decimal.tp);
+                x.sp_tp = helper.mul(x.spamount, x.unit_price, p_decimal.tp);
+
+                const auditAmount = x.audit_amount ? x.audit_amount.split(',') : [];
+                for (const [i, aa] of auditAmount.entries()) {
+                    const amountField = 'qty_' + (i+1), tpField = 'tp_' + (i+1);
+                    x[amountField] = aa ? parseFloat(aa) : 0;
+                    x[tpField] = helper.mul(x[amountField], x.unit_price, p_decimal.tp);
+                    if (plan) {
+                        plan[tpField] = helper.add(plan[tpField], x[tpField]);
+                    }
+                }
+            });
+            bills.sort(function(a, b ) {
+                const aCIndex = data.findIndex(function (c) {
+                    return c.id === a.cpid;
+                });
+                const bCIndex = data.findIndex(function (c) {
+                    return c.id === b.cpid;
+                });
+                return aCIndex === bCIndex
+                    ? helper.compareCode(a.code, b.code)
+                    : aCIndex - bCIndex;
+            });
+            this.changePlanData = { data, bills };
+        }
+
+        async _generateChangePlanAudit() {
+            if (!!this.changePlanData.audit) return;
+
+            this.changePlanData.audit = [];
+            for (const c of this.changePlanData.data) {
+                const changeAudit = await this.ctx.service.changePlanAudit.getAllDataByCondition({ where: { cpid: c.id, times: c.times }});
+                const changeAuditFilter = this.ctx.helper.filterLastestData(changeAudit, ['aid']);
+                this.changePlanData.audit.push(...changeAuditFilter);
+            }
+        }
+
+        async getChangePlanData(tid, sid, fields) {
+            try {
+                await this._generateChangePlan(tid);
+                return this.changePlanData.data;
+            } catch (err) {
+                return [];
+            }
+        }
+
+        async getChangePlanBillsData(tid, sid, fields) {
+            try {
+                await this._generateChangePlan(tid);
+                return this.changePlanData.bills;
+            } catch (err) {
+                return [];
+            }
+        }
+
+        async getChangePlanAuditData(tid, sid, fields) {
+            try {
+                await this._generateChangePlan(tid);
+                await this._generateChangePlanAudit();
+                return this.changePlanData.audit;
+            } catch (err) {
+                this.ctx.log(err);
+                return [];
+            }
+        }
+
         async _generateChangeProject(tid) {
         async _generateChangeProject(tid) {
             if (!!this.changeProjectData) return;
             if (!!this.changeProjectData) return;
             const where = { tid };
             const where = { tid };

+ 1 - 1
app/service/rpt_gather_memory.js

@@ -527,7 +527,7 @@ module.exports = app => {
                 }
                 }
             }
             }
 
 
-            this.resultTree.generateSortNodes();
+            this.resultTree.resortChildrenDefault();
             gatherUtils.completeGatherData(this.resultTree.nodes, completeDatas);
             gatherUtils.completeGatherData(this.resultTree.nodes, completeDatas);
             return this.resultTree.getDefaultDatas();
             return this.resultTree.getDefaultDatas();
         }
         }

+ 3 - 1
app/service/tender.js

@@ -115,6 +115,8 @@ module.exports = app => {
                     '        t.id IN ( SELECT cpa.`tid` FROM ' + this.ctx.service.changeProjectAudit.tableName + ' AS cpa WHERE cpa.`aid` = ' + session.sessionUser.accountId + ' GROUP BY cpa.`tid`))' : '';
                     '        t.id IN ( SELECT cpa.`tid` FROM ' + this.ctx.service.changeProjectAudit.tableName + ' AS cpa WHERE cpa.`aid` = ' + session.sessionUser.accountId + ' GROUP BY cpa.`tid`))' : '';
                 const changeApplySql = this.ctx.session.sessionProject.page_show.openChangeApply ? '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
                 const changeApplySql = this.ctx.session.sessionProject.page_show.openChangeApply ? '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
                     '        t.id IN ( SELECT caa.`tid` FROM ' + this.ctx.service.changeApplyAudit.tableName + ' AS caa WHERE caa.`aid` = ' + session.sessionUser.accountId + ' GROUP BY caa.`tid`))' : '';
                     '        t.id IN ( SELECT caa.`tid` FROM ' + this.ctx.service.changeApplyAudit.tableName + ' AS caa WHERE caa.`aid` = ' + session.sessionUser.accountId + ' GROUP BY caa.`tid`))' : '';
+                const changePlanSql = this.ctx.session.sessionProject.page_show.openChangePlan ? '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
+                    '        t.id IN ( SELECT cpla.`tid` FROM ' + this.ctx.service.changePlanAudit.tableName + ' AS cpla WHERE cpla.`aid` = ' + session.sessionUser.accountId + ' GROUP BY cpla.`tid`))' : '';
                 // 协审sql
                 // 协审sql
                 const changeProjectXsSql = this.ctx.session.sessionProject.page_show.openChangeProject ? '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
                 const changeProjectXsSql = this.ctx.session.sessionProject.page_show.openChangeProject ? '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
                     '        t.id IN ( SELECT cpxa.`tid` FROM ' + this.ctx.service.changeProjectXsAudit.tableName + ' AS cpxa WHERE cpxa.`aid` = ' + session.sessionUser.accountId + ' GROUP BY cpxa.`tid`))' : '';
                     '        t.id IN ( SELECT cpxa.`tid` FROM ' + this.ctx.service.changeProjectXsAudit.tableName + ' AS cpxa WHERE cpxa.`aid` = ' + session.sessionUser.accountId + ' GROUP BY cpxa.`tid`))' : '';
@@ -146,7 +148,7 @@ module.exports = app => {
                     // 参与审批 预付款 的标段
                     // 参与审批 预付款 的标段
                     '    OR (t.id IN ( SELECT ad.`tid` FROM ?? AS ad WHERE ad.`audit_id` = ? GROUP BY ad.`tid`))' +
                     '    OR (t.id IN ( SELECT ad.`tid` FROM ?? AS ad WHERE ad.`audit_id` = ? GROUP BY ad.`tid`))' +
                     // 参与审批 变更立项书及变更申请 的标段
                     // 参与审批 变更立项书及变更申请 的标段
-                    changeProjectSql + changeApplySql + changeProjectXsSql +
+                    changeProjectSql + changeApplySql + changePlanSql + changeProjectXsSql +
                     // 游客权限的标段
                     // 游客权限的标段
                     '    OR (t.id IN ( SELECT tt.`tid` FROM ?? AS tt WHERE tt.`user_id` = ?))' +
                     '    OR (t.id IN ( SELECT tt.`tid` FROM ?? AS tt WHERE tt.`user_id` = ?))' +
                     // 未参与,但可见的标段
                     // 未参与,但可见的标段

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

@@ -51,6 +51,9 @@
                         </div>
                         </div>
                     </div>
                     </div>
                 </div>
                 </div>
+                <div class="d-inline-block">
+                    <span class="ml-3">本页小计:<%- page_total %>元</span><span class="ml-3">合计:<%- tp %>元</span>
+                </div>
             </div>
             </div>
             <% if (tender.user_id === uid) { %>
             <% if (tender.user_id === uid) { %>
             <div class="ml-auto">
             <div class="ml-auto">
@@ -67,14 +70,14 @@
                     <thead>
                     <thead>
                     <tr>
                     <tr>
                         <th width="13%" id="sort_change">变更申请编号</th><th width="15%">变更工程名称</th>
                         <th width="13%" id="sort_change">变更申请编号</th><th width="15%">变更工程名称</th>
-                        <th width="10%">创建时间</th><th width="15%">变更立项书</th><th width="13%">变更通知书</th>
+                        <th width="10%">变更金额</th><th width="15%">变更立项书</th><th width="13%">变更通知书</th>
                         <th width="7%">通知书发出人</th><th width="7%">审批状态</th><th width="13%">审批进度</th><th width="5%">操作</th>
                         <th width="7%">通知书发出人</th><th width="7%">审批状态</th><th width="13%">审批进度</th><th width="5%">操作</th>
                     </tr>
                     </tr>
                     </thead>
                     </thead>
                     <tbody id="changeList">
                     <tbody id="changeList">
                     <% for (const c of changes) { %>
                     <% for (const c of changes) { %>
                         <tr><td><a href="/tender/<%- tender.id %>/change/apply/<%- c.id %>/information"><%- c.code %></a></td><td><%- c.name %></td>
                         <tr><td><a href="/tender/<%- tender.id %>/change/apply/<%- c.id %>/information"><%- c.code %></a></td><td><%- c.name %></td>
-                            <td><%- ctx.helper.formatFullDate(c.in_time) %></td>
+                            <td style="text-align: right"><%- c.total_price %></td>
                             <td><%- c.project_code %></td>
                             <td><%- c.project_code %></td>
                             <td><% if (c.notice_code) { %><a href="/tender/<%- tender.id %>/change/apply/<%- c.id %>/information/notice"><%- c.notice_code %></a><% } %></td>
                             <td><% if (c.notice_code) { %><a href="/tender/<%- tender.id %>/change/apply/<%- c.id %>/information/notice"><%- c.notice_code %></a><% } %></td>
                             <td><%- c.account_name %></td>
                             <td><%- c.account_name %></td>

+ 118 - 91
app/view/change/apply_information.ejs

@@ -10,6 +10,11 @@
                 <div class="d-inline-block" id="change-apply-code">
                 <div class="d-inline-block" id="change-apply-code">
                     <%- change.code %>
                     <%- change.code %>
                 </div>
                 </div>
+                <% if (!change.readOnly) { %>
+                    <div class="d-inline-block">
+                        <a href="#shuliangguize" data-toggle="modal" data-target="#shuliangguize" class="btn btn-outline-primary btn-sm"><i class="fa fa-cog"></i></a>
+                    </div>
+                <% } %>
             </div>
             </div>
             <div class="ml-auto" id="sp-btn">
             <div class="ml-auto" id="sp-btn">
                 <% if (ctx.change.status === auditConst.status.uncheck) { %>
                 <% if (ctx.change.status === auditConst.status.uncheck) { %>
@@ -38,109 +43,122 @@
     </div>
     </div>
     <div class="content-wrap">
     <div class="content-wrap">
         <div class="c-body">
         <div class="c-body">
-            <div class="sjs-height-0">
-                <div class="col-xl-8 mx-auto">
-                    <h4 class="text-center py-2">工程变更申请书</h4>
-                    <table class="table table-bordered" id="apply-table">
-                        <tr>
-                            <th width="120" class="text-center" style="vertical-align: middle">变更申请编号<b class="text-danger">*&nbsp;</b></th>
-                            <td><input class="form-control form-control-sm" value="<%- change.code %>" data-name="code" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
-                        </tr>
-                        <tr>
-                            <th width="120" class="text-center" style="vertical-align: middle">变更工程名称<b class="text-danger">*&nbsp;</b></th>
-                            <td><input class="form-control form-control-sm" value="<%- change.name %>" data-name="name" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
-                            <th width="140" class="text-center" style="vertical-align: middle">桩号</th>
-                            <td><input class="form-control form-control-sm" type="text" value="<%- change.peg %>" data-name="peg" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
-                        </tr>
-                        <tr>
-                            <th width="" class="text-center" style="vertical-align: middle">原设计图名称</th>
-                            <td><input class="form-control form-control-sm" value="<%- change.org_name %>" data-name="org_name" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
-                            <th width="" class="text-center" style="vertical-align: middle">图号</th>
-                            <td><input class="form-control form-control-sm" type="text" value="<%- change.new_code %>" data-name="new_code" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
-                        </tr>
-                        <tr>
-                            <th width="" class="text-center" style="vertical-align: middle">变更设计名称</th>
-                            <td><input class="form-control form-control-sm" value="<%- change.design_name %>" data-name="design_name" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
-                            <th width="" class="text-center" style="vertical-align: middle">变更图号</th>
-                            <td><input class="form-control form-control-sm" value="<%- change.c_new_code %>" data-name="c_new_code" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
-                        </tr>
-                        <tr>
-                            <th width="" class="text-center" style="vertical-align: middle">变更立项编号</th>
-                            <td><input class="form-control form-control-sm" value="<%- change.project_code %>" data-name="project_code" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder="自动读取,没有就为空,可编辑"></td>
-                            <th width="" class="text-center" style="vertical-align: middle">原工程造价(元)</th>
-                            <td><input class="form-control form-control-sm" type="text" value="<%- change.org_price %>" data-name="org_price" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
-                        </tr>
-                        <tr>
-                            <th width="" class="text-center" style="vertical-align: middle">工程变更类别</th>
-                            <td><input class="form-control form-control-sm" type="text" value="<%- change.class %>" data-name="class" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
-                            <th width="" class="text-center" style="vertical-align: middle">变更后工程造价(元)</th>
-                            <td><input class="form-control form-control-sm" type="text" value="<%- change.change_price %>" data-name="change_price" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
-                        </tr>
-                        <tr>
-                            <th width="" class="text-center" style="vertical-align: middle">工程变更性质</th>
-                            <% if (change.readOnly) { %>
-                            <td><input class="form-control form-control-sm" type="text" value="<%- change.quality %>" data-name="quality" readonly placeholder=""></td>
-                            <% } else { %>
-                            <td><select class="form-control form-control-sm" name="quality" data-name="quality" <% if (change.readOnly) { %>readonly<% } %>>
-                                <% for (const q in changeConst.quality) { %>
-                                    <% const cQuality = changeConst.quality[q] %>
-                                    <option <% if (cQuality.name === change.quality) { %> selected<% } %>><%- cQuality.name %></option>
+            <div class="sjs-height-0" data-spy="scroll" data-target="#navbar-example">
+                <div class="row mx-0">
+                    <!--<div class="col-xl-2" id="navbar-example">-->
+                        <!--<ul class="nav flex-column nav-pills nav-stacked nav-padding sticky-top">-->
+                            <!--<li class="nav-item"><a class="nav-link active" href="#xinxi">基本信息</a></li>-->
+                            <!--<li class="nav-item"><a class="nav-link" href="#qingdan">变更清单</a></li>-->
+                            <!--<li class="nav-item"><a class="nav-link" href="#fujian">附件</a></li>-->
+                        <!--</ul>-->
+                    <!--</div>-->
+                    <div class="col-xl-8 mx-auto">
+                        <h4 id="xinxi" class="text-center py-2">工程变更申请书</h4>
+                        <h5>基本信息</h5>
+                        <table class="table table-bordered" id="apply-table">
+                            <tr>
+                                <th width="120" class="text-center" style="vertical-align: middle">变更申请编号<b class="text-danger">*&nbsp;</b></th>
+                                <td><input class="form-control form-control-sm" value="<%- change.code %>" data-name="code" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
+                            </tr>
+                            <tr>
+                                <th width="120" class="text-center" style="vertical-align: middle">变更工程名称<b class="text-danger">*&nbsp;</b></th>
+                                <td><input class="form-control form-control-sm" value="<%- change.name %>" data-name="name" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
+                                <th width="140" class="text-center" style="vertical-align: middle">桩号</th>
+                                <td><input class="form-control form-control-sm" type="text" value="<%- change.peg %>" data-name="peg" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
+                            </tr>
+                            <tr>
+                                <th width="" class="text-center" style="vertical-align: middle">原设计图名称</th>
+                                <td><input class="form-control form-control-sm" value="<%- change.org_name %>" data-name="org_name" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
+                                <th width="" class="text-center" style="vertical-align: middle">图号</th>
+                                <td><input class="form-control form-control-sm" type="text" value="<%- change.new_code %>" data-name="new_code" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
+                            </tr>
+                            <tr>
+                                <th width="" class="text-center" style="vertical-align: middle">变更设计名称</th>
+                                <td><input class="form-control form-control-sm" value="<%- change.design_name %>" data-name="design_name" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
+                                <th width="" class="text-center" style="vertical-align: middle">变更图号</th>
+                                <td><input class="form-control form-control-sm" value="<%- change.c_new_code %>" data-name="c_new_code" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
+                            </tr>
+                            <tr>
+                                <th width="" class="text-center" style="vertical-align: middle">变更立项编号</th>
+                                <td><input class="form-control form-control-sm" value="<%- change.project_code %>" data-name="project_code" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder="自动读取,没有就为空,可编辑"></td>
+                                <th width="" class="text-center" style="vertical-align: middle">原工程造价(元)</th>
+                                <td><input class="form-control form-control-sm" type="text" value="<%- change.org_price %>" data-name="org_price" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
+                            </tr>
+                            <tr>
+                                <th width="" class="text-center" style="vertical-align: middle">工程变更类别</th>
+                                <td><input class="form-control form-control-sm" type="text" value="<%- change.class %>" data-name="class" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
+                                <th width="" class="text-center" style="vertical-align: middle">变更后工程造价(元)</th>
+                                <td><input class="form-control form-control-sm" type="text" value="<%- change.change_price %>" data-name="change_price" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
+                            </tr>
+                            <tr>
+                                <th width="" class="text-center" style="vertical-align: middle">工程变更性质</th>
+                                <% if (change.readOnly) { %>
+                                    <td><input class="form-control form-control-sm" type="text" value="<%- change.quality %>" data-name="quality" readonly placeholder=""></td>
+                                <% } else { %>
+                                    <td><select class="form-control form-control-sm" name="quality" data-name="quality" <% if (change.readOnly) { %>readonly<% } %>>
+                                            <% for (const q in changeConst.quality) { %>
+                                                <% const cQuality = changeConst.quality[q] %>
+                                                <option <% if (cQuality.name === change.quality) { %> selected<% } %>><%- cQuality.name %></option>
+                                            <% } %>
+                                        </select></td>
                                 <% } %>
                                 <% } %>
-                            </select></td>
-                            <% } %>
-                            <th width="" class="text-center" style="vertical-align: middle">工程造价增减(元)</th>
-                            <td><input class="form-control form-control-sm" type="text" value="<%- change.crease_price %>" data-name="crease_price" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
-                        </tr>
-                        <tr>
-                            <th width="" class="text-center" style="vertical-align: middle">变更原因<b class="text-danger">*&nbsp;</b></th>
-                            <td colspan="3"><textarea class="form-control form-control-sm" data-name="reason" <% if (change.readOnly) { %>readonly<% } %> rows="3"><%- change.reason %></textarea></td>
-                        </tr>
-                        <tr>
-                            <th width="" class="text-center" style="vertical-align: middle">原设计情况描述</th>
-                            <td colspan="3"><textarea class="form-control form-control-sm" data-name="org_content" <% if (change.readOnly) { %>readonly<% } %> rows="3"><%- change.org_content %></textarea></td>
-                        </tr>
-                        <tr>
-                            <th width="" class="text-center" style="vertical-align: middle">现场实际情况描述</th>
-                            <td colspan="3"><textarea class="form-control form-control-sm" data-name="site_content" <% if (change.readOnly) { %>readonly<% } %> rows="3"><%- change.site_content %></textarea></td>
-                        </tr>
-                        <tr>
-                            <th width="" class="text-center" style="vertical-align: middle">变更内容</th>
-                            <td colspan="3"><textarea class="form-control form-control-sm" data-name="content" <% if (change.readOnly) { %>readonly<% } %> rows="3"><%- change.content %></textarea></td>
-                        </tr>
-                    </table>
-                    <table class="table table-bordered">
-                        <thead>
-                        <tr>
-                            <th></th>
-                            <th>附件</th>
-                            <th>上传者</th>
-                            <th>资料类型</th>
-                            <th>上传时间</th>
-                            <th>操作</th>
-                        </tr>
-                        </thead>
-                        <tbody>
-                        <!--<tr>-->
+                                <th width="" class="text-center" style="vertical-align: middle">工程造价增减(元)</th>
+                                <td><input class="form-control form-control-sm" type="text" value="<%- change.crease_price %>" data-name="crease_price" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
+                            </tr>
+                            <tr>
+                                <th width="" class="text-center" style="vertical-align: middle">变更原因<b class="text-danger">*&nbsp;</b></th>
+                                <td colspan="3"><textarea class="form-control form-control-sm" data-name="reason" <% if (change.readOnly) { %>readonly<% } %> rows="3"><%- change.reason %></textarea></td>
+                            </tr>
+                            <tr>
+                                <th width="" class="text-center" style="vertical-align: middle">原设计情况描述</th>
+                                <td colspan="3"><textarea class="form-control form-control-sm" data-name="org_content" <% if (change.readOnly) { %>readonly<% } %> rows="3"><%- change.org_content %></textarea></td>
+                            </tr>
+                            <tr>
+                                <th width="" class="text-center" style="vertical-align: middle">现场实际情况描述</th>
+                                <td colspan="3"><textarea class="form-control form-control-sm" data-name="site_content" <% if (change.readOnly) { %>readonly<% } %> rows="3"><%- change.site_content %></textarea></td>
+                            </tr>
+                            <tr>
+                                <th width="" class="text-center" style="vertical-align: middle">变更内容</th>
+                                <td colspan="3"><textarea class="form-control form-control-sm" data-name="content" <% if (change.readOnly) { %>readonly<% } %> rows="3"><%- change.content %></textarea></td>
+                            </tr>
+                        </table>
+                        <h5 id="qingdan">变更清单</h5>
+                        <div style="height: 300px;" id="apply-spread"></div>
+                        <h5 id="fujian">附件</h5>
+                        <table class="table table-bordered">
+                            <thead>
+                            <tr>
+                                <th></th>
+                                <th>附件</th>
+                                <th>上传者</th>
+                                <th>资料类型</th>
+                                <th>上传时间</th>
+                                <th>操作</th>
+                            </tr>
+                            </thead>
+                            <tbody>
+                            <!--<tr>-->
                             <!--<td colspan="5"><button type="button" class="btn btn-primary btn-sm"  data-toggle="modal" data-target="#upload-fj">上传附件</button></td>-->
                             <!--<td colspan="5"><button type="button" class="btn btn-primary btn-sm"  data-toggle="modal" data-target="#upload-fj">上传附件</button></td>-->
-                        <!--</tr>-->
-                        <tbody id="file-content">
-                        </tbody>
-                        <!--<tr>-->
+                            <!--</tr>-->
+                            <tbody id="file-content">
+                            </tbody>
+                            <!--<tr>-->
                             <!--<td>1</td>-->
                             <!--<td>1</td>-->
                             <!--<td>XXX设计图纸</td>-->
                             <!--<td>XXX设计图纸</td>-->
                             <!--<td>仁温书</td>-->
                             <!--<td>仁温书</td>-->
                             <!--<td>2021-12-09 16:58:47</td>-->
                             <!--<td>2021-12-09 16:58:47</td>-->
                             <!--<td><a href="#" class="mr-2"><i class="fa fa-download"></i></a><a href="#" class="text-danger"><i class="fa fa-remove"></i></a></td>-->
                             <!--<td><a href="#" class="mr-2"><i class="fa fa-download"></i></a><a href="#" class="text-danger"><i class="fa fa-remove"></i></a></td>-->
-                        <!--</tr>-->
-                        <!--<tr>-->
+                            <!--</tr>-->
+                            <!--<tr>-->
                             <!--<td>1</td>-->
                             <!--<td>1</td>-->
                             <!--<td>XXX资料说明</td>-->
                             <!--<td>XXX资料说明</td>-->
                             <!--<td>仁温书</td>-->
                             <!--<td>仁温书</td>-->
                             <!--<td>2021-12-09 16:58:47</td>-->
                             <!--<td>2021-12-09 16:58:47</td>-->
                             <!--<td><a href="#" class="mr-2"><i class="fa fa-download"></i></a><a href="#" class="text-danger"><i class="fa fa-remove"></i></a></td>-->
                             <!--<td><a href="#" class="mr-2"><i class="fa fa-download"></i></a><a href="#" class="text-danger"><i class="fa fa-remove"></i></a></td>-->
-                        <!--</tr>-->
-                        </tbody>
-                    </table>
+                            <!--</tr>-->
+                            </tbody>
+                        </table>
+                    </div>
                 </div>
                 </div>
             </div>
             </div>
         </div>
         </div>
@@ -154,4 +172,13 @@
     const whiteList = JSON.parse('<%- JSON.stringify(whiteList) %>');
     const whiteList = JSON.parse('<%- JSON.stringify(whiteList) %>');
     const preUrl = '<%- preUrl %>';
     const preUrl = '<%- preUrl %>';
     const change = JSON.parse(unescape('<%- escape(JSON.stringify(change)) %>'));
     const change = JSON.parse(unescape('<%- escape(JSON.stringify(change)) %>'));
+    let listRule = JSON.parse(unescape('<%- escape(JSON.stringify(listRule)) %>'));
+    const readOnly = <%- change.readOnly %>;
+    const totalPriceUnit = '<%- tpUnit %>';
+    const unitPriceUnit = '<%- upUnit %>';
+    const precision = JSON.parse('<%- JSON.stringify(precision) %>');
+    let changeUnits = JSON.parse('<%- JSON.stringify(changeUnits) %>');
+    changeUnits = _.map(changeUnits, 'unit');
+    changeUnits.push('');
+    let changeList = JSON.parse(unescape('<%- escape(JSON.stringify(changeList)) %>'));
 </script>
 </script>

+ 50 - 0
app/view/change/apply_information_modal.ejs

@@ -96,6 +96,56 @@
             </div>
             </div>
         </div>
         </div>
     </div>
     </div>
+    <!-- 数量读取规则 -->
+    <div class="modal fade" id="shuliangguize" 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="">数据来源</label>
+                        <div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="radio" name="data_source" id="gclGather" value="1" <% if(listRule.source === 1) { %>checked<% } %>>
+                                <label class="form-check-label" for="gclGather">台账数量</label>
+                            </div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="radio" name="data_source" id="dealBills" value="2" <% if(listRule.source === 2) { %>checked<% } %>>
+                                <label class="form-check-label" for="dealBills">签约数量</label>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label for="">匹配规则</label>
+                        <div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="checkbox" id="rule_code" value="code" disabled checked>
+                                <label class="form-check-label" for="rule_code">编号</label>
+                            </div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="checkbox" id="rule_code" value="name" disabled checked>
+                                <label class="form-check-label" for="rule_code">名称</label>
+                            </div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" name="data_rule" type="checkbox" id="rule_unit" value="unit" <% if(ctx.helper._.indexOf(listRule.rule, 'unit') !== -1) { %>checked<% } %>>
+                                <label class="form-check-label" for="rule_unit">单位</label>
+                            </div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" name="data_rule" type="checkbox" id="rule_unitprice" value="unit_price" <% if(ctx.helper._.indexOf(listRule.rule, 'unit_price') !== -1) { %>checked<% } %>>
+                                <label class="form-check-label" for="rule_unitprice">单价</label>
+                            </div>
+                        </div>
+                    </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="setListRule">确定</button>
+                </div>
+            </div>
+        </div>
+    </div>
 <% } %>
 <% } %>
 
 
 <!--审批流程/结果-->
 <!--审批流程/结果-->

+ 2 - 0
app/view/change/index.ejs

@@ -151,5 +151,7 @@
             autoFlashHeight();
             autoFlashHeight();
         }
         }
     });
     });
+    const openChangePlan = parseInt('<%- ctx.session.sessionProject.page_show.openChangePlan %>');
+    const changePlanList = JSON.parse(unescape('<%- escape(JSON.stringify(changePlanList)) %>'));
 </script>
 </script>
 <script src="/public/js/change.js"></script>
 <script src="/public/js/change.js"></script>

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

@@ -392,10 +392,13 @@
     const ledgerConsts = JSON.parse('<%- JSON.stringify(ledgerConsts) %>');
     const ledgerConsts = JSON.parse('<%- JSON.stringify(ledgerConsts) %>');
     const auditStatus = parseInt('<%- auditStatus %>');
     const auditStatus = parseInt('<%- auditStatus %>');
     const changeName = JSON.parse(unescape('<%- escape(JSON.stringify(change.name)) %>'));
     const changeName = JSON.parse(unescape('<%- escape(JSON.stringify(change.name)) %>'));
+    const changeTp = parseFloat('<%- change.total_price ? change.total_price : 0 %>');
+    const changeStatus = parseFloat('<%- change.status %>');
     const touristPermission = parseInt('<%- ctx.tender.touristPermission.file %>');
     const touristPermission = parseInt('<%- ctx.tender.touristPermission.file %>');
     const auditList = JSON.parse(unescape('<%- escape(JSON.stringify(auditList)) %>'));
     const auditList = JSON.parse(unescape('<%- escape(JSON.stringify(auditList)) %>'));
     const precision = JSON.parse('<%- JSON.stringify(precision) %>');
     const precision = JSON.parse('<%- JSON.stringify(precision) %>');
     const whiteList = JSON.parse('<%- JSON.stringify(whiteList) %>');
     const whiteList = JSON.parse('<%- JSON.stringify(whiteList) %>');
+    const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
     let attData = JSON.parse(unescape('<%- escape(JSON.stringify(attList)) %>'));
     let attData = JSON.parse(unescape('<%- escape(JSON.stringify(attList)) %>'));
     let currPageFileData = [];
     let currPageFileData = [];
     autoFlashHeight();
     autoFlashHeight();

+ 21 - 5
app/view/change/modal.ejs

@@ -37,11 +37,27 @@
                         <div class="invalid-feedback" style="display: none" id="bjHint">您输入的编号已存在。</div>
                         <div class="invalid-feedback" style="display: none" id="bjHint">您输入的编号已存在。</div>
                     </div>
                     </div>
                 </div>
                 </div>
-                <div class="form-group">
-                    <label>变更工程名称<b class="text-danger">*</b></label>
-                    <input class="form-control form-control-sm" value="" type="text" id="bj-name">
-                    <div class="invalid-feedback" style="display: none" id="name_error_msg">名称超过100个字,请缩减名称。</div>
-                </div>
+                <% if (ctx.session.sessionProject.page_show.openChangePlan) { %>
+                    <div class="form-group">
+                        <label>关联变更方案</label>
+                        <select class="form-control form-control-sm" id="plan-code">
+                            <option></option>
+                            <% for (const cp of changePlanList) { %>
+                                <% if (ctx.helper._.indexOf(apLists, cp.code) === -1) { %>
+                                    <option><%- cp.code %></option>
+                                <% } %>
+                            <% } %>
+                        </select>
+                    </div>
+                    <input value="" type="hidden" id="bj-name">
+                <% } else { %>
+                    <div class="form-group">
+                        <label>变更工程名称<b class="text-danger">*</b></label>
+                        <input class="form-control form-control-sm" value="" type="text" id="bj-name">
+                        <div class="invalid-feedback" style="display: none" id="name_error_msg">名称超过100个字,请缩减名称。</div>
+                    </div>
+                    <input type="hidden" value="" id="plan-code">
+                <% } %>
             </div>
             </div>
             <div class="modal-footer">
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal" id="addCancel">关闭</button>
                 <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal" id="addCancel">关闭</button>

+ 125 - 0
app/view/change/plan.ejs

@@ -0,0 +1,125 @@
+<% include ../tender/tender_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <div class="btn-group" id="sort-dropdown">
+                        <button type="button" class="btn btn-sm btn-light text-primary dropdown-toggle" data-toggle="dropdown" id="bpaixu">排序:创建时间</button>
+                        <div class="dropdown-menu" aria-labelledby="bpaixu">
+                            <ul class="list-unstyled px-3 mb-0" id="sort-radio">
+                                <li class="mb-2">
+                                    <div class="custom-control custom-radio">
+                                        <input type="radio" class="custom-control-input" id="pai1" name="paizhi" value="time" checked="">
+                                        <label class="custom-control-label" for="pai1">创建时间</label>
+                                    </div>
+                                </li>
+                                <li class="mb-2">
+                                    <div class="custom-control custom-radio">
+                                        <input type="radio" class="custom-control-input" id="pai3" name="paizhi" value="code">
+                                        <label class="custom-control-label" for="pai3">变更方案编号</label>
+                                    </div>
+                                </li>
+                            </ul>
+                            <ul class="list-unstyled px-3 pt-2 mb-0 border-top" id="order-radio">
+                                <li class="mb-2">
+                                    <div class="custom-control custom-radio">
+                                        <input type="radio" class="custom-control-input" id="pdown" name="paixu" value="desc" checked="">
+                                        <label class="custom-control-label" for="pdown">降序</label>
+                                    </div>
+                                </li>
+                                <li class="mb-2">
+                                    <div class="custom-control custom-radio">
+                                        <input type="radio" class="custom-control-input" id="pup" name="paixu" value="asc">
+                                        <label class="custom-control-label" for="pup">升序</label>
+                                    </div>
+                                </li>
+                            </ul>
+                        </div>
+                    </div>
+                </div>
+                <div class="d-inline-block">
+                    <div class="btn-group">
+                        <button type="button" class="btn btn-sm btn-light text-primary dropdown-toggle" data-toggle="dropdown" id="zhankai"><% if (status !== 0) { %><%- filter.statusString[status] %>(<%- filter.count[status] %>)<% } else { %>全部<% } %></button>
+                        <div class="dropdown-menu" aria-labelledby="zhankai" id="status_select">
+                            <% if (status !== 0) { %><a class="dropdown-item" data-val="0" href="javascript:void(0);">全部</a><% } %>
+                            <% for (const fs in filter.status) { %>
+                                <% const f = filter.status[fs]; %>
+                                <% if (f !== status) { %><a class="dropdown-item" data-val="<%- f %>" href="javascript:void(0);"><%- filter.statusString[f] %>(<%- filter.count[f] %>)</a><% } %>
+                            <% } %>
+                        </div>
+                    </div>
+                </div>
+                <div class="d-inline-block">
+                    <span class="ml-3">本页小计:<%- page_total %>元</span><span class="ml-3">合计:<%- tp %>元</span>
+                </div>
+            </div>
+            <% if (tender.user_id === uid) { %>
+            <div class="ml-auto">
+                <a href="#add-bj" data-toggle="modal" data-target="#add-bj" class="btn btn-sm btn-primary pull-right ml-1">新建变更方案</a>
+                <a href="#setting" data-toggle="modal" data-target="#setting" class="btn btn-sm btn-outline-primary pull-right ml-1"><i class="fa fa-cog"></i></a>
+            </div>
+            <% } %>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0">
+                <table class="table table-bordered">
+                    <thead>
+                    <tr>
+                        <th width="15%" id="sort_change">变更方案编号</th><th width="20%">变更工程名称</th>
+                        <th width="10%">变更性质</th><th width="15%">变更金额</th><th width="15%">变更申请编号</th>
+                        <th width="7%">审批状态</th><th width="13%">审批进度</th><th width="5%">操作</th>
+                    </tr>
+                    </thead>
+                    <tbody id="changeList">
+                    <% for (const c of changes) { %>
+                        <tr><td><a href="/tender/<%- tender.id %>/change/plan/<%- c.id %>/information"><%- c.code %></a></td><td><%- c.name %></td>
+                            <td><%- c.quality %></td>
+                            <td style="text-align: right"><%- c.total_price %></td>
+                            <td><%- c.apply_code %></td>
+                            <td>
+                                <% if ((c.status === auditConst.status.uncheck || c.status === auditConst.status.checkNo) && c.uid === ctx.session.sessionUser.accountId) { %>
+                                    <a href="/tender/<%- tender.id %>/change/plan/<%- c.id %>/information" class="btn <%- auditConst.statusButtonClass[c.status] %> btn-sm"><%- auditConst.statusButton[c.status] %></a>
+                                <% } else if (c.status === auditConst.status.checking && c.curAuditor && c.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
+                                    <a href="/tender/<%- tender.id %>/change/plan/<%- c.id %>/information" class="btn <%- auditConst.statusButtonClass[c.status] %> btn-sm"><%- auditConst.statusButton[c.status] %></a>
+                                <% } else { %>
+                                    <span class="<%- auditConst.auditProgressClass[c.status] %>"><%- auditConst.auditProgress[c.status] %></span>
+                                <% } %>
+                            </td>
+                            <% if (c.status === auditConst.status.uncheck) { %>
+                            <td>
+                                待上报
+                            </td>
+                            <% } else { %>
+                            <td><%- c.curAuditor.name %>-<%- c.curAuditor.role %> <span class="<%- auditConst.statusClass[c.status] %>"><%- auditConst.statusString[c.status] %></span></td>
+                            <% } %>
+                            <td>
+                                <% if (c.uid === uid && (c.status === auditConst.status.uncheck || c.status === auditConst.status.checkNo)) { %><a href="#del-bg" cpid="<%= c.id %>" data-toggle="modal" data-target="#del-bg" class="btn btn-outline-danger btn-sm delete-cpid-modal">删除</a><% } %>
+                            </td>
+                        </tr>
+                    <% } %>
+                    </tbody>
+                </table>
+                <!--翻页-->
+                <% include ../layout/page.ejs %>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    autoFlashHeight();
+    const tenderId = parseInt('<%- tender.id %>');
+    const tenderName = JSON.parse(unescape('<%- escape(JSON.stringify(tender.name)) %>'));
+    const dealCode = JSON.parse(unescape('<%- escape(JSON.stringify(dealCode)) %>'));
+    const ruleConst = JSON.parse(unescape('<%- escape(JSON.stringify(ruleConst)) %>'));
+    let codeRule = JSON.parse(unescape('<%- escape(JSON.stringify(codeRule)) %>'));
+    let connectorRule = '<%- c_connector %>';
+    const cRuleFirst = parseInt('<%- c_rule_first %>');
+    const ruleType = parseInt('<%- ruleType %>');
+    const openChangeApply = parseInt('<%- ctx.session.sessionProject.page_show.openChangeApply %>');
+    const rulesType = '<%- rule_type %>';
+    const changeApplyList = JSON.parse(unescape('<%- escape(JSON.stringify(changeApplyList)) %>'));
+</script>

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

@@ -0,0 +1,177 @@
+<% include ../tender/tender_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title"><!--收起详解目录添加类名 fluid -->
+        <div class="title-main d-flex"><!--工具-->
+            <% include ../tender/tender_sub_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <a <% if (returnUrl) { %>href="<%- returnUrl %>"<% } else { %>class="change_plan_sort_link" href="/tender/<%- tender.id %>/change/plan"<% } %>><i class="fa fa-chevron-left mr-2"></i><span>返回</span></a>
+                </div>
+                <div class="d-inline-block" id="change-plan-code">
+                    <%- change.code %>
+                </div>
+                <% if (!change.readOnly) { %>
+                <div class="d-inline-block">
+                    <a href="#shuliangguize" data-toggle="modal" data-target="#shuliangguize" class="btn btn-outline-primary btn-sm"><i class="fa fa-cog"></i></a>
+                </div>
+                <% } %>
+            </div>
+            <div class="ml-auto" id="sp-btn">
+                <% if (ctx.change.status === auditConst.status.uncheck) { %>
+                    <% if (ctx.session.sessionUser.accountId === ctx.change.uid) { %>
+                        <a id="sub-sp-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm">上报审批</a>
+                    <% } else { %>
+                        <a id="sub-sp-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sub-sp" class="btn btn-outline-secondary btn-sm">上报中</a>
+                    <% } %>
+                <% } else if (ctx.change.status === auditConst.status.checking) { %>
+                    <% if (ctx.change.curAuditor && ctx.change.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
+                        <a id="sp-done-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sp-done" class="btn btn-success btn-sm">审批通过</a>
+                        <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm">审批退回</a>
+                    <% } else { %>
+                        <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>
+                <% } else if (ctx.change.status === auditConst.status.checkNo) { %>
+                    <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-muted sp-list-btn">审批退回</a>
+                    <% 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>
+                    <% } %>
+                <% } %>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0" data-spy="scroll" data-target="#navbar-example">
+                <div class="row mx-0">
+                    <!--<div class="col-xl-2" id="navbar-example">-->
+                        <!--<ul class="nav flex-column nav-pills nav-stacked nav-padding sticky-top">-->
+                            <!--<li class="nav-item"><a class="nav-link active" href="#xinxi">基本信息</a></li>-->
+                            <!--<li class="nav-item"><a class="nav-link" href="#qingdan">变更清单</a></li>-->
+                            <!--<li class="nav-item"><a class="nav-link" href="#fujian">附件</a></li>-->
+                        <!--</ul>-->
+                    <!--</div>-->
+                    <div class="col-xl-8 mx-auto">
+                        <h4 id="xinxi" class="text-center py-2">工程变更方案</h4>
+                        <h5>基本信息</h5>
+                        <table class="table table-bordered" id="plan-table">
+                            <tr>
+                                <th width="120" class="text-center" style="vertical-align: middle">变更方案编号<b class="text-danger">*&nbsp;</b></th>
+                                <td><input class="form-control form-control-sm" value="<%- change.code %>" data-name="code" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
+                                <th width="140" class="text-center" style="vertical-align: middle">变更申请编号</th>
+                                <td><input class="form-control form-control-sm" type="text" value="<%- change.apply_code %>" data-name="apply_code" <% if (change.readOnly) { %>readonly<% } %> placeholder="自动读取,没有就为空,可编辑"></td>
+                            </tr>
+                            <tr>
+                                <th width="120" class="text-center" style="vertical-align: middle">变更工程名称<b class="text-danger">*&nbsp;</b></th>
+                                <td><input class="form-control form-control-sm" value="<%- change.name %>" data-name="name" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
+                                <th width="140" class="text-center" style="vertical-align: middle">桩号</th>
+                                <td><input class="form-control form-control-sm" type="text" value="<%- change.peg %>" data-name="peg" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
+                            </tr>
+                            <tr>
+                                <th width="" class="text-center" style="vertical-align: middle">原设计图名称</th>
+                                <td><input class="form-control form-control-sm" value="<%- change.org_name %>" data-name="org_name" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
+                                <th width="" class="text-center" style="vertical-align: middle">图号</th>
+                                <td><input class="form-control form-control-sm" type="text" value="<%- change.new_code %>" data-name="new_code" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
+                            </tr>
+                            <tr>
+                                <th width="" class="text-center" style="vertical-align: middle">变更设计名称</th>
+                                <td><input class="form-control form-control-sm" value="<%- change.design_name %>" data-name="design_name" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
+                                <th width="" class="text-center" style="vertical-align: middle">变更图号</th>
+                                <td><input class="form-control form-control-sm" value="<%- change.c_new_code %>" data-name="c_new_code" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
+                            </tr>
+                            <tr>
+                                <th width="" class="text-center" style="vertical-align: middle">工程变更类别</th>
+                                <td><input class="form-control form-control-sm" type="text" value="<%- change.class %>" data-name="class" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
+                                <th width="" class="text-center" style="vertical-align: middle">工程变更性质</th>
+                                <% if (change.readOnly) { %>
+                                    <td><input class="form-control form-control-sm" type="text" value="<%- change.quality %>" data-name="quality" readonly placeholder=""></td>
+                                <% } else { %>
+                                    <td><select class="form-control form-control-sm" name="quality" data-name="quality" <% if (change.readOnly) { %>readonly<% } %>>
+                                            <% for (const q in changeConst.quality) { %>
+                                                <% const cQuality = changeConst.quality[q] %>
+                                                <option <% if (cQuality.name === change.quality) { %> selected<% } %>><%- cQuality.name %></option>
+                                            <% } %>
+                                        </select></td>
+                                <% } %>
+                            </tr>
+                            <tr>
+                                <th width="" class="text-center" style="vertical-align: middle">变更原因<b class="text-danger">*&nbsp;</b></th>
+                                <td colspan="3"><textarea class="form-control form-control-sm" data-name="reason" <% if (change.readOnly) { %>readonly<% } %> rows="3"><%- change.reason %></textarea></td>
+                            </tr>
+                            <tr>
+                                <th width="" class="text-center" style="vertical-align: middle">变更内容</th>
+                                <td colspan="3"><textarea class="form-control form-control-sm" data-name="content" <% if (change.readOnly) { %>readonly<% } %> rows="3"><%- change.content %></textarea></td>
+                            </tr>
+                            <tr>
+                                <th width="" class="text-center" style="vertical-align: middle">方案描述</th>
+                                <td colspan="3"><textarea class="form-control form-control-sm" data-name="memo" <% if (change.readOnly) { %>readonly<% } %> rows="3"><%- change.memo %></textarea></td>
+                            </tr>
+                        </table>
+                        <h5 id="qingdan" class="d-inline-block">变更清单</h5>
+                        <% if (change.status === auditConst.status.checked) { %>
+                        <div class="d-inline-block ml-3">
+                            <div class="custom-control custom-checkbox" style="line-height: normal;">
+                                <input type="checkbox" class="custom-control-input" id="show-table-detail">
+                                <label class="custom-control-label" for="show-table-detail">审批过程</label>
+                            </div>
+                        </div>
+                        <% } %>
+                        <div style="height: 300px;" id="plan-spread"></div>
+                        <h5 id="fujian">附件</h5>
+                        <table class="table table-bordered">
+                            <thead>
+                            <tr>
+                                <th></th>
+                                <th>附件</th>
+                                <th>上传者</th>
+                                <th>上传时间</th>
+                                <th>操作</th>
+                            </tr>
+                            </thead>
+                            <tbody>
+                            <!--<tr>-->
+                            <!--<td colspan="5"><button type="button" class="btn btn-primary btn-sm"  data-toggle="modal" data-target="#upload-fj">上传附件</button></td>-->
+                            <!--</tr>-->
+                            <tbody id="file-content">
+                            </tbody>
+                            <!--<tr>-->
+                            <!--<td>1</td>-->
+                            <!--<td>XXX设计图纸</td>-->
+                            <!--<td>仁温书</td>-->
+                            <!--<td>2021-12-09 16:58:47</td>-->
+                            <!--<td><a href="#" class="mr-2"><i class="fa fa-download"></i></a><a href="#" class="text-danger"><i class="fa fa-remove"></i></a></td>-->
+                            <!--</tr>-->
+                            <!--<tr>-->
+                            <!--<td>1</td>-->
+                            <!--<td>XXX资料说明</td>-->
+                            <!--<td>仁温书</td>-->
+                            <!--<td>2021-12-09 16:58:47</td>-->
+                            <!--<td><a href="#" class="mr-2"><i class="fa fa-download"></i></a><a href="#" class="text-danger"><i class="fa fa-remove"></i></a></td>-->
+                            <!--</tr>-->
+                            </tbody>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    autoFlashHeight();
+    const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
+    const fileTypeConst = JSON.parse(unescape('<%- escape(JSON.stringify(changeConst.file_type)) %>'));
+    const fileList = JSON.parse(unescape('<%- escape(JSON.stringify(fileList)) %>')) || [];
+    const whiteList = JSON.parse('<%- JSON.stringify(whiteList) %>');
+    const preUrl = '<%- preUrl %>';
+    const change = JSON.parse(unescape('<%- escape(JSON.stringify(change)) %>'));
+    let listRule = JSON.parse(unescape('<%- escape(JSON.stringify(listRule)) %>'));
+    const readOnly = <%- change.readOnly %>;
+    const totalPriceUnit = '<%- tpUnit %>';
+    const unitPriceUnit = '<%- upUnit %>';
+    const precision = JSON.parse('<%- JSON.stringify(precision) %>');
+    let changeUnits = JSON.parse('<%- JSON.stringify(changeUnits) %>');
+    changeUnits = _.map(changeUnits, 'unit');
+    changeUnits.push('');
+    let changeList = JSON.parse(unescape('<%- escape(JSON.stringify(changeList)) %>'));
+</script>

+ 786 - 0
app/view/change/plan_information_modal.ejs

@@ -0,0 +1,786 @@
+<!--添加附件-->
+<div class="modal fade" id="addfujian">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="myModalLabel">上传附件</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label for="file-modal">大小限制:30MB,支持office等文档格式、图片格式、压缩包格式</label>
+                    <!-- <p><a href="javascript: void(0);" class="btn btn-primary" id="file-modal-target">选择文件</a></p> -->
+                    <input type="file" id="file-modal" multiple="multiple">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button id="file-cancel" type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">取消</button>
+                <button id="file-ok" type="button" class="btn btn-primary btn-sm">添加</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% if ((ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.checkNo) && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.tender.isTourist)) { %>
+    <!--上报审批-->
+    <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">
+                            <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">
+                                    <% 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.change.auditorList.length; i < iLen; i++) { %>
+                                    <li class="list-group-item" auditorId="<%- ctx.change.auditorList[i].aid %>">
+                                        <% if (ctx.session.sessionUser.accountId === ctx.change.uid && !ctx.tender.isTourist) { %>
+                                            <a href="javascript: void(0)" class="text-danger pull-right">移除</a>
+                                        <% } %>
+                                        <span><%- ctx.change.auditorList[i].order %> <%- ctx.change.auditorList[i].name %></span>
+                                        <small class="text-muted"><%- ctx.change.auditorList[i].role %></small>
+                                    </li>
+                                <% } %>
+                            </ul>
+                        </div>
+                    </div>
+                </div>
+                <form class="modal-footer" method="post" action="<%- preUrl %>/audit/start" onsubmit="return checkAuditorFrom()">
+                    <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.change.uid) { %>
+                        <button class="btn btn-primary btn-sm" type="submit">确认上报</button>
+                    <% } %>
+                </form>
+            </div>
+        </div>
+    </div>
+    <!-- 数量读取规则 -->
+    <div class="modal fade" id="shuliangguize" 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="">数据来源</label>
+                        <div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="radio" name="data_source" id="gclGather" value="1" <% if(listRule.source === 1) { %>checked<% } %>>
+                                <label class="form-check-label" for="gclGather">台账数量</label>
+                            </div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="radio" name="data_source" id="dealBills" value="2" <% if(listRule.source === 2) { %>checked<% } %>>
+                                <label class="form-check-label" for="dealBills">签约数量</label>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label for="">匹配规则</label>
+                        <div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="checkbox" id="rule_code" value="code" disabled checked>
+                                <label class="form-check-label" for="rule_code">编号</label>
+                            </div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="checkbox" id="rule_code" value="name" disabled checked>
+                                <label class="form-check-label" for="rule_code">名称</label>
+                            </div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" name="data_rule" type="checkbox" id="rule_unit" value="unit" <% if(ctx.helper._.indexOf(listRule.rule, 'unit') !== -1) { %>checked<% } %>>
+                                <label class="form-check-label" for="rule_unit">单位</label>
+                            </div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" name="data_rule" type="checkbox" id="rule_unitprice" value="unit_price" <% if(ctx.helper._.indexOf(listRule.rule, 'unit_price') !== -1) { %>checked<% } %>>
+                                <label class="form-check-label" for="rule_unitprice">单价</label>
+                            </div>
+                        </div>
+                    </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="setListRule">确定</button>
+                </div>
+            </div>
+        </div>
+    </div>
+<% } %>
+
+<!--审批流程/结果-->
+<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"><%- ctx.change.status !== auditConst.status.checkNo ? '审批流程' : '重新上报' %></h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-4">
+                        <% if(ctx.change.status === auditConst.status.checkNo) { %>
+                            <a class="sp-list-item" href="#sub-sp" data-toggle="modal" data-target="#sub-sp" id="hideSp">修改审批流程</a>
+                        <% } %>
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush" id="auditors-list">
+                                <% ctx.change.auditors2.forEach((item, idx) => { %>
+                                    <% if (idx === 0) { %>
+                                        <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                            <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                            <small class="text-muted"><%- item.role %></small>
+                                            <span class="pull-right">原报</span>
+                                        </li>
+                                    <% } else if(idx === ctx.change.auditors2.length -1 && idx !== 0) { %>
+                                        <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                            <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                            <small class="text-muted"><%- item.role %></small>
+                                            <span class="pull-right">终审</span>
+                                        </li>
+                                    <% } else {%>
+                                        <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                            <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                            <small class="text-muted"><%- item.role %></small>
+                                            <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
+                                        </li>
+                                    <% } %>
+                                <% }) %>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto">
+                        <% ctx.change.auditHistory.forEach((auditors, idx) => { %>
+                            <!-- 展开/收起历史流程 -->
+                            <% if(idx === ctx.change.auditHistory.length - 1 && ctx.change.auditHistory.length !== 1) { %>
+                                <div class="text-right">
+                                    <a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a>
+                                </div>
+                            <% } %>
+                            <div class="<%- idx < ctx.change.auditHistory.length - 1 ? 'fold-card' : '' %>">
+                                <div class="text-center text-muted"><%- idx+1 %>#</div>
+                                <ul class="timeline-list list-unstyled mt-2">
+                                    <% auditors.forEach((auditor, index) => { %>
+                                        <% if (index === 0) { %>
+                                            <li class="timeline-list-item pb-2">
+                                                <div class="timeline-item-date">
+                                                    <%- ctx.helper.formatDate(auditor.begin_time) %>
+                                                </div>
+                                                <div class="timeline-item-tail"></div>
+                                                <div class="timeline-item-icon bg-success text-light">
+                                                    <i class="fa fa-caret-down"></i>
+                                                </div>
+                                                <div class="timeline-item-content">
+                                                    <div class="card">
+                                                        <div class="card-body p-3">
+                                                            <div class="card-text">
+                                                                <p class="mb-1"><span
+                                                                            class="h5"><%- ctx.change.user.name %></span><span
+                                                                            class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                                </p>
+                                                                <p class="text-muted mb-0"><%- ctx.change.user.role %></p>
+                                                            </div>
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </li>
+                                            <li class="timeline-list-item pb-2">
+                                                <div class="timeline-item-date">
+                                                    <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                </div>
+                                                <% if(index < auditors.length - 1) { %>
+                                                    <div class="timeline-item-tail"></div>
+                                                <% } %>
+                                                <% if(auditor.status === auditConst.status.checked) { %>
+                                                    <div class="timeline-item-icon bg-success text-light">
+                                                        <i class="fa fa-check"></i>
+                                                    </div>
+                                                <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                    <div class="timeline-item-icon bg-warning text-light">
+                                                        <i class="fa fa-level-up"></i>
+                                                    </div>
+                                                <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                    <div class="timeline-item-icon bg-warning text-light">
+                                                        <i class="fa fa-ellipsis-h"></i>
+                                                    </div>
+                                                <% } else {%>
+                                                    <div class="timeline-item-icon bg-secondary text-light">
+                                                    </div>
+                                                <% } %>
+                                                <div class="timeline-item-content">
+                                                    <div class="card">
+                                                        <div class="card-body p-3">
+                                                            <div class="card-text">
+                                                                <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
+                                                                            class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                </p>
+                                                                <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                            </div>
+                                                        </div>
+
+                                                        <!--审批意见-->
+                                                        <% if (auditor.opinion) { %>
+                                                            <div class="card-body p-3 border-top">
+                                                                <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                            </div>
+                                                        <% } %>
+                                                    </div>
+                                                </div>
+                                            </li>
+                                        <% } else {%>
+                                            <li class="timeline-list-item pb-2">
+                                                <div class="timeline-item-date">
+                                                    <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                </div>
+                                                <% if(index < auditors.length - 1) { %>
+                                                    <div class="timeline-item-tail"></div>
+                                                <% } %>
+                                                <% if(auditor.status === auditConst.status.checked) { %>
+                                                    <div class="timeline-item-icon bg-success text-light">
+                                                        <i class="fa fa-check"></i>
+                                                    </div>
+                                                <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                    <div class="timeline-item-icon bg-warning text-light">
+                                                        <i class="fa fa-level-up"></i>
+                                                    </div>
+                                                <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                    <div class="timeline-item-icon bg-warning text-light">
+                                                        <i class="fa fa-ellipsis-h"></i>
+                                                    </div>
+                                                <% } else { %>
+                                                    <div class="timeline-item-icon bg-secondary text-light">
+                                                    </div>
+                                                <% } %>
+                                                <div class="timeline-item-content">
+                                                    <div class="card">
+                                                        <div class="card-body p-3">
+                                                            <div class="card-text">
+                                                                <p class="mb-1"><span class="h5"><%- auditor.name %></span>
+                                                                    <span
+                                                                            class="pull-right
+                                                                            <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                        <%- auditor.status === auditConst.status.checkNo ? ctx.change.user.name : '' %>
+                                                        </span>
+                                                                </p>
+                                                                <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                            </div>
+                                                        </div>
+                                                        <!--审批意见-->
+                                                        <% if (auditor.opinion) { %>
+                                                            <div class="card-body p-3 border-top">
+                                                                <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                            </div>
+                                                        <% } %>
+                                                    </div>
+                                                </div>
+                                            </li>
+                                        <% } %>
+                                    <% }) %>
+                                </ul>
+                            </div>
+
+                        <% }) %>
+                    </div>
+                </div>
+            </div>
+            <form class="modal-footer" method="post" action="<%- preUrl %>/audit/start" onsubmit="return checkAuditorFrom()">
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <% if(ctx.change.status === auditConst.status.checkNo && ctx.session.sessionUser.accountId === ctx.change.uid) { %>
+                    <button class="btn btn-primary btn-sm sp-list-item" type="submit">确认上报</button>
+                <% } %>
+            </form>
+        </div>
+    </div>
+</div>
+<% if (ctx.change.status === auditConst.status.checking) { %>
+    <% if (ctx.change.curAuditor && ctx.change.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
+        <!--审批通过-->
+        <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="<%- preUrl %>/audit/check" method="post" onsubmit="return auditCheck(0);">
+                    <div class="modal-header">
+                        <h5 class="modal-title">审批通过</h5>
+                    </div>
+                    <div class="modal-body">
+                        <div class="row">
+                            <div class="col-4">
+                                <div class="card mt-3">
+                                    <ul class="list-group list-group-flush">
+                                        <% ctx.change.auditors2.forEach((item, idx) => { %>
+                                            <% if (idx === 0) { %>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right">原报</span>
+                                                </li>
+                                            <% } else if(idx === ctx.change.auditors2.length -1 && idx !== 0) { %>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right">终审</span>
+                                                </li>
+                                            <% } else {%>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
+                                                </li>
+                                            <% } %>
+                                        <% }) %>
+                                    </ul>
+                                </div>
+                            </div>
+                            <div class="col-8 modal-height-500" style="overflow: auto">
+                                <% ctx.change.auditHistory.forEach((auditors, idx) => { %>
+                                    <!-- 展开/收起历史流程 -->
+                                    <% if(idx === ctx.change.auditHistory.length - 1 && ctx.change.auditHistory.length !== 1) { %>
+                                        <div class="text-right">
+                                            <a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a>
+                                        </div>
+                                    <% } %>
+                                    <div class="<%- idx < ctx.change.auditHistory.length - 1 ? 'fold-card' : '' %>">
+                                        <div class="text-center text-muted"><%- idx+1 %>#</div>
+                                        <ul class="timeline-list list-unstyled mt-2">
+                                            <% auditors.forEach((auditor, index) => { %>
+                                                <% if (index === 0) { %>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.begin_time) %>
+                                                        </div>
+                                                        <div class="timeline-item-tail"></div>
+                                                        <div class="timeline-item-icon bg-success text-light">
+                                                            <i class="fa fa-caret-down"></i>
+                                                        </div>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span
+                                                                                    class="h5"><%- ctx.change.user.name %></span><span
+                                                                                    class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- ctx.change.user.role %></p>
+                                                                    </div>
+                                                                </div>
+                                                            </div>
+                                                        </div>
+                                                    </li>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                        </div>
+                                                        <% if(index < auditors.length - 1) { %>
+                                                            <div class="timeline-item-tail"></div>
+                                                        <% } %>
+                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                            <div class="timeline-item-icon bg-success text-light">
+                                                                <i class="fa fa-check"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-level-up"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-ellipsis-h"></i>
+                                                            </div>
+                                                        <% } else {%>
+                                                            <div class="timeline-item-icon bg-secondary text-light">
+                                                            </div>
+                                                        <% } %>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
+                                                                                    class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                                    </div>
+                                                                </div>
+                                                                <!--审批意见-->
+                                                                <% if(auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <div class="card-body p-3 border-top">
+                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                                            <label>审批意见<b class="text-danger">*</b></label>
+                                                                            <textarea class="form-control form-control-sm"
+                                                                                      name="opinion">同意</textarea>
+                                                                        <% } else { %>
+                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        <% } %>
+                                                                    </div>
+                                                                <% } %>
+                                                            </div>
+                                                        </div>
+                                                    </li>
+                                                <% } else {%>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                        </div>
+                                                        <% if(index < auditors.length - 1) { %>
+                                                            <div class="timeline-item-tail"></div>
+                                                        <% } %>
+                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                            <div class="timeline-item-icon bg-success text-light">
+                                                                <i class="fa fa-check"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-level-up"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-ellipsis-h"></i>
+                                                            </div>
+                                                        <% } else { %>
+                                                            <div class="timeline-item-icon bg-secondary text-light">
+                                                            </div>
+                                                        <% } %>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span class="h5"><%- auditor.name %></span>
+                                                                            <span
+                                                                                    class="pull-right
+                                                                                    <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                                <%- auditor.status === auditConst.status.checkNo ? ctx.change.user.name : '' %>
+                                                                </span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                                    </div>
+                                                                </div>
+                                                                <!--审批意见-->
+                                                                <% if(auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <div class="card-body p-3 border-top">
+                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                                            <label>审批意见<b class="text-danger">*</b></label>
+                                                                            <textarea class="form-control form-control-sm"
+                                                                                      name="opinion">同意</textarea>
+                                                                        <% } else { %>
+                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        <% } %>
+                                                                    </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="<%- preUrl %>/audit/check" method="post"
+                      onsubmit="return auditCheck(1);">
+                    <div class="modal-header">
+                        <h5 class="modal-title">审批退回</h5>
+                    </div>
+                    <div class="modal-body">
+                        <div class="row">
+                            <div class="col-4">
+                                <div class="card mt-3">
+                                    <ul class="list-group list-group-flush">
+                                        <% ctx.change.auditors2.forEach((item, idx) => { %>
+                                            <% if (idx === 0) { %>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right">原报</span>
+                                                </li>
+                                            <% } else if(idx === ctx.change.auditors2.length -1 && idx !== 0) { %>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right">终审</span>
+                                                </li>
+                                            <% } else {%>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
+                                                </li>
+                                            <% } %>
+                                        <% }) %>
+                                    </ul>
+                                </div>
+                            </div>
+                            <div class="col-8 modal-height-500" style="overflow: auto">
+                                <% ctx.change.auditHistory.forEach((auditors, idx) => { %>
+                                    <!-- 展开/收起历史流程 -->
+                                    <% if(idx === ctx.change.auditHistory.length - 1 && ctx.change.auditHistory.length !== 1) { %>
+                                        <div class="text-right">
+                                            <a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a>
+                                        </div>
+                                    <% } %>
+                                    <div class="<%- idx < ctx.change.auditHistory.length - 1 ? 'fold-card' : '' %>">
+                                        <div class="text-center text-muted"><%- idx+1 %>#</div>
+                                        <ul class="timeline-list list-unstyled mt-2">
+                                            <% auditors.forEach((auditor, index) => { %>
+                                                <% if (index === 0) { %>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.begin_time) %>
+                                                        </div>
+                                                        <div class="timeline-item-tail"></div>
+                                                        <div class="timeline-item-icon bg-success text-light">
+                                                            <i class="fa fa-caret-down"></i>
+                                                        </div>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span
+                                                                                    class="h5"><%- ctx.change.user.name %></span><span
+                                                                                    class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- ctx.change.user.role %></p>
+                                                                    </div>
+                                                                </div>
+                                                            </div>
+                                                        </div>
+                                                    </li>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                        </div>
+                                                        <% if(index < auditors.length - 1) { %>
+                                                            <div class="timeline-item-tail"></div>
+                                                        <% } %>
+                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                            <div class="timeline-item-icon bg-success text-light">
+                                                                <i class="fa fa-check"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-level-up"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-ellipsis-h"></i>
+                                                            </div>
+                                                        <% } else {%>
+                                                            <div class="timeline-item-icon bg-secondary text-light">
+                                                            </div>
+                                                        <% } %>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
+                                                                                    class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                                    </div>
+                                                                </div>
+
+                                                                <!--审批意见-->
+                                                                <% if(auditor.times === ctx.change.times && auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <div class="card-body p-3 border-top">
+                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                                            <label>审批意见<b class="text-danger">*</b></label>
+                                                                            <textarea class="form-control form-control-sm"
+                                                                                      name="opinion">不同意</textarea>
+                                                                            <% if (ctx.change.curAuditor.aid === auditor.aid) { %>
+                                                                                <div id="reject-process" class="alert alert-warning"
+                                                                                     style="margin-top: 15px;">
+                                                                                    <div class="form-check form-check-inline">
+                                                                                        <input class="form-check-input" type="radio" name="checkType"
+                                                                                               id="inlineRadio1" value="<%- auditConst.status.checkNo %>" checked>
+                                                                                        <label class="form-check-label" for="inlineRadio1">退回原报
+                                                                                            <%- ctx.change.user.name %></label>
+                                                                                    </div>
+                                                                                </div>
+                                                                            <% } %>
+                                                                        <% } else if(auditor.status === auditConst.status.checked){ %>
+                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        <% } %>
+                                                                    </div>
+                                                                <% } %>
+                                                            </div>
+                                                        </div>
+                                                    </li>
+                                                <% } else {%>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                        </div>
+                                                        <% if(index < auditors.length - 1) { %>
+                                                            <div class="timeline-item-tail"></div>
+                                                        <% } %>
+                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                            <div class="timeline-item-icon bg-success text-light">
+                                                                <i class="fa fa-check"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo) {%>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-level-up"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-ellipsis-h"></i>
+                                                            </div>
+                                                        <% } else { %>
+                                                            <div class="timeline-item-icon bg-secondary text-light">
+                                                            </div>
+                                                        <% } %>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span class="h5"><%- auditor.name %></span>
+                                                                            <span
+                                                                                    class="pull-right
+                                                                                    <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                                <%- auditor.status === auditConst.status.checkNo ? ctx.change.user.name : '' %>
+                                                                </span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                                    </div>
+                                                                </div>
+                                                                <!--审批意见-->
+                                                                <% if(auditor.times === ctx.change.times && auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <div class="card-body p-3 border-top">
+                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                                            <label>审批意见<b class="text-danger">*</b></label>
+                                                                            <textarea class="form-control form-control-sm"
+                                                                                      name="opinion">不同意</textarea>
+                                                                            <% if (ctx.change.curAuditor.aid === auditor.aid ) { %>
+                                                                                <div id="reject-process" class="alert alert-warning"
+                                                                                     style="margin-top: 15px;">
+                                                                                    <div class="form-check form-check-inline">
+                                                                                        <input class="form-check-input" type="radio" name="checkType"
+                                                                                               id="inlineRadio1" value="<%- auditConst.status.checkNo %>" checked>
+                                                                                        <label class="form-check-label" for="inlineRadio1">退回原报
+                                                                                            <%- ctx.change.user.name %></label>
+                                                                                    </div>
+                                                                                </div>
+                                                                            <% } %>
+                                                                        <% } else { %>
+                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        <% } %>
+
+                                                                    </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.session.sessionUser.accountId === ctx.change.uid && (ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.checkNo)) { %>
+    <script>
+        const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
+        const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
+    </script>
+<% } %>
+<script>const cur_uid = parseInt('<%- ctx.session.sessionUser.accountId %>');</script>
+<script>
+    $('.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('展开历史审核记录')
+            })
+        }
+    });
+
+    $('.sp-list-btn').click(function () {
+        const type = $(this).data('type')
+        if (type === 'hide') {
+            $('.sp-list-item').hide()
+            $('.modal-title').text('审批流程')
+        } else {
+            $('.sp-list-item').show()
+            $('.modal-title').text('重新上报')
+        }
+    });
+</script>

+ 148 - 0
app/view/change/plan_modal.ejs

@@ -0,0 +1,148 @@
+<!--删除标段-->
+<div class="modal fade" id="del-bg" 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>
+            <form class="modal-footer" action="/tender/<%- tender.id %>/change/plan/delete" method="post">
+                <input type="hidden" name="cpid" id="delete-cpid" value="">
+                <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>
+            </form>
+        </div>
+    </div>
+</div>
+
+<% if (tender.user_id === uid) { %>
+<!--弹出添加变更令-->
+<div class="modal fade" id="add-bj-modal" 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>编号<b class="text-danger">*</b></label>
+                    <div class="input-group">
+                        <input type="text" class="form-control form-control-sm is-invalid" placeholder="请输入编号" value="变更方案编号" id="bj-code">
+                        <div class="input-group-append" id="autoCodeShow" <% if (codeRule.length === 0) { %>style="display: none"<% } %>>
+                            <button class="btn btn-sm btn-outline-secondary" type="button" title="自动编号" id="autoCode"><i class="fa fa-repeat"></i></button>
+                        </div>
+                        <div class="invalid-feedback" style="display: none" id="bjHint">您输入的编号已存在。</div>
+                    </div>
+                </div>
+                <% if (ctx.session.sessionProject.page_show.openChangeApply) { %>
+                    <div class="form-group">
+                        <label>关联变更申请</label>
+                        <select class="form-control form-control-sm" id="apply-code">
+                            <option></option>
+                            <% for (const cp of changeApplyList) { %>
+                                <% if (ctx.helper._.indexOf(acLists, cp.code) === -1) { %>
+                                    <option><%- cp.code %></option>
+                                <% } %>
+                            <% } %>
+                        </select>
+                    </div>
+                    <input value="" type="hidden" id="bj-name">
+                <% } else { %>
+                    <div class="form-group">
+                        <label>变更工程名称<b class="text-danger">*</b></label>
+                        <input class="form-control form-control-sm" value="" type="text" id="bj-name">
+                        <div class="invalid-feedback" style="display: none" id="name_error_msg">名称超过100个字,请缩减名称。</div>
+                    </div>
+                    <input type="hidden" value="" id="apply-code">
+                <% } %>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal" id="addCancel">关闭</button>
+                <a href="javascript: void(0)" class="btn btn-primary btn-sm" id="addOk">确认新建</a>
+            </div>
+        </div>
+    </div>
+</div>
+<!--设置-->
+<div class="modal fade" id="setting" 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">
+                <ul class="nav nav-tabs mb-3" role="tablist">
+                    <li class="nav-item">
+                        <a class="nav-link active" data-toggle="tab" href="#bianhao" role="tab" aria-controls="home" aria-selected="true">编号规则</a>
+                    </li>
+                </ul>
+                <div class="tab-content">
+                    <div class="tab-pane active" id="bianhao">
+                        <h5>
+                            当前规则:
+                            <span id="preview">
+                                <% if (codeRule && codeRule instanceof Array) { %>
+                                    <% const preview = []; %>
+                                    <% for (const rule of codeRule) { %>
+                                        <% preview.push(rule.preview); %>
+                                    <% } %>
+                                    <%- preview.join(tender.c_connector !== null && tender.c_connector !== '3' ? ruleConst.connectorString[tender.c_connector] : ''); %>
+                                <% } %>
+                            </span>
+                        </h5>
+                        <h5 id="ruleParts">
+                            <% if (codeRule && codeRule instanceof Array) { %>
+                                <% for (const rule of codeRule) { %>
+                                <span class="badge badge-light" title="<%- ruleConst.ruleString[rule.rule_type] %>">
+                                    <span>
+                                        <%- rule.preview %>
+                                    </span>
+                                    <a href="javascript: void(0);" class="text-danger" title="移除"><i class="fa fa-remove"></i></a>
+                                </span>
+                                <% } %>
+                            <% } %>
+                        </h5>
+                        <h5 class="my-3">连接符</h5>
+                        <div class="form-group">
+                            <select class="form-control form-control-sm connector-change">
+                                <option disabled selected>请选择</option>
+                                <% for (const index in ruleConst.connectorString) { %>
+                                    <option value="<%- index %>" <% if (tender.c_connector !== null && tender.c_connector === parseInt(index)) { %>selected<% } %>><%- ruleConst.connectorString[index] %></option>
+                                <% } %>
+                            </select>
+                        </div>
+                        <h5 class="my-3">添加新规则组件</h5>
+                        <div class="form-group">
+                            <select class="form-control form-control-sm rule-change">
+                                <option disabled selected>请选择组件</option>
+                                <% for (const index in ruleConst.ruleString) { %>
+                                <option value="<%- index %>"><%- ruleConst.ruleString[index] %></option>
+                                <% } %>
+                            </select>
+                        </div>
+                        <div class="form-group" id="format" style="display: none">
+                            <label>自动编号位数</label>
+                            <input min="3" class="form-control form-control-sm" step="1" max="6" value="3" type="number">
+                        </div>
+                        <div class="form-group" id="text" style="display: none">
+                            <label>起始编号</label>
+                            <input class="form-control form-control-sm" value="001" type="text">
+                        </div>
+                        <button class="btn btn-sm btn-outline-primary" id="addRule">添加组件</button>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <% if (c_rule_first) { %><button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal" id="changeFirst">暂时不需要</button><% } %>
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal" id="hide_modal" <% if (c_rule_first) { %>style="display: none"<% } %>>关闭</button>
+                <button type="button" class="btn btn-primary btn-sm" id="setRule">确定添加</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% } %>
+
+

+ 31 - 1
app/view/dashboard/index.ejs

@@ -28,7 +28,7 @@
                     <div class="card">
                     <div class="card">
                         <div class="card-header">需要你处理</div>
                         <div class="card-header">需要你处理</div>
                         <div class="card-body">
                         <div class="card-body">
-                            <% if (auditTenders.length !== 0 || auditRevise.length !== 0 || auditStages.length !== 0 || auditChanges.length !== 0 || auditMaterial.length !== 0 || auditAdvance.length !== 0 || auditChangeProject.length !== 0) { %>
+                            <% if (auditTenders.length !== 0 || auditRevise.length !== 0 || auditStages.length !== 0 || auditChanges.length !== 0 || auditMaterial.length !== 0 || auditAdvance.length !== 0 || auditChangeProject.length !== 0 || auditChangeApply.length !== 0 || auditChangePlan.length !== 0) { %>
                                 <ul class="list-unstyled m-0">
                                 <ul class="list-unstyled m-0">
                                     <% for (const t of auditTenders) { %>
                                     <% for (const t of auditTenders) { %>
                                         <% if (t.ledger_status === acLedger.status.checking) { %>
                                         <% if (t.ledger_status === acLedger.status.checking) { %>
@@ -148,6 +148,20 @@
                                             </div>
                                             </div>
                                         </li>
                                         </li>
                                     <% } %>
                                     <% } %>
+                                    <% for (const acp of auditChangePlan) { %>
+                                        <li class="media pb-3 mb-3 border-bottom-1">
+                                            <div class="media-body">
+                                                <div class="row">
+                                                    <div class="col-auto"><span class="badge badge-danger">变更方案</span></div>
+                                                    <div class="col-6"><a href="/tender/<%- acp.tid %>"><%- acp.name %></a> 变更方案 <%- acp.mcode %></div>
+                                                    <div class="col-3 ml-auto text-right pl-0"><a href="/tender/<%- acp.tid %>/change/plan/<%- acp.cpid %>/information" class="btn btn-sm btn-outline-primary"><% if (acp.mstatus !== acChangePlan.status.checkNo) { %>审批<% } else { %>重新上报<% } %></a></div>
+                                                </div>
+                                                <p class="mt-1 mb-0"><%- ctx.session.sessionUser.name %><small class="ml-1 text-muted"><%- (role ? '- ' + role : '') %></small>
+                                                    <span class="pull-right text-muted"><%- ctx.moment(acp.begin_time).format('YYYY-MM-DD HH:mm:ss') %></span>
+                                                </p>
+                                            </div>
+                                        </li>
+                                    <% } %>
                                 <% for (const am of auditMaterial) { %>
                                 <% for (const am of auditMaterial) { %>
                                         <% if (am.mstatus !== acMaterial.status.checkNo) { %>
                                         <% if (am.mstatus !== acMaterial.status.checkNo) { %>
                                             <li class="media pb-3 mb-3 border-bottom-1">
                                             <li class="media pb-3 mb-3 border-bottom-1">
@@ -331,6 +345,22 @@
                                                     </p>
                                                     </p>
                                                 </div>
                                                 </div>
                                             </li>
                                             </li>
+                                        <% } else if(notice.type === pushType.changePlan && ctx.session.sessionProject.page_show.openChangePlan) { %>
+                                            <li class="media pb-3 mb-3 border-bottom-1">
+                                                <div class="media-body">
+                                                    <div class="row">
+                                                        <div class="col-auto"><span class="badge badge-danger">变更方案</span></div>
+                                                        <div class="col-6">
+                                                            <a href="/tender/<%- notice.tid %>"><%- notice.name %></a>
+                                                            <a href="/tender/<%- notice.tid %>/change/plan/<%- notice.cpid %>/information"><%- notice.c_code %> </a>
+                                                            <%- acChangePlan.statusString[notice.status] %>
+                                                        </div>
+                                                    </div>
+                                                    <p class="mt-1 mb-0"><%- notice.su_name %><small class="ml-1 text-muted"><%- (notice.su_role ? '- ' + notice.su_role : '') %></small>
+                                                        <span class="pull-right text-muted"><%- ctx.helper.formatFullDate(notice.create_time) %></span>
+                                                    </p>
+                                                </div>
+                                            </li>
                                         <% } else if(notice.type === pushType.advance) { %>
                                         <% } else if(notice.type === pushType.advance) { %>
                                             <li class="media pb-3 mb-3 border-bottom-1">
                                             <li class="media pb-3 mb-3 border-bottom-1">
                                                 <div class="media-body">
                                                 <div class="media-body">

+ 10 - 0
app/view/material/index.ejs

@@ -23,6 +23,7 @@
                         <th rowspan="2">期数</th>
                         <th rowspan="2">期数</th>
                         <th class="text-center" rowspan="2">添加时间</th>
                         <th class="text-center" rowspan="2">添加时间</th>
                         <th class="text-center" rowspan="2">计量期</th>
                         <th class="text-center" rowspan="2">计量期</th>
+                        <th class="text-center" rowspan="2">调差周期</th>
                         <th class="text-center" colspan="<% if (openMaterialTax && !allMaterialTax) { %>3<% } else { %>2<% } %>">信息价调差</th>
                         <th class="text-center" colspan="<% if (openMaterialTax && !allMaterialTax) { %>3<% } else { %>2<% } %>">信息价调差</th>
                         <th class="text-center" colspan="2">指数法调差</th>
                         <th class="text-center" colspan="2">指数法调差</th>
                         <th class="text-center" colspan="<% if (openMaterialTax) { %>3<% } else { %>2<% } %>">合计</th>
                         <th class="text-center" colspan="<% if (openMaterialTax) { %>3<% } else { %>2<% } %>">合计</th>
@@ -48,6 +49,12 @@
                             </td>
                             </td>
                             <td class="text-center"><%= moment(m.in_time).format('YYYY-MM-DD') %></td>
                             <td class="text-center"><%= moment(m.in_time).format('YYYY-MM-DD') %></td>
                             <td class="text-center">第 <%= m.s_order %> 期</td>
                             <td class="text-center">第 <%= m.s_order %> 期</td>
+                            <td class="text-center">
+                                <span data-toggle="tooltip" data-placement="bottom" data-original-title="<%- (m.period ? m.period : '') %>">
+                                    <%- (m.period ? (m.period.split('~')[1] ? m.period.split('~')[1] : m.period)  : '') %>
+                                </span>
+                                <a href="#edit" class="edit-material" data-mid="<%- m.id %>" data-order="<%- m.order %>" data-period="<%- m.period %>" data-toggle="modal" data-target="#edit"><i class="fa fa-pencil-square-o "></i></a>
+                            </td>
                             <td class="text-right"><%= m.m_tp !== null ? ctx.helper.round(m.m_tp, m.decimal.tp) : null %></td>
                             <td class="text-right"><%= m.m_tp !== null ? ctx.helper.round(m.m_tp, m.decimal.tp) : null %></td>
                             <% if (openMaterialTax) { %><td class="text-right"><% if (m.material_tax) { %><% if (m.m_tax_tp) { %><%- m.m_tax_tp %><% } else { %><%- m.m_tp %><% } %><% } %></td><% } %>
                             <% if (openMaterialTax) { %><td class="text-right"><% if (m.material_tax) { %><% if (m.m_tax_tp) { %><%- m.m_tax_tp %><% } else { %><%- m.m_tp %><% } %><% } %></td><% } %>
                             <% if ((openMaterialTax && !allMaterialTax) || !openMaterialTax) { %><td class="text-right"><% if (!m.material_tax) { %><%= m.m_tp !== null ? ctx.helper.round(ctx.helper.mul(m.m_tp, 1+m.rate/100), m.decimal.tp) : null %><% } %></td><% } %>
                             <% if ((openMaterialTax && !allMaterialTax) || !openMaterialTax) { %><td class="text-right"><% if (!m.material_tax) { %><%= m.m_tp !== null ? ctx.helper.round(ctx.helper.mul(m.m_tp, 1+m.rate/100), m.decimal.tp) : null %><% } %></td><% } %>
@@ -85,6 +92,9 @@
     </div>
     </div>
 </div>
 </div>
 <script src="/public/js/sub_menu.js"></script>
 <script src="/public/js/sub_menu.js"></script>
+<script src="/public/js/datepicker/datepicker.min.js"></script>
+<script src="/public/js/datepicker/datepicker.zh.js"></script>
+
 <script>
 <script>
     const tenderId = '<%- ctx.tender.id %>';
     const tenderId = '<%- ctx.tender.id %>';
     const auditConst = JSON.parse('<%- auditConst2 %>');
     const auditConst = JSON.parse('<%- auditConst2 %>');

+ 5 - 5
app/view/material/list.ejs

@@ -53,11 +53,11 @@
                             <li class="nav-item">
                             <li class="nav-item">
                                 <a class="nav-link active" data-toggle="tab" href="#xmujie" role="tab">清单明细</a>
                                 <a class="nav-link active" data-toggle="tab" href="#xmujie" role="tab">清单明细</a>
                             </li>
                             </li>
-                            <!--<li class="ml-auto">-->
-                            <!--<% if (!material.readOnly) { %>-->
-                            <!--<button class="btn btn-sm btn-primary" id="user_all_material">应用调差工料至其他清单明细</button>-->
-                            <!--<% } %>-->
-                            <!--</li>-->
+                            <li class="ml-auto">
+                            <% if (!material.readOnly || material.status === auditConst.status.checked) { %>
+                            <button class="btn btn-sm btn-primary" id="user_all_material">应用调差工料至其他清单明细</button>
+                            <% } %>
+                            </li>
                         </ul>
                         </ul>
                     </div>
                     </div>
                     <div class="sp-wrap" id="leaf-xmj-spread">
                     <div class="sp-wrap" id="leaf-xmj-spread">

+ 30 - 0
app/view/material/modal.ejs

@@ -13,6 +13,10 @@
                     <input class="form-control form-control-sm" value="第 <%- materials.length + 1 %> 期" type="text" readonly="">
                     <input class="form-control form-control-sm" value="第 <%- materials.length + 1 %> 期" type="text" readonly="">
                 </div>
                 </div>
                 <div class="form-group">
                 <div class="form-group">
+                    <label>调差周期</label>
+                    <input class="datepicker-here form-control form-control-sm" autocomplete="off" readonly name="period" placeholder="点击选择时间" data-range="true" data-multiple-dates-separator=" ~ " data-language="zh" type="text">
+                </div>
+                <div class="form-group">
                     <label>本期调差,包含计量期<span class="ml-2 text-danger" id="show_order" style="display: none">第<b class="mx-2"></b>期</span></label>
                     <label>本期调差,包含计量期<span class="ml-2 text-danger" id="show_order" style="display: none">第<b class="mx-2"></b>期</span></label>
                     <div class="row">
                     <div class="row">
                         <% for (const stage of stages) { %>
                         <% for (const stage of stages) { %>
@@ -58,6 +62,32 @@
     </div>
     </div>
 </div>
 </div>
 <% } %>
 <% } %>
+<!--设置-->
+<div class="modal fade" id="edit" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <form class="modal-content" action="<%- preUrl + '/measure/material/save' %>" method="post" onsubmit="return checkForm();">
+            <div class="modal-header">
+                <h5 class="modal-title">调差周期编辑</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label>期</label>
+                    <input class="form-control form-control-sm" id="edit-name" value="" type="text" readonly="" name="name">
+                </div>
+                <div class="form-group">
+                    <label>调差周期<b class="text-danger">*</b></label>
+                    <input class="datepicker-here form-control form-control-sm" autocomplete="off" readonly id="edit-period" name="period" placeholder="点击选择时间" data-range="true" data-multiple-dates-separator=" ~ " data-language="zh" type="text">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                <input type="hidden" name="mid" id="edit-mid" value="">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="submit" class="btn btn-primary btn-sm" id="edit-ok" >确定修改</button>
+            </div>
+        </form>
+    </div>
+</div>
 <!--审批流程/结果-->
 <!--审批流程/结果-->
 <div class="modal fade" id="sp-list" data-backdrop="static">
 <div class="modal fade" id="sp-list" data-backdrop="static">
     <div class="modal-dialog modal-lg" role="document">
     <div class="modal-dialog modal-lg" role="document">

+ 1 - 0
app/view/report/index.ejs

@@ -33,6 +33,7 @@
                         <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
                         <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
                             <a class="dropdown-item" href="javascript: void(0)" onclick="rptChangeRela.showChangeRela('change_project');">变更立项</a>
                             <a class="dropdown-item" href="javascript: void(0)" onclick="rptChangeRela.showChangeRela('change_project');">变更立项</a>
                             <a class="dropdown-item" href="javascript: void(0)" onclick="rptChangeRela.showChangeRela('change_apply');">变更申请</a>
                             <a class="dropdown-item" href="javascript: void(0)" onclick="rptChangeRela.showChangeRela('change_apply');">变更申请</a>
+                            <a class="dropdown-item" href="javascript: void(0)" onclick="rptChangeRela.showChangeRela('change_plan');">变更方案</a>
                             <a class="dropdown-item" href="javascript: void(0)" onclick="rptChangeRela.showChangeRela('change');">变更令</a>
                             <a class="dropdown-item" href="javascript: void(0)" onclick="rptChangeRela.showChangeRela('change');">变更令</a>
                         </div>
                         </div>
                     </div>
                     </div>

+ 14 - 0
app/view/setting/fun.ejs

@@ -62,6 +62,12 @@
                                                 <label class="form-check-label" for="openChangeApply">显示「变更申请」页面</label>
                                                 <label class="form-check-label" for="openChangeApply">显示「变更申请」页面</label>
                                             </div>
                                             </div>
                                         </div>
                                         </div>
+                                        <div class="form-group mb-1">
+                                            <div class="form-check form-check-inline">
+                                                <input class="form-check-input" type="checkbox" id="openChangePlan" <% if(ctx.session.sessionProject.page_show.openChangePlan) { %>checked<% } %> onchange="updateSetting(3);">
+                                                <label class="form-check-label" for="openChangePlan">显示「变更方案」页面</label>
+                                            </div>
+                                        </div>
                                     </div>
                                     </div>
                                 </div>
                                 </div>
                             </div>
                             </div>
@@ -115,9 +121,16 @@
     const updateSetting = function (tab = false) {
     const updateSetting = function (tab = false) {
         if (!$('#openChangeApply')[0].checked && $('#openChangeProject')[0].checked && tab === 1) {
         if (!$('#openChangeApply')[0].checked && $('#openChangeProject')[0].checked && tab === 1) {
             $('#openChangeApply').prop('checked', true);
             $('#openChangeApply').prop('checked', true);
+            $('#openChangePlan').prop('checked', true);
         } else if (!$('#openChangeApply')[0].checked && $('#openChangeProject')[0].checked && tab === 2) {
         } else if (!$('#openChangeApply')[0].checked && $('#openChangeProject')[0].checked && tab === 2) {
             $('#openChangeProject').prop('checked', false);
             $('#openChangeProject').prop('checked', false);
         }
         }
+        if (!$('#openChangePlan')[0].checked && $('#openChangeApply')[0].checked && tab === 2) {
+            $('#openChangePlan').prop('checked', true);
+        } else if (!$('#openChangePlan')[0].checked && $('#openChangeApply')[0].checked && tab === 3) {
+            $('#openChangeApply').prop('checked', false);
+            $('#openChangeProject').prop('checked', false);
+        }
         postData('/setting/fun/update', {
         postData('/setting/fun/update', {
             imType: parseInt($('[name=im_type]:checked').val()),
             imType: parseInt($('[name=im_type]:checked').val()),
             banOver: $('[name=ban_over]')[0].checked,
             banOver: $('[name=ban_over]')[0].checked,
@@ -125,6 +138,7 @@
             needGcl: $('#need_gcl')[0].checked,
             needGcl: $('#need_gcl')[0].checked,
             openChangeProject: $('#openChangeProject')[0].checked,
             openChangeProject: $('#openChangeProject')[0].checked,
             openChangeApply: $('#openChangeApply')[0].checked,
             openChangeApply: $('#openChangeApply')[0].checked,
+            openChangePlan: $('#openChangePlan')[0].checked,
             openMaterialTax: $('#openMaterialTax')[0].checked,
             openMaterialTax: $('#openMaterialTax')[0].checked,
             openMaterialChecklist: $('#openMaterialChecklist')[0].checked,
             openMaterialChecklist: $('#openMaterialChecklist')[0].checked,
         });
         });

+ 5 - 4
app/view/stage/bwtz.ejs

@@ -119,6 +119,7 @@
         const def = thirdParty.dagl.find(function (x) {
         const def = thirdParty.dagl.find(function (x) {
             return x.value === data.dagl_status;
             return x.value === data.dagl_status;
         });
         });
+        if (data.code === '1-2-6-1-1-1') console.log(def);
         return def ? def.name : '';
         return def ? def.name : '';
     }
     }
     function getColor (sheet, data, row, col, defaultColor) {
     function getColor (sheet, data, row, col, defaultColor) {
@@ -181,14 +182,14 @@
             <% if (ctx.session.sessionProject.gxby) { %>
             <% if (ctx.session.sessionProject.gxby) { %>
             {
             {
                 title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@',
                 title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@',
-                readOnly: true, getValue: getGxbyText, cellType: 'activeImageBtn', normalImg: '#rela-file-icon', indent: 5,
+                readOnly: true, getValue: getGxbyText, cellType: 'imageBtn', normalImg: '#rela-file-icon', indent: 12,
                 imgAlign: 2, showImage: function (data) { return data && data.gxby_url }
                 imgAlign: 2, showImage: function (data) { return data && data.gxby_url }
             },
             },
             <% } %>
             <% } %>
             <% if (ctx.session.sessionProject.dagl) { %>
             <% if (ctx.session.sessionProject.dagl) { %>
             {
             {
                 title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@',
                 title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@',
-                readOnly: true, getValue: getDaglText, cellType: 'activeImageBtn', normalImg: '#rela-file-icon', indent: 5,
+                readOnly: true, getValue: getDaglText, cellType: 'imageBtn', normalImg: '#rela-file-icon', indent: 12,
                 imgAlign: 2, showImage: function (data) { return data && data.dagl_url }
                 imgAlign: 2, showImage: function (data) { return data && data.dagl_url }
             },
             },
             <% } %>
             <% } %>
@@ -256,14 +257,14 @@
             <% if (ctx.session.sessionProject.gxby) { %>
             <% if (ctx.session.sessionProject.gxby) { %>
             {
             {
                 title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@',
                 title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@',
-                readOnly: true, getValue: getGxbyText, cellType: 'activeImageBtn', normalImg: '#rela-file-icon', indent: 5,
+                readOnly: true, getValue: getGxbyText, cellType: 'imageBtn', normalImg: '#rela-file-icon', indent: 5,
                 imgAlign: 2, showImage: function (data) { return data && data.gxby_url },
                 imgAlign: 2, showImage: function (data) { return data && data.gxby_url },
             },
             },
             <% } %>
             <% } %>
             <% if (ctx.session.sessionProject.dagl) { %>
             <% if (ctx.session.sessionProject.dagl) { %>
             {
             {
                 title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@',
                 title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@',
-                readOnly: true, getValue: getDaglText, cellType: 'activeImageBtn', normalImg: '#rela-file-icon', indent: 5,
+                readOnly: true, getValue: getDaglText, cellType: 'imageBtn', normalImg: '#rela-file-icon', indent: 5,
                 imgAlign: 2, showImage: function (data) { return data && data.dagl_url },
                 imgAlign: 2, showImage: function (data) { return data && data.dagl_url },
             },
             },
             <% } %>
             <% } %>

+ 29 - 13
app/view/tender/detail.ejs

@@ -75,7 +75,7 @@
                         <div class="row ml-1 mb-3">
                         <div class="row ml-1 mb-3">
                             <div class="col-12">
                             <div class="col-12">
                                 <div class="row">
                                 <div class="row">
-                                    <div class="col-2 pl-0">
+                                    <div class="col-auto pl-0">
                                         <div class="card text-center <% if (!ctx.tender.info.display.dayMode) { %>bg-dark text-white<% } %>">
                                         <div class="card text-center <% if (!ctx.tender.info.display.dayMode) { %>bg-dark text-white<% } %>">
                                             <div class="card-body">
                                             <div class="card-body">
                                                 <h5 class="card-title card-big-title"><%- ctx.helper.formatMoney(tender.total_price) %></h5>
                                                 <h5 class="card-title card-big-title"><%- ctx.helper.formatMoney(tender.total_price) %></h5>
@@ -83,7 +83,23 @@
                                             </div>
                                             </div>
                                         </div>
                                         </div>
                                     </div>
                                     </div>
-                                    <div class="col-2 pl-0">
+                                    <div class="col-auto pl-0">
+                                        <div class="card text-center <% if (!ctx.tender.info.display.dayMode) { %>bg-dark text-white<% } %>">
+                                            <div class="card-body">
+                                                <h5 class="card-title card-big-title"><%- ctx.helper.formatMoney(tender.change_tp) %></h5>
+                                                <p class="card-text text-muted">已批复变更金额</p>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div class="col-auto pl-0">
+                                        <div class="card text-center <% if (!ctx.tender.info.display.dayMode) { %>bg-dark text-white<% } %>">
+                                            <div class="card-body">
+                                                <h5 class="card-title card-big-title"><%- ctx.helper.formatMoney(ctx.helper.add(tender.total_price, tender.change_tp)) %></h5>
+                                                <p class="card-text text-muted">变更后金额</p>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div class="col-auto pl-0">
                                         <div class="card text-center <% if (!ctx.tender.info.display.dayMode) { %>bg-dark text-white<% } %>">
                                         <div class="card text-center <% if (!ctx.tender.info.display.dayMode) { %>bg-dark text-white<% } %>">
                                             <div class="card-body">
                                             <div class="card-body">
                                                 <h5 class="card-title card-big-title"><%- ctx.helper.formatMoney(tender.end_qc_tp) %> <small class="text-danger"  data-toggle="tooltip" data-placement="bottom" title="" data-original-title="占合同比例"><%- tender.qc_ratio %>%</small></h5>
                                                 <h5 class="card-title card-big-title"><%- ctx.helper.formatMoney(tender.end_qc_tp) %> <small class="text-danger"  data-toggle="tooltip" data-placement="bottom" title="" data-original-title="占合同比例"><%- tender.qc_ratio %>%</small></h5>
@@ -91,7 +107,7 @@
                                             </div>
                                             </div>
                                         </div>
                                         </div>
                                     </div>
                                     </div>
-                                    <div class="col-2 pl-0">
+                                    <div class="col-auto pl-0">
                                         <div class="card text-center <% if (!ctx.tender.info.display.dayMode) { %>bg-dark text-white<% } %>">
                                         <div class="card text-center <% if (!ctx.tender.info.display.dayMode) { %>bg-dark text-white<% } %>">
                                             <div class="card-body">
                                             <div class="card-body">
                                                 <h5 class="card-title card-big-title"><%- ctx.helper.formatMoney(tender.gather_tp) %></h5>
                                                 <h5 class="card-title card-big-title"><%- ctx.helper.formatMoney(tender.gather_tp) %></h5>
@@ -99,7 +115,7 @@
                                             </div>
                                             </div>
                                         </div>
                                         </div>
                                     </div>
                                     </div>
-                                    <div class="col-2 pl-0">
+                                    <div class="col-auto pl-0">
                                         <div class="card text-center <% if (!ctx.tender.info.display.dayMode) { %>bg-dark text-white<% } %>">
                                         <div class="card text-center <% if (!ctx.tender.info.display.dayMode) { %>bg-dark text-white<% } %>">
                                             <div class="card-body">
                                             <div class="card-body">
                                                 <h5 class="card-title card-big-title"><%- ctx.helper.formatMoney(tender.end_gather_tp) %></h5>
                                                 <h5 class="card-title card-big-title"><%- ctx.helper.formatMoney(tender.end_gather_tp) %></h5>
@@ -107,15 +123,15 @@
                                             </div>
                                             </div>
                                         </div>
                                         </div>
                                     </div>
                                     </div>
-                                    <div class="col-2 pl-0">
-                                        <div class="card text-center <% if (!ctx.tender.info.display.dayMode) { %>bg-dark text-white<% } %>">
-                                            <div class="card-body">
-                                                <h5 class="card-title card-big-title"><%- ctx.helper.formatMoney(tender.pre_gather_tp) %></h5>
-                                                <p class="card-text text-muted">截止上期完成</p>
-                                            </div>
-                                        </div>
-                                    </div>
-                                    <div class="col-2 pl-0">
+                                    <!--<div class="col-auto pl-0">-->
+                                        <!--<div class="card text-center <% if (!ctx.tender.info.display.dayMode) { %>bg-dark text-white<% } %>">-->
+                                            <!--<div class="card-body">-->
+                                                <!--<h5 class="card-title card-big-title"><%- ctx.helper.formatMoney(tender.pre_gather_tp) %></h5>-->
+                                                <!--<p class="card-text text-muted">截止上期完成</p>-->
+                                            <!--</div>-->
+                                        <!--</div>-->
+                                    <!--</div>-->
+                                    <div class="col-auto pl-0">
                                         <div class="card text-center <% if (!ctx.tender.info.display.dayMode) { %>bg-dark text-white<% } %>">
                                         <div class="card text-center <% if (!ctx.tender.info.display.dayMode) { %>bg-dark text-white<% } %>">
                                             <div class="card-body">
                                             <div class="card-body">
                                                 <h5 class="card-title card-big-title"><%- ctx.helper.formatMoney(tender.yf_tp) %></h5>
                                                 <h5 class="card-title card-big-title"><%- ctx.helper.formatMoney(tender.yf_tp) %></h5>

+ 3 - 2
app/view/tender/tender_sub_menu.ejs

@@ -36,7 +36,7 @@
             </ul>
             </ul>
         </div>
         </div>
         <div class="nav-box">
         <div class="nav-box">
-            <% if (!ctx.session.sessionProject.page_show.openChangeProject && !ctx.session.sessionProject.page_show.openChangeApply) { %>
+            <% if (!ctx.session.sessionProject.page_show.openChangeProject && !ctx.session.sessionProject.page_show.openChangeApply && !ctx.session.sessionProject.page_show.openChangePlan) { %>
             <ul class="nav-list list-unstyled">
             <ul class="nav-list list-unstyled">
                 <li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change') !== -1) { %>class="active"<% } %>><a class="change_sort_link h3" href="/tender/<%- ctx.tender.id %>/change"><i class="fa fa-retweet fa-fw"></i> <span>工程变更</span></a></li>
                 <li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change') !== -1) { %>class="active"<% } %>><a class="change_sort_link h3" href="/tender/<%- ctx.tender.id %>/change"><i class="fa fa-retweet fa-fw"></i> <span>工程变更</span></a></li>
             </ul>
             </ul>
@@ -45,7 +45,8 @@
             <ul class="nav-list list-unstyled sub-list">
             <ul class="nav-list list-unstyled sub-list">
                 <% if (ctx.session.sessionProject.page_show.openChangeProject) { %><li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/project') !== -1) { %>class="active"<% } %>><a class="change_project_sort_link" href="/tender/<%- ctx.tender.id %>/change/project"><span>变更立项</span></a></li><% } %>
                 <% if (ctx.session.sessionProject.page_show.openChangeProject) { %><li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/project') !== -1) { %>class="active"<% } %>><a class="change_project_sort_link" href="/tender/<%- ctx.tender.id %>/change/project"><span>变更立项</span></a></li><% } %>
                     <% if (ctx.session.sessionProject.page_show.openChangeApply) { %><li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/apply') !== -1) { %>class="active"<% } %>><a class="change_apply_sort_link" href="/tender/<%- ctx.tender.id %>/change/apply"><span>变更申请</span></a></li><% } %>
                     <% if (ctx.session.sessionProject.page_show.openChangeApply) { %><li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/apply') !== -1) { %>class="active"<% } %>><a class="change_apply_sort_link" href="/tender/<%- ctx.tender.id %>/change/apply"><span>变更申请</span></a></li><% } %>
-                <li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change') !== -1 && ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/project') === -1 && ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/apply') === -1) { %>class="active"<% } %>><a class="change_sort_link" href="/tender/<%- ctx.tender.id %>/change"><span>变更令</span></a></li>
+                    <% if (ctx.session.sessionProject.page_show.openChangePlan) { %><li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/plan') !== -1) { %>class="active"<% } %>><a class="change_plan_sort_link" href="/tender/<%- ctx.tender.id %>/change/plan"><span>变更方案</span></a></li><% } %>
+                <li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change') !== -1 && ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/project') === -1 && ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/apply') === -1 &&  ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/plan') === -1) { %>class="active"<% } %>><a class="change_sort_link" href="/tender/<%- ctx.tender.id %>/change"><span>变更令</span></a></li>
             </ul>
             </ul>
             <% } %>
             <% } %>
         </div>
         </div>

+ 3 - 2
app/view/tender/tender_sub_mini_menu.ejs

@@ -36,7 +36,7 @@
             </ul>
             </ul>
         </div>
         </div>
         <div class="nav-box">
         <div class="nav-box">
-            <% if (!ctx.session.sessionProject.page_show.openChangeProject && !ctx.session.sessionProject.page_show.openChangeApply) { %>
+            <% if (!ctx.session.sessionProject.page_show.openChangeProject && !ctx.session.sessionProject.page_show.openChangeApply && !ctx.session.sessionProject.page_show.openChangePlan) { %>
                 <ul class="nav-list list-unstyled">
                 <ul class="nav-list list-unstyled">
                     <li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change') !== -1) { %>class="active"<% } %>><a class="change_sort_link h3" href="/tender/<%- ctx.tender.id %>/change"><i class="fa fa-retweet fa-fw"></i> <span>工程变更</span></a></li>
                     <li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change') !== -1) { %>class="active"<% } %>><a class="change_sort_link h3" href="/tender/<%- ctx.tender.id %>/change"><i class="fa fa-retweet fa-fw"></i> <span>工程变更</span></a></li>
                 </ul>
                 </ul>
@@ -45,7 +45,8 @@
                 <ul class="nav-list list-unstyled sub-list">
                 <ul class="nav-list list-unstyled sub-list">
                     <% if (ctx.session.sessionProject.page_show.openChangeProject) { %><li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/project') !== -1) { %>class="active"<% } %>><a class="change_project_sort_link" href="/tender/<%- ctx.tender.id %>/change/project"><span>变更立项</span></a></li><% } %>
                     <% if (ctx.session.sessionProject.page_show.openChangeProject) { %><li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/project') !== -1) { %>class="active"<% } %>><a class="change_project_sort_link" href="/tender/<%- ctx.tender.id %>/change/project"><span>变更立项</span></a></li><% } %>
                     <% if (ctx.session.sessionProject.page_show.openChangeApply) { %><li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/apply') !== -1) { %>class="active"<% } %>><a class="change_apply_sort_link" href="/tender/<%- ctx.tender.id %>/change/apply"><span>变更申请</span></a></li><% } %>
                     <% if (ctx.session.sessionProject.page_show.openChangeApply) { %><li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/apply') !== -1) { %>class="active"<% } %>><a class="change_apply_sort_link" href="/tender/<%- ctx.tender.id %>/change/apply"><span>变更申请</span></a></li><% } %>
-                    <li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change') !== -1 && ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/project') === -1 && ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/apply') === -1) { %>class="active"<% } %>><a class="change_sort_link" href="/tender/<%- ctx.tender.id %>/change"><span>变更令</span></a></li>
+                    <% if (ctx.session.sessionProject.page_show.openChangePlan) { %><li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/plan') !== -1) { %>class="active"<% } %>><a class="change_plan_sort_link" href="/tender/<%- ctx.tender.id %>/change/plan"><span>变更方案</span></a></li><% } %>
+                    <li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/change') !== -1 && ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/project') === -1 && ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/apply') === -1 &&  ctx.url.indexOf('/tender/' + ctx.tender.id + '/change/plan') === -1) { %>class="active"<% } %>><a class="change_sort_link" href="/tender/<%- ctx.tender.id %>/change"><span>变更令</span></a></li>
                 </ul>
                 </ul>
             <% } %>
             <% } %>
         </div>
         </div>

+ 137 - 5
builder_report_index_define.js

@@ -60,6 +60,7 @@ const ledger_cooperation = {
         { name: '台账id', field: 'ledger_id', type: dataType.int },
         { name: '台账id', field: 'ledger_id', type: dataType.int },
         { name: '密码', field: 'pwd', type: dataType.str },
         { name: '密码', field: 'pwd', type: dataType.str },
         { name: '签字地址', field: 'sign_path', type: dataType.str },
         { name: '签字地址', field: 'sign_path', type: dataType.str },
+        { name: '单位/协作人名称', field: 'company', type: dataType.str },
     ],
     ],
 };
 };
 // 其他台账
 // 其他台账
@@ -304,6 +305,8 @@ const change_bills = {
         { name: '(空) 截止本期-已变更', field: 'used_qty', type: dataType.currency },
         { name: '(空) 截止本期-已变更', field: 'used_qty', type: dataType.currency },
         { name: '(空) 变更令-本期使用', field: 'is_used_bgl', type: dataType.int },
         { name: '(空) 变更令-本期使用', field: 'is_used_bgl', type: dataType.int },
         { name: '(空) 变更清单-本期使用', field: 'is_used', type: dataType.int },
         { name: '(空) 变更清单-本期使用', field: 'is_used', type: dataType.int },
+        { name: '审批流程中读取数量', field: 'sp_qty', type: dataType.currency },
+        { name: '审批流程中金额', field: 'sp_tp', type: dataType.currency },
     ],
     ],
 };
 };
 const change_audit = {
 const change_audit = {
@@ -407,6 +410,8 @@ const change_apply = {
         { name: '原设计情况描述', field: 'org_content', type: dataType.str },
         { name: '原设计情况描述', field: 'org_content', type: dataType.str },
         { name: '现场实际情况描述', field: 'site_content', type: dataType.str },
         { name: '现场实际情况描述', field: 'site_content', type: dataType.str },
         { name: '内容摘要', field: 'content', type: dataType.str },
         { name: '内容摘要', field: 'content', type: dataType.str },
+        { name: '金额', field: 'total_price', type: dataType.currency },
+        { name: '小数位数设置JSON', field: 'decimal', type: dataType.currency },
     ],
     ],
 };
 };
 const change_apply_audit = {
 const change_apply_audit = {
@@ -428,6 +433,120 @@ const change_apply_audit = {
         { name: '审批意见', field: 'opinion', type: dataType.str },
         { name: '审批意见', field: 'opinion', type: dataType.str },
     ],
     ],
 };
 };
+const change_apply_bills = {
+    name: '变更申请 清单(mem_change_apply_bills)',
+    remark: '',
+    id: 79,
+    key: 'mem_change_apply_bills',
+    prefix: '变更申请 清单',
+    cols: [
+        { name: 'id', field: 'id', type: dataType.int },
+        { name: '标段id', field: 'tid', type: dataType.int },
+        { name: '变更申请id', field: 'caid', type: dataType.int },
+        { name: '清单编号', field: 'code', type: dataType.str },
+        { name: '名称', field: 'name', type: dataType.str },
+        { name: '单位', field: 'unit', type: dataType.str },
+        { name: '单价', field: 'unit_price', type: dataType.currency },
+        { name: '原数量', field: 'oamount', type: dataType.currency },
+        { name: '变更数量', field: 'camount', type: dataType.currency },
+        { name: '原金额', field: 'o_tp', type: dataType.currency },
+        { name: '变更金额', field: 'c_tp', type: dataType.currency },
+    ]
+};
+
+const change_plan = {
+    name: '变更方案(mem_change_plan)',
+    remark: '',
+    id: 80,
+    key: 'mem_change_plan',
+    prefix: '变更方案',
+    cols: [
+        { name: 'id', field: 'id', type: dataType.int },
+        { name: '标段id', field: 'tid', type: dataType.int },
+        { name: '报表用,是否选择', field: 'selected', type: dataType.int },
+        { name: '发起人', field: 'uid', type: dataType.int },
+        { name: '变更方案编号', field: 'code', type: dataType.str },
+        { name: '变更工程名称', field: 'name', type: dataType.str },
+        { name: '发起时间', field: 'in_time', type: dataType.time },
+        { name: '方案状态', field: 'status', type: dataType.int },
+        { name: '审批次数', field: 'times', type: dataType.int },
+        { name: '变更申请编号', field: 'apply_code', type: dataType.str },
+        { name: '原设计图名称', field: 'org_name', type: dataType.str },
+        { name: '桩号', field: 'peg', type: dataType.str },
+        { name: '图号', field: 'new_code', type: dataType.str },
+        { name: '变更图号', field: 'c_new_code', type: dataType.str },
+        { name: '变更设计名称', field: 'design_name', type: dataType.str },
+        { name: '工程变更类别', field: 'class', type: dataType.str },
+        { name: '工程变更性质', field: 'quality', type: dataType.str },
+        { name: '变更原因', field: 'reason', type: dataType.str },
+        { name: '变更内容', field: 'content', type: dataType.str },
+        { name: '方案描述', field: 'memo', type: dataType.str },
+        { name: '金额', field: 'total_price', type: dataType.currency },
+        { name: '小数位数设置JSON', field: 'decimal', type: dataType.int },
+    ],
+};
+const change_plan_audit = {
+    name: '变更方案 审批(mem_change_plan_audit)',
+    remark: '',
+    id: 81,
+    key: 'mem_change_plan_audit',
+    prefix: '变更方案 审批',
+    cols: [
+        { name: 'id', field: 'id', type: dataType.int },
+        { name: '标段id', field: 'tid', type: dataType.int },
+        { name: '方案id', field: 'cpid', type: dataType.int },
+        { name: '审批人id', field: 'aid', type: dataType.int },
+        { name: '审批顺序', field: 'order', type: dataType.int },
+        { name: '审批次数', field: 'times', type: dataType.int },
+        { name: '审批状态', field: 'status', type: dataType.int },
+        { name: '开始审批时间', field: 'begin_time', type: dataType.time },
+        { name: '结束审批时间', field: 'end_time', type: dataType.time },
+        { name: '审批意见', field: 'opinion', type: dataType.str },
+    ],
+};
+const change_plan_bills = {
+    name: '变更方案 清单(mem_change_plan_bills)',
+    remark: '',
+    id: 82,
+    key: 'mem_change_plan_bills',
+    prefix: '变更方案 清单',
+    cols: [
+        { name: 'id', field: 'id', type: dataType.int },
+        { name: '标段id', field: 'tid', type: dataType.int },
+        { name: '变更方案id', field: 'cpid', type: dataType.int },
+        { name: '清单编号', field: 'code', type: dataType.str },
+        { name: '名称', field: 'name', type: dataType.str },
+        { name: '单位', field: 'unit', type: dataType.str },
+        { name: '单价', field: 'unit_price', type: dataType.currency },
+        { name: '原数量', field: 'oamount', type: dataType.currency },
+        { name: '变更数量', field: 'camount', type: dataType.currency },
+        { name: '审批完成后变更数量', field: 'samount', type: dataType.currency },
+        { name: '用户填的数目, json', field: 'audit_amount', type: dataType.str },
+        { name: '审批流程中读取数量', field: 'spamount', type: dataType.currency },
+        { name: '原金额', field: 'o_tp', type: dataType.currency },
+        { name: '变更金额', field: 'c_tp', type: dataType.currency },
+        { name: '审批完成后变更金额', field: 's_tp', type: dataType.currency },
+        { name: '审批流程中读取金额', field: 'sp_tp', type: dataType.currency },
+        { name: '数量_1', field: 'qty_1', type: dataType.currency },
+        { name: '金额_1', field: 'tp_1', type: dataType.currency },
+        { name: '数量_2', field: 'qty_2', type: dataType.currency },
+        { name: '金额_2', field: 'tp_2', type: dataType.currency },
+        { name: '数量_3', field: 'qty_3', type: dataType.currency },
+        { name: '金额_3', field: 'tp_3', type: dataType.currency },
+        { name: '数量_4', field: 'qty_4', type: dataType.currency },
+        { name: '金额_4', field: 'tp_4', type: dataType.currency },
+        { name: '数量_5', field: 'qty_5', type: dataType.currency },
+        { name: '金额_5', field: 'tp_5', type: dataType.currency },
+        { name: '数量_6', field: 'qty_6', type: dataType.currency },
+        { name: '金额_6', field: 'tp_6', type: dataType.currency },
+        { name: '数量_7', field: 'qty_7', type: dataType.currency },
+        { name: '金额_7', field: 'tp_7', type: dataType.currency },
+        { name: '数量_8', field: 'qty_8', type: dataType.currency },
+        { name: '金额_8', field: 'tp_8', type: dataType.currency },
+        { name: '数量_9', field: 'qty_9', type: dataType.currency },
+        { name: '金额_9', field: 'tp_9', type: dataType.currency },
+    ]
+};
 
 
 // 期 - 清单
 // 期 - 清单
 const stage_bills = {
 const stage_bills = {
@@ -849,6 +968,7 @@ const stage_pay = {
         { name: '流程-本期-金额_8', field: 'r8_tp', type: dataType.currency },
         { name: '流程-本期-金额_8', field: 'r8_tp', type: dataType.currency },
         { name: '流程-本期-金额_9', field: 'r9_tp', type: dataType.currency },
         { name: '流程-本期-金额_9', field: 'r9_tp', type: dataType.currency },
         { name: '第几期开始计量', field: 'start_stage_order', type: dataType.int },
         { name: '第几期开始计量', field: 'start_stage_order', type: dataType.int },
+        { name: '本期批注', field: 'postil', type: dataType.str },
     ],
     ],
 };
 };
 
 
@@ -909,13 +1029,17 @@ const stage_im_zl = {
         { name: '审批时间2', field: 'co_time2', type: dataType.time },
         { name: '审批时间2', field: 'co_time2', type: dataType.time },
         { name: '审批时间3', field: 'co_time3', type: dataType.time },
         { name: '审批时间3', field: 'co_time3', type: dataType.time },
         { name: '审批时间4', field: 'co_time4', type: dataType.time },
         { name: '审批时间4', field: 'co_time4', type: dataType.time },
-        { name: '审批意见1', field: 'co_opinion1', type: dataType.time },
-        { name: '审批意见2', field: 'co_opinion2', type: dataType.time },
-        { name: '审批意见3', field: 'co_opinion3', type: dataType.time },
-        { name: '审批意见4', field: 'co_opinion4', type: dataType.time },
+        { name: '审批意见1', field: 'co_opinion1', type: dataType.str },
+        { name: '审批意见2', field: 'co_opinion2', type: dataType.str },
+        { name: '审批意见3', field: 'co_opinion3', type: dataType.str },
+        { name: '审批意见4', field: 'co_opinion4', type: dataType.str },
         { name: '单位工程', field: 'dwgc', type: dataType.str },
         { name: '单位工程', field: 'dwgc', type: dataType.str },
         { name: '分部工程', field: 'fbgc', type: dataType.str },
         { name: '分部工程', field: 'fbgc', type: dataType.str },
         { name: '分项工程', field: 'fxgc', type: dataType.str },
         { name: '分项工程', field: 'fxgc', type: dataType.str },
+        { name: '单位/协同人姓名1', field: 'co_company1', type: dataType.str },
+        { name: '单位/协同人姓名2', field: 'co_company2', type: dataType.str },
+        { name: '单位/协同人姓名3', field: 'co_company3', type: dataType.str },
+        { name: '单位/协同人姓名4', field: 'co_company4', type: dataType.str },
     ],
     ],
 };
 };
 const stage_im_tz = {
 const stage_im_tz = {
@@ -968,6 +1092,10 @@ const stage_im_tz = {
         { name: '单位工程', field: 'dwgc', type: dataType.str },
         { name: '单位工程', field: 'dwgc', type: dataType.str },
         { name: '分部工程', field: 'fbgc', type: dataType.str },
         { name: '分部工程', field: 'fbgc', type: dataType.str },
         { name: '分项工程', field: 'fxgc', type: dataType.str },
         { name: '分项工程', field: 'fxgc', type: dataType.str },
+        { name: '单位/协同人姓名1', field: 'co_company1', type: dataType.str },
+        { name: '单位/协同人姓名2', field: 'co_company2', type: dataType.str },
+        { name: '单位/协同人姓名3', field: 'co_company3', type: dataType.str },
+        { name: '单位/协同人姓名4', field: 'co_company4', type: dataType.str },
     ],
     ],
 };
 };
 const stage_im_tz_bills = {
 const stage_im_tz_bills = {
@@ -1395,6 +1523,7 @@ const select_material = {
         { name: '截止上期金额', field: 'pre_tp', type: dataType.currency },
         { name: '截止上期金额', field: 'pre_tp', type: dataType.currency },
         { name: '税率', field: 'rate', type: dataType.int },
         { name: '税率', field: 'rate', type: dataType.int },
         { name: '创建时间', field: 'in_time', type: dataType.str },
         { name: '创建时间', field: 'in_time', type: dataType.str },
+        { name: '调差周期', field: 'period', type: dataType.str },
 
 
     ],
     ],
 };
 };
@@ -1447,6 +1576,7 @@ const material = {
         { name: '截止上期材料含税金额', field: 'm_tax_pre_tp', type: dataType.currency },
         { name: '截止上期材料含税金额', field: 'm_tax_pre_tp', type: dataType.currency },
         { name: '小数位数设置JSON', field: 'decimal', type: dataType.currency },
         { name: '小数位数设置JSON', field: 'decimal', type: dataType.currency },
         { name: '批复时间', field: 'checked_time', type: dataType.str },
         { name: '批复时间', field: 'checked_time', type: dataType.str },
+        { name: '调差周期', field: 'period', type: dataType.str },
 
 
     ],
     ],
 };
 };
@@ -2370,7 +2500,9 @@ const defines = [
     stage_bills, stage_bills_compare,
     stage_bills, stage_bills_compare,
     stage_jgcl, stage_bonus, stage_other, stage_safe_prod, stage_temp_land,
     stage_jgcl, stage_bonus, stage_other, stage_safe_prod, stage_temp_land,
     change, change_bills, change_audit,
     change, change_bills, change_audit,
-    change_apply, change_apply_audit, change_project, change_project_audit,
+    change_apply, change_apply_audit, change_apply_bills,
+    change_plan, change_plan_audit, change_plan_bills,
+    change_project, change_project_audit,
     stage_pos, stage_pos_compare,
     stage_pos, stage_pos_compare,
     stage_pay,
     stage_pay,
     stage_im_zl, stage_im_tz, stage_im_tz_bills,
     stage_im_zl, stage_im_tz, stage_im_tz_bills,

+ 30 - 1
config/web.js

@@ -926,8 +926,14 @@ const JsFiles = {
                 mergeFile: 'change_apply',
                 mergeFile: 'change_apply',
             },
             },
             apply_information: {
             apply_information: {
-                files: ['/public/js/moment/moment.min.js'],
+                files: ['/public/js/moment/moment.min.js', '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js', '/public/js/decimal.min.js'],
                 mergeFiles: [
                 mergeFiles: [
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/gcl_gather.js',
                     '/public/js/sub_menu.js',
                     '/public/js/sub_menu.js',
                     '/public/js/change_apply_audit.js',
                     '/public/js/change_apply_audit.js',
                     '/public/js/change_apply_information.js',
                     '/public/js/change_apply_information.js',
@@ -943,6 +949,29 @@ const JsFiles = {
                 ],
                 ],
                 mergeFile: 'change_apply_information_notice',
                 mergeFile: 'change_apply_information_notice',
             },
             },
+            plan: {
+                files: ['/public/js/moment/moment.min.js'],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/change_plan.js',
+                ],
+                mergeFile: 'change_plan',
+            },
+            plan_information: {
+                files: ['/public/js/moment/moment.min.js', '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js', '/public/js/decimal.min.js'],
+                mergeFiles: [
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/gcl_gather.js',
+                    '/public/js/sub_menu.js',
+                    '/public/js/change_plan_audit.js',
+                    '/public/js/change_plan_information.js',
+                ],
+                mergeFile: 'change_plan_information',
+            },
         },
         },
         datacollect: {
         datacollect: {
             index: {
             index: {

+ 99 - 1
sql/update.sql

@@ -139,4 +139,102 @@ CREATE TABLE `zh_change_project_xs_audit`  (
   PRIMARY KEY (`id`) USING BTREE,
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `cpid`(`cpid`) USING BTREE,
   INDEX `cpid`(`cpid`) USING BTREE,
   INDEX `tid`(`tid`) USING BTREE
   INDEX `tid`(`tid`) USING BTREE
-) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '立项协审人' ROW_FORMAT = Dynamic;
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '立项协审人' ROW_FORMAT = Dynamic;
+
+
+ALTER TABLE `zh_change_apply`
+ADD `total_price` DECIMAL(30,8) NULL DEFAULT NULL COMMENT '金额' AFTER `content`,
+ADD `decimal` VARCHAR(255) NULL DEFAULT NULL COMMENT '小数位数设置JSON' AFTER `total_price`;
+
+DROP TABLE IF EXISTS `zh_change_plan`;
+CREATE TABLE `zh_change_plan`  (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `selected` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '报表用,是否选择',
+  `uid` int(11) NOT NULL COMMENT '发起人',
+  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '变更方案编号',
+  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '变更工程名称',
+  `in_time` datetime NOT NULL COMMENT '发起时间',
+  `status` tinyint(2) NOT NULL COMMENT '方案状态',
+  `times` tinyint(2) NOT NULL COMMENT '审批次数',
+  `apply_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '变更申请编号',
+  `org_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '原设计图名称',
+  `peg` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '桩号',
+  `new_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '图号',
+  `c_new_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '变更图号',
+  `design_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '变更设计名称',
+  `class` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '工程变更类别',
+  `quality` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '工程变更性质',
+  `reason` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL COMMENT '变更原因',
+  `content` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL COMMENT '变更内容',
+  `memo` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL COMMENT '方案描述',
+  `total_price` decimal(30, 8) NULL DEFAULT NULL COMMENT '金额',
+  `decimal` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '小数位数设置JSON',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '变更方案表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for zh_change_plan_attachment
+-- ----------------------------
+DROP TABLE IF EXISTS `zh_change_plan_attachment`;
+CREATE TABLE `zh_change_plan_attachment`  (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `cpid` int(11) NOT NULL COMMENT '方案id',
+  `uid` int(11) NOT NULL COMMENT '上传者id',
+  `filename` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '文件名称',
+  `fileext` varchar(5) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '文件后缀',
+  `filesize` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '文件大小',
+  `filepath` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '文件存储路径',
+  `upload_time` datetime NOT NULL COMMENT '上传时间',
+  `extra_upload` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否为审核通过后再次上传的文件,0为否',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_cid`(`cpid`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '变更立项附件表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for zh_change_plan_audit
+-- ----------------------------
+DROP TABLE IF EXISTS `zh_change_plan_audit`;
+CREATE TABLE `zh_change_plan_audit`  (
+  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `cpid` int(11) NOT NULL COMMENT '方案id',
+  `aid` int(11) NOT NULL COMMENT '审批人id',
+  `order` int(11) NOT NULL COMMENT '审批顺序',
+  `times` int(11) NOT NULL COMMENT '审批次数',
+  `status` tinyint(1) NOT NULL COMMENT '审批状态',
+  `begin_time` datetime NULL DEFAULT NULL COMMENT '开始审批时间',
+  `end_time` datetime NULL DEFAULT NULL COMMENT '结束审批时间',
+  `opinion` varchar(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '审批意见',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '变更立项审批表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for zh_change_plan_list
+-- ----------------------------
+DROP TABLE IF EXISTS `zh_change_plan_list`;
+CREATE TABLE `zh_change_plan_list`  (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `cpid` int(11) NOT NULL COMMENT '方案id',
+  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '清单编号',
+  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '清单名称',
+  `unit` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '单位',
+  `unit_price` decimal(30, 8) NULL DEFAULT NULL COMMENT '单价',
+  `oamount` decimal(30, 8) NULL DEFAULT NULL COMMENT '原数量',
+  `camount` decimal(30, 8) NULL DEFAULT NULL COMMENT '变更数量',
+  `samount` decimal(30, 8) NULL DEFAULT NULL COMMENT '审批完成后变更数量',
+  `audit_amount` varchar(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '用户填的数目,json',
+  `spamount` decimal(30, 8) NULL DEFAULT NULL COMMENT '审批流程中读取数量',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '变更方案清单表' ROW_FORMAT = Dynamic;
+
+ALTER TABLE `zh_material` ADD `period` VARCHAR(255) NULL DEFAULT NULL COMMENT '调差周期' AFTER `s_order`;
+
+ALTER TABLE `zh_tender`
+ADD `c_apply_list_rule` VARCHAR(255) NULL DEFAULT NULL COMMENT '变更申请统一原设计数量读取规则' AFTER `pos_file`,
+ADD `c_plan_list_rule` VARCHAR(255) NULL DEFAULT NULL COMMENT '变更方案统一原设计数量读取规则' AFTER `c_apply_list_rule`;
+
+ALTER TABLE `zh_change` ADD `plan_code` VARCHAR(255) NULL DEFAULT NULL COMMENT '变更方案编号' AFTER `name`;
+