Browse Source

Merge branch 'master' of http://192.168.1.41:3000/maixinrong/Calculation

Conflicts:
	app/view/report/index.ejs
TonyKang 5 years ago
parent
commit
9006304cef
75 changed files with 5328 additions and 3320 deletions
  1. 12 0
      app/const/audit.js
  2. 50 0
      app/const/sms_type.js
  3. 29 3
      app/controller/change_controller.js
  4. 66 1
      app/controller/measure_controller.js
  5. 59 2
      app/controller/profile_controller.js
  6. 1 1
      app/controller/sign_controller.js
  7. 70 22
      app/controller/stage_controller.js
  8. 4 1
      app/extend/helper.js
  9. 5 1
      app/lib/pay_calc.js
  10. 35 22
      app/lib/sms.js
  11. 6 1
      app/middleware/stage_check.js
  12. 3 2
      app/public/js/change_calculation.js
  13. 64 3
      app/public/js/global.js
  14. 86 120
      app/public/js/ledger.js
  15. 47 2
      app/public/js/ledger_audit.js
  16. 163 0
      app/public/js/ledger_search.js
  17. 241 0
      app/public/js/measure_compare.js
  18. 31 6
      app/public/js/profile.js
  19. 48 4
      app/public/js/revise.js
  20. 14 0
      app/public/js/spreadjs_rela/spreadjs_zh.js
  21. 97 11
      app/public/js/stage.js
  22. 2 2
      app/public/js/stage_audit.js
  23. 55 25
      app/public/js/stage_change.js
  24. 80 37
      app/public/js/stage_compare.js
  25. 44 5
      app/public/js/stage_detail.js
  26. 19 0
      app/public/js/stage_gather.js
  27. 20 1
      app/public/js/stage_pay.js
  28. 50 0
      app/public/js/sub_menu.js
  29. 9 3
      app/router.js
  30. 176 17
      app/service/change.js
  31. 0 4
      app/service/ledger.js
  32. 52 1
      app/service/ledger_audit.js
  33. 37 7
      app/service/project_account.js
  34. 55 1
      app/service/stage.js
  35. 141 5
      app/service/stage_audit.js
  36. 20 12
      app/service/stage_bills.js
  37. 14 0
      app/service/stage_bills_final.js
  38. 15 1
      app/service/stage_pos_final.js
  39. 20 1
      app/view/change/index.ejs
  40. 4 0
      app/view/change/info.ejs
  41. 25 0
      app/view/change/info_modal.ejs
  42. 12 4
      app/view/ledger/audit.ejs
  43. 23 26
      app/view/ledger/explode.ejs
  44. 3 1
      app/view/ledger/explode_modal.ejs
  45. 25 5
      app/view/measure/compare.ejs
  46. 4 4
      app/view/measure/compare_modal.ejs
  47. 28 9
      app/view/measure/stage.ejs
  48. 22 0
      app/view/measure/stage_modal.ejs
  49. 16 0
      app/view/profile/modal.ejs
  50. 5 6
      app/view/profile/sign.ejs
  51. 45 13
      app/view/profile/sms.ejs
  52. 23 1
      app/view/report/index.ejs
  53. 22 3
      app/view/revise/index.ejs
  54. 22 22
      app/view/revise/info.ejs
  55. 4 5
      app/view/sign/info.ejs
  56. 7 4
      app/view/stage/audit_btn.ejs
  57. 66 27
      app/view/stage/audit_modal.ejs
  58. 4 9
      app/view/stage/change.ejs
  59. 33 29
      app/view/stage/compare.ejs
  60. 2 2
      app/view/stage/compare_modal.ejs
  61. 2 1
      app/view/stage/deal.ejs
  62. 9 4
      app/view/stage/detail.ejs
  63. 2 2
      app/view/stage/detail_modal.ejs
  64. 4 3
      app/view/stage/gather.ejs
  65. 3 2
      app/view/stage/index.ejs
  66. 2 7
      app/view/stage/pay.ejs
  67. 2 1
      app/view/stage/stage_sub_menu.ejs
  68. 60 0
      app/view/stage/stage_sub_mini_menu.ejs
  69. 21 1
      app/view/tender/detail.ejs
  70. 2 1
      app/view/tender/tender_sub_menu.ejs
  71. 39 0
      app/view/tender/tender_sub_mini_menu.ejs
  72. 3 3
      config/config.default.js
  73. 33 1
      config/web.js
  74. 2810 2800
      package-lock.json
  75. 1 0
      package.json

+ 12 - 0
app/const/audit.js

@@ -69,6 +69,7 @@ const stage = (function () {
         checked: 3, // 审批通过
         checkNo: 4, // 审批退回原报
         checkNoPre: 5, // 审批退回上一人
+        checkAgain: 6, // 重新审批
     };
 
     // 流程状态提示
@@ -78,6 +79,7 @@ const stage = (function () {
     statusString[status.checked] = '审批通过';
     statusString[status.checkNo] = '审批退回';
     statusString[status.checkNoPre] = '审批退回';
+    statusString[status.checkAgain] = '重新审批';
 
     // 流程状态样式
     const statusClass = [];
@@ -86,6 +88,7 @@ const stage = (function () {
     statusClass[status.checked] = 'text-success';
     statusClass[status.checkNo] = 'text-warning';
     statusClass[status.checkNoPre] = 'text-warning';
+    statusClass[status.checkAgain] = 'text-warning';
 
     /**
      * 期列表,审批状态一列
@@ -97,6 +100,7 @@ const stage = (function () {
     statusButton[status.checked] = '';
     statusButton[status.checkNo] = '重新上报';
     statusButton[status.checkNoPre] = '重新审批';
+    statusButton[status.checkAgain] = '重新审批';
     // 按钮样式
     const statusButtonClass = [];
     statusButtonClass[status.uncheck] = 'btn-primary';
@@ -104,6 +108,7 @@ const stage = (function () {
     statusButtonClass[status.checked] = '';
     statusButtonClass[status.checkNo] = 'btn-warning';
     statusButtonClass[status.checkNoPre] = 'btn-warning';
+    statusButtonClass[status.checkAgain] = 'btn-warning';
     // 描述文本
     const auditString = [];
     auditString[status.uncheck] = '';
@@ -111,6 +116,7 @@ const stage = (function () {
     auditString[status.checked] = '完成';
     auditString[status.checkNo] = '退回';
     auditString[status.checkNoPre] = '退回';
+    auditString[status.checkAgain] = '重新审批';
     // 文字样式
     const auditStringClass = [];
     auditStringClass[status.uncheck] = '';
@@ -118,6 +124,7 @@ const stage = (function () {
     auditStringClass[status.checked] = 'text-success';
     auditStringClass[status.checkNo] = 'text-warning';
     auditStringClass[status.checkNoPre] = 'text-warning';
+    auditStringClass[status.checkAgain] = 'text-warning';
     /* ------------------------------------------------------- */
 
     /**
@@ -130,6 +137,7 @@ const stage = (function () {
     auditProgress[status.checked] = '审批通过';
     auditProgress[status.checkNo] = '审批退回';
     auditProgress[status.checkNoPre] = '审批退回';
+    auditProgress[status.checkAgain] = '重新审批';
     // 样式
     const auditProgressClass = [];
     auditProgressClass[status.uncheck] = '';
@@ -137,6 +145,7 @@ const stage = (function () {
     auditProgressClass[status.checked] = 'text-success';
     auditProgressClass[status.checkNo] = 'text-warning';
     auditProgressClass[status.checkNoPre] = 'text-warning';
+    auditProgressClass[status.checkAgain] = 'text-warning';
     /* ------------------------------------------------------- */
 
     const backType = {
@@ -198,6 +207,7 @@ const auditStatus = {
     // checkNo: 4,     // 审批终止
     back: 5,       // 退回到原报人重新上报
     backnew: 6,    // 退回到上一个审批人
+    checkAgain: 7,    // 重新审批
 };
 
 const auditStatusString = [];
@@ -207,6 +217,7 @@ auditStatusString[auditStatus.checked] = '审批通过';
 // auditStatusString[auditStatus.checkNo] = '审批终止';
 auditStatusString[auditStatus.back] = '退回';
 auditStatusString[auditStatus.backnew] = '审批退回';
+auditStatusString[auditStatus.checkAgain] = '重新审批';
 
 const auditStatusClass = [];
 auditStatusClass[auditStatus.uncheck] = '';
@@ -215,6 +226,7 @@ auditStatusClass[auditStatus.checked] = 'text-success';
 // auditStatusClass[auditStatus.checkNo] = 'text-danger';
 auditStatusClass[auditStatus.back] = 'text-warning';
 auditStatusClass[auditStatus.backnew] = 'text-warning';
+auditStatusClass[auditStatus.checkAgain] = 'text-warning';
 
 /* ------------------------------------------------------- */
 

+ 50 - 0
app/const/sms_type.js

@@ -0,0 +1,50 @@
+'use strict';
+
+/**
+ * 短信通知类型
+ *
+ * @author Ellisran
+ * @date 2019/8/20
+ * @version
+ */
+const smsConst = {
+    TZ: 'TZ',
+    JL: 'JL',
+    BG: 'BG',
+};
+const judgeConst = {
+    approval: 1,
+    result: 2,
+}
+const smsType = {
+    TZ: {
+        name: '台账审批',
+        path: '',
+        children: [
+            { title: '需要我审批', value: 1 },
+            { title: '审批结果', value: 2 },
+        ],
+    },
+    JL: {
+        name: '计量审批',
+        path: '',
+        children: [
+            { title: '需要我审批', value: 1 },
+            { title: '审批结果', value: 2 },
+        ],
+    },
+    BG: {
+        name: '变更管理',
+        path: '',
+        children: [
+            { title: '需要我审批', value: 1 },
+            { title: '审批结果', value: 2 },
+        ],
+    },
+};
+
+module.exports = {
+    const: smsConst,
+    judge: judgeConst,
+    type: smsType,
+};

+ 29 - 3
app/controller/change_controller.js

@@ -484,16 +484,16 @@ module.exports = app => {
                 let result = false;
                 switch (status) {
                     case 3:// 审批通过
-                        result = await ctx.service.change.approvalSuccess(ctx.request.body);
+                        result = await ctx.service.change.approvalSuccess(ctx.request.body, changeData);
                         break;
                     case 4:// 审批终止
                         result = await ctx.service.change.approvalStop(ctx.request.body);
                         break;
                     case 5:// 审批退回到原报人
-                        result = await ctx.service.change.approvalBack(ctx.request.body);
+                        result = await ctx.service.change.approvalBack(ctx.request.body, changeData);
                         break;
                     case 6:// 审批退回到上一个审批人
-                        result = await ctx.service.change.approvalBackNew(ctx.request.body);
+                        result = await ctx.service.change.approvalBackNew(ctx.request.body, changeData);
                         break;
                     default:break;
                 }
@@ -689,6 +689,32 @@ module.exports = app => {
                 ctx.redirect(ctx.request.header.referer);
             }
         }
+
+        /**
+         * 变更令重新审批
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async checkAgain(ctx) {
+            try {
+                const changeData = await ctx.service.change.getDataByCondition({ cid: ctx.request.body.cid });
+                if (!changeData) {
+                    throw '变更令数据错误';
+                }
+                if (changeData.status !== audit.flow.status.checked || ctx.session.sessionUser.accountId !== changeData.uid) {
+                    throw '您无权进行该操作';
+                }
+                // 重新审批
+                const result = await ctx.service.change.checkAgain(changeData.cid);
+                if (!result) {
+                    throw '重新审批失败';
+                }
+                ctx.redirect('/tender/' + changeData.tid + '/change/' + changeData.cid + '/info');
+            } catch (err) {
+                console.log(err);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
     }
 
     return ChangeController;

+ 66 - 1
app/controller/measure_controller.js

@@ -181,13 +181,78 @@ module.exports = app => {
                     tender: ctx.tender.data,
                     tenderMenu: this.menu.tenderMenu,
                     preUrl: '/tender/' + ctx.tender.id,
-                }
+                };
+                renderData.stages = await ctx.service.stage.getAllDataByCondition({
+                    where: {tid: ctx.tender.id, status: auditConst.status.checked},
+                    orders: [['order', 'asc']],
+                });
+                renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.measure.compare);
                 await this.layout('measure/compare.ejs', renderData, 'measure/compare_modal.ejs');
             } catch (err) {
                 this.log(err);
                 ctx.redirect(this.menu.menu.dashboard.url);
             }
         }
+
+        /**
+         * 多期比较 - 获取数据(Ajax)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async loadCompareData(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const result = {
+                    main: null,
+                    stages: []
+                };
+                if (data.main) {
+                    result.main = {};
+                    result.main.ledger = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
+                    result.main.pos = await ctx.service.pos.getPosData({tid: ctx.tender.id});
+                }
+                if (data.stages) {
+                    for (const order of data.stages) {
+                        const data = { order: order, bills: [], pos: [] };
+                        const stage = await this.ctx.service.stage.getDataByCondition({tid: ctx.tender.id, order: order});
+                        data.bills = await ctx.service.stageBills.getLastestStageData(ctx.tender.id, stage.id);
+                        data.pos = await ctx.service.stagePos.getLastestStageData(ctx.tender.id, stage.id);
+                        result.stages.push(data);
+                    }
+                }
+                ctx.body = {err: 0, msg: '', data: result};
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '获取数据错误');
+            }
+        }
+
+        /**
+         * 删除期(Post)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async delete(ctx) {
+            try {
+                const stage_id = ctx.request.body.stage_id;
+                const stageInfo = await ctx.service.stage.getDataById(stage_id);
+                // 获取最新的期数
+                const stage_highOrder = await ctx.service.stage.count({
+                    tid: ctx.tender.id,
+                });
+                if (stageInfo === undefined || ctx.session.sessionUser.accountId !== stageInfo.user_id || stage_highOrder !== stageInfo.order) {
+                    throw '您无权删除计量期';
+                }
+                const result = await ctx.service.stage.deleteStage(stage_id);
+                if (!result) {
+                    throw '删除计量期失败,请重试';
+                }
+                ctx.redirect('/tender/' + ctx.tender.id + '/measure/stage/');
+            } catch (err) {
+                this.log(err);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
     }
 
     return MeasureController;

+ 59 - 2
app/controller/profile_controller.js

@@ -9,6 +9,7 @@
  */
 
 const profileMenu = require('../../config/menu').profileMenu;
+const smsTypeConst = require('../const/sms_type');
 const qr = require('qr-image');
 
 module.exports = app => {
@@ -152,7 +153,7 @@ module.exports = app => {
                 ctx.helper.validate(rule);
 
                 const sessionUser = ctx.session.sessionUser;
-                const result = await ctx.service.projectAccount.bindMobile(sessionUser.accountId, ctx.request.body);
+                const result = await ctx.service.projectAccount.bindMobile(sessionUser.accountId, ctx.request.body, ctx.session.sessionProject.id);
 
                 if (!result) {
                     throw '绑定手机失败!';
@@ -180,8 +181,31 @@ module.exports = app => {
 
             const renderData = {
                 accountData,
+                smsType: smsTypeConst.type,
             };
-            await this.layout('profile/sms.ejs', renderData);
+            await this.layout('profile/sms.ejs', renderData, 'profile/modal.ejs');
+        }
+
+        /**
+         * 短信通知类型设置
+         *
+         * @param {object} ctx - egg全局变量
+         * @return {void}
+         */
+        async smsType(ctx) {
+            try {
+                const sessionUser = ctx.session.sessionUser;
+                const result = await ctx.service.projectAccount.smsTypeSet(sessionUser.accountId, ctx.request.body);
+
+                if (!result) {
+                    throw '修改通知类型失败!';
+                }
+                this.setMessage('通知类型绑定成功', this.messageType.SUCCESS);
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+            ctx.redirect(ctx.request.header.referer);
         }
 
         /**
@@ -204,6 +228,39 @@ module.exports = app => {
         }
 
         /**
+         * 电子签名删除
+         *
+         * @param {object} ctx - egg全局变量
+         * @return {void}
+         */
+        async signDelete(ctx) {
+            const response = {
+                err: 0,
+                msg: '',
+            };
+            try {
+                const sessionUser = ctx.session.sessionUser;
+
+                // 获取账号数据
+                const accountData = await ctx.service.projectAccount.getDataByCondition({ id: sessionUser.accountId });
+
+                if (accountData.sign_path === '') {
+                    throw '不存在签名';
+                }
+
+                const result = await ctx.service.projectAccount.update({ sign_path: '' }, { id: sessionUser.accountId });
+                if (!result) {
+                    throw '移除签名失败';
+                }
+            } catch (error) {
+                response.err = 1;
+                response.msg = error.toString();
+            }
+
+            ctx.body = response;
+        }
+
+        /**
          * 生成二维码
          *
          * @param {object} ctx - egg全局变量

+ 1 - 1
app/controller/sign_controller.js

@@ -30,7 +30,7 @@ module.exports = app => {
                 if (userinfo && userinfo.session_token && userinfo.session_token === ctx.query.app_token) {
                     renderData.id = userinfo.id;
                     renderData.name = userinfo.name;
-                    renderData.role = userinfo.role;
+                    // renderData.role = userinfo.role;
                 } else {
                     throw '参数有误, 无法访问本页.';
                 }

+ 70 - 22
app/controller/stage_controller.js

@@ -194,7 +194,7 @@ module.exports = app => {
                 let curStageData, preStageData;
                 responseData.data = await ctx.service.pos.getPosData(condition);
                 // 根据当前人,或指定对象查询数据
-                console.time('cur');
+                //console.time('cur');
                 const curWhere = JSON.parse(ctx.request.body.data);
                 if (ctx.stage.readOnly) {
                     curStageData = await ctx.service.stagePos.getAuditorStageData(ctx.tender.id,
@@ -202,22 +202,22 @@ module.exports = app => {
                 } else {
                     curStageData = await ctx.service.stagePos.getLastestStageData(ctx.tender.id, ctx.stage.id, curWhere);
                 }
-                console.timeEnd('cur');
+                //console.timeEnd('cur');
                 // 查询截止上期数据
-                console.time('pre');
+                //console.time('pre');
                 if (ctx.stage.order > 1) {
                     preStageData = await ctx.service.stagePosFinal.getFinalData(ctx.tender.data, ctx.stage);
                     //responseData.data.preStageData = await ctx.service.stagePos.getEndStageData(ctx.tender.id, ctx.stage.order - 1);
                 } else {
                     preStageData = [];
                 }
-                console.timeEnd('pre');
-                console.time('assign');
+                //console.timeEnd('pre');
+                //console.time('assign');
                 this.ctx.helper.assignRelaData(responseData.data, [
                     {data: curStageData, fields: ['contract_qty', 'qc_qty', 'postil'], prefix: '', relaId: 'pid'},
                     {data: preStageData, fields: ['contract_qty', 'qc_qty'], prefix: 'pre_', relaId: 'pid'}
                 ]);
-                console.timeEnd('assign');
+                //console.timeEnd('assign');
                 ctx.body = responseData;
             } catch (err) {
                 this.log(err);
@@ -739,11 +739,34 @@ module.exports = app => {
         async _getChangeDetailData(tid, sid, cid) {
             const data = {};
             data.attachments = await this.ctx.service.changeAtt.getChangeAttachment(cid);
+            data.bills = await this.ctx.service.changeAuditList.getAllDataByCondition({where: {cid: cid}});
             data.addUsedBills = await this.ctx.service.stageChange.getUsedData(tid, cid);
             data.curUsedBills = await this.ctx.service.stageChange.getStageUsedData(sid, cid);
             return data;
         }
         /**
+         * 获取变更数据 Post(Ajax)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async getChangeData(ctx) {
+            try {
+                const data = {};
+                data.tenderInfo = ctx.tender.info;
+                data.ledger = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
+                data.usedChangesId = await ctx.service.stageChange.getStageUsedChangeId(ctx.stage.id);
+                data.changes = await ctx.service.change.getChangeAndUsedInfo(ctx.tender.id);
+                if (data.changes.length > 0) {
+                    const change = data.changes[0];
+                    change.detail = await this._getChangeDetailData(ctx.tender.id, ctx.stage.id, change.cid);
+                }
+                ctx.body = {err: 0, msg: '', data: data};
+            } catch (err) {
+                this.log(err);
+                ctx.body = {err: 1, msg: err.toString(), data: null};
+            }
+        }
+        /**
          * 变更令 (Get)
          * @param ctx
          * @returns {Promise<void>}
@@ -752,16 +775,9 @@ module.exports = app => {
             try {
                 await this._getStageAuditViewData(ctx);
                 const renderData = await this._getDefaultRenderData(ctx);
-                renderData.ledger = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
-                renderData.changes = await ctx.service.change.getChangeAndUsedInfo(ctx.tender.id);
-                if (renderData.changes.length > 0) {
-                    const change = renderData.changes[0];
-                    change.detail = await this._getChangeDetailData(ctx.tender.id, ctx.stage.id, change.cid);
-                }
-                renderData.usedChangesId = await ctx.service.stageChange.getStageUsedChangeId(ctx.stage.id);
                 renderData.changeConst = changeConst;
                 renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.stage.change);
-                await this.layout('stage/change.ejs', renderData);
+                await this.layout('stage/change.ejs', renderData, 'stage/audit_modal.ejs');
             } catch (err) {
                 this.log(err);
                 ctx.redirect('/tender/' + ctx.tender.id + '/measure/stage');
@@ -788,6 +804,8 @@ module.exports = app => {
                 if (ctx.stage.status === auditConst.status.checking || ctx.stage.status === auditConst.status.checked) {
                     throw '当前不允许添加审核人';
                 }
+
+                ctx.stage.auditorList = await ctx.service.stageAudit.getAuditors(ctx.stage.id, ctx.stage.times);
                 // 检查审核人是否已存在
                 const exist = this.app._.find(ctx.stage.auditorList, {aid: id});
                 if (exist) {
@@ -901,6 +919,28 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 重新审批
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async checkAuditAgain(ctx) {
+            try {
+                if (ctx.stage.user_id === ctx.session.sessionUser.accountId && ctx.stage.status === auditConst.status.checked && ctx.stage.order === ctx.stage.highOrder) {
+                    await ctx.service.stageAudit.checkAgain(ctx.stage.id, ctx.stage.times);
+                    console.log('success');
+                    ctx.redirect(ctx.request.header.referer);
+                } else {
+                    throw '您无权进行该操作';
+                }
+            } catch (err) {
+                console.log(err);
+                this.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
         // 清单汇总相关
         _getGatherSpreadSetting() {
             const _ = this.app._;
@@ -988,11 +1028,11 @@ module.exports = app => {
                 await this._getStageAuditViewData(ctx);
                 const renderData = await this._getDefaultRenderData(ctx);
                 [renderData.ledgerSpread, renderData.posSpread] = getCompareSpreadSetting();
-                renderData.ledger = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
-                renderData.orgStageLedger = await ctx.service.stageBills.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.times, 0);
-                renderData.pos = await ctx.service.pos.getPosData({tid: ctx.tender.id});
-                renderData.orgStagePos = await ctx.service.stagePos.getAuditorStageData(ctx.tender.id,
-                    ctx.stage.id, ctx.stage.times, ctx.stage.curAuditor ? ctx.stage.curAuditor.order : 0);
+                // renderData.ledger = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
+                // renderData.orgStageLedger = await ctx.service.stageBills.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.times, 0);
+                // renderData.pos = await ctx.service.pos.getPosData({tid: ctx.tender.id});
+                // renderData.orgStagePos = await ctx.service.stagePos.getAuditorStageData(ctx.tender.id,
+                //     ctx.stage.id, ctx.stage.times, ctx.stage.curAuditor ? ctx.stage.curAuditor.order : 0);
                 renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.stage.compare);
                 await this.layout('stage/compare.ejs', renderData, 'stage/compare_modal.ejs');
             } catch (err) {
@@ -1004,12 +1044,20 @@ module.exports = app => {
         async compareAuditor(ctx) {
             try {
                 const data = JSON.parse(ctx.request.body.data);
-                const result = [];
-                for (const order of data.auditors) {
+                const result = {
+                    main: null,
+                    roles: []
+                };
+                if (data.main) {
+                    result.main = {};
+                    result.main.ledger = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
+                    result.main.pos = await ctx.service.pos.getPosData({tid: ctx.tender.id});
+                }
+                for (const order of data.roles) {
                     const data = { order: order, bills: [], pos: [] };
                     data.bills = await ctx.service.stageBills.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.times, order);
                     data.pos = await ctx.service.stagePos.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.times, order);
-                    result.push(data);
+                    result.roles.push(data);
                 }
                 ctx.body = {err: 0, msg: '', data: result};
             } catch (err) {

+ 4 - 1
app/extend/helper.js

@@ -232,16 +232,19 @@ module.exports = {
     async sendRequest(url, data, type = 'POST', dataType = 'json') {
         // 发起请求
         try {
+            console.log(url);
+            console.log(data);
             const response = await this.ctx.curl(url, {
                 method: type,
                 data,
                 dataType,
             });
+            console.log(response);
             if (response.status !== 200) {
                 throw '请求失败';
             }
             return response.data;
-        } catch(err) {
+        } catch (err) {
             throw '请求失败';
         }
     },

+ 5 - 1
app/lib/pay_calc.js

@@ -173,7 +173,11 @@ class PayCalculate {
                             p.tp = value;
                         }
                     } else if (p.tp && !this.ctx.helper.checkZero(p.tp)) {
-                        p.tp = this.ctx.helper.round(p.tp, this.decimal);
+                        if (p.rprice) {
+                            p.tp = Math.min(this.ctx.helper.sub(p.rprice, p.pre_total_price), this.ctx.helper.round(p.tp, this.decimal));
+                        } else {
+                            p.tp = this.ctx.helper.round(p.tp, this.decimal);
+                        }
                     }
                 } else {
                     p.tp = 0;

+ 35 - 22
app/lib/sms.js

@@ -8,7 +8,7 @@
  * @version
  */
 
-const xmlReader = require('xmlreader');
+// const xmlReader = require('xmlreader');
 class SMS {
 
     /**
@@ -19,7 +19,10 @@ class SMS {
      */
     constructor(ctx) {
         this.ctx = ctx;
-        this.url = 'http://101.132.42.40:7862/sms';
+        // this.url = 'https://sms.yunpian.com/v2/sms/batch_send.json';
+        this.url = 'http://sms.haotingyun.com/v2/sms/single_send.json';
+        this.url2 = 'http://sms.haotingyun.com/v2/sms/tpl_single_send.json';
+        // this.url = 'http://101.132.42.40:7862/sms';
     }
 
     /**
@@ -27,30 +30,40 @@ class SMS {
      *
      * @param {String|Array} mobile - 发送的电话号码
      * @param {String} content - 发送的内容
+     * @param {String} tplId - 补充的tpl_id(防止模板无法找到发送失败)
      * @return {Boolean} - 发送结果
      */
-    async send(mobile, content) {
+    async send(mobile, content, tplId = '') {
         if (mobile instanceof Array) {
             mobile = mobile.join(',');
         }
         let result = false;
         const config = this.ctx.app.config.sms;
         const postData = {
-            action: 'send',
-            account: config.account,
-            password: config.password,
+            // action: 'send',
+            // account: config.account,
+            // password: config.password,
+            apikey: config.authKey,
             mobile,
-            content,
-            extno: config.extno,
+            text: content,
+            // extno: config.extno,
         };
+        if (tplId !== '') {
+            postData.tpl_id = tplId;
+        }
+        const url = tplId !== '' ? this.url2 : this.url;
         try {
-            const response = await this.ctx.helper.sendRequest(this.url, postData, 'POST', 'text');
-            const xmlData = await this.xmlParse(response);
-            if (xmlData === undefined || xmlData.returnstatus.text() !== 'Success') {
+            const response = await this.ctx.helper.sendRequest(url, postData, 'POST');
+            // const xmlData = await this.xmlParse(response);
+            // if (xmlData === undefined || xmlData.returnstatus.text() !== 'Success') {
+            //     throw '短信发送失败!';
+            // }
+            if (response === undefined && response.code !== 0) {
                 throw '短信发送失败!';
             }
             result = true;
         } catch (error) {
+            console.log(error);
             result = false;
         }
 
@@ -63,17 +76,17 @@ class SMS {
      * @param {String} xml - xml数据
      * @return {Object} - 解析结果
      */
-    xmlParse(xml) {
-        return new Promise(function(resolve, reject) {
-            xmlReader.read(xml, function(errors, xmlData) {
-                if (errors) {
-                    reject('');
-                } else {
-                    resolve(xmlData.returnsms);
-                }
-            });
-        });
-    }
+    // xmlParse(xml) {
+    //     return new Promise(function(resolve, reject) {
+    //         xmlReader.read(xml, function(errors, xmlData) {
+    //             if (errors) {
+    //                 reject('');
+    //             } else {
+    //                 resolve(xmlData.returnsms);
+    //             }
+    //         });
+    //     });
+    // }
 }
 
 module.exports = SMS;

+ 6 - 1
app/middleware/stage_check.js

@@ -59,7 +59,7 @@ module.exports = options => {
                 } else if (stage.status === status.checked) {
                     stage.curOrder = _.max(_.map(stage.auditors, 'order'));
                 } else {
-                    stage.curOrder = stage.curAuditor.order - 1;
+                    stage.curOrder = stage.curAuditor.aid === accountId ? stage.curAuditor.order : stage.curAuditor.order - 1;
                 }
             } else if (auditorIds.indexOf(accountId) !== -1) { // 审批人
                 if (stage.status === status.uncheck) {
@@ -87,6 +87,11 @@ module.exports = options => {
             } else { // 其他不可见
                 throw '您无权查看该数据';
             }
+
+            // 获取最新的期
+            stage.highOrder = yield this.service.stage.count({
+                tid: this.tender.id,
+            });
             this.stage = stage;
             yield next;
         } catch (err) {

+ 3 - 2
app/public/js/change_calculation.js

@@ -122,8 +122,9 @@ Number.prototype.sub = function (arg){
 function roundnum(val,decimals){
     if(val !== ''){
         val = parseFloat(val);
+        let _sign = (val < 0) ? -1 : 1;
         if(decimals < 1){
-            val = (Math.round(val)).toString();
+            val = (Math.round(val*_sign)/_sign).toString();
         }else{
             let num = val.toString();
             if(num.lastIndexOf('.') == -1){
@@ -136,7 +137,7 @@ function roundnum(val,decimals){
                     // num += makezero(parseInt(decimals)-parseInt(valdecimals));
                     val = num;
                 }else if(parseInt(valdecimals) > parseInt(decimals)){
-                    val = parseFloat(val) != 0 ? Math.round(val.mul(makemultiple(decimals))).div(makemultiple(decimals)) : makedecimalzero(decimals);
+                    val = parseFloat(val) != 0 ? Math.round(val.mul(makemultiple(decimals)*_sign)).div(makemultiple(decimals)*_sign) : makedecimalzero(decimals);
                     let num = val.toString();
                     if(num.lastIndexOf('.') == -1){
                         // num += '.';

+ 64 - 3
app/public/js/global.js

@@ -121,7 +121,8 @@ function toast(message, type, icon) {
  * @param {function} successCallback - 返回成功回调
  * @param {function} errorCallBack - 返回失败回调
  */
-const postData = function (url, data, successCallback, errorCallBack) {
+const postData = function (url, data, successCallback, errorCallBack, showWaiting = false) {
+    if (showWaiting) showWaitingView();
     $.ajax({
         type:"POST",
         url: url,
@@ -144,12 +145,14 @@ const postData = function (url, data, successCallback, errorCallBack) {
                     errorCallBack(result.msg);
                 }
             }
+            if (showWaiting) closeWaitingView();
         },
         error: function(jqXHR, textStatus, errorThrown){
             toast('error ' + textStatus + " " + errorThrown, 'error', 'exclamation-circle');
             if (errorCallBack) {
                 errorCallBack();
             }
+            if (showWaiting) closeWaitingView();
         }
     });
 };
@@ -161,7 +164,8 @@ const postData = function (url, data, successCallback, errorCallBack) {
  * @param {function} successCallback - 返回成功回调
  * @param {function} errorCallBack - 返回失败回调
  */
-const postDataCompress = function (url, data, successCallback, errorCallBack) {
+const postDataCompress = function (url, data, successCallback, errorCallBack, showWaiting = false) {
+    if (showWaiting) showWaitingView();
     $.ajax({
         type:"POST",
         url: url,
@@ -184,12 +188,14 @@ const postDataCompress = function (url, data, successCallback, errorCallBack) {
                     errorCallBack(result.msg);
                 }
             }
+            if (showWaiting) closeWaitingView();
         },
         error: function(jqXHR, textStatus, errorThrown){
             toast('error ' + textStatus + " " + errorThrown, 'error', 'exclamation-circle');
             if (errorCallBack) {
                 errorCallBack();
             }
+            if (showWaiting) closeWaitingView();
         }
     });
 };
@@ -201,7 +207,8 @@ const postDataCompress = function (url, data, successCallback, errorCallBack) {
  * @param {function} successCallback - 返回成功回调
  * @param {function} errorCallBack - 返回失败回调
  */
-const postDataWithFile = function (url, formData, successCallback, errorCallBack) {
+const postDataWithFile = function (url, formData, successCallback, errorCallBack, showWaiting = false) {
+    if (showWaiting) showWaitingView();
     $.ajax({
         type:"POST",
         url: url,
@@ -228,12 +235,14 @@ const postDataWithFile = function (url, formData, successCallback, errorCallBack
                     errorCallBack();
                 }
             }
+            if (showWaiting) closeWaitingView();
         },
         error: function(jqXHR, textStatus, errorThrown){
             toast('error ' + textStatus + " " + errorThrown, 'error', 'exclamation-circle');
             if (errorCallBack) {
                 errorCallBack();
             }
+            if (showWaiting) closeWaitingView();
         }
     });
 };
@@ -260,6 +269,58 @@ function checkZero(value) {
 function checkFieldChange(o, n) {
     return o == n || ((!o || o === '') && (n === ''));
 }
+//关闭等待窗口
+function closeWaitingView() {
+    var bgDiv = document.getElementById("bgDiv");
+    var msgDiv = document.getElementById("msgDiv");
+    //移除背景遮罩层div
+    if(bgDiv != null){
+        document.body.removeChild(bgDiv);
+    }
+    //移除中间信息提示层div
+    if(msgDiv != null){
+        document.body.removeChild(msgDiv);
+    }
+}
+//显示等待窗口
+function showWaitingView() {
+    var msgw = 300; //提示窗口的宽度
+    var msgh = 100; //提示窗口的高度
+
+    var sWidth, sHeight;
+    sWidth = document.body.clientWidth;
+    sHeight = document.body.clientHeight;
+
+    //背景遮罩层div
+    var bgObj = document.createElement("div");
+    bgObj.setAttribute('id', 'bgDiv');
+    bgObj.style.zIndex = '9998';
+    bgObj.style.position = "absolute";
+    bgObj.style.top = "0px";
+    bgObj.style.background = "#888";
+    bgObj.style.filter = "progid:DXImageTransform.Microsoft.Alpha(style=3,opacity=25,finishOpacity=75";
+    bgObj.style.opacity = "0.6";
+    bgObj.style.left = "0px";
+    bgObj.style.width = sWidth + "px";
+    bgObj.style.height = sHeight + "px";
+    document.body.appendChild(bgObj);
+
+    //信息提示层div
+    var msgObj = document.createElement("div");
+    msgObj.style.zIndex = '9999';
+    msgObj.setAttribute("id", "msgDiv");
+    msgObj.setAttribute("align", "center");
+    msgObj.style.position = "absolute";
+    msgObj.style.font = "12px/1.6em Verdana, Geneva, Arial, Helvetica, sans-serif";
+    msgObj.style.width = msgw + "px";
+    msgObj.style.height = msgh + "px";
+    msgObj.style.top = (document.documentElement.scrollTop + (sHeight - msgh) / 2) + "px";
+    msgObj.style.left = (sWidth - msgw) / 2 + "px";
+    document.body.appendChild(msgObj);
+
+    //中间等待图标
+    document.getElementById("msgDiv").innerHTML = '<i class="fa fa-spinner fa-pulse fa-3x fa-fw"></i>';
+}
 
 /**
  * 设置本地缓存

+ 86 - 120
app/public/js/ledger.js

@@ -5,6 +5,7 @@
  * @date 2018/02/05
  * @version
  */
+
 const ckBillsSpread = window.location.pathname + '-billsSelect';
 function checkTzMeasureType () {
     return tender.measure_type === measureType.tz.value;
@@ -52,6 +53,24 @@ $(document).ready(function() {
     });
     const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
 
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        //key: 'ledger.memu.1.0.0',
+        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();
+            ledgerSpread.refresh();
+            posSpread.refresh();
+        }
+    });
+
     // 定义事件
     const treeOperationObj = {
         getSelectNode: function (sheet) {
@@ -488,7 +507,7 @@ $(document).ready(function() {
                 self.refreshTree(sheet, result);
                 self.refreshOperationValid(sheet);
                 removeLocalCache(copyBlockTag);
-            });
+            }, null, true);
         },
         selectionChanged: function (e, info) {
             if (!info.oldSelections[0] || info.newSelections[0].row !== info.oldSelections[0].row) {
@@ -500,7 +519,45 @@ $(document).ready(function() {
         },
         topRowChanged(e, info) {
             SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
-        }
+        },
+        cut: function (sheet, sel, callback) {
+            if (!sheet || !sel) return;
+
+            const sortData = SpreadJsObj.getSortData(sheet), datas = [];
+            for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
+                const node = sortData[iRow];
+                if (node) {
+                    const data = sheet.zh_tree.getNodeKeyData(node);
+                    for (let iCol = sel.col; iCol < sel.col + sel.colCount; iCol++) {
+                        const col = sheet.zh_setting.cols[iCol];
+                        if (col.field === 'b_code' || col.field === 'sgfh_qty' || col.field === 'sgfh_tp' ||
+                            col.field === 'sjcl_qty' || col.field === 'sjcl_tp' ||
+                            col.field === 'qtcl_qty' || col.field === 'qtcl_tp') {
+                            const lPos = pos.getLedgerPos(node.id);
+                            if (lPos && lPos.length > 0) {
+                                toast('不可剪切', 'warning');
+                                return;
+                            }
+                        }
+                        const style = sheet.getStyle(iRow, iCol);
+                        if (style.locked) {
+                            toast('不可剪切', 'warning');
+                            return;
+                        }
+                        const colSetting = sheet.zh_setting.cols[iCol];
+                        data[colSetting.field] = null;
+                    }
+                    datas.push(data);
+                }
+            }
+
+            if (datas.length > 0) {
+                sheet.zh_tree.update('/tender/' + getTenderId() + '/ledger/update', datas, function (result) {
+                    callback();
+                    treeOperationObj.refreshTree(sheet, result);
+                });
+            }
+        },
     };
     // 绑定事件
     ledgerSpread.bind(GC.Spread.Sheets.Events.SelectionChanged, treeOperationObj.selectionChanged);
@@ -513,7 +570,6 @@ $(document).ready(function() {
         ledgerSpread.bind(GC.Spread.Sheets.Events.ClipboardPasted, treeOperationObj.clipboardPasted);
         SpreadJsObj.addDeleteBind(ledgerSpread, treeOperationObj.deletePress);
         ledgerSpread.bind(GC.Spread.Sheets.Events.ClipboardChanging, function (e, info) {
-            console.log(info);
             const copyText = SpreadJsObj.getFilterCopyText(info.sheet);
         });
         ledgerSpread.bind(GC.Spread.Sheets.Events.ClipboardChanged, function (e, info) {
@@ -521,6 +577,7 @@ $(document).ready(function() {
         });
         ledgerSpread.bind(GC.Spread.Sheets.Events.ClipboardPasting, function (e, info) {
         });
+        SpreadJsObj.addCutEvents(ledgerSpread, treeOperationObj.cut);
 
         // 绑定 删除等 顶部按钮
         $('#insert').click(() => {
@@ -679,58 +736,7 @@ $(document).ready(function() {
 
     treeOperationObj.refreshOperationValid(ledgerSpread.getActiveSheet());
 
-    const posSearch = (function () {
-        let resultArr = [];
-        const search = function (keyword) {
-            if (keyword && keyword !== '') {
-                resultArr = [];
-                const sortData = posSpread.getActiveSheet().zh_data;
-                if (sortData) {
-                    for (let i = 0, iLength = sortData.length; i < iLength; i++) {
-                        const sd = sortData[i];
-                        if (sd.name && sd.name.indexOf(keyword) > -1) {
-                            resultArr.push({index: i, data: sd});
-                        }
-                    }
-                }
-                $('#pos-search-hint').html(' ' + resultArr.length + '个匹配').show();
-            } else {
-                resultArr = [];
-                $('#pos-search-hint').hide();
-            }
-        };
-        const locateNext = function () {
-            if (resultArr.length > 0) {
-                const sheet = posSpread.getActiveSheet();
-                const sel = sheet.getSelections()[0];
-                const curRow = sel ? sel.row : 0;
-                let next = _.find(resultArr, function (d) {
-                    return d.index > curRow;
-                });
-                if (!next) next = resultArr[0];
-                if (next.index !== curRow) {
-                    sheet.setSelection(next.index, sel ? sel.col : 0, 1, 1);
-                    sheet.showRow(next.index, spreadNS.VerticalPosition.center);
-                }
-            }
-        };
-        const locatePre = function () {
-            if (resultArr.length > 0) {
-                const sheet = posSpread.getActiveSheet();
-                const sel = sheet.getSelections()[0];
-                const curRow = sel ? sel.row : 0;
-                let next = _.findLast(resultArr, function (d) {
-                    return d.index < curRow;
-                });
-                if (!next) next = resultArr[resultArr.length - 1];
-                if (next.index !== curRow) {
-                    sheet.setSelection(next.index, sel ? sel.col : 0, 1, 1);
-                    sheet.showRow(next.index, spreadNS.VerticalPosition.center);
-                }
-            }
-        };
-        return {search, locateNext, locatePre};
-    })();
+    const posSearch = $.posSearch({selector: '#pos-search', searchSpread: posSpread});
     // 台账模式加载部位明细数据
     if (checkTzMeasureType()) {
         $.divResizer({
@@ -748,15 +754,6 @@ $(document).ready(function() {
             posOperationObj.loadCurPosData();
             SpreadJsObj.resetTopAndSelect(posSpread.getActiveSheet());
         });
-        $('#pos-keyword').bind('input propertychange', function () {
-            posSearch.search(this.value);
-        });
-        $('#search-pre-pos').click(function () {
-            posSearch.locatePre();
-        });
-        $('#search-next-pos').click(function () {
-            posSearch.locateNext();
-        });
     }
     // 绑定部位明细编辑事件
     const posOperationObj = {
@@ -1146,19 +1143,26 @@ $(document).ready(function() {
                 dealBills.spread.refresh();
             } else if (tab.attr('content') === '#search' && !searchLedger) {
                 if (!searchLedger) {
-                    searchLedger = new SearchLedger($('#search'), {
-                        cols: [
-                            {title: '项目节编号', field: 'code', hAlign: 0, width: 120, formatter: '@', readOnly: true},
-                            {title: '清单编号', field: 'b_code', hAlign: 0, width: 120, formatter: '@', readOnly: true},
-                            {title: '名称', field: 'name', width: 230, hAlign: 0, formatter: '@', readOnly: true},
-                            {title: '单位', field: 'unit', width: 50, hAlign: 1, formatter: '@', readOnly: true},
-                            {title: '单价', field: 'unit_price', hAlign: 2, width: 50, readOnly: true},
-                            {title: '数量', field: 'quantity', hAlign: 2, width: 50, readOnly: true},
-                        ],
-                        emptyRows: 0,
-                        headRows: 1,
-                        headRowHeight: [40],
-                        defaultRowHeight: 21,
+                    searchLedger = $.billsSearch({
+                        selector: '#search',
+                        searchSpread: ledgerSpread,
+                        resultSpreadSetting: {
+                            cols: [
+                                {title: '项目节编号', field: 'code', hAlign: 0, width: 120, formatter: '@', readOnly: true},
+                                {title: '清单编号', field: 'b_code', hAlign: 0, width: 120, formatter: '@', readOnly: true},
+                                {title: '名称', field: 'name', width: 230, hAlign: 0, formatter: '@', readOnly: true},
+                                {title: '单位', field: 'unit', width: 50, hAlign: 1, formatter: '@', readOnly: true},
+                                {title: '单价', field: 'unit_price', hAlign: 2, width: 50, readOnly: true},
+                                {title: '数量', field: 'quantity', hAlign: 2, width: 50, readOnly: true},
+                            ],
+                            emptyRows: 0,
+                            headRows: 1,
+                            headRowHeight: [40],
+                            defaultRowHeight: 21,
+                        },
+                        afterLocated: function () {
+                            posOperationObj.loadCurPosData();
+                        }
                     });
                 }
                 searchLedger.spread.refresh();
@@ -1436,7 +1440,7 @@ $(document).ready(function() {
                         treeOperationObj.refreshTree(sheet, result);
                         treeOperationObj.refreshOperationValid(sheet, selection);
                         self.obj.modal('hide');
-                    });
+                    }, null, true);
                 }
             });
         }
@@ -1498,47 +1502,6 @@ $(document).ready(function() {
             return result;
         }
      }
-    class SearchLedger {
-        constructor(obj, spreadSetting) {
-            const self = this;
-            this.obj = obj;
-            this.spreadSetting = spreadSetting;
-            this.spread = SpreadJsObj.createNewSpread($('#search-result', this.obj)[0]);
-            SpreadJsObj.initSheet(this.spread.getActiveSheet(), this.spreadSetting);
-            SpreadJsObj.forbiddenSpreadContextMenu('#search-result', this.spread);
-            $('input', this.obj).bind('keydown', function (e) {
-                if (e.keyCode == 13) {
-                    self.search();
-                }
-            });
-            $('#searchLedger', this.obj).bind('click', () => {self.search()});
-            this.spread.getActiveSheet().bind(GC.Spread.Sheets.Events.CellDoubleClick, function (e, info) {
-                const sheet = info.sheet;
-                const data = sheet.zh_data;
-                if (!data) { return }
-
-                const curBills = data[info.row];
-                if (!curBills) { return }
-
-                SpreadJsObj.locateTreeNode(ledgerSpread.getActiveSheet(), curBills.ledger_id, true);
-            });
-        }
-        search () {
-            const keyword = $('input', this.obj).val();
-            this.searchResult = [];
-            const sortData = SpreadJsObj.getSortData(ledgerSpread.getActiveSheet());
-            for (const node of sortData) {
-                if ((node.code && node.code.indexOf(keyword) > -1) ||
-                    node.b_code && node.b_code.indexOf(keyword) > -1 ||
-                    node.name && node.name.indexOf(keyword) > -1) {
-                    const data = JSON.parse(JSON.stringify(node));
-                    data.visible = true;
-                    this.searchResult.push(data);
-                }
-            }
-            SpreadJsObj.loadSheetData(this.spread.getActiveSheet(), 'data', this.searchResult);
-        }
-    }
 
     $('#searchAccount').click(() => {
         const data = {
@@ -1665,6 +1628,7 @@ $(document).ready(function() {
             return;
         }$('#upload-ledger-sheets').hide();
         try {
+            $('#select-excel-loading').show();
             xlsxUtils.import(this.files[0], (excelData) => {
                 if (excelData.SheetNames.length > 0) {
                     const html = [];
@@ -1690,8 +1654,10 @@ $(document).ready(function() {
                     toast('选择的Excel无有效数据,请重新选择', 'hint');
                     $('#upload-ledger-sheets').hide();
                 }
+                $('#select-excel-loading').hide();
             });
         } catch(err) {
+            $('#select-excel-loading').hide();
             toast('加载excel异常,请刷新当前页面', 'error');
             $('#upload-ledger-sheets').hide();
         }
@@ -1711,7 +1677,7 @@ $(document).ready(function() {
                 pos.loadDatas(result.pos);
                 posOperationObj.loadCurPosData();
                 $('#upload-ledger').modal('hide');
-            });
+            }, null, true);
         }
     });
     $('#upload-ledger').bind('hidden.bs.modal', function () {

+ 47 - 2
app/public/js/ledger_audit.js

@@ -47,6 +47,25 @@ $(document).ready(() => {
     });
     const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
 
+    const posSearch = $.posSearch({selector: '#pos-search', searchSpread: posSpread});
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        //key: 'ledger.audit.memu.1.0.0',
+        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();
+            ledgerSpread.refresh();
+            posSpread.refresh();
+        }
+    });
+
     const loadCurPosData = function () {
         const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
         if (node) {
@@ -75,15 +94,16 @@ $(document).ready(() => {
         });
     }
 
-    ledgerSpread.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, info) {
+    ledgerSpread.bind(spreadNS.Events.SelectionChanged, function (e, info) {
         loadCurPosData();
         SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
+        posSearch.search($('#pos-keyword').val());
     });
     ledgerSpread.bind(spreadNS.Events.TopRowChanged, function (e, info) {
         SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
     });
 
-    let dealBills;
+    let dealBills, searchLedger;
     $.divResizer({
         select: '#right-spr',
         callback: function () {
@@ -143,6 +163,31 @@ $(document).ready(() => {
                     defaultRowHeight: 21,
                 });
                 dealBills.loadData();
+            } else if (tab.attr('content') === '#search' && !searchLedger) {
+                if (!searchLedger) {
+                    searchLedger = $.billsSearch({
+                        selector: '#search',
+                        searchSpread: ledgerSpread,
+                        resultSpreadSetting: {
+                            cols: [
+                                {title: '项目节编号', field: 'code', hAlign: 0, width: 120, formatter: '@', readOnly: true},
+                                {title: '清单编号', field: 'b_code', hAlign: 0, width: 120, formatter: '@', readOnly: true},
+                                {title: '名称', field: 'name', width: 230, hAlign: 0, formatter: '@', readOnly: true},
+                                {title: '单位', field: 'unit', width: 50, hAlign: 1, formatter: '@', readOnly: true},
+                                {title: '单价', field: 'unit_price', hAlign: 2, width: 50, readOnly: true},
+                                {title: '数量', field: 'quantity', hAlign: 2, width: 50, readOnly: true},
+                            ],
+                            emptyRows: 0,
+                            headRows: 1,
+                            headRowHeight: [40],
+                            defaultRowHeight: 21,
+                        },
+                        afterLocated: function () {
+                            loadCurPosData();
+                        }
+                    });
+                }
+                searchLedger.spread.refresh();
             }
         } else {
             tab.removeClass('active');

+ 163 - 0
app/public/js/ledger_search.js

@@ -0,0 +1,163 @@
+'use strict';
+
+/**
+ * 台账搜索相关(多个页面均使用:台账分解、台账审批、台账修订)
+ *
+ * 搜索基于spreadjs,请放在gc.spread.sheets.all.10.0.1.min.js/spreadjs_zh.js之后
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+(function($){
+    $.posSearch = function (setting) {
+        if (!setting.selector || !setting.searchSpread) return;
+        const searchHtml =
+            '                                <div class="mt-1 ml-2">\n' +
+            '                                    <div class="input-group input-group-sm">\n' +
+            '                                        <input type="text" class="form-control" placeholder="输入名称查找" id="pos-keyword">\n' +
+            '                                        <div class="input-group-append">\n' +
+            '                                            <span class="input-group-text" id="pos-search-hint">结果:0</span>\n' +
+            '                                        </div>\n' +
+            '                                        <div class="input-group-append" >\n' +
+            '                                            <button class="btn btn-outline-secondary" type="button" title="上一个" id="search-pre-pos"><i class="fa fa-angle-double-left"></i></button>\n' +
+            '                                            <button class="btn btn-outline-secondary" type="button" title="下一个" id="search-next-pos"><i class="fa fa-angle-double-right"></i></button>\n' +
+            '                                        </div>\n' +
+            '                                    </div>\n' +
+            '                                </div>\n';
+        $(setting.selector).html(searchHtml);
+        const sheet = setting.searchSpread.getActiveSheet();
+        const searchObj = (function () {
+            let resultArr = [];
+            const search = function (keyword) {
+                if (keyword && keyword !== '') {
+                    resultArr = [];
+                    const sortData = sheet.zh_data;
+                    if (sortData) {
+                        for (let i = 0, iLength = sortData.length; i < iLength; i++) {
+                            const sd = sortData[i];
+                            if (sd.name && sd.name.indexOf(keyword) > -1) {
+                                resultArr.push({index: i, data: sd});
+                            }
+                        }
+                    }
+                } else {
+                    resultArr = [];
+                }
+                $('#pos-search-hint').html('结果:' + resultArr.length);
+            };
+            const searchAndLocate = function (keyword) {
+                search(keyword);
+                if (resultArr.length > 0) {
+                    const sel = sheet.getSelections()[0];
+                    const curRow = sel ? sel.row : 0;
+                    const pos = resultArr[0];
+                    if (pos.index !== curRow) {
+                        sheet.setSelection(pos.index, sel ? sel.col : 0, 1, 1);
+                        sheet.showRow(pos.index, spreadNS.VerticalPosition.center);
+                    }
+                }
+            };
+            const locateNext = function () {
+                if (resultArr.length > 0) {
+                    const sel = sheet.getSelections()[0];
+                    const curRow = sel ? sel.row : 0;
+                    let next = _.find(resultArr, function (d) {
+                        return d.index > curRow;
+                    });
+                    if (!next) next = resultArr[0];
+                    if (next.index !== curRow) {
+                        sheet.setSelection(next.index, sel ? sel.col : 0, 1, 1);
+                        sheet.showRow(next.index, spreadNS.VerticalPosition.center);
+                    }
+                }
+            };
+            const locatePre = function () {
+                if (resultArr.length > 0) {
+                    const sel = sheet.getSelections()[0];
+                    const curRow = sel ? sel.row : 0;
+                    let next = _.findLast(resultArr, function (d) {
+                        return d.index < curRow;
+                    });
+                    if (!next) next = resultArr[resultArr.length - 1];
+                    if (next.index !== curRow) {
+                        sheet.setSelection(next.index, sel ? sel.col : 0, 1, 1);
+                        sheet.showRow(next.index, spreadNS.VerticalPosition.center);
+                    }
+                }
+            };
+            return {search, searchAndLocate, locateNext, locatePre};
+        })();
+        // $('#pos-keyword').bind('input propertychange', function () {
+        //     posSearch.search(this.value);
+        // });
+        $('#pos-keyword').bind('keydown', function(e){
+            if (e.keyCode == 13) searchObj.searchAndLocate(this.value);
+        });
+        $('#search-pre-pos').click(function () {
+            searchObj.locatePre();
+        });
+        $('#search-next-pos').click(function () {
+            searchObj.locateNext();
+        });
+        return searchObj;
+    }
+    $.billsSearch = function (setting) {
+        if (!setting.selector || !setting.searchSpread || !setting.resultSpreadSetting) return;
+        const resultId = setting.id + '-search-result';
+        const obj = $(setting.selector);
+        obj.html(
+            '                        <div class="sjs-bar-1">\n' +
+            '                            <div class="input-group input-group-sm pb-1">\n' +
+            '                                <input id="searchKeyword" type="text" class="form-control" placeholder="可查找 项目节编号/清单编号/名称" aria-label="Recipient\'s username" aria-describedby="button-addon2">\n' +
+            '                                <div class="input-group-append">\n' +
+            '                                    <button class="btn btn-outline-secondary" type="button"">搜索</button>\n' +
+            '                                </div>\n' +
+            '                            </div>\n' +
+            '                        </div>\n' +
+            '                        <div id="' + resultId + '" class="sjs-sh-1">\n' +
+            '                        </div>'
+        );
+        autoFlashHeight();
+        const resultSpread = SpreadJsObj.createNewSpread($('#' + resultId)[0]);
+        SpreadJsObj.initSheet(resultSpread.getActiveSheet(), setting.resultSpreadSetting);
+        SpreadJsObj.forbiddenSpreadContextMenu('#' + resultId, resultSpread);
+        const searchSheet = setting.searchSpread.getActiveSheet();
+        let searchResult = [];
+        const searchBills = function () {
+            const keyword = $('input', obj).val();
+            searchResult = [];
+            const sortData = SpreadJsObj.getSortData(searchSheet);
+            for (const node of sortData) {
+                if ((node.code && node.code.indexOf(keyword) > -1) ||
+                    node.b_code && node.b_code.indexOf(keyword) > -1 ||
+                    node.name && node.name.indexOf(keyword) > -1) {
+                    const data = JSON.parse(JSON.stringify(node));
+                    data.visible = true;
+                    searchResult.push(data);
+                }
+            }
+            SpreadJsObj.loadSheetData(resultSpread.getActiveSheet(), 'data', searchResult);
+        };
+
+        $('input', this.obj).bind('keydown', function (e) {
+            if (e.keyCode == 13) searchBills();
+        });
+        $('button', this.obj).bind('click', () => {searchBills()});
+        resultSpread.getActiveSheet().bind(spreadNS.Events.CellDoubleClick, function (e, info) {
+            const sheet = info.sheet;
+            const data = sheet.zh_data;
+            if (!data) { return }
+
+            const curBills = data[info.row];
+            if (!curBills) { return }
+
+            SpreadJsObj.locateTreeNode(searchSheet, curBills.ledger_id, true);
+            if (setting.afterLocated) {
+                setting.afterLocated();
+            }
+        });
+        return {spread: resultSpread};
+    }
+})(jQuery);

+ 241 - 0
app/public/js/measure_compare.js

@@ -0,0 +1,241 @@
+'use strict';
+
+/**
+ * 多期比较
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const billsSpreadSetting = {
+    baseCols: [
+        {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 150, formatter: '@', cellType: 'tree'},
+        {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 80, formatter: '@'},
+        {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+        {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
+        {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+        {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, formatter: '@'},
+        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, formatter: '@'},
+    ],
+    extraCols: [
+        {title: '%s|数量', colSpan: '2|1', rowSpan: '1|1', field: 'gather_qty%s', hAlign: 2, width: 60, formatter: '@'},
+        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gather_tp%s', hAlign: 2, width: 60, formatter: '@'},
+    ],
+    emptyRows: 3,
+    headRows: 2,
+    headRowHeight: [40, 40],
+    defaultRowHeight: 21,
+    readOnly: true,
+};
+const posSpreadSetting = {
+    baseCols: [
+        {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+        {title: '台账数量', colSpan: '1', rowSpan: '1', field: 'quantity', hAlign: 2, width: 60},
+    ],
+    extraCols: [
+        {title: '%s数量', colSpan: '1', rowSpan: '1', field: 'gather_qty%s', hAlign: 2, width: 60},
+    ],
+    emptyRows: 3,
+    headRows: 1,
+    headRowHeight: [40],
+    defaultRowHeight: 21,
+    readOnly: true,
+};
+function initSpreadSettingWithRoles(compareRoles) {
+    function setSpreadSettingCols(setting, fieldSufs, Roles) {
+        function addExtraCols(fieldSuf, Role) {
+            for (const ec of setting.extraCols) {
+                const col = JSON.parse(JSON.stringify(ec));
+                col.title = _.replace(col.title, '%s', Role);
+                col.field = _.replace(col.field, '%s', fieldSuf);
+                setting.cols.push(col);
+            }
+        }
+        setting.cols = [];
+        for (const col of setting.baseCols) {
+            setting.cols.push(col);
+        }
+        for (const index in fieldSufs) {
+            addExtraCols(fieldSufs[index], Roles[index]);
+        }
+    }
+    const fieldSufs = [], roles = [], trs = $('tr[stage-id]');
+    for (let r of compareRoles) {
+        if (r > 0) {
+            const tr = trs[r-1];
+            if (tr) {
+                fieldSufs.push(r + '');
+                roles.push(tr.children[0].textContent);
+            }
+        }
+    }
+    setSpreadSettingCols(billsSpreadSetting, fieldSufs, roles);
+    setSpreadSettingCols(posSpreadSetting, fieldSufs, roles);
+}
+function calculateStageLedgerData(datas) {
+    for (const d of datas) {
+        d.gather_qty = ZhCalc.add(d.contract_qty, d.qc_qty);
+        d.gather_tp = ZhCalc.add(d.contract_tp, d.qc_tp);
+    }
+}
+function calculateStagePosData(datas) {
+    for (const d of datas) {
+        d.gather_qty = ZhCalc.add(d.contract_qty, d.qc_qty);
+    }
+}
+
+$(document).ready(() => {
+    autoFlashHeight();
+    initSpreadSettingWithRoles([]);
+    const billsSpread = SpreadJsObj.createNewSpread($('#bills-spread')[0]);
+    const billsSheet = billsSpread.getActiveSheet();
+    SpreadJsObj.initSheet(billsSheet, billsSpreadSetting);
+    const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
+    const posSheet = posSpread.getActiveSheet();
+    SpreadJsObj.initSheet(posSheet, posSpreadSetting);
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        //key: 'measure.compare.memu.1.0.0',
+        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();
+            billsSpread.refresh();
+            posSpread.refresh();
+        }
+    });
+    // 上下窗口resizer
+    $.divResizer({
+        select: '#main-resize',
+        callback: function () {
+            billsSpread.refresh();
+            let bcontent = $(".bcontent-wrap").length > 0 ? $(".bcontent-wrap").height() : 0;
+            $(".sp-wrap").height(bcontent-40);
+            posSpread.refresh();
+        }
+    });
+
+    const cTree = createNewPathTree('master', {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        keys: ['id', 'tender_id', 'ledger_id'],
+        masterId: 'id',
+        minorId: 'lid',
+        calcFields: [],
+    });
+    const cPos = new MasterPosData({
+        id: 'id', ledgerId: 'lid', masterId: 'id', minorId: 'pid',
+        calcFun: function (pos) {
+            pos.gather_qty = ZhCalc.add(pos.contract_qty, pos.qc_qty);
+        }
+    });
+
+    postData(window.location.pathname + '/load', {main: true}, function (result) {
+        cTree.loadDatas(result.main.ledger);
+        cPos.loadDatas(result.main.pos);
+        SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, cTree);
+        loadPosData(0);
+    }, null, true);
+    function loadPosData(iRow) {
+        const node = iRow ? billsSheet.zh_tree.nodes[iRow] : SpreadJsObj.getSelectObject(billsSheet);
+        if (node) {
+            SpreadJsObj.loadSheetData(posSheet, SpreadJsObj.DataType.Data, cPos.getLedgerPos(node.id));
+        } else {
+            SpreadJsObj.loadSheetData(posSheet, SpreadJsObj.DataType.Data, []);
+        }
+        SpreadJsObj.resetTopAndSelect(posSheet);
+    }
+    billsSheet.bind(spreadNS.Events.SelectionChanged, function (e, info) {
+        if (info.newSelections) {
+            const iNewRow = info.newSelections[0].row;
+            if (info.oldSelections) {
+                const iOldRow = info.oldSelections[0].row;
+                if (iNewRow !== iOldRow) {
+                    loadPosData(iNewRow);
+                }
+            } else {
+                loadPosData(iNewRow);
+            }
+        }
+    });
+    $('#select-qi-ok').click(function () {
+        function refreshView () {
+            const compareStages = [];
+            for (let order = 0, iLength = trs.length; order < iLength; order++) {
+                const tr = trs[order];
+                if ($('input', tr)[0].checked) {
+                    compareStages.push(order + 1);
+                }
+            }
+            //setLocalCache(cCacheKey, compareStages.join(','));
+            initSpreadSettingWithRoles(compareStages);
+            SpreadJsObj.initSheet(billsSheet, billsSpreadSetting);
+            treeCalc.calculateAll(cTree);
+            SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, cTree);
+            SpreadJsObj.initSheet(posSheet, posSpreadSetting);
+            loadPosData();
+        }
+        let loadData = [], showData = [], trs = $('tr[stage-id]');
+        for (let order = 0, iLength = trs.length; order < iLength; order++) {
+            const tr = trs[order];
+            if ($('input[type=checkbox]', tr)[0].checked) {
+                if (!cTree.minorData[order + 1]) {
+                    loadData.push(order + 1);
+                }
+                showData.push(order + 1);
+            }
+        }
+        if (loadData.length > 0) {
+            postData(window.location.pathname + '/load', {stages: loadData}, function (result) {
+                for (const aData of result.stages) {
+                    calculateStageLedgerData(aData.bills);
+                    cTree.loadMinorData(aData.bills, aData.order + '', ['gather_qty', 'gather_tp'], ['gather_tp']);
+                    treeCalc.calculateAll(cTree);
+                    calculateStagePosData(aData.pos);
+                    cPos.loadMinorData(aData.pos, aData.order + '', ['gather_qty']);
+                }
+                refreshView();
+                $('#select-qi').modal('hide');
+            }, null, true);
+        } else {
+            refreshView();
+            $('#select-qi').modal('hide');
+        }
+    });
+    (function (select, sheet) {
+        if (!sheet.zh_tree) return;
+        $(select).click(function () {
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            switch (tag) {
+                case "1":
+                case "2":
+                case "3":
+                case "4":
+                case "5":
+                    tree.expandByLevel(parseInt(tag));
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "last":
+                    tree.expandByCustom(() => { return true; });
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "leafXmj":
+                    tree.expandToLeafXmj();
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+            }
+        });
+    })('a[name=showLevel]', billsSheet);
+});

+ 31 - 6
app/public/js/profile.js

@@ -10,26 +10,30 @@ $(document).ready(function() {
         if (user !== '') {
             $(".title-bar h2").text(user);
         }
-
+        $.validator.addMethod("isMobile", function(value, element) {
+            var length = value.length;
+            var mobile = /^1[3456789]\d{9}$/;
+            return this.optional(element) || (length == 11 && mobile.test(value));
+        }, "请正确填写您的手机号码");
         const options = {
             rules: '',
             errorPlacement: function(error, element) {
                 $(element).addClass('is-invalid');
-                $(element).after(error);
+                $('.input-group-append').after(error);
             },
             errorClass: "invalid-feedback",
             errorElement: "div",
             highlight: false,
             success: function(element) {
-                $(element).prev('input').removeClass('is-invalid');
+                $(element).siblings('input').removeClass('is-invalid');
                 $(element).remove();
             },
         };
 
         options.rules = {
             auth_mobile: {
-                mobile: true,
                 required: true,
+                isMobile: true,
             },
         };
         $("#mobile-form").validate(options);
@@ -61,8 +65,10 @@ $(document).ready(function() {
                     isPosting = false;
                     if (response.err === 0) {
                         codeSuccess(btn);
+                        $("input[name='code']").removeAttr('readonly');
+                        $("#bind-btn").removeClass('disabled').removeClass('btn-secondary').addClass('btn-primary');
                     } else {
-                        alert(response.msg);
+                        toast(response.msg, 'error');
                     }
                 }
             });
@@ -71,11 +77,30 @@ $(document).ready(function() {
         // 绑定按钮
         $("#bind-btn").click(function() {
             const code = $("input[name='code']").val();
+            const mobile = $("input[name='auth_mobile']").val();
+            if (!(/^1[3456789]\d{9}$/.test(mobile))) {
+                toast('请填写正确的手机号码', 'error');
+                return false;
+            }
             if (code.length < 6) {
-                alert('请填写正确的验证码');
+                // alert('请填写正确的验证码');
+                toast('请填写正确的验证码', 'error');
                 return false;
             }
         });
+        // 修改手机
+        $('#change-mobile').click(function () {
+            $(this).parents('.form-group').hide();
+            $('#mobile-form').show();
+        });
+
+        // 移除签名
+        $('#delete-sign').click(function () {
+            postData('/profile/sign/delete', {}, function (result) {
+                $('#sign-show').remove();
+                toast('移除成功', 'success');
+            })
+        })
     } catch (error) {
         console.log(error);
     }

+ 48 - 4
app/public/js/revise.js

@@ -19,6 +19,26 @@ $(document).ready(() => {
     const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
     const posSheet = posSpread.getActiveSheet();
     SpreadJsObj.initSheet(posSheet, posSpreadSetting);
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'revise.info.memu.1.0.0',
+        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();
+            billsSpread.refresh();
+            posSpread.refresh();
+        }
+    });
+    const posSearch = $.posSearch({selector: '#pos-search', searchSpread: posSpread});
+
     // 初始化 清单树结构
     const treeSetting = {
         id: 'ledger_id',
@@ -135,9 +155,8 @@ $(document).ready(() => {
             if (info.newSelections[0].row !== info.oldSelections[0].row) {
                 billsTreeSpreadObj.refreshOperationValid(info.sheet);
                 posSpreadObj.loadCurPosData();
-                SpreadJsObj.saveTopAndSelect(posSheet, ckBillsSpread);
-                SpreadJsObj.resetTopAndSelect(billsSheet);
-                //posSearch.search($('#pos-keyword').val());
+                SpreadJsObj.saveTopAndSelect(billsSheet, ckBillsSpread);
+                posSearch.search($('#pos-keyword').val());
             }
         },
         /**
@@ -556,7 +575,7 @@ $(document).ready(() => {
             return result;
         }
     }
-    let stdChapter, stdBills;
+    let stdChapter, stdBills, searchLedger;
     const dealBills = new DealBills('#deal-bills-spread', {
         cols: [
             {title: '清单编号', field: 'code', hAlign: 0, width: 120, formatter: '@', readOnly: true},
@@ -672,6 +691,31 @@ $(document).ready(() => {
             } else if (tab.attr('content') === '#deal-bills') {
                 dealBills.loadData();
                 dealBills.spread.refresh();
+            } else if (tab.attr('content') === '#search' && !searchLedger) {
+                if (!searchLedger) {
+                    searchLedger = $.billsSearch({
+                        selector: '#search',
+                        searchSpread: billsSpread,
+                        resultSpreadSetting: {
+                            cols: [
+                                {title: '项目节编号', field: 'code', hAlign: 0, width: 120, formatter: '@', readOnly: true},
+                                {title: '清单编号', field: 'b_code', hAlign: 0, width: 120, formatter: '@', readOnly: true},
+                                {title: '名称', field: 'name', width: 230, hAlign: 0, formatter: '@', readOnly: true},
+                                {title: '单位', field: 'unit', width: 50, hAlign: 1, formatter: '@', readOnly: true},
+                                {title: '单价', field: 'unit_price', hAlign: 2, width: 50, readOnly: true},
+                                {title: '数量', field: 'quantity', hAlign: 2, width: 50, readOnly: true},
+                            ],
+                            emptyRows: 0,
+                            headRows: 1,
+                            headRowHeight: [40],
+                            defaultRowHeight: 21,
+                        },
+                        afterLocated: function () {
+                            posSpreadObj.loadCurPosData();
+                        }
+                    });
+                }
+                searchLedger.spread.refresh();
             }
         }
         billsSpread.refresh();

+ 14 - 0
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -80,10 +80,24 @@ const SpreadJsObj = {
         spread.options.allowUserDragDrop = false;
         spread.options.allowUserEditFormula = false;
         spread.options.allowExtendPasteRange = true;
+        spread.options.copyPasteHeaderOptions = spreadNS.CopyPasteHeaderOptions.noHeaders;
         spread.getActiveSheet().options.clipBoardOptions = GC.Spread.Sheets.ClipboardPasteOptions.values;//设置粘贴时只粘贴值
         spread.getActiveSheet().setRowCount(3);
         return spread;
     },
+    addCutEvents: function (spread, fun) {
+        const cut = spreadNS.Commands.cut.execute;
+        spreadNS.Commands.cut.execute = function (context, options, isUndo) {
+            const self = this, sheet = context.getActiveSheet();
+            const sels = sheet.getSelections();
+            const sel = sels ? sels[0] : null;
+            if (sel) {
+                return fun(sheet,  sel, function () {
+                    cut(context, options, isUndo);
+                });
+            }
+        }
+    },
     /**
      * 保护sheet(需设置保护后, 单元格的locked等属性方可生效)
      * @param {GC.Spread.Sheets.Worksheet} sheet

+ 97 - 11
app/public/js/stage.js

@@ -1130,7 +1130,7 @@ $(document).ready(() => {
         stagePos.calculateAll();
         stagePosSpreadObj.loadCurPosData();
         SpreadJsObj.resetTopAndSelect(spSpread.getActiveSheet());
-    });
+    }, null, true);
     spSpread.bind(spreadNS.Events.EditEnded, stagePosSpreadObj.editEnded);
     spSpread.bind(spreadNS.Events.ClipboardPasting, stagePosSpreadObj.clipboardPasting);
     spSpread.bind(spreadNS.Events.ClipboardPasted, stagePosSpreadObj.clipboardPasted);
@@ -1180,6 +1180,23 @@ $(document).ready(() => {
             }
         })
     }
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        //key: 'stage.memu.1.0.0',
+        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();
+            slSpread.refresh();
+            spSpread.refresh();
+        }
+    });
 
     $('#row-view').on('show.bs.modal', function () {
         const html = [], customDisplay = customColDisplay();
@@ -1223,7 +1240,7 @@ $(document).ready(() => {
                     {title: '数量', field: 'quantity', hAlign: 2, width: 50},
                     {title: '完成率(%)', field: 'complete_percent', hAlign: 2, width: 70},
                 ],
-                emptyRows: 3,
+                emptyRows: 0,
                 headRows: 1,
                 headRowHeight: [40],
                 defaultRowHeight: 21,
@@ -1236,7 +1253,7 @@ $(document).ready(() => {
             });
             $('#searchLedger', this.obj).bind('click', () => {self.searchText()});
             $('#over', this.obj).bind('change', () => {self.searchOver()});
-            $('#empty', this.obj).bind('change', () => {self.searchEmpty()});
+            $('#empty', this.obj).bind('change', () => {self.searchLess()});
             this.spread.getActiveSheet().bind(GC.Spread.Sheets.Events.CellDoubleClick, function (e, info) {
                 const sheet = info.sheet;
                 const data = sheet.zh_data;
@@ -1245,9 +1262,17 @@ $(document).ready(() => {
                 const curBills = data[info.row];
                 if (!curBills) { return }
 
-                SpreadJsObj.locateTreeNode(self.mainSpread.getActiveSheet(), curBills.ledger_id);
+                SpreadJsObj.locateTreeNode(self.mainSpread.getActiveSheet(), curBills.ledger_id, true);
+                stagePosSpreadObj.loadCurPosData();
             });
         }
+        calculateCompletePercent() {
+            if (!this.searchResult) return;
+            for (const sr of this.searchResult) {
+                const base = ZhCalc.add(sr.total_price, sr.end_qc_tp);
+                sr.complete_percent = base !== 0 ? ZhCalc.mul(ZhCalc.div(sr.end_gather_tp, base), 100, 2) : 0;
+            }
+        }
         searchText() {
             const keyword = $('#keyword', this.obj).val();
             if (keyword !== '') {
@@ -1259,9 +1284,12 @@ $(document).ready(() => {
                     if ((node.code && node.code.indexOf(keyword) > -1) ||
                         node.b_code && node.b_code.indexOf(keyword) > -1 ||
                         node.name && node.name.indexOf(keyword) > -1) {
-                        this.searchResult.push(node);
+                        const data = JSON.parse(JSON.stringify(node));
+                        data.visible = true;
+                        this.searchResult.push(data);
                     }
                 }
+                this.calculateCompletePercent();
                 SpreadJsObj.loadSheetData(this.spread.getActiveSheet(), 'data', this.searchResult);
             }
         }
@@ -1272,14 +1300,19 @@ $(document).ready(() => {
                 if (node.children && node.children.length > 0) continue;
                 if (node.end_gather_qty) {
                     if (!node.quantity || Math.abs(node.end_gather_qty) > Math.abs(node.quantity)) {
-                        this.searchResult.push(node);
+                        const data = JSON.parse(JSON.stringify(node));
+                        data.visible = true;
+                        this.searchResult.push(data);
                     }
                 } else if (node.end_gather_tp) {
                     if (!node.total_price || Math.abs(node.end_gather_tp) > Math.abs(node.total_price)) {
-                        this.searchResult.push(node);
+                        const data = JSON.parse(JSON.stringify(node));
+                        data.visible = true;
+                        this.searchResult.push(data);
                     }
                 }
             }
+            this.calculateCompletePercent();
             SpreadJsObj.loadSheetData(this.spread.getActiveSheet(), 'data', this.searchResult);
         }
         searchEmpty() {
@@ -1289,14 +1322,41 @@ $(document).ready(() => {
                 if (node.children && node.children.length > 0) continue;
                 if (node.quantity) {
                     if (!node.end_gather_qty || checkZero(node.end_gather_qty)) {
-                        this.searchResult.push(node);
+                        const data = JSON.parse(JSON.stringify(node));
+                        data.visible = true;
+                        this.searchResult.push(data);
                     }
                 } else if (node.total_price) {
                     if (!node.end_gather_tp || checkZero(node.end_gather_tp)) {
-                        this.searchResult.push(node);
+                        const data = JSON.parse(JSON.stringify(node));
+                        data.visible = true;
+                        this.searchResult.push(data);
                     }
                 }
             }
+            this.calculateCompletePercent();
+            SpreadJsObj.loadSheetData(this.spread.getActiveSheet(), 'data', this.searchResult);
+        }
+        searchLess() {
+            this.searchResult = [];
+            const sortData = SpreadJsObj.getSortData(this.mainSpread.getActiveSheet());
+            for (const node of sortData) {
+                if (node.children && node.children.length > 0) continue;
+                if (node.quantity) {
+                    if (ZhCalc.sub(node.quantity, node.end_gather_qty) > 0) {
+                        const data = JSON.parse(JSON.stringify(node));
+                        data.visible = true;
+                        this.searchResult.push(data);
+                    }
+                } else if (node.total_price) {
+                    if (ZhCalc.sub(node.total_price, node.end_gather_tp) > 0) {
+                        const data = JSON.parse(JSON.stringify(node));
+                        data.visible = true;
+                        this.searchResult.push(data);
+                    }
+                }
+            }
+            this.calculateCompletePercent();
             SpreadJsObj.loadSheetData(this.spread.getActiveSheet(), 'data', this.searchResult);
         }
     }
@@ -1351,8 +1411,8 @@ $(document).ready(() => {
                 const curRow = sel ? sel.row : 0;
                 const pos = resultArr[0];
                 if (pos.index !== curRow) {
-                    sheet.setSelection(next.index, sel ? sel.col : 0, 1, 1);
-                    sheet.showRow(next.index, spreadNS.VerticalPosition.center);
+                    sheet.setSelection(pos.index, sel ? sel.col : 0, 1, 1);
+                    sheet.showRow(pos.index, spreadNS.VerticalPosition.center);
                 }
             }
         };
@@ -1397,6 +1457,19 @@ $(document).ready(() => {
         } else {
             $('[for=' + this.id +']').removeClass('text-warning');
         }
+        if (this.checked) {
+            if ($('#pos-over-search')[0].checked) {
+                $('#pos-search-keyword').attr('placeholder', '按名称查询');
+            } else {
+                $('#pos-search-keyword').attr('placeholder', '漏计中按名称查询');
+            }
+        } else {
+            if ($('#pos-over-search')[0].checked) {
+                $('#pos-search-keyword').attr('placeholder', '超计中按名称查询');
+            } else {
+                $('#pos-search-keyword').attr('placeholder', '按名称查询');
+            }
+        }
         posSearch.searchAndLocate();
     });
     $('#pos-over-search').click(function () {
@@ -1405,6 +1478,19 @@ $(document).ready(() => {
         } else {
             $('[for=' + this.id +']').removeClass('text-danger');
         }
+        if (this.checked) {
+            if ($('#pos-empty-search')[0].checked) {
+                $('#pos-search-keyword').attr('placeholder', '按名称查询');
+            } else {
+                $('#pos-search-keyword').attr('placeholder', '超计中按名称查询');
+            }
+        } else {
+            if ($('#pos-empty-search')[0].checked) {
+                $('#pos-search-keyword').attr('placeholder', '漏计中按名称查询');
+            } else {
+                $('#pos-search-keyword').attr('placeholder', '按名称查询');
+            }
+        }
         posSearch.searchAndLocate();
     });
     $('#pos-search-next').click(() => {posSearch.locateNext()});

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

@@ -134,11 +134,11 @@ $(document).ready(function () {
     $('#hideSp').click(function () {
         $('#sp-list2').modal('hide');
     });
-    $('a[target]').click(function () {
+    $('a[f-target]').click(function () {
         if (stage.check_detail) {
             $('#sub-sp3').modal('show');
         } else {
-            $($(this).attr('target')).modal('show');
+            $($(this).attr('f-target')).modal('show');
         }
     })
 });

+ 55 - 25
app/public/js/stage_change.js

@@ -8,7 +8,6 @@
  * @version
  */
 class ChangeAnalysis {
-
     constructor (ledgerData) {
         this.ledgerTreeSetting = {
             id: 'ledger_id',
@@ -21,7 +20,6 @@ class ChangeAnalysis {
         this.ledgerTree = createNewPathTree('ledger', this.ledgerTreeSetting);
         this.ledgerTree.loadDatas(ledgerData);
     }
-
     getLeafXmj (node) {
         let n = node;
         while (n) {
@@ -32,14 +30,21 @@ class ChangeAnalysis {
         }
         return null;
     }
-
     analyze (change) {
         change.filterBills = false;
         change.attachments = change.detail.attachments;
-        change.bills = change.detail.addUsedBills;
+        change.bills = change.detail.bills;
+        //change.bills = change.detail.addUsedBills;
         for (const b of change.bills) {
+            const aub = change.detail.addUsedBills.find(function (x) {
+                return x.id = b.id;
+            });
+            if (aub) {
+                b.used_qty = aub.used_qty;
+            }
             b.qty = _.toNumber(b.samount);
-            b.valid_qty = ZhCalc.sub(b.qty - b.used_qty);
+            b.valid_qty = ZhCalc.sub(b.qty, b.used_qty);
+            b.tp = ZhCalc.round(ZhCalc.mul(b.qty, b.unit_price), tenderInfo.decimal.tp);
             b.pos = _.filter(change.detail.curUsedBills, {cbid: b.id});
             b.cur_qty = 0;
             for (const p of b.pos) {
@@ -56,6 +61,8 @@ class ChangeAnalysis {
         }
     }
 }
+let changes, usedChangesId, ledger, analysis, tenderInfo;
+
 
 $(document).ready(() => {
     autoFlashHeight();
@@ -118,6 +125,26 @@ $(document).ready(() => {
     };
     const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
     SpreadJsObj.initSheet(posSpread.getActiveSheet(), posSpreadSetting);
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        //key: 'stage.change.memu.1.0.0',
+        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();
+            changeSpread.refresh();
+            billsSpread.refresh();
+            posSpread.refresh();
+        }
+    });
+
     // 上下窗口resizer
     $.divResizer({
         select: '#main-resize',
@@ -141,19 +168,11 @@ $(document).ready(() => {
     });
     // ------------end 初始化界面
 
-    // ------------begin 预处理数据
-    const analysis = new ChangeAnalysis(ledger);
-    if (changes.length > 0) {
-        analysis.analyze(changes[0]);
-    }
-    // ------------end 预处理数据
-
     // ------------begin spread界面操作方法
     const posSpreadObj = {
         loadCurPosData () {
             const bills = SpreadJsObj.getSelectObject(billsSpread.getActiveSheet());
             if (bills) {
-                console.log(bills.pos);
                 SpreadJsObj.loadSheetData(posSpread.getActiveSheet(), SpreadJsObj.DataType.Data, bills.pos);
             } else {
                 SpreadJsObj.loadSheetData(posSpread.getActiveSheet(), SpreadJsObj.DataType.Data, []);
@@ -194,7 +213,7 @@ $(document).ready(() => {
             // 加载变更信息和附件
             const obj = $('.tab-content');
             if (change) {
-                const inputs = $('input[type!=checkbox]', obj);
+                const inputs = $('input[type!=checkbox][type!=radio]', obj);
                 for (const i of inputs) {
                     const field = $(i).attr('name');
                     const text = (field && change[field]) ? change[field] : '';
@@ -226,7 +245,7 @@ $(document).ready(() => {
                 // 变更性质
                 $('select[name=quality]').val(change.quality);
                 // 变更单位
-                $('select[name=company]').html('<option>' + change.company ? change.company : '' + '</option>');
+                $('select[name=company]').html('<option>' + (change.company ? change.company : '') + '</option>');
                 // 费用承担方
                 $('input[name=charge][value=' + change.charge + ']').prop('checked', true);
                 // 附件
@@ -267,16 +286,27 @@ $(document).ready(() => {
     changeSpread.bind(spreadNS.Events.SelectionChanged, changeSpreadObj.selectionChanged);
     // ------------end spread界面操作方法
 
-
-    // ------------begin 加载数据至界面
-    SpreadJsObj.loadSheetData(changeSpread.getActiveSheet(), SpreadJsObj.DataType.Data, changes);
-    changeSpreadObj.loadChangeDetailData();
-    if (usedChangesId.length > 0) {
-        changeSpreadObj.filterUsedChange(true);
-    } else {
-        $('#used-change')[0].checked = false;
-    }
-    // ------------end 加载数据至界面
+    postData(window.location.pathname + '/data', null, function (result) {
+        tenderInfo = result.tenderInfo;
+        changes = result.changes;
+        usedChangesId = result.usedChangesId;
+        ledger = result.ledger;
+        // ------------begin 预处理数据
+        analysis = new ChangeAnalysis(ledger);
+        if (changes.length > 0) {
+            analysis.analyze(changes[0]);
+        }
+        // ------------end 预处理数据
+        // ------------begin 加载数据至界面
+        SpreadJsObj.loadSheetData(changeSpread.getActiveSheet(), SpreadJsObj.DataType.Data, changes);
+        changeSpreadObj.loadChangeDetailData();
+        if (usedChangesId.length > 0) {
+            changeSpreadObj.filterUsedChange(true);
+        } else {
+            $('#used-change')[0].checked = false;
+        }
+        // ------------end 加载数据至界面
+    });
 
     // ------------begin dom事件定义
     // 本期已用变更

+ 80 - 37
app/public/js/stage_compare.js

@@ -8,9 +8,7 @@
  * @version
  */
 
-$(document).ready(function () {
-    autoFlashHeight();
-    // 根据设置整理Spread设置
+function initSpreadSettingWithRoles(compareRoles) {
     function setSpreadSettingCols(setting, fieldSufs, Roles) {
         function addExtraCols(fieldSuf, Role) {
             for (const ec of setting.extraCols) {
@@ -28,27 +26,60 @@ $(document).ready(function () {
             addExtraCols(fieldSufs[index], Roles[index]);
         }
     }
-    setSpreadSettingCols(ledgerSpreadSetting, ['0'], ['原报']);
-    setSpreadSettingCols(posSpreadSetting, ['0'], ['原报']);
-
-    function calculateStageLedgerData(datas) {
-        for (const d of datas) {
-            d.gather_qty = ZhCalc.add(d.contract_qty, d.qc_qty);
-            d.gather_tp = ZhCalc.add(d.contract_tp, d.qc_tp);
+    const fieldSufs = ['0'], roles = ['原报'], trs = $('tr[auditorId]');
+    for (let r of compareRoles) {
+        if (r > 0) {
+            const tr = trs[r-1];
+            if (tr) {
+                fieldSufs.push(r + '');
+                roles.push(tr.children[0].textContent);
+            }
         }
     }
-    function calculateStagePosData(datas) {
-        for (const d of datas) {
-            d.gather_qty = ZhCalc.add(d.contract_qty, d.qc_qty);
-        }
+    setSpreadSettingCols(ledgerSpreadSetting, fieldSufs, roles);
+    setSpreadSettingCols(posSpreadSetting, fieldSufs, roles);
+}
+function calculateStageLedgerData(datas) {
+    for (const d of datas) {
+        d.gather_qty = ZhCalc.add(d.contract_qty, d.qc_qty);
+        d.gather_tp = ZhCalc.add(d.contract_tp, d.qc_tp);
     }
+}
+function calculateStagePosData(datas) {
+    for (const d of datas) {
+        d.gather_qty = ZhCalc.add(d.contract_qty, d.qc_qty);
+    }
+}
 
+$(document).ready(function () {
+    autoFlashHeight();
+    // 根据设置整理Spread设置
+    initSpreadSettingWithRoles(scRoles);
     // 初始化台账
     const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
     SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
     // 初始化部位
     const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
     SpreadJsObj.initSheet(posSpread.getActiveSheet(), posSpreadSetting);
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        //key: 'stage.compare.memu.1.0.0',
+        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();
+            ledgerSpread.refresh();
+            posSpread.refresh();
+        }
+    });
+
     // 上下窗口resizer
     $.divResizer({
         select: '#main-resize',
@@ -72,12 +103,6 @@ $(document).ready(function () {
         calcFields: [],
     };
     const scTree = createNewPathTree('master', scTreeSetting);
-    scTree.loadDatas(ledger);
-    calculateStageLedgerData(orgStageLedger);
-    scTree.loadMinorData(orgStageLedger, '0', ['gather_qty', 'gather_tp'], ['gather_tp']);
-    treeCalc.calculateAll(scTree);
-    scTree.expandByCalcFields();
-    SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, scTree);
     // 加载 部位 数据
     const scPosSetting = {
         id: 'id', ledgerId: 'lid', masterId: 'id', minorId: 'pid',
@@ -86,11 +111,23 @@ $(document).ready(function () {
         pos.gather_qty = ZhCalc.add(pos.contract_qty, pos.qc_qty);
     };
     const scPos = new MasterPosData(scPosSetting);
-    scPos.loadDatas(pos);
-    calculateStagePosData(orgStagePos);
-    scPos.loadMinorData(orgStagePos, '0', ['gather_qty']);
 
-    // 获取项目节数据
+    postData(window.location.pathname + '/load', {main: true, roles: scRoles}, function (result) {
+        scTree.loadDatas(result.main.ledger);
+        scPos.loadDatas(result.main.pos);
+        for (const aData of result.roles) {
+            calculateStageLedgerData(aData.bills);
+            scTree.loadMinorData(aData.bills, aData.order + '', ['gather_qty', 'gather_tp'], ['gather_tp']);
+            treeCalc.calculateAll(scTree);
+            calculateStagePosData(aData.pos);
+            scPos.loadMinorData(aData.pos, aData.order + '', ['gather_qty']);
+        }
+        scTree.expandByCalcFields();
+        SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, scTree);
+        loadPosData(0);
+    }, null, true);
+
+    // 获取部位明细数据
     function loadPosData(iRow) {
         const node = ledgerSpread.getActiveSheet().zh_tree.nodes[iRow];
         if (node) {
@@ -100,12 +137,18 @@ $(document).ready(function () {
         }
         SpreadJsObj.resetTopAndSelect(posSpread.getActiveSheet());
     }
-    loadPosData(0);
     // 切换清单行,读取所属项目节数据
     ledgerSpread.getActiveSheet().bind(spreadNS.Events.SelectionChanged, function (e, info) {
-        const iOldRow = info.oldSelections[0].row, iNewRow = info.newSelections[0].row;
-        if (iNewRow !== iOldRow) {
-            loadPosData(iNewRow);
+        if (info.newSelections) {
+            const iNewRow = info.newSelections[0].row;
+            if (info.oldSelections) {
+                const iOldRow = info.oldSelections[0].row;
+                if (iNewRow !== iOldRow) {
+                    loadPosData(iNewRow);
+                }
+            } else {
+                loadPosData(iNewRow);
+            }
         }
     });
     // 显示本期计量
@@ -120,19 +163,19 @@ $(document).ready(function () {
     // 选择比较人
     $('#select-qi-ok').click(function () {
         function refreshView () {
-            scTreeSetting.calcFields = [];
-            const fieldSufs = ['0'], roles = ['原报'], trs = $('tr[auditorId]');
+            const compareRoles = [0];
             for (let order = 0, iLength = trs.length; order < iLength; order++) {
                 const tr = trs[order];
-                fieldSufs.push((order + 1) + '');
-                roles.push(tr.children[0].textContent);
+                if ($('input', tr)[0].checked) {
+                    compareRoles.push(order + 1);
+                }
             }
-            setSpreadSettingCols(ledgerSpreadSetting, fieldSufs, roles);
+            setLocalCache(scCacheKey, compareRoles.join(','));
+            initSpreadSettingWithRoles(compareRoles);
             SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
             treeCalc.calculateAll(scTree);
             scTree.expandByCalcFields();
             SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, scTree);
-            setSpreadSettingCols(posSpreadSetting, fieldSufs, roles);
             SpreadJsObj.initSheet(posSpread.getActiveSheet(), posSpreadSetting);
             loadPosData(0);
         }
@@ -147,8 +190,8 @@ $(document).ready(function () {
             }
         }
         if (loadData.length > 0) {
-            postData(window.location.pathname + '/load', {auditors: loadData}, function (result) {
-                for (const aData of result) {
+            postData(window.location.pathname + '/load', {roles: loadData}, function (result) {
+                for (const aData of result.roles) {
                     calculateStageLedgerData(aData.bills);
                     scTree.loadMinorData(aData.bills, aData.order + '', ['gather_qty', 'gather_tp'], ['gather_tp']);
                     treeCalc.calculateAll(scTree);
@@ -157,7 +200,7 @@ $(document).ready(function () {
                 }
                 refreshView();
                 $('#select-qi').modal('hide');
-            });
+            }, null, true);
         } else {
             refreshView();
             $('#select-qi').modal('hide');

+ 44 - 5
app/public/js/stage_detail.js

@@ -25,6 +25,24 @@ $(document).ready(() => {
         readOnly: readOnly,
     };
     const detailSpread = SpreadJsObj.createNewSpread($('#detail-spread')[0]);
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        //key: 'stage.detail.memu.1.0.0',
+        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();
+            detailSpread.refresh();
+        }
+    });
+
     SpreadJsObj.initSheet(detailSpread.getActiveSheet(), detailSpreadSetting);
     const detailOperationObj = {
         // 部位明细
@@ -272,6 +290,27 @@ $(document).ready(() => {
             }
         }
     });
+    $('#choose').on('show.bs.modal', function () {
+        function chooseType(obj) {
+            obj.style.cursor = 'default';
+            $(obj).children().addClass('text-primary');
+            $('h5', obj).prepend('<i class="fa fa-check pull-right"></i>');
+        }
+        function validType(obj) {
+            obj.style.cursor = 'pointer';
+            $(obj).children().removeClass('text-primary');
+            $('i', obj).remove();
+        }
+        $('#im-pre').val(stage.im_pre ? stage.im_pre : '');
+        const typeArr = $('div[name="im-type"]');
+        for (const t of typeArr) {
+            if ($(t).attr('im-type') === stage.im_type) {
+                chooseType(t);
+            } else {
+                validType(t)
+            }
+        }
+    });
     // 提交 中间计量模式
     $('#choose-ok').click(() => {
         const chooseType = _.find($('div[name="im-type"]', '#im-type'), function (it) {
@@ -388,18 +427,18 @@ $(document).ready(() => {
                     }
                 }
             });
-            SpreadJsObj.loadSheetData(gsSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, gsTree);
-            const gatherNodes = stage.im_gather_node ? _.map(stage.im_gather_node.split(','), _.toNumber) : [];
+            const gatherNodes = stage.im_gather_node ? _.map(stage.im_gather_node.split(',')) : [];
             for (const node of gsTree.datas) {
-                node.check = gatherNodes.indexOf(node.id) !== -1;
+                node.check = gatherNodes.indexOf(node.id + '') !== -1;
             }
+            SpreadJsObj.loadSheetData(gsSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, gsTree);
             gsTree.expandByLevel(4);
             SpreadJsObj.refreshTreeRowVisible(gsSpread.getActiveSheet());
             SpreadJsObj.resetFieldReadOnly(gsSpread.getActiveSheet, 'check', !$('#im-gather-check')[0].checked);
         } else {
-            const gatherNodes = stage.im_gather_node ? _.map(stage.im_gather_node.split(','), _.toNumber) : [];
+            const gatherNodes = stage.im_gather_node ? _.map(stage.im_gather_node.split(',')) : [];
             for (const node of gsTree.datas) {
-                node.check = gatherNodes.indexOf(node.id) !== -1;
+                node.check = gatherNodes.indexOf(node.id + '') !== -1;
             }
             SpreadJsObj.reLoadColsData(gsSpread.getActiveSheet(), [0]);
         }

+ 19 - 0
app/public/js/stage_gather.js

@@ -20,6 +20,25 @@ $(document).ready(function () {
     // 初始化所属项目节
     const leafXmjSpread = SpreadJsObj.createNewSpread($('#leaf-xmj-spread')[0]);
     SpreadJsObj.initSheet(leafXmjSpread.getActiveSheet(), leafXmjSpreadSetting);
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        //key: 'stage.gather.memu.1.0.0',
+        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();
+            gclSpread.refresh();
+            leafXmjSpread.refresh();
+        }
+    });
+
     // 上下窗口resizer
     $.divResizer({
         select: '#main-resize',

+ 20 - 1
app/public/js/stage_pay.js

@@ -21,6 +21,7 @@ function loadUpdateDealPays(newPay, fields) {
     for (const np of newPays) {
         const op = _.find(dealPay, {id: np.id});
         for (const prop in np) {
+            if (prop === 'attachment') continue;
             if (!fields || fields.indexOf(prop) >= 0) {
                 op[prop] = np[prop];
             }
@@ -46,6 +47,24 @@ $(document).ready(() => {
     autoFlashHeight();
 
     const paySpread = SpreadJsObj.createNewSpread($('#pay-spread')[0]);
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        //key: 'stage.pay.memu.1.0.0',
+        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();
+            paySpread.refresh();
+        }
+    });
+
     const paySpreadSetting = {
         cols: [
             {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 150, formatter: '@', readOnly: 'readOnly.name'},
@@ -497,7 +516,7 @@ $(document).ready(() => {
         $('#del').click(paySpreadObj.del);
         $('#up-move').click(paySpreadObj.upMove);
         $('#down-move').click(paySpreadObj.downMove);
-        $('#expr').change(function () {
+        $('#expr').bind('change mouseleave', function () {
             const expr = $(this);
             const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
             const field = expr.attr('field'), orgValue = expr.attr('org'), newValue = expr.val();

+ 50 - 0
app/public/js/sub_menu.js

@@ -0,0 +1,50 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+(function($){
+    $.subMenu = function (setting) {
+        const menu = $(setting.menu), miniMenu = $(setting.miniMenu);
+        const toMenu = $(setting.toMenu), toMiniMenu = $(setting.toMiniMenu);
+
+        const showMenu = function () {
+            menu.show();
+            miniMenu.removeClass('d-flex').hide();
+            setting.callback({mini: false});
+        };
+        const showMiniMenu = function () {
+            menu.hide();
+            miniMenu.addClass('d-flex').show();
+            setting.callback({mini: true});
+        };
+        const menuType = setting.key ? getLocalCache(setting.key) : null;
+        if (menuType && menuType === 'mini-menu') {
+            showMiniMenu();
+        } else {
+            showMenu();
+        }
+        toMenu.click(function () {
+            showMenu();
+            if (setting.key) {
+                setLocalCache(setting.key, 'menu');
+            }
+        });
+        toMiniMenu.click(function () {
+            showMiniMenu();
+            if (setting.key) {
+                setLocalCache(setting.key, 'miniMenu');
+            }
+        });
+        miniMenu.mouseenter(function () {
+            $(setting.miniMenuList).show();
+        });
+        miniMenu.mouseleave(function () {
+            $(setting.miniMenuList).hide();
+        });
+    }
+})(jQuery);

+ 9 - 3
app/router.js

@@ -107,7 +107,7 @@ module.exports = app => {
     app.post('/tender/:id/revise/save', sessionAuth, tenderCheck, 'reviseController.save');
     app.get('/tender/:id/revise/info', sessionAuth, tenderCheck, 'reviseController.info');
     app.post('/tender/:id/revise/info/base-opr', sessionAuth, tenderCheck, 'reviseController.baseOpr');
-    app.post('/tender/:id/revise/info/batch-insert', sessionAuth, tenderCheck, 'reviseController.batchInsert')
+    app.post('/tender/:id/revise/info/batch-insert', sessionAuth, tenderCheck, 'reviseController.batchInsert');
     //app.post('/tender/:id/ledger/revise/audit/', sessionAuth, tenderCheck, 'ledgerController.reviseStatus');
     //app.get('/tender/:id/ledger/index', sessionAuth, 'ledgerController.index');
 
@@ -129,10 +129,12 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/auditors', sessionAuth, tenderCheck, 'measureController.stageAuditors');
     app.post('/tender/:id/measure/add', sessionAuth, tenderCheck, 'measureController.add');
     app.post('/tender/:id/measure/save', sessionAuth, tenderCheck, 'measureController.save');
+    app.post('/tender/:id/measure/stage/delete', sessionAuth, tenderCheck, 'measureController.delete');
     // 计量台账 -- 清单汇总
     app.get('/tender/:id/measure/gather', sessionAuth, tenderCheck, 'measureController.gather');
     // 计量台账 -- 审核比较
     app.get('/tender/:id/measure/compare', sessionAuth, tenderCheck, 'measureController.compare');
+    app.post('/tender/:id/measure/compare/load', sessionAuth, tenderCheck, 'measureController.loadCompareData');
 
     // 期计量详细
     // 本期计量台账
@@ -158,14 +160,16 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/pay/upload/file', sessionAuth, tenderCheck, stageCheck, 'stageController.payUploadFile');
     app.get('/tender/:id/measure/stage/:order/pay/download/file/:pid/:index', sessionAuth, 'stageController.payDownloadFile');
     app.post('/tender/:id/measure/stage/:order/pay/delete/file', sessionAuth, 'stageController.payDeleteFile');
-    // 变更
+    // 变更概况
     app.get('/tender/:id/measure/stage/:order/change', sessionAuth, tenderCheck, stageCheck, 'stageController.change');
+    app.post('/tender/:id/measure/stage/:order/change/data', sessionAuth, tenderCheck, stageCheck, 'stageController.getChangeData');
     app.post('/tender/:id/measure/stage/:order/change/detail', sessionAuth, tenderCheck, stageCheck, 'stageController.changeDetail');
     // 审批
     app.post('/tender/:id/measure/stage/:order/audit/add', sessionAuth, tenderCheck, stageCheck, 'stageController.addAudit');
     app.post('/tender/:id/measure/stage/:order/audit/delete', sessionAuth, tenderCheck, stageCheck, 'stageController.deleteAudit');
     app.post('/tender/:id/measure/stage/:order/audit/start', sessionAuth, tenderCheck, stageCheck, 'stageController.startAudit');
     app.post('/tender/:id/measure/stage/:order/audit/check', sessionAuth, tenderCheck, stageCheck, 'stageController.checkAudit');
+    app.get('/tender/:id/measure/stage/:order/audit/check/again', sessionAuth, tenderCheck, stageCheck, 'stageController.checkAuditAgain');
     // 清单汇总
     app.get('/tender/:id/measure/stage/:order/gather', sessionAuth, tenderCheck, stageCheck, 'stageController.gather');
     // 审核比较
@@ -186,7 +190,6 @@ module.exports = app => {
     app.post('/tender/:id/change/newCode', sessionAuth, tenderCheck, 'changeController.newCode');
     app.post('/tender/:id/change/add', sessionAuth, tenderCheck, 'changeController.add');
     app.get('/tender/:id/change/:cid/info', sessionAuth, tenderCheck, 'changeController.info');
-    app.get('/tender/:id/change/:cid/info', sessionAuth, tenderCheck, 'changeController.info');
     app.post('/change/upload/file', sessionAuth, 'changeController.uploadFile');
     app.get('/change/download/file/:id', sessionAuth, 'changeController.downloadFile');
     app.post('/change/delete/file', sessionAuth, 'changeController.deleteFile');
@@ -195,6 +198,7 @@ module.exports = app => {
     app.post('/change/save', sessionAuth, 'changeController.save');
 
     app.post('/change/approval', sessionAuth, 'changeController.approval');
+    app.post('/change/check/again', sessionAuth, 'changeController.checkAgain');
 
     // 变更单位管理
     app.post('/change/update/company', sessionAuth, 'changeController.updateCompany');
@@ -202,7 +206,9 @@ module.exports = app => {
     // 个人账号相关
     app.get('/profile/info', sessionAuth, 'profileController.info');
     app.get('/profile/sms', sessionAuth, 'profileController.sms');
+    app.post('/profile/sms/type', sessionAuth, 'profileController.smsType');
     app.get('/profile/sign', sessionAuth, 'profileController.sign');
+    app.post('/profile/sign/delete', sessionAuth, 'profileController.signDelete');
     app.get('/profile/safe', sessionAuth, 'profileController.safe');
     app.post('/profile/save', sessionAuth, 'profileController.saveBase');
     app.post('/profile/password', sessionAuth, 'profileController.modifyPassword');

+ 176 - 17
app/service/change.js

@@ -11,6 +11,8 @@
 const audit = require('../const/audit');
 const fs = require('fs');
 const path = require('path');
+const smsTypeConst = require('../const/sms_type');
+const SMS = require('../lib/sms');
 
 module.exports = app => {
     class Change extends app.BaseService {
@@ -293,7 +295,7 @@ module.exports = app => {
                 if (postData.changestatus !== undefined && parseInt(postData.changestatus) === 1) {
                     change_status = true;
                     // 更新原报人审批状态
-                    await this.transaction.update(this.ctx.service.changeAudit.tableName, { id: lastUser.id, status: 3, sin_time: new Date() });
+                    await this.transaction.update(this.ctx.service.changeAudit.tableName, { id: lastUser.id, status: audit.flow.auditStatus.checked, sin_time: new Date() });
                 }
                 // 再插入postData里的变更审批人和清单
                 if (postData.changeaudit !== undefined && postData.changeaudit !== '') {
@@ -303,7 +305,7 @@ module.exports = app => {
                     let uSort = parseInt(lastUser.usort) + 1;
                     for (const [index, ca] of changeAudit.entries()) {
                         const auditInfo = ca.split('/%/');
-                        const uStatus = change_status && index === 0 ? 2 : 1;
+                        const uStatus = change_status && index === 0 ? audit.flow.auditStatus.checking : audit.flow.auditStatus.uncheck;
                         const sin_time = change_status && index === 0 ? new Date() : null;
                         const caArray = {
                             tid: tenderId,
@@ -321,6 +323,19 @@ module.exports = app => {
                         uSite++;
                         uSort++;
                         insertCA.push(caArray);
+
+                        // 添加短信通知-需要审批提醒功能
+                        if (change_status && index === 0) {
+                            const smsUser = await this.ctx.service.projectAccount.getDataById(auditInfo[0]);
+                            if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                                const smsType = JSON.parse(smsUser.sms_type);
+                                if (smsType[smsTypeConst.const.BG] !== undefined && smsType[smsTypeConst.const.BG].indexOf(smsTypeConst.judge.approval.toString()) !== -1) {
+                                    const sms = new SMS(this.ctx);
+                                    const content = '【纵横计量支付】' + changeInfo.code + '变更需要您审批。';
+                                    sms.send(smsUser.auth_mobile, content);
+                                }
+                            }
+                        }
                     }
                     await this.transaction.insert(this.ctx.service.changeAudit.tableName, insertCA);
                 }
@@ -382,7 +397,7 @@ module.exports = app => {
                     },
                 };
                 if (change_status) {
-                    cArray.status = 2;
+                    cArray.status = audit.flow.status.checking;
                     cArray.cin_time = Date.parse(new Date()) / 1000;
                 }
                 await this.transaction.update(this.tableName, cArray, options);
@@ -398,9 +413,10 @@ module.exports = app => {
         /**
          * 审批通过
          * @param {int} postData - 表单提交的数据
+         * @param {int} changeData - 变更令的数据
          * @return {void}
          */
-        async approvalSuccess(postData) {
+        async approvalSuccess(postData, changeData) {
             // 初始化事务
             this.transaction = await this.db.beginTransaction();
             let result = false;
@@ -409,12 +425,12 @@ module.exports = app => {
                 const audit_update = {
                     id: postData.audit_id,
                     sdesc: postData.sdesc,
-                    status: 3,
+                    status: audit.flow.auditStatus.checked,
                     sin_time: new Date(),
                 };
                 const change_update = {
                     w_code: postData.w_code,
-                    status: 2,
+                    status: audit.flow.status.checking,
                     cin_time: Date.parse(new Date()) / 1000,
                 };
                 await this.transaction.update(this.ctx.service.changeAudit.tableName, audit_update);
@@ -442,17 +458,40 @@ module.exports = app => {
                 }
                 if (postData.audit_next_id === undefined) {
                     // 变更令审批完成
-                    change_update.status = 3;
+                    change_update.status = audit.flow.status.checked;
                     change_update.p_code = postData.p_code;
                     change_update.sin_time = Date.parse(new Date()) / 1000;
                     change_update.total_price = total_price;
+
+                    // 添加短信通知-审批通过提醒功能
+                    const smsUser = await this.ctx.service.projectAccount.getDataById(changeData.uid);
+                    if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                        const smsType = JSON.parse(smsUser.sms_type);
+                        if (smsType[smsTypeConst.const.BG] !== undefined && smsType[smsTypeConst.const.BG].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
+                            const sms = new SMS(this.ctx);
+                            const content = '【纵横计量支付】' + changeData.code + '变更,审批通过。';
+                            sms.send(smsUser.auth_mobile, content);
+                        }
+                    }
                 } else {
                     // 设置下一个审批人为审批状态
                     const nextAudit_update = {
                         id: postData.audit_next_id,
-                        status: 2,
+                        status: audit.flow.auditStatus.checking,
                     };
                     await this.transaction.update(this.ctx.service.changeAudit.tableName, nextAudit_update);
+
+                    // 添加短信通知-需要审批提醒功能
+                    const nextAuditData = await this.ctx.service.changeAudit.getDataById(postData.audit_next_id);
+                    const smsUser = await this.ctx.service.projectAccount.getDataById(nextAuditData.uid);
+                    if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                        const smsType = JSON.parse(smsUser.sms_type);
+                        if (smsType[smsTypeConst.const.BG] !== undefined && smsType[smsTypeConst.const.BG].indexOf(smsTypeConst.judge.approval.toString()) !== -1) {
+                            const sms = new SMS(this.ctx);
+                            const content = '【纵横计量支付】' + changeData.code + '变更需要您审批。';
+                            sms.send(smsUser.auth_mobile, content);
+                        }
+                    }
                 }
                 const options = {
                     where: {
@@ -511,9 +550,10 @@ module.exports = app => {
         /**
          * 审批退回到原报人
          * @param {int} postData - 表单提交的数据
+         * @param {int} changeData - 变更令的数据
          * @return {void}
          */
-        async approvalBack(postData) {
+        async approvalBack(postData, changeData) {
             // 初始化事务
             this.transaction = await this.db.beginTransaction();
             let result = false;
@@ -523,7 +563,7 @@ module.exports = app => {
                 const audit_update = {
                     id: postData.audit_id,
                     sdesc: postData.sdesc,
-                    status: 5,
+                    status: audit.flow.auditStatus.back,
                     sin_time: new Date(),
                 };
                 await this.transaction.update(this.ctx.service.changeAudit.tableName, audit_update);
@@ -545,7 +585,7 @@ module.exports = app => {
                         times: newTimes,
                         usite: al.usite,
                         usort,
-                        status: al.usite !== 0 ? 1 : 2,
+                        status: al.usite !== 0 ? audit.flow.auditStatus.uncheck : audit.flow.auditStatus.checking,
                     };
                     insert_audit_array.push(insert_audit);
                     usort++;
@@ -554,7 +594,7 @@ module.exports = app => {
                 // 设置变更令退回
                 const change_update = {
                     w_code: postData.w_code,
-                    status: 5,
+                    status: audit.flow.status.back,
                     times: newTimes,
                     cin_time: Date.parse(new Date()) / 1000,
                 };
@@ -566,6 +606,17 @@ module.exports = app => {
                 await this.transaction.update(this.tableName, change_update, options);
                 await this.transaction.commit();
                 result = true;
+
+                // 添加短信通知-审批退回提醒功能
+                const smsUser = await this.ctx.service.projectAccount.getDataById(changeData.uid);
+                if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                    const smsType = JSON.parse(smsUser.sms_type);
+                    if (smsType[smsTypeConst.const.BG] !== undefined && smsType[smsTypeConst.const.BG].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
+                        const sms = new SMS(this.ctx);
+                        const content = '【纵横计量支付】' + changeData.code + '变更,审批退回。';
+                        sms.send(smsUser.auth_mobile, content);
+                    }
+                }
             } catch (error) {
                 await this.transaction.rollback();
                 result = false;
@@ -576,9 +627,10 @@ module.exports = app => {
         /**
          * 审批退回到上一个审批人
          * @param {int} postData - 表单提交的数据
+         * @param {int} changeData - 变更令的数据
          * @return {void}
          */
-        async approvalBackNew(postData) {
+        async approvalBackNew(postData, changeData) {
             // 初始化事务
             this.transaction = await this.db.beginTransaction();
             let result = false;
@@ -588,7 +640,7 @@ module.exports = app => {
                 const audit_update = {
                     id: postData.audit_id,
                     sdesc: postData.sdesc,
-                    status: 6,
+                    status: audit.flow.auditStatus.backnew,
                     sin_time: new Date(),
                 };
                 await this.transaction.update(this.ctx.service.changeAudit.tableName, audit_update);
@@ -614,7 +666,7 @@ module.exports = app => {
                     times: lastauditInfo.times,
                     usite: lastauditInfo.usite,
                     usort,
-                    status: 2,
+                    status: audit.flow.auditStatus.checking,
                 };
                 await this.transaction.insert(this.ctx.service.changeAudit.tableName, insert_audit1);
                 usort++;
@@ -629,7 +681,7 @@ module.exports = app => {
                     times: auditInfo.times,
                     usite: auditInfo.usite,
                     usort,
-                    status: 1,
+                    status: audit.flow.auditStatus.uncheck,
                 };
                 await this.transaction.insert(this.ctx.service.changeAudit.tableName, insert_audit2);
 
@@ -657,7 +709,7 @@ module.exports = app => {
                 // 设置变更令退回
                 const change_update = {
                     w_code: postData.w_code,
-                    status: 6,
+                    status: audit.flow.status.backnew,
                     cin_time: Date.parse(new Date()) / 1000,
                 };
                 const options = {
@@ -668,6 +720,17 @@ module.exports = app => {
                 await this.transaction.update(this.tableName, change_update, options);
                 await this.transaction.commit();
                 result = true;
+
+                // 添加短信通知-需要审批提醒功能
+                const smsUser = await this.ctx.service.projectAccount.getDataById(lastauditInfo.uid);
+                if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                    const smsType = JSON.parse(smsUser.sms_type);
+                    if (smsType[smsTypeConst.const.BG] !== undefined && smsType[smsTypeConst.const.BG].indexOf(smsTypeConst.judge.approval.toString()) !== -1) {
+                        const sms = new SMS(this.ctx);
+                        const content = '【纵横计量支付】' + changeData.code + '变更需要您审批。';
+                        sms.send(smsUser.auth_mobile, content);
+                    }
+                }
             } catch (error) {
                 await this.transaction.rollback();
                 result = false;
@@ -795,6 +858,102 @@ module.exports = app => {
             }
             return result;
         }
+
+        /**
+         * 重新审批变更令
+         * @param { string } cid - 查询的清单
+         * @return {Promise<*>} - 可用的变更令列表
+         */
+        async checkAgain(cid) {
+            // 初始化事务
+            this.transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                const changeInfo = await this.getDataByCondition({ cid });
+
+                // 获取终审
+                const auditInfo = (await this.ctx.service.changeAudit.getAllDataByCondition({ where: { cid }, orders: [['usort', 'desc']], limit: 1, offset: 0 }))[0];
+                let usort = auditInfo.usort + 1;
+
+                // 新增2个审批状态到审批列表中
+                const insert_audit1 = {
+                    tid: auditInfo.tid,
+                    cid: auditInfo.cid,
+                    uid: auditInfo.uid,
+                    name: auditInfo.name,
+                    jobs: auditInfo.jobs,
+                    company: auditInfo.company,
+                    times: auditInfo.times,
+                    usite: auditInfo.usite,
+                    usort,
+                    sin_time: new Date(),
+                    status: audit.flow.auditStatus.checkAgain,
+                };
+                await this.transaction.insert(this.ctx.service.changeAudit.tableName, insert_audit1);
+                usort++;
+                // 新增2个审批人到审批列表中
+                const insert_audit2 = {
+                    tid: auditInfo.tid,
+                    cid: auditInfo.cid,
+                    uid: auditInfo.uid,
+                    name: auditInfo.name,
+                    jobs: auditInfo.jobs,
+                    company: auditInfo.company,
+                    times: auditInfo.times,
+                    usite: auditInfo.usite,
+                    usort,
+                    status: audit.flow.auditStatus.checking,
+                };
+                await this.transaction.insert(this.ctx.service.changeAudit.tableName, insert_audit2);
+
+                // 审批列表数据也要回退
+                let total_price = 0;
+                const changeList = await this.ctx.service.changeAuditList.getAllDataByCondition({ where: { cid: changeInfo.cid } });
+                for (const cl of changeList) {
+                    const audit_amount = cl.audit_amount.split(',');
+                    audit_amount.splice(-1, 1);
+                    const list_update = {
+                        id: cl.id,
+                        audit_amount: audit_amount.join(','),
+                        samount: '',
+                    };
+                    total_price += this.ctx.helper.accMul(cl.unit_price, cl.camount);
+                    await this.transaction.update(this.ctx.service.changeAuditList.tableName, list_update);
+                }
+
+                // 设置变更令审批中
+                const change_update = {
+                    p_code: null,
+                    status: audit.flow.status.checking,
+                    cin_time: Date.parse(new Date()) / 1000,
+                    sin_time: null,
+                    total_price,
+                };
+                const options = {
+                    where: {
+                        cid: changeInfo.cid,
+                    },
+                };
+                await this.transaction.update(this.tableName, change_update, options);
+                await this.transaction.commit();
+                result = true;
+
+                // 添加短信通知-需要审批提醒功能
+                const smsUser = await this.ctx.service.projectAccount.getDataById(auditInfo.uid);
+                if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                    const smsType = JSON.parse(smsUser.sms_type);
+                    if (smsType[smsTypeConst.const.BG] !== undefined && smsType[smsTypeConst.const.BG].indexOf(smsTypeConst.judge.approval.toString()) !== -1) {
+                        const sms = new SMS(this.ctx);
+                        const content = '【纵横计量支付】' + changeInfo.code + '变更需要您审批。';
+                        sms.send(smsUser.auth_mobile, content);
+                    }
+                }
+            } catch (error) {
+                await this.transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
     }
     return Change;
 };

+ 0 - 4
app/service/ledger.js

@@ -1491,7 +1491,6 @@ module.exports = app => {
          * @return {Object} - 提价后的数据(其中新增粘贴数据,只返回第一层)
          */
         async pasteBlock(tenderId, selectId, block) {
-            console.log(1);
             if ((tenderId <= 0) || (selectId <= 0)) {
                 return [];
             }
@@ -1516,7 +1515,6 @@ module.exports = app => {
                 throw '复制数据错误:仅可操作同层节点';
             }
             const orgParentPath = copyNodes[0].full_path.replace(copyNodes[0].ledger_id, '');
-            console.log(2);
 
             const newIds = [];
             this.transaction = await this.db.beginTransaction();
@@ -1579,7 +1577,6 @@ module.exports = app => {
                 await this.transaction.rollback();
                 throw err;
             }
-            console.log(3);
 
             // 查询应返回的结果
             const order = [];
@@ -1593,7 +1590,6 @@ module.exports = app => {
                 ledger: { create: createData, update: updateData },
                 pos: posData,
             };
-            console.log(4);
         }
 
         /**

+ 52 - 1
app/service/ledger_audit.js

@@ -9,6 +9,8 @@
  */
 
 const auditConst = require('../const/audit').ledger;
+const smsTypeConst = require('../const/sms_type');
+const SMS = require('../lib/sms');
 
 module.exports = app => {
     class LedgerAudit extends app.BaseService {
@@ -183,8 +185,21 @@ module.exports = app => {
             try {
                 await transaction.update(this.tableName, {id: audit.id, status: auditConst.status.checking, begin_time: new Date()});
                 await transaction.update(this.ctx.service.tender.tableName, {id: tenderId, ledger_status: auditConst.status.checking});
+
+                // 添加短信通知-需要审批提醒功能
+                const smsUser = await this.ctx.service.projectAccount.getDataById(audit.audit_id);
+                if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                    const smsType = JSON.parse(smsUser.sms_type);
+                    if (smsType[smsTypeConst.const.TZ] !== undefined && smsType[smsTypeConst.const.TZ].indexOf(smsTypeConst.judge.approval.toString()) !== -1) {
+                        const tenderInfo = await this.ctx.service.tender.getDataById(tenderId);
+                        const sms = new SMS(this.ctx);
+                        const content = '【纵横计量支付】' + tenderInfo.name + '台帐需要您审批。';
+                        sms.send(smsUser.auth_mobile, content);
+                    }
+                }
+
                 await transaction.commit();
-            } catch(err) {
+            } catch (err) {
                 await transaction.rollback();
                 throw err;
             }
@@ -234,11 +249,35 @@ module.exports = app => {
                     // 无下一审核人表示,审核结束
                     if (nextAudit) {
                         await transaction.update(this.tableName, {id: nextAudit.id, status: auditConst.status.checking, begin_time: time});
+
+                        // 添加短信通知-需要审批提醒功能
+                        const smsUser = await this.ctx.service.projectAccount.getDataById(nextAudit.audit_id);
+                        if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                            const smsType = JSON.parse(smsUser.sms_type);
+                            if (smsType[smsTypeConst.const.TZ] !== undefined && smsType[smsTypeConst.const.TZ].indexOf(smsTypeConst.judge.approval.toString()) !== -1) {
+                                const tenderInfo = await this.ctx.service.tender.getDataById(tenderId);
+                                const sms = new SMS(this.ctx);
+                                const content = '【纵横计量支付】' + tenderInfo.name + '台帐需要您审批。';
+                                sms.send(smsUser.auth_mobile, content);
+                            }
+                        }
                     } else {
                         // 同步标段信息
                         await transaction.update(this.ctx.service.tender.tableName, {id: tenderId, ledger_status: checkType});
                         // 拷贝备份台账数据
                         await this._copyHisytoryLedger(tenderId, transaction, times);
+
+                        // 添加短信通知-审批通过提醒功能
+                        const tenderInfo = await this.ctx.service.tender.getDataById(tenderId);
+                        const smsUser = await this.ctx.service.projectAccount.getDataById(tenderInfo.user_id);
+                        if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                            const smsType = JSON.parse(smsUser.sms_type);
+                            if (smsType[smsTypeConst.const.TZ] !== undefined && smsType[smsTypeConst.const.TZ].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
+                                const sms = new SMS(this.ctx);
+                                const content = '【纵横计量支付】' + tenderInfo.name + '台账审批通过。';
+                                sms.send(smsUser.auth_mobile, content);
+                            }
+                        }
                     }
                 } else {
                     // 同步标段信息
@@ -255,6 +294,18 @@ module.exports = app => {
                     await transaction.insert(this.tableName, auditors);
                     // 拷贝备份台账数据
                     await this._copyHisytoryLedger(tenderId, transaction, times);
+
+                    // 添加短信通知-审批退回提醒功能
+                    const tenderInfo = await this.ctx.service.tender.getDataById(tenderId);
+                    const smsUser = await this.ctx.service.projectAccount.getDataById(tenderInfo.user_id);
+                    if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                        const smsType = JSON.parse(smsUser.sms_type);
+                        if (smsType[smsTypeConst.const.TZ] !== undefined && smsType[smsTypeConst.const.TZ].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
+                            const sms = new SMS(this.ctx);
+                            const content = '【纵横计量支付】' + tenderInfo.name + '台账审批退回。';
+                            sms.send(smsUser.auth_mobile, content);
+                        }
+                    }
                 }
 
                 await transaction.commit();

+ 37 - 7
app/service/project_account.js

@@ -364,9 +364,11 @@ module.exports = app => {
             // 通过密码验证后修改数据
             const encryptNewPassword = crypto.createHmac('sha1', accountData.account).update(newPassword)
                 .digest().toString('base64');
-            const updateData = { password: encryptNewPassword };
-            const result = await this.save(updateData, accountId);
+            const updateData = { id: accountId, password: encryptNewPassword };
+            // const result = await this.save(updateData, accountId);
+            const operate = await this.db.update(this.tableName, updateData);
 
+            const result = operate.affectedRows > 0;
             return result;
         }
 
@@ -387,7 +389,7 @@ module.exports = app => {
             // 发送短信
             try {
                 const sms = new SMS(this.ctx);
-                const content = '【纵横计量支付】验证码:' + randString + ',15分钟有效。';
+                const content = '【纵横计量支付】验证码:' + randString + ',15分钟有效。';
                 result = await sms.send(mobile, content);
             } catch (error) {
                 result = false;
@@ -401,9 +403,10 @@ module.exports = app => {
          *
          * @param {Number} accountId - 账号id
          * @param {Object} data - post过来的数据
+         * @param {Object} pid - 项目id
          * @return {Boolean} - 绑定结果
          */
-        async bindMobile(accountId, data) {
+        async bindMobile(accountId, data, pid) {
             const cacheKey = 'smsCode:' + accountId;
             const cacheCode = await this.cache.get(cacheKey);
             if (cacheCode === null || data.code === undefined || cacheCode !== (data.code + data.auth_mobile)) {
@@ -411,14 +414,18 @@ module.exports = app => {
             }
 
             // 查找是否有重复的认证手机
-            const accountData = await this.getDataByCondition({ auth_mobile: data.auth_mobile });
+            const accountData = await this.getDataByCondition({ project_id: pid, auth_mobile: data.auth_mobile });
             if (accountData !== null) {
                 throw '已存在对应的手机';
             }
 
-            const updateData = { auth_mobile: data.auth_mobile };
+            const updateData = { id: accountId, auth_mobile: data.auth_mobile };
+
+            // return this.save(updateData, accountId);
+            const operate = await this.db.update(this.tableName, updateData);
 
-            return this.save(updateData, accountId);
+            const result = operate.affectedRows > 0;
+            return result;
         }
 
         /**
@@ -506,6 +513,29 @@ module.exports = app => {
 
             return result;
         }
+
+        /**
+         * 短信通知类型设置
+         *
+         * @param {String} id - 账号id
+         * @param {Number} data - 通知类型
+         * @return {Boolean} - 返回修改结果
+         */
+        async smsTypeSet(id, data) {
+            if (data._csrf !== undefined) {
+                delete data._csrf;
+            }
+            const updateData = {
+                id,
+                sms_type: JSON.stringify(data),
+            };
+
+            const operate = await this.db.update(this.tableName, updateData);
+
+            const result = operate.affectedRows > 0;
+
+            return result;
+        }
     }
 
     return ProjectAccount;

+ 55 - 1
app/service/stage.js

@@ -10,6 +10,8 @@
 
 const auditConst = require('../const/audit').stage;
 const payConst = require('../const/deal_pay.js');
+const fs = require('fs');
+const path = require('path');
 
 module.exports = app => {
     class Stage extends app.BaseService {
@@ -119,6 +121,8 @@ module.exports = app => {
                 const curAuditor = await this.ctx.service.stageAudit.getCurAuditor(stage.id, stage.times);
                 const isActive = curAuditor ? curAuditor.id === this.ctx.session.sessionUser.accountId : stage.user_id === this.ctx.session.sessionUser.accountId;
                 if (isActive) {
+                    stage.curTimes = stage.times;
+                    stage.curOrder = curAuditor ? curAuditor.order : 0;
                     const tpData = await this.ctx.service.stageBills.getSumTotalPrice(stage);
                     stage.contract_tp = tpData.contract_tp;
                     stage.qc_tp = tpData.qc_tp;
@@ -158,6 +162,10 @@ module.exports = app => {
                 user_id: this.ctx.session.sessionUser.accountId,
             };
             if (preStage) {
+                newStage.im_type = preStage.im_type;
+                newStage.im_pre = preStage.im_pre;
+                newStage.im_gather = preStage.im_gather;
+                newStage.im_gather_node = preStage.im_gather_node;
                 newStage.pre_contract_tp = this.ctx.helper.add(preStage.pre_contract_tp, preStage.contract_tp);
                 newStage.pre_qc_tp = this.ctx.helper.add(preStage.pre_qc_tp, preStage.qc_tp);
             }
@@ -275,7 +283,53 @@ module.exports = app => {
             const result = await this.db.update(this.tableName, {id: sid, check_detail: check});
             return result.affectedRows === 1;
         }
+
+        /**
+         * 删除计量期
+         *
+         * @param {Number} id - 期Id
+         * @returns {Promise<void>}
+         */
+        async deleteStage(id) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.delete(this.tableName, { id });
+                await transaction.delete(this.ctx.service.stageAudit.tableName, { sid: id });
+                await transaction.delete(this.ctx.service.stageBills.tableName, { sid: id });
+                await transaction.delete(this.ctx.service.stageChange.tableName, { sid: id });
+                await transaction.delete(this.ctx.service.stagePos.tableName, { sid: id });
+                await transaction.delete(this.ctx.service.stageDetail.tableName, { sid: id });
+                await transaction.delete(this.ctx.service.stagePosFinal.tableName, { sid: id });
+                await transaction.delete(this.ctx.service.stageBillsFinal.tableName, { sid: id });
+                // 删除计量合同支付附件
+                const payList = await this.ctx.service.stagePay.getAllDataByCondition({ where: { sid: id } });
+                if (payList) {
+                    for (const pt of payList) {
+                        if (pt.attachment !== null && pt.attachment !== '') {
+                            const payAttList = JSON.parse(pt.attachment);
+                            for (const pat of payAttList) {
+                                await fs.unlinkSync(path.join(this.app.baseDir, pat.filepath));
+                            }
+                        }
+                    }
+                }
+                await transaction.delete(this.ctx.service.stagePay.tableName, { sid: id });
+                // 删除计量附件文件
+                const attList = await this.ctx.service.stageAtt.getAllDataByCondition({ where: { sid: id } });
+                if (attList.length !== 0) {
+                    for (const att of attList) {
+                        await fs.unlinkSync(path.join(this.app.baseDir, att.filepath));
+                    }
+                }
+                await transaction.delete(this.ctx.service.StageAtt.tableName, { sid: id });
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
     }
 
     return Stage;
-};
+};

+ 141 - 5
app/service/stage_audit.js

@@ -9,6 +9,8 @@
  */
 
 const auditConst = require('../const/audit').stage;
+const smsTypeConst = require('../const/sms_type');
+const SMS = require('../lib/sms');
 
 module.exports = app => {
     class StageAudit extends app.BaseService {
@@ -191,8 +193,22 @@ module.exports = app => {
                 await transaction.update(this.ctx.service.stage.tableName, {
                     id: stageId, status: auditConst.status.checking,
                     contract_tp: tpData.contract_tp,
-                    qc_tp: tpData.qc_tp
+                    qc_tp: tpData.qc_tp,
                 });
+
+                // 添加短信通知-需要审批提醒功能
+                const smsUser = await this.ctx.service.projectAccount.getDataById(audit.aid);
+                if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                    const smsType = JSON.parse(smsUser.sms_type);
+                    if (smsType[smsTypeConst.const.JL] !== undefined && smsType[smsTypeConst.const.JL].indexOf(smsTypeConst.judge.approval.toString()) !== -1) {
+                        const tenderInfo = await this.ctx.service.tender.getDataById(audit.tid);
+                        const stageInfo = await this.ctx.service.stage.getDataById(audit.sid);
+                        const sms = new SMS(this.ctx);
+                        const content = '【纵横计量支付】' + tenderInfo.name + '第' + stageInfo.order + '期,需要您审批。';
+                        sms.send(smsUser.auth_mobile, content);
+                    }
+                }
+
                 // todo 更新标段tender状态 ?
                 await transaction.commit();
             } catch(err) {
@@ -229,6 +245,19 @@ module.exports = app => {
                         contract_tp: tpData.contract_tp,
                         qc_tp: tpData.qc_tp,
                     });
+
+                    // 添加短信通知-需要审批提醒功能
+                    const smsUser = await this.ctx.service.projectAccount.getDataById(nextAudit.aid);
+                    if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                        const smsType = JSON.parse(smsUser.sms_type);
+                        if (smsType[smsTypeConst.const.JL] !== undefined && smsType[smsTypeConst.const.JL].indexOf(smsTypeConst.judge.approval.toString()) !== -1) {
+                            const tenderInfo = await this.ctx.service.tender.getDataById(nextAudit.tid);
+                            const stageInfo = await this.ctx.service.stage.getDataById(nextAudit.sid);
+                            const sms = new SMS(this.ctx);
+                            const content = '【纵横计量支付】' + tenderInfo.name + '第' + stageInfo.order + '期,需要您审批。';
+                            sms.send(smsUser.auth_mobile, content);
+                        }
+                    }
                 } else {
                     // 本期结束
                     // 生成截止本期数据 final数据
@@ -240,6 +269,19 @@ module.exports = app => {
                         contract_tp: tpData.contract_tp,
                         qc_tp: tpData.qc_tp,
                     });
+
+                    // 添加短信通知-审批通过提醒功能
+                    const stageInfo = await this.ctx.service.stage.getDataById(stageId);
+                    const smsUser = await this.ctx.service.projectAccount.getDataById(stageInfo.user_id);
+                    if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                        const smsType = JSON.parse(smsUser.sms_type);
+                        if (smsType[smsTypeConst.const.JL] !== undefined && smsType[smsTypeConst.const.JL].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
+                            const tenderInfo = await this.ctx.service.tender.getDataById(stageInfo.tid);
+                            const sms = new SMS(this.ctx);
+                            const content = '【纵横计量支付】' + tenderInfo.name + '第' + stageInfo.order + '期,审批通过。';
+                            sms.send(smsUser.auth_mobile, content);
+                        }
+                    }
                 }
                 await transaction.commit();
             } catch (err) {
@@ -283,6 +325,20 @@ module.exports = app => {
                 await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
                 // 复制一份最新数据给原报
                 await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times + 1, 0, transaction);
+
+                // 添加短信通知-审批退回提醒功能
+                const stageInfo = await this.ctx.service.stage.getDataById(stageId);
+                const smsUser = await this.ctx.service.projectAccount.getDataById(stageInfo.user_id);
+                if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                    const smsType = JSON.parse(smsUser.sms_type);
+                    if (smsType[smsTypeConst.const.JL] !== undefined && smsType[smsTypeConst.const.JL].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
+                        const tenderInfo = await this.ctx.service.tender.getDataById(stageInfo.tid);
+                        const sms = new SMS(this.ctx);
+                        const content = '【纵横计量支付】' + tenderInfo.name + '第' + stageInfo.order + '期,审批退回。';
+                        sms.send(smsUser.auth_mobile, content);
+                    }
+                }
+
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
@@ -297,7 +353,13 @@ module.exports = app => {
             if (!audit || audit.order <= 1) {
                 throw '审核数据错误';
             }
-            const preAuditor = await this.getDataByCondition({sid: stageId, times: times, order: audit.order - 1});
+            // 添加重新审批后,不能用order-1,取groupby值里的上一个才对
+            // const preAuditor = await this.getDataByCondition({sid: stageId, times: times, order: audit.order - 1});
+            const auditors2 = await this.getAuditGroupByList(stageId, times);
+            const auditorIndex = await auditors2.findIndex(function(item) {
+                return item.aid === audit.aid;
+            });
+            const preAuditor = auditors2[auditorIndex - 1];
 
             const transaction = await this.db.beginTransaction();
             try {
@@ -312,19 +374,33 @@ module.exports = app => {
                 // 上一审批人,当前审批人 再次添加至流程
                 const newAuditors = [];
                 newAuditors.push({
-                    tid: preAuditor.tid, sid: preAuditor.sid, aid: preAuditor.aid,
-                    times: preAuditor.times, order: preAuditor.order + 2, status: auditConst.status.checking,
+                    tid: audit.tid, sid: audit.sid, aid: preAuditor.aid,
+                    times: audit.times, order: audit.order + 1, status: auditConst.status.checking,
                     begin_time: time,
                 });
                 newAuditors.push({
                     tid: audit.tid, sid: audit.sid, aid: audit.aid,
-                    times: audit.times, order: audit.order + 2, status: auditConst.status.uncheck
+                    times: audit.times, order: audit.order + 2, status: auditConst.status.uncheck,
                 });
                 await transaction.insert(this.tableName, newAuditors);
                 // 计算该审批人最终数据
                 await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
                 // 复制一份最新数据给下一人
                 await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, audit.order + 1, transaction);
+
+                // 添加短信通知-需要审批提醒功能
+                const smsUser = await this.ctx.service.projectAccount.getDataById(preAuditor.aid);
+                if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                    const smsType = JSON.parse(smsUser.sms_type);
+                    if (smsType[smsTypeConst.const.JL] !== undefined && smsType[smsTypeConst.const.JL].indexOf(smsTypeConst.judge.approval.toString()) !== -1) {
+                        const tenderInfo = await this.ctx.service.tender.getDataById(audit.tid);
+                        const stageInfo = await this.ctx.service.stage.getDataById(audit.sid);
+                        const sms = new SMS(this.ctx);
+                        const content = '【纵横计量支付】' + tenderInfo.name + '第' + stageInfo.order + '期,需要您审批。';
+                        sms.send(smsUser.auth_mobile, content);
+                    }
+                }
+
                 await transaction.commit();
             } catch(err) {
                 await transaction.rollback();
@@ -480,6 +556,66 @@ module.exports = app => {
         }
 
         /**
+         * 审批
+         * @param {Number} stageId - 标段id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<void>}
+         */
+        async checkAgain(stageId, times = 1) {
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const audit = (await this.getAllDataByCondition({ where: { sid: stageId, times }, orders: [['order', 'desc']], limit: 1, offset: 0 }))[0];
+            if (!audit || audit.order <= 1) {
+                throw '审核数据错误';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 当前审批人2次添加至流程中
+                const newAuditors = [];
+                newAuditors.push({
+                    tid: audit.tid, sid: audit.sid, aid: audit.aid,
+                    times: audit.times, order: audit.order + 1, status: auditConst.status.checkAgain,
+                    begin_time: time, end_time: time, opinion: '',
+                });
+                newAuditors.push({
+                    tid: audit.tid, sid: audit.sid, aid: audit.aid,
+                    times: audit.times, order: audit.order + 2, status: auditConst.status.checking,
+                    begin_time: time,
+                });
+                await transaction.insert(this.tableName, newAuditors);
+
+                // 复制一份最新数据给下一人
+                await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, audit.order + 2, transaction);
+
+                // 本期结束
+                // 生成截止本期数据 final数据
+                await this.ctx.service.stageBillsFinal.delGenerateFinalData(transaction, this.ctx.tender, this.ctx.stage);
+                await this.ctx.service.stagePosFinal.delGenerateFinalData(transaction, this.ctx.tender, this.ctx.stage);
+                // 同步 期信息
+                await transaction.update(this.ctx.service.stage.tableName, {
+                    id: stageId, status: auditConst.status.checking,
+                });
+
+                // 添加短信通知-需要审批提醒功能
+                const smsUser = await this.ctx.service.projectAccount.getDataById(audit.aid);
+                if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                    const smsType = JSON.parse(smsUser.sms_type);
+                    if (smsType[smsTypeConst.const.JL] !== undefined && smsType[smsTypeConst.const.JL].indexOf(smsTypeConst.judge.approval.toString()) !== -1) {
+                        const tenderInfo = await this.ctx.service.tender.getDataById(audit.tid);
+                        const stageInfo = await this.ctx.service.stage.getDataById(audit.sid);
+                        const sms = new SMS(this.ctx);
+                        const content = '【纵横计量支付】' + tenderInfo.name + '第' + stageInfo.order + '期,需要您审批。';
+                        sms.send(smsUser.auth_mobile, content);
+                    }
+                }
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
          * 获取审核人需要审核的期列表
          *
          * @param auditorId

+ 20 - 12
app/service/stage_bills.js

@@ -139,7 +139,7 @@ module.exports = app => {
             return await this.db.queryOne(sql, sqlParam);
         }
 
-        async _insertStageBillsData(transaction, insertData, ledgerData) {
+        async _insertStageBillsData(transaction, insertData, orgData, ledgerData) {
             const info = this.ctx.tender.info;
             const d = {
                 tid: this.ctx.tender.id,
@@ -149,6 +149,13 @@ module.exports = app => {
                 order: this.ctx.stage.curOrder,
                 said: this.ctx.session.sessionUser.accountId,
             };
+            if (orgData) {
+                d.contract_qty = orgData.contract_qty;
+                d.contract_tp = orgData.contract_tp;
+                d.qc_qty = orgData.qc_qty;
+                d.qc_tp = orgData.qc_tp;
+                d.postil = orgData.postil;
+            }
 
             const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, ledgerData.unit);
             if (insertData.contract_qty) {
@@ -187,13 +194,14 @@ module.exports = app => {
                         d.qc_qty = this.round(d.qc_qty, precision.value);
                         d.qc_tp = this.ctx.helper.mul(d.qc_qty, ledgerBills.unit_price, info.decimal.tp);
                     }
-                    if (!stageBills) {
-                        d.tid = this.ctx.tender.id;
-                        d.sid = this.ctx.stage.id;
-                        d.said = this.ctx.session.sessionUser.accountId;
-                        d.times = this.ctx.stage.curTimes;
-                        d.order = this.ctx.stage.curOrder;
-                        await transaction.insert(this.tableName, d);
+                    if (!stageBills || stageBills.times !== this.ctx.stage.curTimes || stageBills.order !== this.ctx.stage.curOrder) {
+                        await this._insertStageBillsData(transaction, d, stageBills, ledgerBills);
+                        // d.tid = this.ctx.tender.id;
+                        // d.sid = this.ctx.stage.id;
+                        // d.said = this.ctx.session.sessionUser.accountId;
+                        // d.times = this.ctx.stage.curTimes;
+                        // d.order = this.ctx.stage.curOrder;
+                        // await transaction.insert(this.tableName, d);
                     } else {
                         d.id = stageBills.id;
                         await transaction.update(this.tableName, d);
@@ -235,11 +243,11 @@ module.exports = app => {
                         updateData.id = stageBills.id;
                         await transaction.update(this.tableName, updateData);
                     } else {
-                        await this._insertStageBillsData(transaction, updateData, ledgerBills);
+                        await this._insertStageBillsData(transaction, updateData, stageBills, ledgerBills);
                     }
                 }
             } else {
-                await this._insertStageBillsData(transaction, updateData, ledgerBills);
+                await this._insertStageBillsData(transaction, updateData, stageBills, ledgerBills);
             }
         };
         /**
@@ -279,11 +287,11 @@ module.exports = app => {
                         posGather.id = stageBills.id;
                         await transaction.update(this.tableName, posGather);
                     } else {
-                        await this._insertStageBillsData(transaction, posGather, ledgerBills);
+                        await this._insertStageBillsData(transaction, posGather, stageBills, ledgerBills);
                     }
                 }
             } else {
-                await this._insertStageBillsData(transaction, posGather, ledgerBills);
+                await this._insertStageBillsData(transaction, posGather, stageBills, ledgerBills);
             }
         }
 

+ 14 - 0
app/service/stage_bills_final.js

@@ -106,6 +106,20 @@ module.exports = app => {
                 await transaction.query(sql, sqlParam);
             }
         }
+
+        /**
+         * 删除生成本期最终数据
+         * @param transaction - 所属事务
+         * @param {Object} tender - 标段
+         * @param {Object} stage - 本期
+         * @returns {Promise<void>}
+         */
+        async delGenerateFinalData(transaction, tender, stage) {
+            if (!transaction || !tender || !stage) {
+                throw '数据错误';
+            }
+            await transaction.delete(this.tableName, { tid: tender.id, sid: stage.id });
+        }
     }
 
     return StageBillsFinal;

+ 15 - 1
app/service/stage_pos_final.js

@@ -91,7 +91,21 @@ module.exports = app => {
                 await transaction.query(sql, sqlParam);
             }
         }
+
+        /**
+         * 删除生成本期最终数据
+         * @param transaction - 所属事务
+         * @param {Object} tender - 标段
+         * @param {Object} stage - 本期
+         * @returns {Promise<void>}
+         */
+        async delGenerateFinalData(transaction, tender, stage) {
+            if (!transaction || !tender || !stage) {
+                throw '数据错误';
+            }
+            await transaction.delete(this.tableName, { tid: tender.id, sid: stage.id });
+        }
     }
 
     return StagePosFinal;
-}
+}

+ 20 - 1
app/view/change/index.ejs

@@ -1,7 +1,8 @@
 <% include ../tender/tender_sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
-        <div class="title-main d-flex justify-content-between">
+        <div class="title-main d-flex">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
             <div>
                 <div class="d-inline-block">
                     <select class="form-control form-control-sm" id="status_select">
@@ -76,4 +77,22 @@
         </div>
     </div>
 </div>
+<script src="/public/js/sub_menu.js"></script>
+<script>
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        //key: 'change.memu.1.0.0',
+        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();
+        }
+    });
+</script>
 <script src="/public/js/change.js"></script>

+ 4 - 0
app/view/change/info.ejs

@@ -107,6 +107,10 @@
             <% } else if (auditStatus === 7) { %>
                 <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm btn-block">审批中</a>
             <% } %>
+            <% if (auditStatus === 4 && ctx.session.sessionUser.accountId === change.uid) { %>
+                <!--重新审批-->
+                <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-back" class="btn btn-warning btn-sm btn-block">重新审批</a>
+            <% } %>
         </div>
     </div>
 </div>

+ 25 - 0
app/view/change/info_modal.ejs

@@ -370,6 +370,8 @@
                                     <span class="text-danger pull-right">审批终止</span>
                                     <% } else if (al.usite !== 0 && (al.status === 5 || al.status === 6)) { %>
                                     <span class="text-warning pull-right">审批退回 <% if (al.status === 5) {%><%= auditList3[time][0].name %><% } %></span>
+                                    <% } else if (al.usite !== 0 && al.status === 7) { %>
+                                        <span class="text-warning pull-right">重新审批</span>
                                     <% } %>
                                     <h5 class="card-title">
                                         <% if (al.usite === 0 && al.status === 2 ) { %>
@@ -384,6 +386,8 @@
                                         <i class="fa <% if (aindex+1 === auditList3[time].length) { %>fa-stop-circle<% } else { %>fa-chevron-circle-down<% } %> text-danger"></i>
                                         <% } else if (al.status === 5 || al.status === 6) { %>
                                         <i class="fa <% if (aindex+1 === auditList3[time].length) { %>fa-stop-circle<% } else { %>fa-chevron-circle-down<% } %> text-warning"></i>
+                                        <% } else if (al.status === 7) { %>
+                                        <i class="fa fa-chevron-circle-down text-warning"></i>
                                         <% } %>
                                         <%= al.name %>&nbsp;<small class="text-muted"><%= al.jobs %></small>
                                     </h5>
@@ -585,3 +589,24 @@
         </div>
     </div>
 </div>
+<% if (auditStatus === 4 && ctx.session.sessionUser.accountId === change.uid) { %>
+<!--重新审批-->
+<div class="modal fade" id="sp-down-back" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <form class="modal-content" method="post" action="/change/check/again">
+            <div class="modal-header">
+                <h5 class="modal-title">重新审批</h5>
+            </div>
+            <div class="modal-body">
+                <h5>确认由「终审-<%= auditList[auditList.length-1].name %>」重新审批「<%= change.code %>」?</h5>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" name="cid" value="<%= change.cid %>">
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="submit" class="btn btn-warning">确定重审</button>
+            </div>
+        </form>
+    </div>
+</div>
+<% } %>

+ 12 - 4
app/view/ledger/audit.ejs

@@ -1,7 +1,8 @@
 <% include ../tender/tender_sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
-        <div class="title-main d-flex justify-content-between">
+        <div class="title-main d-flex">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
             <div class="d-inline-block">
                 <div class="dropdown">
                     <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@@ -19,7 +20,7 @@
                 </div>
             </div>
             <div></div>
-            <div>
+            <div class="ml-auto">
                 <% if (tender.ledger_status === auditConst.status.checkNo) { %>
                     <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm pull-right text-dark">退回意见</a>
                 <% } else if (tender.ledger_status === auditConst.status.checking) { %>
@@ -52,6 +53,8 @@
                             <li class="nav-item">
                                 <a class="nav-link active" href="javascript:void(0)">部位明细</a>
                             </li>
+                            <li class="nav-item" id="pos-search">
+                            </li>
                         </ul>
                     </div>
                     <div class="sp-wrap" id="pos-spread">
@@ -64,10 +67,12 @@
                 <div class="resize-x" id="right-spr" r-Type="width" div1="#left-view" div2="#right-view" title="调整大小" a-type="percent"><!--调整左右高度条--></div>
                 <div class="tab-content">
                     <div id="deal-bills" class="tab-pane">
-                        <div class="sjs-bar-1"></div>
-                        <div class="sjs-sh-1" id="deal-bills-spread">
+                        <div class="sjs-bar-2"></div>
+                        <div class="sjs-sh-2" id="deal-bills-spread">
                         </div>
                     </div>
+                    <div id="search" class="tab-pane">
+                    </div>
                 </div>
             </div>
         </div>
@@ -78,6 +83,9 @@
                 <li class="nav-item">
                     <a class="nav-link" href="javascript: void(0);" role="tab" content="#deal-bills">签约清单</a>
                 </li>
+                <li class="nav-item">
+                    <a class="nav-link" href="javascript: void(0);" role="tab" content="#search">查找定位</a>
+                </li>
             </ul>
         </div>
     </div>

+ 23 - 26
app/view/ledger/explode.ejs

@@ -1,7 +1,8 @@
 <% include ../tender/tender_sub_menu.ejs %>
 <div class="panel-content">
-    <div class="panel-title">
-        <div class="title-main  d-flex"><!--工具-->
+    <div class="panel-title"><!--收起详解目录添加类名 fluid -->
+        <div class="title-main d-flex"><!--工具-->
+            <% include ../tender/tender_sub_mini_menu.ejs %>
             <div>
                 <div class="d-inline-block">
                     <div class="dropdown">
@@ -73,17 +74,7 @@
                             <li class="nav-item">
                                 <a class="nav-link active" href="javascript:void(0)">部位明细</a>
                             </li>
-                            <li class="nav-item">
-                                <div class="mt-1 ml-2">
-                                    <div class="input-group input-group-sm">
-                                        <input type="text" class="form-control" placeholder="输入名称查找" id="pos-keyword">
-                                        <div class="input-group-append" id="button-addon3">
-                                            <button class="btn btn-outline-secondary" type="button" title="上一个" id="search-pre-pos"><i class="fa fa-angle-double-left"></i></button>
-                                            <button class="btn btn-outline-secondary" type="button" title="下一个" id="search-next-pos"><i class="fa fa-angle-double-right"></i></button>
-                                        </div>
-                                        <div class="small" id="pos-search-hint" style="vertical-align: bottom; display: none"></div>
-                                    </div>
-                                </div>
+                            <li class="nav-item" id="pos-search">
                             </li>
                         </ul>
                     </div>
@@ -96,34 +87,40 @@
                 <div class="resize-x" id="right-spr" r-Type="width" div1="#left-view" div2="#right-view" title="调整大小" a-type="percent"><!--调整左右高度条--></div>
                 <div class="tab-content">
                     <div id="search" class="tab-pane">
-                        <div class="sjs-bar-1">
-                            <div class="input-group input-group-sm">
-                                <input id="searchKeyword" type="text" class="form-control" placeholder="可查找 项目节编号/清单编号/名称" aria-label="Recipient's username" aria-describedby="button-addon2">
-                                <div class="input-group-append">
-                                    <button class="btn btn-outline-secondary" type="button" id="searchLedger">搜索</button>
-                                </div>
-                            </div>
-                        </div>
-                        <div id="search-result" class="sjs-sh-1">
-                        </div>
+                        <!--<div class="sjs-bar-1">-->
+                            <!--<div class="input-group input-group-sm">-->
+                                <!--<input id="searchKeyword" type="text" class="form-control" placeholder="可查找 项目节编号/清单编号/名称" aria-label="Recipient's username" aria-describedby="button-addon2">-->
+                                <!--<div class="input-group-append">-->
+                                    <!--<button class="btn btn-outline-secondary" type="button" id="searchLedger">搜索</button>-->
+                                <!--</div>-->
+                            <!--</div>-->
+                        <!--</div>-->
+                        <!--<div id="search-result" class="sjs-sh-1">-->
+                        <!--</div>-->
                     </div>
                     <div id="std-chapter" class="tab-pane">
                         <div class="sjs-bar-2">
-                            <select class="form-control form-control-sm"><option>0号计量台帐部位参考(项目节)</option></select>
+                            <div class="pb-1">
+                                <select class="form-control form-control-sm"><option>0号计量台帐部位参考(项目节)</option></select>
+                            </div>
                         </div>
                         <div id="std-chapter-spread" class="sjs-sh-2">
                         </div>
                     </div>
                     <div id="std-bills" class="tab-pane">
                         <div class="sjs-bar-3">
-                            <select class="form-control form-control-sm"><option>0号计量台帐清单参考(工程量清单)</option></select>
+                            <div class="pb-1">
+                                <select class="form-control form-control-sm"><option>0号计量台帐清单参考(工程量清单)</option></select>
+                            </div>
                         </div>
                         <div id="std-bills-spread" class="sjs-sh-3">
                         </div>
                     </div>
                     <div id="deal-bills" class="tab-pane">
                         <div class="sjs-bar-4">
-                            <a href="#upload-deal" data-toggle="modal" data-target="#upload-deal" class="btn btn-sm btn-primary">上传签约清单</a>
+                            <div class="pb-1">
+                                <a href="#upload-deal" data-toggle="modal" data-target="#upload-deal" class="btn btn-sm btn-primary">上传签约清单</a>
+                            </div>
                         </div>
                         <div id="deal-bills-spread" class="sjs-sh-4">
                         </div>

+ 3 - 1
app/view/ledger/explode_modal.ejs

@@ -8,7 +8,9 @@
             <div class="modal-body">
                 <p>请上传符合格式的 <b>0号台帐</b> 格式的 .xls和.xlsx 文件,<a id="downloadLedgerTemplate" href="/tender/<%- ctx.tender.id %>/ledger/download/template">下载示例</a>。</p>
                 <div class="form-group">
-                    <label for="exampleFormControlFile1">选择文件</label>
+                    <div>
+                        <label for="exampleFormControlFile1">选择文件</label><i class="fa fa-spinner fa-pulse fa-lg fa-fw text-primary" id="select-excel-loading" style="display: none;"></i>
+                    </div>
                     <input type="file" class="form-control-file" id="upload-ledger-file" accept="*.xls">
                 </div>
                 <div id="upload-ledger-sheets" style="display: none;">

+ 25 - 5
app/view/measure/compare.ejs

@@ -1,11 +1,30 @@
 <% include ../tender/tender_sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
-        <div class="title-main d-flex justify-content-between">
-            <h2>期审核比较</h2>
+        <div class="title-main d-flex">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
             <div>
-                <button href="#cate-set" class="btn btn-sm btn-light" data-toggle="modal" data-target="#select-qi"><i class="fa fa-clone"></i> 选择比较期</button>
+                <div class="d-inline-block">
+                    <div class="dropdown">
+                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                            <i class="fa fa-list-ol"></i> 显示层级
+                        </button>
+                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                            <a class="dropdown-item" name="showLevel" tag="1" href="javascirpt: void(0);">第一层</a>
+                            <a class="dropdown-item" name="showLevel" tag="2" href="javascirpt: void(0);">第二层</a>
+                            <a class="dropdown-item" name="showLevel" tag="3" href="javascirpt: void(0);">第三层</a>
+                            <a class="dropdown-item" name="showLevel" tag="4" href="javascirpt: void(0);">第四层</a>
+                            <a class="dropdown-item" name="showLevel" tag="5" href="javascirpt: void(0);">第五层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascirpt: void(0);">最底层</a>
+                            <a class="dropdown-item" name="showLevel" tag="leafXmj" href="javascirpt: void(0);">只显示项目节</a>
+                        </div>
+                    </div>
+                </div>
+                <div class="d-inline-block">
+                    <button href="#cate-set" class="btn btn-sm btn-light text-primary" data-toggle="modal" data-target="#select-qi"><i class="fa fa-clone"></i> 选择比较期</button>
+                </div>
             </div>
+            <div class="ml-auto"></div>
         </div>
     </div>
     <div class="content-wrap">
@@ -13,7 +32,8 @@
         <div class="c-body">
             <div class="sjs-height-1" id="bills-spread">
             </div>
-            <div class="bcontent-wrap">
+            <div class="bcontent-wrap" id="main-bottom">
+                <div id="main-resize" class="resize-y"  r-Type="height" div1="#bills-spread" div2="#main-bottom" store-id="compare-main" store-version="1.0.0" min="100"></div>
                 <div class="bc-bar mb-1">
                     <ul class="nav nav-tabs">
                         <li class="nav-item">
@@ -23,7 +43,7 @@
                 </div>
                 <div class="tab-content">
                     <div class="tab-pane active" id="xmujie">
-                        <div class="sp-wrap" id="bills-spread2">
+                        <div class="sp-wrap" id="pos-spread">
                         </div>
                     </div>
                 </div>

+ 4 - 4
app/view/measure/compare_modal.ejs

@@ -8,14 +8,14 @@
             <div class="modal-body">
                 <table class="table table-sm">
                     <tr><th>期</th><th width="90">选择</th></tr>
-                    <tr><td>1期</td><td><input type="checkbox"></td></tr>
-                    <tr><td>2期</td><td><input type="checkbox"></td></tr>
-                    <tr><td>3期</td><td><input type="checkbox"></td></tr>
+                    <% for (const s of stages) { %>
+                    <tr stage-id="<%- s.id %>"><td><%- s.order %>期</td><td><input type="checkbox"></td></tr>
+                    <% } %>
                 </table>
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-primary" >确认</button>
+                <button type="button" class="btn btn-primary" id="select-qi-ok">确认</button>
             </div>
         </div>
     </div>

+ 28 - 9
app/view/measure/stage.ejs

@@ -1,7 +1,8 @@
 <% include ../tender/tender_sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
-        <div class="title-main d-flex justify-content-between">
+        <div class="title-main d-flex">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
             <h2>
                 期列表
             </h2>
@@ -25,8 +26,8 @@
                     <th class="text-center">本期完成计量</th>
                     <th class="text-center">截止上期完成计量</th>
                     <th class="text-center">截止本期完成计量</th>
-                    <th class="text-center">审批状态</th>
                     <th class="text-center">审批进度</th>
+                    <th class="text-center">操作</th>
                 </tr>
                 </thead>
                 <tbody>
@@ -44,6 +45,12 @@
                     <td class="text-right"><%- (s.tp ? s.tp : '')%></td>
                     <td class="text-right"><%- (s.pre_tp ? s.pre_tp : '')%></td>
                     <td class="text-right"><%- (s.end_tp ? s.end_tp : '')%></td>
+                    <td class="<%- auditConst.auditProgressClass[s.status] %>">
+                        <% if (s.curAuditor) { %>
+                            <a href="#sp-list" data-toggle="modal" data-target="#sp-list" s-order="<%- s.order %>"><%- s.curAuditor.name %><%if (s.curAuditor.role !== '' && s.curAuditor.role !== null) { %>-<%- s.curAuditor.role %><% } %></a>
+                        <% } %>
+                        <%- auditConst.auditProgress[s.status] %>
+                    </td>
                     <td class="text-center">
                     <% if (s.status === auditConst.status.uncheck && s.user_id === ctx.session.sessionUser.accountId) { %>
                         <a href="<%- '/tender/' + ctx.tender.id + '/measure/stage/' + s.order %>" class="btn <%- auditConst.statusButtonClass[s.status] %> btn-sm"><%- auditConst.statusButton[s.status] %></a>
@@ -56,13 +63,9 @@
                     <% } else { %>
                         <span class="<%- auditConst.auditStringClass[s.status] %>"><%- auditConst.auditString[s.status] %></span>
                     <% } %>
-                    </td>
-
-                    <td class="<%- auditConst.auditProgressClass[s.status] %>">
-                        <% if (s.curAuditor) { %>
-                        <a href="#sp-list" data-toggle="modal" data-target="#sp-list" s-order="<%- s.order %>"><%- s.curAuditor.name %><%if (s.curAuditor.role !== '' && s.curAuditor.role !== null) { %>-<%- s.curAuditor.role %><% } %></a>
-                        <% } %>
-                        <%- auditConst.auditProgress[s.status] %>
+                    <% if (s.user_id === ctx.session.sessionUser.accountId && s.order === stages.length) { %>
+                        <a href="#del-qi" class="btn btn-outline-danger btn-sm ml-1" data-toggle="modal" data-target="#del-qi">删除</a>
+                    <% } %>
                     </td>
                 </tr>
                 <% } %>
@@ -71,6 +74,22 @@
         </div>
     </div>
 </div>
+<script src="/public/js/sub_menu.js"></script>
 <script>
     const stages = JSON.parse('<%- JSON.stringify(stages) %>');
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        //key: 'measure.memu.1.0.0',
+        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();
+        }
+    });
 </script>

+ 22 - 0
app/view/measure/stage_modal.ejs

@@ -30,6 +30,28 @@
     </div>
 </div>
 <% } %>
+<% if (stages && stages.length >= 1) { %>
+<!--删除期-->
+<div class="modal fade" id="del-qi" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <form class="modal-content" action="<%- preUrl + '/measure/stage/delete' %>" method="post">
+            <div class="modal-header">
+                <h5 class="modal-title">删除期</h5>
+            </div>
+            <div class="modal-body">
+                <h5>确认删除「第<%= stages.length %>期」?</h5>
+                <h5>删除后,数据无法恢复,请谨慎操作。</h5>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" name="stage_id" value="<%= stages[stages.length-1].id %>">
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <button type="submit" class="btn btn-danger">确定删除</button>
+            </div>
+        </form>
+    </div>
+</div>
+<% } %>
 <!--审批流程/结果-->
 <div class="modal fade" id="sp-list" data-backdrop="static">
     <div class="modal-dialog modal-lg" role="document">

+ 16 - 0
app/view/profile/modal.ejs

@@ -0,0 +1,16 @@
+<!--短信图示-->
+<div class="modal fade" id="sms-view" >
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">短信示例</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                图片
+            </div>
+        </div>
+    </div>
+</div>

+ 5 - 6
app/view/profile/sign.ejs

@@ -31,15 +31,17 @@
                             <div><img src="/profile/qrCode" width="150"></div>
                             <small class="form-text text-danger">微信扫码使用在线手写程序</small>
                         </div>
-                        <button type="submit" class="btn btn-danger">移除签名</button>
+                        <button type="button" class="btn btn-danger" id="delete-sign">移除签名</button>
                         <div class="form-group">
                             <label>签名图预览</label>
                             <div>
                                 <div class="position-relative">
                                     <img src="/public/images/baobiao3.png">
-                                    <div class="position-absolute fixed-top" style="left:290px;top:320px">
-                                        <img src="/public/images/user-sign.png" width="90">
+                                    <% if (accountData.sign_path !== '') { %>
+                                    <div class="position-absolute fixed-top" id="sign-show" style="left:290px;top:320px">
+                                        <img src="/public/upload/sign/<%= accountData.sign_path %>" width="90">
                                     </div>
+                                    <% } %>
                                 </div>
                             </div>
                         </div>
@@ -50,9 +52,6 @@
     </div>
 </div>
 <script type="text/javascript">
-    new Vue({
-        el: '#app',
-    });
     const csrf = '<%= ctx.csrf %>';
 </script>
 <script type="text/javascript" src="/public/js/profile.js"></script>

+ 45 - 13
app/view/profile/sms.ejs

@@ -9,36 +9,68 @@
         <div class="c-body">
             <div class="row m-0">
                 <div class="col-5 my-3">
+                    <% if (accountData.auth_mobile !== '') { %>
+                    <!--已绑定手机-->
+                    <div class="form-group">
+                        <label>已认证手机(用于 找回密码、接收通知)</label>
+                        <div class="input-group mb-3">
+                            <input class="form-control" readonly="" value="<%= accountData.auth_mobile %>">
+                            <div class="input-group-append">
+                                <button class="btn btn-outline-secondary" id="change-mobile">修改手机</button>
+                            </div>
+                        </div>
+                    </div>
+                    <% } %>
                     <!--绑定手机-->
-                    <form id="mobile-form" method="post" action="/profile/bind">
+                    <form id="mobile-form" method="post" action="/profile/bind" <% if (accountData.auth_mobile !== '') { %>style="display: none" <% } %>>
                         <div class="form-group">
                             <label>认证手机(用于 找回密码、接收通知)</label>
-                            <input class="form-control" placeholder="输入11位手机号码" value="<%= accountData.auth_mobile %>"
-                                   <% if(accountData.auth_mobile !== '') { %>disabled="disabled"<% } %> name="auth_mobile"
-                                    maxlength="11"/>
-                        </div>
-                        <% if (accountData.auth_mobile === '') { %>
-                        <div class="form-group">
                             <div class="input-group mb-3">
-                                <input class="form-control" readonly="readonly" name="code"/>
-                                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
+                                <input class="form-control" placeholder="输入11位手机号码" value="" name="auth_mobile" maxlength="11"/>
                                 <div class="input-group-append">
                                     <button class="btn btn-outline-secondary" type="button" id="get-code">获取验证码</button>
                                 </div>
                             </div>
                         </div>
+                        <div class="form-group">
+                            <div class="input-group mb-3">
+                                <input class="form-control" type="text" readonly="readonly" name="code" placeholder="输入短信中的6位验证码" />
+                                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
+                            </div>
+                        </div>
                         <button type="submit" class="btn btn-secondary disabled" id="bind-btn">确认绑定</button>
-                        <% } %>
                     </form>
+                    <% if (accountData.auth_mobile !== '') { %>
+                    <!--短信通知开关(已有认证手机后显示)-->
+                    <div class="mt-5">
+                        <h4>通知类型</h4>
+                        <p class="text-muted">勾选您需要接收的短信类型。</p>
+                        <form id="sms-form" method="post" action="/profile/sms/type">
+                            <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
+                            <% const user_smsType = accountData.sms_type !== '' ? JSON.parse(accountData.sms_type) : null; %>
+                            <% for (const s in smsType) { %>
+                            <div class="form-group row">
+                                <label class="col-auto col-form-label"><%= smsType[s].name %><a href="#sms-view" data-toggle="modal" data-target="#sms-view" class="ml-2"><i class="fa fa-info-circle"></i></a></label>
+                                <div class="col-5">
+                                    <% for (const c of smsType[s].children) { %>
+                                    <div class="form-check ">
+                                        <input class="form-check-input" type="checkbox" name="<%= s %>[]" value="<%= c.value %>" <% if (user_smsType !== null && user_smsType[s] !== undefined && user_smsType[s].indexOf(c.value.toString()) !== -1) { %>checked<% } %>>
+                                        <label class="form-check-label" for="<%= s %>"><%= c.title %></label>
+                                    </div>
+                                    <% } %>
+                                </div>
+                            </div>
+                            <% } %>
+                            <button type="submit" class="btn btn-primary">确认修改</button>
+                        </form>
+                    </div>
+                    <% } %>
                 </div>
             </div>
         </div>
     </div>
 </div>
 <script type="text/javascript">
-    new Vue({
-        el: '#app',
-    });
     const csrf = '<%= ctx.csrf %>';
 </script>
 <script type="text/javascript" src="/public/js/profile.js"></script>

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

@@ -1,7 +1,8 @@
 <% include ../tender/tender_sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
-        <div class="title-main d-flex justify-content-between">
+        <div class="title-main d-flex">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
             <div>
                 <div class="d-inline-block">
                     <div class="dropdown">
@@ -128,8 +129,29 @@
         </div>
     </div>
 </div>
+<<<<<<< HEAD
 <!--1弹出纸张页边距-->
 <%include ./rpt_margins.html %>
+=======
+<script src="/public/js/sub_menu.js"></script>
+<script>
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        //key: 'report.memu.1.0.0',
+        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();
+        }
+    });
+</script>
+>>>>>>> 7f7a3213d57fc78c74b4fbde9bb82f387fc7ac03
 
 <script type="text/javascript">  autoFlashHeight();</script>
 <!-- zTree -->

+ 22 - 3
app/view/revise/index.ejs

@@ -1,8 +1,9 @@
 <% include ../tender/tender_sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
-        <div class="title-main  d-flex justify-content-between"><!--工具-->
-            <div>
+        <div class="title-main d-flex"><!--工具-->
+            <% include ../tender/tender_sub_mini_menu.ejs %>
+            <div class="fluid">
                 <div class="d-inline-block">
                     <div class="input-group input-group-sm">
                         <input class="datepicker-here form-control mt-0" placeholder="按时间筛选" data-range="true" data-multiple-dates-separator=" - " data-language="zh" type="text" style="width:190px">
@@ -70,4 +71,22 @@
             <% include ../layout/page.ejs %>
         </div>
     </div>
-</div>
+</div>
+<script src="/public/js/sub_menu.js"></script>
+<script>
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        //key: 'revise.memu.1.0.0',
+        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();
+        }
+    });
+</script>

+ 22 - 22
app/view/revise/info.ejs

@@ -2,6 +2,7 @@
 <div class="panel-content">
     <div class="panel-title">
         <div class="title-main  d-flex">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
             <!--工具-->
             <% if ((revise.status === audit.status.uncheck || revise.status === audit.status.checkNo) && revise.uid === ctx.session.sessionUser.accountId) { %>
             <div>
@@ -21,7 +22,6 @@
                         </div>
                     </div>
                 </div>
-                <!--编制人工具-->
                 <div class="d-inline-block">
                     <a href="javascript: void(0);" name="base-opr" type="add" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="插入"><i class="fa fa-plus" aria-hidden="true"></i></a>
                     <a href="javascript: void(0);" name="base-opr" type="delete" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>
@@ -43,7 +43,7 @@
                 </div>
             </div>
             <% } %>
-            <div>
+            <div class="ml-auto">
                 <% if (revise.status === audit.status.uncheck) { %>
                 <a href="#sub-sp" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm pull-right">上报审批</a>
                 <% } else if (revise.status === audit.status.checkNo) { %>
@@ -76,18 +76,9 @@
                     <div class="bc-bar mb-1">
                         <ul class="nav nav-tabs">
                             <li class="nav-item">
-                                <a class="nav-link active" href="#">部位明细</a>
+                                <a class="nav-link active" href="javascript:void(0)">部位明细</a>
                             </li>
-                            <li class="nav-item">
-                                <div class="mt-1 ml-2">
-                                    <div class="input-group input-group-sm">
-                                        <input type="text" class="form-control" placeholder="输入名称查找">
-                                        <div class="input-group-append" id="button-addon3">
-                                            <button class="btn btn-outline-secondary" type="button" title="上一个"><i class="fa fa-angle-double-left"></i></button>
-                                            <button class="btn btn-outline-secondary" type="button" title="下一个"><i class="fa fa-angle-double-right"></i></button>
-                                        </div>
-                                    </div>
-                                </div>
+                            <li class="nav-item" id="pos-search">
                             </li>
                         </ul>
                     </div>
@@ -100,10 +91,10 @@
                 <div class="resize-x" id="revise-right-spr" r-Type="width" div1="#left-view" div2="#right-view" title="调整大小" a-type="percent"><!--调整左右高度条--></div>
                 <div class="tab-content">
                     <div id="xd-content" class="tab-pane">
-                        <div class="sjs-bar-1">
+                        <div class="sjs-bar-2">
                             <div class="d-flex"><a href="javascirpt: void(0);" class="btn btn-sm btn-outline-success mb-1 ml-auto" id="save">保存</a></div>
                         </div>
-                        <div class="sjs-sh-1" style="overflow:auto">
+                        <div class="sjs-sh-2" style="overflow:auto">
                             <div class="form-group mt-2">
                                 <label >创建时间</label>
                                 <input type="" class="form-control" value="<%- revise.in_time.toLocaleString() %>" disabled>
@@ -118,22 +109,28 @@
                             </div>
                         </div>
                     </div>
+                    <div id="search" class="tab-pane">
+                    </div>
                     <div id="std-chapter" class="tab-pane">
-                        <div class="sjs-bar-2">
-                            <select class="form-control form-control-sm"><option>0号计量台帐部位参考(项目节)</option></select>
+                        <div class="sjs-bar-3">
+                            <div class="pb-1">
+                                <select class="form-control form-control-sm"><option>0号计量台帐部位参考(项目节)</option></select>
+                            </div>
                         </div>
-                        <div id="std-chapter-spread" class="sjs-sh-2">
+                        <div id="std-chapter-spread" class="sjs-sh-3">
                         </div>
                     </div>
                     <div id="std-bills" class="tab-pane">
-                        <div class="sjs-bar-3">
-                            <select class="form-control form-control-sm"><option>0号计量台帐部位参考(项目节)</option></select>
+                        <div class="sjs-bar-4">
+                            <div class="pb-1">
+                                <select class="form-control form-control-sm"><option>0号计量台帐部位参考(工程量清單)</option></select>
+                            </div>
                         </div>
-                        <div id="std-bills-spread" class="sjs-sh-3">
+                        <div id="std-bills-spread" class="sjs-sh-4">
                         </div>
                     </div>
                     <div id="deal-bills" class="tab-pane">
-                        <div id="deal-bills-spread" class="sjs-sh-4">
+                        <div id="deal-bills-spread" class="sjs-sh-5">
                         </div>
                     </div>
                 </div>
@@ -147,6 +144,9 @@
                     <a class="nav-link" content="#xd-content" href="javascript: void(0);">修订详情</a>
                 </li>
                 <li class="nav-item">
+                    <a class="nav-link" content="#search" href="javascript: void(0);">查找定位</a>
+                </li>
+                <li class="nav-item">
                     <a class="nav-link" content="#std-chapter" href="javascript: void(0);">项目节</a>
                 </li>
                 <li class="nav-item">

+ 4 - 5
app/view/sign/info.ejs

@@ -44,7 +44,7 @@
         canvas {
             flex: 1;
             cursor: crosshair;
-            border:2px dashed lightgray;
+            border:2px dashed #007bff;
         }
         .image-box {
             width: 100%;
@@ -70,8 +70,8 @@
         <div id="canvasBox" :style="getHorizontalStyle" v-show="!showBox">
             <div class="greet">
                 <span>{{msg}}</span>
-                <input type="button" value="清" @touchstart="clear" @mousedown="clear"/>
-                <input type="button" value="生成png图片" @touchstart="savePNG" @mousedown="savePNG"/>
+                <input type="button" value="清" @touchstart="clear" @mousedown="clear"/>
+                <input type="button" value="下一步 生成签名图" @touchstart="savePNG" @mousedown="savePNG"/>
             </div>
             <canvas></canvas>
         </div>
@@ -92,13 +92,12 @@
 <script>
     const id = '<%- id %>';
     const name = '<%- name %>';
-    const role = '<%- role %>';
     const csrf = '<%= ctx.csrf %>';
     new Vue({
         el: '#app',
         data() {
             return {
-                msg: '你好,' + name + (role !== '' ? '-' + role : '') + '请在下方空白处签名',
+                msg: '您好,' + name + ',请在虚线框内手写您的签名。',
                 degree: 90, // 屏幕整体旋转的角度, 可取 -90,90,180等值
                 signImage: null,
                 showBox: false,

+ 7 - 4
app/view/stage/audit_btn.ejs

@@ -1,11 +1,11 @@
 <div class="contarl-box">
     <% if (ctx.stage.status === auditConst.status.uncheck) { %>
         <% if (ctx.session.sessionUser.accountId === ctx.stage.user_id) {%>
-            <a href="javascript: void(0);" target="#sub-sp" class="btn btn-primary btn-sm btn-block">上报审批</a>
+            <a href="javascript: void(0);" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm btn-block">上报审批</a>
         <% } %>
     <% } else if (ctx.stage.status === auditConst.status.checking) { %>
         <% if (ctx.stage.curAuditor && ctx.stage.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
-            <a href="javascript: void(0);" target="#sp-done" class="btn btn-success btn-sm btn-block">审批通过</a>
+            <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-done" class="btn btn-success btn-sm btn-block">审批通过</a>
             <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm btn-block">审批退回</a>
         <% } else { %>
             <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm btn-block">审批中</a>
@@ -15,13 +15,16 @@
     <% } else if (ctx.stage.status === auditConst.status.checkNo) { %>
         <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm btn-block text-muted">审批退回</a>
         <% if (ctx.session.sessionUser.accountId === ctx.stage.user_id) { %>
-            <a href="javascript: void(0);" target="#sp-list2" class="btn btn-primary btn-sm btn-block">重新上报</a>
+            <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-list2" class="btn btn-primary btn-sm btn-block">重新上报</a>
         <% } %>
     <% } else if (ctx.stage.status === auditConst.status.checkNoPre) { %>
         <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm btn-block text-muted">审批退回</a>
         <% if (ctx.session.sessionUser.accountId === ctx.stage.curAuditor.aid) { %>
-            <a href="javascript: void(0);" target="#sp-done" class="btn btn-success btn-sm btn-block">审批通过</a>
+            <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-done" class="btn btn-success btn-sm btn-block">审批通过</a>
             <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm btn-block">审批退回</a>
         <% } %>
     <% } %>
+    <% if (ctx.stage.user_id === ctx.session.sessionUser.accountId && ctx.stage.status === auditConst.status.checked && ctx.stage.order === ctx.stage.highOrder) { %>
+        <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-back" class="btn btn-warning btn-sm btn-block">重新审批</a>
+    <% } %>
 </div>

+ 66 - 27
app/view/stage/audit_modal.ejs

@@ -81,7 +81,9 @@
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <% if (ctx.url !== '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.stage.order + '/detail') { %>
                 <a href="/tender/<%- ctx.tender.id %>/measure/stage/<%- ctx.stage.order %>/detail" class="btn btn-primary">进入中间计量</a>
+                <% } %>
             </div>
         </div>
     </div>
@@ -142,6 +144,11 @@
                                     <h5 class="card-title">
                                         <i class="<%- (iA < auditors.length - 1 ? 'fa fa-chevron-circle-down text-warning' : 'fa fa-stop-circle text-warning') %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small>
                                     </h5>
+                                    <% } else if (auditors[iA].status === auditConst.status.checkAgain) { %>
+                                    <span class="text-warning pull-right"><small><%- auditors[iA].end_time.toLocaleString() %></small> 重新审批</span>
+                                    <h5 class="card-title">
+                                        <i class="<%- (iA < auditors.length - 1 ? 'fa fa-chevron-circle-down text-warning' : 'fa fa-stop-circle text-warning') %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small>
+                                    </h5>
                                     <% } else { %>
                                     <h5 class="card-title">
                                         <i class="<%- (iA < auditors.length - 1 ? 'fa fa-chevron-circle-down' : 'fa fa-stop-circle') %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small>
@@ -223,6 +230,11 @@
                                     <h5 class="card-title">
                                         <i class="<%- (iA < auditors.length - 1 ? 'fa fa-chevron-circle-down text-warning' : 'fa fa-stop-circle text-warning') %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small>
                                     </h5>
+                                    <% } else if (auditors[iA].status === auditConst.status.checkAgain) { %>
+                                    <span class="text-warning pull-right"><small><%- auditors[iA].end_time.toLocaleString() %></small> 重新审批</span>
+                                    <h5 class="card-title">
+                                        <i class="<%- (iA < auditors.length - 1 ? 'fa fa-chevron-circle-down text-warning' : 'fa fa-stop-circle text-warning') %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small>
+                                    </h5>
                                     <% } else { %>
                                     <h5 class="card-title">
                                         <i class="<%- (iA < auditors.length - 1 ? 'fa fa-chevron-circle-down' : 'fa fa-stop-circle') %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small>
@@ -241,9 +253,10 @@
                                             <label class="form-check-label" for="inlineRadio1">退回上报 <%- ctx.stage.user.name %></label>
                                         </div>
                                         <% if (auditors[iA].order > 1 && auditors[iA].aid !== auditors[0].aid) { %>
+                                        <% const auditorIndex = ctx.stage.auditors2.findIndex(function (item) { return item.aid === auditors[iA].aid }) %>
                                         <div class="form-check form-check-inline">
                                             <input class="form-check-input" type="radio" name="checkType" id="inlineRadio2" value="<%- auditConst.status.checkNoPre %>" checked>
-                                            <label class="form-check-label" for="inlineRadio2">退回上一审批人 <%- auditors[iA-1].name %></label>
+                                            <label class="form-check-label" for="inlineRadio2">退回上一审批人 <%- ctx.stage.auditors2[auditorIndex-1].name %></label>
                                         </div>
                                         <% } %>
                                     </div>
@@ -275,7 +288,9 @@
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <% if (ctx.url !== '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.stage.order + '/detail') { %>
                 <a href="/tender/<%- ctx.tender.id %>/measure/stage/<%- ctx.stage.order %>/detail" class="btn btn-primary">进入中间计量</a>
+                <% } %>
             </div>
         </div>
     </div>
@@ -324,7 +339,7 @@
                                     <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa <%if (iA === ah.length - 1) { %>fa-stop-circle<% } else { %>fa-chevron-circle-down<% } %> <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
-                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre || ah[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- ah[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -335,7 +350,7 @@
                                     <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa fa-stop-circle <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
-                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre || ah[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- ah[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -346,7 +361,7 @@
                                     <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa fa-chevron-circle-down <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
-                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre || ah[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- ah[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -372,7 +387,7 @@
                                     <span class="<%- auditConst.statusClass[auditors[iA].status] %> pull-right"><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa <%if (iA === auditors.length - 1) { %>fa-stop-circle<% } else { %>fa-chevron-circle-down<% } %> <%- auditConst.statusClass[auditors[iA].status] %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small></h5>
-                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre || auditors[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- auditors[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- auditors[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -383,7 +398,7 @@
                                     <span class="<%- auditConst.statusClass[auditors[iA].status] %> pull-right"><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa fa-stop-circle <%- auditConst.statusClass[auditors[iA].status] %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small></h5>
-                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre || auditors[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- auditors[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- auditors[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -394,7 +409,7 @@
                                     <span class="<%- auditConst.statusClass[auditors[iA].status] %> pull-right"><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa fa-chevron-circle-down <%- auditConst.statusClass[auditors[iA].status] %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small></h5>
-                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre || auditors[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- auditors[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- auditors[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -458,7 +473,7 @@
                                     <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa <%if (iA === ah.length - 1) { %>fa-stop-circle<% } else { %>fa-chevron-circle-down<% } %> <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
-                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre || ah[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- ah[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -469,7 +484,7 @@
                                     <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa fa-stop-circle <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
-                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre || ah[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- ah[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -480,7 +495,7 @@
                                     <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa fa-chevron-circle-down <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
-                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre || ah[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- ah[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -506,7 +521,7 @@
                                     <span class="<%- auditConst.statusClass[auditors[iA].status] %> pull-right"><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa <%if (iA === auditors.length - 1) { %>fa-stop-circle<% } else { %>fa-chevron-circle-down<% } %> <%- auditConst.statusClass[auditors[iA].status] %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small></h5>
-                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre || auditors[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- auditors[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- auditors[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -517,7 +532,7 @@
                                     <span class="<%- auditConst.statusClass[auditors[iA].status] %> pull-right"><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa fa-stop-circle <%- auditConst.statusClass[auditors[iA].status] %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small></h5>
-                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre || auditors[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- auditors[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- auditors[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -528,7 +543,7 @@
                                     <span class="<%- auditConst.statusClass[auditors[iA].status] %> pull-right"><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa fa-chevron-circle-down <%- auditConst.statusClass[auditors[iA].status] %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small></h5>
-                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre || auditors[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- auditors[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- auditors[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -591,7 +606,7 @@
                                     <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa <%if (iA === ah.length - 1) { %>fa-stop-circle<% } else { %>fa-chevron-circle-down<% } %> <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
-                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre || ah[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- ah[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -602,7 +617,7 @@
                                     <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa fa-stop-circle <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
-                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre || ah[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- ah[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -613,7 +628,7 @@
                                     <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa fa-chevron-circle-down <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
-                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre || ah[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- ah[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -680,7 +695,7 @@
                                                         <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                                     <% } %>
                                                     <h5 class="card-title"><i class="fa <%if (iA === ah.length - 1) { %>fa-stop-circle<% } else { %>fa-chevron-circle-down<% } %> <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
-                                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) { %>
+                                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre || ah[iA].status === auditConst.status.checkAgain) { %>
                                                         <p class="card-text mb-1"><%- ah[iA].opinion %></p>
                                                         <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
                                                     <% } %>
@@ -691,7 +706,7 @@
                                                         <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                                     <% } %>
                                                     <h5 class="card-title"><i class="fa fa-stop-circle <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
-                                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) { %>
+                                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre || ah[iA].status === auditConst.status.checkAgain) { %>
                                                         <p class="card-text mb-1"><%- ah[iA].opinion %></p>
                                                         <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
                                                     <% } %>
@@ -702,7 +717,7 @@
                                                         <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                                     <% } %>
                                                     <h5 class="card-title"><i class="fa fa-chevron-circle-down <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
-                                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) { %>
+                                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre || ah[iA].status === auditConst.status.checkAgain) { %>
                                                         <p class="card-text mb-1"><%- ah[iA].opinion %></p>
                                                         <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
                                                     <% } %>
@@ -760,7 +775,9 @@
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
-                <a href="/tender/<%- ctx.tender.id %>/measure/stage/<% ctx.stage.order %>/detail" class="btn btn-primary">进入中间计量</a>
+                <% if (ctx.url !== '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.stage.order + '/detail') { %>
+                <a href="/tender/<%- ctx.tender.id %>/measure/stage/<%- ctx.stage.order %>/detail" class="btn btn-primary">进入中间计量</a>
+                <% } %>
             </div>
         </div>
     </div>
@@ -810,7 +827,7 @@
                                     <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa <%if (iA === ah.length - 1) { %>fa-stop-circle<% } else { %>fa-chevron-circle-down<% } %> <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
-                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre || ah[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- ah[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -821,7 +838,7 @@
                                     <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa fa-stop-circle <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
-                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre || ah[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- ah[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -832,7 +849,7 @@
                                     <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa fa-chevron-circle-down <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
-                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre || ah[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- ah[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -858,7 +875,7 @@
                                     <span class="<%- auditConst.statusClass[auditors[iA].status] %> pull-right"><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa <%if (iA === auditors.length - 1) { %>fa-stop-circle<% } else { %>fa-chevron-circle-down<% } %> <%- auditConst.statusClass[auditors[iA].status] %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small></h5>
-                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre || auditors[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- auditors[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- auditors[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -869,7 +886,7 @@
                                     <span class="<%- auditConst.statusClass[auditors[iA].status] %> pull-right"><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa fa-stop-circle <%- auditConst.statusClass[auditors[iA].status] %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small></h5>
-                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre || auditors[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- auditors[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- auditors[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -880,7 +897,7 @@
                                     <span class="<%- auditConst.statusClass[auditors[iA].status] %> pull-right"><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- ctx.stage.user.name %><% } %></span>
                                     <% } %>
                                     <h5 class="card-title"><i class="fa fa-chevron-circle-down <%- auditConst.statusClass[auditors[iA].status] %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small></h5>
-                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre) { %>
+                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo || auditors[iA].status === auditConst.status.checkNoPre || auditors[iA].status === auditConst.status.checkAgain) { %>
                                     <p class="card-text mb-1"><%- auditors[iA].opinion %></p>
                                     <p class="card-text"><small class="text-muted"><%- auditors[iA].end_time.toLocaleDateString() %></small></p>
                                     <% } %>
@@ -1051,9 +1068,10 @@
                                             <label class="form-check-label" for="inlineRadio1">退回上报 <%- ctx.stage.user.name %></label>
                                         </div>
                                         <% if (auditors[iA].order > 1 && auditors[iA].aid !== auditors[0].aid) { %>
+                                        <% const auditorIndex = ctx.stage.auditors2.findIndex(function (item) { return item.aid === auditors[iA].aid }) %>
                                         <div class="form-check form-check-inline">
                                             <input class="form-check-input" type="radio" name="checkType" id="inlineRadio2" value="<%- auditConst.status.checkNoPre %>" checked>
-                                            <label class="form-check-label" for="inlineRadio2">退回上一审批人 <%- auditors[iA-1].name %></label>
+                                            <label class="form-check-label" for="inlineRadio2">退回上一审批人 <%- ctx.stage.auditors2[auditorIndex-1].name %></label>
                                         </div>
                                         <% } %>
                                     </div>
@@ -1085,10 +1103,31 @@
                 </div>
                 <div class="modal-footer">
                     <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                    <% if (ctx.url !== '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.stage.order + '/detail') { %>
                     <a href="/tender/<%- ctx.tender.id %>/measure/stage/<%- ctx.stage.order %>/detail" class="btn btn-primary">进入中间计量</a>
+                    <% } %>
                 </div>
             </div>
         </div>
     </div>
     <% } %>
 <% } %>
+<% if (ctx.stage.user_id === ctx.session.sessionUser.accountId && ctx.stage.status === auditConst.status.checked && ctx.stage.order === ctx.stage.highOrder) { %>
+    <!--上报审批 需要完成中间计量-->
+    <div class="modal fade" id="sp-down-back" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">重新审批</h5>
+                </div>
+                <div class="modal-body">
+                    <h5>确认由「终审-<%= ctx.stage.auditors[ctx.stage.auditors.length-1].name %>」重新审批「第<%= ctx.stage.order %>期」?</h5>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                    <a href="<%- preUrl %>/audit/check/again" class="btn btn-warning">确定重审</a>
+                </div>
+            </div>
+        </div>
+    </div>
+<% } %>

+ 4 - 9
app/view/stage/change.ejs

@@ -1,7 +1,8 @@
 <% include ./stage_sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
-        <div class="title-main d-flex justify-content-between">
+        <div class="title-main d-flex">
+            <% include ./stage_sub_mini_menu.ejs %>
             <div>
                 <a class="btn btn-sm btn-light">
                     <div class="custom-control custom-checkbox">
@@ -135,9 +136,8 @@
                                     <label>费用承担方</label>
                                     <div class="radio">
                                         <% for (const c in changeConst.charge) { %>
-                                        <% const cCharge = changeConst.charge[c] %>
                                         <label class="radio-inline">
-                                            <input value="<%- cCharge.value %>" name="charge" type="radio" disabled> <%- cCharge.name %>
+                                            <input value="<%- changeConst.charge[c].value %>" name="charge" type="radio" disabled> <%- changeConst.charge[c].name %>
                                         </label>
                                         <% } %>
                                     </div>
@@ -163,9 +163,4 @@
         </div>
     </div>
 </div>
-<img src="/public/images/icon-ok.png" id="icon-ok" />
-<script>
-    const changes = JSON.parse('<%- JSON.stringify(changes) %>');
-    const usedChangesId = JSON.parse('<%- JSON.stringify(usedChangesId) %>')
-    const ledger = JSON.parse('<%- JSON.stringify(ledger) %>');
-</script>
+<img src="/public/images/icon-ok.png" id="icon-ok" />

+ 33 - 29
app/view/stage/compare.ejs

@@ -1,33 +1,38 @@
 <% include ./stage_sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
-        <div class="title-main d-flex justify-content-between">
-            <div class="d-inline-block">
-                <div class="dropdown">
-                    <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                        <i class="fa fa-list-ol"></i> 显示层级
-                    </button>
-                    <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
-                        <a class="dropdown-item" name="showLevel" tag="1" href="javascirpt: void(0);">第一层</a>
-                        <a class="dropdown-item" name="showLevel" tag="2" href="javascirpt: void(0);">第二层</a>
-                        <a class="dropdown-item" name="showLevel" tag="3" href="javascirpt: void(0);">第三层</a>
-                        <a class="dropdown-item" name="showLevel" tag="4" href="javascirpt: void(0);">第四层</a>
-                        <a class="dropdown-item" name="showLevel" tag="5" href="javascirpt: void(0);">第五层</a>
-                        <a class="dropdown-item" name="showLevel" tag="last" href="javascirpt: void(0);">最底层</a>
-                        <a class="dropdown-item" name="showLevel" tag="leafXmj" href="javascirpt: void(0);">只显示项目节</a>
-                    </div>
-                </div>
-            </div>
+        <div class="title-main d-flex">
+            <% include ./stage_sub_mini_menu.ejs %>
             <div>
-                <a class="btn btn-sm btn-light">
-                    <div class="custom-control custom-checkbox">
-                        <input type="checkbox" class="custom-control-input" id="customCheckDisabled" checked="">
-                        <label class="custom-control-label text-primary" for="customCheckDisabled">显示本期计量</label>
+                <div class="d-inline-block">
+                    <div class="dropdown">
+                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                            <i class="fa fa-list-ol"></i> 显示层级
+                        </button>
+                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                            <a class="dropdown-item" name="showLevel" tag="1" href="javascirpt: void(0);">第一层</a>
+                            <a class="dropdown-item" name="showLevel" tag="2" href="javascirpt: void(0);">第二层</a>
+                            <a class="dropdown-item" name="showLevel" tag="3" href="javascirpt: void(0);">第三层</a>
+                            <a class="dropdown-item" name="showLevel" tag="4" href="javascirpt: void(0);">第四层</a>
+                            <a class="dropdown-item" name="showLevel" tag="5" href="javascirpt: void(0);">第五层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascirpt: void(0);">最底层</a>
+                            <a class="dropdown-item" name="showLevel" tag="leafXmj" href="javascirpt: void(0);">只显示项目节</a>
+                        </div>
                     </div>
-                </a>
-                <% if (ctx.stage.status !== auditConst.status.uncheck) { %>
-                <button href="#cate-set" class="btn btn-sm btn-light text-primary" data-toggle="modal" data-target="#select-qi"><i class="fa fa-users"></i> 选择比较人</button>
-                <% } %>
+                </div>
+                <div class="d-inline-block">
+                    <a class="btn btn-sm btn-light">
+                        <div class="custom-control custom-checkbox">
+                            <input type="checkbox" class="custom-control-input" id="customCheckDisabled" checked="">
+                            <label class="custom-control-label text-primary" for="customCheckDisabled">显示本期计量</label>
+                        </div>
+                    </a>
+                </div>
+                <div class="d-inline-block">
+                    <% if (ctx.stage.status !== auditConst.status.uncheck) { %>
+                    <button href="#cate-set" class="btn btn-sm btn-light text-primary" data-toggle="modal" data-target="#select-qi"><i class="fa fa-users"></i> 选择比较人</button>
+                    <% } %>
+                </div>
             </div>
         </div>
     </div>
@@ -60,8 +65,7 @@
     const stage = JSON.parse('<%- JSON.stringify(ctx.stage) %>');
     const ledgerSpreadSetting = JSON.parse('<%- JSON.stringify(ledgerSpread) %>');
     const posSpreadSetting = JSON.parse('<%- JSON.stringify(posSpread) %>');
-    const ledger = JSON.parse('<%- JSON.stringify(ledger) %>');
-    const orgStageLedger = JSON.parse('<%- JSON.stringify(orgStageLedger) %>');
-    const pos = JSON.parse('<%- JSON.stringify(pos) %>');
-    const orgStagePos = JSON.parse('<%- JSON.stringify(orgStagePos) %>');
+    const scCacheKey = 'stage-compare-role-' + stage.tid + '-' + stage.order;
+    let scRoles = getLocalCache(scCacheKey);
+    scRoles = scRoles ? scRoles.split(',') : [0];
 </script>

+ 2 - 2
app/view/stage/compare_modal.ejs

@@ -10,8 +10,8 @@
                 <table class="table table-sm">
                     <tr><th>审批人</th><th width="90">选择</th></tr>
                     <% for (const a of ctx.stage.auditors) { %>
-                    <% if (a.status === auditConst.status.checked) { %>
-                    <tr auditorId="<%- a.id %>"><td><%- a.name %></td><td><input type="checkbox"></td></tr>
+                    <% if (a.status === auditConst.status.checked || a.aid === ctx.session.sessionUser.accountId) { %>
+                    <tr auditorId="<%- a.aid %>"><td><%- a.name %></td><td><input type="checkbox"></td></tr>
                     <% } %>
                     <% } %>
                 </table>

+ 2 - 1
app/view/stage/deal.ejs

@@ -1,6 +1,7 @@
 <div class="panel-content">
     <div class="panel-title">
-        <div class="title-main d-flex justify-content-between">
+        <div class="title-main d-flex">
+            <% include ./stage_sub_mini_menu.ejs %>
             <div>
                 <div class="d-inline-block">
                     <a href="#" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="插入"><i class="fa fa-sign-in" aria-hidden="true"></i></a>

+ 9 - 4
app/view/stage/detail.ejs

@@ -1,7 +1,8 @@
 <% include ./stage_sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
-        <div class="title-main d-flex justify-content-between">
+        <div class="title-main d-flex">
+            <% include ./stage_sub_mini_menu.ejs %>
             <div>
                 <div class="d-inline-block">
                     <div class="form-group">
@@ -11,13 +12,17 @@
             </div>
             <div class="ml-auto">
                 <!--完成中间计量-->
-                <% if (!ctx.stage.readOnly) { %>
+                <% if (!ctx.stage.readOnly && ctx.stage.check_detail) { %>
                 <% if (ctx.stage.check_detail) { %>
                 <a href="javascirpt: void(0);" data-toggle="modal" data-target="#done" class="btn btn-success btn-sm pull-right">完成本期中间计量</a>
                 <% } else { %>
+                <% if (ctx.stage.user_id = ctx.session.sessionUser.accountId) {%>
+                <span class="text-success" id="locked-hint">请继续完成上报审批。如需修改计量,点击 <a href="/tender/<%- ctx.tender.id %>/measure/stage/<%- ctx.stage.order %>/detail/unlock">编辑</a></span>
+                <% } else { %>
                 <span class="text-success" id="locked-hint">已完成,请继续完成「上报审批」 <a href="/tender/<%- ctx.tender.id %>/measure/stage/<%- ctx.stage.order %>/detail/unlock">解锁编辑</a></span>
                 <% } %>
                 <% } %>
+                <% } %>
             </div>
         </div>
     </div>
@@ -49,8 +54,8 @@
                             <% if (!stage.readOnly && stage.check_detail) { %>
                             <div class="d-flex justify-content-end mt-1 mr-1">
                                 <a href="javascript: void(0);" class="btn btn-sm btn-outline-primary" id="edit-detail">编辑</a>
-                                <a href="javascript: void(0);" class="btn btn-sm btn-outline-success mr-1" id="save-detail">保存</a>
-                                <a href="javascript: void(0);" class="btn btn-sm btn-outline-secondary" id="cancel-detail">取消</a>
+                                <a href="javascript: void(0);" class="btn btn-sm btn-outline-success mr-1" id="save-detail" style="display: none;">保存</a>
+                                <a href="javascript: void(0);" class="btn btn-sm btn-outline-secondary" id="cancel-detail" style="display: none;">取消</a>
                             </div>
                             <% } %>
                             <div class="form-group">

+ 2 - 2
app/view/stage/detail_modal.ejs

@@ -35,7 +35,7 @@
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
-                <% if (!stage.readOnly) { %>
+                <% if (!stage.readOnly && stage.user_id === ctx.session.sessionUser.accountId) { %>
                 <button type="button" class="btn btn-primary" id="choose-ok">重新生成</button>
                 <% } %>
             </div>
@@ -68,7 +68,7 @@
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
-                <% if (!stage.readOnly) { %>
+                <% if (!stage.readOnly && stage.user_id === ctx.session.sessionUser.accountId) { %>
                 <button type="button" class="btn btn-primary" id="choose2-ok">确定</button>
                 <% } %>
             </div>

+ 4 - 3
app/view/stage/gather.ejs

@@ -1,7 +1,8 @@
-<% include ../tender/tender_sub_menu.ejs %>
+<% include ./stage_sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
-        <div class="title-main d-flex justify-content-between">
+        <div class="title-main d-flex">
+            <% include ./stage_sub_mini_menu.ejs %>
             <div>
                 <div class="dropdown">
                     <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@@ -87,4 +88,4 @@
     const pos = JSON.parse('<%- JSON.stringify(pos) %>');
     const curPosData = JSON.parse('<%- JSON.stringify(curPosData) %>');
     const dealBills = JSON.parse('<%- JSON.stringify(dealBills) %>');
-</script>
+</script>

+ 3 - 2
app/view/stage/index.ejs

@@ -2,6 +2,7 @@
 <div class="panel-content">
     <div class="panel-title">
         <div class="title-main d-flex">
+            <% include ./stage_sub_mini_menu.ejs %>
             <div>
                 <div class="d-inline-block">
                     <div class="dropdown">
@@ -74,7 +75,7 @@
                                                 </div>
                                             </div>
                                         </div>
-                                        <input type="text" class="form-control" placeholder="漏计中按名称查找" id="pos-search-keyword">
+                                        <input type="text" class="form-control" placeholder="按名称查找" id="pos-search-keyword">
                                         <!--搜索结果显示-->
                                         <div class="input-group-append" >
                                             <span class="input-group-text" id="pos-search-result">结果:0</span>
@@ -98,7 +99,7 @@
                     <!--查找定位-->
                     <div id="search" class="tab-pane tab-select-show">
                         <div class="sjs-bar-1">
-                            <div class="input-group input-group-sm">
+                            <div class="input-group input-group-sm pb-1">
                                 <div class="input-group-prepend">
                                     <div class="input-group-text">
                                         <input type="radio" name="searchType" id="over"> 超计

+ 2 - 7
app/view/stage/pay.ejs

@@ -1,7 +1,8 @@
 <% include ./stage_sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
-        <div class="title-main d-flex justify-content-between">
+        <div class="title-main d-flex">
+            <% include ./stage_sub_mini_menu.ejs %>
             <div>
                 <div class="d-inline-block">
                     <a href="javascript: void(0);" id="add" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="添加"><i class="fa fa-plus" aria-hidden="true"></i></a>
@@ -18,12 +19,6 @@
                     </div>
                 </div>
             </div>
-            <div class="ml-auto">
-                <!--完成中间计量-->
-                <% if (!ctx.stage.check_detail && !ctx.stage.readOnly) { %>
-                <span class="text-success" id="locked-hint">已完成,请继续完成「上报审批」 <a href="/tender/<%- ctx.tender.id %>/measure/stage/<%- ctx.stage.order %>/detail/unlock">解锁编辑</a></span>
-                <% } %>
-            </div>
         </div>
     </div>
     <div class="content-wrap">

+ 2 - 1
app/view/stage/stage_sub_menu.ejs

@@ -1,4 +1,4 @@
-<div class="panel-sidebar">
+<div class="panel-sidebar" id="sub-menu">
     <div class="panel-title">
         <div class="title-bar">
             <h2 class="text-truncate" style="white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" data-toggle="tooltip" data-placement="right" title=""  data-original-title="第<%- ctx.stage.order%>期 - <%- tender.name %>">第<%- ctx.stage.order%>期 - <%- tender.name %></h2>
@@ -56,5 +56,6 @@
                 </ul>
             </div>
         <% include ./audit_btn.ejs %>
+        <div class="side-fold"><a href="javascript: void(0)" data-toggle="tooltip" data-placement="top" data-original-title="折叠侧栏" id="to-mini-menu"><i class="fa fa-sign-out fa-flip-horizontal"></i></a></div>
     </div>
 </div>

+ 60 - 0
app/view/stage/stage_sub_mini_menu.ejs

@@ -0,0 +1,60 @@
+<!--折起的菜单-->
+<div class="min-side" id="sub-mini-menu" style="display: none;">
+    <div class="side-switch">
+        <i class="fa fa-bars"></i>
+    </div>
+    <div class="side-menu" id="mini-menu-list" style="display: none">
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li class=""><a href="/tender/<%- ctx.tender.id %>/measure/stage"><i class="fa fa-chevron-left "></i> <span>返回</span></a></li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.stage.order) { %>active<% } %>">
+                    <a href="/tender/<%- ctx.tender.id %>/measure/stage/<%- ctx.stage.order %>"><span class="ml-3">计量台帐</span></a>
+                </li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.stage.order + '/detail') { %>active<% } %>">
+                    <a href="/tender/<%- ctx.tender.id %>/measure/stage/<%- stage.order %>/detail"><span class="ml-3">中间计量</span></a>
+                    <span class=" position-absolute tips-dot" data-toggle="tooltip" data-placement="bottom" data-original-title="完成中间计量" id="check_point" <% if (!ctx.stage.check_detail) { %>style="display: none;"<% } %>>
+                            <i class="fa fa-circle text-danger"></i>
+                        </span>
+                </li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.stage.order + '/pay') { %>active<% } %>">
+                    <a href="/tender/<%- ctx.tender.id %>/measure/stage/<%- stage.order %>/pay"><span class="ml-3">合同支付</span></a>
+                </li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.stage.order + '/change') { %>active<% } %>">
+                    <a href="/tender/<%- ctx.tender.id %>/measure/stage/<%- stage.order %>/change"><span class="ml-3">变更概况</span></a>
+                </li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.stage.order + '/gather') { %>active<% } %>">
+                    <a href="/tender/<%- ctx.tender.id %>/measure/stage/<%- stage.order %>/gather"><span class="ml-3">清单汇总</span></a>
+                </li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.stage.order + '/compare') { %>active<% } %>">
+                    <a href="/tender/<%- ctx.tender.id %>/measure/stage/<%- stage.order %>/compare"><span class="ml-3">审核比较</span></a>
+                </li>
+            </ul>
+        </div>
+        <% include ./audit_btn.ejs %>
+        <div class="side-fold"><a href="javascript: void(0);" data-toggle="tooltip" data-placement="top" data-original-title="展开侧栏" id="to-menu"><i class="fa fa-thumb-tack"></i></a></div>
+    </div>
+</div>

+ 21 - 1
app/view/tender/detail.ejs

@@ -1,7 +1,8 @@
 <% include ./tender_sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
-        <div class="title-main  d-flex justify-content-between">
+        <div class="title-main d-flex">
+            <% include ./tender_sub_mini_menu.ejs %>
             <% if (tender.ledger_status !== audit.ledger.status.uncheck) { %>
             <h2>进行至
                 <% if (lastStage) { %>
@@ -529,6 +530,25 @@
         </div>
     </div>
 </div>
+
+<script src="/public/js/sub_menu.js"></script>
+<script>
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        //key: 'tender.memu.1.0.0',
+        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();
+        }
+    });
+</script>
 <script type="text/javascript">
     //4 标段期数计量进度//
     var myChart = echarts.init(document.getElementById('chartContainer4'));

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

@@ -1,4 +1,4 @@
-<div class="panel-sidebar">
+<div class="panel-sidebar" id="sub-menu">
     <div class="panel-title">
         <div class="title-bar">
             <h2 class="text-truncate" data-toggle="tooltip" data-placement="right" title=""  data-original-title="<%- tender.name %>"><%- tender.name %></h2>
@@ -22,5 +22,6 @@
             <% } %>
         </div>
         <% } %>
+        <div class="side-fold"><a href="javascript: void(0)" data-toggle="tooltip" data-placement="top" data-original-title="折叠侧栏" id="to-mini-menu"><i class="fa fa-sign-out fa-flip-horizontal"></i></a></div>
     </div>
 </div>

+ 39 - 0
app/view/tender/tender_sub_mini_menu.ejs

@@ -0,0 +1,39 @@
+<!--折起的菜单-->
+<div class="min-side" id="sub-mini-menu" style="display: none;">
+    <div class="side-switch">
+        <i class="fa fa-bars"></i>
+    </div>
+    <div class="side-menu" id="mini-menu-list" style="display: none">
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id) { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>"><i class="fa fa-pie-chart"></i> <span>标段概况</span></a></li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <h3><i class="fa fa-list-alt"></i> 0号台帐</h3>
+            <ul class="nav-list list-unstyled sub-list">
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/ledger/explode') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/ledger/explode"><span>台帐分解</span></a></li>
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/ledger/audit') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/ledger/audit"><span>台帐审批</span></a></li>
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/revise') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/revise"><span>台帐修订</span></a></li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <h3><i class="fa fa-calendar-check-o"></i> 计量台帐</h3>
+            <ul class="nav-list list-unstyled sub-list">
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/stage') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/measure/stage"><span>期列表</span></a></li>
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/compare') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/measure/compare"><span>多期比较</span></a></li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/change') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/change"><i class="fa fa-retweet"></i> <span>工程变更</span></a></li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/report') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/report"><i class="fa fa-file-text-o"></i> <span>报表</span></a></li>
+            </ul>
+        </div>
+        <div class="side-fold"><a href="javascript: void(0);" data-toggle="tooltip" data-placement="top" data-original-title="展开侧栏" id="to-menu"><i class="fa fa-thumb-tack"></i></a></div>
+    </div>
+</div>

+ 3 - 3
config/config.default.js

@@ -82,9 +82,9 @@ module.exports = appInfo => {
 
     // 发送短信相关
     config.sms = {
-        account: '710030',
-        password: 'w7pRhJ',
-        extno: '10690587',
+        // account: '710030',
+        // password: 'w7pRhJ',
+        // extno: '10690587',
         authKey: 'fb5ef483e44b9556512a9febef376051',
     };
 

+ 33 - 1
config/web.js

@@ -101,8 +101,10 @@ const JsFiles = {
                     "/public/js/decimal.min.js",
                 ],
                 mergeFiles: [
+                    "/public/js/sub_menu.js",
                     "/public/js/div_resizer.js",
                     "/public/js/spreadjs_rela/spreadjs_zh.js",
+                    "/public/js/ledger_search.js",
                     "/public/js/zh_calc.js",
                     "/public/js/path_tree.js",
                     "/public/js/ledger_tree_col.js",
@@ -116,8 +118,10 @@ const JsFiles = {
                     "/public/js/decimal.min.js",
                 ],
                 mergeFiles: [
+                    "/public/js/sub_menu.js",
                     "/public/js/div_resizer.js",
                     "/public/js/spreadjs_rela/spreadjs_zh.js",
+                    "/public/js/ledger_search.js",
                     "/public/js/zh_calc.js",
                     "/public/js/path_tree.js",
                     "/public/js/ledger_audit.js",
@@ -130,8 +134,10 @@ const JsFiles = {
                     "/public/js/decimal.min.js",
                 ],
                 mergeFiles: [
+                    "/public/js/sub_menu.js",
                     "/public/js/div_resizer.js",
                     "/public/js/spreadjs_rela/spreadjs_zh.js",
+                    "/public/js/ledger_search.js",
                     "/public/js/zh_calc.js",
                     "/public/js/path_tree.js",
                     "/public/js/revise.js",
@@ -148,6 +154,7 @@ const JsFiles = {
                     "/public/js/toastr.min.js",
                 ],
                 mergeFiles: [
+                    "/public/js/sub_menu.js",
                     "/public/js/div_resizer.js",
                     "/public/js/spreadjs_rela/spreadjs_zh.js",
                     "/public/js/zh_calc.js",
@@ -165,6 +172,7 @@ const JsFiles = {
                     "/public/js/html2canvas/canvas2image.js",
                 ],
                 mergeFiles: [
+                    "/public/js/sub_menu.js",
                     "/public/js/spreadjs_rela/spreadjs_zh.js",
                     "/public/js/zh_calc.js",
                     "/public/js/path_tree.js",
@@ -179,6 +187,7 @@ const JsFiles = {
                     "/public/js/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js",
                 ],
                 mergeFiles: [
+                    "/public/js/sub_menu.js",
                     "/public/js/spreadjs_rela/spreadjs_zh.js",
                     "/public/js/stage_pay.js",
                     "/public/js/stage_audit.js",
@@ -191,11 +200,13 @@ const JsFiles = {
                     "/public/js/decimal.min.js",
                 ],
                 mergeFiles: [
+                    "/public/js/sub_menu.js",
                     "/public/js/div_resizer.js",
                     "/public/js/spreadjs_rela/spreadjs_zh.js",
                     "/public/js/zh_calc.js",
                     "/public/js/path_tree.js",
                     "/public/js/stage_change.js",
+                    "/public/js/stage_audit.js",
                 ],
                 mergeFile: 'stage_change',
             },
@@ -205,12 +216,14 @@ const JsFiles = {
                     "/public/js/decimal.min.js",
                 ],
                 mergeFiles: [
+                    "/public/js/sub_menu.js",
                     "/public/js/div_resizer.js",
                     "/public/js/spreadjs_rela/spreadjs_zh.js",
                     "/public/js/zh_calc.js",
                     "/public/js/path_tree.js",
                     "/public/js/gcl_gather.js",
                     "/public/js/stage_gather.js",
+                    "/public/js/stage_audit.js",
                 ],
                 mergeFile: 'stage_gather',
             },
@@ -220,15 +233,34 @@ const JsFiles = {
                     "/public/js/decimal.min.js",
                 ],
                 mergeFiles: [
+                    "/public/js/sub_menu.js",
                     "/public/js/div_resizer.js",
                     "/public/js/spreadjs_rela/spreadjs_zh.js",
                     "/public/js/zh_calc.js",
                     "/public/js/path_tree.js",
                     "/public/js/stage_compare.js",
+                    "/public/js/stage_audit.js",
                 ],
                 mergeFile: 'stage_compare',
             }
-        }
+        },
+        measure: {
+            compare: {
+                files: [
+                    "/public/js/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js",
+                    "/public/js/decimal.min.js",
+                ],
+                mergeFiles: [
+                    "/public/js/sub_menu.js",
+                    "/public/js/div_resizer.js",
+                    "/public/js/spreadjs_rela/spreadjs_zh.js",
+                    "/public/js/zh_calc.js",
+                    "/public/js/path_tree.js",
+                    "/public/js/measure_compare.js"
+                ],
+                mergeFile: 'measure_compare',
+            }
+        },
     }
 
 };

File diff suppressed because it is too large
+ 2810 - 2800
package-lock.json


+ 1 - 0
package.json

@@ -4,6 +4,7 @@
   "description": "calculation paying frontend",
   "private": true,
   "dependencies": {
+    "ali-rds": "^3.3.0",
     "bignumber.js": "^8.1.1",
     "decimal.js": "^10.2.0",
     "egg": "^1.13.0",