Преглед на файлове

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

TonyKang преди 5 години
родител
ревизия
657c997d8b
променени са 100 файла, в които са добавени 10402 реда и са изтрити 3506 реда
  1. 9 0
      .vscode/settings.json
  2. 1 1
      app/base/base_bills_service.js
  3. 15 0
      app/base/base_service.js
  4. 70 13
      app/const/audit.js
  5. 19 0
      app/const/sms_type.js
  6. 17 1
      app/const/spread.js
  7. 26 0
      app/const/third_party.js
  8. 35 0
      app/const/wechat_template.js
  9. 473 0
      app/controller/advance_controller.js
  10. 17 13
      app/controller/change_controller.js
  11. 3 0
      app/controller/dashboard_controller.js
  12. 9 7
      app/controller/ledger_audit_controller.js
  13. 13 10
      app/controller/ledger_controller.js
  14. 162 16
      app/controller/material_controller.js
  15. 21 19
      app/controller/measure_controller.js
  16. 8 5
      app/controller/profile_controller.js
  17. 5 1
      app/controller/report_controller.js
  18. 79 55
      app/controller/revise_controller.js
  19. 17 3
      app/controller/setting_controller.js
  20. 44 20
      app/controller/stage_controller.js
  21. 110 32
      app/controller/wechat_controller.js
  22. 82 8
      app/extend/helper.js
  23. 3 2
      app/lib/bills_pos_convert.js
  24. 91 0
      app/lib/sql_builder.js
  25. 189 0
      app/lib/wechat.js
  26. 73 0
      app/middleware/advance_check.js
  27. 0 1
      app/middleware/material_check.js
  28. 7 2
      app/middleware/tender_check.js
  29. 3 2
      app/middleware/wechat_auth.js
  30. 96 11
      app/public/css/main.css
  31. 274 0
      app/public/js/advance.js
  32. 380 0
      app/public/js/advance_audit.js
  33. 137 42
      app/public/js/ledger.js
  34. 301 9
      app/public/js/material.js
  35. 64 51
      app/public/js/material_audit.js
  36. 192 61
      app/public/js/measure_material.js
  37. 187 73
      app/public/js/measure_stage.js
  38. 29 0
      app/public/js/revise.js
  39. 181 0
      app/public/js/shares/cs_tools.js
  40. 10 1
      app/public/js/shares/sjs_setting.js
  41. 61 11
      app/public/js/stage.js
  42. 123 52
      app/public/js/stage_audit.js
  43. 4 2
      app/public/js/stage_pay.js
  44. 44 3
      app/router.js
  45. 170 0
      app/service/advance.js
  46. 499 0
      app/service/advance_audit.js
  47. 55 0
      app/service/advance_file.js
  48. 313 214
      app/service/change.js
  49. 18 1
      app/service/change_att.js
  50. 7 8
      app/service/change_audit.js
  51. 1 1
      app/service/change_audit_list.js
  52. 1 1
      app/service/change_company.js
  53. 16 15
      app/service/ledger.js
  54. 182 121
      app/service/ledger_audit.js
  55. 4 9
      app/service/ledger_revise.js
  56. 123 23
      app/service/material_audit.js
  57. 38 14
      app/service/material_bills.js
  58. 8 1
      app/service/material_file.js
  59. 2 2
      app/service/material_list.js
  60. 224 0
      app/service/material_month.js
  61. 37 37
      app/service/measure_audit.js
  62. 22 22
      app/service/measure_bills.js
  63. 9 9
      app/service/measure_pos.js
  64. 11 12
      app/service/notice_push.js
  65. 6 3
      app/service/pos.js
  66. 54 1
      app/service/project_account.js
  67. 2 2
      app/service/report.js
  68. 265 99
      app/service/revise_audit.js
  69. 27 27
      app/service/stage.js
  70. 17 4
      app/service/stage_att.js
  71. 318 232
      app/service/stage_audit.js
  72. 6 0
      app/service/tender.js
  73. 25 20
      app/service/tender_info.js
  74. 24 0
      app/view/advance/audit_btn.ejs
  75. 397 0
      app/view/advance/detail.ejs
  76. 99 0
      app/view/advance/index.ejs
  77. 66 0
      app/view/advance/modal.ejs
  78. 738 0
      app/view/advance/modal_audit.ejs
  79. 1 1
      app/view/change/info.ejs
  80. 48 3
      app/view/dashboard/index.ejs
  81. 14 4
      app/view/ledger/audit.ejs
  82. 504 317
      app/view/ledger/audit_modal.ejs
  83. 22 5
      app/view/ledger/explode.ejs
  84. 234 251
      app/view/ledger/explode_modal.ejs
  85. 2 2
      app/view/material/audit_btn.ejs
  86. 592 752
      app/view/material/audit_modal.ejs
  87. 40 16
      app/view/material/info.ejs
  88. 47 0
      app/view/material/info_modal.ejs
  89. 1 58
      app/view/material/modal.ejs
  90. 20 59
      app/view/measure/stage_modal.ejs
  91. 2 0
      app/view/profile/sms.ejs
  92. 2 0
      app/view/profile/wechat.ejs
  93. 2 2
      app/view/revise/history.ejs
  94. 198 0
      app/view/revise/history_modal.ejs
  95. 23 4
      app/view/revise/info.ejs
  96. 779 506
      app/view/revise/info_modal.ejs
  97. 193 120
      app/view/revise/modal.ejs
  98. 9 1
      app/view/setting/user.ejs
  99. 201 0
      app/view/shares/ledger_check_modal.ejs
  100. 0 0
      app/view/stage/audit_btn.ejs

+ 9 - 0
.vscode/settings.json

@@ -0,0 +1,9 @@
+{
+  "path-intellisense.mappings": {
+    "@": "${workspaceRoot}"
+  },
+  "editor.formatOnSave": false,
+  "editor.codeActionsOnSave": {
+    "source.fixAll.eslint": true
+  }
+}

+ 1 - 1
app/base/base_bills_service.js

@@ -522,7 +522,7 @@ class BaseBillsSerivce extends TreeService {
     _calcExpr(data, field, expr, defaultValue, precision) {
         if (expr) {
             try {
-                data[field] = this.ctx.helper.round(math(expr), precision.value);
+                data[field] = this.ctx.helper.round(math.eval(expr), precision.value);
             } catch (err) {
             }
         } else {

+ 15 - 0
app/base/base_service.js

@@ -174,6 +174,21 @@ class BaseService extends Service {
     }
 
     /**
+     * 获取数据条数(SqlBuilder版本)
+     *
+     * @return {Array} - 返回分页数据
+     */
+    async getCountWithBuilder() {
+        // 由于egg-mysql不提供like、不等于操作,所以需要自己组sql
+        if (this.sqlBuilder === null) {
+            return [];
+        }
+        const [sql, sqlParam] = this.sqlBuilder.buildCount(this.tableName);
+        const result = await this.db.query(sql, sqlParam);
+        return result ? result.length : 0;
+    }
+
+    /**
      * 获取分页数据
      *
      * @param {Object} condition - 搜索条件

+ 70 - 13
app/const/audit.js

@@ -33,7 +33,7 @@ const ledger = (function() {
     const auditString = [];
     auditString[status.uncheck] = '';
     auditString[status.checking] = '审批中';
-    auditString[status.checked] = '审批完成';
+    auditString[status.checked] = '审批通过';
     auditString[status.checkNo] = '审批退回';
     // 文字样式
     const auditStringClass = [];
@@ -55,8 +55,8 @@ const revise = (function() {
     const statusString = [];
     statusString[status.uncheck] = '草稿';
     statusString[status.checking] = '审批中';
-    statusString[status.checked] = '完成';
-    statusString[status.checkNo] = '退回';
+    statusString[status.checked] = '审批通过';
+    statusString[status.checkNo] = '审批退回';
 
     const statusClass = [];
     statusClass[status.uncheck] = '';
@@ -69,7 +69,7 @@ const revise = (function() {
     const auditString = [];
     auditString[status.uncheck] = '';
     auditString[status.checking] = '审批中';
-    auditString[status.checked] = '审批完成';
+    auditString[status.checked] = '审批通过';
     auditString[status.checkNo] = '审批退回';
     // 文字样式
     const auditStringClass = [];
@@ -124,7 +124,7 @@ const stage = (function() {
 
     /**
      * 期列表,审批状态一列
-      */
+     */
     // 按钮
     const statusButton = [];
     statusButton[status.uncheck] = '待上报';
@@ -145,9 +145,9 @@ const stage = (function() {
     const auditString = [];
     auditString[status.uncheck] = '';
     auditString[status.checking] = '审批中';
-    auditString[status.checked] = '完成';
-    auditString[status.checkNo] = '退回';
-    auditString[status.checkNoPre] = '退回';
+    auditString[status.checked] = '审批通过';
+    auditString[status.checkNo] = '审批退回';
+    auditString[status.checkNoPre] = '审批退回';
     auditString[status.checkAgain] = '重新审批';
     // 文字样式
     const auditStringClass = [];
@@ -161,7 +161,7 @@ const stage = (function() {
 
     /**
      * 期列表,审批进度一列
-    */
+     */
     // 描述文本
     const auditProgress = [];
     auditProgress[status.uncheck] = '待上报';
@@ -184,10 +184,21 @@ const stage = (function() {
         org: 1,
         pre: 2,
     };
-    return { status, statusString, statusClass, statusButton, statusButtonClass, auditString, auditStringClass, auditProgress, auditProgressClass, backType, timesLen: 100 };
+    return {
+        status,
+        statusString,
+        statusClass,
+        statusButton,
+        statusButtonClass,
+        auditString,
+        auditStringClass,
+        auditProgress,
+        auditProgressClass,
+        backType,
+        timesLen: 100,
+    };
 })();
 
-
 // 变更令状态
 const status = {
     uncheck: 1, // 待上报
@@ -216,10 +227,10 @@ statusButtonClass[status.backnew] = 'btn-success';
 const statusString = [];
 statusString[status.uncheck] = '';
 statusString[status.checking] = '审批中';
-statusString[status.checked] = '审批完成';
+statusString[status.checked] = '审批通过';
 // statusString[status.checkNo] = '终止';
 statusString[status.back] = '审批退回';
-statusString[status.backnew] = '退回';
+statusString[status.backnew] = '审批退回';
 
 const statusClass = [];
 statusClass[status.uncheck] = '';
@@ -335,6 +346,50 @@ const material = (function() {
     return { status, statusString, statusClass, statusButton, statusButtonClass, auditProgress, auditProgressClass };
 })();
 
+// 预付款审批流程
+const advance = (function() {
+    const type = {
+        start: 0,
+        material: 1,
+    };
+
+    const status = {
+        uncheck: 1, // 待上报
+        checking: 2, // 待审批|审批中
+        checked: 3, // 审批通过
+        checkNo: 4, // 审批退回原报
+        checkNoPre: 5, // 审批退回上一人
+    };
+
+    const statusString = [];
+    statusString[status.uncheck] = '未上报';
+    statusString[status.checking] = '审批中';
+    statusString[status.checked] = '审批通过';
+    statusString[status.checkNo] = '审批退回';
+    statusString[status.checkNoPre] = '审批退回';
+
+    const statusClass = [];
+    statusClass[status.uncheck] = '';
+    statusClass[status.checking] = 'text-warning';
+    statusClass[status.checked] = 'text-success';
+    statusClass[status.checkNo] = 'text-warning';
+    statusClass[status.checkNoPre] = 'text-warning';
+
+    // 标段概况页
+    // 描述文本
+    const auditString = [];
+    auditString[status.uncheck] = '';
+    auditString[status.checking] = '审批中';
+    auditString[status.checked] = '审批通过';
+    auditString[status.checkNo] = '审批退回';
+    // 文字样式
+    const auditStringClass = [];
+    auditStringClass[status.uncheck] = '';
+    auditStringClass[status.checking] = 'text-warning';
+    auditStringClass[status.checked] = 'text-success';
+    auditStringClass[status.checkNo] = 'text-warning';
+    return { type, status, statusString, statusClass, auditString, auditStringClass };
+})();
 // 推送类型
 const pushType = {
     material: 1,
@@ -342,6 +397,7 @@ const pushType = {
     change: 3,
     revise: 4,
     ledger: 5,
+    advance: 6,
 };
 
 module.exports = {
@@ -361,4 +417,5 @@ module.exports = {
     },
     filter,
     pushType,
+    advance,
 };

+ 19 - 0
app/const/sms_type.js

@@ -12,6 +12,7 @@ const smsConst = {
     JL: 'JL',
     BG: 'BG',
     XD: 'XD',
+    TC: 'TC',
 };
 const judgeConst = {
     approval: 1,
@@ -21,6 +22,8 @@ const smsType = {
     TZ: {
         name: '台账审批',
         path: '',
+        wechat: true,
+        sms: true,
         children: [
             { title: '需要我审批', value: 1 },
             { title: '审批结果', value: 2 },
@@ -29,6 +32,8 @@ const smsType = {
     XD: {
         name: '修订审批',
         path: '',
+        wechat: true,
+        sms: true,
         children: [
             { title: '需要我审批', value: 1 },
             { title: '审批结果', value: 2 },
@@ -37,6 +42,8 @@ const smsType = {
     JL: {
         name: '计量审批',
         path: '',
+        wechat: true,
+        sms: true,
         children: [
             { title: '需要我审批', value: 1 },
             { title: '审批结果', value: 2 },
@@ -45,6 +52,18 @@ const smsType = {
     BG: {
         name: '变更管理',
         path: '',
+        wechat: true,
+        sms: true,
+        children: [
+            { title: '需要我审批', value: 1 },
+            { title: '审批结果', value: 2 },
+        ],
+    },
+    TC: {
+        name: '材料调差',
+        path: '',
+        wechat: true,
+        sms: false,
         children: [
             { title: '需要我审批', value: 1 },
             { title: '审批结果', value: 2 },

+ 17 - 1
app/const/spread.js

@@ -13,6 +13,10 @@ const dgnCols = ['dgn_qty1', 'dgn_qty2', 'dgn_price'];
 const clCols = ['sjcl_qty', 'sjcl_tp', 'qtcl_qty', 'qtcl_tp', 'quantity', 'total_price'];
 const stageDgnCols = ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2', 'final_dgn_price'];
 const realCompleteCols = ['real_qty', 'estimate_qty'];
+const thirdPartyCols = {
+    gxby: ['gxby'],
+    dagl: ['dagl']
+};
 
 const withCl = {
     ledger: {
@@ -193,6 +197,8 @@ const stageTz = {
             //{title: '累计完成率(%)', colSpan: '1', rowSpan: '2', field: 'percent', hAlign: 0, width: 100, readOnly: true, type: 'Number'},
             {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
             {title: '总额计量', colSpan: '1', rowSpan: '2', field: 'is_tp', hAlign: 1, width: 60, cellType: 'checkbox'},
+            {title: '交工状态', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            {title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true},
         ],
         emptyRows: 0,
         headRows: 2,
@@ -216,6 +222,8 @@ const stageTz = {
             {title: '|完成', colSpan: '|1', rowSpan: '|1', field: 'end_gather_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '本期批注', colSpan: '1', rowSpan: '2', field: 'postil', hAlign: 0, width: 80, formatter: '@', cellType: 'autoTip'},
             {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
+            {title: '交工状态', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            {title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true},
         ],
         emptyRows: 3,
         headRows: 2,
@@ -260,6 +268,8 @@ const stageCl = {
             {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
             {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
             {title: '总额计量', colSpan: '1', rowSpan: '2', field: 'is_tp', hAlign: 1, width: 60, cellType: 'checkbox'},
+            {title: '交工状态', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            {title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true},
         ],
         emptyRows: 0,
         headRows: 2,
@@ -287,6 +297,8 @@ const stageCl = {
             {title: '本期批注', colSpan: '1', rowSpan: '2', field: 'postil', hAlign: 0, width: 80, formatter: '@', cellType: 'autoTip'},
             {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
             {title: '添加期数', colSpan: '1', rowSpan: '2', field: 'add_stage_order', hAlign:1, width: 80, readOnly: true},
+            {title: '交工状态', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            {title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true},
         ],
         emptyRows: 20,
         headRows: 2,
@@ -331,6 +343,8 @@ const stageNoCl = {
             {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
             {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
             {title: '总额计量', colSpan: '1', rowSpan: '2', field: 'is_tp', hAlign: 1, width: 60, cellType: 'checkbox'},
+            {title: '交工状态', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            {title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true},
         ],
         emptyRows: 0,
         headRows: 2,
@@ -355,6 +369,8 @@ const stageNoCl = {
             {title: '本期批注', colSpan: '1', rowSpan: '2', field: 'postil', hAlign: 0, width: 80, formatter: '@', cellType: 'autoTip'},
             {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
             {title: '添加期数', colSpan: '1', rowSpan: '2', field: 'add_stage_order', hAlign:1, width: 80, readOnly: true},
+            {title: '交工状态', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            {title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true},
         ],
         emptyRows: 20,
         headRows: 2,
@@ -540,7 +556,7 @@ module.exports = {
     stageNoCl,
     stageGather,
     stageCompare,
-    filterCols: { tzWithoutCols, dgnCols, clCols, stageDgnCols, realCompleteCols},
+    filterCols: { tzWithoutCols, dgnCols, clCols, stageDgnCols, realCompleteCols, thirdPartyCols},
     measure,
     blank,
 };

+ 26 - 0
app/const/third_party.js

@@ -0,0 +1,26 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const gxby = [
+    {value: 0, name: '未开始'},
+    {value: 1, name: '进行中'},
+    {value: 2, name: '已完成', color: '#5ACD94'}
+];
+const dagl = [
+    {value: 0, name: '未完备'},
+    {value: 1, name: '第一阶段'},
+    {value: 2, name: '第二阶段'},
+    {value: 10, name: '完备', color: '#5ACD94'}
+];
+
+module.exports = {
+    gxby,
+    dagl
+};

+ 35 - 0
app/const/wechat_template.js

@@ -0,0 +1,35 @@
+'use strict';
+
+/**
+ * 微信通知模板
+ *
+ * @author Ellisran
+ * @date 2020/8/10
+ * @version
+ */
+const template = {
+    stage: '5vU3WmR90yDajbs4LWIWH4OQhunYlS1HXTiesIGxrsk',
+    change: 'nSKl9u4DMvu7KtmFFS9bfVvznRDRpx6L7LlTnVFn4fs',
+    ledger: 'ia3ObPVe_0A1GLVpU-jcDe1P6zVriJ36eAijeQvbpFM', // 台账和台账修订共用模板
+    revise: 'ia3ObPVe_0A1GLVpU-jcDe1P6zVriJ36eAijeQvbpFM',
+    material: 'Y9ov80rev3LHcoM2hOQE6jrK_5xZsqE-PZ0Z6HS9VGA',
+    advance: '',
+};
+const status = {
+    check: '待审批',
+    back: '退回',
+    success: '通过',
+    report: '已上报',
+};
+const tips = {
+    check: '需要您审批',
+    back: '审批退回',
+    success: '审批通过',
+    report: '已上报',
+};
+
+module.exports = {
+    template,
+    status,
+    tips,
+};

+ 473 - 0
app/controller/advance_controller.js

@@ -0,0 +1,473 @@
+'use strict';
+const accountGroup = require('../const/account_group').group;
+const auditConst = require('../const/audit').advance;
+const ledgerAuditConst = require('../const/audit').ledger.status;
+const sendToWormhole = require('stream-wormhole');
+const path = require('path');
+const fs = require('fs');
+module.exports = app => {
+    class advanceController extends app.BaseController {
+        constructor(ctx) {
+            super(ctx);
+            const { decimal } = ctx.tender.info;
+            this.decimal = decimal.pay ? decimal.payTp : decimal.tp;
+        }
+
+        /**
+         * 开工预付款页面(AJAX) GET
+         * @param {Object} ctx 全局上下文
+         */
+        async index(ctx) {
+            const type = auditConst.type.start;
+            const advanceList = await ctx.service.advance.getAdvanceList(ctx.tender.id, type);
+            const latestOrder = await ctx.service.advance.getLastestAdvance(ctx.tender.id, type, true);
+            const advancePayTotal = ctx.tender.info.deal_param.startAdvance;
+            const progress = await ctx.service.advance.calcProgress(latestOrder, advancePayTotal);
+            const showAddBtn = ctx.tender.data.ledger_status !== ledgerAuditConst.uncheck && ctx.tender.data.user_id === ctx.session.sessionUser.accountId ? !latestOrder || (latestOrder.status === auditConst.status.checked && latestOrder.prev_total_amount < advancePayTotal) : false;
+            const renderData = {
+                type,
+                decimal: this.decimal,
+                showAddBtn,
+                advanceList,
+                latestOrder,
+                auditConst,
+                preUrl: `/tender/${ctx.tender.id}/advance/${type}/create`,
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.advance.main),
+                advancePayTotal,
+                progress,
+            };
+            await this.layout('advance/index.ejs', renderData, 'advance/modal.ejs');
+        }
+
+        /**
+         * 材料预付款页面(AJAX) GET
+         * @param {Object} ctx 全局上下文
+         */
+        async materialList(ctx) {
+            const type = auditConst.type.material;
+            const advanceList = await ctx.service.advance.getAdvanceList(ctx.tender.id, type);
+            const latestOrder = await ctx.service.advance.getLastestAdvance(ctx.tender.id, type, true);
+            const advancePayTotal = ctx.tender.info.deal_param.materialAdvance;
+            const progress = await ctx.service.advance.calcProgress(latestOrder, advancePayTotal);
+            const showAddBtn = ctx.tender.data.ledger_status !== ledgerAuditConst.uncheck && ctx.tender.data.user_id === ctx.session.sessionUser.accountId ? !latestOrder || (latestOrder.status === auditConst.status.checked && latestOrder.prev_total_amount < advancePayTotal) : false;
+            const renderData = {
+                type,
+                decimal: this.decimal,
+                showAddBtn,
+                advanceList,
+                latestOrder,
+                auditConst,
+                preUrl: `/tender/${ctx.tender.id}/advance/${type}/create`,
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.advance.main),
+                advancePayTotal,
+                progress,
+            };
+            await this.layout('advance/index.ejs', renderData, 'advance/modal.ejs');
+        }
+
+        /**
+         * 获取通用的renderData(用于layout, Menu, subMenu部分)
+         * @param {Object} ctx 全局上下文
+         * @return {{auditConst, jsFiles, accountGroup?, accountList?, auditors, auditHistory, preUrl}} 通用数据
+         * @private
+         */
+        async _getDefaultRenderData(ctx) {
+            const data = {
+                auditConst,
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.advance.info),
+                preUrl: `/tender/${ctx.tender.id}/advance/${ctx.advance.id}`,
+                whiteList: ctx.app.config.multipart.whitelist,
+            };
+            // 获取所有项目参与者
+            if ((ctx.advance.status === auditConst.status.uncheck || ctx.advance.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ctx.advance.uid) {
+                // data.accountGroup = accountGroup;
+                // 获取所有项目参与者
+                const accountList = await ctx.service.projectAccount.getAllDataByCondition({
+                    where: { project_id: ctx.session.sessionProject.id, enable: 1 },
+                    columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile'],
+                });
+                data.accountList = accountList;
+                data.accountGroup = accountGroup.map((item, idx) => {
+                    const groupList = accountList.filter(item => item.account_group === idx);
+                    return { groupName: item, groupList };
+                });
+            }
+            // 获取审核人左边列表
+            data.auditors = await ctx.service.advanceAudit.getAuditorsWithOwner(ctx.advance.id, ctx.advance.times);
+            // data.fileList = await ctx.service.advanceFile.getAdvanceFiles({ vid: ctx.advance.id });
+            // 获取审批流程中右边列表
+            const auditHistory = [];
+            const times = ctx.advance.status === auditConst.status.checkNo ? ctx.advance.times - 1 : ctx.advance.times;
+            if (times >= 1) {
+                for (let i = 1; i <= times; i++) {
+                    auditHistory.push(await ctx.service.advanceAudit.getAuditors(ctx.advance.id, i));
+                }
+            }
+            data.auditHistory = auditHistory;
+            data.fileList = await ctx.service.advanceFile.getAdvanceFiles({ vid: ctx.advance.id }) || [];
+            return data;
+        }
+
+        _checkCanEntry(ctx) {
+            if (ctx.session.sessionUser.accountId !== ctx.advance.uid) {
+                if (ctx.advance.status === auditConst.status.uncheck) {
+                    throw '无权访问';
+                }
+            }
+        }
+
+        /**
+         * 预付款详情页(AJAX) GET
+         * @param {Object} ctx 全局上下文
+         */
+        async detail(ctx) {
+            const advancePayTotal = ctx.advance.type === 0 ? ctx.tender.info.deal_param.startAdvance : ctx.tender.info.deal_param.materialAdvance;
+            try {
+                this._checkCanEntry(ctx);
+                const renderData = await this._getDefaultRenderData(ctx);
+                const { uncheck, checkNo } = auditConst.status;
+                const { status } = ctx.advance;
+                // 获取上一期预付款记录
+                const prevAdvance = await ctx.service.advance.getPreviousRecord(ctx.tender.id, ctx.advance.type);
+                // 最大支付比例
+                const max_pr = ctx.helper.mul(ctx.helper.div(ctx.helper.sub(advancePayTotal, (prevAdvance && prevAdvance.prev_total_amount || 0)), advancePayTotal, 10), 100);
+                renderData.isEdited = status === uncheck || status === checkNo;
+                renderData.advance = ctx.advance;
+                renderData.decimal = this.decimal;
+                renderData.max_pr = max_pr;
+                renderData.advancePayTotal = advancePayTotal;
+                renderData.prevAdvance = prevAdvance;
+                await this.layout('advance/detail.ejs', renderData, 'advance/modal_audit.ejs');
+            } catch (error) {
+                this.log(error);
+                ctx.redirect('/tender/' + ctx.tender.id + '/advance');
+            }
+        }
+
+        /**
+         * 更新
+         * @param {Object} ctx 全局上下文
+         */
+        async update(ctx) {
+            const { id } = ctx.advance;
+            const data = JSON.parse(ctx.request.body.data);
+            try {
+                const result = await ctx.service.advance.updateAdvance(data, id);
+                if (result) {
+                    ctx.body = { err: 0, msg: '请求成功', data: null };
+                } else {
+                    ctx.body = { err: 0, msg: '请求失败', data: null };
+                }
+            } catch (error) {
+                this.log(error);
+                ctx.body = { code: 1, msg: error.toString() };
+            }
+
+        }
+
+        /**
+         * 添加新一期
+         * @param {Object} ctx 全局上下文
+         */
+        async create(ctx) {
+            const type = parseInt(ctx.params.type);
+            try {
+                const record = await ctx.service.advance.createRecord(type);
+                if (!record) {
+                    throw '数据错误';
+                }
+                ctx.redirect(`/tender/${ctx.tender.id}/advance/${record.id}/detail`);
+            } catch (error) {
+                this.log(error);
+                ctx.redirect(`/tender/${ctx.tender.id}/advance`);
+            }
+        }
+
+        /**
+         * 添加审批人
+         * @param {Object} ctx 全局上下文
+         */
+        async addAudit(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const audit_id = data && this.app._.toInteger(data.auditorId);
+                if (isNaN(audit_id) || audit_id <= 0) {
+                    throw '参数错误';
+                }
+                // 检查权限等
+                if (ctx.advance.uid !== ctx.session.sessionUser.accountId) {
+                    throw '您无权添加审核人';
+                }
+                if (ctx.advance.status === auditConst.status.checking || ctx.advance.status === auditConst.status.checked) {
+                    throw '当前不允许添加审核人';
+                }
+                // 检查审核人是否已存在
+                const exist = this.app._.find(ctx.advance.auditors, { audit_id });
+                if (exist) {
+                    throw '该审核人已存在,请勿重复添加';
+                }
+
+                const result = await ctx.service.advanceAudit.addAuditor(ctx.tender.id, ctx.advance.id, audit_id, ctx.advance.times);
+                if (!result) {
+                    throw '添加审核人失败';
+                }
+
+                const audit = await ctx.service.advanceAudit.getAuditor(ctx.advance.id, audit_id, ctx.advance.times);
+                ctx.body = { err: 0, msg: '', data: audit };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
+         * 移除审批人
+         * @param {Object} ctx 全局上下文
+         */
+        async deleteAudit(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const id = data && data.auditorId instanceof Number ? data.auditorId : this.app._.toNumber(data.auditorId);
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+
+                const result = await ctx.service.advanceAudit.deleteAuditor(ctx.advance.id, id, ctx.advance.times);
+                if (!result) {
+                    throw '移除审核人失败';
+                }
+
+                const auditors = await ctx.service.advanceAudit.getAuditGroupByList(ctx.advance.id, ctx.advance.times);
+                ctx.body = { err: 0, msg: '', data: auditors };
+            } catch (err) {
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
+         * 上报审批
+         * @param {Object} ctx 全局上下文
+         */
+        async start(ctx) {
+            const data = JSON.parse(ctx.request.body.data);
+            try {
+                // 检查权限等
+                if (!ctx.advance) {
+                    throw '数据错误';
+                }
+                if (ctx.advance.uid !== ctx.session.sessionUser.accountId) {
+                    throw '您无权上报该期数据';
+                }
+                if (ctx.advance.status === auditConst.status.checking || ctx.advance.status === auditConst.status.checked) {
+                    throw '该预付款期数据当前无法上报';
+                }
+                if (data.cur_amount === 0) {
+                    throw '本期金额不能为空';
+                }
+                await ctx.service.advanceAudit.start(ctx.advance.id, ctx.advance.times, data);
+                ctx.body = { err: 0, msg: '' };
+            } catch (error) {
+                this.log(error);
+                ctx.session.postError = error.toString();
+                ctx.body = { err: 1, msg: error.toString() };
+            }
+        }
+
+        /**
+         * 审批
+         * @param {Object} ctx 全局上下文
+         */
+        async checkAudit(ctx) {
+            try {
+                if (!ctx.advance || ctx.advance.status !== auditConst.status.checking) {
+                    throw '当前材料调差期数据有误';
+                }
+                if (!ctx.advance.curAuditor || ctx.advance.curAuditor.audit_id !== ctx.session.sessionUser.accountId) {
+                    throw '您无权进行该操作';
+                }
+                const data = {
+                    checkType: parseInt(ctx.request.body.checkType),
+                    opinion: ctx.request.body.opinion,
+                };
+                if (!data.checkType || isNaN(data.checkType)) {
+                    throw '提交数据错误';
+                }
+                if (data.checkType === auditConst.status.checkNo) {
+                    if (!data.checkType || isNaN(data.checkType)) {
+                        throw '提交数据错误';
+                    }
+                }
+
+                await ctx.service.advanceAudit.check(ctx.advance.id, data, ctx.advance.times);
+
+                ctx.redirect(ctx.request.header.referer);
+            } catch (err) {
+                this.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        _checkAdvanceFileCanModify(ctx) {
+            // 检查登录用户,是否可操作
+            const accountId = ctx.session.sessionUser.accountId;
+            if (!ctx.advance.curAuditor) {
+                if (ctx.advance.status === auditConst.status.uncheck || ctx.advance.status === auditConst.status.checkNo && accountId === ctx.advance.uid) {
+                    return;
+                }
+                throw '该预付款期当前您无权操作';
+            } else {
+                if (ctx.advance.curAuditor.audit_id === accountId) return;
+                throw '该预付款期当前您无权操作';
+            }
+        }
+
+        /**
+         * 上传附件
+         * @param {*} ctx 上下文
+         */
+        async upload(ctx) {
+            let stream;
+            try {
+                this._checkAdvanceFileCanModify(ctx);
+                const parts = this.ctx.multipart({
+                    autoFields: true,
+                });
+                const files = [];
+                const create_time = Date.parse(new Date()) / 1000;
+                let idx = 0;
+                while ((stream = await parts()) !== undefined) {
+                    if (!stream.filename) {
+                        // 如果没有传入直接返回
+                        return;
+                    }
+                    const fileInfo = path.parse(stream.filename);
+                    const filepath = `public/upload/${this.ctx.tender.id.toString()}/yfk/fujian_${create_time + idx.toString() + fileInfo.ext}`;
+                    await ctx.helper.saveStreamFile(stream, path.resolve(this.app.baseDir, 'app', filepath));
+                    files.push({ filepath, name: stream.filename, ext: fileInfo.ext });
+                    ++idx;
+                    stream && (await sendToWormhole(stream));
+                }
+                const in_time = new Date();
+                const payload = files.map(file => {
+                    let idx;
+                    if (Array.isArray(parts.field.name)) {
+                        idx = parts.field.name.findIndex(name => name === file.name);
+                    } else {
+                        idx = 'isString';
+                    }
+                    const newFile = {
+                        uid: ctx.session.sessionUser.accountId,
+                        vid: ctx.advance.id,
+                        tid: ctx.tender.id,
+                        create_time: in_time,
+                        filepath: file.filepath,
+                        filesize: ctx.helper.bytesToSize(idx === 'isString' ? parts.field.size : parts.field.size[idx]),
+                        filename: file.name,
+                        fileext: file.ext,
+                    };
+                    return newFile;
+                });
+                // 执行文件信息写入数据库
+                await ctx.service.advanceFile.saveFileMsgToDb(payload);
+                // 将最新的当前标段的所有文件信息返回
+                const data = await ctx.service.advanceFile.getAdvanceFiles({ vid: ctx.advance.id });
+                ctx.body = { err: 0, msg: '', data };
+            } catch (err) {
+                stream && (await sendToWormhole(stream));
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
+         * 删除附件
+         * @param {Ojbect} ctx 上下文
+         */
+        async deleteFile(ctx) {
+            try {
+                const { id } = JSON.parse(ctx.request.body.data);
+                const fileInfo = await ctx.service.advanceFile.getDataById(id);
+                if (fileInfo) {
+                    // 先删除文件
+                    await fs.unlinkSync(path.resolve(this.app.baseDir, './app', fileInfo.filepath));
+                    // 再删除数据库
+                    await ctx.service.advanceFile.delete(id);
+                } else {
+                    throw '不存在该文件';
+                }
+                const data = await ctx.service.advanceFile.getAdvanceFiles({ vid: ctx.advance.id });
+                ctx.body = { err: 0, msg: '请求成功', data };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
+         * 下载附件
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async downloadFile(ctx) {
+            const id = ctx.params.fid;
+            if (id) {
+                try {
+                    const fileInfo = await ctx.service.advanceFile.getDataById(id);
+                    if (fileInfo !== undefined && fileInfo !== '') {
+                        const fileName = path.join(__dirname, '../', fileInfo.filepath);
+                        // 解决中文无法下载问题
+                        const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
+                        let disposition = '';
+                        if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
+                            disposition = 'attachment; filename=' + encodeURIComponent(fileInfo.filename);
+                        } else if (userAgent.indexOf('firefox') >= 0) {
+                            disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(fileInfo.filename) + '"';
+                        } else {
+                            /* safari等其他非主流浏览器只能自求多福了 */
+                            disposition = 'attachment; filename=' + new Buffer(fileInfo.filename).toString('binary');
+                        }
+                        ctx.response.set({
+                            'Content-Type': 'application/octet-stream',
+                            'Content-Disposition': disposition,
+                            'Content-Length': fileInfo.filesize,
+                        });
+                        ctx.body = await fs.createReadStream(fileName);
+                    } else {
+                        throw '不存在该文件';
+                    }
+                } catch (err) {
+                    this.log(err);
+                    this.setMessage(err.toString(), this.messageType.ERROR);
+                }
+            }
+        }
+
+        /**
+         * 获取列表页所需的审核弹窗列表
+         * @param {object} ctx 全局上下文
+         */
+        async getAuditors(ctx) {
+            try {
+                const data = {};
+                // 获取审核人左边列表
+                data.auditors = await ctx.service.advanceAudit.getAuditorsWithOwner(ctx.advance.id, ctx.advance.times);
+                // 获取审批流程中右边列表
+                const auditHistory = [];
+                const times = ctx.advance.status === auditConst.status.checkNo ? ctx.advance.times - 1 : ctx.advance.times;
+                if (times >= 1) {
+                    for (let i = 1; i <= times; i++) {
+                        auditHistory.push(await ctx.service.advanceAudit.getAuditors(ctx.advance.id, i));
+                    }
+                }
+                data.auditHistory = auditHistory;
+                data.user = ctx.advance.user;
+                ctx.body = { err: 0, msg: '请求成功', data };
+            } catch (error) {
+                this.log(error);
+                ctx.body = { err: 0, msg: error.toString() };
+            }
+        }
+    }
+    return advanceController;
+};

+ 17 - 13
app/controller/change_controller.js

@@ -266,7 +266,7 @@ module.exports = app => {
                 const auditStatus = await ctx.service.changeAudit.getStatusByChange(change);
 
                 // 获取附件列表
-                const attList = await ctx.service.changeAtt.getAllDataByCondition({ where: { cid: ctx.params.cid } });
+                const attList = await ctx.service.changeAtt.getAllChangeFiles(ctx.params.cid);
 
                 // 根据auditStatus获取审批人列表
                 const auditList = await ctx.service.changeAudit.getListByStatus(change, auditStatus);
@@ -626,16 +626,20 @@ module.exports = app => {
                     if (!stream.filename) {
                         throw '请选择上传的文件!';
                     }
-                    const create_time = Date.parse(new Date()) / 1000;
+                    // const create_time = Date.parse(new Date()) / 1000;
+                    // const fileInfo = path.parse(stream.filename);
+                    // const dirName = 'app/public/upload/changes/' + moment().format('YYYYMMDD');
+                    // const fileName = 'changes' + create_time + '_' + index + fileInfo.ext;
+                    // // 判断文件夹是否存在,不存在则直接创建文件夹
+                    // if (!fs.existsSync(path.join(this.app.baseDir, dirName))) {
+                    //     await fs.mkdirSync(path.join(this.app.baseDir, dirName));
+                    // }
+                    // // 保存文件
+                    // await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, dirName, fileName));
                     const fileInfo = path.parse(stream.filename);
-                    const dirName = 'app/public/upload/changes/' + moment().format('YYYYMMDD');
-                    const fileName = 'changes' + create_time + '_' + index + fileInfo.ext;
-                    // 判断文件夹是否存在,不存在则直接创建文件夹
-                    if (!fs.existsSync(path.join(this.app.baseDir, dirName))) {
-                        await fs.mkdirSync(path.join(this.app.baseDir, dirName));
-                    }
-                    // 保存文件
-                    await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, dirName, fileName));
+                    const create_time = Date.parse(new Date()) / 1000;
+                    const filepath = `public/upload/change/fujian_${create_time + index.toString() + fileInfo.ext}`;
+                    await ctx.helper.saveStreamFile(stream, path.resolve(this.app.baseDir, 'app', filepath));
                     await sendToWormhole(stream);
                     // 保存数据到att表
                     const fileData = {
@@ -643,7 +647,7 @@ module.exports = app => {
                         filename: fileInfo.name,
                         fileext: fileInfo.ext,
                         filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size,
-                        filepath: path.join(dirName, fileName),
+                        filepath,
                     };
                     const result = await ctx.service.changeAtt.save(parts.field, fileData, ctx.session.sessionUser.accountId);
                     if (!result) {
@@ -679,7 +683,7 @@ module.exports = app => {
                 try {
                     const fileInfo = await ctx.service.changeAtt.getDataById(id);
                     if (fileInfo !== undefined && fileInfo !== '') {
-                        const fileName = path.join(this.app.baseDir, fileInfo.filepath);
+                        const fileName = path.join(this.app.baseDir, 'app', fileInfo.filepath);
                         // 解决中文无法下载问题
                         const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
                         let disposition = '';
@@ -723,7 +727,7 @@ module.exports = app => {
                 const fileInfo = await ctx.service.changeAtt.getDataById(data.id);
                 if (fileInfo !== undefined && fileInfo !== '') {
                     // 先删除文件
-                    await fs.unlinkSync(path.join(this.app.baseDir, fileInfo.filepath));
+                    await fs.unlinkSync(path.join(this.app.baseDir, './app', fileInfo.filepath));
                     // 再删除数据库
                     await ctx.service.changeAtt.deleteById(data.id);
                     responseData.data = '';

+ 3 - 0
app/controller/dashboard_controller.js

@@ -28,6 +28,7 @@ module.exports = app => {
             const auditChanges = await ctx.service.changeAudit.getAuditChange(ctx.session.sessionUser.accountId);
             const auditRevise = await ctx.service.reviseAudit.getAuditRevise(ctx.session.sessionUser.accountId);
             const auditMaterial = await ctx.service.materialAudit.getAuditMaterial(ctx.session.sessionUser.accountId);
+            const auditAdvance = await ctx.service.advanceAudit.getAuditAdvance(ctx.session.sessionUser.accountId);
             const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
             // const lastNotice = pa.last_notice ? pa.last_notice : (pa.last_notice === 0 ? new Date() : new Date(pa.last_login * 1000));
             // const noticeLedger = await ctx.service.ledgerAudit.getNoticeTender(ctx.session.sessionProject.id, pa.id);
@@ -62,6 +63,7 @@ module.exports = app => {
                 auditChanges,
                 auditRevise,
                 auditMaterial,
+                auditAdvance,
                 role: pa.role,
                 authMobile: pa.auth_mobile,
                 acLedger: auditConst.ledger,
@@ -69,6 +71,7 @@ module.exports = app => {
                 acChange: auditConst.flow,
                 acRevise: auditConst.revise,
                 acMaterial: auditConst.material,
+                acAdvance: auditConst.advance,
                 noticeList,
                 pushType: auditConst.pushType,
                 projectData,

+ 9 - 7
app/controller/ledger_audit_controller.js

@@ -74,7 +74,7 @@ module.exports = app => {
                     throw '审核信息错误';
                 }
 
-                const auditors = await ctx.service.ledgerAudit.getAuditors(ctx.tender.id, ctx.tender.data.ledger_times);
+                const auditors = await ctx.service.ledgerAudit.getAuditorsWithOwner(ctx.tender.id, ctx.tender.data.ledger_times);
                 if (ctx.tender.data.user_id !== ctx.session.sessionUser.accountId) {
                     if (auditors.length > 0) {
                         const auditor = auditors.find(function(a) { return a.audit_id === ctx.session.sessionUser.accountId; });
@@ -86,12 +86,12 @@ module.exports = app => {
 
                 renderData.user = await ctx.service.projectAccount.getAccountInfoById(ctx.tender.data.user_id);
                 renderData.auditHistory = [];
-                if (ctx.tender.data.ledger_times > 1) {
-                    for (let i = 1; i < ctx.tender.data.ledger_times; i++) {
+                const times = ctx.tender.data.ledger_status === auditConst.status.checkNo ? ctx.tender.data.ledger_times - 1 : ctx.tender.data.ledger_times;
+                if (times >= 1) {
+                    for (let i = 1; i <= times; i++) {
                         renderData.auditHistory.push(await ctx.service.ledgerAudit.getAuditors(ctx.tender.id, i));
                     }
                 }
-
                 const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
                 renderData.curAuditor = curAuditor;
                 renderData.auditConst = auditConst;
@@ -104,7 +104,6 @@ module.exports = app => {
                 await this.layout('ledger/audit.ejs', renderData, 'ledger/audit_modal.ejs');
             } catch (err) {
                 this.log(err);
-                console.log(err);
                 ctx.redirect('/tender/' + ctx.tender.id);
             }
         }
@@ -204,10 +203,13 @@ module.exports = app => {
 
                 await ctx.service.ledgerAudit.start(ctx.tender.id, ctx.tender.data.ledger_times);
 
-                ctx.body = {err: 0, msg: '', data: {url: '/tender/' + ctx.tender.id + '/ledger'}}; //ctx.redirect('/tender/' + ctx.tender.id + '/ledger');
+                // ctx.body = { err: 0, msg: '', data: { url: '/tender/' + ctx.tender.id + '/ledger' } };
+                ctx.redirect('/tender/' + ctx.tender.id + '/ledger');
             } catch (err) {
                 this.log(err);
-                ctx.body = this.ajaxErrorBody(err, '上报失败,请刷新页面重试');
+                // ctx.body = this.ajaxErrorBody(err, '上报失败,请刷新页面重试');
+                ctx.session.postError = err.toString();
+                ctx.redirect(ctx.request.header.referer);
             }
         }
 

+ 13 - 10
app/controller/ledger_controller.js

@@ -116,14 +116,14 @@ module.exports = app => {
             try {
                 const tender = ctx.tender;
                 const [ledgerSpread, posSpread] = this._getSpreadSetting();
+                const times = tender.data.ledger_status === auditConst.status.checkNo ? tender.data.ledger_times - 1 : tender.data.ledger_times;
 
                 const curAuditor = await ctx.service.ledgerAudit.getCurAuditor(tender.id, tender.data.ledger_times);
-                const times = tender.data.ledger_status === auditConst.status.checkNo ? tender.data.ledger_times - 1 : tender.data.ledger_times;
-                const auditors = await ctx.service.ledgerAudit.getAuditors(tender.id, times);
+                const auditors = await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, times);
                 const user = await ctx.service.projectAccount.getAccountInfoById(ctx.tender.data.user_id);
                 const auditHistory = [];
-                if (ctx.tender.data.ledger_times > 1) {
-                    for (let i = 1; i < ctx.tender.data.ledger_times; i++) {
+                if (times >= 1) {
+                    for (let i = 1; i <= times; i++) {
                         auditHistory.push(await ctx.service.ledgerAudit.getAuditors(ctx.tender.id, i));
                     }
                 }
@@ -147,13 +147,17 @@ module.exports = app => {
                     stdChapters,
                 };
                 if ((tender.data.ledger_status === auditConst.status.uncheck || tender.data.ledger_status === auditConst.status.checkNo) && tender.data.user_id === ctx.session.sessionUser.accountId) {
-                    renderData.accountGroup = accountGroup;
+                    // renderData.accountGroup = accountGroup;
                     // 获取所有项目参与者
                     const accountList = await ctx.service.projectAccount.getAllDataByCondition({
                         where: { project_id: ctx.session.sessionProject.id, enable: 1 },
-                        columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group'],
+                        columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile'],
                     });
                     renderData.accountList = accountList;
+                    renderData.accountGroup = accountGroup.map((item, idx) => {
+                        const groupList = accountList.filter(item => item.account_group === idx);
+                        return { groupName: item, groupList };
+                    });
                     renderData.auditorList = await ctx.service.ledgerAudit.getAuditors(tender.id, tender.data.ledger_times);
                 }
 
@@ -260,7 +264,6 @@ module.exports = app => {
             switch (addType) {
                 case stdDataAddType.child:
                     return await ctx.service.ledger.addStdNodeAsChild(ctx.tender.id, data.id, stdData);
-                    break;
                 case stdDataAddType.next:
                     return await ctx.service.ledger.addStdNode(ctx.tender.id, data.id, stdData);
                 case stdDataAddType.withParent:
@@ -448,7 +451,7 @@ module.exports = app => {
                     ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
                 const data = ctx.helper.checkBillsWithPos(ledgerData, posData,
                     ['sgfh_qty', 'qtcl_qty', 'sjcl_qty', 'quantity']);
-                ctx.body = { err: 0, msg: '', data: data };
+                ctx.body = { err: 0, msg: '', data };
             } catch (err) {
                 this.log(err);
                 ctx.body = this.ajaxErrorBody(err, '检查数据错误');
@@ -649,8 +652,8 @@ module.exports = app => {
                 //     await ctx.service.externalData.saveExValue(ctx.tender.id, -1,
                 //         externalDataConst.FuLong.exType, externalDataConst.FuLong.exFields.wbsCode, wbsCodeHis);
                 // }
-                ctx.body = {err: 0, msg: '', data: result};
-            } catch(err) {
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
                 this.log(err);
                 ctx.body = this.ajaxErrorBody(err, '加载合同支付数据错误');
             }

+ 162 - 16
app/controller/material_controller.js

@@ -98,9 +98,11 @@ module.exports = app => {
                 responseData.data.auditHistory = auditHistory;
                 // 获取审批流程中左边列表
                 responseData.data.auditors = await ctx.service.materialAudit.getAuditGroupByList(materialInfo.id, times);
+
+                responseData.data.user = await ctx.service.projectAccount.getAccountInfoById(materialInfo.user_id);
                 // 获取原报信息
-                const materialAuditor = await ctx.service.projectAccount.getAccountInfoById(materialInfo.user_id);
-                responseData.data.materialAuditor = materialAuditor;
+                // const materialAuditor = await ctx.service.projectAccount.getAccountInfoById(materialInfo.user_id);
+                // responseData.data.materialAuditor = materialAuditor;
                 ctx.body = responseData;
             } catch (error) {
                 this.log(error);
@@ -176,13 +178,17 @@ module.exports = app => {
                 material: ctx.material,
             };
             if ((ctx.material.status === auditConst.status.uncheck || ctx.material.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ctx.material.user_id) {
-                data.accountGroup = accountGroup;
+                // data.accountGroup = accountGroup;
                 // 获取所有项目参与者
                 const accountList = await ctx.service.projectAccount.getAllDataByCondition({
                     where: { project_id: ctx.session.sessionProject.id, enable: 1 },
-                    columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group'],
+                    columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile'],
                 });
                 data.accountList = accountList;
+                data.accountGroup = accountGroup.map((item, idx) => {
+                    const groupList = accountList.filter(item => item.account_group === idx);
+                    return { groupName: item, groupList };
+                });
             }
             data.tenderMenu.back.children[0].url = '/tender/' + ctx.tender.id + '/measure/material';
             return data;
@@ -202,19 +208,65 @@ module.exports = app => {
             const times = ctx.material.status === auditConst.status.checkNo ? ctx.material.times - 1 : ctx.material.times;
             ctx.material.user = await ctx.service.projectAccount.getAccountInfoById(ctx.material.user_id);
             ctx.material.auditHistory = [];
-            if (ctx.material.times > 1) {
-                for (let i = 1; i < ctx.material.times; i++) {
+            if (times >= 1) {
+                for (let i = 1; i <= times; i++) {
                     ctx.material.auditHistory.push(await ctx.service.materialAudit.getAuditors(ctx.material.id, i));
                 }
             }
             // 获取审批流程中左边列表
-            ctx.material.auditors2 = await ctx.service.materialAudit.getAuditGroupByList(ctx.material.id, times);
+            ctx.material.auditors2 = await ctx.service.materialAudit.getAuditorsWithOwner(ctx.material.id, times);
             if (ctx.material.status === auditConst.status.uncheck || ctx.material.status === auditConst.status.checkNo) {
                 ctx.material.auditorList = await ctx.service.materialAudit.getAuditors(ctx.material.id, ctx.material.times);
             }
         }
 
         /**
+         * 获取当前期工料列表信息
+         * @param ctx
+         * @return {Promise<void>}
+         * @private
+         */
+        async _getMaterialBillsData(ctx) {
+            // 根据期判断需要获取的工料信息值
+            const searchsql = { tid: ctx.tender.id };
+            if (ctx.material.highOrder !== ctx.material.order) {
+                const midList = await ctx.service.material.getPreMidList(ctx.tender.id, ctx.material.order);
+                searchsql.mid = midList;
+            }
+            // 取所有工料表
+            return await ctx.service.materialBills.getAllDataByCondition({ where: searchsql });
+        }
+
+        /**
+         * 获取当前月信息价列表信息
+         * @param ctx
+         * @return {Promise<void>}
+         * @private
+         */
+        async _getMaterialMonthsData(ctx, materialBillsData) {
+            // 取月信息价表
+            const monthsList = [];
+            if (ctx.material.months) {
+                const material_month = ctx.material.months.split(',');
+                const materialMonthList = await ctx.service.materialMonth.getListByMid(ctx.material.id);
+                for (const mbd of materialBillsData) {
+                    const one_mb = {
+                        code: mbd.code,
+                        name: mbd.name,
+                        unit: mbd.unit,
+                        mb_id: mbd.id,
+                    };
+                    for (const m of material_month) {
+                        const one_mm = _.find(materialMonthList, { mb_id: mbd.id, yearmonth: m });
+                        one_mb[m] = one_mm.msg_tp;
+                    }
+                    monthsList.push(one_mb);
+                }
+            }
+            return monthsList;
+        }
+
+        /**
          * 调差工料页面 (Get)
          * @param {Object} ctx - egg全局变量
          * @return {Promise<void>}
@@ -223,14 +275,7 @@ module.exports = app => {
             try {
                 await this._getMaterialAuditViewData(ctx);
                 const renderData = await this._getDefaultRenderData(ctx);
-                // 根据期判断需要获取的工料信息值
-                const searchsql = { tid: ctx.tender.id };
-                if (ctx.material.highOrder !== ctx.material.order) {
-                    const midList = await ctx.service.material.getPreMidList(ctx.tender.id, ctx.material.order);
-                    searchsql.mid = midList;
-                }
-                // 取所有工料表
-                renderData.materialBillsData = await ctx.service.materialBills.getAllDataByCondition({ where: searchsql });
+                renderData.materialBillsData = await this._getMaterialBillsData(ctx);
                 // 取对应期的截取上期的调差金额和应耗数量
                 if (ctx.material.highOrder !== ctx.material.order) {
                     for (const [mindex, mb] of renderData.materialBillsData.entries()) {
@@ -266,6 +311,9 @@ module.exports = app => {
                 // 取当前期截止上期含税金额
                 renderData.pre_tp_hs = await ctx.service.material.getPreTpHs(ctx.tender.id, ctx.material.order);
 
+                renderData.months = ctx.material.months ? ctx.material.months.split(',') : [];
+                renderData.monthsList = await this._getMaterialMonthsData(ctx, renderData.materialBillsData);
+
                 renderData.materialType = JSON.stringify(materialConst);
                 renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.material.info);
                 await this.layout('material/info.ejs', renderData, 'material/info_modal.ejs');
@@ -471,6 +519,62 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 调差工料 - 编辑月信息价 (Ajax)
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async saveMonth(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = {
+                    err: 0,
+                    msg: '',
+                    data: {},
+                };
+                const monthList = await ctx.service.materialMonth.getListByMid(ctx.material.id);
+                const mbList = await this._getMaterialBillsData(ctx);
+                switch (data.type) {
+                    case 'add':
+                        const tp = await ctx.service.materialMonth.add(data.updateData, monthList, mbList);
+                        const materialBillsData = await this._getMaterialBillsData(ctx);
+                        const monthsList = await this._getMaterialMonthsData(ctx, materialBillsData);
+                        responseData.data = {
+                            m_tp: tp,
+                            monthsList,
+                        };
+                        break;
+                    case 'del':
+                        const tp2 = await ctx.service.materialMonth.del(data.updateData.del_yearmonth, monthList, mbList);
+                        const materialBillsData2 = await this._getMaterialBillsData(ctx);
+                        const monthsList2 = await this._getMaterialMonthsData(ctx, materialBillsData2);
+                        responseData.data = {
+                            m_tp: tp2,
+                            monthsList: monthsList2,
+                        };
+                        break;
+                    case 'update':
+                        const tp3 = await ctx.service.materialMonth.save(data.updateData);
+                        responseData.data = {
+                            m_tp: tp3,
+                        };
+                        break;
+                    case 'paste':
+                        const tp4 = await ctx.service.materialMonth.saveDatas(data.updateData, mbList);
+                        responseData.data = {
+                            m_tp: tp4,
+                        };
+                        break;
+                    default: throw '参数有误';
+                }
+                responseData.data.materialBillsData = await this._getMaterialBillsData(ctx);
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
         // 审批相关
         /**
          * 添加审批人
@@ -635,7 +739,7 @@ module.exports = app => {
                     // const filepath = path.join('public/upload', this.ctx.tender.id.toString(), 'tc', 'fujian_' + create_time + fileInfo.ext);
                     const filepath = `public/upload/${this.ctx.tender.id.toString()}/tc/fujian_${create_time + idx.toString() + fileInfo.ext}`;
                     await ctx.helper.saveStreamFile(stream, path.resolve(this.app.baseDir, 'app', filepath));
-                    files.push({ filepath, name: stream.filename });
+                    files.push({ filepath, name: stream.filename, ext: fileInfo.ext });
                     ++idx;
                 }
                 const upload_time = this.ctx.helper.dateTran(new Date());
@@ -655,6 +759,7 @@ module.exports = app => {
                         filepath: file.filepath,
                         file_size: ctx.helper.bytesToSize(idx === 'isString' ? parts.field.size : parts.field.size[idx]),
                         file_name: file.name,
+                        fileext: file.ext,
                     };
                     return newFile;
                 });
@@ -669,6 +774,47 @@ module.exports = app => {
                 ctx.body = { err: 1, msg: err.toString(), data: null };
             }
         }
+
+        /**
+         * 下载附件
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async downloadFile(ctx) {
+            const id = ctx.params.fid;
+            if (id) {
+                try {
+                    const fileInfo = await ctx.service.materialFile.getDataById(id);
+                    if (fileInfo !== undefined && fileInfo !== '') {
+                        const fileName = path.join(__dirname, '../', fileInfo.filepath);
+                        // 解决中文无法下载问题
+                        const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
+                        let disposition = '';
+                        if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
+                            disposition = 'attachment; filename=' + encodeURIComponent(fileInfo.file_name);
+                        } else if (userAgent.indexOf('firefox') >= 0) {
+                            disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(fileInfo.file_name) + '"';
+                        } else {
+                            /* safari等其他非主流浏览器只能自求多福了 */
+                            disposition = 'attachment; filename=' + new Buffer(fileInfo.file_name).toString('binary');
+                        }
+                        ctx.response.set({
+                            'Content-Type': 'application/octet-stream',
+                            'Content-Disposition': disposition,
+                            'Content-Length': fileInfo.file_size,
+                        });
+                        ctx.body = await fs.createReadStream(fileName);
+                    } else {
+                        throw '不存在该文件';
+                    }
+                } catch (err) {
+                    this.log(err);
+                    this.setMessage(err.toString(), this.messageType.ERROR);
+                }
+            }
+        }
+
+
         /**
          * 查看当前标段以往所有期的附件
          * @param {Object} ctx 上下文

+ 21 - 19
app/controller/measure_controller.js

@@ -34,7 +34,7 @@ module.exports = app => {
         /**
          * 期列表(Get)
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async stage(ctx) {
             try {
@@ -64,7 +64,7 @@ module.exports = app => {
         /**
          * 期审批流程(Get)
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async stageAuditors(ctx) {
             try {
@@ -84,10 +84,12 @@ module.exports = app => {
                 }
                 responseData.data.auditHistory = auditHistory;
                 // 获取审批流程中左边列表
-                responseData.data.auditors = await ctx.service.stageAudit.getAuditGroupByList(stageInfo.id, times);
+                responseData.data.auditors = await ctx.service.stageAudit.getAuditGroupByListWithOwner(stageInfo.id, times);
+
+                responseData.data.user = await ctx.service.projectAccount.getAccountInfoById(stageInfo.user_id);
                 // 获取原报信息
-                const stageAuditor = await ctx.service.projectAccount.getAccountInfoById(stageInfo.user_id);
-                responseData.data.stageAuditor = stageAuditor;
+                // const stageAuditor = await ctx.service.projectAccount.getAccountInfoById(stageInfo.user_id);
+                // responseData.data.stageAuditor = stageAuditor;
                 ctx.body = responseData;
             } catch (error) {
                 this.log(error);
@@ -98,7 +100,7 @@ module.exports = app => {
         /**
          * 新增期(Post)
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async add(ctx) {
             try {
@@ -117,14 +119,14 @@ module.exports = app => {
                 ctx.redirect('/tender/' + ctx.tender.id + '/measure/stage/' + newStage.order);
             } catch (err) {
                 this.log(err);
-                ctx.redirect(ctx.request.header.referer)
+                ctx.redirect(ctx.request.header.referer);
             }
         }
 
         /**
          * 编辑期(Post)
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async save(ctx) {
             try {
@@ -157,7 +159,7 @@ module.exports = app => {
         /**
          * 多期汇总 (Get)
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async gather(ctx) {
             try {
@@ -165,7 +167,7 @@ module.exports = app => {
                     tender: ctx.tender.data,
                     tenderMenu: this.menu.tenderMenu,
                     preUrl: '/tender/' + ctx.tender.id,
-                }
+                };
                 await this.layout('measure/gather.ejs', renderData);
             } catch (err) {
                 this.log(err);
@@ -176,7 +178,7 @@ module.exports = app => {
         /**
          * 多期比较(Get)
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async compare(ctx) {
             try {
@@ -186,7 +188,7 @@ module.exports = app => {
                     preUrl: '/tender/' + ctx.tender.id,
                 };
                 renderData.stages = await ctx.service.stage.getAllDataByCondition({
-                    where: {tid: ctx.tender.id, status: auditConst.status.checked},
+                    where: { tid: ctx.tender.id, status: auditConst.status.checked },
                     orders: [['order', 'asc']],
                 });
                 renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.measure.compare);
@@ -200,30 +202,30 @@ module.exports = app => {
         /**
          * 多期比较 - 获取数据(Ajax)
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async loadCompareData(ctx) {
             try {
                 const data = JSON.parse(ctx.request.body.data);
                 const result = {
                     main: null,
-                    stages: []
+                    stages: [],
                 };
                 if (data.main) {
                     result.main = {};
                     result.main.ledger = await ctx.service.ledger.getData(ctx.tender.id);
-                    result.main.pos = await ctx.service.pos.getPosData({tid: ctx.tender.id});
+                    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});
+                        const data = { order, bills: [], pos: [] };
+                        const stage = await this.ctx.service.stage.getDataByCondition({ tid: ctx.tender.id, order });
                         data.bills = await ctx.service.stageBills.getLastestStageData(ctx.tender.id, stage.id);
                         data.pos = await ctx.service.stagePos.getLastestStageData2(ctx.tender.id, stage.id);
                         result.stages.push(data);
                     }
                 }
-                ctx.body = {err: 0, msg: '', data: result};
+                ctx.body = { err: 0, msg: '', data: result };
             } catch (err) {
                 this.log(err);
                 ctx.body = this.ajaxErrorBody(err, '获取数据错误');
@@ -233,7 +235,7 @@ module.exports = app => {
         /**
          * 删除期(Post)
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async delete(ctx) {
             try {

+ 8 - 5
app/controller/profile_controller.js

@@ -403,20 +403,23 @@ module.exports = app => {
                     throw '解绑微信失败!';
                 }
                 // 解绑成功通知
-                const templateId = 'gsYUIt5CFY1uJQhiqnkhu38yi0adkZtEH9fGI49g_Lk';
+                const templateId = '0w0Yp65X4PHccTLeAyE5aQhS-blS-bylwxAPYEGy3CI';
                 const url = '';
                 const msgData = {
                     first: {
-                        value: '你已成功与纵横云计量帐号解除绑定',
+                        value: '您好,纵横云计量与微信解绑成功。',
                     },
                     keyword1: {
-                        value: sessionUser.account,
+                        value: ctx.session.sessionProject.code,
                     },
                     keyword2: {
-                        value: '项目编号' + ctx.session.sessionProject.code + ' 账号' + sessionUser.account + ' 解绑成功',
+                        value: sessionUser.account,
+                    },
+                    keyword3: {
+                        value: moment(new Date()).format('YYYY-MM-DD'),
                     },
                     remark: {
-                        value: '欢迎使用纵横云计量,我们竭诚为您服务。',
+                        value: '感谢您的使用,要接收通知请重新绑定。',
                     },
                 };
                 await app.wechat.api.sendTemplate(accountData.wx_openid, templateId, url, '', msgData);

+ 5 - 1
app/controller/report_controller.js

@@ -171,8 +171,12 @@ module.exports = app => {
                     where: { project_id: ctx.session.sessionProject.id, enable: 1 },
                     columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group'],
                 });
+                const newAccountGroup = accountGroup.map((item, idx) => {
+                    const groupList = accountList.filter(item => item.account_group === idx);
+                    return { groupName: item, groupList };
+                });
                 const renderData = {
-                    accountGroup,
+                    accountGroup: newAccountGroup,
                     accountList,
                     tender: tender.data,
                     tenderInfo: tender.info,

+ 79 - 55
app/controller/revise_controller.js

@@ -37,7 +37,7 @@ module.exports = app => {
         /**
          * 是否可以新增修订
          * @param ctx
-         * @returns {Promise<boolean>}
+         * @return {Promise<boolean>}
          * @private
          */
         async _getAddReviseValid(ctx) {
@@ -58,7 +58,7 @@ module.exports = app => {
         async index(ctx) {
             try {
                 // 分页相关
-                const count = await ctx.service.ledgerRevise.count({tid: ctx.tender.id});
+                const count = await ctx.service.ledgerRevise.count({ tid: ctx.tender.id });
                 const ledgerRevise = await ctx.service.ledgerRevise.getReviseList(ctx.tender.id);
                 if (ledgerRevise.length > 0) {
                     const revise = ledgerRevise[0];
@@ -85,7 +85,7 @@ module.exports = app => {
                     preUrl: '/tender/' + ctx.tender.id,
                     pageInfo: {
                         page: ctx.page,
-                        total: Math.ceil(count/app.config.pageSize),
+                        total: Math.ceil(count / app.config.pageSize),
                         queryData: JSON.stringify(ctx.urlInfo.query),
                     },
                     ledgerRevise,
@@ -105,7 +105,7 @@ module.exports = app => {
         /**
          * 修订审批流程(Get)
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async reviseAuditors(ctx) {
             try {
@@ -122,12 +122,13 @@ module.exports = app => {
                         auditHistory.push(await ctx.service.reviseAudit.getAuditors2ReviseList(reviseInfo.id, i));
                     }
                 }
+                responseData.data.user = await ctx.service.projectAccount.getAccountInfoById(reviseInfo.uid);
                 responseData.data.auditHistory = auditHistory;
                 // 获取审批流程中左边列表
-                responseData.data.auditors = await ctx.service.reviseAudit.getAuditGroupByList(reviseInfo.id, times);
+                responseData.data.auditors = await ctx.service.reviseAudit.getAuditorsWithOwner(reviseInfo.id, times);
                 // 获取原报信息
-                const reviseAuditor = await ctx.service.projectAccount.getAccountInfoById(reviseInfo.uid);
-                responseData.data.reviseAuditor = reviseAuditor;
+                // const reviseAuditor = await ctx.service.projectAccount.getAccountInfoById(reviseInfo.uid);
+                // responseData.data.reviseAuditor = reviseAuditor;
                 ctx.body = responseData;
             } catch (error) {
                 this.log(error);
@@ -138,7 +139,7 @@ module.exports = app => {
         /**
          * 新增 修订 (Post)
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async add(ctx) {
             try {
@@ -149,7 +150,7 @@ module.exports = app => {
                 const revise = await ctx.service.ledgerRevise.add(ctx.tender.id, ctx.session.sessionUser.accountId);
 
                 ctx.redirect('/tender/' + ctx.tender.id + '/revise/info');
-            } catch(err) {
+            } catch (err) {
                 this.log(err);
                 ctx.redirect(ctx.request.header.referer);
             }
@@ -158,7 +159,7 @@ module.exports = app => {
         /**
          * 作废 修订 (Post)
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async cancel(ctx) {
             try {
@@ -176,7 +177,7 @@ module.exports = app => {
                 }
 
                 ctx.redirect('/tender/' + ctx.tender.id + '/revise/');
-            } catch(err) {
+            } catch (err) {
                 this.log(err);
                 ctx.redirect(ctx.request.header.referer);
             }
@@ -185,7 +186,7 @@ module.exports = app => {
         /**
          * 保存 修订详情 (Ajax-post)
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async save(ctx) {
             try {
@@ -203,14 +204,14 @@ module.exports = app => {
                 }
                 const data = JSON.parse(ctx.request.body.data);
                 if (data.content === undefined) {
-                    throw '保存数据有误'
+                    throw '保存数据有误';
                 }
-                const result = await ctx.service.ledgerRevise.defaultUpdate({id: revise.id, content: data.content});
+                const result = await ctx.service.ledgerRevise.defaultUpdate({ id: revise.id, content: data.content });
                 if (result.affectedRows !== 1) {
                     throw '保存数据失败,请重试';
                 }
-                ctx.body = {err: 0, msg: '', data: null};
-            } catch(err) {
+                ctx.body = { err: 0, msg: '', data: null };
+            } catch (err) {
                 this.log(err);
                 ctx.body = this.ajaxErrorBody(err, '保存数据失败,请重试');
             }
@@ -259,22 +260,24 @@ module.exports = app => {
             const [stdBills, stdChapters] = await this.ctx.service.valuation.getValuationStdList(
                 ctx.tender.data.valuation, ctx.tender.data.measure_type);
             const curAuditor = await ctx.service.reviseAudit.getCurAuditor(revise.id, revise.times);
-            const auditors = await ctx.service.reviseAudit.getAuditors(revise.id, revise.times);
+            const auditors = await ctx.service.reviseAudit.getAuditorsWithOwner(revise.id, revise.times);
             const user = await ctx.service.projectAccount.getAccountInfoById(revise.uid);
+            const times = revise.status === audit.revise.status.checkNo ? revise.times - 1 : revise.times;
             const auditHistory = [];
-            if (revise.times > 1) {
-                for (let i = 1; i < revise.times; i++) {
+            if (times >= 1) {
+                for (let i = 1; i <= times; i++) {
                     auditHistory.push(await ctx.service.reviseAudit.getAuditors(revise.id, i));
                 }
             }
             return {
-                revise: revise, tender: ctx.tender.data,
-                //reviseBills, revisePos,
+                revise, tender: ctx.tender.data,
+                // reviseBills, revisePos,
                 ledgerSpread, posSpread, tenderMenu, measureType,
                 preUrl: '/tender/' + ctx.tender.id,
                 audit: audit.revise,
                 jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.revise.info),
                 stdBills,
+                auditConst: audit.revise,
                 stdChapters,
                 curAuditor,
                 auditors,
@@ -287,7 +290,7 @@ module.exports = app => {
          * 历史修订页面(修订完成后,用于查看)
          * @param ctx
          * @param revise
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          * @private
          */
         async _getHistoryReviseInfo(ctx, revise) {
@@ -304,7 +307,7 @@ module.exports = app => {
          * 修订页面(草稿,审批中)
          * @param ctx
          * @param revise
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          * @private
          */
         async _getAuditReviseInfo(ctx, revise) {
@@ -350,10 +353,14 @@ module.exports = app => {
             renderData.history = false;
             renderData.historyRevise = [];
             // 添加审批人相关
-            renderData.accountGroup = accountGroup;
+            // renderData.accountGroup = accountGroup;
             renderData.accountList = await ctx.service.projectAccount.getAllDataByCondition({
                 where: { project_id: ctx.session.sessionProject.id, enable: 1 },
-                columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group'],
+                columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile'],
+            });
+            renderData.accountGroup = accountGroup.map((item, idx) => {
+                const groupList = renderData.accountList.filter(item => item.account_group === idx);
+                return { groupName: item, groupList };
             });
             renderData.auditorList = await ctx.service.reviseAudit.getAuditors(revise.id, revise.times);
             renderData.curAuditor = await ctx.service.reviseAudit.getCurAuditor(revise.id, revise.times);
@@ -363,7 +370,7 @@ module.exports = app => {
         /**
          * 修订 详细页面(Get)
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async info(ctx) {
             try {
@@ -430,8 +437,8 @@ module.exports = app => {
                         }
                     }
                 }
-                ctx.body = {err: 0, msg: '', data: {bills: reviseBills, pos: revisePos}};
-            } catch(err) {
+                ctx.body = { err: 0, msg: '', data: { bills: reviseBills, pos: revisePos } };
+            } catch (err) {
                 ctx.helper.log(err);
                 this.ajaxErrorBody(err, '加载台账修订数据错误');
             }
@@ -454,7 +461,7 @@ module.exports = app => {
 
                 const data = ctx.helper.checkBillsWithPos(reviseBills, revisePos,
                     ['sgfh_qty', 'qtcl_qty', 'sjcl_qty', 'quantity']);
-                ctx.body = { err: 0, msg: '', data: data };
+                ctx.body = { err: 0, msg: '', data };
             } catch (err) {
                 this.log(err);
                 ctx.body = this.ajaxErrorBody(err, '检查数据错误');
@@ -466,21 +473,36 @@ module.exports = app => {
                 const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id, false);
                 if (!revise) throw '台账修订数据有误';
 
-                //const reviseBills = await ctx.service.ledger.getData(ctx.tender.id);
-                //const revisePos = await ctx.service.pos.getPosData({tid: ctx.tender.id});
+                // const reviseBills = await ctx.service.ledger.getData(ctx.tender.id);
+                // const revisePos = await ctx.service.pos.getPosData({tid: ctx.tender.id});
                 const [ledgerSpread, posSpread] = this._getSpreadSetting(revise);
                 ledgerSpread.readOnly = true;
                 posSpread.readOnly = true;
                 const historyRevise = await ctx.service.ledgerRevise.getReviseList(ctx.tender.id);
+                // 获取审批流程中右边列表
+                const auditHistory = [];
+                const times = revise.status === audit.revise.status.checkNo ? revise.times - 1 : revise.times;
+                if (times >= 1) {
+                    for (let i = 1; i <= times; i++) {
+                        auditHistory.push(await ctx.service.reviseAudit.getAuditors2ReviseList(revise.id, i));
+                    }
+                }
+                const user = await ctx.service.projectAccount.getAccountInfoById(revise.uid);
+                // 获取审批流程中左边列表
+                const auditors = await ctx.service.reviseAudit.getAuditorsWithOwner(revise.id, times);
                 const renderData = {
                     measureType, audit, revise,
                     ledgerSpread, posSpread,
-                    //reviseBills, revisePos,
+                    // reviseBills, revisePos,
                     readOnly: true,
                     historyRevise,
+                    auditHistory,
+                    auditors,
+                    auditConst: audit.revise,
+                    user,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.revise.history),
                 };
-                await this.layout('revise/history.ejs', renderData);
+                await this.layout('revise/history.ejs', renderData, 'revise/history_modal.ejs');
             } catch (err) {
                 this.log(err);
                 ctx.redirect(ctx.request.header.referer);
@@ -494,7 +516,7 @@ module.exports = app => {
                 const reviseInfo = await ctx.service.ledgerRevise.getRevise(ctx.tender.id, data.rid);
                 reviseInfo.end_time_str = reviseInfo.end_time ? reviseInfo.end_time.toLocaleString() : '';
                 ctx.body = { err: 0, msg: '', data: reviseInfo };
-            } catch(err) {
+            } catch (err) {
                 this.log(err);
                 ctx.body = this.ajaxErrorBody(err, '获取台账修订历史数据错误');
             }
@@ -510,8 +532,8 @@ module.exports = app => {
                 if (!revise) throw '台账修订数据有误';
 
                 const reviseBills = await ctx.service.ledger.getData(ctx.tender.id);
-                const revisePos = await ctx.service.pos.getPosData({tid: ctx.tender.id});
-                ctx.body = {err: 0, msg: '', data: {bills: reviseBills, pos: revisePos}};
+                const revisePos = await ctx.service.pos.getPosData({ tid: ctx.tender.id });
+                ctx.body = { err: 0, msg: '', data: { bills: reviseBills, pos: revisePos } };
             } catch (error) {
                 ctx.helper.log(error);
                 this.ajaxErrorBody(error, '获取台账修订数据错误,请刷新页面');
@@ -567,13 +589,14 @@ module.exports = app => {
             if ((isNaN(data.id) || data.id <= 0) ||
                 (!data.tid && data.tid <= 0) ||
                 (!data.block || data.block.length <= 0)) throw '参数错误';
-            return await this.ctx.service.reviseBills.pasteBlockData(this.ctx.tender.id, data.id, data.block, {crid: revise.id});
+            return await this.ctx.service.reviseBills.pasteBlockData(this.ctx.tender.id, data.id, data.block, { crid: revise.id });
         }
         async _addStd(revise, data) {
             if ((isNaN(data.id) || data.id <= 0) || !data.stdType || !data.stdNode) throw '参数错误';
             // todo 校验项目是否使用该库的权限
 
-            let stdLib, addType;
+            let stdLib,
+                addType;
             switch (data.stdType) {
                 case 'xmj':
                     stdLib = this.ctx.service.stdXmj;
@@ -594,7 +617,6 @@ module.exports = app => {
             const stdData = await stdLib.getDataByDataId(data.stdLibId, data.stdNode);
             switch (addType) {
                 case stdDataAddType.child:
-                    return await this.ctx.service.reviseBills.addStdNodeAsChild(revise.tid, data.id, stdData, revise.id);
                     break;
                 case stdDataAddType.next:
                     return await this.ctx.service.reviseBills.addStdNode(revise.tid, data.id, stdData, revise.id);
@@ -611,9 +633,9 @@ module.exports = app => {
                 return await this.ctx.service.reviseBills.addChild(revise.tid, data.id, data.dealBills, revise.id);
             } else if (data.type === 'next') {
                 return await this.ctx.service.reviseBills.addBillsNode(revise.tid, data.id, data.dealBills, revise.id);
-            } else {
-                throw '数据错误';
             }
+            throw '数据错误';
+
         }
         async _addBg(revise, data) {
             if (!data.type || !data.bgBills) throw '数据错误';
@@ -622,9 +644,9 @@ module.exports = app => {
                 return await this.ctx.service.reviseBills.addChild(revise.tid, data.id, data.bgBills, revise.id);
             } else if (data.type === 'next') {
                 return await this.ctx.service.reviseBills.addBillsNode(revise.tid, data.id, data.bgBills, revise.id);
-            } else {
-                throw '数据错误';
             }
+            throw '数据错误';
+
         }
         async _updatePos(revise, data) {
             await this.checkMeasureType(measureType.tz.value);
@@ -635,9 +657,9 @@ module.exports = app => {
                 case 'update':
                     if (data.postData instanceof Array) {
                         return await this.ctx.service.revisePos.updatePosArr(revise.tid, data.postData);
-                    } else {
-                        return await this.ctx.service.revisePos.updatePos(revise.tid, data.postData);
                     }
+                    return await this.ctx.service.revisePos.updatePos(revise.tid, data.postData);
+
                 case 'delete':
                     return await this.ctx.service.revisePos.deletePos(revise.tid, data.postData);
                 case 'paste':
@@ -651,7 +673,7 @@ module.exports = app => {
                 if (!ctx.tender.data) throw '标段数据错误';
                 const data = JSON.parse(ctx.request.body.data);
                 if (!data.postType || !data.postData) throw '数据错误';
-                const responseData = {err: 0, msg: '', data: {}};
+                const responseData = { err: 0, msg: '', data: {} };
 
                 const revise = await this.checkRevise(ctx);
 
@@ -707,10 +729,10 @@ module.exports = app => {
                 const ueType = ctx.params.ueType;
                 const compressData = ctx.request.body.data;
                 const data = JSON.parse(LzString.decompressFromUTF16(compressData));
-                const responseData = { err: 0, msg: '', data: {}, };
+                const responseData = { err: 0, msg: '', data: {} };
                 switch (ueType) {
                     case 'gcl2xmj':
-                        responseData.data = await ctx.service.reviseBills.importGclExcel(data.id, data.sheet, {crid: revise.id});
+                        responseData.data = await ctx.service.reviseBills.importGclExcel(data.id, data.sheet, { crid: revise.id });
                         break;
                 }
                 ctx.body = responseData;
@@ -725,7 +747,7 @@ module.exports = app => {
          * 填设计量(Ajax)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async deal2sgfh(ctx) {
             try {
@@ -734,7 +756,7 @@ module.exports = app => {
 
                 await this.ctx.service.revise.deal2sgfh(ctx.tender.id);
                 const ledgerData = await ctx.service.revise.getData(ctx.tender.id);
-                ctx.body = {err: 0, msg: '', data: {bills: ledgerData}};
+                ctx.body = { err: 0, msg: '', data: { bills: ledgerData } };
             } catch (err) {
                 this.log(err);
                 ctx.body = { err: 1, msg: err.toString(), data: null };
@@ -769,7 +791,7 @@ module.exports = app => {
                 if (!result) throw '添加审核人失败';
 
                 const resultData = await ctx.service.reviseAudit.getAuditor(revise.id, id, revise.times);
-                ctx.body = {err: 0, msg: '', data: resultData};
+                ctx.body = { err: 0, msg: '', data: resultData };
             } catch (err) {
                 this.log(err, '数据错误');
                 ctx.body = this.ajaxErrorBody(err);
@@ -797,7 +819,7 @@ module.exports = app => {
                 }
 
                 const resultData = await ctx.service.reviseAudit.getAuditors(revise.id, revise.times);
-                ctx.body = {err: 0, msg: '', data: resultData};
+                ctx.body = { err: 0, msg: '', data: resultData };
             } catch (err) {
                 this.log(err, '数据错误');
                 ctx.body = this.ajaxErrorBody(err);
@@ -821,11 +843,13 @@ module.exports = app => {
                 if (revise.uid !== ctx.session.sessionUser.accountId) throw '上报失败';
 
                 await ctx.service.reviseAudit.start(revise, revise.times);
-
-                ctx.body = {err: 0, msg: '', data: {}};
+                ctx.redirect('/tender/' + ctx.tender.id + '/revise/info');
+                // ctx.body = { err: 0, msg: '', data: {} };
             } catch (err) {
                 this.log(err);
-                ctx.body = this.ajaxErrorBody(err, '上报失败');
+                // ctx.body = this.ajaxErrorBody(err, '上报失败');
+                this.postError(err, '审批失败');
+                ctx.redirect('/tender/' + ctx.tender.id + '/revise/info');
             }
         }
         /**

+ 17 - 3
app/controller/setting_controller.js

@@ -90,14 +90,22 @@ module.exports = app => {
                 if (ctx.session.sessionUser.is_admin === 0) {
                     throw '没有访问权限';
                 }
+                let keyword = '';
+                if (ctx.query.keyword) {
+                    keyword = ctx.query.keyword;
+                }
 
                 const page = ctx.page;
 
+                // 过滤数据
+                ctx.service.projectAccount.searchFilter(ctx.request.query, projectId);
+                const total = await ctx.service.projectAccount.getCountWithBuilder();
+
                 // 获取数据规则
                 // const rule = ctx.service.projectAccount.rule('updateUser');
                 // const frontRule = ctx.helper.validateConvert(rule);
 
-                const total = await ctx.service.projectAccount.count({ project_id: projectId });
+                // const total = await ctx.service.projectAccount.count({ project_id: projectId });
 
                 // 获取项目用户列表
                 // const accountData = await ctx.service.projectAccount.getAllDataByCondition({
@@ -106,8 +114,12 @@ module.exports = app => {
                 //     limit: (app.config.pageSize * (page - 1)),
                 //     offset: app.config.pageSize,
                 // });
-                const columns = ['id', 'account', 'name', 'company', 'role', 'mobile', 'auth_mobile', 'telephone', 'enable', 'is_admin', 'account_group', 'bind'];
-                const accountData = await ctx.service.projectAccount.getListByProjectId(columns, projectId);
+                // const columns = ['id', 'account', 'name', 'company', 'role', 'mobile', 'auth_mobile', 'telephone', 'enable', 'is_admin', 'account_group', 'bind'];
+                // const accountData = await ctx.service.projectAccount.getListByProjectId(columns, projectId);
+                const accountData = await ctx.service.projectAccount.getListWithBuilder();
+
+                // 获取账号个数
+                const user_total = await ctx.service.projectAccount.count({ project_id: projectId });
 
                 // 分页相关
                 const pageInfo = {
@@ -122,6 +134,8 @@ module.exports = app => {
                     accountGroup,
                     permission,
                     pageInfo,
+                    keyword,
+                    user_total,
                     // rule: JSON.stringify(frontRule),
                 };
                 await this.layout('setting/user.ejs', renderData, 'setting/user_modal.ejs');

+ 44 - 20
app/controller/stage_controller.js

@@ -54,15 +54,23 @@ module.exports = app => {
                 measureType,
                 preUrl: '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.params.order,
                 stage: ctx.stage,
+                thirdParty: {
+                    gxby: ctx.session.sessionProject.gxby_status,
+                    dagl: ctx.session.sessionProject.dagl_status,
+                },
             };
             if ((ctx.stage.status === auditConst.status.uncheck || ctx.stage.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ctx.stage.user_id) {
-                data.accountGroup = accountGroup;
+                // data.accountGroup = accountGroup;
                 // 获取所有项目参与者
                 const accountList = await ctx.service.projectAccount.getAllDataByCondition({
                     where: { project_id: ctx.session.sessionProject.id, enable: 1 },
-                    columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group'],
+                    columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile'],
                 });
                 data.accountList = accountList;
+                data.accountGroup = accountGroup.map((item, idx) => {
+                    const groupList = accountList.filter(item => item.account_group === idx);
+                    return { groupName: item, groupList };
+                });
             }
             data.tenderMenu.back.children[0].url = '/tender/' + ctx.tender.id + '/measure/stage';
 
@@ -109,6 +117,14 @@ module.exports = app => {
             if (!tender.info.display.stage.realComplete) {
                 removeFieldCols(pos, spreadConst.filterCols.realCompleteCols);
             }
+            if (!this.ctx.session.sessionProject.gxby) {
+                removeFieldCols(ledger, spreadConst.filterCols.thirdPartyCols.gxby);
+                removeFieldCols(pos, spreadConst.filterCols.thirdPartyCols.gxby);
+            }
+            if (!this.ctx.session.sessionProject.dagl) {
+                removeFieldCols(ledger, spreadConst.filterCols.thirdPartyCols.dagl);
+                removeFieldCols(pos, spreadConst.filterCols.thirdPartyCols.dagl);
+            }
             if (this.ctx.stage.readOnly || this.ctx.stage.revising) {
                 ledger.readOnly = true;
                 pos.readOnly = true;
@@ -127,13 +143,13 @@ module.exports = app => {
 
             ctx.stage.user = await ctx.service.projectAccount.getAccountInfoById(ctx.stage.user_id);
             ctx.stage.auditHistory = [];
-            if (ctx.stage.times > 1) {
-                for (let i = 1; i < ctx.stage.times; i++) {
+            if (times >= 1) {
+                for (let i = 1; i <= times; i++) {
                     ctx.stage.auditHistory.push(await ctx.service.stageAudit.getAuditors(ctx.stage.id, i));
                 }
             }
             // 获取审批流程中左边列表
-            ctx.stage.auditors2 = await ctx.service.stageAudit.getAuditGroupByList(ctx.stage.id, times);
+            ctx.stage.auditors2 = await ctx.service.stageAudit.getAuditGroupByListWithOwner(ctx.stage.id, times);
             if (ctx.stage.status === auditConst.status.uncheck || ctx.stage.status === auditConst.status.checkNo) {
                 ctx.stage.auditorList = await ctx.service.stageAudit.getAuditors(ctx.stage.id, ctx.stage.times);
             }
@@ -164,6 +180,7 @@ module.exports = app => {
                 renderData.whiteList = this.ctx.app.config.multipart.whitelist;
                 renderData.imType = tenderConst.imType;
                 renderData.curAuditor = ctx.stage.curAuditor;
+                renderData.auditConst = auditConst;
                 // 获取附件列表
                 const attData = await ctx.service.stageAtt.getDataByTenderIdAndStageId(ctx.tender.id, ctx.params.order);
                 for (const index in attData) {
@@ -720,7 +737,7 @@ module.exports = app => {
                     const payCalculator = new PayCalculator(ctx, ctx.stage, ctx.tender.info);
                     await payCalculator.calculateAll(renderData.dealPay);
                     await this._updateStageCache(ctx, payCalculator);
-                    //renderData.calcBase = payCalculator.bases;
+                    // renderData.calcBase = payCalculator.bases;
                 }
                 await this.layout('stage/pay.ejs', renderData, 'stage/pay_modal.ejs');
             } catch (err) {
@@ -996,7 +1013,6 @@ module.exports = app => {
 
                 await ctx.service.stageAudit.start(ctx.stage.id, ctx.stage.times);
                 const auditor = await ctx.service.stageAudit.getCurAuditor(ctx.stage.id, ctx.stage.times);
-                console.log('stage-auditor:', auditor);
                 ctx.body = { err: 0, msg: '', data: [] };
             } catch (err) {
                 this.log(err);
@@ -1039,7 +1055,6 @@ module.exports = app => {
                     order: stageOrder,
                 });
                 const auditor = await ctx.service.stageAudit.getCurAuditor(ctx.stage.id, ctx.stage.times);
-                console.log('stage-auditor:', auditor, 'status:', stage.status);
                 ctx.body = { err: 0, msg: '', data: [] };
                 // ctx.redirect(ctx.request.header.referer);
             } catch (err) {
@@ -1282,17 +1297,20 @@ module.exports = app => {
                     if (!stream.filename) {
                         throw '请选择上传的文件!';
                     }
-                    const dirName = 'app/public/upload/stage/' + moment().format('YYYYMMDD');
+                    // const dirName = 'app/public/upload/stage/' + moment().format('YYYYMMDD');
                     // 判断文件夹是否存在,不存在则直接创建文件夹
-                    if (!fs.existsSync(path.join(this.app.baseDir, dirName))) {
-                        await fs.mkdirSync(path.join(this.app.baseDir, dirName));
-                    }
-                    const create_time = Date.parse(new Date()) / 1000;
+                    // if (!fs.existsSync(path.join(this.app.baseDir, dirName))) {
+                    //     await fs.mkdirSync(path.join(this.app.baseDir, dirName));
+                    // }
                     const fileInfo = path.parse(stream.filename);
-                    const fileName = 'stage' + create_time + '_' + index + fileInfo.ext;
-
+                    const create_time = Date.parse(new Date()) / 1000;
+                    const filepath = `public/upload/${this.ctx.tender.id}/stage/fujian_${create_time + index.toString() + fileInfo.ext}`;
+                    await ctx.helper.saveStreamFile(stream, path.resolve(this.app.baseDir, 'app', filepath));
+                    // console.log(await fs.existsSync(path.resolve(this.app.baseDir, 'app', filepath)));
+                    // const fileInfo = path.parse(stream.filename);
+                    // const fileName = 'stage' + create_time + '_' + index + fileInfo.ext;
                     // 保存文件
-                    await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, dirName, fileName));
+                    // await ctx.helper.saveStreamFile(stream, path.resolve(this.app.baseDir, dirName, fileName));
 
                     if (stream) {
                         await sendToWormhole(stream);
@@ -1306,7 +1324,7 @@ module.exports = app => {
                         filename: fileInfo.name,
                         fileext: fileInfo.ext,
                         filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size,
-                        filepath: path.join(dirName, fileName),
+                        filepath,
                     };
                     if (ctx.reUploadPermission) {
                         fileData.re_upload = 1;
@@ -1345,7 +1363,7 @@ module.exports = app => {
                 try {
                     const fileInfo = await ctx.service.stageAtt.getDataById(id);
                     if (fileInfo !== undefined && fileInfo !== '') {
-                        const fileName = path.join(this.app.baseDir, fileInfo.filepath);
+                        const fileName = path.join(this.app.baseDir, 'app', fileInfo.filepath);
                         // 解决中文无法下载问题
                         const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
                         let disposition = '';
@@ -1385,7 +1403,13 @@ module.exports = app => {
                 try {
                     const fileInfo = await ctx.service.stageAtt.getDataById(id);
                     if (fileInfo && Object.keys(fileInfo).length) {
-                        fileInfo.filepath && (responseData.data = { filepath: fileInfo.filepath.slice(3) });
+                        let filepath = fileInfo.filepath;
+                        if (!ctx.helper.canPreview(fileInfo.fileext)) {
+                            filepath = `/tender/${ctx.tender.id}/measure/stage/${ctx.params.order}/download/file/${fileInfo.id}`;
+                        } else {
+                            filepath = '/' + filepath;
+                        }
+                        fileInfo.filepath && (responseData.data = { filepath });
                     }
                 } catch (error) {
                     this.log(error);
@@ -1415,7 +1439,7 @@ module.exports = app => {
                 const fileInfo = await ctx.service.stageAtt.getDataById(data.id);
                 if (fileInfo !== undefined && fileInfo !== '') {
                     // 先删除文件
-                    await fs.unlinkSync(path.join(this.app.baseDir, fileInfo.filepath));
+                    await fs.unlinkSync(path.join(this.app.baseDir, './app', fileInfo.filepath));
                     // 再删除数据库
                     await ctx.service.stageAtt.deleteById(data.id);
                     responseData.data = '';

+ 110 - 32
app/controller/wechat_controller.js

@@ -12,6 +12,8 @@ const moment = require('moment');
 // const Controller = require('egg').Controller;
 const crypto = require('crypto');
 const maintainConst = require('../const/maintain');
+const wxConst = require('../const/wechat_template.js');
+const smsTypeConst = require('../const/sms_type');
 
 module.exports = app => {
     class WechatController extends app.BaseController {
@@ -74,11 +76,13 @@ module.exports = app => {
                     errorMessage,
                     user,
                 };
-                // ctx.body = renderData;
                 await ctx.render('wechat/bind.ejs', renderData);
             } catch (e) {
-                console.log(e);
-                ctx.body = e;
+                const renderData = {
+                    status: 1,
+                    msg: e,
+                };
+                await ctx.render('wechat/tips.ejs', renderData);
             }
         }
 
@@ -97,7 +101,7 @@ module.exports = app => {
                 if (accountData.wx_openid || ctx.session.wechatToken.openid === accountData.wx_openid) {
                     throw '该账号已经绑定过微信';
                 }
-                const wxAccountData = await ctx.service.projectAccount.getDataByCondition({ wx_openid: ctx.session.wechatToken.openid });
+                const wxAccountData = await ctx.service.projectAccount.getDataByCondition({ project_id: accountData.project_id, wx_openid: ctx.session.wechatToken.openid });
                 if (wxAccountData) {
                     throw '该微信号已绑定过本项目账号';
                 }
@@ -109,24 +113,31 @@ module.exports = app => {
                 }
                 const projectData = await ctx.service.project.getDataById(accountData.project_id);
                 // 绑定成功通知
-                const templateId = 'ElT988uf7EV8ROPKSAX7z7tN9ZxZCDMaXK5ouc9N49E';
+                const templateId = 'JGJeWU2FT4syWKUE5haEf3iiqaRJ1XrsxY1PKixqLpw';
                 const url = '';
                 const msgData = {
                     first: {
-                        value: '微信绑定成功',
+                        value: '您好,纵横云计量与微信绑定成功',
                     },
                     keyword1: {
-                        value: accountData.account,
+                        value: projectData.code,
                     },
                     keyword2: {
-                        value: '项目编号' + projectData.code + ' 账号' + accountData.account + ' 绑定成功',
+                        value: accountData.account,
+                    },
+                    keyword3: {
+                        value: moment(new Date()).format('YYYY-MM-DD'),
                     },
                     remark: {
-                        value: '欢迎使用纵横云计量,我们竭诚为您服务。',
+                        value: '感谢您的使用。',
                     },
                 };
                 await app.wechat.api.sendTemplate(ctx.session.wechatToken.openid, templateId, url, '', msgData);
-                ctx.body = '绑定成功';
+                const renderData = {
+                    status: 0,
+                    msg: '绑定成功',
+                };
+                await ctx.render('wechat/tips.ejs', renderData);
             } catch (error) {
                 this.log(error);
                 ctx.session.loginError = error;
@@ -134,10 +145,74 @@ module.exports = app => {
             }
         }
 
-        async urlchange(ctx) {
-            // 设置用户微信登录项目,跳转到对应wap页面
-            co
-            ctx.redirect(ctx.query.url);
+        // 设置用户微信登录项目,跳转到对应wap页面
+        async url2wap(ctx) {
+            try {
+                if (!ctx.query.project || !ctx.query.url) {
+                    throw '参数有误';
+                }
+                const code = ctx.query.project;
+                // 查找项目数据
+                const projectData = await ctx.service.project.getProjectByCode(code.toString().trim());
+                if (projectData === null) {
+                    throw '不存在项目数据';
+                }
+                const pa = await ctx.service.projectAccount.getDataByCondition({ project_id: projectData.id, wx_openid: ctx.session.wechatToken.openid });
+                if (!pa) {
+                    throw '该微信号未绑定此项目';
+                }
+                if (pa.enable !== 1) {
+                    throw '该账号已被停用,请联系销售人员';
+                }
+                // 设置项目和用户session记录
+                const result = await ctx.service.projectAccount.accountLogin({ project: projectData, accountData: pa }, 3);
+                if (!result) {
+                    throw '登录出错';
+                }
+                ctx.redirect(ctx.query.url);
+            } catch (error) {
+                const renderData = {
+                    status: 1,
+                    msg: error,
+                };
+                await ctx.render('wechat/tips.ejs', renderData);
+            }
+        }
+
+        async project(ctx) {
+            try {
+                // const user = await app.wechat.oauth.getUser(ctx.session.wechatToken.openid);
+                const openid = ctx.session.wechatToken.openid;
+                // const openid = 'fasdfklahsdklf';
+                const paList = await ctx.service.projectAccount.getAllDataByCondition({ where: { wx_openid: openid } });
+                const pidList = ctx.app._.uniq(ctx.app._.map(paList, 'project_id'));
+                const pList = [];
+                const redirect_url = ctx.protocol + '://' + ctx.host + '/wap/dashboard';
+                for (const p of pidList) {
+                    const pro = await ctx.service.project.getDataById(p);
+                    pList.push(pro);
+                }
+                if (pList.length === 0) {
+                    throw '该微信号未绑定任何项目';
+                }
+                // 获取系统维护信息
+                const maintainData = await ctx.service.maintain.getDataById(1);
+                const renderData = {
+                    maintainData,
+                    maintainConst,
+                    // user,
+                    pList,
+                    redirect_url,
+                };
+                // ctx.body = renderData;
+                await ctx.render('wechat/project.ejs', renderData);
+            } catch (e) {
+                const renderData = {
+                    status: 1,
+                    msg: e,
+                };
+                await ctx.render('wechat/tips.ejs', renderData);
+            }
         }
 
         async oauthTxt(ctx) {
@@ -146,27 +221,30 @@ module.exports = app => {
 
         async testwx(ctx) {
             try {
-                const sessionUser = ctx.session.sessionUser;
-                // 获取账号数据
-                const accountData = await ctx.service.projectAccount.getDataByCondition({ id: sessionUser.accountId });
-                // 解绑成功通知
-                const templateId = 'gsYUIt5CFY1uJQhiqnkhu38yi0adkZtEH9fGI49g_Lk';
-                const url = '';
-                const msgData = {
-                    first: {
-                        value: '你已成功与纵横云计量帐号解除绑定',
+                const sck = 'https://scn.ink/';
+                // 微信模板通知
+                const tender = {
+                    data: {
+                        name: 'XXX标段',
                     },
-                    keyword1: {
-                        value: sessionUser.account,
-                    },
-                    keyword2: {
-                        value: '项目编号' + ctx.session.sessionProject.code + ' 账号' + sessionUser.account + ' 解绑成功',
-                    },
-                    remark: {
-                        value: '欢迎使用纵横云计量,我们竭诚为您服务。',
+                    info: {
+                        deal_info: {
+                            buildName: 'XX项目',
+                        },
                     },
                 };
-                await app.wechat.api.sendTemplate(accountData.wx_openid, templateId, url, '', msgData);
+                ctx.tender = tender;
+                const stageInfo = await ctx.service.stage.getDataById(1704);
+                const shenpiUrl = await ctx.helper.urlToShort(ctx.protocol + '://' + ctx.host + '/wap/tender/1998/stage/' + stageInfo.order);
+                const wechatData = {
+                    wap_url: sck + shenpiUrl,
+                    qi: stageInfo.order,
+                    status: wxConst.status.success,
+                    tips: wxConst.tips.success,
+                    code: 'P1002',
+                };
+                // ctx.body = { tender, wechatData };
+                await ctx.helper.sendWechat(133, smsTypeConst.const.JL, smsTypeConst.judge.result.toString(), wxConst.template.stage, wechatData);
                 ctx.body = 'success';
             } catch (error) {
                 console.log(error);

+ 82 - 8
app/extend/helper.js

@@ -17,6 +17,7 @@ const bc = require('../lib/base_calc.js');
 const Decimal = require('decimal.js');
 Decimal.set({ precision: 50, defaults: true });
 const SMS = require('../lib/sms');
+const WX = require('../lib/wechat');
 
 module.exports = {
     _,
@@ -547,6 +548,7 @@ module.exports = {
         // 检查文件夹是否存在,不存在则直接创建文件夹
         const pathName = path.dirname(fileName);
         if (!fs.existsSync(pathName)) {
+            console.log('11111');
             await this.recursiveMkdirSync(pathName);
         }
         await fs.writeFileSync(fileName, buffer);
@@ -611,7 +613,7 @@ module.exports = {
      * @param {Object} Obj - 检查的数据
      * @param {Array} fields - 检查的属性
      * @param {Number} precision - 精度
-     * @constructor
+     * @class
      */
     checkFieldPrecision(Obj, fields, precision = 2) {
         if (Obj) {
@@ -786,7 +788,7 @@ module.exports = {
                     }
                 }
             }
-        }
+        };
         for (const m of main) {
             index[indexPre + m.id] = m;
             for (const r of rela) {
@@ -820,17 +822,24 @@ module.exports = {
         }
         return '';
     },
-    formatMoney(s, dot = ',') {
-        if (!s) return '0.00';
-        s = parseFloat((s + '').replace(/[^\d\.-]/g, '')).toFixed(2) + '';
-        let l = s.split('.')[0].split('').reverse(),
+    formatMoney(s = 0, dot = ',', decimal = 2) {
+        if (!s) {
+            s = 0;
+            return s.toFixed(decimal);
+        }
+        s = parseFloat((s + '').replace(/[^\d\.-]/g, '')).toFixed(decimal) + '';
+        if (!decimal) {
+            s += '.';
+        }
+        const l = s.split('.')[0].split('').reverse(),
             r = s.split('.')[1];
         let t = '';
         for (let i = 0; i < l.length; i++) {
             t += l[i] + ((i + 1) % 3 == 0 && (i + 1) != l.length ? dot : '');
         }
-        return t.split('').reverse().join('') + '.' + r;
+        return t.split('').reverse().join('') + (decimal === 0 ? '' : '.' + r);
     },
+
     transFormToChinese(num) {
         const changeNum = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
         const unit = ['', '十', '百', '千', '万'];
@@ -911,6 +920,33 @@ module.exports = {
         return moment(time).format('YYYY-MM-DD HH:mm:ss');
     },
 
+    // 预付款详情页时间线所需格式
+    formatDate(date) {
+        if (!date) return '';
+        const year = date.getFullYear();
+        let mon = date.getMonth() + 1;
+        let day = date.getDate();
+        let hour = date.getHours();
+        let minute = date.getMinutes();
+        let scond = date.getSeconds();
+        if (mon < 10) {
+            mon = '0' + mon.toString();
+        }
+        if (day < 10) {
+            day = '0' + day.toString();
+        }
+        if (hour < 10) {
+            hour = '0' + hour.toString();
+        }
+        if (minute < 10) {
+            minute = '0' + minute.toString();
+        }
+        if (scond < 10) {
+            scond = '0' + scond.toString();
+        }
+        return `${year}<span>${mon}-${day}</span><span>${hour}:${minute}:${scond}</span>`;
+    },
+
     timeAdd(duration) {
         const d = parseInt(duration);
         let time = 0;
@@ -977,11 +1013,39 @@ module.exports = {
         }
     },
 
+
+    async sendWechat(userId, type, judge, template, data = {}) {
+        const wechats = [];
+        if (!userId || (userId instanceof Array && userId.length === 0)) return;
+        const wxUser = await this.ctx.service.projectAccount.getAllDataByCondition({ where: { id: userId } });
+        for (const user of wxUser) {
+            if (!user.wx_openid || user.wx_openid === '') continue;
+            if (!user.wx_type || user.wx_type === '') continue;
+
+            const wxType = JSON.parse(user.wx_type);
+            if (wxType[type] && wxType[type].indexOf(judge) !== -1) {
+                wechats.push(user.wx_openid);
+            }
+        }
+
+        if (wechats.length > 0) {
+            const wx = new WX(this.ctx);
+            const tenderName = await wx.contentChange(this.ctx.tender.data.name);
+            const projectName = await wx.contentChange(this.ctx.tender.info.deal_info.buildName);
+            const param = {
+                projectName,
+                tenderName,
+            };
+            const postParam = Object.assign(param, data);
+            wx.Send(wechats, template, postParam);
+        }
+    },
+
     /**
      *
      * @param setting
      * @param data
-     * @returns {{} & any & {"!ref": string} & {"!cols"}}
+     * @return {{} & any & {"!ref": string} & {"!cols"}}
      */
     simpleXlsxSheetData(setting, data) {
         const headerStyle = {
@@ -1142,4 +1206,14 @@ module.exports = {
             }
         }
     },
+
+    /**
+     * 匹配图片、pdf(用于预览)
+     * @param {String} ext 后缀名
+     * @return {Boolean} 匹配结果
+     */
+    canPreview(ext) {
+        const reg = /(.png)|(.gif)|(.txt)|(.jpg)|(.jpeg)|(.pdf)/;
+        return reg.test(ext);
+    },
 };

+ 3 - 2
app/lib/bills_pos_convert.js

@@ -69,7 +69,7 @@ class BillsPosConvert {
 
     _convertXmj(data) {
         const xmj = this.resultTree.addData(data, ['ledger_id', 'ledger_pid', 'order', 'level', 'tender_id', 'full_path',
-            'code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'drawing_code', 'postil', 'memo']);
+            'code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'drawing_code', 'postil', 'memo', 'gxby_status', 'dagl_status']);
         return xmj;
     }
     // v1
@@ -124,7 +124,8 @@ class BillsPosConvert {
         if (pos && pos.length > 0) {
             for (const p of pos) {
                 const posUnit = xmj.unitTree.addNode({pos_name: p.name,
-                    b_code: null, name: '', unit: null, unit_price: null});
+                    b_code: null, name: '', unit: null, unit_price: null,
+                    gxby_status: p.gxby_status, dagl_status: p.dagl_status});
                 if (p.drawing_code && posUnit.drawing_code.indexOf(p.drawing_code) < 0)
                     posUnit.drawing_code.push(p.drawing_code);
                 if (p.memo) posUnit.memo.push(p.memo);

+ 91 - 0
app/lib/sql_builder.js

@@ -33,6 +33,20 @@ class SqlBuilder {
     }
 
     /**
+     * 设置新的orWhere数据
+     *
+     * @param {String} field - where中的字段名称
+     * @param {Object} data - where中的value部分
+     * @return {void}
+     */
+    setNewOrWhere(data) {
+        if (Object.keys(data).length <= 0) {
+            return;
+        }
+        this.newOrWhere.push(data);
+    }
+
+    /**
      * 更新字段设置
      *
      * @param {String} field - set的字段
@@ -54,6 +68,7 @@ class SqlBuilder {
     resetCondition() {
         this.andWhere = [];
         this.orWhere = [];
+        this.newOrWhere = [];
         this.columns = [];
         this.limit = -1;
         this.offset = -1;
@@ -147,6 +162,9 @@ class SqlBuilder {
                 this.sqlParam.push(where.field);
                 // 赋值参数
                 this.sqlParam.push.apply(this.sqlParam, where.data.value);
+            } else if (where.data.operate === 'find_in_set') {
+                whereArr.push(' find_in_set (' + where.data.value + ', ?? )');
+                this.sqlParam.push(where.field);
             } else {
                 // 普通操作
                 whereArr.push(' ?? ' + where.data.operate + ' ' + where.data.value);
@@ -179,6 +197,51 @@ class SqlBuilder {
     }
 
     /**
+     * orWhere设置-旧的不好用,独立写了一个包含括号的or兼容,传对象数组,
+     * 例:
+     * [{ field: 'office_share', value: managerSession.office, operate: 'find_in_set'},
+     * {field: 'office',value: managerSession.office,operate: '='},......]
+     *
+     * @return {void}
+     */
+    _newOrWhereBuild() {
+        if (this.newOrWhere.length <= 0) {
+            return;
+        }
+
+        const whereArr = [];
+        for (const oneOw in this.newOrWhere) {
+            for (const where of this.newOrWhere[oneOw]) {
+                if (where.operate === 'in') {
+                    // in操作
+                    const valueLength = where.value instanceof Array ? where.value.length : 1;
+                    // 生成参数集
+                    const inArr = [];
+                    for (let i = 0; i < valueLength; i++) {
+                        inArr.push('?');
+                    }
+                    const inData = inArr.join(',');
+                    whereArr.push(' ?? IN (' + inData + ')');
+                    this.sqlParam.push(where.field);
+                    // 赋值参数
+                    this.sqlParam.push.apply(this.sqlParam, where.value);
+                } else if (where.operate === 'find_in_set') {
+                    whereArr.push(' find_in_set (' + where.value + ', ?? )');
+                    this.sqlParam.push(where.field);
+                } else {
+                    // 普通操作
+                    whereArr.push(' ?? ' + where.operate + ' ' + where.value);
+                    this.sqlParam.push(where.field);
+                }
+            }
+            let whereString = whereArr.join(' OR ');
+            whereString = '(' + whereString + ')';
+            // 如果前面已经有设置过WHERE则不需要再重复添加
+            this.sql += this.sql.indexOf('WHERE') > 0 ? ' AND ' + whereString : ' WHERE ' + whereString;
+        }
+    }
+
+    /**
      * limit设置
      *
      * @return {void}
@@ -230,6 +293,7 @@ class SqlBuilder {
         this._setDataBuild();
         this._andWhereBuild();
         this._orWhereBuild();
+        this._newOrWhereBuild();
 
         this._orderByBuild();
         this._limitBuild();
@@ -242,5 +306,32 @@ class SqlBuilder {
         return [sql, sqlParam];
     }
 
+    /**
+     * 构建sql
+     *
+     * @param {String} tableName - 表名
+     * @param {String} type - 类型
+     * @return {Array} - 返回数组,第一个元素为sql语句,第二个元素为sql中问号部分的param
+     */
+    buildCount(tableName, type = 'select') {
+        this.type = type;
+        this._typeBuild(tableName);
+        if (this.sql === '') {
+            throw '类型错误';
+        }
+
+        this._setDataBuild();
+        this._andWhereBuild();
+        this._orWhereBuild();
+        this._newOrWhereBuild();
+
+        const sql = this.sql;
+        const sqlParam = this.sqlParam;
+        // 重置数据
+        // this.resetCondition();
+
+        return [sql, sqlParam];
+    }
+
 }
 module.exports = SqlBuilder;

+ 189 - 0
app/lib/wechat.js

@@ -0,0 +1,189 @@
+'use strict';
+
+/**
+ * 微信模板信息发送相关接口
+ *
+ * @author ellisran
+ * @date 2018/1/25
+ * @version
+ */
+
+// const xmlReader = require('xmlreader');
+const moment = require('moment');
+const wxConst = require('../const/wechat_template.js');
+class WX {
+
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局变量
+     * @return {void}
+     */
+    constructor(ctx) {
+        this.ctx = ctx;
+    }
+
+    /**
+     * 发送微信审批通知模板
+     *
+     * @param {Array} wx_openid - 发送的微信账号
+     * @param {String} template - 模板id
+     * @param {Object} data - 一些模板展示数据
+     * @return {Boolean} - 发送结果
+     */
+    async Send(wx_openid, template, data) {
+        let flag = false;
+        try {
+            const templateId = template;
+            const sck = 'https://scn.ink/';
+            for (const openid of wx_openid) {
+                let url = '';
+                let msgData = '';
+                let remark = '';
+                switch (templateId) {
+                    case wxConst.template.stage :
+                        url = this.ctx.protocol + '://' + this.ctx.host + '/wx/url2wap?project=' + data.code + '&url=' + sck + data.wap_url;
+                        msgData = {
+                            first: {
+                                value: '您好,本期计量' + data.tips,
+                            },
+                            keyword1: {
+                                value: data.projectName,
+                            },
+                            keyword2: {
+                                value: data.tenderName,
+                            },
+                            keyword3: {
+                                value: '第' + data.qi + '期',
+                            },
+                            keyword4: {
+                                value: data.status,
+                            },
+                            remark: {
+                                value: '查看审批详情,请登录PC端系统。',
+                            },
+                        };
+                        break;
+                    case wxConst.template.change:
+                        url = this.ctx.protocol + '://' + this.ctx.host + '/wx/url2wap?project=' + data.code + '&url=' + sck + data.wap_url;
+                        remark = data.status === wxConst.status.check ? '微信可快速审批,如需进行详细审批' :
+                            (data.status === wxConst.status.success ? '审批已通过,查看审批结果' : '审批被退回,查看退回结果');
+                        msgData = {
+                            first: {
+                                value: '您好,工程变更申请' + data.tips,
+                            },
+                            keyword1: {
+                                value: data.projectName,
+                            },
+                            keyword2: {
+                                value: data.tenderName,
+                            },
+                            keyword3: {
+                                value: data.c_name,
+                            },
+                            remark: {
+                                value: remark + ',请登录PC端系统。',
+                            },
+                        };
+                        break;
+                    case wxConst.template.ledger:
+                        remark = data.status === wxConst.status.check ? '微信暂无法在线审批' :
+                            (data.status === wxConst.status.success ? '审批已通过,查看审批结果' : '审批被退回,查看退回结果');
+                        msgData = {
+                            first: {
+                                value: '您好,台帐' + data.tips,
+                            },
+                            keyword1: {
+                                value: data.projectName,
+                            },
+                            keyword2: {
+                                value: data.tenderName,
+                            },
+                            keyword3: {
+                                value: moment(new Date(data.begin_time)).format('YYYY-MM-DD'),
+                            },
+                            remark: {
+                                value: remark + ',请登录PC端系统。',
+                            },
+                        };
+                        break;
+                    case wxConst.template.revise:
+                        remark = data.status === wxConst.status.check ? '微信暂无法在线审批' :
+                            (data.status === wxConst.status.success ? '审批已通过,查看审批结果' :
+                                (data.status === wxConst.status.back ? '审批被退回,查看退回结果' : '审批已上报,查看审批结果'));
+                        msgData = {
+                            first: {
+                                value: '您好,台帐修订' + data.tips,
+                            },
+                            keyword1: {
+                                value: data.projectName,
+                            },
+                            keyword2: {
+                                value: data.tenderName,
+                            },
+                            keyword3: {
+                                value: moment(new Date(data.begin_time)).format('YYYY-MM-DD'),
+                            },
+                            remark: {
+                                value: remark + ',请登录PC端系统。',
+                            },
+                        };
+                        break;
+                    case wxConst.template.material:
+                        remark = data.status === wxConst.status.check ? '微信暂无法在线审批' :
+                            (data.status === wxConst.status.success ? '审批已通过,查看审批结果' : '审批被退回,查看退回结果');
+                        msgData = {
+                            first: {
+                                value: '您好,第' + data.qi + '期材料调差申请' + data.tips,
+                            },
+                            keyword1: {
+                                value: data.projectName,
+                            },
+                            keyword2: {
+                                value: data.tenderName,
+                            },
+                            keyword3: {
+                                value: moment(new Date(data.begin_time)).format('YYYY-MM-DD'),
+                            },
+                            keyword4: {
+                                value: data.m_tp,
+                            },
+                            keyword5: {
+                                value: data.hs_m_tp,
+                            },
+                            remark: {
+                                value: remark + ',请登录PC端系统。',
+                            },
+                        };
+                        break;
+                    default:break;
+                }
+                if (msgData !== '') {
+                    // console.log(openid, templateId, url, msgData);
+                    await this.ctx.app.wechat.api.sendTemplate(openid, templateId, url, '', msgData);
+                }
+            }
+            flag = true;
+        } catch (e) {
+            console.log(e);
+            flag = false;
+        }
+        return flag;
+    }
+
+    /**
+     * 关键字转换,并限制20个字以内
+     *
+     * @param {String} content - 内容
+     * @return {Object} - 解析结果
+     */
+    async contentChange(content) {
+        let str = content.replace(/【/g, '(');
+        str = str.replace(/】/g, ')');
+        str = str.replace(/工程款/g, '***');
+        str = str.length > 20 ? str.substring(0, 17) + '...' : str;
+        return str;
+    }
+}
+
+module.exports = WX;

+ 73 - 0
app/middleware/advance_check.js

@@ -0,0 +1,73 @@
+'use strict';
+
+/**
+ * 预付款中间件
+ * @author lanjianrong
+ * @date 2020/8/10
+ * @version
+ */
+
+const status = require('../const/audit').advance.status;
+// const _ = require('lodash')
+
+module.exports = () => {
+    /**
+     * 预付款 中间件
+     * 1. 读取期数据
+     * 2. 检验用户是否参与期(不校验具体权限)
+     *
+     * 写入ctx.advance数据
+     * 其中:
+     * advance.auditors: 审批人列表(退回原报时,加载上一流程)
+     * advance.curAuditor: 当前审批人(未上报为空,审批通过 or 退回原报时,为空)
+     * advance.readonly: 登录人,是否可操作
+     * advance.curTimes: 当前登录人,操作、查阅数据times
+     * advance.curOrder: 当前登录人,操作、查阅数据order
+     *
+     * 该方法为通用方法,如需advance其他数据,请在controller中查询
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* advanceCheck(next) {
+        try {
+            // 读取预付款id
+            const id = parseInt(this.params.order);
+            if (!id || id <= 0) {
+                throw '您访问的预付款期不存在';
+            }
+            const advance = yield this.service.advance.getDataById(id);
+            if (!advance) {
+                throw '预付款数据错误';
+            }
+
+            advance.user = yield this.service.projectAccount.getAccountInfoById(advance.uid);
+            // 读取审核人列表数据
+            advance.auditors = yield this.service.advanceAudit.getAuditors(advance.id, advance.times);
+            advance.curAuditor = yield this.service.advanceAudit.getCurAuditor(advance.id, advance.times);
+
+            // 获取最新的期
+            // advance.highOrder = yield this.service.advance.getLastestAdvance(this.tender.id, type, true)
+
+            this.advance = advance;
+            yield next;
+        } catch (err) {
+            this.helper.log(err);
+            // 输出错误到日志
+            if (err.stack) {
+                this.logger.error(err);
+            } else {
+                this.getLogger('fail').info(
+                    JSON.stringify({
+                        error: err,
+                        project: this.session.sessionProject,
+                        user: this.session.sessionUser,
+                        body: this.session.body,
+                    })
+                );
+            }
+            // 重定向值标段管理
+            this.redirect(this.request.headers.referer);
+        }
+    };
+};

+ 0 - 1
app/middleware/material_check.js

@@ -43,7 +43,6 @@ module.exports = options => {
             if (!material) {
                 throw '材料调差期数据错误';
             }
-
             // 读取原报、审核人数据
             material.auditors = yield this.service.materialAudit.getAuditors(material.id, material.times);
             // if (material.status > 1) {

+ 7 - 2
app/middleware/tender_check.js

@@ -23,7 +23,7 @@ module.exports = options => {
     return function* tenderCheck(next) {
         try {
             // 读取标段数据
-            const tender = { id: parseInt(this.params.id), };
+            const tender = { id: parseInt(this.params.id) };
             if (!tender.id) {
                 throw '当前未打开标段';
             }
@@ -43,6 +43,7 @@ module.exports = options => {
                 tender.data.ledger_times = 1;
             }
             if (tender.data.project_id !== this.session.sessionProject.id) {
+
                 throw '您无权查看该项目';
             } else {
                 const accountId = this.session.sessionUser.accountId;
@@ -62,11 +63,14 @@ module.exports = options => {
                     const reviseAuditorsId = this.helper._.map(reviseAuditors, 'audit_id');
                     const materialAuditors = yield this.service.materialAudit.getAllAuditors(tender.id);
                     const materialAuditorsId = this.helper._.map(materialAuditors, 'aid');
+                    const advanceAuditors = yield this.service.advanceAudit.getAllAuditors(tender.id);
+                    const advanceAuditorsId = this.helper._.map(advanceAuditors, 'audit_id');
                     const tenderPermission = this.session.sessionUser.permission ? this.session.sessionUser.permission.tender : null;
                     if (auditorsId.indexOf(accountId) === -1 && tender.data.user_id !== accountId &&
                         (tenderPermission === null || tenderPermission === undefined || tenderPermission.indexOf('2') === -1) &&
                         stageAuditorsId.indexOf(accountId) === -1 && changeAuditorsId.indexOf(accountId) === -1 &&
-                        reviseAuditorsId.indexOf(accountId) === -1 && materialAuditorsId.indexOf(accountId) === -1) {
+                        reviseAuditorsId.indexOf(accountId) === -1 && materialAuditorsId.indexOf(accountId) === -1 &&
+                        advanceAuditorsId.indexOf(accountId) === -1) {
                         throw '您无权查看该项目';
                     }
                 }
@@ -77,6 +81,7 @@ module.exports = options => {
             this.session.sessionProject.page_show = yield this.service.project.getPageshow(this.session.sessionProject.id);
             yield next;
         } catch (err) {
+            console.log(err);
             // 输出错误到日志
             if (err.stack) {
                 this.logger.error(err);

+ 3 - 2
app/middleware/wechat_auth.js

@@ -11,12 +11,13 @@ module.exports = (options, app) => {
         try {
             // 判断session
             if (!ctx.session.wechatToken && ctx.query.code) {
-                const token = await app.wechat.oauth.getAccessToken(ctx.query.code);
+                const token = await ctx.app.wechat.oauth.getAccessToken(ctx.query.code);
                 ctx.session.wechatToken = token.data;
             }
             const wechatToken = ctx.session.wechatToken;
             if (!wechatToken) {
-                ctx.redirect('/wx/oauth?redirect_uri=' + encodeURIComponent(ctx.request.headers.referer));
+                ctx.redirect('/wx/oauth?redirect_uri=' + encodeURIComponent(ctx.protocol + '://' + ctx.host + ctx.request.url));
+                return;
             }
             // 同步系统维护信息
             await ctx.service.maintain.syncMaintainData();

+ 96 - 11
app/public/css/main.css

@@ -61,6 +61,30 @@ font-size: .875rem;
 }
 .bs-popover-auto[x-placement^="bottom"] .arrow::after, .bs-popover-bottom .arrow::after{
   border-bottom-color:#000;
+}/*
+.btn.disabled, .btn:disabled {
+  opacity:.4
+}*/
+.btn-primary.disabled, .btn-primary:disabled {
+  color:#fff;
+  border-color:#666;
+  background: #666
+}
+.btn-outline-primary.disabled, .btn-outline-primary:disabled {
+  color:#666;
+  border-color:#666;
+  background: #ddd
+}
+.btn-outline-primary.disabled .badge, .btn-outline-primary:disabled .badge{
+  background: #999
+}
+.group-tab .btn-light{
+  color:#007bff;
+  border-color:#d3d9df;
+  cursor: pointer;
+}
+.group-tab .btn-light.active{
+  cursor: default;
 }
 /*在谷歌下移除input[number]的上下箭头*/
 input.nospin[type='number']::-webkit-outer-spin-button,
@@ -80,23 +104,23 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
 /*滚动条*/
 /* 滚动条 */
 /*水平滚动条的样式*/
-/*::-webkit-scrollbar-thumb:horizontal { 
+/*::-webkit-scrollbar-thumb:horizontal {
 	width: 5px;
 	background-color: #e9ecef;
 	-webkit-border-radius: 0;
 }*/
 /*滚动条的背景颜色,滚动条的圆角宽度*/
 /*::-webkit-scrollbar-track-piece {
-	background-color: #efefef; 
-	-webkit-border-radius: 0; 
+	background-color: #efefef;
+	-webkit-border-radius: 0;
 }*/
 /*滚动条的宽度,滚动条的高度*/
 /*::-webkit-scrollbar {
-	width: 14px; 
-	height: 14px; 
+	width: 14px;
+	height: 14px;
 }*/
 /*垂直滚动条的样式*/
-/*::-webkit-scrollbar-thumb:vertical { 
+/*::-webkit-scrollbar-thumb:vertical {
 	height: 50px;
 	background-color: #e9ecef;
 	-webkit-border-radius: 0;
@@ -105,7 +129,7 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
 	border: 1px solid #ced4da;
 }*/
 /*滚动条的hover样式*/
-/*::-webkit-scrollbar-thumb:hover { 
+/*::-webkit-scrollbar-thumb:hover {
 	height: 50px;
 	background-color: #ced4da;
 	-webkit-border-radius: 0;
@@ -677,7 +701,7 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
   font-size: 14px
 }
 .bd-toc {
-  
+
     position: sticky;
     top:3rem;
     height: calc(100vh - 10rem);
@@ -832,7 +856,7 @@ body{
   line-height: 30px;
 }
 .panel-title > .title-main .btn.pull-right {
-    margin: 5px 0 0 0 
+    margin: 5px 0 0 0
 }
 .panel-content{
   padding-top:35px;
@@ -1055,6 +1079,67 @@ a.maintain-icon .fa{
     }
 }
 
-a.maintain-icon:hover .fa{ 
+a.maintain-icon:hover .fa{
     animation-iteration-count:0
-}
+}
+/*审批列表*/
+.timeline-list .timeline-list-item{
+  position: relative;
+}
+.timeline-list-item .timeline-item-date{
+  width:50px;
+  position:absolute;
+  left:0px;
+  text-align: center;
+  color:#ccc;
+}
+.timeline-list-item .timeline-item-date span{
+  display: block;
+  color: #333;
+  text-align: center;
+}
+.timeline-list-item .timeline-item-tail{
+  position: absolute;
+  top: 10px;
+  left: 55px;
+  height: calc(100% - 10px);
+  border-left: 1px solid #ddd ;
+}
+.timeline-list-item .timeline-item-icon{
+  width:20px;
+  height:20px;
+  position: absolute;
+  border-radius: 100px;
+  text-align: center;
+  line-height: 20px;
+  left:45px;
+}
+.timeline-list-item .timeline-item-content{
+  position: relative;
+  margin: 0 0 0 70px;
+  word-break: break-word;
+}
+
+.book-list{
+  padding: 0;
+  margin: 0;
+  height: 285px;
+  overflow-y: auto;
+}
+.book-list dt{
+  padding-left:5px;
+  background-color: #e2e6ea
+}
+.book-list dd{
+  padding-left:15px;
+  cursor: pointer;
+}
+.book-list dd:hover{
+  background-color: #f2f2f2
+}
+.dd-content {
+  display: none;
+}
+.fold-card {
+  display: none;
+}

+ 274 - 0
app/public/js/advance.js

@@ -0,0 +1,274 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author lanjianrong
+ * @date 2020/8/12
+ * @version
+ */
+
+$(document).ready(function () {
+    $('#advance_add').click(function() {
+        postData(`${window.location.pathname}/${type}/create`, {}, res => {
+            const html = `<tr>
+                <td><a href="/tender/${res.tid}/advance/${res.id}/detail" data-id="${res.id}">第${res.order}期</a></td>
+                <td>${res.pay_ratio || 0}%</td>
+                <td class="text-right">${formatMoney((res.cur_amount || 0), ',', decimal)}</td>
+                <td class="text-right">${formatMoney((res.prev_amount || 0), ',', decimal)}</td>
+                <td class="text-right">${formatMoney((res.prev_total_amount || 0),',', decimal)}</td>
+                <td><a class="btn btn-sm" href="#file" data-toggle="modal" data-target="#file"><i class="fa fa-paperclip "></i> 3</a></td>
+                <td>${auditConst.statusString[res.status]}</td>
+                <td><a href="/tender/${res.tid}/advance/${res.id}/detail" class="btn btn-primary btn-sm">编辑</a></td>
+                </tr>`
+            $('#advanceList').prepend(html)
+            $('#advance_add').remove()
+            window.location.href = `${window.location.pathname}/${res.id}/detail`
+        })
+        return false
+    })
+
+    $('#advanceList').on('click', `a[href="#file"]`, function() {
+        const { fileList = [] } = advanceList.find(item => item.id === parseInt($(this).data('id')))
+        $('#file-content').empty()
+        let html = `<thead>
+                            <tr>
+                                <th width="70%">文件名</th>
+                                <th width="20%">上传时间</th>
+                                <th width="10%">操作</th>
+                            </tr>
+                        </thead>`
+        fileList.forEach(file => {
+            html+= `<tr>
+                            <td>${file.filename}</td>
+                            <td>${new Date(file.create_time).toLocaleDateString()}</td>
+                            <td>
+                                <a href="/${file.filepath}" target="_blank" title="下载"><i class="fa fa-download"></i></a>
+                            </td>
+                        </tr>`
+        })
+        $('#file-content').append(html)
+    })
+
+    $('a[data-target="#sp-list" ]').on('click', function () {
+        const id = $(this).data('vid')
+        postData(`${window.location.pathname.replace('/material', '')}/${id}/auditors`, {}, (res) => {
+            const { auditHistory, auditors, user } = res
+            let auditorsHTML = ''
+            let historyHTML = ''
+            auditors.forEach((auditor, idx) => {
+                if (idx === 0) {
+                    auditorsHTML += `<li class="list-group-item">
+                        <i class="fa fa fa-play-circle fa-rotate-90"></i> ${auditor.name}
+                        <small class="text-muted">${auditor.role}</small>
+                        <span class="pull-right">原报</span>
+                    </li>`
+                } else if(idx === auditors.length -1 && idx !== 0) {
+                    auditorsHTML += `<li class="list-group-item">
+                        <i class="fa fa fa-stop-circle"></i> ${auditor.name}
+                        <small class="text-muted">${auditor.role}</small>
+                        <span class="pull-right">终审</span>
+                    </li>`
+                } else {
+                    auditorsHTML += `<li class="list-group-item">
+                        <i class="fa fa-chevron-circle-down"></i> ${auditor.name}
+                        <small class="text-muted">${auditor.role}</small>
+                        <span class="pull-right">${transFormToChinese(idx)}审</span>
+                    </li>`
+                }
+            })
+            $('#auditor-list').empty()
+            $('#auditor-list').append(auditorsHTML)
+            auditHistory.forEach((auditors, idx) => {
+                historyHTML += `<div class="${idx < auditHistory.length - 1 ? 'fold-card' : ''}">
+                <div class="text-center text-muted"
+                    ${idx === auditHistory.length - 1 ? 'id="end-target"' : ""}>
+                    ${idx === auditHistory.length - 1 ? 1 : idx+1}#</div>
+                <ul class="timeline-list list-unstyled mt-2">`
+                auditors.forEach((auditor, index) => {
+                    if (index === 0) {
+                        historyHTML += `<li class="timeline-list-item pb-2">
+                            <div class="timeline-item-date">
+                                ${formatDate(auditor.create_time)}
+                            </div>
+                            <div class="timeline-item-tail"></div>
+                            <div class="timeline-item-icon bg-success text-light">
+                                <i class="fa fa-caret-down"></i>
+                            </div>
+                            <div class="timeline-item-content">
+                                <div class="card">
+                                    <div class="card-body p-3">
+                                        <div class="card-text">
+                                            <p class="mb-1"><span
+                                                    class="h5">${user.name}</span><span
+                                                    class="pull-right text-success">${idx !== 0 ? '重新' : ''}上报审批</span>
+                                            </p>
+                                            <p class="text-muted mb-0">${user.role}</p>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </li>
+                        <li class="timeline-list-item pb-2">
+                            <div class="timeline-item-date">
+                                ${formatDate(auditor.end_time)}
+                            </div>`
+
+                            if(index < auditors.length - 1) {
+                                historyHTML += `<div class="timeline-item-tail"></div>`
+                            }
+                            if(auditor.status === auditConst.status.checked) {
+                                historyHTML += `<div class="timeline-item-icon bg-success text-light">
+                                    <i class="fa fa-check"></i>
+                                </div>`
+
+                            } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {
+                                historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                    <i class="fa fa-level-up"></i>
+                                </div>`
+                            } else if(auditor.status === auditConst.status.checking) {
+                                historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                    <i class="fa fa-ellipsis-h"></i>
+                                </div>`
+                            } else {
+                                historyHTML += `<div class="timeline-item-icon bg-secondary text-light"></div>`
+
+                            }
+                            historyHTML += `<div class="timeline-item-content">
+                                <div class="card">
+                                    <div class="card-body p-3">
+                                        <div class="card-text">
+                                            <p class="mb-1"><span class="h5">${auditor.name}</span><span
+                                                    class="pull-right ${auditConst.statusClass[auditor.status]}">${auditConst.statusString[auditor.status]}</span>
+                                            </p>
+                                            <p class="text-muted mb-0">${auditor.role}</p>
+                                        </div>
+                                    </div>`
+                            if (auditor.opinion) {
+                            historyHTML += `<div class="card-body p-3 border-top">
+                                    <p style="margin: 0;">${auditor.opinion}</p>
+                                </div>`
+                            }
+                            historyHTML += `</div></div></li>`
+                    } else {
+                        historyHTML += `<li class="timeline-list-item pb-2">
+                        <div class="timeline-item-date">
+                            ${formatDate(auditor.end_time)}
+                        </div>`
+
+                        if(index < auditors.length - 1) {
+                            historyHTML += `<div class="timeline-item-tail"></div>`
+                        }
+                        if(auditor.status === auditConst.status.checked) {
+                            historyHTML += `<div class="timeline-item-icon bg-success text-light">
+                                <i class="fa fa-check"></i>
+                            </div>`
+                        } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {
+                            historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                <i class="fa fa-level-up"></i>
+                            </div>`
+
+                        } else if(auditor.status === auditConst.status.checking) {
+                            historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                <i class="fa fa-ellipsis-h"></i>
+                            </div>`
+                        } else {
+                            historyHTML += `<div class="timeline-item-icon bg-secondary text-light"></div>`
+                        }
+                        historyHTML += `<div class="timeline-item-content">
+                        <div class="card">
+                            <div class="card-body p-3">
+                                <div class="card-text">
+                                    <p class="mb-1"><span class="h5">${auditor.name}</span>
+                                        <span
+                                            class="pull-right
+                                                            ${auditConst.statusClass[auditor.status]}">${auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''}
+                                            ${auditor.status === auditConst.status.checkNo ? user.name : ''}
+                                            ${auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : ''}
+                                        </span>
+                                    </p>
+                                    <p class="text-muted mb-0">${auditor.role}</p>
+                                </div>
+                            </div>`
+
+                        if (auditor.opinion) {
+                        historyHTML += `<div class="card-body p-3 border-top">
+                            <p style="margin: 0;">${auditor.opinion} </p>
+                        </div>`
+                        }
+                        historyHTML += `</div></div></li>`
+                    }
+                })
+                historyHTML += '</ul></div>'
+                if(idx === auditHistory.length - 1 && auditHistory.length !== 1) {
+                    historyHTML += `<div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
+                    data-idx="${idx + 1}">展开历史审批流程</a></div>`
+                }
+            })
+            $('#audit-list').empty()
+            $('#audit-list').append(historyHTML)
+        })
+    })
+    function formatMoney(s, dot = ',', decimal = 2) {
+        if (!s) {
+            s = 0;
+            return s.toFixed(decimal);
+        }
+        s = parseFloat((s + '').replace(/[^\d\.-]/g, '')).toFixed(decimal) + '';
+        if (!decimal) {
+            s += '.';
+        }
+        const l = s.split('.')[0].split('').reverse(),
+            r = s.split('.')[1];
+        let t = '';
+        for (let i = 0; i < l.length; i++) {
+            t += l[i] + ((i + 1) % 3 == 0 && (i + 1) != l.length ? dot : '');
+        }
+        return t.split('').reverse().join('') + (decimal === 0 ? '' : '.' + r);
+    }
+
+    function transFormToChinese(num) {
+        const changeNum = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
+        const unit = ['', '十', '百', '千', '万'];
+        num = parseInt(num);
+        const getWan = temp => {
+            const strArr = temp.toString().split('').reverse();
+            let newNum = '';
+            for (let i = 0; i < strArr.length; i++) {
+                newNum = (i == 0 && strArr[i] == 0 ? '' : (i > 0 && strArr[i] == 0 && strArr[i - 1] == 0 ? '' : changeNum[strArr[i]] + (strArr[i] == 0 ? unit[0] : unit[i]))) + newNum;
+            }
+            return strArr.length === 2 && newNum.indexOf('一十') !== -1 ? newNum.replace('一十', '十') : newNum;
+        };
+        const overWan = Math.floor(num / 10000);
+        let noWan = num % 10000;
+        if (noWan.toString().length < 4) noWan = '0' + noWan;
+        return overWan ? getWan(overWan) + '万' + getWan(noWan) : getWan(num);
+    }
+
+    function formatDate(date) {
+        if (!date) return '';
+        date = new Date(date)
+        const year = date.getFullYear();
+        let mon = date.getMonth() + 1;
+        let day = date.getDate();
+        let hour = date.getHours();
+        let minute = date.getMinutes();
+        let scond = date.getSeconds();
+        if (mon < 10) {
+            mon = '0' + mon.toString();
+        }
+        if (day < 10) {
+            day = '0' + day.toString();
+        }
+        if (hour < 10) {
+            hour = '0' + hour.toString();
+        }
+        if (minute < 10) {
+            minute = '0' + minute.toString();
+        }
+        if (scond < 10) {
+            scond = '0' + scond.toString();
+        }
+        return `${year}<span>${mon}-${day}</span><span>${hour}:${minute}:${scond}</span>`;
+    }
+})

+ 380 - 0
app/public/js/advance_audit.js

@@ -0,0 +1,380 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author lanjianrong
+ * @date 2020/8/7
+ * @version
+ */
+
+$(document).ready(function () {
+    autoFlashHeight()
+    let oldVal = null
+    let timer = null
+    let oldSearchVal = null
+    handleFileList(fileList)
+    // 控制上报弹窗的文案
+    function checkModal(isHide) {
+        if (isHide) {
+            $('#tm-fail').show()
+            $('#tm-success').hide()
+            $('#tm-submit').hide()
+        } else {
+            $('#tm-fail').hide()
+            $('#tm-success').show()
+            $('#tm-submit').show()
+        }
+    }
+
+    $('#gr-search').bind('input propertychange', function(e) {
+        oldSearchVal = e.target.value
+        timer && clearTimeout(timer)
+        timer = setTimeout(() => {
+            const newVal = $('#gr-search').val()
+            let html = ''
+            if (newVal && newVal === oldSearchVal) {
+                accountList.filter(item => item && cur_uid !== item.id && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1))).forEach(item => {
+                    html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                        <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                class="ml-auto">${item.mobile || ''}</span></p>
+                        <span class="text-muted">${item.role || ''}</span>
+                    </dd>`
+                })
+                $('.book-list').empty()
+                $('.book-list').append(html)
+            } else {
+                if (!$('.acc-btn').length) {
+                    accountGroup.forEach((group, idx) => {
+                        if (!group) return
+                        html += `<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="${idx}" data-type="hide"><i class="fa fa-plus-square"></i>
+                        </a> ${group.groupName}</dt>
+                        <div class="dd-content" data-toggleid="${idx}">`
+                        group.groupList.forEach(item => {
+                            if (item.id !== cur_uid) {
+                                html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                                    <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                            class="ml-auto">${item.mobile || ''}</span></p>
+                                    <span class="text-muted">${item.role || ''}</span>
+                                </dd>`
+                            }
+                        });
+                        html += '</div>'
+                    })
+                    $('.book-list').empty()
+                    $('.book-list').append(html)
+                }
+            }
+        }, 400);
+    })
+
+    // 添加审批流程按钮逻辑
+    $('.book-list').on('click', 'dt', function () {
+        const idx = $(this).find('.acc-btn').attr('data-groupid')
+        const type = $(this).find('.acc-btn').attr('data-type')
+        if (type === 'hide') {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).show(() => {
+                $(this).children().find('i').removeClass('fa-plus-square').addClass('fa-minus-square-o')
+                $(this).find('.acc-btn').attr('data-type', 'show')
+
+            })
+        } else {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).hide(() => {
+                $(this).children().find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square')
+                $(this).find('.acc-btn').attr('data-type', 'hide')
+            })
+        }
+        return false
+    })
+
+    // 添加到审批流程中
+    $('dl').on('click', 'dd', function () {
+        const id = parseInt($(this).data('id'))
+        if (id !== 0) {
+            postData(preUrl + '/audit/add', { auditorId: id }, (data) => {
+                // <p class="m-0 ml-2"><small class="text-muted">中交第一公路工程局有限公司国道311线满别公路施工一分部</small></p>
+                const html = []
+                html.push('<li class="list-group-item" auditorId="'+ data.audit_id +'"><a href="javascript: void(0)" class="text-danger pull-right">移除</a>')
+                html.push('<span>')
+                html.push(data.order + ' ')
+                html.push(data.name + ' ')
+                html.push('</span>')
+                html.push('<small class="text-muted">')
+                html.push(data.role)
+                html.push('</small>')
+                html.push(`<p class="m-0 ml-2"><small class="text-muted">${data.company}</small></p></li>`)
+                $('#auditors').append(html.join(''))
+
+                if ($('.fa-stop-circle').length) {
+                    $('.fa-stop-circle').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down')
+                }
+                const auditorsHTML = `<li class="list-group-item" data-auditorId='${data.audit_id}'><i class="fa fa fa-stop-circle" ></i> ${data.name} <small class="text-muted">${data.role}</small></li>`
+                $('#auditors2').append(auditorsHTML)
+
+                if ($('#auditors')[0].children.length > 0) {
+                    checkModal(false)
+                }
+            });
+        }
+    });
+
+    // 删除审批人
+    $('body').on('click', '#auditors li>a', function () {
+        const li = $(this).parent()
+        const data = {
+            auditorId: parseInt(li.attr('auditorId')),
+        };
+        postData(preUrl + '/audit/delete', data, (result) => {
+            li.remove();
+            for (const rst of result) {
+                const aLi = $('li[auditorId=' + rst.audit_id + ']');
+                $('span', aLi).text(rst.order + ' ' + rst.name + ' ')
+            }
+            // 删除左边审核人
+            $(`#auditors2 li[data-auditorId='${data.auditorId}']`).remove()
+        })
+    })
+
+    $('#au-btn').on('click','a', function() {
+        const content = $(this).data('target')
+        switch (content) {
+            case '#sub-sp':
+                if ($('#auditors')[0].children.length) {
+                    checkModal(false)
+                }
+                break;
+            default:
+                break;
+        }
+    })
+
+    // 上报审批
+    $('#tm-submit').click(function() {
+        const pay_ratio = parseFloat($(`.pay-input[data-type=0]`).val())
+        const cur_amount = parseFloat($(`.pay-input[data-type=1]`).val())
+        if (!pay_ratio || !cur_amount) {
+            return toastr.error('请填写本期金额!')
+        }
+        const prev_amount = prevAdvance && prevAdvance.prev_total_amount || 0
+        const prev_total_amount = ZhCalc.add(cur_amount, prev_amount)
+        const remark = filterText($('#ad-remark').val())
+        const data = {pay_ratio, cur_amount, prev_amount, prev_total_amount, remark, status: auditConst.status.checking}
+        postData(preUrl + '/audit/start', data, (data) => {
+            window.location.reload()
+        }, () => {
+            window.location.reload()
+        })
+    })
+
+    // 重新上报
+    $('#sp-list2-btn').click(() => {
+        $('#tm-submit').trigger('click')
+    })
+    // 转化成两位小数
+    function fixedToSub(s, decimal = 2) {
+        return parseFloat(parseFloat(s).toFixed(decimal))
+    }
+    // 自动转换支付比例和本期金额
+    $('.pay-input').on('input propertychange', function(e) {
+        let val = parseFloat(e.target.value)
+        const p_amount = prevAdvance && prevAdvance.prev_total_amount || 0 // 截止本期金额
+        const re_amount = ZhCalc.sub(advancePayTotal, p_amount) // 剩余未付款的总额
+        const min = parseFloat($(this).attr('min'))
+        const max = parseFloat($(this).attr('max'))
+        const type = parseInt($(this).data('type'))
+        let pay_ratio = null
+        let cur_amount = null
+        if (val < min) {
+            // 限制最小值为min
+            $(this).val(min)
+            val = min
+        }
+        if (max && val > max) {
+            // 限制最大值为max
+            $(this).val(max)
+            val = max
+        }
+        // 本期金额转化
+        if (type === 1) {
+            if (val > re_amount) {
+                // 限制不能超过最大值
+                val = re_amount
+            }
+            $(this).val(fixedToSub(val, decimal)) // 重新赋值限制只有两位小数
+            const pay_a_input = $(`.pay-input[data-type=${reverse(type)}]`)
+            pay_ratio = parseFloat(ZhCalc.mul(ZhCalc.div(val, advancePayTotal), 100).toFixed(2))
+            cur_amount = val
+            pay_a_input.val(pay_ratio)
+            // 截止本期金额文案更新
+            $('#p_total2').text(formatMoney(ZhCalc.add(val, p_amount), ',', decimal))
+        } else {
+            if (val.toFixed(1) === max.toFixed(1)) {
+                val = max
+            }
+            // 支付比例转化
+            $(this).val(fixedToSub(val)) // 重新赋值限制只有两位小数
+            const cur_m_input = $(`.pay-input[data-type=${reverse(type)}]`)
+            cur_amount = ZhCalc.mul(advancePayTotal, ZhCalc.div(val, 100))
+            pay_ratio = val
+            cur_m_input.val(cur_amount.toFixed(decimal))
+            // 截止本期金额文案更新
+            $('#p_total2').text(formatMoney(ZhCalc.add(cur_amount, p_amount), ',', decimal))
+        }
+        const data = {
+            pay_ratio,
+            cur_amount,
+        }
+        oldVal = {
+            cur_amount: parseFloat($(`.pay-input[data-type=${1}]`).val()),
+            remark: filterText($('#ad-remark').val())
+        }
+        clearTimeout(timer)
+        timer = setTimeout(() => {
+            if (checkInput()) {
+                update(data)
+                clearTimeout(timer)
+            }
+        }, 2000);
+    })
+
+    function checkInput() {
+        const newVal = {
+            cur_amount: parseFloat($(`.pay-input[data-type=${1}]`).val()),
+            remark: filterText($('#ad-remark').val())
+        }
+        return newVal.cur_amount === oldVal.cur_amount && newVal.remark === oldVal.remark
+    }
+
+    $('#ad-remark').on('input propertychange', function(e) {
+        const remark = filterText(e.target.value);
+        oldVal = {
+            pay_ratio: parseFloat($(`.pay-input[data-type=${0}]`).val()),
+            cur_amount: parseFloat($(`.pay-input[data-type=${1}]`).val()),
+            remark
+        }
+        const data = oldVal
+        clearTimeout(timer)
+        timer = setTimeout(() => {
+            if (checkInput()) {
+                update(data)
+                clearTimeout(timer)
+            }
+        }, 2000);
+    })
+
+    function filterText(text) {
+        if (!text) return null
+        return text.replace(/(\r\n)|(\n)/g, '<br/>').replace(/\s/g, ' ')
+    }
+    function update(data) {
+        postData(preUrl + '/update', data)
+    }
+
+    // $('#file-modal-target').click(function () {
+    //     $('#file-modal').trigger('click')
+    // })
+    $('#file-ok').click(function () {
+        const files = Array.from($('#file-modal')[0].files)
+        const valiData = files.map(v => {
+            const ext = v.name.substring(v.name.lastIndexOf('.') + 1)
+            return {
+                size: v.size,
+                ext
+            }
+        })
+        if (validateFiles(valiData)) {
+            if (files.length) {
+                const formData = new FormData()
+                files.forEach(file => {
+                    formData.append('name', file.name)
+                    formData.append('size', file.size)
+                    formData.append('file', file)
+                })
+                postDataWithFile(preUrl + '/file/upload', formData, function (result) {
+                    handleFileList(result)
+                    $('#file-cancel').click()
+                });
+            }
+        }
+    })
+    function handleFileList(files = []) {
+        $('#file-content').empty()
+        const { uncheck, checkNo } = auditConst.status
+        const newFiles = files.map(file => {
+            let showDel = false;
+            if (file.uid === cur_uid) {
+                if (!curAuditor) {
+                    advance.status === uncheck && cur_uid === advance.uid && (showDel = true)
+                    advance.status === checkNo && cur_uid === advance.uid && (showDel = true)
+                } else {
+                    curAuditor.audit_id === cur_uid && (showDel = true)
+                }
+            }
+            return {...file, showDel}
+        })
+        let html = `<tr><td colspan="3"><a href="#addfujian" data-toggle="modal" class="btn btn-sm btn-light text-primary" data-placement="bottom" title="" data-original-title="添加清单"><i class="fa fa-cloud-upload" aria-hidden="true"></i> 上传附件</a></td></tr>`
+        newFiles.forEach((file, idx) => {
+            if (file.showDel) {
+                html += `<tr><td width="70">${idx + 1}</td><td><a href="/${file.filepath}" target="_blank">${file.filename}</a></td><td width="90"><a href="javascript: void(0);" class="text-danger file-del" data-id="${file.id}">移除</a></td></tr>`
+            } else {
+                html += `<tr><td width="70">${idx + 1}</td><td><a href="/${file.filepath}" target="_blank">${file.filename}</a></td><td width="90"></td></tr>`
+            }
+        })
+        $('#file-content').append(html)
+    }
+
+    $('#file-content').on('click', 'a', function () {
+        if ($(this).hasClass('file-del')) {
+            const id = $(this).data('id')
+            postData(preUrl + '/file/del', {id}, (result) => {
+                handleFileList(result)
+            })
+        }
+    })
+
+})
+
+
+/**
+ * 校验文件大小、格式
+ * @param {Array} files 文件数组
+ */
+function validateFiles(files) {
+    if (files.length > 10) {
+        toastr.error('至多同时上传10个文件');
+        return false
+    }
+    return files.every(file => {
+        if (file.size > 1024 * 1024 * 30) {
+            toastr.error('文件大小限制为30MB');
+            return false
+        }
+        if (whiteList.indexOf('.' + file.ext) === -1) {
+            toastr.error('请上传正确的格式文件');
+            return false
+        }
+        return true
+    })
+}
+
+function reverse(num){
+    return 1^num
+}
+
+function formatMoney(s, dot = ',', decimal = 2) {
+    if (!s) {
+        s = 0;
+        return s.toFixed(decimal);
+    }
+    s = parseFloat((s + '').replace(/[^\d\.-]/g, '')).toFixed(decimal) + '';
+    if (!decimal) {
+        s += '.';
+    }
+    const l = s.split('.')[0].split('').reverse(),
+        r = s.split('.')[1];
+    let t = '';
+    for (let i = 0; i < l.length; i++) {
+        t += l[i] + ((i + 1) % 3 == 0 && (i + 1) != l.length ? dot : '');
+    }
+    return t.split('').reverse().join('') + (decimal === 0 ? '' : '.' + r);
+}

+ 137 - 42
app/public/js/ledger.js

@@ -77,6 +77,21 @@ $(document).ready(function() {
             if (posSpread) posSpread.refresh();
         },
     });
+    const checkList = $.ledger_checkList({
+        id: 'check-list',
+        tabSelector: '#check-list-tab',
+        selector: '#check-list',
+        relaSpread: ledgerSpread,
+        storeKey: 'ledger-check-' + getTenderId(),
+        checkType: ledgerCheckType,
+        afterLocated:  function () {
+            posOperationObj.loadCurPosData();
+        },
+        afterShow: function () {
+            ledgerSpread.refresh();
+            if (posSpread) posSpread.refresh();
+        },
+    });
 
     $.subMenu({
         menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
@@ -111,6 +126,9 @@ $(document).ready(function() {
             if (errorList) {
                 errorList.spread.refresh();
             }
+            if (checkList) {
+                checkList.spread.refresh();
+            }
         }
     });
 
@@ -942,6 +960,15 @@ $(document).ready(function() {
     sjsSettingObj.setFxTreeStyle(ledgerSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
     ledgerTreeCol.initSpreadSetting(ledgerSpreadSetting);
+    // ledgerSpreadSetting.cols.push(
+    //     {title: 'ledger_id', colSpan: '1', rowSpan: '2', field: 'ledger_id', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+    //     {title: 'ledger_pid', colSpan: '1', rowSpan: '2', field: 'ledger_pid', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+    //     {title: 'level', colSpan: '1', rowSpan: '2', field: 'level', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+    //     {title: 'order', colSpan: '1', rowSpan: '2', field: 'order', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+    //     {title: 'is_leaf', colSpan: '1', rowSpan: '2', field: 'is_leaf', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+    //     {title: 'full_path', colSpan: '1', rowSpan: '2', field: 'full_path', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+    //     {title: 'node_type', colSpan: '1', rowSpan: '2', field: 'node_type', hAlign: 2, width: 60, type: 'Number', readOnly: true}
+    // );
     SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
     SpreadJsObj.selChangedRefreshBackColor(ledgerSpread.getActiveSheet());
     // 绑定事件
@@ -1362,7 +1389,7 @@ $(document).ready(function() {
                         hint: '0号台账',
                         url: '/template/导入分项清单EXCEL格式.xls',
                     },
-                    callback: function (sheet) {     
+                    callback: function (sheet) {
                         postDataCompress(window.location.pathname + '/upload-excel/tz', sheet, function (result) {
                             ledgerTree.loadDatas(result.bills);
                             treeCalc.calculateAll(ledgerTree);
@@ -1373,7 +1400,7 @@ $(document).ready(function() {
                     },
                     // callback: function (file, select) {
                     //     const formData = new FormData();
-                    //     formData.append('file', file.files[0]);     
+                    //     formData.append('file', file.files[0]);
                     //     postDataWithFileProgress(window.location.pathname + '/upload-excel?ueType=tz&sheetName=' + select, formData, function (result) {
                     //         ledgerTree.loadDatas(result.bills);
                     //         treeCalc.calculateAll(ledgerTree);
@@ -1431,6 +1458,7 @@ $(document).ready(function() {
                 return !readOnly;
             }
         };
+        billsContextMenuOptions.items.sprImport = '-----------';
     }
     $.contextMenu(billsContextMenuOptions);
 
@@ -1995,6 +2023,8 @@ $(document).ready(function() {
 
         treeOperationObj.refreshOperationValid(ledgerSpread.getActiveSheet());
         treeOperationObj.loadExprToInput(ledgerSpread.getActiveSheet());
+
+        checkList.loadHisCheckData();
     }, null, true);
 
     $.divResizer({
@@ -2204,6 +2234,8 @@ $(document).ready(function() {
                 searchLedger.spread.refresh();
             } else if (tab.attr('content') === '#error-list') {
                 errorList.spread.refresh();
+            } else if (tab.attr('content') === '#check-list') {
+                checkList.spread.refresh();
             }
         } else { // 收起工具栏
             tab.removeClass('active');
@@ -2605,47 +2637,105 @@ $(document).ready(function() {
         }
      }
 
-    $('#searchAccount').click(() => {
-        const data = {
-            keyword: $('#searchName').val(),
-        };
-        postData('/search/user', data, (data) => {
-            const resultDiv = $('#searchResult');
-            if (data) {
-                $('h5>span', resultDiv).text(data.name);
-                $('#addAuditor').attr('auditorId', data.id);
-                $('h6', resultDiv).text(data.role);
-                $('p', resultDiv).text(data.company);
-                resultDiv.show();
+    // $('#searchAccount').click(() => {
+    //     const data = {
+    //         keyword: $('#searchName').val(),
+    //     };
+    //     postData('/search/user', data, (data) => {
+    //         const resultDiv = $('#searchResult');
+    //         if (data) {
+    //             $('h5>span', resultDiv).text(data.name);
+    //             $('#addAuditor').attr('auditorId', data.id);
+    //             $('h6', resultDiv).text(data.role);
+    //             $('p', resultDiv).text(data.company);
+    //             resultDiv.show();
+    //         } else {
+    //             toastr.info('未查询到该审核人');
+    //             resultDiv.hide();
+    //         }
+    //     }, () => {
+    //         $('#searchResult').hide();
+    //     });
+    // });
+    // // 审批人分组选择
+    // $('#account_group').change(function () {
+    //     let account_html = '<option value="0">选择审批人</option>';
+    //     for (const account of accountList) {
+    //         if (parseInt($(this).val()) === 0 || parseInt($(this).val()) === account.account_group) {
+    //             const role = account.role !== '' ? '(' + account.role + ')' : '';
+    //             const company = account.company !== '' ? ' -' + account.company : '';
+    //             account_html += '<option value="' + account.id + '">' + account.name + role + company + '</option>';
+    //         }
+    //     }
+    //     $('#account_list').html(account_html);
+    // });
+
+    let timer = null
+    let oldSearchVal = null
+
+    $('#gr-search').bind('input propertychange', function(e) {
+        oldSearchVal = e.target.value
+        timer && clearTimeout(timer)
+        timer = setTimeout(() => {
+            const newVal = $('#gr-search').val()
+            let html = ''
+            if (newVal && newVal === oldSearchVal) {
+                accountList.filter(item => item && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1))).forEach(item => {
+                    html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                        <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                class="ml-auto">${item.mobile || ''}</span></p>
+                        <span class="text-muted">${item.role || ''}</span>
+                    </dd>`
+                })
+                $('.book-list').empty()
+                $('.book-list').append(html)
             } else {
-                toastr.info('未查询到该审核人');
-                resultDiv.hide();
-            }
-        }, () => {
-            $('#searchResult').hide();
-        });
-    });
-    // 审批人分组选择
-    $('#account_group').change(function () {
-        let account_html = '<option value="0">选择审批人</option>';
-        for (const account of accountList) {
-            if (parseInt($(this).val()) === 0 || parseInt($(this).val()) === account.account_group) {
-                const role = account.role !== '' ? '(' + account.role + ')' : '';
-                const company = account.company !== '' ? ' -' + account.company : '';
-                account_html += '<option value="' + account.id + '">' + account.name + role + company + '</option>';
+                if (!$('.acc-btn').length) {
+                    accountGroup.forEach((group, idx) => {
+                        if (!group) return
+                        html += `<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="${idx}" data-type="hide"><i class="fa fa-plus-square"></i>
+                        </a> ${group.groupName}</dt>
+                        <div class="dd-content" data-toggleid="${idx}">`
+                        group.groupList.forEach(item => {
+                            html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                                    <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                            class="ml-auto">${item.mobile || ''}</span></p>
+                                    <span class="text-muted">${item.role || ''}</span>
+                                </dd>`
+                        });
+                        html += '</div>'
+                    })
+                    $('.book-list').empty()
+                    $('.book-list').append(html)
+                }
             }
+        }, 400);
+    })
+
+    // 添加审批流程按钮逻辑
+    $('.book-list').on('click', 'dt', function () {
+        const idx = $(this).find('.acc-btn').attr('data-groupid')
+        const type = $(this).find('.acc-btn').attr('data-type')
+        if (type === 'hide') {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).show(() => {
+                $(this).children().find('i').removeClass('fa-plus-square').addClass('fa-minus-square-o')
+                $(this).find('.acc-btn').attr('data-type', 'show')
+
+            })
+        } else {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).hide(() => {
+                $(this).children().find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square')
+                $(this).find('.acc-btn').attr('data-type', 'hide')
+            })
         }
-        $('#account_list').html(account_html);
-    });
+        return false
+    })
+
     // 添加到审批流程中
-    $('body').on('change', '#account_list', function () {
-        let id = $(this).val();
-        id = parseInt(id);
-        if (id !== 0) {
-            const data = {
-                auditorId: $(this).val(),
-            };
-            postData('/tender/' + getTenderId() + '/ledger/audit/add', data, (data) => {
+    $('dl').on('click', 'dd', function () {
+        const auditorId = parseInt($(this).data('id'))
+        if (auditorId) {
+            postData('/tender/' + getTenderId() + '/ledger/audit/add', { auditorId }, (data) => {
                 const html = [];
                 html.push('<li class="list-group-item" auditorId="' + data.audit_id + '"><a href="javascript: void(0)" class="text-danger pull-right">移除</a>');
                 html.push('<span>');
@@ -2718,11 +2808,11 @@ $(document).ready(function() {
     });
 
     $('#hideSp').click(function () {
-        $('#sp-list2').modal('hide');
+        $('#sp-list').modal('hide');
     });
 
     // 多层modal关闭后的滚动bug修复
-    $('#sp-list2').on('hidden.bs.modal', function (e) {
+    $('#sp-list').on('hidden.bs.modal', function (e) {
         $(document.body).addClass('modal-open');
     });
 
@@ -2843,6 +2933,12 @@ $(document).ready(function() {
         }
         return false;
     });
+
+    LedgerChecker({
+        ledgerTree: ledgerTree,
+        ledgerPos: pos,
+        checkList: checkList,
+    });
 });
 
 // 检查上报情况
@@ -2855,4 +2951,3 @@ function checkAuditorFrom () {
         return true;
     }
 }
-

+ 301 - 9
app/public/js/material.js

@@ -94,6 +94,24 @@ const is_numeric = (value) => {
         return !Number.isNaN(Number(value)) && value.toString().trim() !== '';
     }
 };
+
+function setMonthHtml() {
+    let html = '';
+    let qihtml = '';
+    for (const m of months) {
+        html += '<div class="custom-control custom-checkbox mb-2">\n' +
+            '                            <input type="checkbox" name="del_month" value="' + m + '" class="custom-control-input" id="month_' + m + '">\n' +
+            '                            <label class="custom-control-label" for="month_' + m + '">' + m + '月</label>\n' +
+            '                        </div>';
+        qihtml += parseInt(m.split('-')[1]) + '月,';
+    }
+    if (months.length > 0) {
+        qihtml = '<span class="mx-2 text-muted">/</span>本期月信息价:' + qihtml;
+        qihtml = qihtml.substring(0, qihtml.length-1);
+    }
+    $('#show_month').html(html);
+    $('#qi-month').html(qihtml);
+}
 $(document).ready(() => {
     autoFlashHeight();
     const materialSpread = SpreadJsObj.createNewSpread($('#material-spread')[0]);
@@ -108,7 +126,7 @@ $(document).ready(() => {
             {title: '本期应耗数量', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 2, width: 100, type: 'Number', readOnly: true},
             {title: '基准价', colSpan: '1', rowSpan: '2', field: 'basic_price', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.isEdit'},
             {title: '基准时间', colSpan: '1', rowSpan: '2', field: 'basic_times', hAlign: 0, width: 80, formatter: '@', readOnly: 'readOnly.isEdit'},
-            {title: '本期信息价|单价', colSpan: '3|1', rowSpan: '1|1', field: 'msg_tp', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.remark'},
+            {title: '本期信息价|单价', colSpan: '3|1', rowSpan: '1|1', field: 'msg_tp', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.msg_tp'},
             {title: '|时间', colSpan: '|1', rowSpan: '|1', field: 'msg_times', hAlign: 0, width: 80, formatter: '@', readOnly: 'readOnly.remark'},
             {title: '|价差', colSpan: '1', rowSpan: '1|1', field: 'msg_spread', hAlign: 2, width: 60, type: 'Number', readOnly: true, getValue: 'getValue.msg_spread'},
             {title: '本期材料调差|上涨幅度(%)', colSpan: '4|1', rowSpan: '1|1', field: 'm_up_risk', hAlign: 2, width: 100, type: 'Number', readOnly: 'readOnly.isEdit'},
@@ -202,6 +220,9 @@ $(document).ready(() => {
             remark: function () {
                 return readOnly;
             },
+            msg_tp: function () {
+                return !(!readOnly && months.length === 0);
+            }
         },
     };
     SpreadJsObj.initSpreadSettingEvents(materialSpreadSetting, materialCol);
@@ -209,7 +230,7 @@ $(document).ready(() => {
     SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialBillsData);
 
     const materialSpreadObj = {
-        refreshActn: function () {
+        refreshActn: function (rowCount = 1) {
             const setObjEnable = function (obj, enable) {
                 if (enable) {
                     obj.removeClass('disabled');
@@ -220,7 +241,7 @@ $(document).ready(() => {
             const sheet = materialSpread.getActiveSheet();
             const select = SpreadJsObj.getSelectObject(sheet);
             // 还需判断是否已被调差清单调用
-            setObjEnable($('#del'), !readOnly && select && materialBase.isUsed(select));
+            setObjEnable($('#del'), !readOnly && select && materialBase.isUsed(select) && rowCount === 1);
         },
         add: function () {
             const sheet = materialSpread.getActiveSheet();
@@ -231,6 +252,16 @@ $(document).ready(() => {
                     SpreadJsObj.reLoadRowData(sheet, materialBillsData.length - 1);
                     sheet.setSelection(materialBillsData.length - 1, 0, 1, 1);
                     materialSpreadObj.refreshActn();
+                    // 月信息价需要同时添加空白的list
+                    if (months.length > 0) {
+                        const one_month ={ mb_id: result.id, code: '', name: null, unit: null, };
+                        for (const m of months) {
+                            one_month[m] = null;
+                        }
+                        monthsList.push(one_month);
+                        materialMonthSpread.getActiveSheet().addRows(monthsList.length - 1, 1);
+                        SpreadJsObj.reLoadRowData(materialMonthSpread.getActiveSheet(), monthsList.length - 1);
+                    }
                 }
             });
         },
@@ -243,16 +274,22 @@ $(document).ready(() => {
                 const index = materialBillsData.indexOf(select);
                 materialBillsData.splice(index, 1);
                 sheet.deleteRows(index, 1);
-                SpreadJsObj.reLoadSheetData(materialSpread.getActiveSheet());
+                // SpreadJsObj.reLoadSheetData(materialSpread.getActiveSheet());
                 const sel = sheet.getSelections();
                 sheet.setSelection(index > 0 ? index - 1 : 0, sel.length > 0 ? sel[0].col : 0, 1, 1);
                 materialSpreadObj.refreshActn();
+                // 月信息价需要同时删除
+                if (months.length > 0) {
+                    monthsList.splice(index, 1);
+                    materialMonthSpread.getActiveSheet().deleteRows(index, 1);
+                    // SpreadJsObj.reLoadSheetData(materialMonthSpread.getActiveSheet());
+                }
             });
         },
         selectionChanged: function (e, info) {
-            materialSpreadObj.refreshActn();
             const sel = info.sheet.getSelections()[0];
             const col = info.sheet.zh_setting.cols[sel.col];
+            materialSpreadObj.refreshActn(sel.rowCount);
             const data = SpreadJsObj.getSelectObject(info.sheet);
             materialSpreadObj.setReadOnly(true);
         },
@@ -341,6 +378,11 @@ $(document).ready(() => {
                     m_tp = result.m_tp;
                     resetTpTable();
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    // 判断如果是更改了编号,名称,单位,月信息价需要跟着改变值
+                    if (months.length > 0 && (col.field === 'code' || col.field === 'name' || col.field === 'unit')) {
+                        monthsList[info.row][col.field] = validText;
+                        SpreadJsObj.reLoadRowData(materialMonthSpread.getActiveSheet(), info.row);
+                    }
                 }, function () {
                     select[col.field] = orgValue;
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -465,11 +507,19 @@ $(document).ready(() => {
             // 更新至服务器
             postData(window.location.pathname + '/save', { type:'paste', updateData: data }, function (result) {
                 materialBillsData = result.info;
-                SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialBillsData);
+                SpreadJsObj.reLoadSheetData(materialSpread.getActiveSheet());
                 // for (const row in rowData) {
                 //     materialBillsData.splice(index, 1, result.info[row]);
                 //     SpreadJsObj.reLoadRowData(info.sheet, row);
                 // }
+                if (months.length > 0) {
+                    for (const [i,m] of monthsList.entries()) {
+                        m.code = materialBillsData[i].code;
+                        m.name = materialBillsData[i].name;
+                        m.unit = materialBillsData[i].unit;
+                    }
+                    SpreadJsObj.reLoadSheetData(materialMonthSpread.getActiveSheet());
+                }
                 m_tp = result.m_tp;
                 resetTpTable();
             }, function () {
@@ -495,10 +545,182 @@ $(document).ready(() => {
     // msg_range.formatter("yyyy-MM-dd");
     sheet.resumePaint();
 
+    const materialMonthSpread = SpreadJsObj.createNewSpread($('#material-month-spread')[0]);
+    const materialMonthSpreadSetting = {
+        cols: [
+            {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 60, formatter: '@', readOnly: true},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 180, formatter: '@', readOnly: true},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: true},
+            {title: '平均单价', colSpan: '1', rowSpan: '2', field: 'average_msg_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true, getValue:'getValue.average_msg_tp'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: readOnly,
+    };
+    if (months.length > 0) {
+        for (const m of months) {
+            const month = parseInt(m.split('-')[1]);
+            const newCols = {title: month + '月|单价', colSpan: '1|1', rowSpan: '1|1', field: m, hAlign: 2, width: 60, type: '@', readOnly: 'readOnly.isEdit'};
+            materialMonthSpreadSetting.cols.push(newCols);
+        }
+    }
+
+    const materialMonthCol = {
+        getValue: {
+            average_msg_tp: function (data) {
+                let msg_tp = 0;
+                for (const m of months) {
+                    msg_tp += data[m];
+                }
+                const average_tp = ZhCalc.round(ZhCalc.div(msg_tp, months.length), 3);
+                return average_tp;
+            },
+        },
+        readOnly: {
+            isEdit: function (data) {
+                return readOnly;
+            },
+        },
+    };
+
+    SpreadJsObj.initSpreadSettingEvents(materialMonthSpreadSetting, materialMonthCol);
+    SpreadJsObj.initSheet(materialMonthSpread.getActiveSheet(), materialMonthSpreadSetting);
+    SpreadJsObj.loadSheetData(materialMonthSpread.getActiveSheet(), SpreadJsObj.DataType.Data, monthsList);
+
+    const materialMonthSpreadObj = {
+        editEnded: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                // 未改变值则不提交
+                const validText = is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : null);
+                const orgValue = select[col.field];
+                if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                // 判断部分值是否输入的是数字判断和数据计算
+                if (isNaN(validText)) {
+                    toastr.error('不能输入其它非数字类型字符');
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                const num = parseFloat(validText);
+                if (validText !== null && (num < 0 || !/^\d+(\.\d{1,3})?$/.test(num))) {
+                    toastr.error('请输入大于0并且小于3位小数的浮点数');
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                select[col.field] = validText;
+
+                // 更新至服务器
+                postData(window.location.pathname + '/month/save', { type:'update', updateData: { mb_id: select.mb_id, yearmonth: col.field, value: validText } }, function (result) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    materialBillsData = result.materialBillsData;
+                    SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialBillsData);
+                    m_tp = result.m_tp;
+                    resetTpTable();
+                }, function () {
+                    select[col.field] = orgValue;
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                });
+            }
+        },
+        deletePress: function (sheet) {
+            return;
+        },
+        clipboardPasted(e, info) {
+            const hint = {
+                cellError: {type: 'error', msg: '粘贴内容超出了表格范围'},
+                numberExpr: {type: 'error', msg: '不能粘贴其它非数字类型字符'},
+                numberCan: {type: 'error', msg: '请粘贴大于0并且小于3位小数的浮点数'},
+            };
+            const range = info.cellRange;
+            const sortData = info.sheet.zh_data || [];
+            if (info.cellRange.row + info.cellRange.rowCount > sortData.length) {
+                toastMessageUniq(hint.cellError);
+                SpreadJsObj.reLoadSheetHeader(materialMonthSpread.getActiveSheet());
+                SpreadJsObj.reLoadSheetData(materialMonthSpread.getActiveSheet());
+                return;
+            }
+            if (sortData.length > 0 && range.col + range.colCount > 4 + months.length) {
+                toastMessageUniq(hint.cellError);
+                SpreadJsObj.reLoadSheetHeader(materialMonthSpread.getActiveSheet());
+                SpreadJsObj.reLoadSheetData(materialMonthSpread.getActiveSheet());
+                return;
+            }
+            const data = [];
+            for (let iRow = 0; iRow < range.rowCount; iRow++) {
+                let bPaste = true;
+                const curRow = range.row + iRow;
+                const materialMonthData = sortData[curRow];
+                const hintRow = range.rowCount > 1 ? curRow : '';
+                let sameCol = 0;
+                for (let iCol = 0; iCol < range.colCount; iCol++) {
+                    const curCol = range.col + iCol;
+                    const colSetting = info.sheet.zh_setting.cols[curCol];
+                    if (!colSetting) continue;
+
+                    let validText = info.sheet.getText(curRow, curCol);
+                    validText = is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : null);
+                    const orgValue = sortData[curRow][colSetting.field];
+                    if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                        sameCol++;
+                        if (range.colCount === sameCol)  {
+                            bPaste = false;
+                        }
+                        continue;
+                    }
+                    const num = parseFloat(validText);
+                    if (isNaN(validText)) {
+                        toastMessageUniq(getPasteHint(hint.numberExpr, hintRow));
+                        bPaste = false;
+                        continue;
+                    }
+                    if (validText !== null && (num < 0 || !/^\d+(\.\d{1,3})?$/.test(num))) {
+                        toastMessageUniq(getPasteHint(hint.numberCan, hintRow));
+                        bPaste = false;
+                        continue;
+                    }
+                    materialMonthData[colSetting.field] = validText;
+                    sortData[curRow][colSetting.field] = validText;
+                }
+                if (bPaste) {
+                    data.push(materialMonthData);
+                } else {
+                    SpreadJsObj.reLoadRowData(info.sheet, curRow);
+                }
+            }
+            if (data.length === 0) {
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                return;
+            }
+            // 更新至服务器
+            postData(window.location.pathname + '/month/save', { type:'paste', updateData: data }, function (result) {
+                SpreadJsObj.reLoadSheetData(materialMonthSpread.getActiveSheet());
+                materialBillsData = result.materialBillsData;
+                SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialBillsData);
+                m_tp = result.m_tp;
+                resetTpTable();
+            }, function () {
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                return;
+            });
+        },
+    };
+
+    materialMonthSpread.bind(spreadNS.Events.ClipboardPasted, materialMonthSpreadObj.clipboardPasted);
+    SpreadJsObj.addDeleteBind(materialMonthSpread, materialMonthSpreadObj.deletePress);
+
     if (!readOnly) {
         $('#add').click(materialSpreadObj.add);
         $('#del').click(materialSpreadObj.del);
         materialSpread.bind(spreadNS.Events.EditEnded, materialSpreadObj.editEnded);
+        materialMonthSpread.bind(spreadNS.Events.EditEnded, materialMonthSpreadObj.editEnded);
         // 右键菜单
         $.contextMenu({
             selector: '#material-spread',
@@ -523,8 +745,9 @@ $(document).ready(() => {
                     disabled: function (key, opt) {
                         const sheet = materialSpread.getActiveSheet();
                         const select = SpreadJsObj.getSelectObject(sheet);
-                        materialSpreadObj.refreshActn();
-                        if (!readOnly && select && materialBase.isUsed(select)) {
+                        const sel = sheet.getSelections()[0];
+                        materialSpreadObj.refreshActn(sel.rowCount);
+                        if (!readOnly && select && materialBase.isUsed(select) && sel.rowCount === 1) {
                             return false;
                         } else {
                             return true;
@@ -697,7 +920,76 @@ $(document).ready(() => {
                 SpreadJsObj.reLoadRowData(sheet, index);
                 $('#bcyy').modal('hide');
             });
-        })
+        });
+
+        // 创建月信息价
+        $('#make-month').click(function() {
+            const yearmonth = $('#months').val();
+            const result = yearmonth.match(/^(\d{1,4})(-|\/)(\d{1,2})$/);
+            if (result === null) {
+                toastr.error('请选择正确的信息价月份');
+                return false;
+            }
+            //判断是否已存在当前月份
+            if (months.indexOf(yearmonth) !== -1) {
+                toastr.error('调差期已创建过本月的信息价');
+                return false;
+            }
+            postData(window.location.pathname + '/month/save', { type: 'add', updateData: { yearmonth: yearmonth } }, function (data) {
+                const month = parseInt(yearmonth.split('-')[1]);
+                const newCols = {title: month + '月|单价', colSpan: '1|1', rowSpan: '1|1', field: yearmonth, hAlign: 2, width: 60, type: '@', readOnly: 'readOnly.isEdit'};
+                materialMonthSpreadSetting.cols.push(newCols);
+                months.push(yearmonth);
+                monthsList = data.monthsList;
+                SpreadJsObj.reinitSheetHeader(materialMonthSpread.getActiveSheet(), materialMonthSpreadSetting);
+                SpreadJsObj.initSpreadSettingEvents(materialMonthSpreadSetting, materialMonthCol);
+                // SpreadJsObj.reLoadSheetData(materialMonthSpread.getActiveSheet());
+                SpreadJsObj.loadSheetData(materialMonthSpread.getActiveSheet(), SpreadJsObj.DataType.Data, monthsList);
+                setMonthHtml();
+
+                // 工料表单价显示也要更新
+                materialBillsData = data.materialBillsData;
+                SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialBillsData);
+                m_tp = data.m_tp;
+                resetTpTable();
+                $('#add-month').modal('hide');
+            });
+        });
+        // 删除月信息价
+        $('#del-month').click(function () {
+            const del_month_array = [];
+            $("input[name='del_month']:checked").each(function () {
+                del_month_array.push($(this).val());
+            });
+            if (del_month_array.length === 0) {
+                toastr.error('请选择要移除的月信息价');
+                return false;
+            }
+            postData(window.location.pathname + '/month/save', { type: 'del', updateData: { del_yearmonth: del_month_array } }, function (data) {
+                for (const dm of del_month_array) {
+                    _.remove(materialMonthSpreadSetting.cols, function (n) {
+                        return n.field === dm;
+                    });
+                    _.remove(months, function (n) {
+                        return n === dm;
+                    });
+                }
+                monthsList = data.monthsList;
+                console.log(monthsList);
+                SpreadJsObj.reinitSheetHeader(materialMonthSpread.getActiveSheet(), materialMonthSpreadSetting);
+                SpreadJsObj.initSpreadSettingEvents(materialMonthSpreadSetting, materialMonthCol);
+                SpreadJsObj.loadSheetData(materialMonthSpread.getActiveSheet(), SpreadJsObj.DataType.Data, monthsList);
+                setMonthHtml();
+
+                // 工料表单价显示也要更新
+                materialBillsData = data.materialBillsData;
+                console.log(materialBillsData);
+                SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialBillsData);
+                m_tp = data.m_tp;
+                resetTpTable();
+                $('#remove-month').modal('hide');
+            });
+        });
     } else {
         // SpreadJsObj.forbiddenSpreadContextMenu('#material-spread', materialSpread);
     }

+ 64 - 51
app/public/js/material_audit.js

@@ -9,65 +9,78 @@
  */
 
 $(document).ready(function () {
+    let timer = null
+    let oldSearchVal = null
     // 获取审核相关url
     function getUrlPre () {
         const path = window.location.pathname.split('/');
         return _.take(path, 6).join('/');
     }
 
-    // 搜索审批人
-    $('#searchAccount').click(() => {
-        const data = {
-            keyword: $('#searchName').val(),
-        };
-        postData('/search/user', data, (data) => {
-            const resultDiv = $('#searchResult');
-            if (data) {
-                $('h5>span', resultDiv).text(data.name);
-                $('#addAuditor').attr('auditorId', data.id);
-                $('h6', resultDiv).text(data.role);
-                $('p', resultDiv).text(data.company);
-                resultDiv.show();
+    $('#gr-search').bind('input propertychange', function(e) {
+        oldSearchVal = e.target.value
+        timer && clearTimeout(timer)
+        timer = setTimeout(() => {
+            const newVal = $('#gr-search').val()
+            let html = ''
+            if (newVal && newVal === oldSearchVal) {
+                accountList.filter(item => item && cur_uid !== item.id && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1))).forEach(item => {
+                    html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                        <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                class="ml-auto">${item.mobile || ''}</span></p>
+                        <span class="text-muted">${item.role || ''}</span>
+                    </dd>`
+                })
+                $('.book-list').empty()
+                $('.book-list').append(html)
             } else {
-                toast('未查询到该审核人', 'info');
-                resultDiv.hide();
-            }
-        }, () => {
-            $('#searchResult').hide();
-        });
-    });
-    // 添加审批人
-    $('#addAuditor').click(() => {
-        postData(getUrlPre() + '/audit/add', { auditorId: $('#addAuditor').attr('auditorId') }, (data) => {
-            const html = [];
-            html.push('<li class="list-group-item" auditorId="'+ data.aid +'"><a href="javascript: void(0)" class="text-danger pull-right">移除</a>');
-            html.push('<span>');
-            html.push(data.order + ' ');
-            html.push(data.name + ' ');
-            html.push('</span>');
-            html.push('<small class="text-muted">');
-            html.push(data.role);
-            html.push('</small></li>');
-            $('#auditors').append(html.join(''));
-        });
-    });
-    // 审批人分组选择
-    $('#account_group').change(function () {
-        let account_html = '<option value="0">选择审批人</option>';
-        for (const account of accountList) {
-            if (parseInt($(this).val()) === 0 || parseInt($(this).val()) === account.account_group) {
-                const role = account.role !== '' ? '(' + account.role + ')' : '';
-                const company = account.company !== '' ? ' -' + account.company : '';
-                account_html += '<option value="' + account.id + '">' + account.name + role + company + '</option>';
+                if (!$('.acc-btn').length) {
+                    accountGroup.forEach((group, idx) => {
+                        if (!group) return
+                        html += `<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="${idx}" data-type="hide"><i class="fa fa-plus-square"></i>
+                        </a> ${group.groupName}</dt>
+                        <div class="dd-content" data-toggleid="${idx}">`
+                        group.groupList.forEach(item => {
+                            if (item.id !== cur_uid) {
+                                html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                                    <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                            class="ml-auto">${item.mobile || ''}</span></p>
+                                    <span class="text-muted">${item.role || ''}</span>
+                                </dd>`
+                            }
+                        });
+                        html += '</div>'
+                    })
+                    $('.book-list').empty()
+                    $('.book-list').append(html)
+                }
             }
+        }, 400);
+    })
+
+    // 添加审批流程按钮逻辑
+    $('.book-list').on('click', 'dt', function () {
+        const idx = $(this).find('.acc-btn').attr('data-groupid')
+        const type = $(this).find('.acc-btn').attr('data-type')
+        if (type === 'hide') {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).show(() => {
+                $(this).children().find('i').removeClass('fa-plus-square').addClass('fa-minus-square-o')
+                $(this).find('.acc-btn').attr('data-type', 'show')
+
+            })
+        } else {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).hide(() => {
+                $(this).children().find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square')
+                $(this).find('.acc-btn').attr('data-type', 'hide')
+            })
         }
-        $('#account_list').html(account_html);
-    });
+        return false
+    })
+
     // 添加到审批流程中
-    $('body').on('change', '#account_list', function () {
-        let id = $(this).val();
-        id = parseInt(id);
-        if (id !== 0) {
+    $('dl').on('click', 'dd', function () {
+        const id = parseInt($(this).data('id'))
+        if (id) {
             postData(getUrlPre() + '/audit/add', { auditorId: id }, (data) => {
                 const html = [];
                 html.push('<li class="list-group-item" auditorId="'+ data.aid +'"><a href="javascript: void(0)" class="text-danger pull-right">移除</a>');
@@ -142,14 +155,14 @@ $(document).ready(function () {
     });
     // 退回选择修改审批人流程
     $('#hideSp').click(function () {
-        $('#sp-list2').modal('hide');
+        $('#sp-list').modal('hide');
     });
     $('a[f-target]').click(function () {
         $($(this).attr('f-target')).modal('show');
     });
 
     // 多层modal关闭后的滚动bug修复
-    $('#sp-list2').on('hidden.bs.modal', function (e) {
+    $('#sp-list').on('hidden.bs.modal', function (e) {
         $(document.body).addClass('modal-open');
     });
 });

+ 192 - 61
app/public/js/measure_material.js

@@ -14,79 +14,210 @@ $(function () {
             order: $(this).attr('m-order'),
         };
         postData('/tender/' + tenderId + '/measure/material/auditors', data, function (result) {
-            const materialAuditor = result.materialAuditor;
-            const auditors = result.auditors;
-            const auditHistory = result.auditHistory;
-            // 生成左边列表流程
-            const lefthtml = [];
-            lefthtml.push('<li class="list-group-item"><i class="fa fa fa-play-circle fa-rotate-90"></i> '+ materialAuditor.name +'  <small class="text-muted">'+ materialAuditor.role +'</small><span class="pull-right">原报</span></li>');
-            for (const [index,a] of auditors.entries()) {
-                if (index+1 === auditors.length) {
-                    lefthtml.push('<li class="list-group-item"><i class="fa fa-stop-circle"></i> '+ a.name +'  <small class="text-muted">'+ a.role +'</small><span class="pull-right">终审</span></li>');
+            const { auditHistory, auditors, user } = result
+            let auditorsHTML = ''
+            let historyHTML = ''
+            auditors.forEach((auditor, idx) => {
+                if (idx === 0) {
+                    auditorsHTML += `<li class="list-group-item">
+                        <i class="fa fa fa-play-circle fa-rotate-90"></i> ${auditor.name}
+                        <small class="text-muted">${auditor.role}</small>
+                        <span class="pull-right">原报</span>
+                    </li>`
+                } else if(idx === auditors.length -1 && idx !== 0) {
+                    auditorsHTML += `<li class="list-group-item">
+                        <i class="fa fa fa-stop-circle"></i> ${auditor.name}
+                        <small class="text-muted">${auditor.role}</small>
+                        <span class="pull-right">终审</span>
+                    </li>`
                 } else {
-                    lefthtml.push('<li class="list-group-item"><i class="fa fa-chevron-circle-down"></i> '+ a.name +'  <small class="text-muted">'+ a.role +'</small><span class="pull-right">' + transFormToChinese(index+1) + '审</span></li>');
+                    auditorsHTML += `<li class="list-group-item">
+                        <i class="fa fa-chevron-circle-down"></i> ${auditor.name}
+                        <small class="text-muted">${auditor.role}</small>
+                        <span class="pull-right">${transFormToChinese(idx)}审</span>
+                    </li>`
                 }
-            }
-            $('#auditor-list').html(lefthtml.join(''));
+            })
+            $('#auditor-list').empty()
+            $('#auditor-list').append(auditorsHTML)
+            auditHistory.forEach((auditors, idx) => {
+                historyHTML += `<div class="${idx < auditHistory.length - 1 ? 'fold-card' : ''}">
+                <div class="text-center text-muted"
+                    ${idx === auditHistory.length - 1 ? 'id="end-target"' : ""}>
+                    ${idx === auditHistory.length - 1 ? 1 : idx + 1}#</div>
+                <ul class="timeline-list list-unstyled mt-2">`
+                auditors.forEach((auditor, index) => {
+                    if (index === 0) {
+                        historyHTML += `<li class="timeline-list-item pb-2">
+                            <div class="timeline-item-date">
+                                ${formatDate(auditor.begin_time)}
+                            </div>
+                            <div class="timeline-item-tail"></div>
+                            <div class="timeline-item-icon bg-success text-light">
+                                <i class="fa fa-caret-down"></i>
+                            </div>
+                            <div class="timeline-item-content">
+                                <div class="card">
+                                    <div class="card-body p-3">
+                                        <div class="card-text">
+                                            <p class="mb-1"><span
+                                                    class="h5">${user.name}</span><span
+                                                    class="pull-right text-success">${idx !== 0 ? '重新' : ''}上报审批</span>
+                                            </p>
+                                            <p class="text-muted mb-0">${user.role}</p>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </li>
+                        <li class="timeline-list-item pb-2">
+                            <div class="timeline-item-date">
+                                ${formatDate(auditor.end_time)}
+                            </div>`
 
-            // 生成右边列表流程
-            const righthtml = [];
-            for(const ah of auditHistory) {
-                righthtml.push('<div class="card mt-3"><ul class="list-group list-group-flush">');
-                for (let iA = 0; iA < ah.length; iA++) {
-                    if (iA === 0) {
-                        righthtml.push('<li class="list-group-item">');
-                        righthtml.push('<h5 class="card-title">');
-                        righthtml.push('<i class="fa fa-play-circle fa-rotate-90 text-success"></i> '+ materialAuditor.name +' <small class="text-muted">'+ materialAuditor.role +'</small><span class="pull-right">原报</span></h5>');
-                        righthtml.push('<div class="ml-3">');
-                        righthtml.push('<span class="text-success"><small>' + (ah[iA].begin_time ? moment(ah[iA].begin_time).format('YYYY-MM-DD') : '') + '</small>'+ (auditHistory.indexOf(ah) > 0 ? '重新' : '') + '上报</span>');
-                        righthtml.push('</div></li>');
-                        righthtml.push('<li class="list-group-item">');
-                        righthtml.push('<h5 class="card-title"><i class="fa '+ (iA === ah.length - 1 ? 'fa-stop-circle ' : 'fa-chevron-circle-down ') + auditConst.statusClass[ah[iA].status] +'"></i> '+ ah[iA].name +' <small class="text-muted">'+ ah[iA].role +'</small><span class="pull-right">' + (ah[iA].sort === ah[iA].max_sort ? '终' : transFormToChinese(ah[iA].sort)) + '审</span></h5>');
-                        righthtml.push('<div class="ml-3">');
-                        if (ah[iA].status !== auditConst.status.uncheck) {
-                            let timeHtml = '';
-                            if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) {
-                                timeHtml = '<small>'+ (ah[iA].end_time ? moment(ah[iA].end_time).format('YYYY-MM-DD') : '') +'</small> ';
+                            if(index < auditors.length - 1) {
+                                historyHTML += `<div class="timeline-item-tail"></div>`
                             }
-                            righthtml.push('<span class="'+ auditConst.statusClass[ah[iA].status] +'">' + timeHtml + auditConst.statusString[ah[iA].status] + (ah[iA].status === auditConst.status.checkNo ? ' ' + materialAuditor.name : '') + '</span>');
-                        }
-                        righthtml.push('<p class="card-text">'+ (ah[iA].opinion !== null ? ah[iA].opinion : '') +'</p></div>');
-                        righthtml.push('</li>');
-                    } else if (iA === ah.length - 1) {
-                        righthtml.push('<li class="list-group-item">');
-                        righthtml.push('<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><span class="pull-right">终审</span></h5>');
-                        righthtml.push('<div class="ml-3">');
-                        if (ah[iA].status !== auditConst.status.uncheck) {
-                            let timeHtml = '';
-                            if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) {
-                                timeHtml = '<small>'+ (ah[iA].end_time ? moment(ah[iA].end_time).format('YYYY-MM-DD') : '') +'</small> ';
+                            if(auditor.status === auditConst.status.checked) {
+                                historyHTML += `<div class="timeline-item-icon bg-success text-light">
+                                    <i class="fa fa-check"></i>
+                                </div>`
+
+                            } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {
+                                historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                    <i class="fa fa-level-up"></i>
+                                </div>`
+                            } else if(auditor.status === auditConst.status.checking) {
+                                historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                    <i class="fa fa-ellipsis-h"></i>
+                                </div>`
+                            } else {
+                                historyHTML += `<div class="timeline-item-icon bg-secondary text-light"></div>`
+
                             }
-                            righthtml.push('<span class="'+ auditConst.statusClass[ah[iA].status] +'">' + timeHtml + auditConst.statusString[ah[iA].status] + (ah[iA].status === auditConst.status.checkNo ? ' ' + materialAuditor.name : '') + '</span>');
-                        }
-                        righthtml.push('<p class="card-text">'+ (ah[iA].opinion !== null ? ah[iA].opinion : '') +'</p></div>');
-                        righthtml.push('</li>');
-                    } else {
-                        righthtml.push('<li class="list-group-item">');
-                        righthtml.push('<h5 class="card-title"><i class="fa '+ (iA === ah.length - 1 ? 'fa-stop-circle ' : 'fa-chevron-circle-down ') + auditConst.statusClass[ah[iA].status] +'"></i> '+ ah[iA].name +' <small class="text-muted">'+ ah[iA].role +'</small><span class="pull-right">' + (ah[iA].sort === ah[iA].max_sort ? '终' : transFormToChinese(ah[iA].sort)) + '审</span></h5>');
-                        righthtml.push('<div class="ml-3">');
-                        if (ah[iA].status !== auditConst.status.uncheck) {
-                            let timeHtml = '';
-                            if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) {
-                                timeHtml = '<small>'+ (ah[iA].end_time ? moment(ah[iA].end_time).format('YYYY-MM-DD') : '') +'</small> ';
+                            historyHTML += `<div class="timeline-item-content">
+                                <div class="card">
+                                    <div class="card-body p-3">
+                                        <div class="card-text">
+                                            <p class="mb-1"><span class="h5">${auditor.name}</span><span
+                                                    class="pull-right ${auditConst.statusClass[auditor.status]}">${auditConst.statusString[auditor.status]}</span>
+                                            </p>
+                                            <p class="text-muted mb-0">${auditor.role}</p>
+                                        </div>
+                                    </div>`
+                            if (auditor.opinion) {
+                            historyHTML += `<div class="card-body p-3 border-top">
+                                    <p style="margin: 0;">${auditor.opinion}</p>
+                                </div>`
                             }
-                            righthtml.push('<span class="'+ auditConst.statusClass[ah[iA].status] +'">' + timeHtml + auditConst.statusString[ah[iA].status] + (ah[iA].status === auditConst.status.checkNo ? ' ' + materialAuditor.name : '') + '</span>');
+                            historyHTML += `</div></div></li>`
+                    } else {
+                        historyHTML += `<li class="timeline-list-item pb-2">
+                        <div class="timeline-item-date">
+                            ${formatDate(auditor.end_time)}
+                        </div>`
+
+                        if(index < auditors.length - 1) {
+                            historyHTML += `<div class="timeline-item-tail"></div>`
+                        }
+                        if(auditor.status === auditConst.status.checked) {
+                            historyHTML += `<div class="timeline-item-icon bg-success text-light">
+                                <i class="fa fa-check"></i>
+                            </div>`
+                        } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {
+                            historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                <i class="fa fa-level-up"></i>
+                            </div>`
+
+                        } else if(auditor.status === auditConst.status.checking) {
+                            historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                <i class="fa fa-ellipsis-h"></i>
+                            </div>`
+                        } else {
+                            historyHTML += `<div class="timeline-item-icon bg-secondary text-light"></div>`
+                        }
+                        historyHTML += `<div class="timeline-item-content">
+                        <div class="card">
+                            <div class="card-body p-3">
+                                <div class="card-text">
+                                    <p class="mb-1"><span class="h5">${auditor.name}</span>
+                                        <span
+                                            class="pull-right
+                                                            ${auditConst.statusClass[auditor.status]}">${auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''}
+                                            ${auditor.status === auditConst.status.checkNo ? user.name : ''}
+                                            ${auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : ''}
+                                        </span>
+                                    </p>
+                                    <p class="text-muted mb-0">${auditor.role}</p>
+                                </div>
+                            </div>`
+
+                        if (auditor.opinion) {
+                        historyHTML += `<div class="card-body p-3 border-top">
+                            <p style="margin: 0;">${auditor.opinion} </p>
+                        </div>`
                         }
-                        righthtml.push('<p class="card-text">'+ (ah[iA].opinion !== null ? ah[iA].opinion : '') +'</p></div>');
-                        righthtml.push('</li>');
+                        historyHTML += `</div></div></li>`
                     }
+                })
+                historyHTML += '</ul></div>'
+                if(idx === auditHistory.length - 1 && auditHistory.length !== 1) {
+                    historyHTML += `<div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
+                    data-idx="${idx + 1}">展开历史审批流程</a></div>`
                 }
-                righthtml.push('</ul></div>');
-            }
-            $('#auditor-list2').html(righthtml.join(''));
+            })
+            $('#audit-list').empty()
+            $('#audit-list').append(historyHTML)
         });
     });
 
+    // 展开/收起历史审核记录
+    $('#audit-list').on('click', 'a', function() {
+        const type = $(this).data('target')
+        const auditCard = $(this).parent().parent()
+        console.log('auditCard', auditCard)
+        if (type === 'show') {
+            $(this).data('target', 'hide')
+            auditCard.find('.fold-card').slideDown('swing', () => {
+                auditCard.find('#end-target').text($(this).data('idx') + '#')
+                auditCard.find('#fold-btn').text('收起历史审核记录')
+            })
+        } else {
+            $(this).data('target', 'show')
+            auditCard.find('.fold-card').slideUp('swing', () => {
+                auditCard.find('#end-target').text('1#')
+                auditCard.find('#fold-btn').text('展开历史审核记录')
+            })
+        }
+    });
+
+    function formatDate(date) {
+        if (!date) return '';
+        date = new Date(date)
+        const year = date.getFullYear();
+        let mon = date.getMonth() + 1;
+        let day = date.getDate();
+        let hour = date.getHours();
+        let minute = date.getMinutes();
+        let scond = date.getSeconds();
+        if (mon < 10) {
+            mon = '0' + mon.toString();
+        }
+        if (day < 10) {
+            day = '0' + day.toString();
+        }
+        if (hour < 10) {
+            hour = '0' + hour.toString();
+        }
+        if (minute < 10) {
+            minute = '0' + minute.toString();
+        }
+        if (scond < 10) {
+            scond = '0' + scond.toString();
+        }
+        return `${year}<span>${mon}-${day}</span><span>${hour}:${minute}:${scond}</span>`;
+    };
+
     // 计量期选中
     $('.select-stage-order').on('click', function () {
         const stageList = $('.select-stage-order:checked');

+ 187 - 73
app/public/js/measure_stage.js

@@ -10,82 +10,196 @@
 
 // 获取审批流程
 $('a[data-target="#sp-list" ]').on('click', function () {
-   const data = {
-       order: $(this).attr('s-order'),
-   };
-   postData('/tender/' + tenderId + '/measure/stage/auditors', data, function (result) {
-       const stageAuditor = result.stageAuditor;
-       const auditors = result.auditors;
-       const auditHistory = result.auditHistory;
-       // 生成左边列表流程
-       const lefthtml = [];
-       lefthtml.push('<li class="list-group-item"><i class="fa fa fa-play-circle fa-rotate-90"></i> '+ stageAuditor.name +'  <small class="text-muted">'+ stageAuditor.role +'</small><span class="pull-right">原报</span></li>');
-       for (const [index,a] of auditors.entries()) {
-           if (index+1 === auditors.length) {
-               lefthtml.push('<li class="list-group-item"><i class="fa fa-stop-circle"></i> '+ a.name +'  <small class="text-muted">'+ a.role +'</small><span class="pull-right">终审</span></li>');
-           } else {
-               lefthtml.push('<li class="list-group-item"><i class="fa fa-chevron-circle-down"></i> '+ a.name +'  <small class="text-muted">'+ a.role +'</small><span class="pull-right">' + transFormToChinese(index+1) + '审</span></li>');
-           }
-       }
-       $('#auditor-list').html(lefthtml.join(''));
+    const data = {
+        order: $(this).attr('s-order'),
+    };
+    postData('/tender/' + tenderId + '/measure/stage/auditors', data, function (result) {
+        const { auditHistory, auditors, user } = result
+        let auditorsHTML = ''
+        let historyHTML = ''
+        auditors.forEach((auditor, idx) => {
+            if (idx === 0) {
+                auditorsHTML += `<li class="list-group-item">
+                    <i class="fa fa fa-play-circle fa-rotate-90"></i> ${auditor.name}
+                    <small class="text-muted">${auditor.role}</small>
+                    <span class="pull-right">原报</span>
+                </li>`
+            } else if(idx === auditors.length -1 && idx !== 0) {
+                auditorsHTML += `<li class="list-group-item">
+                    <i class="fa fa fa-stop-circle"></i> ${auditor.name}
+                    <small class="text-muted">${auditor.role}</small>
+                    <span class="pull-right">终审</span>
+                </li>`
+            } else {
+                auditorsHTML += `<li class="list-group-item">
+                    <i class="fa fa-chevron-circle-down"></i> ${auditor.name}
+                    <small class="text-muted">${auditor.role}</small>
+                    <span class="pull-right">${transFormToChinese(idx)}审</span>
+                </li>`
+            }
+        })
+        $('#auditor-list').empty()
+        $('#auditor-list').append(auditorsHTML)
+        auditHistory.forEach((auditors, idx) => {
+            historyHTML += `<div class="${idx < auditHistory.length - 1 ? 'fold-card' : ''}">
+            <div class="text-center text-muted"
+                ${idx === auditHistory.length - 1 ? 'id="end-target"' : ""}>
+                ${idx === auditHistory.length - 1 ? 1 : idx + 1}#</div>
+            <ul class="timeline-list list-unstyled mt-2">`
+            auditors.forEach((auditor, index) => {
+                if (index === 0) {
+                    historyHTML += `<li class="timeline-list-item pb-2">
+                        <div class="timeline-item-date">
+                            ${formatDate(auditor.begin_time)}
+                        </div>
+                        <div class="timeline-item-tail"></div>
+                        <div class="timeline-item-icon bg-success text-light">
+                            <i class="fa fa-caret-down"></i>
+                        </div>
+                        <div class="timeline-item-content">
+                            <div class="card">
+                                <div class="card-body p-3">
+                                    <div class="card-text">
+                                        <p class="mb-1"><span
+                                                class="h5">${user.name}</span><span
+                                                class="pull-right text-success">${idx !== 0 ? '重新' : ''}上报审批</span>
+                                        </p>
+                                        <p class="text-muted mb-0">${user.role}</p>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </li>
+                    <li class="timeline-list-item pb-2">
+                        <div class="timeline-item-date">
+                            ${formatDate(auditor.end_time)}
+                        </div>`
+
+                        if(index < auditors.length - 1) {
+                            historyHTML += `<div class="timeline-item-tail"></div>`
+                        }
+                        if(auditor.status === auditConst.status.checked) {
+                            historyHTML += `<div class="timeline-item-icon bg-success text-light">
+                                <i class="fa fa-check"></i>
+                            </div>`
+
+                        } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {
+                            historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                <i class="fa fa-level-up"></i>
+                            </div>`
+                        } else if(auditor.status === auditConst.status.checking) {
+                            historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                <i class="fa fa-ellipsis-h"></i>
+                            </div>`
+                        } else {
+                            historyHTML += `<div class="timeline-item-icon bg-secondary text-light"></div>`
+
+                        }
+                        historyHTML += `<div class="timeline-item-content">
+                            <div class="card">
+                                <div class="card-body p-3">
+                                    <div class="card-text">
+                                        <p class="mb-1"><span class="h5">${auditor.name}</span><span
+                                                class="pull-right ${auditConst.statusClass[auditor.status]}">${auditConst.statusString[auditor.status]}</span>
+                                        </p>
+                                        <p class="text-muted mb-0">${auditor.role}</p>
+                                    </div>
+                                </div>`
+                        if (auditor.opinion) {
+                        historyHTML += `<div class="card-body p-3 border-top">
+                                <p style="margin: 0;">${auditor.opinion}</p>
+                            </div>`
+                        }
+                        historyHTML += `</div></div></li>`
+                } else {
+                    historyHTML += `<li class="timeline-list-item pb-2">
+                    <div class="timeline-item-date">
+                        ${formatDate(auditor.end_time)}
+                    </div>`
+
+                    if(index < auditors.length - 1) {
+                        historyHTML += `<div class="timeline-item-tail"></div>`
+                    }
+                    if(auditor.status === auditConst.status.checked) {
+                        historyHTML += `<div class="timeline-item-icon bg-success text-light">
+                            <i class="fa fa-check"></i>
+                        </div>`
+                    } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {
+                        historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                            <i class="fa fa-level-up"></i>
+                        </div>`
+
+                    } else if(auditor.status === auditConst.status.checking) {
+                        historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                            <i class="fa fa-ellipsis-h"></i>
+                        </div>`
+                    } else {
+                        historyHTML += `<div class="timeline-item-icon bg-secondary text-light"></div>`
+                    }
+                    historyHTML += `<div class="timeline-item-content">
+                    <div class="card">
+                        <div class="card-body p-3">
+                            <div class="card-text">
+                                <p class="mb-1"><span class="h5">${auditor.name}</span>
+                                    <span
+                                        class="pull-right
+                                                        ${auditConst.statusClass[auditor.status]}">${auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''}
+                                        ${auditor.status === auditConst.status.checkNo ? user.name : ''}
+                                        ${auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : ''}
+                                    </span>
+                                </p>
+                                <p class="text-muted mb-0">${auditor.role}</p>
+                            </div>
+                        </div>`
+
+                    if (auditor.opinion) {
+                    historyHTML += `<div class="card-body p-3 border-top">
+                        <p style="margin: 0;">${auditor.opinion} </p>
+                    </div>`
+                    }
+                    historyHTML += `</div></div></li>`
+                }
+            })
+            historyHTML += '</ul></div>'
+            if(idx === auditHistory.length - 1 && auditHistory.length !== 1) {
+                historyHTML += `<div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
+                data-idx="${idx + 1}">展开历史审批流程</a></div>`
+            }
+        })
+        $('#audit-list').empty()
+        $('#audit-list').append(historyHTML)
+    })
 
-       // 生成右边列表流程
-       const righthtml = [];
-       for(const ah of auditHistory) {
-           righthtml.push('<div class="card mt-3"><ul class="list-group list-group-flush">');
-           for (let iA = 0; iA < ah.length; iA++) {
-               if (iA === 0) {
-                   righthtml.push('<li class="list-group-item">');
-                   righthtml.push('<h5 class="card-title">');
-                   righthtml.push('<i class="fa fa-play-circle fa-rotate-90 text-success"></i> '+ stageAuditor.name +' <small class="text-muted">'+ stageAuditor.role +'</small><span class="pull-right">原报</span></h5>');
-                   righthtml.push('<div class="ml-3">');
-                   righthtml.push('<span class="text-success"><small>' + (ah[iA].begin_time ? moment(ah[iA].begin_time).format('YYYY-MM-DD HH:mm:ss') : '') + '</small> '+ (auditHistory.indexOf(ah) > 0 ? '重新' : '') + '上报</span></div></li>');
-                   righthtml.push('<li class="list-group-item">');
-                   righthtml.push('<h5 class="card-title"><i class="fa '+ (iA === ah.length - 1 ? 'fa-stop-circle ' : 'fa-chevron-circle-down ') + auditConst.statusClass[ah[iA].status] +'"></i> '+ ah[iA].name +' <small class="text-muted">'+ ah[iA].role +'</small><span class="pull-right">' + (ah[iA].sort === ah[iA].max_sort ? '终' : transFormToChinese(ah[iA].sort)) + '审</span></h5>');
-                   righthtml.push('<div class="ml-3">');
-                   if (ah[iA].status !== auditConst.status.uncheck) {
-                       let timeHtml = '';
-                       if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) {
-                           timeHtml = '<small>'+ (ah[iA].end_time ? moment(ah[iA].end_time).format('YYYY-MM-DD HH:mm:ss') : '') +'</small> ';
-                       }
-                       righthtml.push('<span class="' + auditConst.statusClass[ah[iA].status] +'">'+ timeHtml + auditConst.statusString[ah[iA].status] + (ah[iA].status === auditConst.status.checkNo ? ' ' + stageAuditor.name : '') + '</span>');
-                   }
-                   righthtml.push('<p class="card-text">'+ (ah[iA].opinion !== null ? ah[iA].opinion : '') +'</p></div>');
-                   righthtml.push('</li>');
-               } else if (iA === ah.length - 1) {
-                   righthtml.push('<li class="list-group-item">');
-                   righthtml.push('<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><span class="pull-right">终审</span></h5>');
-                   righthtml.push('<div class="ml-3">');
-                   if (ah[iA].status !== auditConst.status.uncheck) {
-                       let timeHtml = '';
-                       if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) {
-                           timeHtml = '<small>'+ (ah[iA].end_time ? moment(ah[iA].end_time).format('YYYY-MM-DD HH:mm:ss') : '') +'</small> ';
-                       }
-                       righthtml.push('<span class="' + auditConst.statusClass[ah[iA].status] +'">' + timeHtml + auditConst.statusString[ah[iA].status] + (ah[iA].status === auditConst.status.checkNo ? ' ' + stageAuditor.name : '') + '</span>');
-                   }
-                   righthtml.push('<p class="card-text">'+ (ah[iA].opinion !== null ? ah[iA].opinion : '') +'</p></div>');
-                   righthtml.push('</li>');
-               } else {
-                   righthtml.push('<li class="list-group-item">');
-                   righthtml.push('<h5 class="card-title"><i class="fa '+ (iA === ah.length - 1 ? 'fa-stop-circle ' : 'fa-chevron-circle-down ') + auditConst.statusClass[ah[iA].status] +'"></i> '+ ah[iA].name +' <small class="text-muted">'+ ah[iA].role +'</small><span class="pull-right">' + (ah[iA].sort === ah[iA].max_sort ? '终' : transFormToChinese(ah[iA].sort)) + '审</span></h5>');
-                   righthtml.push('<div class="ml-3">');
-                   if (ah[iA].status !== auditConst.status.uncheck) {
-                       let timeHtml = '';
-                       if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo || ah[iA].status === auditConst.status.checkNoPre) {
-                           timeHtml = '<small>'+ (ah[iA].end_time ? moment(ah[iA].end_time).format('YYYY-MM-DD HH:mm:ss') : '') +'</small> ';
-                       }
-                       righthtml.push('<span class="' + auditConst.statusClass[ah[iA].status] +'">'+ timeHtml + auditConst.statusString[ah[iA].status] + (ah[iA].status === auditConst.status.checkNo ? ' ' + stageAuditor.name : '') + '</span>');
-                   }
-                   righthtml.push('<p class="card-text">'+ (ah[iA].opinion !== null ? ah[iA].opinion : '') +'</p></div>');
-                   righthtml.push('</li>');
-               }
-           }
-           righthtml.push('</ul></div>');
-       }
-       $('#auditor-list2').html(righthtml.join(''));
-   })
 });
 
+
+function formatDate(date) {
+    if (!date) return '';
+    date = new Date(date)
+    const year = date.getFullYear();
+    let mon = date.getMonth() + 1;
+    let day = date.getDate();
+    let hour = date.getHours();
+    let minute = date.getMinutes();
+    let scond = date.getSeconds();
+    if (mon < 10) {
+        mon = '0' + mon.toString();
+    }
+    if (day < 10) {
+        day = '0' + day.toString();
+    }
+    if (hour < 10) {
+        hour = '0' + hour.toString();
+    }
+    if (minute < 10) {
+        minute = '0' + minute.toString();
+    }
+    if (scond < 10) {
+        scond = '0' + scond.toString();
+    }
+    return `${year}<span>${mon}-${day}</span><span>${hour}:${minute}:${scond}</span>`;
+};
+
 function checkValidForm() {
     if ($('#add-qi input[name="date"]').val() === '') {
         toastr.error('请选择计量年月');

+ 29 - 0
app/public/js/revise.js

@@ -59,6 +59,21 @@ $(document).ready(() => {
             if (posSpread) posSpread.refresh();
         },
     });
+    const checkList = $.ledger_checkList({
+        id: 'check-list',
+        tabSelector: '#check-list-tab',
+        selector: '#check-list',
+        relaSpread: billsSpread,
+        storeKey: 'revise-check-' + window.location.pathname.split('/')[2] + '-' + window.location.pathname.split('/')[4],
+        checkType: ledgerCheckType,
+        afterLocated:  function () {
+            posSpreadObj.loadCurPosData();
+        },
+        afterShow: function () {
+            billsSpread.refresh();
+            if (posSpread) posSpread.refresh();
+        },
+    });
 
     // 初始化 节点树结构
     const treeSetting = {
@@ -1734,6 +1749,8 @@ $(document).ready(() => {
         pos.loadDatas(result.pos);
         posSpreadObj.loadCurPosData();
         SpreadJsObj.resetTopAndSelect(posSheet);
+
+        checkList.loadHisCheckData();
     }, null);    
     $.divResizer({
         select: '#revise-resize',
@@ -2249,6 +2266,9 @@ $(document).ready(() => {
             if (searchLedger) {
                 searchLedger.spread.refresh();
             }
+            if (checkList) {
+                checkList.spread.refresh();
+            }
         }
     });
 
@@ -2429,6 +2449,8 @@ $(document).ready(() => {
                 searchLedger.spread.refresh();
             } else if (tab.attr('content') === '#error-list') {
                 errorList.spread.refresh();
+            } else if (tab.attr('content') === '#check-list') {
+                checkList.spread.refresh();
             }
         }
         else {// 收起工具栏
@@ -2492,6 +2514,13 @@ $(document).ready(() => {
         },
         errorList: errorList,
     });
+
+    LedgerChecker({
+        ledgerTree: billsTree,
+        ledgerPos: pos,
+        checkList: checkList,
+    });
+
     $('[name=revise-start]').submit(function (e) {
         if (checkAuditorFrom()) {
             $(this).parent().parent().parent().modal('hide');

+ 181 - 0
app/public/js/shares/cs_tools.js

@@ -174,6 +174,187 @@ const showSideTools = function (show) {
         }
     };
 
+    $.ledger_checkList = function (setting) {
+        const checkTypeText = [];
+        for (const ct in setting.checkType) {
+            checkTypeText[setting.checkType[ct].value] = setting.checkType[ct].text;
+        }
+        if (!setting.spreadSetting) {
+            setting.spreadSetting = {
+                cols: [
+                    {
+                        title: '类型', field: 'type', width: 150, formatter: '@',
+                        getValue: function (data){
+                            if (setting.checkType) {
+                                return checkTypeText[data.type] || '';
+                            } else {
+                                return '';
+                            }
+                        }
+                    },
+                    {title: '行号', field: 'serialNo', hAlign: 1, width: 40, formatter: '@'},
+                    {title: '项目节编号', field: 'code', width: 80, formatter: '@'},
+                    {title: '清单编号', field: 'b_code', width: 80, formatter: '@'},
+                    {title: '清单名称', field: 'name', width: 150, formatter: '@'},
+                ],
+                emptyRows: 0,
+                headRows: 1,
+                headRowHeight: [32],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                selectedBackColor: '#fffacd',
+                readOnly: true,
+            };
+        }
+
+        const clearCheckData = function () {
+            if (setting.storeKey) removeLocalCache(setting.storeKey);
+        };
+
+        const autoShowHistory = function (show) {
+            if (setting.storeKey) {
+                setLocalCache(setting.storeKey + '-showHis', show.toString());
+            }
+        };
+
+        if (setting.selector && setting.relaSpread) {
+            const resultId = setting.id + '-spread';
+            const obj = $(setting.selector);
+            const dropdown = [];
+            if (setting.checkType) {
+                dropdown.push('<div class="dropdown">');
+                dropdown.push('<button class="btn btn-sm btn-outline-primary dropdown-toggle" type="button" id="'+ setting.id + 'drop" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">所有类型</button>');
+                dropdown.push('<div class="dropdown-menu" aria-labelledby="'+ setting.id + 'drop">');
+                dropdown.push('<a class="dropdown-item" href="javascript: void(0);" check-type="all">所有类型</a>');
+                for (const ct in setting.checkType) {
+                    dropdown.push('<a class="dropdown-item" href="javascript: void(0);" check-type="' + setting.checkType[ct].value +'">' + setting.checkType[ct].text + '</a>');
+                }
+                dropdown.push('</div>');
+                dropdown.push('</div>');
+            }
+            obj.html(
+                '<div class="sjs-bar">\n' +
+                '    <div class="pb-1 d-flex">\n' + dropdown.join('') +
+                '        <span class="ml-auto pr-2" id="' + setting.id + '-time">检查时间:2020-08-01 13:20:25</span>\n' +
+                '    </div>\n' +
+                '</div>' +
+                '<div id="' + resultId + '" class="sjs-sh">\n' +
+                '</div>'
+            );
+            autoFlashHeight();
+
+
+            const spread = SpreadJsObj.createNewSpread($('#' + resultId)[0]);
+            const sheet = spread.getActiveSheet();
+            SpreadJsObj.initSheet(sheet, setting.spreadSetting);
+            SpreadJsObj.forbiddenSpreadContextMenu('#' + resultId, spread);
+
+            spread.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(setting.relaSpread.getActiveSheet(), curBills.ledger_id, true);
+                if (setting.afterLocated) {
+                    setting.afterLocated();
+                }
+            });
+
+            const filterCheckData = function () {
+                const filter = $(this).attr('check-type');
+                $('#' + setting.id + 'drop').html(this.innerHTML);
+                for (const d of sheet.zh_data) {
+                    if (filter === 'all') {
+                        d.visible = true;
+                    } else {
+                        d.visible = d.type == filter;
+                    }
+                }
+                SpreadJsObj.refreshTreeRowVisible(sheet);
+            };
+            $('a[check-type]').bind('click', filterCheckData);
+
+            const hideCheckData = function () {
+                const tab = $(setting.tabSelector), tabPanel = $(tab.attr('content'));
+                if (tab.hasClass('active')) {
+                    $('a', '#side-menu').removeClass('active');
+                    tab.addClass('active');
+                    $('.tab-content .tab-pane').removeClass('active');
+                    tabPanel.addClass('active');
+                    showSideTools(false);
+                    if (spread) spread.refresh();
+                    if (setting.afterShow) setting.afterShow();
+                    tab.hide();
+                }
+            };
+
+            const loadCheckData = function (data, his = false) {
+                const sourceTree = setting.relaSpread.getActiveSheet().zh_tree;
+                if (!sourceTree) return;
+
+                for (const d of data.warning_data) {
+                    d.serialNo = sourceTree.getNodeIndex(sourceTree.getItems(d.ledger_id)) + 1;
+                }
+
+                $('#' + setting.id + '-time').html('检查时间:' + moment(data.check_time).format('YYYY-MM-DD hh:mm:ss'));
+                SpreadJsObj.loadSheetData(sheet, SpreadJsObj.DataType.Data, data.warning_data);
+                if (!his && setting.storeKey) {
+                    setLocalCache(setting.storeKey, JSON.stringify(data));
+                }
+                $(setting.tabSelector).show();
+            };
+            const showCheckList = function () {
+                const tab = $(setting.tabSelector), tabPanel = $(tab.attr('content'));
+                $('a', '#side-menu').removeClass('active');
+                tab.addClass('active');
+                $('.tab-content .tab-pane').removeClass('active');
+                tabPanel.addClass('active');
+                showSideTools(true);
+                spread.refresh();
+                if (setting.afterShow) setting.afterShow();
+            };
+            const loadHisCheckData = function () {
+                if (setting.storeKey) {
+                    const storeStr = getLocalCache(setting.storeKey);
+
+                    const storeData = storeStr ? JSON.parse(storeStr) : null;
+                    if (storeData) {
+                        loadCheckData(storeData, true);
+                        const showHis = getLocalCache(setting.storeKey + '-showHis');
+                        if (showHis === 'true') {
+                            showCheckList();
+                            removeLocalCache(setting.storeKey + '-showHis');
+                        }
+                    }
+                }
+            };
+            return {
+                spread: spread,
+                loadCheckData: loadCheckData,
+                clearCheckData: clearCheckData,
+                loadHisCheckData: loadHisCheckData,
+                show: showCheckList,
+                hide: hideCheckData,
+                autoShowHistory: autoShowHistory,
+            };
+        } else {
+            const loadCheckData = function (data) {
+                if (setting.storeKey) {
+                    setLocalCache(setting.storeKey, JSON.stringify(data));
+                }
+            };
+            return {
+                loadCheckData: loadCheckData,
+                clearCheckData: clearCheckData,
+                autoShowHistory: autoShowHistory,
+            };
+        }
+    };
+
     $.posSearch = function (setting) {
         if (!setting.selector || !setting.searchSpread) return;
         const searchHtml =

+ 10 - 1
app/public/js/shares/sjs_setting.js

@@ -52,5 +52,14 @@ const sjsSettingObj = (function () {
             if (col) col.formatter = '#,##0.######';
         }
     };
-    return {setFxTreeStyle, FxTreeStyle, setGridSelectStyle, setTpThousandthFormat, setThousandthFormat};
+
+    const setPropValue = function (setting, fields, prop, value) {
+        for (const f of fields) {
+            const col = setting.cols.find(function (x) {
+                return x.field === f
+            });
+            if (col) col[prop] = value;
+        }
+    };
+    return {setFxTreeStyle, FxTreeStyle, setGridSelectStyle, setTpThousandthFormat, setThousandthFormat, setPropValue};
 })();

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

@@ -137,6 +137,19 @@ function getNodeList(node) {
     })
 }
 
+function getGxbyText(data) {
+    const def = thirdParty.gxby.find(function (x) {
+        return x.value === data.gxby_status;
+    });
+    return def ? def.name : '';
+}
+function getDaglText(data) {
+    const def = thirdParty.dagl.find(function (x) {
+        return x.value === data.dagl_status;
+    });
+    return def ? def.name : '';
+}
+
 $(document).ready(() => {
     let detail, searchLedger, checkedChanges;
     // 界面布局
@@ -220,14 +233,14 @@ $(document).ready(() => {
                 font: '12px 微软雅黑',
                 getColor: function (sheet, data, row, col, defaultColor) {
                     if (col.field === 'uamount') {
-                        if (!data.vamount) {
-                            return (data.uamount && math.abs(data.uamount) > 0) ? '#ff6f5c' : defaultColor;
-                        } else if (data.uamount) {
-                            return data.vamount > 0
-                                ? data.uamount > data.vamount ? '#ff6f5c' : defaultColor
-                                : data.uamount < data.vamount ? '#ff6f5c' : defaultColor;
+                        if (data.bamount > 0) {
+                            const usedAmount = ZhCalc.add(data.uamount, data.pre_amount);
+                            return usedAmount < 0 || usedAmount > data.bamount ? '#ff6f5c' : defaultColor;
+                        } else if (data.bamount < 0) {
+                            const usedAmount = ZhCalc.add(data.uamount, data.pre_amount);
+                            return usedAmount > 0 || usedAmount < data.bamount ? '#ff6f5c' : defaultColor;
                         } else {
-                            return defaultColor;
+                            return data.uamount ? '#ff6f5c' : defaultColor;
                         }
                     } else {
                         return defaultColor;
@@ -319,16 +332,23 @@ $(document).ready(() => {
                 const data = { target: self.callData, change: [] };
                 for (const c of self.displayChanges) {
                     if (c.uamount) {
-                        const vamount = (!c.vamount || checkZero(c.vamount)) ? 0 : c.vamount;
-                        if (vamount === 0) {
-                            if ((c.b_amount > 0 && c.uamount > c.b_amount) || (c.b_amount < 0 && c.uamount < b_amount)) {
+                        if (c.bamount > 0) {
+                            const usedAmount = ZhCalc.add(c.uamount, c.pre_amount);
+                            if (usedAmount < 0 || usedAmount > c.bamount) {
                                 toastr.error('变更令:' + c.code + ' 超计,请修改本期计量后,再提交');
                                 return;
                             }
-                        } else if ((vamount > 0 && c.uamount > vamount) || (vamount < 0 && c.uamount < vamount)) {
+                        } else if (c.bamount < 0) {
+                            const usedAmount = ZhCalc.add(c.uamount, c.pre_amount);
+                            if (usedAmount > 0 || usedAmount < c.bamount) {
+                                toastr.error('变更令:' + c.code + ' 超计,请修改本期计量后,再提交');
+                                return;
+                            }
+                        } else {
                             toastr.error('变更令:' + c.code + ' 超计,请修改本期计量后,再提交');
                             return;
                         }
+
                         data.change.push({ cid: c.cid, cbid: c.cbid, qty: c.uamount });
                     }
                 }
@@ -359,6 +379,7 @@ $(document).ready(() => {
                     c.uamount = uc.qty;
                     c.vamount = ZhCalc.add(c.vamount, c.uamount);
                 }
+                c.pre_amount = ZhCalc.sub(c.used_amount, c.uamount);
             }
         }
         _loadChangeDetail(change) {
@@ -484,6 +505,18 @@ $(document).ready(() => {
     ledgerSpreadSetting.dgnUpFields = ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'];
     ledgerSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
         if (data) {
+            if (col.field === 'gxby') {
+                const def = thirdParty.gxby.find(function (x) {
+                    return x.value === data.gxby_status;
+                });
+                if (def && def.color) return def.color;
+            } else if (col.field === 'dagl') {
+                const def = thirdParty.dagl.find(function (x) {
+                    return x.value === data.dagl_status;
+                });
+                if (def && def.color) return def.color;
+            }
+
             const posRange = stagePos.ledgerPos[itemsPre + data.id] || [];
             if (posRange.length > 0) {
                 for (const p of posRange) {
@@ -500,6 +533,8 @@ $(document).ready(() => {
         }
     };
     sjsSettingObj.setFxTreeStyle(ledgerSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    sjsSettingObj.setPropValue(ledgerSpreadSetting, ['gxby'], 'getValue', getGxbyText);
+    sjsSettingObj.setPropValue(ledgerSpreadSetting, ['dagl'], 'getValue', getDaglText);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
     SpreadJsObj.initSheet(slSpread.getActiveSheet(), ledgerSpreadSetting);
     slSpread.getActiveSheet().frozenColumnCount(5);
@@ -523,10 +558,25 @@ $(document).ready(() => {
         changesObj.loadChanges({bills: node, pos: data});
     };
     posSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
+        if (data) {
+            if (col.field === 'gxby') {
+                const def = thirdParty.gxby.find(function (x) {
+                    return x.value === data.gxby_status;
+                });
+                if (def && def.color) return def.color;
+            } else if (col.field === 'dagl') {
+                const def = thirdParty.dagl.find(function (x) {
+                    return x.value === data.dagl_status;
+                });
+                if (def && def.color) return def.color;
+            }
+        }
         return data && data.end_contract_qty > data.quantity ? '#f8d7da' : defaultColor;
     };
     sjsSettingObj.setGridSelectStyle(posSpreadSetting);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(posSpreadSetting);
+    sjsSettingObj.setPropValue(posSpreadSetting, ['gxby'], 'getValue', getGxbyText);
+    sjsSettingObj.setPropValue(posSpreadSetting, ['dagl'], 'getValue', getDaglText);
     SpreadJsObj.initSheet(spSpread.getActiveSheet(), posSpreadSetting);
 
     const errorList = $.cs_errorList({

+ 123 - 52
app/public/js/stage_audit.js

@@ -9,48 +9,110 @@
  */
 
 $(document).ready(function () {
+    let timer = null
+    let oldSearchVal = null
     // 获取审核相关url
     function getUrlPre () {
         const path = window.location.pathname.split('/');
         return _.take(path, 6).join('/');
     }
 
-    // 搜索审批人
-    $('#searchAccount').click(() => {
-        const data = {
-            keyword: $('#searchName').val(),
-        };
-        postData('/search/user', data, (data) => {
-            const resultDiv = $('#searchResult');
-            if (data) {
-                $('h5>span', resultDiv).text(data.name);
-                $('#addAuditor').attr('auditorId', data.id);
-                $('h6', resultDiv).text(data.role);
-                $('p', resultDiv).text(data.company);
-                resultDiv.show();
+    $('#gr-search').bind('input propertychange', function(e) {
+        oldSearchVal = e.target.value
+        timer && clearTimeout(timer)
+        timer = setTimeout(() => {
+            const newVal = $('#gr-search').val()
+            let html = ''
+            if (newVal && newVal === oldSearchVal) {
+                accountList.filter(item => item && cur_uid !== item.id && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1))).forEach(item => {
+                    html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                        <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                class="ml-auto">${item.mobile || ''}</span></p>
+                        <span class="text-muted">${item.role || ''}</span>
+                    </dd>`
+                })
+                $('.book-list').empty()
+                $('.book-list').append(html)
             } else {
-                toastr.info('未查询到该审核人');
-                resultDiv.hide();
+                if (!$('.acc-btn').length) {
+                    accountGroup.forEach((group, idx) => {
+                        if (!group) return
+                        html += `<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="${idx}" data-type="hide"><i class="fa fa-plus-square"></i>
+                        </a> ${group.groupName}</dt>
+                        <div class="dd-content" data-toggleid="${idx}">`
+                        group.groupList.forEach(item => {
+                            if (item.id !== cur_uid) {
+                                html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                                    <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                            class="ml-auto">${item.mobile || ''}</span></p>
+                                    <span class="text-muted">${item.role || ''}</span>
+                                </dd>`
+                            }
+                        });
+                        html += '</div>'
+                    })
+                    $('.book-list').empty()
+                    $('.book-list').append(html)
+                }
             }
-        }, () => {
-            $('#searchResult').hide();
-        });
-    });
+        }, 400);
+    })
+
+    // 添加审批流程按钮逻辑
+    $('.book-list').on('click', 'dt', function () {
+        const idx = $(this).find('.acc-btn').attr('data-groupid')
+        const type = $(this).find('.acc-btn').attr('data-type')
+        if (type === 'hide') {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).show(() => {
+                $(this).children().find('i').removeClass('fa-plus-square').addClass('fa-minus-square-o')
+                $(this).find('.acc-btn').attr('data-type', 'show')
+
+            })
+        } else {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).hide(() => {
+                $(this).children().find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square')
+                $(this).find('.acc-btn').attr('data-type', 'hide')
+            })
+        }
+        return false
+    })
+
+    // 搜索审批人
+    // $('#searchAccount').click(() => {
+    //     const data = {
+    //         keyword: $('#searchName').val(),
+    //     };
+    //     postData('/search/user', data, (data) => {
+    //         const resultDiv = $('#searchResult');
+    //         if (data) {
+    //             $('h5>span', resultDiv).text(data.name);
+    //             $('#addAuditor').attr('auditorId', data.id);
+    //             $('h6', resultDiv).text(data.role);
+    //             $('p', resultDiv).text(data.company);
+    //             resultDiv.show();
+    //         } else {
+    //             toastr.info('未查询到该审核人');
+    //             resultDiv.hide();
+    //         }
+    //     }, () => {
+    //         $('#searchResult').hide();
+    //     });
+    // });
     // 添加审批人
-    $('#addAuditor').click(() => {
-        postData(getUrlPre() + '/audit/add', { auditorId: $('#addAuditor').attr('auditorId') }, (data) => {
-            const html = [];
-            html.push('<li class="list-group-item" auditorId="'+ data.aid +'"><a href="javascript: void(0)" class="text-danger pull-right">移除</a>');
-            html.push('<span>');
-            html.push(data.order + ' ');
-            html.push(data.name + ' ');
-            html.push('</span>');
-            html.push('<small class="text-muted">');
-            html.push(data.role);
-            html.push('</small></li>');
-            $('#auditors').append(html.join(''));
-        });
-    });
+    // $('#addAuditor').click(() => {
+    //     postData(getUrlPre() + '/audit/add', { auditorId: $('#addAuditor').attr('auditorId') }, (data) => {
+    //         const html = [];
+    //         html.push('<li class="list-group-item" auditorId="'+ data.aid +'"><a href="javascript: void(0)" class="text-danger pull-right">移除</a>');
+    //         html.push('<span>');
+    //         html.push(data.order + ' ');
+    //         html.push(data.name + ' ');
+    //         html.push('</span>');
+    //         html.push('<small class="text-muted">');
+    //         html.push(data.role);
+    //         html.push('</small></li>');
+    //         $('#auditors').append(html.join(''));
+    //     });
+    // });
     // 审批人分组选择
     $('#account_group').change(function () {
         let account_html = '<option value="0">选择审批人</option>';
@@ -64,9 +126,8 @@ $(document).ready(function () {
         $('#account_list').html(account_html);
     });
     // 添加到审批流程中
-    $('body').on('change', '#account_list', function () {
-        let id = $(this).val();
-        id = parseInt(id);
+    $('dl').on('click', 'dd', function () {
+        const id = parseInt($(this).data('id'));
         if (id !== 0) {
             postData(getUrlPre() + '/audit/add', { auditorId: id }, (data) => {
                 const html = [];
@@ -142,14 +203,14 @@ $(document).ready(function () {
     });
     // 退回选择修改审批人流程
     $('#hideSp').click(function () {
-        $('#sp-list2').modal('hide');
+        $('#sp-list').modal('hide');
     });
     $('a[f-target]').click(function () {
         $($(this).attr('f-target')).modal('show');
     });
 
     // 多层modal关闭后的滚动bug修复
-    $('#sp-list2').on('hidden.bs.modal', function (e) {
+    $('#sp-list').on('hidden.bs.modal', function (e) {
         $(document.body).addClass('modal-open');
     });
 
@@ -198,20 +259,30 @@ function checkAuditorFrom () {
     return true;
 }
 // texterea换行
-function auditCheck(i) {
-    const inlineRadio1 = $('#inlineRadio1:checked').val()
-    const inlineRadio2 = $('#inlineRadio2:checked').val()
-    if (i === 1) {
-        if (!inlineRadio1 && !inlineRadio2) {
-            if (!$('#warning-text').length) {
-                $('#reject-process').prepend('<p id="warning-text" style="color: red; margin: 0;">请选择退回流程</p>');
-            }
-            return false;
-        }
-        if ($('#warning-text').length) $('#warning-text').remove()
-    }
-    return true;
-}
+// function auditCheck(i) {
+//     debugger
+//     const inlineRadio1 = $('#inlineRadio1:checked').val()
+//     const inlineRadio2 = $('#inlineRadio2:checked').val()
+//     if (i === 1) {
+//         if (!inlineRadio1 && !inlineRadio2) {
+//             if (!$('#warning-text').length) {
+//                 $('#reject-process').prepend('<p id="warning-text" style="color: red; margin: 0;">请选择退回流程</p>');
+//             }
+//             return false;
+//         }
+//         if ($('#warning-text').length) $('#warning-text').remove()
+//     }
+//     $(this).parent().parent().modal('hide');
+//         const data = {
+//             opinion: $('[name=opinion]', this).val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' '),
+//             checkType: parseInt($('[name=checkType]:checked', this).val()),
+//         };
+//         debugger
+//         postData(this.action, data, function () {
+//             window.location.reload();
+//         })
+//         return false
+// }
 
 /**
  * 获取成功后的操作

+ 4 - 2
app/public/js/stage_pay.js

@@ -511,7 +511,7 @@ $(document).ready(() => {
             if (data) {
                 if (col.field === 'tp') {
                     $('#expr').val(data.expr).attr('field', 'expr').attr('org', data.expr)
-                        .attr('readOnly', readOnly|| payBase.isSpecial(data));
+                        .attr('readOnly', readOnly|| (payBase.isYF(data) || payBase.isWC(data)));
                 } else if (col.field === 'sprice') {
                     $('#expr').val(data.sexpr).attr('field', 'sexpr').attr('org', data.sexpr)
                         .attr('readOnly', readOnly|| payCol.readOnly.sprice(data));
@@ -892,7 +892,9 @@ $(document).ready(() => {
             if (field === 'expr') {
                 data.type = 'stage';
                 data.updateData = { pid: select.pid, tp: null, expr: newValue };
-                const [valid, msg] = paySpreadObj._checkExpr(newValue, data.updateData);
+                const [valid, msg] = payBase.isSF(select)
+                    ? paySpreadObj._checkSfExpr(newValue, data.updateData)
+                    : paySpreadObj._checkExpr(newValue, data.updateData);
                 if (!valid) {
                     toastr.warning(msg);
                     this.value = select.expr;

+ 44 - 3
app/router.js

@@ -1,7 +1,6 @@
 'use strict';
 
 module.exports = app => {
-
     // session验证中间件
     const sessionAuth = app.middlewares.sessionAuth();
     // 创建时间自动填充中间件
@@ -18,7 +17,8 @@ module.exports = app => {
     const api2otherCheck = app.middlewares.api2otherCheck();
     // 微信验证登录中间件
     const wechatAuth = app.middlewares.wechatAuth();
-
+    // 预付款中间件
+    const advanceCheck = app.middlewares.advanceCheck();
     // 登入登出相关
     app.get('/login', 'loginController.index');
     app.get('/login/port', api2otherCheck, 'loginController.port');
@@ -90,8 +90,10 @@ module.exports = app => {
     // 金额概况
     app.get('/list', sessionAuth, 'tenderController.listDefault');
     app.get('/list/info', sessionAuth, 'tenderController.listInfo');
+
     // 计量进度
     app.get('/list/progress', sessionAuth, 'tenderController.listProgress');
+
     // 管理标段
     app.get('/list/manage', sessionAuth, 'tenderController.listManage');
     app.post('/list/add', sessionAuth, 'tenderController.addTender');
@@ -104,6 +106,23 @@ module.exports = app => {
     app.post('/tender/:id/save', sessionAuth, tenderCheck, 'tenderController.saveTenderInfo');
     app.post('/tender/rule', sessionAuth, 'tenderController.rule');
     app.post('/tender/:id/rule/first', sessionAuth, tenderCheck, 'tenderController.ruleFirst');
+
+    // 预付款
+    app.get('/tender/:id/advance', sessionAuth, tenderCheck, 'advanceController.index');
+    app.get('/tender/:id/advance/material', sessionAuth, tenderCheck, 'advanceController.materialList');
+    app.post('/tender/:id/advance/create', sessionAuth, tenderCheck, 'advanceController.create');
+    app.get('/tender/:id/advance/:order/detail', sessionAuth, tenderCheck, advanceCheck, 'advanceController.detail');
+    app.post('/tender/:id/advance/:type/create', sessionAuth, tenderCheck, 'advanceController.create');
+    app.post('/tender/:id/advance/:order/audit/add', sessionAuth, tenderCheck, advanceCheck, 'advanceController.addAudit');
+    app.post('/tender/:id/advance/:order/audit/delete', sessionAuth, tenderCheck, advanceCheck, 'advanceController.deleteAudit');
+    app.post('/tender/:id/advance/:order/audit/start', sessionAuth, tenderCheck, advanceCheck, 'advanceController.start');
+    app.post('/tender/:id/advance/:order/audit/check', sessionAuth, tenderCheck, advanceCheck, 'advanceController.checkAudit');
+    app.post('/tender/:id/advance/:order/update', sessionAuth, tenderCheck, advanceCheck, 'advanceController.update');
+    app.post('/tender/:id/advance/:order/file/upload', sessionAuth, tenderCheck, advanceCheck, 'advanceController.upload');
+    app.get('/tender/:id/advance/:order/file/:fid/download', sessionAuth, tenderCheck, 'advanceController.downloadFile');
+    app.post('/tender/:id/advance/:order/file/del', sessionAuth, tenderCheck, advanceCheck, 'advanceController.deleteFile');
+    app.post('/tender/:id/advance/:order/auditors', sessionAuth, tenderCheck, advanceCheck, 'advanceController.getAuditors');
+
     // 标段协作办公
     app.get('/tender/:id/cooperation', sessionAuth, tenderCheck, 'tenderController.tenderCooperation');
 
@@ -118,6 +137,7 @@ module.exports = app => {
     app.post('/tender/:id/pos/paste', sessionAuth, tenderCheck, 'ledgerController.posPaste');
     app.post('/tender/:id/ledger/deal2sgfh', sessionAuth, tenderCheck, 'ledgerController.deal2sgfh');
     app.post('/tender/:id/ledger/check', sessionAuth, tenderCheck, 'ledgerController.check');
+
     // 台账审批相关
     app.get('/tender/:id/ledger/audit', sessionAuth, tenderCheck, 'ledgerAuditController.index');
     app.post('/tender/:id/ledger/audit/add', sessionAuth, tenderCheck, 'ledgerAuditController.add');
@@ -139,6 +159,7 @@ module.exports = app => {
     app.post('/tender/:id/revise/cancel', sessionAuth, tenderCheck, 'reviseController.cancel');
     app.post('/tender/:id/revise/save', sessionAuth, tenderCheck, 'reviseController.save');
     // app.post('/tender/:id/revise/deal2sgfh', sessionAuth, tenderCheck, 'reviseController.deal2sgfh');
+
     // 台账修订页面
     app.get('/tender/:id/revise/info', sessionAuth, tenderCheck, 'reviseController.info');
     app.post('/tender/:id/revise/auditors', sessionAuth, tenderCheck, 'reviseController.reviseAuditors');
@@ -146,10 +167,12 @@ module.exports = app => {
     app.post('/tender/:id/revise/info/update', sessionAuth, tenderCheck, 'reviseController.update');
     app.post('/tender/:id/revise/info/upload-excel/:ueType', sessionAuth, tenderCheck, 'reviseController.uploadExcel');
     app.post('/tender/:id/revise/info/check', sessionAuth, tenderCheck, 'reviseController.checkData');
+
     // 查看修订数据
     app.get('/tender/:id/revise/history', sessionAuth, tenderCheck, 'reviseController.history');
     app.post('/tender/:id/revise/history/load', sessionAuth, tenderCheck, 'reviseController.loadHistoryData');
     app.post('/tender/:id/revise/history/info', sessionAuth, tenderCheck, 'reviseController.historyInfo');
+
     // 修订审批
     app.post('/tender/:id/revise/audit/add', sessionAuth, tenderCheck, 'reviseController.addAuditor');
     app.post('/tender/:id/revise/audit/remove', sessionAuth, tenderCheck, 'reviseController.removeAuditor');
@@ -168,8 +191,10 @@ module.exports = app => {
     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');
@@ -183,12 +208,14 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/valid-change', sessionAuth, tenderCheck, stageCheck, 'stageController.searchValidChange');
     app.post('/tender/:id/measure/stage/:order/use-change', sessionAuth, tenderCheck, stageCheck, 'stageController.useChange');
     app.post('/tender/:id/measure/stage/:order/check', sessionAuth, tenderCheck, stageCheck, 'stageController.check');
+
     // 计量附件
     app.post('/tender/:id/measure/stage/:order/upload/file', sessionAuth, tenderCheck, stageCheck, 'stageController.uploadFile');
     app.get('/tender/:id/measure/stage/:order/download/file/:fid', sessionAuth, 'stageController.downloadFile');
     app.post('/tender/:id/measure/stage/:order/delete/file', sessionAuth, tenderCheck, stageCheck, 'stageController.deleteFile');
     app.post('/tender/:id/measure/stage/:order/save/file', sessionAuth, tenderCheck, stageCheck, 'stageController.saveFile');
     app.post('/tender/:id/measure/stage/:order/check/file', sessionAuth, tenderCheck, stageCheck, 'stageController.checkFile');
+
     // 中间计量
     app.get('/tender/:id/measure/stage/:order/detail', sessionAuth, tenderCheck, stageCheck, 'stageController.detail');
     app.post('/tender/:id/measure/stage/:order/detail/build', sessionAuth, tenderCheck, stageCheck, 'stageController.buildDetailData');
@@ -197,6 +224,7 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/detail/save', sessionAuth, tenderCheck, stageCheck, 'stageController.saveDetailData');
     app.post('/tender/:id/measure/stage/:order/detail/add-img', sessionAuth, tenderCheck, stageCheck, 'stageController.addCalcImage');
     app.post('/tender/:id/measure/stage/:order/detail/merge-img', sessionAuth, tenderCheck, stageCheck, 'stageController.mergeCalcImage');
+
     // 合同支付
     app.get('/tender/:id/measure/stage/:order/pay', sessionAuth, tenderCheck, stageCheck, 'stageController.pay');
     app.post('/tender/:id/measure/stage/:order/pay/detail', sessionAuth, tenderCheck, stageCheck, 'stageController.chapterDetail');
@@ -204,24 +232,30 @@ 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, tenderCheck, stageCheck, '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/bwtz', sessionAuth, tenderCheck, stageCheck, 'stageController.bwtz');
     app.post('/tender/:id/measure/stage/:order/bwtz/load', sessionAuth, tenderCheck, stageCheck, 'stageController.loadBwtz');
+
     // 清单汇总
     app.get('/tender/:id/measure/stage/:order/gather', sessionAuth, tenderCheck, stageCheck, 'stageController.gather');
+
     // 审核比较
     app.get('/tender/:id/measure/stage/:order/compare', sessionAuth, tenderCheck, stageCheck, 'stageController.compare');
     app.post('/tender/:id/measure/stage/:order/compare/load', sessionAuth, tenderCheck, stageCheck, 'stageController.compareAuditor');
+
     // 附加功能
     app.get('/tender/:id/measure/stage/:order/extra/jgcl', sessionAuth, tenderCheck, stageCheck, 'stageExtraController.jgcl');
     app.post('/tender/:id/measure/stage/:order/extra/jgcl/load', sessionAuth, tenderCheck, stageCheck, 'stageExtraController.loadJgcl');
@@ -235,9 +269,11 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/extra/upload/file', sessionAuth, tenderCheck, stageCheck, 'stageExtraController.uploadFile');
     app.get('/tender/:id/measure/stage/:order/extra/download/file', sessionAuth, tenderCheck, stageCheck, 'stageExtraController.downloadFile');
     app.post('/tender/:id/measure/stage/:order/extra/delete/file', sessionAuth, tenderCheck, stageCheck, 'stageExtraController.deleteFile');
+
     // 期审批管理
     app.get('/tender/:id/measure/stage/:order/manager', sessionAuth, tenderCheck, stageCheck, 'stageController.manager');
     app.post('/tender/:id/measure/stage/:order/manager/audit/delete', sessionAuth, tenderCheck, stageCheck, 'stageController.managerAuditDelete');
+
     // 报表
     app.get('/tender/:id/report', sessionAuth, tenderCheck, 'reportController.index');
     app.get('/tender/:id/measure/stage/:order/report', sessionAuth, tenderCheck, stageCheck, 'reportController.index');
@@ -291,6 +327,8 @@ module.exports = app => {
     // 调差工料
     app.get('/tender/:id/measure/material/:order', sessionAuth, tenderCheck, materialCheck, 'materialController.info');
     app.post('/tender/:id/measure/material/:order/save', sessionAuth, tenderCheck, materialCheck, 'materialController.saveBillsData');
+    // 月信息价
+    app.post('/tender/:id/measure/material/:order/month/save', sessionAuth, tenderCheck, materialCheck, 'materialController.saveMonth');
     // 调差清单
     app.get('/tender/:id/measure/material/:order/list', sessionAuth, tenderCheck, materialCheck, 'materialController.list');
     app.post('/tender/:id/measure/material/:order/list/save', sessionAuth, tenderCheck, materialCheck, 'materialController.saveListsData');
@@ -298,6 +336,7 @@ module.exports = app => {
     // 附件
     app.get('/tender/:id/measure/material/:order/file', sessionAuth, tenderCheck, materialCheck, 'materialController.file');
     app.post('/tender/:id/measure/material/:order/file/upload', sessionAuth, tenderCheck, materialCheck, 'materialController.upload');
+    app.get('/tender/:id/measure/material/:order/file/:fid/download', sessionAuth, tenderCheck, 'materialController.downloadFile');
     app.post('/tender/:id/measure/material/:order/file/find', sessionAuth, tenderCheck, materialCheck, 'materialController.getCurMatericalFiles');
     app.post('/tender/measure/material/file/delete', sessionAuth, 'materialController.deleteFile');
 
@@ -355,6 +394,8 @@ module.exports = app => {
     app.get('/wx/oauth', 'wechatController.oauth');
     app.get('/wx/bind', wechatAuth, 'wechatController.bind');
     app.post('/wx/bindwx', wechatAuth, 'wechatController.bindwx');
-    app.get('/wx/test', wechatAuth, 'wechatController.testwx');
+    app.get('/wx/url2wap', wechatAuth, 'wechatController.url2wap');
+    app.get('/wx/project', wechatAuth, 'wechatController.project');
+    app.get('/wx/test', 'wechatController.testwx');
     app.get('/MP_verify_t3MkWAMqplVxPjmr.txt', 'wechatController.oauthTxt');
 };

+ 170 - 0
app/service/advance.js

@@ -0,0 +1,170 @@
+'use strict';
+
+const auditConst = require('../const/audit').advance;
+
+module.exports = app => {
+    class Advance extends app.BaseService {
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'advance_pay';
+        }
+        /**
+         * 获取预付款列表
+         * @param {Number} tid 标段id
+         * @param {Number} type 预付款类型
+         */
+        async getAdvanceList(tid, type) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('tid', {
+                value: tid,
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere('type', {
+                value: type,
+                operate: '=',
+            });
+            if (this.ctx.session.sessionUser.accountId !== this.ctx.tender.data.user_id) {
+                this.sqlBuilder.setAndWhere('status', {
+                    value: auditConst.status.uncheck,
+                    operate: '!=',
+                });
+            }
+            this.sqlBuilder.orderBy = [['order', 'desc']];
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const advance = await this.db.query(sql, sqlParam);
+            for (const item of advance) {
+                item.curAuditor = await this.ctx.service.advanceAudit.getAuditorByStatus(item.id, item.status, item.times);
+                if (item.status === auditConst.status.checkNoPre) {
+                    item.curAuditor2 = await this.ctx.service.advanceAudit.getAuditorByStatus(item.id, auditConst.status.checking);
+                }
+                item.fileList = await this.ctx.service.advanceFile.getAdvanceFiles({ vid: item.id });
+            }
+            return advance;
+        }
+
+        /**
+         * 获取预付款最新一期
+         * @param {Number} tid 标段id
+         * @param {String} type 类型: 开工预付款|材料预付款 (0|1)
+         * @param {Boolean} includeUnCheck 包括未上报的
+         * @return {Promise<*>} 实例结果集
+         */
+        async getLastestAdvance(tid, type, includeUnCheck = false) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('tid', {
+                value: tid,
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere('type', {
+                value: type,
+                operate: '=',
+            });
+            if (!includeUnCheck) {
+                this.sqlBuilder.setAndWhere('status', {
+                    value: auditConst.status.uncheck,
+                    operate: '!=',
+                });
+            }
+            this.sqlBuilder.orderBy = [['order', 'desc']];
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const advance = await this.db.queryOne(sql, sqlParam);
+            return advance;
+        }
+
+        /**
+         * 创建一条新的记录
+         * @param {String} type 类型: 开工预付款|材料预付款 (start|material)
+         * @return {Promise<*>} 插入结果集
+         */
+        async createRecord(type) {
+            const { ctx } = this;
+            const uid = ctx.session.sessionUser.accountId;
+            const tid = ctx.tender.id;
+            let latestOrder = await this.getLastestAdvance(tid, type);
+            if (!latestOrder) {
+                latestOrder = {
+                    order: 1,
+                    prev_amount: 0.00,
+                    prev_total_amount: 0.00,
+                };
+            } else {
+                latestOrder.order = latestOrder.order + 1;
+            }
+            const record = await this.db.insert(this.tableName, { type, uid, tid, status: auditConst.status.uncheck, order: latestOrder.order, prev_amount: latestOrder.prev_total_amount, prev_total_amount: latestOrder.prev_total_amount });
+            const auditors = await ctx.service.advanceAudit.getAuditGroupByList(latestOrder.id, latestOrder.times);
+            auditors.forEach(async (auditor, idx) => {
+                const { audit_id } = auditor;
+                await ctx.service.advanceAudit.db.insert(ctx.service.advanceAudit.tableName, {
+                    tid: latestOrder.tid, audit_id, type: latestOrder.type, vid: record.insertId, times: 1, order: idx + 1, status: 1, create_time: new Date(),
+                });
+            });
+            return await this.getDataById(record.insertId);
+        }
+
+        /**
+         * 获取上一期预付款记录
+         * @param {Number} tid 标段id
+         * @param {Number} type 预付款类型
+         */
+        async getPreviousRecord(tid, type) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('tid', {
+                value: tid,
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere('type', {
+                value: type,
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere('order', {
+                value: this.ctx.advance.order - 1,
+                operate: '=',
+            });
+            this.sqlBuilder.orderBy = [['order', 'desc']];
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        /**
+         * 计算列表进度条所需数据
+         * @param {Object} latest 最新期的数据
+         * @param {Number} payTotal 预付款金额总数
+         * @return {Object} progress所需数据
+         */
+        async calcProgress(latest, payTotal) {
+            const { ctx } = this;
+            if (!latest) {
+                latest = { prev_total_amount: 0, prev_amount: 0, cur_amount: 0 };
+            }
+            const surplus = ctx.helper.sub(payTotal, latest.prev_total_amount || 0);
+            const p_ratio = ctx.helper.mul(ctx.helper.div(latest.prev_amount || 0, payTotal), 100, 2); // 截止上期金额总数
+            const c_ratio = ctx.helper.mul(ctx.helper.div(latest.cur_amount || 0, payTotal), 100, 2); // 截止本期金额总数
+            const s_ratio = ctx.helper.mul(ctx.helper.div(surplus, payTotal), 100, 2); // 剩余金额总数
+            return {
+                p_amount: latest.prev_amount,
+                c_amount: latest.cur_amount,
+                s_amount: surplus,
+                p_ratio,
+                c_ratio,
+                s_ratio,
+            };
+        }
+
+        /**
+         * 更新预付款记录
+         * @param {Object} payload 载荷
+         * @param {Number} id 预付款id
+         */
+        async updateAdvance(payload, id) {
+            const { ctx } = this;
+            const prevRecord = await this.getPreviousRecord(ctx.tender.id, ctx.advance.type) || { prev_total_amount: 0 };
+            const { cur_amount } = payload;
+            payload.prev_total_amount = ctx.helper.add(cur_amount, prevRecord.prev_total_amount);
+            return await this.update(payload, {
+                id,
+            });
+
+        }
+    }
+    return Advance;
+};

+ 499 - 0
app/service/advance_audit.js

@@ -0,0 +1,499 @@
+'use strict';
+
+const auditConst = require('../const/audit').advance;
+const pushType = require('../const/audit').pushType;
+module.exports = app => {
+    class AdvanceAudit extends app.BaseService {
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'advance_audit';
+        }
+
+        /**
+         * 获取审核人流程列表
+         * @param {Number} vid 预付款记录id
+         * @param {Number} times 审核次数
+         * @return {Promise<Array>} 查询结果集
+         */
+        async getAuditGroupByList(vid, times = 1) {
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`vid`, la.`order` ' +
+                'FROM ?? AS la, ?? AS pa ' +
+                'WHERE la.`vid` = ? and la.`times` = ? and la.`audit_id` = pa.`id` GROUP BY la.`audit_id` ORDER BY la.`order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, vid, times];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 获取审核人流程列表(包括原报)
+         * @param {Number} vid 预付款记录id
+         * @param {Number} times 审核次数
+         * @return {Promise<Array>} 查询结果集(包括原报)
+         */
+        async getAuditorsWithOwner(vid, times = 1) {
+            const result = await this.getAuditGroupByList(vid, times);
+            const sql =
+                'SELECT pa.`id` As audit_id, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As vid, 0 As `order`' +
+                '  FROM ' +
+                this.ctx.service.advance.tableName +
+                ' As s' +
+                '  LEFT JOIN ' +
+                this.ctx.service.projectAccount.tableName +
+                ' As pa' +
+                '  ON s.uid = pa.id' +
+                '  WHERE s.id = ?';
+            const sqlParam = [times, vid, vid];
+            const user = await this.db.queryOne(sql, sqlParam);
+            result.unshift(user);
+            return result;
+        }
+
+        /**
+         * 获取最新审核顺序
+         * @param {Number} vid - 预付款id
+         * @param {Number} times - 第几次审批
+         * @return {Number} 审核顺序
+         */
+        async getNewOrder(vid, times = 1) {
+            const sql = 'SELECT Max(??) As max_order FROM ?? Where `vid` = ? and `times` = ?';
+            const sqlParam = ['order', this.tableName, vid, times];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result && result.max_order ? result.max_order + 1 : 1;
+        }
+
+        /**
+         * 新增审核人
+         * @param {Number} tid - 标段id
+         * @param {Number} vid - 预付款id
+         * @param {Number} audit_id - 审核人id
+         * @param {Number} times - 第几次审批
+         * @return {Boolean} 是否插入成功
+         */
+        async addAuditor(tid, vid, audit_id, times = 1) {
+            const newOrder = await this.getNewOrder(vid, times);
+            const record = {
+                tid,
+                vid,
+                audit_id,
+                times,
+                order: newOrder,
+                status: auditConst.status.uncheck,
+            };
+            const result = await this.db.insert(this.tableName, record);
+            return result && result.affectedRows === 1;
+        }
+
+        /**
+         * 移除审核人
+         * @param {Number} vid - 预付款id
+         * @param {Number} audit_id - 审核人id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<boolean>}
+         */
+        async deleteAuditor(vid, audit_id, times = 1) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const condition = { vid, audit_id, times };
+                const auditor = await this.getDataByCondition(condition);
+                if (!auditor) {
+                    throw '该审核人不存在';
+                }
+                await this._syncOrderByDelete(transaction, vid, auditor.order, times);
+                await transaction.delete(this.tableName, condition);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
+
+        /**
+         * 移除审核人时,同步其后审核人order
+         * @param {Function} transaction - 事务
+         * @param {Number} vid - 预付款id
+         * @param {Number} order - 审核顺序
+         * @param {Number} times - 第几次审批
+         * @return {Promise<*>} 查询结果集
+         * @private
+         */
+        async _syncOrderByDelete(transaction, vid, order, times) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('vid', {
+                value: this.db.escape(vid),
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere('order', {
+                value: order,
+                operate: '>=',
+            });
+            this.sqlBuilder.setAndWhere('times', {
+                value: times,
+                operate: '=',
+            });
+            this.sqlBuilder.setUpdateData('order', {
+                value: 1,
+                selfOperate: '-',
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+            const data = await transaction.query(sql, sqlParam);
+            return data;
+        }
+
+        /**
+         * 获取当前审核人
+         * @param {Number} vid - 预付款id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<Object>} 查询结果集
+         */
+        async getCurAuditor(vid, times = 1) {
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`create_time`, la.`end_time` ' +
+                'FROM ?? AS la, ?? AS pa ' +
+                'WHERE la.`vid` = ? and la.`status` = ? and la.`times` = ?' +
+                '    and la.`audit_id` = pa.`id`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, vid, auditConst.status.checking, times];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        /**
+         * 获取审核列表信息
+         * @param {Number} vid - 预付款id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<Array>} 查询结果集
+         */
+        async getAuditors(vid, times = 1) {
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`create_time`, la.`end_time`, g.`sort` ' +
+                'FROM ?? AS la, ?? AS pa, (SELECT `audit_id`,(@i:=@i+1) as `sort` FROM ??, (select @i:=0) as it WHERE `vid` = ? AND `times` = ? GROUP BY `audit_id`) as g ' +
+                'WHERE la.`vid` = ? and la.`times` = ? and la.`audit_id` = pa.`id` and g.`audit_id` = la.`audit_id` order by la.`order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, vid, times, vid, times];
+            const result = await this.db.query(sql, sqlParam);
+            const sql2 = 'SELECT COUNT(a.`audit_id`) as num FROM (SELECT `audit_id` FROM ?? WHERE `vid` = ? AND `times` = ? GROUP BY `audit_id`) as a';
+            const sqlParam2 = [this.tableName, vid, times];
+            const count = await this.db.queryOne(sql2, sqlParam2);
+            for (const i in result) {
+                result[i].max_sort = count.num;
+            }
+            return result;
+        }
+
+        /**
+         * 获取审核人信息
+         * @param {Number} vid - 预付款id
+         * @param {Number} audit_id - 审核人id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<*>} 查询结果
+         */
+        async getAuditor(vid, audit_id, times = 1) {
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`create_time`, la.`end_time` ' +
+                'FROM ?? AS la, ?? AS pa ' +
+                'WHERE la.`vid` = ? and la.`audit_id` = ? and la.`times` = ?' +
+                '    and la.`audit_id` = pa.`id`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, vid, audit_id, times];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        /**
+         * 开始审批
+         * @param {Number} vid - 预付款id
+         * @param {Number} times - 第几次审批
+         * @param {Object} data - 载荷
+         */
+        async start(vid, times = 1, data) {
+            const audit = await this.getDataByCondition({ vid, times, order: 1 });
+            if (!audit) {
+                throw '请先选择审批人,再上报数据';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, { id: audit.id, status: auditConst.status.checking, create_time: new Date() });
+                await transaction.update(this.ctx.service.advance.tableName, {
+                    id: audit.vid,
+                    ...data,
+                });
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
+
+        async _checked(pid, advanceId, checkData, times) {
+            const time = new Date();
+
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({ vid: advanceId, times, status: auditConst.status.checking });
+            if (!audit) {
+                throw '审核数据错误';
+            }
+
+            // 获取审核人列表
+            const sql = 'SELECT `tid`, `vid`, `audit_id`, `order` FROM ?? WHERE `vid` = ? and `times` = ? GROUP BY `audit_id` ORDER BY `id` ASC';
+            const sqlParam = [this.tableName, advanceId, times];
+            const auditors = await this.db.query(sql, sqlParam);
+
+            const nextAudit = await this.getDataByCondition({ vid: advanceId, times, order: audit.order + 1 });
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
+                // 获取推送必要信息
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, advanceId, audit.audit_id);
+                // 添加推送
+                const records = [{ pid, type: pushType.advance, uid: this.ctx.advance.uid, status: auditConst.status.checked, content: noticeContent }];
+                auditors.forEach(audit => {
+                    records.push({ pid, type: pushType.advance, uid: audit.audit_id, status: auditConst.status.checked, content: noticeContent });
+                });
+                await transaction.insert(this.ctx.service.noticePush.tableName, records);
+                // 无下一审核人表示,审核结束
+                if (nextAudit) {
+                    // 流程至下一审批人
+                    await transaction.update(this.tableName, { id: nextAudit.id, status: auditConst.status.checking, create_time: time });
+
+                    // 同步期信息
+                    await transaction.update(this.ctx.service.advance.tableName, {
+                        id: advanceId,
+                        status: auditConst.status.checking,
+                    });
+                } else {
+                    await transaction.update(this.ctx.service.advance.tableName, {
+                        id: advanceId,
+                        status: checkData.checkType,
+                        end_time: time,
+                    });
+                }
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async _checkNo(pid, advanceId, checkData, times) {
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({ vid: advanceId, times, status: auditConst.status.checking });
+            if (!audit) {
+                throw '审核数据错误';
+            }
+            const sql = 'SELECT `tid`, `vid`, `audit_id`, `order` FROM ?? WHERE `vid` = ? and `times` = ? GROUP BY `audit_id` ORDER BY `id` ASC';
+            const sqlParam = [this.tableName, advanceId, times];
+            const auditors = await this.db.query(sql, sqlParam);
+            let order = 1;
+            for (const a of auditors) {
+                a.times = times + 1;
+                a.order = order;
+                a.status = auditConst.status.uncheck;
+                order++;
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
+
+                // 添加到消息推送表
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, advanceId, audit.audit_id);
+                const records = [{ pid, type: pushType.advance, uid: this.ctx.advance.uid, status: auditConst.status.checkNo, content: noticeContent }];
+                auditors.forEach(audit => {
+                    records.push({ pid, type: pushType.advance, uid: audit.audit_id, status: auditConst.status.checkNo, content: noticeContent });
+                });
+                await transaction.insert(this.ctx.service.noticePush.tableName, records);
+
+                // 同步期信息
+                await transaction.update(this.ctx.service.advance.tableName, {
+                    id: advanceId,
+                    status: checkData.checkType,
+                    times: times + 1,
+                });
+                // 拷贝新一次审核流程列表
+                await transaction.insert(this.tableName, auditors);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async _checkNoPre(pid, advanceId, checkData, times) {
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({ vid: advanceId, times, status: auditConst.status.checking });
+            if (!audit || audit.order <= 1) {
+                throw '审核数据错误';
+            }
+            // 添加重新审批后,不能用order-1,取groupby值里的上一个才对
+            const auditors2 = await this.getAuditGroupByList(advanceId, times);
+            const auditorIndex = await auditors2.findIndex(function(item) {
+                return item.audit_id === audit.audit_id;
+            });
+            const preAuditor = auditors2[auditorIndex - 1];
+            const noticeContent = await this.getNoticeContent(pid, audit.tid, advanceId, audit.audit_id);
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 添加到消息推送表
+                const records = [{ pid, type: pushType.advance, uid: this.ctx.advance.uid, status: auditConst.status.checkNoPre, content: noticeContent }];
+                auditors2.forEach(audit => {
+                    records.push({ pid, type: pushType.advance, uid: audit.audit_id, status: auditConst.status.checkNoPre, content: noticeContent });
+                });
+                await transaction.insert(this.ctx.service.noticePush.tableName, records);
+                await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
+                // 顺移气候审核人流程顺序
+                this.initSqlBuilder();
+                this.sqlBuilder.setAndWhere('vid', { value: advanceId, operate: '=' });
+                this.sqlBuilder.setAndWhere('order', { value: audit.order, operate: '>' });
+                this.sqlBuilder.setUpdateData('order', { value: 2, selfOperate: '+' });
+                const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+                await transaction.query(sql, sqlParam);
+                const newAuditors = [];
+                newAuditors.push({
+                    tid: audit.tid,
+                    vid: audit.vid,
+                    audit_id: preAuditor.audit_id,
+                    times: audit.times,
+                    order: audit.order + 1,
+                    status: auditConst.status.checking,
+                    create_time: time,
+                });
+                newAuditors.push({
+                    tid: audit.tid,
+                    vid: audit.vid,
+                    audit_id: audit.audit_id,
+                    times: audit.times,
+                    order: audit.order + 2,
+                    status: auditConst.status.uncheck,
+                });
+
+                await transaction.insert(this.tableName, newAuditors);
+                await transaction.commit();
+            } catch (error) {
+                await transaction.rollback();
+                throw error;
+            }
+        }
+
+        /**
+         * 审批
+         * @param {Number} advanceId - 预付款id
+         * @param {auditConst.status.checked|auditConst.status.checkNo} checkData - 审批结果
+         * @param {Number} times - 第几次审批
+         * @return {Promise<void>}
+         */
+        async check(advanceId, checkData, times = 1) {
+            if (checkData.checkType !== auditConst.status.checked && checkData.checkType !== auditConst.status.checkNo && checkData.checkType !== auditConst.status.checkNoPre) {
+                throw '提交数据错误';
+            }
+            const pid = this.ctx.session.sessionProject.id;
+            switch (checkData.checkType) {
+                case auditConst.status.checked:
+                    await this._checked(pid, advanceId, checkData, times);
+                    break;
+                case auditConst.status.checkNo:
+                    await this._checkNo(pid, advanceId, checkData, times);
+                    break;
+                case auditConst.status.checkNoPre:
+                    await this._checkNoPre(pid, advanceId, checkData, times);
+                    break;
+                default:
+                    throw '无效审批操作';
+            }
+        }
+
+        /**
+         * 用于添加推送所需的content内容
+         * @param {Number} pid 项目id
+         * @param {Number} tid 台账id
+         * @param {Number} vid 预付款id
+         * @param {Number} uid 审批人id
+         */
+        async getNoticeContent(pid, tid, vid, uid) {
+            const noticeSql =
+                'SELECT * FROM (SELECT ' +
+                '  t.`id` As `tid`, ad.`vid`, t.`name`, m.`order`, pa.`name` As `su_name`, pa.role As `su_role`' +
+                '  FROM (SELECT * FROM ?? WHERE `id` = ? ) As t' +
+                '  LEFT JOIN ?? As m On t.`id` = m.`tid` AND m.`id` = ?' +
+                '  LEFT JOIN ?? As ad ON m.`id` = ad.`vid`' +
+                '  LEFT JOIN ?? As pa ON pa.`id` = ?' +
+                '  WHERE  t.`project_id` = ? ) as new_t GROUP BY new_t.`tid`';
+            const noticeSqlParam = [
+                this.ctx.service.tender.tableName,
+                tid,
+                this.ctx.service.advance.tableName,
+                vid,
+                this.tableName,
+                this.ctx.service.projectAccount.tableName,
+                uid,
+                pid,
+            ];
+            const content = await this.db.query(noticeSql, noticeSqlParam);
+            return content.length ? JSON.stringify(content[0]) : '';
+        }
+
+        /**
+         * 通过状态获取审核人信息
+         * @param {Number} vid - 预付款id
+         * @param {Number} status - 期状态
+         * @param {Number} times - 审批次数
+         * @return {Object} auditor 审核人信息
+         */
+        async getAuditorByStatus(vid, status, times = 1) {
+            let auditor = null;
+            let sql = '';
+            let sqlParam = '';
+            switch (status) {
+                case auditConst.status.checking:
+                case auditConst.status.checked:
+                case auditConst.status.checkNoPre:
+                    sql =
+                        'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`vid`, la.`order` ' +
+                        'FROM ?? AS la, ?? AS pa ' +
+                        'WHERE la.`vid` = ? and la.`status` = ? and la.`audit_id` = pa.`id` order by la.`times` desc, la.`order` desc';
+                    sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, vid, status];
+                    auditor = await this.db.queryOne(sql, sqlParam);
+                    break;
+                case auditConst.status.checkNo:
+                    sql =
+                        'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`vid`, la.`order` ' +
+                        'FROM ?? AS la, ?? AS pa ' +
+                        'WHERE la.`vid` = ? and la.`status` = ? and la.`times` = ? and la.`audit_id` = pa.`id` order by la.`times` desc, la.`order` desc';
+                    sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, vid, auditConst.status.checkNo, parseInt(times) - 1];
+                    auditor = await this.db.queryOne(sql, sqlParam);
+                    break;
+                case auditConst.status.uncheck:
+                default:
+                    break;
+            }
+            return auditor;
+        }
+
+        /**
+         * 获取所有审核人
+         * @param {Number} tenderId 标段id
+         */
+        async getAllAuditors(tenderId) {
+            const sql = 'SELECT au.audit_id, au.tid FROM ' + this.tableName + ' au' +
+                '  LEFT JOIN ' + this.ctx.service.tender.tableName + ' t On au.tid = t.id' +
+                '  WHERE t.id = ?' +
+                '  GROUP BY  au.audit_id';
+            const sqlParam = [tenderId];
+            return this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 获取待处理审核列表
+         * @param {Number} auditorId 审核人id
+         */
+        async getAuditAdvance(auditorId) {
+            const sql = 'SELECT ma.`audit_id`, ma.`times`, ma.`order`, ma.`create_time`, ma.`end_time`, ma.`tid`, ma.`vid`,' +
+                        '    m.`order` As `morder`, m.`status` As `mstatus`,' +
+                        '    t.`name`, t.`project_id`, t.`type`, t.`user_id` ' +
+                        '  FROM ?? AS ma, ?? AS m, ?? As t ' +
+                        '  WHERE ((ma.`audit_id` = ? and ma.`status` = ?) OR (m.`uid` = ? and ma.`status` = ? and m.`status` = ? and ma.`times` = (m.`times`-1)))' +
+                        '    and ma.`vid` = m.`id` and ma.`tid` = t.`id` ORDER BY ma.`create_time` DESC';
+            const sqlParam = [this.tableName, this.ctx.service.advance.tableName, this.ctx.service.tender.tableName, auditorId, auditConst.status.checking, auditorId, auditConst.status.checkNo, auditConst.status.checkNo];
+            return await this.db.query(sql, sqlParam);
+        }
+    }
+    return AdvanceAudit;
+};

+ 55 - 0
app/service/advance_file.js

@@ -0,0 +1,55 @@
+'use strict';
+/**
+ * 附件表 数据模型
+ * @author LanJianRong
+ * @date 2020/8/17
+ * @version
+ */
+
+module.exports = app => {
+    class AdvanceFile extends app.BaseService {
+        /**
+         * 构造函数
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'advance_file';
+        }
+
+        /**
+         * 获取文件
+         * @param {*} payload 载荷|查询条件
+         */
+        async getAdvanceFiles(payload) {
+            const { ctx } = this;
+            const result = await this.db.select(this.tableName, { where: payload });
+            const list = result.map(item => {
+                if (!ctx.helper.canPreview(item.fileext)) {
+                    item.filepath = `tender/${ctx.tender.id}/advance/${item.vid}/file/${item.id}/download`;
+                }
+                return item;
+            });
+            return list;
+        }
+        /**
+         * 存储上传的文件信息至数据库
+         * @param {Array} payload 载荷
+         * @return {Promise<void>} 数据库插入执行实例
+         */
+        async saveFileMsgToDb(payload) {
+            return await this.db.insert(this.tableName, payload);
+        }
+
+        /**
+         * 删除附件
+         * @param {Number} id - 附件id
+         * @return {void}
+         */
+        async delete(id) {
+            return await this.deleteById(id);
+        }
+    }
+    return AdvanceFile;
+};

+ 313 - 214
app/service/change.js

@@ -14,6 +14,7 @@ const path = require('path');
 const smsTypeConst = require('../const/sms_type');
 const SMS = require('../lib/sms');
 const SmsAliConst = require('../const/sms_alitemplate');
+const wxConst = require('../const/wechat_template');
 const pushType = require('../const/audit').pushType;
 
 module.exports = app => {
@@ -111,18 +112,20 @@ module.exports = app => {
 
                 // 把提交人信息添加到zh_change_audit
                 const userInfo = await this.ctx.service.projectAccount.getDataById(userId);
-                const changeaudit = [{
-                    tid: tenderId,
-                    cid,
-                    uid: userId,
-                    name: userInfo.name,
-                    jobs: userInfo.role,
-                    company: userInfo.company,
-                    times: 1,
-                    usite: 0,
-                    usort: 0,
-                    status: 2,
-                }];
+                const changeaudit = [
+                    {
+                        tid: tenderId,
+                        cid,
+                        uid: userId,
+                        name: userInfo.name,
+                        jobs: userInfo.role,
+                        company: userInfo.company,
+                        times: 1,
+                        usite: 0,
+                        usort: 0,
+                        status: 2,
+                    },
+                ];
                 // 并把之前存在的变更令审批人添加到zh_change_audit
                 // 先找出标段最近存在的变更令审批人的变更令info
                 const changeInfo = await this.ctx.service.change.getHaveAuditLastInfo(tenderId);
@@ -215,7 +218,6 @@ module.exports = app => {
             });
         }
 
-
         /**
          * 获取变更令列表
          * @param {int} tenderId - 标段id
@@ -227,34 +229,48 @@ module.exports = app => {
             let sql = '';
             let sqlParam = '';
             switch (status) {
-                case 0:// 包含你的所有变更令
-                    sql = 'SELECT a.* FROM ?? AS a WHERE a.tid = ? AND ' +
+                case 0: // 包含你的所有变更令
+                    sql =
+                        'SELECT a.* FROM ?? AS a WHERE a.tid = ? AND ' +
                         '(a.uid = ? OR (a.status != ? AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid)) OR a.status = ? ) ORDER BY a.in_time DESC';
-                    sqlParam = [this.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.flow.status.uncheck,
-                        this.ctx.service.changeAudit.tableName, this.ctx.session.sessionUser.accountId, audit.flow.status.checked];
+                    sqlParam = [
+                        this.tableName,
+                        tenderId,
+                        this.ctx.session.sessionUser.accountId,
+                        audit.flow.status.uncheck,
+                        this.ctx.service.changeAudit.tableName,
+                        this.ctx.session.sessionUser.accountId,
+                        audit.flow.status.checked,
+                    ];
                     break;
-                case 1:// 待处理(你的)
+                case 1: // 待处理(你的)
                     sql = 'SELECT a.* FROM ?? as a WHERE cid in(SELECT b.cid FROM ?? as b WHERE tid = ? AND uid = ? AND status = ?) ORDER BY in_time DESC';
                     sqlParam = [this.tableName, this.ctx.service.changeAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.flow.auditStatus.checking];
                     break;
-                case 5:// 待上报(所有的)PS:取未上报和退回的变更令
-                    sql = 'SELECT a.* FROM ?? AS a WHERE ' +
+                case 5: // 待上报(所有的)PS:取未上报和退回的变更令
+                    sql =
+                        'SELECT a.* FROM ?? AS a WHERE ' +
                         'a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid) AND ' +
                         '(a.status = ? OR a.status = ?) AND a.tid = ? ORDER BY a.in_time DESC';
-                    sqlParam = [this.tableName, this.ctx.service.changeAudit.tableName,
-                        this.ctx.session.sessionUser.accountId, audit.flow.status.uncheck, audit.flow.status.back, tenderId];
+                    sqlParam = [
+                        this.tableName,
+                        this.ctx.service.changeAudit.tableName,
+                        this.ctx.session.sessionUser.accountId,
+                        audit.flow.status.uncheck,
+                        audit.flow.status.back,
+                        tenderId,
+                    ];
                     break;
-                case 2:// 进行中(所有的)
-                case 4:// 终止(所有的)
-                    sql = 'SELECT a.* FROM ?? AS a WHERE ' +
+                case 2: // 进行中(所有的)
+                case 4: // 终止(所有的)
+                    sql =
+                        'SELECT a.* FROM ?? AS a WHERE ' +
                         'a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid) AND ' +
                         'a.status = ? AND a.tid = ? ORDER BY a.in_time DESC';
-                    sqlParam = [this.tableName, this.ctx.service.changeAudit.tableName,
-                        this.ctx.session.sessionUser.accountId, status, tenderId];
+                    sqlParam = [this.tableName, this.ctx.service.changeAudit.tableName, this.ctx.session.sessionUser.accountId, status, tenderId];
                     break;
-                case 3:// 已完成(所有的)
-                    sql = 'SELECT a.* FROM ?? AS a WHERE ' +
-                        'a.status = ? AND a.tid = ? ORDER BY a.in_time DESC';
+                case 3: // 已完成(所有的)
+                    sql = 'SELECT a.* FROM ?? AS a WHERE a.status = ? AND a.tid = ? ORDER BY a.in_time DESC';
                     sqlParam = [this.tableName, status, tenderId];
                     break;
                 default:
@@ -278,36 +294,49 @@ module.exports = app => {
          */
         async getCountByStatus(tenderId, status) {
             switch (status) {
-                case 0:// 包含你的所有变更令
-                    const sql = 'SELECT count(*) AS count FROM ?? AS a WHERE a.tid = ? AND ' +
+                case 0: // 包含你的所有变更令
+                    const sql =
+                        'SELECT count(*) AS count FROM ?? AS a WHERE a.tid = ? AND ' +
                         '(a.uid = ? OR a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid))';
-                    const sqlParam = [this.tableName, tenderId, this.ctx.session.sessionUser.accountId,
-                        this.ctx.service.changeAudit.tableName, this.ctx.session.sessionUser.accountId];
+                    const sqlParam = [
+                        this.tableName,
+                        tenderId,
+                        this.ctx.session.sessionUser.accountId,
+                        this.ctx.service.changeAudit.tableName,
+                        this.ctx.session.sessionUser.accountId,
+                    ];
                     const result = await this.db.query(sql, sqlParam);
                     return result[0].count;
-                case 1:// 待处理(你的)
+                case 1: // 待处理(你的)
                     return await this.ctx.service.changeAudit.count({
                         tid: tenderId,
                         uid: this.ctx.session.sessionUser.accountId,
                         status: 2,
                     });
-                case 5:// 待上报(所有的)PS:取未上报和退回的变更令
-                    const sql2 = 'SELECT count(*) AS count FROM ?? AS a WHERE ' +
+                case 5: // 待上报(所有的)PS:取未上报和退回的变更令
+                    const sql2 =
+                        'SELECT count(*) AS count FROM ?? AS a WHERE ' +
                         'a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid) ' +
                         'AND (a.status = ? OR a.status = ?) AND a.tid = ?';
-                    const sqlParam2 = [this.tableName, this.ctx.service.changeAudit.tableName,
-                        this.ctx.session.sessionUser.accountId, audit.flow.status.uncheck, audit.flow.status.back, tenderId];
+                    const sqlParam2 = [
+                        this.tableName,
+                        this.ctx.service.changeAudit.tableName,
+                        this.ctx.session.sessionUser.accountId,
+                        audit.flow.status.uncheck,
+                        audit.flow.status.back,
+                        tenderId,
+                    ];
                     const result2 = await this.db.query(sql2, sqlParam2);
                     return result2[0].count;
-                case 2:// 进行中(所有的)
-                case 4:// 终止(所有的)
-                    const sql3 = 'SELECT count(*) AS count FROM ?? AS a WHERE ' +
+                case 2: // 进行中(所有的)
+                case 4: // 终止(所有的)
+                    const sql3 =
+                        'SELECT count(*) AS count FROM ?? AS a WHERE ' +
                         'a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid) AND a.status = ? AND a.tid = ?';
-                    const sqlParam3 = [this.tableName, this.ctx.service.changeAudit.tableName,
-                        this.ctx.session.sessionUser.accountId, status, tenderId];
+                    const sqlParam3 = [this.tableName, this.ctx.service.changeAudit.tableName, this.ctx.session.sessionUser.accountId, status, tenderId];
                     const result3 = await this.db.query(sql3, sqlParam3);
                     return result3[0].count;
-                case 3:// 已完成(所有的)
+                case 3: // 已完成(所有的)
                     const sql4 = 'SELECT count(*) AS count FROM ?? WHERE status = ? AND tid = ?';
                     const sqlParam4 = [this.tableName, status, tenderId];
                     const result4 = await this.db.query(sql4, sqlParam4);
@@ -341,7 +370,11 @@ 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: audit.flow.auditStatus.checked, 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 !== '') {
@@ -373,21 +406,24 @@ module.exports = app => {
 
                         // 添加短信通知-需要审批提醒功能
                         if (change_status && index === 0) {
-                            // const smsUser = await this.ctx.service.projectAccount.getDataById(auditInfo[0]);
-                            // if (smsUser.auth_mobile !== '' && smsUser.auth_mobile !== undefined && smsUser.sms_type !== '' && smsUser.sms_type !== null) {
-                            //     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 result = await this.ctx.helper.urlToShort('http://' + this.ctx.request.header.host + '/wap/tender/' + changeInfo.tid + '/change/' + changeInfo.cid + '/info#shenpi');
-                            //         const content = '【纵横计量支付】' + changeInfo.code + '变更需要您审批。' + result;
-                            //         sms.send(smsUser.auth_mobile, content);
-                            //     }
-                            // }
                             const sms = new SMS(this.ctx);
                             const code = await sms.contentChange(changeInfo.code);
-                            const shenpiUrl = await this.ctx.helper.urlToShort('http://' + this.ctx.request.header.host + '/wap/tender/' + changeInfo.tid + '/change/' + changeInfo.cid + '/info#shenpi');
-                            await this.ctx.helper.sendAliSms(auditInfo[0], smsTypeConst.const.BG,
-                                smsTypeConst.judge.approval.toString(), SmsAliConst.template.change_check, { biangeng: code, code: shenpiUrl });
+                            const shenpiUrl = await this.ctx.helper.urlToShort(
+                                this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + changeInfo.tid + '/change/' + changeInfo.cid + '/info#shenpi'
+                            );
+                            await this.ctx.helper.sendAliSms(auditInfo[0], smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), SmsAliConst.template.change_check, {
+                                biangeng: code,
+                                code: shenpiUrl,
+                            });
+                            // 微信模板通知
+                            const wechatData = {
+                                wap_url: shenpiUrl,
+                                status: wxConst.status.check,
+                                tips: wxConst.tips.check,
+                                code: this.ctx.session.sessionProject.code,
+                                c_name: changeInfo.name,
+                            };
+                            await this.ctx.helper.sendWechat(auditInfo[0], smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
                         }
                     }
                     await this.transaction.insert(this.ctx.service.changeAudit.tableName, insertCA);
@@ -425,8 +461,7 @@ module.exports = app => {
                             delete clArray.unit_price;
                         }
                         insertCL.push(clArray);
-                        total_price = this.ctx.helper.accAdd(total_price,
-                            this.ctx.helper.mul(clArray.unit_price, clArray.spamount, tenderInfo.decimal.tp));
+                        total_price = this.ctx.helper.accAdd(total_price, this.ctx.helper.mul(clArray.unit_price, clArray.spamount, tenderInfo.decimal.tp));
                     }
                     await this.transaction.insert(this.ctx.service.changeAuditList.tableName, insertCL);
                 }
@@ -484,11 +519,19 @@ module.exports = app => {
             try {
                 // 获取所有审核人列表
                 const auditors = await this.ctx.service.changeAudit.getAllAuditors(changeData.tid);
+                console.log('auditors', auditors);
+                console.log('postData', postData);
                 // 添加到消息推送表
-                const noticeContent = await this.getNoticeContent(pid, changeData.tid, changeData.cid, postData.audit_id);
-                const records = [{ pid, type: pushType.change, uid: changeData.uid, status: audit.flow.status.checked, content: noticeContent }];
+                const noticeContent = await this.getNoticeContent(pid, changeData.tid, changeData.cid, this.ctx.session.sessionUser.accountId);
+                const records = [];
                 auditors.forEach(auditor => {
-                    records.push({ pid, type: pushType.change, uid: auditor.uid, status: audit.flow.status.checked, content: noticeContent });
+                    records.push({
+                        pid,
+                        type: pushType.change,
+                        uid: auditor.uid,
+                        status: audit.flow.status.checked,
+                        content: noticeContent,
+                    });
                 });
                 await this.transaction.insert('zh_notice', records);
 
@@ -517,8 +560,7 @@ module.exports = app => {
                         tenderInfo = await this.ctx.service.tenderInfo.getTenderInfo(changeListInfo.tid);
                     }
                     if (changeListInfo !== undefined) {
-                        total_price = this.ctx.helper.add(total_price,
-                            this.ctx.helper.mul(changeListInfo.unit_price, amount, tenderInfo.decimal.tp));
+                        total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(changeListInfo.unit_price, amount, tenderInfo.decimal.tp));
                         const audit_amount = changeListInfo.audit_amount !== null && changeListInfo.audit_amount !== '' ? changeListInfo.audit_amount.split(',') : [];
                         audit_amount.push(amount);
                         const list_update = {
@@ -538,36 +580,34 @@ module.exports = app => {
                     change_update.p_code = postData.p_code;
                     change_update.sin_time = Date.parse(new Date()) / 1000;
 
-                    // 添加到消息推送表
-                    const noticeContent = await this.getNoticeContent(pid, changeData.tid, changeData.cid);
-                    await this.transaction.insert('zh_notice', { pid, type: pushType.change, uid: changeData.uid, status: audit.flow.status.checked, is_read: 0, content: noticeContent });
                     // 添加短信通知-审批通过提醒功能
                     // const mobile_array = [];
                     const auditList = await this.ctx.service.changeAudit.getListGroupByTimes(changeData.cid, changeData.times);
-                    // for (const user of auditList) {
-                    //     const smsUser = await this.ctx.service.projectAccount.getDataById(user.uid);
-                    //     if (smsUser.auth_mobile !== '' && smsUser.auth_mobile !== undefined && smsUser.sms_type !== '' && smsUser.sms_type !== null) {
-                    //         const smsType = JSON.parse(smsUser.sms_type);
-                    //         if (smsType[smsTypeConst.const.BG] !== undefined && smsType[smsTypeConst.const.BG].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
-                    //             mobile_array.push(smsUser.auth_mobile);
-                    //         }
-                    //     }
-                    // }
-                    // if (mobile_array.length > 0) {
-                    //     const sms = new SMS(this.ctx);
-                    //     const content = '【纵横计量支付】' + changeData.code + '变更,审批通过。';
-                    //     sms.send(mobile_array, content);
-                    // }
                     const users = this._.map(auditList, 'uid');
                     const sms = new SMS(this.ctx);
                     const code = await sms.contentChange(changeData.code);
-                    await this.ctx.helper.sendAliSms(users, smsTypeConst.const.BG,
-                        smsTypeConst.judge.result.toString(), SmsAliConst.template.change_result, { biangeng: code, status: SmsAliConst.status.success });
+                    await this.ctx.helper.sendAliSms(users, smsTypeConst.const.BG, smsTypeConst.judge.result.toString(), SmsAliConst.template.change_result, {
+                        biangeng: code,
+                        status: SmsAliConst.status.success,
+                    });
+                    // 微信模板通知
+                    const shenpiUrl = await this.ctx.helper.urlToShort(
+                        this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + changeData.tid + '/change/' + changeData.cid + '/info#shenpi'
+                    );
+                    const wechatData = {
+                        wap_url: shenpiUrl,
+                        status: wxConst.status.success,
+                        tips: wxConst.tips.success,
+                        code: this.ctx.session.sessionProject.code,
+                        c_name: changeData.name,
+                    };
+                    await this.ctx.helper.sendWechat(users, smsTypeConst.const.BG, smsTypeConst.judge.result.toString(), wxConst.template.change, wechatData);
                 } else {
                     // 设置下一个审批人为审批状态
                     const nextAudit_update = {
                         id: postData.audit_next_id,
                         status: audit.flow.auditStatus.checking,
+                        sin_time: new Date(),
                     };
                     await this.transaction.update(this.ctx.service.changeAudit.tableName, nextAudit_update);
 
@@ -575,20 +615,22 @@ module.exports = app => {
                     const nextAuditData = await this.ctx.service.changeAudit.getDataById(postData.audit_next_id);
                     const sms = new SMS(this.ctx);
                     const code = await sms.contentChange(changeData.code);
-                    // const smsUser = await this.ctx.service.projectAccount.getDataById(nextAuditData.uid);
-                    // if (smsUser.auth_mobile !== '' && smsUser.auth_mobile !== undefined && smsUser.sms_type !== '' && smsUser.sms_type !== null) {
-                    //     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 code = await sms.contentChange(changeData.code);
-                    //         const result = await this.ctx.helper.urlToShort('http://' + this.ctx.request.header.host + '/wap/tender/' + changeData.tid + '/change/' + changeData.cid + '/info#shenpi');
-                    //         const content = '【纵横计量支付】' + code + '变更需要您审批。' + result;
-                    //         sms.send(smsUser.auth_mobile, content);
-                    //     }
-                    // }
-                    const shenpiUrl = await this.ctx.helper.urlToShort('http://' + this.ctx.request.header.host + '/wap/tender/' + changeData.tid + '/change/' + changeData.cid + '/info#shenpi');
-                    await this.ctx.helper.sendAliSms(nextAuditData.uid, smsTypeConst.const.BG,
-                        smsTypeConst.judge.approval.toString(), SmsAliConst.template.change_check, { biangeng: code, code: shenpiUrl });
+                    const shenpiUrl = await this.ctx.helper.urlToShort(
+                        this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + changeData.tid + '/change/' + changeData.cid + '/info#shenpi'
+                    );
+                    await this.ctx.helper.sendAliSms(nextAuditData.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), SmsAliConst.template.change_check, {
+                        biangeng: code,
+                        code: shenpiUrl,
+                    });
+                    // 微信模板通知
+                    const wechatData = {
+                        wap_url: shenpiUrl,
+                        status: wxConst.status.check,
+                        tips: wxConst.tips.check,
+                        code: this.ctx.session.sessionProject.code,
+                        c_name: changeData.name,
+                    };
+                    await this.ctx.helper.sendWechat(nextAuditData.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
                 }
                 change_update.total_price = total_price;
                 const options = {
@@ -661,10 +703,16 @@ module.exports = app => {
                 // 获取所有审核人列表
                 const auditors = await this.ctx.service.changeAudit.getAllAuditors(changeData.tid);
                 // 添加到消息推送表
-                const noticeContent = await this.getNoticeContent(pid, changeData.tid, changeData.cid, postData.audit_id);
-                const records = [{ pid, type: pushType.change, uid: changeData.uid, status: audit.flow.status.backnew, content: noticeContent }];
+                const noticeContent = await this.getNoticeContent(pid, changeData.tid, changeData.cid, this.ctx.session.sessionUser.accountId);
+                const records = [];
                 auditors.forEach(auditor => {
-                    records.push({ pid, type: pushType.change, uid: auditor.uid, status: audit.flow.status.backnew, content: noticeContent });
+                    records.push({
+                        pid,
+                        type: pushType.change,
+                        uid: auditor.uid,
+                        status: audit.flow.status.backnew,
+                        content: noticeContent,
+                    });
                 });
                 await this.transaction.insert('zh_notice', records);
                 const changeInfo = await this.getDataByCondition({ cid: postData.change_id });
@@ -702,11 +750,12 @@ module.exports = app => {
                 }
                 await this.transaction.insert(this.ctx.service.changeAudit.tableName, insert_audit_array);
                 // 变更金额也退回
-                const changeList = await this.ctx.service.changeAuditList.getAllDataByCondition({ where: { cid: changeInfo.cid } });
+                const changeList = await this.ctx.service.changeAuditList.getAllDataByCondition({
+                    where: { cid: changeInfo.cid },
+                });
                 let total_price = 0;
                 for (const cl of changeList) {
-                    total_price = this.ctx.helper.add(total_price,
-                        this.ctx.helper.mul(cl.unit_price, cl.camount, tenderInfo.decimal.tp));
+                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, cl.camount, tenderInfo.decimal.tp));
                 }
                 // 设置变更令退回
                 const change_update = {
@@ -725,30 +774,25 @@ module.exports = app => {
 
                 await this.transaction.commit();
                 result = true;
-
-                // 添加短信通知-审批退回提醒功能
-                // const mobile_array = [];
-                // for (const user of insert_audit_array) {
-                //     const smsUser = await this.ctx.service.projectAccount.getDataById(user.uid);
-                //     if (smsUser.auth_mobile !== '' && smsUser.auth_mobile !== undefined && smsUser.sms_type !== '' && smsUser.sms_type !== null) {
-                //         const smsType = JSON.parse(smsUser.sms_type);
-                //         if (smsType[smsTypeConst.const.BG] !== undefined && smsType[smsTypeConst.const.BG].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
-                //             mobile_array.push(smsUser.auth_mobile);
-                //         }
-                //     }
-                // }
-                // if (mobile_array.length > 0) {
-                //     const sms = new SMS(this.ctx);
-                //     const code = await sms.contentChange(changeData.code);
-                //     const content = '【纵横计量支付】' + code + '变更,审批退回。';
-                //     sms.send(mobile_array, content);
-                // }
                 const users = this._.map(insert_audit_array, 'uid');
                 const sms = new SMS(this.ctx);
                 const code = await sms.contentChange(changeData.code);
-                await this.ctx.helper.sendAliSms(users, smsTypeConst.const.BG,
-                    smsTypeConst.judge.result.toString(), SmsAliConst.template.change_result, { biangeng: code, status: SmsAliConst.status.back });
-
+                await this.ctx.helper.sendAliSms(users, smsTypeConst.const.BG, smsTypeConst.judge.result.toString(), SmsAliConst.template.change_result, {
+                    biangeng: code,
+                    status: SmsAliConst.status.back,
+                });
+                // 微信模板通知
+                const shenpiUrl = await this.ctx.helper.urlToShort(
+                    this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + changeInfo.tid + '/change/' + changeInfo.cid + '/info#shenpi'
+                );
+                const wechatData = {
+                    wap_url: shenpiUrl,
+                    status: wxConst.status.back,
+                    tips: wxConst.tips.back,
+                    code: this.ctx.session.sessionProject.code,
+                    c_name: changeInfo.name,
+                };
+                await this.ctx.helper.sendWechat(users, smsTypeConst.const.BG, smsTypeConst.judge.result.toString(), wxConst.template.change, wechatData);
             } catch (error) {
                 await this.transaction.rollback();
                 result = false;
@@ -771,14 +815,19 @@ module.exports = app => {
                 // 获取所有审核人列表
                 const auditors = await this.ctx.service.changeAudit.getAllAuditors(changeData.tid);
                 // 添加到消息推送表
-                const noticeContent = await this.getNoticeContent(pid, changeData.tid, changeData.cid, postData.audit_id);
-                const records = [{ pid, type: pushType.change, uid: changeData.uid, status: audit.flow.status.back, content: noticeContent }];
+                const noticeContent = await this.getNoticeContent(pid, changeData.tid, changeData.cid, this.ctx.session.sessionUser.accountId);
+                const records = [];
                 auditors.forEach(auditor => {
-                    records.push({ pid, type: pushType.change, uid: auditor.uid, status: audit.flow.status.back, content: noticeContent });
+                    records.push({
+                        pid,
+                        type: pushType.change,
+                        uid: auditor.uid,
+                        status: audit.flow.status.back,
+                        content: noticeContent,
+                    });
                 });
                 await this.transaction.insert('zh_notice', records);
 
-
                 const changeInfo = await this.getDataByCondition({ cid: postData.change_id });
                 const tenderInfo = await this.ctx.service.tenderInfo.getTenderInfo(changeInfo.tid);
                 // 设置审批人退回
@@ -812,6 +861,7 @@ module.exports = app => {
                     usite: lastauditInfo.usite,
                     usort,
                     status: audit.flow.auditStatus.checking,
+                    sin_time: new Date(),
                 };
                 await this.transaction.insert(this.ctx.service.changeAudit.tableName, insert_audit1);
                 usort++;
@@ -840,7 +890,9 @@ module.exports = app => {
                 }
 
                 // 审批列表数据也要回退
-                const changeList = await this.ctx.service.changeAuditList.getAllDataByCondition({ where: { cid: changeInfo.cid } });
+                const changeList = await this.ctx.service.changeAuditList.getAllDataByCondition({
+                    where: { cid: changeInfo.cid },
+                });
                 let total_price = 0;
                 for (const cl of changeList) {
                     const audit_amount = cl.audit_amount.split(',');
@@ -851,8 +903,7 @@ module.exports = app => {
                         audit_amount: audit_amount.join(','),
                         spamount: parseFloat(last_amount),
                     };
-                    total_price = this.ctx.helper.add(total_price,
-                        this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), tenderInfo.decimal.tp));
+                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), tenderInfo.decimal.tp));
                     await this.transaction.update(this.ctx.service.changeAuditList.tableName, list_update);
                 }
 
@@ -871,24 +922,24 @@ 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 !== '' && smsUser.auth_mobile !== undefined && smsUser.sms_type !== '' && smsUser.sms_type !== null) {
-                //     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 code = await sms.contentChange(changeData.code);
-                //         const result = await this.ctx.helper.urlToShort('http://' + this.ctx.request.header.host + '/wap/tender/' + changeData.tid + '/change/' + changeData.cid + '/info#shenpi');
-                //         const content = '【纵横计量支付】' + code + '变更需要您审批。' + result;
-                //         sms.send(smsUser.auth_mobile, content);
-                //     }
-                // }
                 const sms = new SMS(this.ctx);
                 const code = await sms.contentChange(changeData.code);
-                const shenpiUrl = await this.ctx.helper.urlToShort('http://' + this.ctx.request.header.host + '/wap/tender/' + changeData.tid + '/change/' + changeData.cid + '/info#shenpi');
-                await this.ctx.helper.sendAliSms(lastauditInfo.uid, smsTypeConst.const.BG,
-                    smsTypeConst.judge.approval.toString(), SmsAliConst.template.change_check, { biangeng: code, code: shenpiUrl });
+                const shenpiUrl = await this.ctx.helper.urlToShort(
+                    this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + changeData.tid + '/change/' + changeData.cid + '/info#shenpi'
+                );
+                await this.ctx.helper.sendAliSms(lastauditInfo.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), SmsAliConst.template.change_check, {
+                    biangeng: code,
+                    code: shenpiUrl,
+                });
+                // 微信模板通知
+                const wechatData = {
+                    wap_url: shenpiUrl,
+                    status: wxConst.status.check,
+                    tips: wxConst.tips.check,
+                    code: this.ctx.session.sessionProject.code,
+                    c_name: changeInfo.name,
+                };
+                await this.ctx.helper.sendWechat(lastauditInfo.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
             } catch (error) {
                 await this.transaction.rollback();
                 result = false;
@@ -904,38 +955,52 @@ module.exports = app => {
          */
         async getValidChanges(tid, bills, pos) {
             const timesLen = 100;
-            const filter = 'cb.`code` = ' + this.db.escape(bills.b_code) +
-                ' And cb.`name` = ' + this.db.escape(bills.name) +
-                ' And cb.`unit` = ' + this.db.escape(bills.unit) +
-                ' And cb.`unit_price` = ' + this.db.escape(bills.unit_price) +
+            const filter =
+                'cb.`code` = ' +
+                this.db.escape(bills.b_code) +
+                ' And cb.`name` = ' +
+                this.db.escape(bills.name) +
+                ' And cb.`unit` = ' +
+                this.db.escape(bills.unit) +
+                ' And cb.`unit_price` = ' +
+                this.db.escape(bills.unit_price) +
                 (pos ? ' And cb.`bwmx` = ' + this.db.escape(pos.name) : '');
-            const sql = 'SELECT c.cid, c.code, c.name, c.w_code, c.p_code, c.peg, c.org_name, c.org_code, c.new_name, c.new_code,' +
-                        '    c.content, c.basis, c.memo, c.type, c.class, c.quality, c.company, c.charge, ' +
-                        '    cb.id As cbid, cb.code As b_code, cb.name As b_name, cb.unit As b_unit, cb.samount As b_amount, cb.detail As b_detail, cb.bwmx As b_bwmx, ' +
-                        '    scb.used_amount' +
-                        '  FROM ' + this.tableName + ' As c ' +
-                        '  Left Join ' + this.ctx.service.changeAuditList.tableName + ' As cb On c.cid = cb.cid ' +
-                        '  Left Join (' +
-                        '    SELECT SUM(sc.qty) As used_amount, sc.cbid' +
-                        '      FROM ' + this.ctx.service.stageChange.tableName + ' As sc' +
-                        '      INNER JOIN (SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `flow`, cbid, sid ' +
-                        '        FROM ' + this.ctx.service.stageChange.tableName +
-                        '        WHERE tid = ?' +
-                        '        GROUP BY cbid, sid' +
-                        '      ) As MF' +
-                        '      ON (sc.stimes * ' + timesLen + ' + sc.sorder) = MF.flow And sc.cbid = MF.cbid And sc.sid = MF.sid' +
-                        '    GROUP BY sc.cbid' +
-                        '  ) As scb ON cb.id = scb.cbid' +
-                        '  WHERE c.tid = ? And c.status = ? And c.valid And ' + filter +
-                        '  ORDER BY c.in_time';
+            const sql =
+                'SELECT c.cid, c.code, c.name, c.w_code, c.p_code, c.peg, c.org_name, c.org_code, c.new_name, c.new_code,' +
+                '    c.content, c.basis, c.memo, c.type, c.class, c.quality, c.company, c.charge, ' +
+                '    cb.id As cbid, cb.code As b_code, cb.name As b_name, cb.unit As b_unit, cb.samount As b_amount, cb.detail As b_detail, cb.bwmx As b_bwmx, ' +
+                '    scb.used_amount' +
+                '  FROM ' +
+                this.tableName +
+                ' As c ' +
+                '  Left Join ' +
+                this.ctx.service.changeAuditList.tableName +
+                ' As cb On c.cid = cb.cid ' +
+                '  Left Join (' +
+                '    SELECT SUM(sc.qty) As used_amount, sc.cbid' +
+                '      FROM ' +
+                this.ctx.service.stageChange.tableName +
+                ' As sc' +
+                '      INNER JOIN (SELECT MAX(`stimes` * ' +
+                timesLen +
+                ' + `sorder`) As `flow`, cbid, sid ' +
+                '        FROM ' +
+                this.ctx.service.stageChange.tableName +
+                '        WHERE tid = ?' +
+                '        GROUP BY cbid, sid' +
+                '      ) As MF' +
+                '      ON (sc.stimes * ' +
+                timesLen +
+                ' + sc.sorder) = MF.flow And sc.cbid = MF.cbid And sc.sid = MF.sid' +
+                '    GROUP BY sc.cbid' +
+                '  ) As scb ON cb.id = scb.cbid' +
+                '  WHERE c.tid = ? And c.status = ? And c.valid And ' +
+                filter +
+                '  ORDER BY c.in_time';
             const sqlParam = [tid, tid, audit.flow.status.checked];
             const changes = await this.db.query(sql, sqlParam);
             for (const c of changes) {
-                const aSql = 'SELECT ca.*, pa.name As u_name, pa.role As u_role ' +
-                             '  FROM ?? As ca ' +
-                             '  Left Join ?? As pa ' +
-                             '  On ca.uid = pa.id ' +
-                             '  Where ca.cid = ?';
+                const aSql = 'SELECT ca.*, pa.name As u_name, pa.role As u_role ' + '  FROM ?? As ca ' + '  Left Join ?? As pa ' + '  On ca.uid = pa.id ' + '  Where ca.cid = ?';
                 const aSqlParam = [this.ctx.service.changeAtt.tableName, this.ctx.service.projectAccount.tableName, c.cid];
                 c.attachments = await this.db.query(aSql, aSqlParam);
             }
@@ -951,36 +1016,57 @@ module.exports = app => {
             const lastStage = await this.ctx.service.stage.getLastestStage(tid, true);
             let filter;
             if (lastStage.id === this.ctx.stage.id) {
-                filter = this.db.format(' And (s.`order` < ? OR (s.`order` = ? And (sChange.`stimes` < ? OR (sChange.`stimes` = ? And sChange.`sorder` <= ?))))',
-                    [lastStage.order, lastStage.order, this.ctx.stage.curTimes, this.ctx.stage.curTimes, this.ctx.stage.curOrder]);
+                filter = this.db.format(' And (s.`order` < ? OR (s.`order` = ? And (sChange.`stimes` < ? OR (sChange.`stimes` = ? And sChange.`sorder` <= ?))))', [
+                    lastStage.order,
+                    lastStage.order,
+                    this.ctx.stage.curTimes,
+                    this.ctx.stage.curTimes,
+                    this.ctx.stage.curOrder,
+                ]);
             } else {
                 if (lastStage.status === audit.stage.status.uncheck) {
                     filter = ' And s.order < ' + lastStage.order;
                 } else if (lastStage.status === audit.stage.status.checked) {
                     filter = '';
                 } else if (lastStage.status === audit.stage.status.checkNo) {
-                    filter = this.db.format(' And (s.`order` < ? OR (s.`order` = ? And sChange.`stimes` <= ?))',
-                        [lastStage.order, lastStage.order, lastStage.times]);
+                    filter = this.db.format(' And (s.`order` < ? OR (s.`order` = ? And sChange.`stimes` <= ?))', [lastStage.order, lastStage.order, lastStage.times]);
                 } else {
                     const curAuditor = await this.ctx.service.stageAudit.getCurAuditor(lastStage.id, lastStage.times);
-                    filter = this.db.format(' And (s.`order` < ? OR (s.`order` = ? And (sChange.`stimes` < ? OR (sChange.`stimes` = ? And sChange.`sorder` <= ?))))',
-                        [lastStage.order, lastStage.order, lastStage.times, lastStage.times, curAuditor.order - 1]);
+                    filter = this.db.format(' And (s.`order` < ? OR (s.`order` = ? And (sChange.`stimes` < ? OR (sChange.`stimes` = ? And sChange.`sorder` <= ?))))', [
+                        lastStage.order,
+                        lastStage.order,
+                        lastStage.times,
+                        lastStage.times,
+                        curAuditor.order - 1,
+                    ]);
                 }
             }
-            const sql = 'SELECT C.*, Sum(U.utp) As used_tp, Round(Sum(U.utp) / C.total_price * 100, 2) As used_pt' +
-                '  FROM ' + this.tableName + ' As C' +
+            const sql =
+                'SELECT C.*, Sum(U.utp) As used_tp, Round(Sum(U.utp) / C.total_price * 100, 2) As used_pt' +
+                '  FROM ' +
+                this.tableName +
+                ' As C' +
                 '  LEFT JOIN (SELECT sc.tid, sc.cid, sc.cbid, Round(SUM(sc.qty) * cb.unit_price, ?) As utp' +
-                '    FROM ' + this.ctx.service.stageChange.tableName + ' As sc' +
+                '    FROM ' +
+                this.ctx.service.stageChange.tableName +
+                ' As sc' +
                 '    INNER JOIN (' +
                 '      SELECT MAX(`stimes`) As `stimes`, MAX(`sorder`) As `sorder`, `lid`, `pid`, `cbid`, sChange.`sid` ' +
-                '        FROM ' + this.ctx.service.stageChange.tableName + ' As sChange ' +
-                '        LEFT JOIN ' + this.ctx.service.stage.tableName + ' As s' +
+                '        FROM ' +
+                this.ctx.service.stageChange.tableName +
+                ' As sChange ' +
+                '        LEFT JOIN ' +
+                this.ctx.service.stage.tableName +
+                ' As s' +
                 '        ON sChange.sid = s.id' +
-                '        WHERE sChange.tid = ?' + filter +
+                '        WHERE sChange.tid = ?' +
+                filter +
                 '        GROUP By `lid`, `pid`, `cbid`, `sid`' +
                 '    ) As m' +
                 '    ON sc.stimes = m.stimes And sc.sorder = m.sorder And sc.`cbid` = m.`cbid` AND sc.`sid` = m.`sid` And sc.`lid` = m.`lid` And sc.`pid` = m.`pid`' +
-                '    LEFT JOIN ' + this.ctx.service.changeAuditList.tableName + ' As cb ON sc.cbid = cb.id' +
+                '    LEFT JOIN ' +
+                this.ctx.service.changeAuditList.tableName +
+                ' As cb ON sc.cbid = cb.id' +
                 '    GROUP By sc.`cbid`' +
                 '  ) As U ON C.cid = U.cid' +
                 '  WHERE C.tid = ? And C.status = ? And C.valid' +
@@ -1036,7 +1122,14 @@ module.exports = app => {
                 const tenderInfo = await this.ctx.service.tenderInfo.getTenderInfo(changeInfo.tid);
 
                 // 获取终审
-                const auditInfo = (await this.ctx.service.changeAudit.getAllDataByCondition({ where: { cid }, orders: [['usort', 'desc']], limit: 1, offset: 0 }))[0];
+                const auditInfo = (
+                    await this.ctx.service.changeAudit.getAllDataByCondition({
+                        where: { cid },
+                        orders: [['usort', 'desc']],
+                        limit: 1,
+                        offset: 0,
+                    })
+                )[0];
                 let usort = auditInfo.usort + 1;
 
                 // 新增2个审批状态到审批列表中
@@ -1067,12 +1160,15 @@ module.exports = app => {
                     usite: auditInfo.usite,
                     usort,
                     status: audit.flow.auditStatus.checking,
+                    sin_time: new Date(),
                 };
                 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 } });
+                const changeList = await this.ctx.service.changeAuditList.getAllDataByCondition({
+                    where: { cid: changeInfo.cid },
+                });
                 for (const cl of changeList) {
                     const audit_amount = cl.audit_amount.split(',');
                     const last_amount = audit_amount[audit_amount.length - 1];
@@ -1082,8 +1178,7 @@ module.exports = app => {
                         audit_amount: audit_amount.join(','),
                         samount: '',
                     };
-                    total_price = this.ctx.helper.add(total_price,
-                        this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), tenderInfo.decimal.tp));
+                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), tenderInfo.decimal.tp));
                     await this.transaction.update(this.ctx.service.changeAuditList.tableName, list_update);
                 }
 
@@ -1103,24 +1198,26 @@ 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(auditInfo.uid);
-                // if (smsUser.auth_mobile !== '' && smsUser.auth_mobile !== undefined && smsUser.sms_type !== '' && smsUser.sms_type !== null) {
-                //     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 code = await sms.contentChange(changeInfo.code);
-                //         const result = await this.ctx.helper.urlToShort('http://' + this.ctx.request.header.host + '/wap/tender/' + changeInfo.tid + '/change/' + changeInfo.cid + '/info#shenpi');
-                //         const content = '【纵横计量支付】' + code + '变更需要您审批。' + result;
-                //         sms.send(smsUser.auth_mobile, content);
-                //     }
-                // }
                 const sms = new SMS(this.ctx);
                 const code = await sms.contentChange(changeInfo.code);
-                const shenpiUrl = await this.ctx.helper.urlToShort('http://' + this.ctx.request.header.host + '/wap/tender/' + changeInfo.tid + '/change/' + changeInfo.cid + '/info#shenpi');
-                await this.ctx.helper.sendAliSms(auditInfo.uid, smsTypeConst.const.BG,
-                    smsTypeConst.judge.approval.toString(), SmsAliConst.template.change_check, { biangeng: code, code: shenpiUrl });
+                const shenpiUrl = await this.ctx.helper.urlToShort(
+                    this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + changeInfo.tid + '/change/' + changeInfo.cid + '/info#shenpi'
+                );
+                await this.ctx.helper.sendAliSms(auditInfo.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), SmsAliConst.template.change_check, {
+                    biangeng: code,
+                    code: shenpiUrl,
+                });
+
+                // 微信模板通知
+                const wechatData = {
+                    wap_url: shenpiUrl,
+                    status: wxConst.status.check,
+                    tips: wxConst.tips.check,
+                    code: this.ctx.session.sessionProject.code,
+                    c_name: changeInfo.name,
+                };
+                await this.ctx.helper.sendWechat(auditInfo.uid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
+
             } catch (error) {
                 await this.transaction.rollback();
                 result = false;
@@ -1154,15 +1251,17 @@ module.exports = app => {
          * @param {Number} pid 项目id
          * @param {Number} tid 台账id
          * @param {Number} cid 变更id
+         * @param {Number} uid 审核人id
          */
-        async getNoticeContent(pid, tid, cid) {
-            const noticeSql = 'SELECT * FROM (SELECT ' +
+        async getNoticeContent(pid, tid, cid, uid) {
+            const noticeSql =
+                'SELECT * FROM (SELECT ' +
                 '  t.`id` As `tid`, t.`name`, c.`cid`, c.`code` As `c_code`, pa.`name` As `su_name`, pa.role As `su_role`' +
                 '  FROM (SELECT * FROM ?? WHERE `id` = ? ) As t' +
                 '  LEFT JOIN ?? As c ON c.`cid` = ?' +
-                '  LEFT JOIN ?? As pa ON c.`uid` = pa.`id`' +
+                '  LEFT JOIN ?? As pa ON pa.`id` = ?' +
                 '  WHERE  t.`project_id` = ? ) as new_t GROUP BY new_t.`tid`';
-            const noticeSqlParam = [this.ctx.service.tender.tableName, tid, this.ctx.service.change.tableName, cid, this.ctx.service.projectAccount.tableName, pid];
+            const noticeSqlParam = [this.ctx.service.tender.tableName, tid, this.ctx.service.change.tableName, cid, this.ctx.service.projectAccount.tableName, uid, pid];
             const content = await this.db.query(noticeSql, noticeSqlParam);
             return content.length ? JSON.stringify(content[0]) : '';
         }

+ 18 - 1
app/service/change_att.js

@@ -42,7 +42,7 @@ module.exports = app => {
         /**
          * 获取 变更令 所有附件
          * @param {uuid} cid - 变更令id
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async getChangeAttachment(cid) {
             const sql = 'SELECT ca.*, pa.name As u_name, pa.role As u_role ' +
@@ -53,6 +53,23 @@ module.exports = app => {
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, cid];
             return await this.db.query(sql, sqlParam);
         }
+
+        /**
+         * 获取所有附件
+         * @param {String} cid 变更令id
+         */
+        async getAllChangeFiles(cid) {
+            const { ctx } = this;
+            const result = await this.db.select(this.tableName, { where: { cid } });
+            return result.map(item => {
+                if (!ctx.helper.canPreview(item.fileext)) {
+                    item.filepath = `/change/download/file/${item.id}`;
+                } else {
+                    item.filepath = '/' + item.filepath;
+                }
+                return item;
+            });
+        }
     }
 
     return ChangeAtt;

+ 7 - 8
app/service/change_audit.js

@@ -270,7 +270,7 @@ module.exports = app => {
                 '    t.`name`, t.`type`, t.`user_id` ' +
                 '  FROM ?? AS ca, ?? AS c, ?? As t ' +
                 '  WHERE ca.`uid` = ? and ca.`status` = ? and c.`status` != ?' +
-                '    and ca.`cid` = c.`cid` and ca.`tid` = t.`id`';
+                '    and ca.`cid` = c.`cid` and ca.`tid` = t.`id` ORDER BY ca.`sin_time` DESC';
             const sqlParam = [this.tableName, this.ctx.service.change.tableName, this.ctx.service.tender.tableName, uid, 2, audit.flow.status.uncheck];
             const changes = await this.db.query(sql, sqlParam);
             for (const c of changes) {
@@ -308,21 +308,20 @@ module.exports = app => {
             //             '  ORDER BY new_t.`cu_time`';
             // const sqlParam = [this.ctx.service.tender.tableName, uid, this.tableName, uid, this.ctx.service.change.tableName, this.tableName, pid, time, audit.flow.status.checking];
             // return await this.db.query(sql, sqlParam);
-            let notice =  await this.db.select('zh_notice', {
+            let notice = await this.db.select('zh_notice', {
                 where: { pid, type: pushType.change, uid },
                 orders: [['create_time', 'desc']],
-                limit: 10, offset: 0
+                limit: 10, offset: 0,
             });
             notice = notice.map(v => {
-                const extra = JSON.parse(v.content)
-                delete v.content
-                return { ...v, ...extra }
-            })
+                const extra = JSON.parse(v.content);
+                delete v.content;
+                return { ...v, ...extra };
+            });
             return notice;
         }
 
 
-
         async getAllAuditors(tenderId) {
             const sql = 'SELECT ca.uid, ca.tid FROM ' + this.tableName + ' ca' +
                 '  LEFT JOIN ' + this.ctx.service.tender.tableName + ' t On ca.tid = t.id' +

+ 1 - 1
app/service/change_audit_list.js

@@ -23,7 +23,7 @@ module.exports = app => {
             this.tableName = 'change_audit_list';
         }
 
-        async gatherBgBills (tid) {
+        async gatherBgBills(tid) {
             const sql = 'SELECT cb.code, cb.name, cb.unit, cb.unit_price, Round(Sum(cb.samount + 0), 6) as quantity' +
                 '  FROM ' + this.tableName + ' cb' +
                 '  LEFT JOIN ' + this.ctx.service.change.tableName + ' c ON cb.cid = c.cid' +

+ 1 - 1
app/service/change_company.js

@@ -40,7 +40,7 @@ module.exports = app => {
                 if (updateIdArray.length !== 0) {
                     for (const index in updateIdArray) {
                         if (updateArray[index].trim() === '') {
-                            await this.transaction.delete(this.tableName, {id: updateIdArray[index]});
+                            await this.transaction.delete(this.tableName, { id: updateIdArray[index] });
                         } else {
                             const updateData = {
                                 id: updateIdArray[index],

+ 16 - 15
app/service/ledger.js

@@ -162,7 +162,7 @@ module.exports = app => {
             const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
             const data = await this.db.query(sql, sqlParam);
 
-            return this._.sortBy(data, function (d) {
+            return this._.sortBy(data, function(d) {
                 return nodesIds.indexOf(d.ledger_id);
             });
         }
@@ -226,7 +226,7 @@ module.exports = app => {
         /**
          * 获取项目工程量
          * @param tenderId
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getGatherGclBills(tenderId) {
             const sql = 'SELECT `b_code`, `name`, `unit`, `unit_price`, ' +
@@ -276,7 +276,7 @@ module.exports = app => {
          * 删除相关数据 用于继承
          * @param mid
          * @param deleteData
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          * @private
          */
         async _deleteRelaData(mid, deleteData) {
@@ -325,7 +325,8 @@ module.exports = app => {
                 throw '复制数据错误:仅可操作同层节点';
             }
 
-            const pasteBillsData = [], pastePosData = [];
+            const pasteBillsData = [],
+                pastePosData = [];
             this.transaction = await this.db.beginTransaction();
             try {
                 // 选中节点的所有后兄弟节点,order+粘贴节点个数
@@ -339,13 +340,13 @@ module.exports = app => {
                     datas = this._.sortBy(datas, 'level');
 
                     // 计算粘贴数据中需更新部分
-                    datas.sort(function (x, y) {
+                    datas.sort(function(x, y) {
                         return x.level - y.level;
                     });
                     for (const data of datas) {
-                        data.children = datas.filter(function (x) {
+                        data.children = datas.filter(function(x) {
                             return x.ledger_pid === data.ledger_id;
-                        })
+                        });
                     }
                     for (let index = 0; index < datas.length; index++) {
                         const data = datas[index];
@@ -375,7 +376,7 @@ module.exports = app => {
                         delete data.children;
                         delete data.crid;
                         delete data.is_tp;
-                        const p = datas.find(function (x) {
+                        const p = datas.find(function(x) {
                             return x.ledger_id === data.ledger_pid;
                         });
                         if (p) {
@@ -383,7 +384,7 @@ module.exports = app => {
                         } else {
                             data.full_path = newParentPath + '' + data.ledger_id;
                         }
-                        pasteBillsData.push(data)
+                        pasteBillsData.push(data);
                     }
                     maxId = maxId + datas.length;
                 }
@@ -629,7 +630,7 @@ module.exports = app => {
          *
          * @param node
          * @param transaction
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          * @private
          */
         async calcNode(node, transaction) {
@@ -658,7 +659,7 @@ module.exports = app => {
          * @return {Promise<void>}
          */
         async calc(tid, id, transaction) {
-            const node = await transaction.get(this.tableName, {id: id});
+            const node = await transaction.get(this.tableName, { id });
             if (!node) {
                 throw '数据错误';
             }
@@ -699,7 +700,7 @@ module.exports = app => {
         /**
          * 导入Excel数据
          * @param excelData
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async importExcel(templateId, excelData) {
             const AnalysisExcel = require('../lib/analysis_excel').AnalysisExcelTree;
@@ -710,8 +711,8 @@ module.exports = app => {
             const orgMaxId = parseInt(await this.cache.get(cacheKey));
             const transaction = await this.db.beginTransaction();
             try {
-                await transaction.delete(this.tableName, {tender_id: this.ctx.tender.id});
-                await transaction.delete(this.ctx.service.pos.tableName, {tid: this.ctx.tender.id});
+                await transaction.delete(this.tableName, { tender_id: this.ctx.tender.id });
+                await transaction.delete(this.ctx.service.pos.tableName, { tid: this.ctx.tender.id });
                 const datas = [];
                 for (const node of cacheTree.items) {
                     const data = {
@@ -752,7 +753,7 @@ module.exports = app => {
                 }
                 await transaction.commit();
                 this.cache.set(cacheKey, cacheTree.keyNodeId, 'EX', this.ctx.app.config.cacheTime);
-                return {bills: datas, pos: cacheTree.pos}
+                return { bills: datas, pos: cacheTree.pos };
             } catch (err) {
                 await transaction.rollback();
                 if (orgMaxId) {

+ 182 - 121
app/service/ledger_audit.js

@@ -12,6 +12,7 @@ const auditConst = require('../const/audit').ledger;
 const smsTypeConst = require('../const/sms_type');
 const SMS = require('../lib/sms');
 const SmsAliConst = require('../const/sms_alitemplate');
+const wxConst = require('../const/wechat_template');
 const pushType = require('../const/audit').pushType;
 
 module.exports = app => {
@@ -33,10 +34,11 @@ module.exports = app => {
          * @param {Number} tenderId - 标段id
          * @param {Number} auditorId - 审核人id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getAuditor(tenderId, auditorId, times = 1) {
-            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
                 'FROM ?? AS la, ?? AS pa ' +
                 'WHERE la.`tender_id` = ? and la.`audit_id` = ? and la.`times` = ?' +
                 '    and la.`audit_id` = pa.`id`';
@@ -50,10 +52,11 @@ module.exports = app => {
          * @param {Number} tenderId - 标段id
          * @param {Number} auditorId - 审核人id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getStatusName(tenderId, times) {
-            const sql = 'SELECT pa.`name` ' +
+            const sql =
+                'SELECT pa.`name` ' +
                 'FROM ?? AS la, ?? AS pa ' +
                 'WHERE la.`tender_id` = ?' +
                 '    and la.`audit_id` = pa.`id` and la.`status` != ? ORDER BY la.`times` DESC, la.`audit_order` DESC';
@@ -66,10 +69,11 @@ module.exports = app => {
          *
          * @param {Number} tenderId - 标段id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getAuditors(tenderId, times = 1) {
-            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
                 'FROM ?? AS la, ?? AS pa, (SELECT `audit_id`,(@i:=@i+1) as `sort` FROM ??, (select @i:=0) as it WHERE `tender_id` = ? AND `times` = ? GROUP BY `audit_id`) as g ' +
                 'WHERE la.`tender_id` = ? and la.`times` = ? and la.`audit_id` = pa.`id` and g.`audit_id` = la.`audit_id` order by la.`audit_order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, tenderId, times, tenderId, times];
@@ -88,10 +92,11 @@ module.exports = app => {
          *
          * @param {Number} tenderId - 标段id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getCurAuditor(tenderId, times = 1) {
-            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
                 'FROM ?? AS la, ?? AS pa ' +
                 'WHERE la.`tender_id` = ? and la.`status` = ? and la.`times` = ?' +
                 '    and la.`audit_id` = pa.`id`';
@@ -104,7 +109,7 @@ module.exports = app => {
          *
          * @param {Number} tenderId - 标段id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<number>}
+         * @return {Promise<number>}
          */
         async getNewOrder(tenderId, times = 1) {
             const sql = 'SELECT Max(??) As max_order FROM ?? Where `tender_id` = ? and `times` = ?';
@@ -119,19 +124,19 @@ module.exports = app => {
          * @param {Number} tenderId - 标段id
          * @param {Number} auditorId - 审核人id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<number>}
+         * @return {Promise<number>}
          */
         async addAuditor(tenderId, auditorId, times = 1) {
             const newOrder = await this.getNewOrder(tenderId, times);
             const data = {
                 tender_id: tenderId,
                 audit_id: auditorId,
-                times: times,
+                times,
                 audit_order: newOrder,
                 status: auditConst.status.uncheck,
             };
             const result = await this.db.insert(this.tableName, data);
-            return result.effectRows = 1;
+            return (result.effectRows = 1);
         }
 
         /**
@@ -140,14 +145,14 @@ module.exports = app => {
          * @param {Number} tenderId - 标段id
          * @param {Number} auditorId - 审核人id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          * @private
          */
         async _syncOrderByDelete(transaction, tenderId, order, times) {
             this.initSqlBuilder();
             this.sqlBuilder.setAndWhere('tender_id', {
                 value: tenderId,
-                operate: '='
+                operate: '=',
             });
             this.sqlBuilder.setAndWhere('audit_order', {
                 value: order,
@@ -173,12 +178,12 @@ module.exports = app => {
          * @param {Number} tenderId - 标段id
          * @param {Number} auditorId - 审核人id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<boolean>}
+         * @return {Promise<boolean>}
          */
         async deleteAuditor(tenderId, auditorId, times = 1) {
             const transaction = await this.db.beginTransaction();
             try {
-                const condition = {tender_id: tenderId, audit_id: auditorId, times: times};
+                const condition = { tender_id: tenderId, audit_id: auditorId, times };
                 const auditor = await this.getDataByCondition(condition);
                 if (!auditor) {
                     throw '该审核人不存在';
@@ -198,28 +203,38 @@ module.exports = app => {
          *
          * @param {Number} tenderId - 标段id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<boolean>}
+         * @return {Promise<boolean>}
          */
         async start(tenderId, times = 1) {
-            const audit = await this.getDataByCondition({tender_id: tenderId, times: times, audit_order: 1});
+            const audit = await this.getDataByCondition({ tender_id: tenderId, times, audit_order: 1 });
             if (!audit) {
                 throw '审核人信息错误';
             }
-            const sum = await this.ctx.service.ledger.addUp({tender_id: tenderId/*, is_leaf: true*/});
+            const sum = await this.ctx.service.ledger.addUp({ tender_id: tenderId /* , is_leaf: true*/ });
 
             const transaction = await this.db.beginTransaction();
             try {
-                await transaction.update(this.tableName, {id: audit.id, status: auditConst.status.checking, begin_time: new Date()});
+                await transaction.update(this.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,
-                    total_price: sum.total_price, deal_tp: sum.deal_tp,
+                    id: tenderId,
+                    ledger_status: auditConst.status.checking,
+                    total_price: sum.total_price,
+                    deal_tp: sum.deal_tp,
                 });
 
                 // 添加短信通知-需要审批提醒功能
-                // await this.ctx.helper.sendUserSms(audit.audit_id, smsTypeConst.const.TZ,
-                //     smsTypeConst.judge.approval.toString(), '台帐需要您审批。');
-                await this.ctx.helper.sendAliSms(audit.audit_id, smsTypeConst.const.TZ,
-                    smsTypeConst.judge.approval.toString(), SmsAliConst.template.ledger_check);
+                await this.ctx.helper.sendAliSms(audit.audit_id, smsTypeConst.const.TZ, smsTypeConst.judge.approval.toString(), SmsAliConst.template.ledger_check);
+                // 微信模板通知
+                const wechatData = {
+                    status: wxConst.status.check,
+                    tips: wxConst.tips.check,
+                    begin_time: Date.parse(new Date()),
+                };
+                await this.ctx.helper.sendWechat(audit.audit_id, smsTypeConst.const.TZ, smsTypeConst.judge.approval.toString(), wxConst.template.ledger, wechatData);
 
                 await transaction.commit();
             } catch (err) {
@@ -233,8 +248,9 @@ module.exports = app => {
          * 审批
          * @param {Number} tenderId - 标段id
          * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
+         * @param {String} opinion 审批意见
          * @param {Number} times - 第几次审批
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async check(tenderId, checkType, opinion, times = 1) {
             if (checkType !== auditConst.status.checked && checkType !== auditConst.status.checkNo) {
@@ -245,7 +261,11 @@ module.exports = app => {
             try {
                 // 整理当前流程审核人状态更新
                 const time = new Date();
-                const audit = await this.getDataByCondition({ tender_id: tenderId, times, status: auditConst.status.checking });
+                const audit = await this.getDataByCondition({
+                    tender_id: tenderId,
+                    times,
+                    status: auditConst.status.checking,
+                });
                 if (!audit) {
                     throw '审核数据错误';
                 }
@@ -253,64 +273,78 @@ module.exports = app => {
                 // 获取审核人列表
                 const auditList = await this.getAuditors(tenderId, times);
                 // 添加推送
-                const noticeContent = await this.getNoticeContent(audit.tender_id, pid, audit.audit_id)
-                const records = [{ pid, type: pushType.ledger, uid: this.ctx.tender.data.user_id, status: auditConst.status.checked, content: noticeContent }]
-                auditList.forEach( audit => {
-                    records.push({ pid, type: pushType.ledger, uid: audit.audit_id, status: auditConst.status.checked, content: noticeContent })
-                })
+                const noticeContent = await this.getNoticeContent(audit.tender_id, pid, audit.audit_id);
+                const records = [
+                    {
+                        pid,
+                        type: pushType.ledger,
+                        uid: this.ctx.tender.data.user_id,
+                        status: checkType,
+                        content: noticeContent,
+                    },
+                ];
+                auditList.forEach(audit => {
+                    records.push({
+                        pid,
+                        type: pushType.ledger,
+                        uid: audit.audit_id,
+                        status: checkType,
+                        content: noticeContent,
+                    });
+                });
                 await transaction.insert('zh_notice', records);
                 // 更新当前审核流程
                 await transaction.update(this.tableName, { id: audit.id, status: checkType, opinion, end_time: time });
+                const begin_audit = await this.getDataByCondition({
+                    tender_id: tenderId,
+                    audit_order: 1,
+                });
                 if (checkType === auditConst.status.checked) {
-                    const nextAudit = await this.getDataByCondition({ tender_id: tenderId, times, audit_order: audit.audit_order + 1 });
+                    const nextAudit = await this.getDataByCondition({
+                        tender_id: tenderId,
+                        times,
+                        audit_order: audit.audit_order + 1,
+                    });
                     // 无下一审核人表示,审核结束
                     if (nextAudit) {
-                        await transaction.update(this.tableName, { id: nextAudit.id, status: auditConst.status.checking, begin_time: time });
-                        // await this.ctx.helper.sendUserSms(nextAudit.audit_id, smsTypeConst.const.TZ,
-                        //     smsTypeConst.judge.approval.toString(), '台帐需要您审批。');
-                        await this.ctx.helper.sendAliSms(nextAudit.audit_id, smsTypeConst.const.TZ,
-                            smsTypeConst.judge.approval.toString(), SmsAliConst.template.ledger_check);
+                        await transaction.update(this.tableName, {
+                            id: nextAudit.id,
+                            status: auditConst.status.checking,
+                            begin_time: time,
+                        });
+                        await this.ctx.helper.sendAliSms(nextAudit.audit_id, smsTypeConst.const.TZ, smsTypeConst.judge.approval.toString(), SmsAliConst.template.ledger_check);
+                        // 微信模板通知
+                        const wechatData = {
+                            status: wxConst.status.check,
+                            tips: wxConst.tips.check,
+                            begin_time: Date.parse(begin_audit.begin_time),
+                        };
+                        await this.ctx.helper.sendWechat(nextAudit.audit_id, smsTypeConst.const.TZ, smsTypeConst.judge.approval.toString(), wxConst.template.ledger, wechatData);
                     } else {
                         // 同步标段信息
-                        await transaction.update(this.ctx.service.tender.tableName, { id: tenderId, ledger_status: checkType });
-
-                        // 添加短信通知-审批通过提醒功能
-                        // const mobile_array = [];
-                        // const tenderInfo = await this.ctx.service.tender.getDataById(tenderId);
-                        // const smsUser1 = await this.ctx.service.projectAccount.getDataById(tenderInfo.user_id);
-                        // if (smsUser1.auth_mobile !== '' && smsUser1.auth_mobile !== undefined && smsUser1.sms_type !== '' && smsUser1.sms_type !== null) {
-                        //     const smsType = JSON.parse(smsUser1.sms_type);
-                        //     if (smsType[smsTypeConst.const.TZ] !== undefined && smsType[smsTypeConst.const.TZ].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
-                        //         mobile_array.push(smsUser1.auth_mobile);
-                        //     }
-                        // }
-                        // const auditList = await this.getAuditors(tenderId, times);
-                        // for (const user of auditList) {
-                        //     const smsUser = await this.ctx.service.projectAccount.getDataById(user.audit_id);
-                        //     if (smsUser.auth_mobile !== '' && smsUser.auth_mobile !== undefined && smsUser.sms_type !== '' && smsUser.sms_type !== null) {
-                        //         const smsType = JSON.parse(smsUser.sms_type);
-                        //         if (mobile_array.indexOf(smsUser.auth_mobile) === -1 && smsType[smsTypeConst.const.TZ] !== undefined && smsType[smsTypeConst.const.TZ].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
-                        //             mobile_array.push(smsUser.auth_mobile);
-                        //         }
-                        //     }
-                        // }
-                        // if (mobile_array.length > 0) {
-                        //     const sms = new SMS(this.ctx);
-                        //     const tenderName = await sms.contentChange(tenderInfo.name);
-                        //     const projectName = await sms.contentChange(this.ctx.tender.info.deal_info.buildName);
-                        //     const ptmsg = projectName !== '' ? '项目「' + projectName + '」标段「' + tenderName + '」' : tenderName;
-                        //     const content = '【纵横计量支付】' + ptmsg + '台账审批通过,请登录系统处理。';
-                        //     sms.send(mobile_array, content);
-                        // }
+                        await transaction.update(this.ctx.service.tender.tableName, {
+                            id: tenderId,
+                            ledger_status: checkType,
+                        });
                         const users = this._.pull(this._.map(auditList, 'audit_id'), audit.id);
-                        // await this.ctx.helper.sendUserSms(users, smsTypeConst.const.TZ,
-                        //     smsTypeConst.judge.result.toString(), '台账审批通过,请登录系统处理。');
-                        await this.ctx.helper.sendAliSms(users, smsTypeConst.const.TZ,
-                            smsTypeConst.judge.result.toString(), SmsAliConst.template.ledger_result, { status: SmsAliConst.status.success });
+                        await this.ctx.helper.sendAliSms(users, smsTypeConst.const.TZ, smsTypeConst.judge.result.toString(), SmsAliConst.template.ledger_result, {
+                            status: SmsAliConst.status.success,
+                        });
+                        // 微信模板通知
+                        const wechatData = {
+                            status: wxConst.status.success,
+                            tips: wxConst.tips.success,
+                            begin_time: Date.parse(begin_audit.begin_time),
+                        };
+                        await this.ctx.helper.sendWechat(users, smsTypeConst.const.TZ, smsTypeConst.judge.result.toString(), wxConst.template.ledger, wechatData);
                     }
                 } else {
                     // 同步标段信息
-                    await transaction.update(this.ctx.service.tender.tableName, { id: tenderId, ledger_times: times + 1, ledger_status: checkType});
+                    await transaction.update(this.ctx.service.tender.tableName, {
+                        id: tenderId,
+                        ledger_times: times + 1,
+                        ledger_status: checkType,
+                    });
                     // 拷贝新一次审核流程列表
                     const auditors = await this.getAllDataByCondition({
                         where: { tender_id: tenderId, times },
@@ -322,39 +356,17 @@ module.exports = app => {
                     }
                     await transaction.insert(this.tableName, auditors);
 
-                    // 添加短信通知-审批退回提醒功能
-                    // const mobile_array = [];
-                    // const tenderInfo = await this.ctx.service.tender.getDataById(tenderId);
-                    // const smsUser1 = await this.ctx.service.projectAccount.getDataById(tenderInfo.user_id);
-                    // if (smsUser1.auth_mobile !== '' && smsUser1.auth_mobile !== undefined && smsUser1.sms_type !== '' && smsUser1.sms_type !== null) {
-                    //     const smsType = JSON.parse(smsUser1.sms_type);
-                    //     if (smsType[smsTypeConst.const.TZ] !== undefined && smsType[smsTypeConst.const.TZ].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
-                    //         mobile_array.push(smsUser1.auth_mobile);
-                    //     }
-                    // }
-                    // for (const user of auditors) {
-                    //     const smsUser = await this.ctx.service.projectAccount.getDataById(user.audit_id);
-                    //     if (smsUser.auth_mobile !== '' && smsUser.auth_mobile !== undefined && smsUser.sms_type !== '' && smsUser.sms_type !== null) {
-                    //         const smsType = JSON.parse(smsUser.sms_type);
-                    //         if (mobile_array.indexOf(smsUser.auth_mobile) === -1 && smsType[smsTypeConst.const.TZ] !== undefined && smsType[smsTypeConst.const.TZ].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
-                    //             mobile_array.push(smsUser.auth_mobile);
-                    //         }
-                    //     }
-                    // }
-                    // if (mobile_array.length > 0) {
-                    //     const sms = new SMS(this.ctx);
-                    //     const tenderName = await sms.contentChange(tenderInfo.name);
-                    //     const projectName = await sms.contentChange(this.ctx.tender.info.deal_info.buildName);
-                    //     const ptmsg = projectName !== '' ? '项目「' + projectName + '」标段「' + tenderName + '」' : tenderName;
-                    //     const content = '【纵横计量支付】' + ptmsg + '台账审批退回,请登录系统处理。';
-                    //     sms.send(mobile_array, content);
-                    // }
                     const users = this._.pull(this._.map(auditList, 'audit_id'), audit.id);
-                    // await this.ctx.helper.sendUserSms(users, smsTypeConst.const.TZ,
-                    //     smsTypeConst.judge.result.toString(), '台账审批退回,请登录系统处理。');
-                    await this.ctx.helper.sendAliSms(users, smsTypeConst.const.TZ,
-                        smsTypeConst.judge.result.toString(), SmsAliConst.template.ledger_result, { status: SmsAliConst.status.back });
-
+                    await this.ctx.helper.sendAliSms(users, smsTypeConst.const.TZ, smsTypeConst.judge.result.toString(), SmsAliConst.template.ledger_result, {
+                        status: SmsAliConst.status.back,
+                    });
+                    // 微信模板通知
+                    const wechatData = {
+                        status: wxConst.status.back,
+                        tips: wxConst.tips.back,
+                        begin_time: Date.parse(begin_audit.begin_time),
+                    };
+                    await this.ctx.helper.sendWechat(audit.audit_id, smsTypeConst.const.TZ, smsTypeConst.judge.result.toString(), wxConst.template.ledger, wechatData);
                 }
 
                 await transaction.commit();
@@ -368,23 +380,32 @@ module.exports = app => {
          * 获取审核人需要审核的标段列表
          *
          * @param auditorId
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getAuditTender(auditorId) {
-            const sql = 'SELECT la.`audit_id`, la.`times`, la.`audit_order`, la.`begin_time`, la.`end_time`, t.`id`, t.`name`, t.`project_id`, t.`type`, t.`user_id`, t.`ledger_status` ' +
+            const sql =
+                'SELECT la.`audit_id`, la.`times`, la.`audit_order`, la.`begin_time`, la.`end_time`, t.`id`, t.`name`, t.`project_id`, t.`type`, t.`user_id`, t.`ledger_status` ' +
                 'FROM ?? AS la, ?? AS t ' +
                 'WHERE ((la.`audit_id` = ? and la.`status` = ?) OR (t.`user_id` = ? and t.`ledger_status` = ? and la.`status` = ? and la.`times` = (t.`ledger_times`-1)))' +
-                '    and la.`tender_id` = t.`id`';
-            const sqlParam = [this.tableName, this.ctx.service.tender.tableName, auditorId, auditConst.status.checking, auditorId, auditConst.status.checkNo, auditConst.status.checkNo];
+                '    and la.`tender_id` = t.`id` ORDER BY la.`begin_time` DESC';
+            const sqlParam = [
+                this.tableName,
+                this.ctx.service.tender.tableName,
+                auditorId,
+                auditConst.status.checking,
+                auditorId,
+                auditConst.status.checkNo,
+                auditConst.status.checkNo,
+            ];
             return await this.db.query(sql, sqlParam);
         }
 
         /**
          * 获取 某时间后 审批进度 更新的台账
-         * @param {Integer} projectId - 项目id
-         * @param {Integer} auditorId - 查询人id
+         * @param {Integer} pid - 项目id
+         * @param {Integer} uid - 查询人id
          * @param {Date} noticeTime - 查询事件
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getNoticeTender(pid, uid, noticeTime) {
             // const sql = 'SELECT * FROM (SELECT la.`audit_id`, la.`times`, la.`audit_order`, la.`end_time`, la.`status`, t.`id`, t.`name`, t.`project_id`, t.`type`, t.`user_id`, ' +
@@ -398,17 +419,17 @@ module.exports = app => {
             //     noticeTime, projectId];
             // return await this.db.query(sql, sqlParam);
 
-
-            let notice =  await this.db.select('zh_notice', {
+            let notice = await this.db.select('zh_notice', {
                 where: { pid, type: pushType.ledger, uid },
                 orders: [['create_time', 'desc']],
-                limit: 10, offset: 0
+                limit: 10,
+                offset: 0,
             });
             notice = notice.map(v => {
-                const extra = JSON.parse(v.content)
-                delete v.content
-                return { ...v, ...extra }
-            })
+                const extra = JSON.parse(v.content);
+                delete v.content;
+                return { ...v, ...extra };
+            });
             return notice;
         }
 
@@ -419,7 +440,8 @@ module.exports = app => {
          * @param {Number} uid 审核人id
          */
         async getNoticeContent(id, pid, uid) {
-            const noticeSql = 'SELECT * FROM (SELECT ' +
+            const noticeSql =
+                'SELECT * FROM (SELECT ' +
                 '  t.`id` As `tid`, t.`name`,  pa.`name` As `su_name`, pa.role As `su_role`' +
                 '  FROM (SELECT * FROM ?? WHERE `id` = ? ) As t' +
                 '  LEFT JOIN ?? As pa ON pa.`id` = ?' +
@@ -428,6 +450,45 @@ module.exports = app => {
             const content = await this.db.query(noticeSql, noticeSqlParam);
             return content.length ? JSON.stringify(content[0]) : '';
         }
+
+        /**
+         * 获取审核人流程列表
+         * @param {Number} tender_id - 标段id
+         * @param {Number} times 审核次数
+         * @return {Promise<Array>} 查询结果集
+         */
+        async getAuditGroupByList(tender_id, times = 1) {
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`tender_id`, la.`audit_order` ' +
+                'FROM ?? AS la, ?? AS pa ' +
+                'WHERE la.`tender_id` = ? and la.`times` = ? and la.`audit_id` = pa.`id` GROUP BY la.`audit_id` ORDER BY la.`audit_order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tender_id, times];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 获取审核人流程列表(包括原报)
+         * @param {Number} tender_id - 标段id
+         * @param {Number} times 审核次数
+         * @return {Promise<Array>} 查询结果集(包括原报)
+         */
+        async getAuditorsWithOwner(tender_id, times = 1) {
+            const result = await this.getAuditGroupByList(tender_id, times);
+            const sql =
+                'SELECT pa.`id` As audit_id, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As tender_id, 0 As `audit_order`' +
+                '  FROM ' +
+                this.ctx.service.tender.tableName +
+                ' As s' +
+                '  LEFT JOIN ' +
+                this.ctx.service.projectAccount.tableName +
+                ' As pa' +
+                '  ON s.user_id = pa.id' +
+                '  WHERE s.id = ?';
+            const sqlParam = [times, tender_id, tender_id];
+            const user = await this.db.queryOne(sql, sqlParam);
+            result.unshift(user);
+            return result;
+        }
     }
 
     return LedgerAudit;

+ 4 - 9
app/service/ledger_revise.js

@@ -76,11 +76,6 @@ module.exports = app => {
             const sqlParam = [tid];
             const result = await this.db.queryOne(sql, sqlParam);
             return result.max_order || 0;
-            // if (result && result.max_order) {
-            //     return result.max_order;
-            // } else {
-            //     return 0;
-            // }
         }
 
         async _initReviseBills(transaction, tid) {
@@ -88,11 +83,11 @@ module.exports = app => {
                 '  (id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
                 '     quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
                 '     sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id, is_tp,' +
-                '     sgfh_expr, sjcl_expr, qtcl_expr)' +
+                '     sgfh_expr, sjcl_expr, qtcl_expr, gxby_status, dagl_status)' +
                 '  Select id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
                 '      quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
                 '      sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id, is_tp,' +
-                '      sgfh_expr, sjcl_expr, qtcl_expr' +
+                '      sgfh_expr, sjcl_expr, qtcl_expr, gxby_status, dagl_status' +
                 '  From ' + this.ctx.service.ledger.tableName +
                 '  Where `tender_id` = ?';
             const sqlParam = [tid];
@@ -103,10 +98,10 @@ module.exports = app => {
             const sql = 'Insert Into ' + this.ctx.service.revisePos.tableName +
                 '  (id, tid, lid, name, drawing_code, quantity, add_stage, add_times, add_user,' +
                 '     sgfh_qty, sjcl_qty, qtcl_qty, crid, in_time, porder, position,' +
-                '     sgfh_expr, sjcl_expr, qtcl_expr)' +
+                '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty, gxby_status, dagl_status)' +
                 '  Select id, tid, lid, name, drawing_code, quantity, add_stage, add_times, add_user,' +
                 '     sgfh_qty, sjcl_qty, qtcl_qty, crid, in_time, porder, position,' +
-                '     sgfh_expr, sjcl_expr, qtcl_expr' +
+                '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty, gxby_status, dagl_status' +
                 '  From ' + this.ctx.service.pos.tableName +
                 '  Where `tid` = ?';
             const sqlParam = [tid];

+ 123 - 23
app/service/material_audit.js

@@ -10,6 +10,8 @@
 
 const auditConst = require('../const/audit').material;
 const pushType = require('../const/audit').pushType;
+const smsTypeConst = require('../const/sms_type');
+const wxConst = require('../const/wechat_template');
 
 module.exports = app => {
     class MaterialAudit extends app.BaseService {
@@ -222,6 +224,19 @@ module.exports = app => {
                 }
                 await transaction.insert(this.ctx.service.materialBillsHistory.tableName, mbhList);
 
+                // 微信模板通知
+                const materialInfo = await this.ctx.service.material.getDataById(materialId);
+                const wechatData = {
+                    qi: materialInfo.order,
+                    status: wxConst.status.check,
+                    tips: wxConst.tips.check,
+                    begin_time: Date.parse(new Date()),
+                    m_tp: this.ctx.helper.round(materialInfo.m_tp, 2),
+                    hs_m_tp: this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2),
+                };
+                await this.ctx.helper.sendWechat(audit.aid, smsTypeConst.const.TC, smsTypeConst.judge.approval.toString(), wxConst.template.material, wechatData);
+
+
                 // 添加短信通知-需要审批提醒功能
                 // const smsUser = await this.ctx.service.projectAccount.getDataById(audit.aid);
                 // if (smsUser.auth_mobile !== '' && smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
@@ -267,13 +282,19 @@ module.exports = app => {
                 await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
 
                 // 获取推送必要信息
-                const noticeContent = await this.getNoticeContent(pid, audit.tid, materialId, audit.aid)
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, materialId, audit.aid);
                 // 添加推送
-                const records = [{ pid, type: pushType.material, uid: this.ctx.material.user_id, status: auditConst.status.checked, content: noticeContent }]
+                const records = [{ pid, type: pushType.material, uid: this.ctx.material.user_id, status: auditConst.status.checked, content: noticeContent }];
                 auditors.forEach(audit => {
-                    records.push({ pid, type: pushType.material, uid: audit.aid, status: auditConst.status.checked, content: noticeContent })
-                })
+                    records.push({ pid, type: pushType.material, uid: audit.aid, status: auditConst.status.checked, content: noticeContent });
+                });
                 await transaction.insert('zh_notice', records);
+                const begin_audit = await this.getDataByCondition({
+                    mid: materialId,
+                    order: 1,
+                });
+                const materialInfo = await this.ctx.service.material.getDataById(materialId);
+
                 // 无下一审核人表示,审核结束
                 if (nextAudit) {
                     // 复制一份下一审核人数据
@@ -286,6 +307,16 @@ module.exports = app => {
                         id: materialId, status: auditConst.status.checking,
                     });
 
+                    // 微信模板通知
+                    const wechatData = {
+                        qi: materialInfo.order,
+                        status: wxConst.status.check,
+                        tips: wxConst.tips.check,
+                        begin_time: Date.parse(begin_audit.begin_time),
+                        m_tp: this.ctx.helper.round(materialInfo.m_tp, 2),
+                        hs_m_tp: this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2),
+                    };
+                    await this.ctx.helper.sendWechat(nextAudit.id, smsTypeConst.const.TC, smsTypeConst.judge.approval.toString(), wxConst.template.material, wechatData);
 
 
                     // 添加短信通知-需要审批提醒功能
@@ -313,6 +344,18 @@ module.exports = app => {
                         id: materialId, status: checkData.checkType,
                     });
 
+                    // 微信模板通知
+                    const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), materialInfo.user_id));
+                    const wechatData = {
+                        qi: materialInfo.order,
+                        status: wxConst.status.success,
+                        tips: wxConst.tips.success,
+                        begin_time: Date.parse(begin_audit.begin_time),
+                        m_tp: this.ctx.helper.round(materialInfo.m_tp, 2),
+                        hs_m_tp: this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2),
+                    };
+                    await this.ctx.helper.sendWechat(users, smsTypeConst.const.TC, smsTypeConst.judge.result.toString(), wxConst.template.material, wechatData);
+
                     // 添加短信通知-审批通过提醒功能
                     // const mobile_array = [];
                     // const stageInfo = await this.ctx.service.stage.getDataById(stageId);
@@ -369,12 +412,12 @@ module.exports = app => {
             try {
                 await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
                 // 添加到消息推送表
-                const noticeContent = await this.getNoticeContent(pid, audit.tid, materialId, audit.aid)
-                const records = [{ pid, type: pushType.material, uid: this.ctx.material.user_id, status: auditConst.status.checkNo, content: noticeContent }]
-                auditors.forEach( audit => {
-                    records.push({ pid, type: pushType.material, uid: audit.aid, status: auditConst.status.checkNo, content: noticeContent })
-                })
-                await transaction.insert('zh_notice', records);
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, materialId, audit.aid);
+                const records = [{ pid, type: pushType.material, uid: this.ctx.material.user_id, status: auditConst.status.checkNo, content: noticeContent }];
+                auditors.forEach(audit => {
+                    records.push({ pid, type: pushType.material, uid: audit.aid, status: auditConst.status.checkNo, content: noticeContent });
+                });
+                await transaction.insert(this.ctx.service.noticePush.tableName, records);
                 // 同步期信息
                 await transaction.update(this.ctx.service.material.tableName, {
                     id: materialId, status: checkData.checkType,
@@ -388,6 +431,23 @@ module.exports = app => {
                     order: this.ctx.material.order,
                 });
 
+                // 微信模板通知
+                const begin_audit = await this.getDataByCondition({
+                    mid: materialId,
+                    order: 1,
+                });
+                const materialInfo = await this.ctx.service.material.getDataById(materialId);
+                const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), materialInfo.user_id));
+                const wechatData = {
+                    qi: materialInfo.order,
+                    status: wxConst.status.back,
+                    tips: wxConst.tips.back,
+                    begin_time: Date.parse(begin_audit.begin_time),
+                    m_tp: this.ctx.helper.round(materialInfo.m_tp, 2),
+                    hs_m_tp: this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2),
+                };
+                await this.ctx.helper.sendWechat(users, smsTypeConst.const.TC, smsTypeConst.judge.result.toString(), wxConst.template.material, wechatData);
+
                 // 计算该审批人最终数据
                 // await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
                 // 复制一份最新数据给原报
@@ -441,14 +501,14 @@ module.exports = app => {
                 return item.aid === audit.aid;
             });
             const preAuditor = auditors2[auditorIndex - 1];
-            const noticeContent = await this.getNoticeContent(pid, audit.tid, materialId, audit.aid)
+            const noticeContent = await this.getNoticeContent(pid, audit.tid, materialId, audit.aid);
             const transaction = await this.db.beginTransaction();
             try {
                 // 添加到消息推送表
-                const records = [{ pid, type: pushType.material, uid: this.ctx.material.user_id, status: auditConst.status.checkNoPre, content: noticeContent }]
-                auditors2.forEach( audit => {
-                    records.push({ pid, type: pushType.material, uid: audit.aid, status: auditConst.status.checkNoPre, content: noticeContent })
-                })
+                const records = [{ pid, type: pushType.material, uid: this.ctx.material.user_id, status: auditConst.status.checkNoPre, content: noticeContent }];
+                auditors2.forEach(audit => {
+                    records.push({ pid, type: pushType.material, uid: audit.aid, status: auditConst.status.checkNoPre, content: noticeContent });
+                });
                 await transaction.insert('zh_notice', records);
                 await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
                 // 顺移气候审核人流程顺序
@@ -469,6 +529,22 @@ module.exports = app => {
                     times: audit.times, order: audit.order + 2, status: auditConst.status.uncheck,
                 });
 
+                // 微信模板通知
+                const begin_audit = await this.getDataByCondition({
+                    mid: materialId,
+                    order: 1,
+                });
+                const materialInfo = await this.ctx.service.material.getDataById(materialId);
+                const wechatData = {
+                    qi: materialInfo.order,
+                    status: wxConst.status.check,
+                    tips: wxConst.tips.check,
+                    begin_time: Date.parse(begin_audit.begin_time),
+                    m_tp: this.ctx.helper.round(materialInfo.m_tp, 2),
+                    hs_m_tp: this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2),
+                };
+                await this.ctx.helper.sendWechat(audit.aid, smsTypeConst.const.TC, smsTypeConst.judge.approval.toString(), wxConst.template.material, wechatData);
+
                 await transaction.insert(this.tableName, newAuditors);
                 await transaction.commit();
             } catch (error) {
@@ -597,7 +673,7 @@ module.exports = app => {
                         '    t.`name`, t.`project_id`, t.`type`, t.`user_id` ' +
                         '  FROM ?? AS ma, ?? AS m, ?? As t ' +
                         '  WHERE ((ma.`aid` = ? and ma.`status` = ?) OR (m.`user_id` = ? and ma.`status` = ? and m.`status` = ? and ma.`times` = (m.`times`-1)))' +
-                        '    and ma.`mid` = m.`id` and ma.`tid` = t.`id`';
+                        '    and ma.`mid` = m.`id` and ma.`tid` = t.`id` ORDER BY ma.`begin_time` DESC';
             const sqlParam = [this.tableName, this.ctx.service.material.tableName, this.ctx.service.tender.tableName, auditorId, auditConst.status.checking, auditorId, auditConst.status.checkNo, auditConst.status.checkNo];
             return await this.db.query(sql, sqlParam);
         }
@@ -624,16 +700,16 @@ module.exports = app => {
             // const sqlParam = [this.ctx.service.tender.tableName, uid, this.tableName, uid, this.ctx.service.material.tableName, this.tableName,
             //     this.ctx.service.projectAccount.tableName, time, pid];
             // return await this.db.query(sql, sqlParam);
-            let notice =  await this.db.select('zh_notice', {
-                where: { pid, type: pushType.material, uid},
+            let notice = await this.db.select('zh_notice', {
+                where: { pid, type: pushType.material, uid },
                 orders: [['create_time', 'desc']],
-                limit: 10, offset: 0
+                limit: 10, offset: 0,
             });
             notice = notice.map(v => {
-                const extra = JSON.parse(v.content)
-                delete v.content
-                return { ...v, ...extra }
-            })
+                const extra = JSON.parse(v.content);
+                delete v.content;
+                return { ...v, ...extra };
+            });
             return notice;
         }
 
@@ -652,6 +728,30 @@ module.exports = app => {
         }
 
         /**
+         * 获取审核人流程列表(包括原报)
+         * @param {Number} materialId 调差id
+         * @param {Number} times 审核次数
+         * @return {Promise<Array>} 查询结果集(包括原报)
+         */
+        async getAuditorsWithOwner(materialId, times = 1) {
+            const result = await this.getAuditGroupByList(materialId, times);
+            const sql =
+                'SELECT pa.`id` As aid, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As mid, 0 As `order`' +
+                '  FROM ' +
+                this.ctx.service.material.tableName +
+                ' As s' +
+                '  LEFT JOIN ' +
+                this.ctx.service.projectAccount.tableName +
+                ' As pa' +
+                '  ON s.user_id = pa.id' +
+                '  WHERE s.id = ?';
+            const sqlParam = [times, materialId, materialId];
+            const user = await this.db.queryOne(sql, sqlParam);
+            result.unshift(user);
+            return result;
+        }
+
+        /**
          * 复制上一期的审批人列表给最新一期
          *
          * @param transaction - 新增一期的事务

+ 38 - 14
app/service/material_bills.js

@@ -33,18 +33,38 @@ module.exports = app => {
             if (!this.ctx.tender || !this.ctx.material) {
                 throw '数据错误';
             }
-            const newBills = {
-                tid: this.ctx.tender.id,
-                mid: this.ctx.material.id,
-                in_time: new Date(),
-            };
-
-            // 新增工料
-            const result = await this.db.insert(this.tableName, newBills);
-            if (result.affectedRows !== 1) {
-                throw '新增工料数据失败';
+            const transaction = await this.db.beginTransaction();
+            try {
+                const newBills = {
+                    tid: this.ctx.tender.id,
+                    mid: this.ctx.material.id,
+                    in_time: new Date(),
+                };
+                // 新增工料
+                const result = await transaction.insert(this.tableName, newBills);
+                if (result.affectedRows !== 1) {
+                    throw '新增工料数据失败';
+                }
+                const insertArray = [];
+                const material_month = this.ctx.material.months ? this.ctx.material.months.split(',') : [];
+                for (const ym of material_month) {
+                    const one_month = {
+                        tid: this.ctx.tender.id,
+                        mid: this.ctx.material.id,
+                        mb_id: result.insertId,
+                        msg_tp: null,
+                        yearmonth: ym,
+                    };
+                    insertArray.push(one_month);
+                }
+                if (insertArray.length !== 0) await transaction.insert(this.ctx.service.materialMonth.tableName, insertArray);
+                await transaction.commit();
+                return await this.getDataById(result.insertId);
+            } catch (error) {
+                console.log(error);
+                await transaction.rollback();
+                throw error;
             }
-            return await this.getDataById(result.insertId);
         }
 
         /**
@@ -66,6 +86,10 @@ module.exports = app => {
                     // 金额发生变化,则重新计算本期金额
                     m_tp = await this.calcMaterialMTp(transaction);
                 }
+                const material_month = this.ctx.material.months ? this.ctx.material.months.split(',') : [];
+                if (material_month.length > 0) {
+                    await transaction.delete(this.ctx.service.materialMonth.tableName, { mb_id: id });
+                }
                 await transaction.commit();
                 return m_tp;
             } catch (err) {
@@ -155,7 +179,7 @@ module.exports = app => {
          * @returns {Promise<*>}
          */
         async calcQuantityByMB(transaction, mid, mb, materialCalculator) {
-            const [newmsg_spread, newm_spread] = await this.getSpread(mb);
+            const [newmsg_spread, newm_spread] = await this.getSpread(mb, null);
             if (mb.t_type === materialConst.t_type[0].value) {
                 const sql = 'SELECT SUM(`gather_qty`*`quantity`) as quantity FROM ' + this.ctx.service.materialList.tableName + ' WHERE `mid`=? AND `mb_id`=? AND `is_join`=1';
                 const sqlParam = [mid, mb.id];
@@ -194,8 +218,8 @@ module.exports = app => {
          * @param data
          * @returns {Promise<void>}
          */
-        async getSpread(data) {
-            data.msg_tp = null;
+        async getSpread(data, msg_tp) {
+            data.msg_tp = msg_tp;
             const msg_spread = this.ctx.helper.round(this.ctx.helper.sub(data.msg_tp, data.basic_price), 3);
             const cor = msg_spread >= 0 ? this.ctx.helper.mul(data.basic_price, this.ctx.helper.div(data.m_up_risk, 100)) : this.ctx.helper.mul(data.basic_price, this.ctx.helper.div(data.m_down_risk, 100));
             const m_spread = Math.abs(msg_spread) > Math.abs(cor) ? (msg_spread > 0 ? this.ctx.helper.round(this.ctx.helper.sub(msg_spread, cor), 3) : this.ctx.helper.round(this.ctx.helper.add(msg_spread, cor), 3)) : 0;

+ 8 - 1
app/service/material_file.js

@@ -27,12 +27,19 @@ module.exports = app => {
          * @return {Promise<void>} 数据库查询实例
          */
         async getAllMaterialFiles(tid, mid) {
+            const { ctx } = this;
             const where = { tid };
             if (mid) where.mid = mid;
-            return await this.db.select(this.tableName, {
+            const result = await this.db.select(this.tableName, {
                 where,
                 orders: [['upload_time', 'desc']],
             });
+            return result.map(item => {
+                if (!ctx.helper.canPreview(item.fileext)) {
+                    item.filepath = `tender/${ctx.tender.id}/measure/material/${item.s_order}/file/${item.id}/download`;
+                }
+                return item;
+            });
         }
 
 

+ 2 - 2
app/service/material_list.js

@@ -174,7 +174,7 @@ module.exports = app => {
          * 修改material_bills的quantity值和计算本期金额
          * @param transaction
          * @param mb_id
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async calcQuantityByML(transaction, mb_id) {
             // 修改material_bills值
@@ -225,7 +225,7 @@ module.exports = app => {
          * @param transaction
          * @param preMaterial
          * @param newMid
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async copyPreMaterialList(transaction, preMaterial, newMaterial) {
             const materialListData = await this.getAllDataByCondition({ where: { tid: this.ctx.tender.id, mid: preMaterial.id } });

+ 224 - 0
app/service/material_month.js

@@ -0,0 +1,224 @@
+'use strict';
+
+/**
+ * 期计量 数据模型
+ *
+ * @author Mai
+ * @date 2018/8/13
+ * @version
+ */
+
+const auditConst = require('../const/audit').material;
+const materialConst = require('../const/material');
+const MaterialCalculator = require('../lib/material_calc');
+
+module.exports = app => {
+    class MaterialMonth extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'material_month';
+        }
+
+        /**
+         * 返回月信息价列表
+         * @return {void}
+         */
+        async getListByMid(mid) {
+            return await this.getAllDataByCondition({ where: { mid } });
+        }
+
+        /**
+         * 添加月信息价 并更新 工料平均单价、本期单价,调差金额等
+         * @return {void}
+         */
+        async add(data, monthList, mbList) {
+            if (!this.ctx.tender || !this.ctx.material) {
+                throw '数据错误';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                const material_month = this.ctx.material.months ? this.ctx.material.months.split(',') : [];
+                material_month.push(data.yearmonth);
+                if (mbList.length !== 0) {
+                    const insertArray = [];
+                    const updateArray = [];
+                    for (const mb of mbList) {
+                        const one_month = {
+                            tid: this.ctx.tender.id,
+                            mid: this.ctx.material.id,
+                            mb_id: mb.id,
+                            msg_tp: monthList.length !== 0 ? null : mb.msg_tp,
+                            yearmonth: data.yearmonth,
+                        };
+                        insertArray.push(one_month);
+                        if (monthList.length !== 0) {
+                            const mb_msg_tp_sum = this._.sumBy(this._.filter(monthList, { mb_id: mb.id }), 'msg_tp');
+                            const new_msg_tp = this.ctx.helper.round(this.ctx.helper.div(this.ctx.helper.add(mb_msg_tp_sum, one_month.msg_tp), material_month.length), 3);
+                            const [newmsg_spread, newm_spread] = await this.ctx.service.materialBills.getSpread(mb, new_msg_tp);
+                            updateArray.push({
+                                id: mb.id,
+                                msg_tp: new_msg_tp,
+                                msg_spread: newmsg_spread,
+                                m_spread: newm_spread,
+                                m_tp: this.ctx.helper.round(this.ctx.helper.mul(mb.quantity, newm_spread), 2),
+                            });
+                        }
+                    }
+                    if (insertArray.length !== 0) await transaction.insert(this.tableName, insertArray);
+                    if (updateArray.length !== 0) await transaction.updateRows(this.ctx.service.materialBills.tableName, updateArray);
+                }
+                await transaction.update(this.ctx.service.material.tableName, { id: this.ctx.material.id, months: material_month.join(',') });
+                const m_tp = await this.ctx.service.materialBills.calcMaterialMTp(transaction);
+                await transaction.commit();
+                this.ctx.material.months = material_month.join(',');
+                return m_tp;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 删除月信息价 并更新 工料平均单价、本期单价,调差金额等
+         * @return {void}
+         */
+        async del(data, monthList, mbList) {
+            if (!this.ctx.tender || !this.ctx.material) {
+                throw '数据错误';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                const material_month = this.ctx.material.months ? this.ctx.material.months.split(',') : [];
+                this._.remove(material_month, function(n) {
+                    return data.indexOf(n) !== -1;
+                });
+                await transaction.delete(this.tableName, { mid: this.ctx.material.id, yearmonth: data });
+                if (mbList.length !== 0) {
+                    const updateArray = [];
+                    for (const mb of mbList) {
+                        if (monthList.length !== 0) {
+                            this._.remove(monthList, function(m) {
+                                return data.indexOf(m.yearmonth) !== -1;
+                            });
+                            const mb_msg_tp_sum = this._.sumBy(this._.filter(monthList, { mb_id: mb.id }), 'msg_tp');
+                            const new_msg_tp = material_month.length !== 0 ? this.ctx.helper.round(this.ctx.helper.div(mb_msg_tp_sum, material_month.length), 3) : null;
+                            const [newmsg_spread, newm_spread] = await this.ctx.service.materialBills.getSpread(mb, new_msg_tp);
+                            updateArray.push({
+                                id: mb.id,
+                                msg_tp: new_msg_tp,
+                                msg_spread: newmsg_spread,
+                                m_spread: newm_spread,
+                                m_tp: this.ctx.helper.round(this.ctx.helper.mul(mb.quantity, newm_spread), 2),
+                            });
+                        }
+                    }
+                    if (updateArray.length !== 0) await transaction.updateRows(this.ctx.service.materialBills.tableName, updateArray);
+                }
+                await transaction.update(this.ctx.service.material.tableName, { id: this.ctx.material.id, months: material_month.join(',') });
+                const m_tp = await this.ctx.service.materialBills.calcMaterialMTp(transaction);
+                await transaction.commit();
+                this.ctx.material.months = material_month.join(',');
+                return m_tp;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 修改月信息价值 并更新 本期单价,调差金额等
+         * @return {void}
+         */
+        async save(data) {
+            if (!this.ctx.tender || !this.ctx.material) {
+                throw '数据错误';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                const material_month = this.ctx.material.months ? this.ctx.material.months.split(',') : [];
+                await transaction.update(this.tableName, { msg_tp: data.value }, { where: { mb_id: data.mb_id, yearmonth: data.yearmonth } });
+                const monthList = await transaction.select(this.tableName, { where: { mb_id: data.mb_id } });
+                const mbInfo = await transaction.get(this.ctx.service.materialBills.tableName, { id: data.mb_id });
+                if (monthList.length !== 0) {
+                    const mb_msg_tp_sum = this._.sumBy(monthList, 'msg_tp');
+                    const new_msg_tp = this.ctx.helper.round(this.ctx.helper.div(mb_msg_tp_sum, material_month.length), 3);
+                    const [newmsg_spread, newm_spread] = await this.ctx.service.materialBills.getSpread(mbInfo, new_msg_tp);
+                    await transaction.update(this.ctx.service.materialBills.tableName, {
+                        id: mbInfo.id,
+                        msg_tp: new_msg_tp,
+                        msg_spread: newmsg_spread,
+                        m_spread: newm_spread,
+                        m_tp: this.ctx.helper.round(this.ctx.helper.mul(mbInfo.quantity, newm_spread), 2),
+                    });
+                }
+                const m_tp = await this.ctx.service.materialBills.calcMaterialMTp(transaction);
+                await transaction.commit();
+                return m_tp;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 修改多个月信息价值 并更新 本期单价,调差金额等
+         * @return {void}
+         */
+        async saveDatas(datas, mbList) {
+            if (!this.ctx.tender || !this.ctx.material) {
+                throw '数据错误';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                const material_month = this.ctx.material.months ? this.ctx.material.months.split(',') : [];
+                const updateArray = [];
+                for (const data of datas) {
+                    for (const m of material_month) {
+                        const one_update = {
+                            row: {
+                                msg_tp: data[m],
+                            },
+                            where: {
+                                mb_id: data.mb_id,
+                                yearmonth: m,
+                            },
+                        };
+                        updateArray.push(one_update);
+                    }
+                }
+                await transaction.updateRows(this.tableName, updateArray);
+                const monthList = await transaction.select(this.tableName, { where: { mid: this.ctx.material.id } });
+                if (mbList.length !== 0) {
+                    const mbUpdateArray = [];
+                    for (const mb of mbList) {
+                        const mb_msg_tp_sum = this._.sumBy(this._.filter(monthList, { mb_id: mb.id }), 'msg_tp');
+                        const new_msg_tp = material_month.length !== 0 ? this.ctx.helper.round(this.ctx.helper.div(mb_msg_tp_sum, material_month.length), 3) : null;
+                        const [newmsg_spread, newm_spread] = await this.ctx.service.materialBills.getSpread(mb, new_msg_tp);
+                        mbUpdateArray.push({
+                            id: mb.id,
+                            msg_tp: new_msg_tp,
+                            msg_spread: newmsg_spread,
+                            m_spread: newm_spread,
+                            m_tp: this.ctx.helper.round(this.ctx.helper.mul(mb.quantity, newm_spread), 2),
+                        });
+                    }
+                    if (mbUpdateArray.length !== 0) await transaction.updateRows(this.ctx.service.materialBills.tableName, mbUpdateArray);
+                }
+                const m_tp = await this.ctx.service.materialBills.calcMaterialMTp(transaction);
+                await transaction.commit();
+                return m_tp;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+    }
+
+    return MaterialMonth;
+};

+ 37 - 37
app/service/measure_audit.js

@@ -29,7 +29,7 @@ module.exports = app => {
          * @param {uuid} mid - 中间计量id
          * @param {Number} auditorId - 审核人id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getAuditor(mid, auditorId, times = 1) {
             const sql = 'SELECT ma.*, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone` ' +
@@ -45,7 +45,7 @@ module.exports = app => {
          *
          * @param {uuid} mid - 中间计量id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getAuditors(mid, times = 1) {
             const sql = 'SELECT ma.*, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone` ' +
@@ -61,7 +61,7 @@ module.exports = app => {
          *
          * @param {uuid} mid - 中间计量id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getCurAuditor(mid, times = 1) {
             const sql = 'SELECT ma.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, ma.`times`, ma.`order`, ma.`status`, ma.`opinion`, ma.`begin_time`, ma.`end_time` ' +
@@ -77,7 +77,7 @@ module.exports = app => {
          *
          * @param {uuid} mid - 中间计量id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<number>}
+         * @return {Promise<number>}
          */
         async getNewOrder(mid, times = 1) {
             const sql = 'SELECT Max(??) As max_order FROM ?? Where `mid` = ? and `times` = ? and `order` > 0';
@@ -93,15 +93,15 @@ module.exports = app => {
          * @param {uuid} mid - 中间计量id
          * @param {Number} auditorId - 审核人id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<number>}
+         * @return {Promise<number>}
          */
         async addAuditor(tenderId, mid, auditorId, times = 1) {
             const newOrder = await this.getNewOrder(mid, times);
             const data = {
                 tender_id: tenderId,
-                mid: mid,
+                mid,
                 audit_id: auditorId,
-                times: times,
+                times,
                 order: newOrder,
                 status: auditConst.status.uncheck,
             };
@@ -115,14 +115,14 @@ module.exports = app => {
          * @param {uuid} mid - 中间计量id
          * @param {Number} auditorId - 审核人id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          * @private
          */
         async _syncOrderByDelete(transaction, mid, order, times) {
             this.initSqlBuilder();
             this.sqlBuilder.setAndWhere('mid', {
                 value: this.db.escape(mid),
-                operate: '='
+                operate: '=',
             });
             this.sqlBuilder.setAndWhere('order', {
                 value: order,
@@ -148,12 +148,12 @@ module.exports = app => {
          * @param {uuid} mid - 中间计量id
          * @param {Number} auditorId - 审核人id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<boolean>}
+         * @return {Promise<boolean>}
          */
         async deleteAuditor(mid, auditorId, times = 1) {
             const transaction = await this.db.beginTransaction();
             try {
-                const condition = {mid: mid, audit_id: auditorId, times: times};
+                const condition = { mid, audit_id: auditorId, times };
                 const auditor = await this.getDataByCondition(condition);
                 if (!auditor) {
                     throw '该审核人不存在';
@@ -161,7 +161,7 @@ module.exports = app => {
                 await this._syncOrderByDelete(transaction, mid, auditor.order, times);
                 await transaction.delete(this.tableName, condition);
                 await transaction.commit();
-            } catch(err) {
+            } catch (err) {
                 await transaction.rollback();
                 throw err;
             }
@@ -169,7 +169,7 @@ module.exports = app => {
         }
 
         _sumBills(bills) {
-            const result = {deal: 0, qc: 0};
+            const result = { deal: 0, qc: 0 };
             for (const b of bills) {
                 result.deal = result.deal + (b.deal_totalprice ? b.deal_totalprice : 0);
                 result.qc = result.qc + (b.qc_totalprice ? b.qc_totalprice : 0);
@@ -182,13 +182,13 @@ module.exports = app => {
          * @param transacation - 事务
          * @param {uuid} mid - 中间计量
          * @param {Number} times - 次数
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          * @private
          */
         async _copyReportBillsHistory(transacation, mid, times) {
-            const bills = await this.ctx.service.measureBills.getAllDataByCondition({ where: {mid: mid}});
+            const bills = await this.ctx.service.measureBills.getAllDataByCondition({ where: { mid } });
             const sum = this._sumBills(bills);
-            const history = await this.getDataByCondition({mid: mid, times: times, order: 0});
+            const history = await this.getDataByCondition({ mid, times, order: 0 });
             if (history) {
                 await transacation.update(this.tableName, {
                     id: history.id,
@@ -197,11 +197,11 @@ module.exports = app => {
                     qc_sum: sum.qc,
                 });
             } else {
-                const measure = await this.ctx.service.measure.getDataByCondition({mid: mid});
+                const measure = await this.ctx.service.measure.getDataByCondition({ mid });
                 await transacation.insert(this.tableName, {
                     tender_id: measure.tender_id,
-                    mid: mid,
-                    times: times,
+                    mid,
+                    times,
                     order: 0,
                     audit_id: measure.user_id,
                     status: 0,
@@ -217,10 +217,10 @@ module.exports = app => {
          *
          * @param {uuid} mid - 中间计量id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<boolean>}
+         * @return {Promise<boolean>}
          */
         async start(mid, times = 1) {
-            const audit = await this.getDataByCondition({mid: mid, times: times, order: 1});
+            const audit = await this.getDataByCondition({ mid, times, order: 1 });
             if (!audit) {
                 throw '审核人信息错误';
             }
@@ -237,13 +237,13 @@ module.exports = app => {
                 });
                 // 改变中间计量状态
                 await transaction.update(this.ctx.service.measure.tableName, {
-                    times: times,
+                    times,
                     status: auditConst.status.checking,
                     deal_sum: sum.deal,
                     qc_sum: sum.qc,
-                }, {where: {mid: mid}});
+                }, { where: { mid } });
                 await transaction.commit();
-            } catch(err) {
+            } catch (err) {
                 await transaction.rollback();
                 throw err;
             }
@@ -255,7 +255,7 @@ module.exports = app => {
          * @param {uuid} mid - 中间计量id
          * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
          * @param {Number} times - 第几次审批
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async check(mid, checkType, opinion, times = 1) {
             if (checkType !== auditConst.status.checked && checkType !== auditConst.status.checkNo) {
@@ -266,17 +266,17 @@ module.exports = app => {
             try {
                 // 整理当前流程审核人状态更新
                 const time = new Date();
-                const audit = await this.getDataByCondition({mid: mid, times: times, status: auditConst.status.checking});
+                const audit = await this.getDataByCondition({ mid, times, status: auditConst.status.checking });
                 if (!audit) {
                     throw '审核数据错误';
                 }
                 // 更新当前审核流程
-                const bills = await this.ctx.service.measureBills.getAllDataByCondition({ where: {mid: mid}});
+                const bills = await this.ctx.service.measureBills.getAllDataByCondition({ where: { mid } });
                 const sum = this._sumBills(bills);
                 await transaction.update(this.tableName, {
                     id: audit.id,
                     status: checkType,
-                    opinion: opinion,
+                    opinion,
                     end_time: time,
                     bills: JSON.stringify(bills),
                     deal_sum: sum.deal,
@@ -284,22 +284,22 @@ module.exports = app => {
                 });
                 const measureTable = this.ctx.service.measure.tableName;
                 if (checkType === auditConst.status.checked) {
-                    const nextAudit = await this.getDataByCondition({mid: mid, times: times, order: audit.order + 1});
+                    const nextAudit = await this.getDataByCondition({ mid, times, order: audit.order + 1 });
                     // 无下一审核人表示,审核结束
                     if (nextAudit) {
-                        await transaction.update(this.tableName, {id: nextAudit.id, status: auditConst.status.checking, begin_time: time});
-                        await transaction.update(measureTable, {deal_sum: sum.deal, qc_sum: sum.qc,}, {where: {mid: mid}});
+                        await transaction.update(this.tableName, { id: nextAudit.id, status: auditConst.status.checking, begin_time: time });
+                        await transaction.update(measureTable, { deal_sum: sum.deal, qc_sum: sum.qc }, { where: { mid } });
                     } else {
                         // 同步标段信息
-                        await transaction.update(measureTable, {status: checkType, deal_sum: sum.deal, qc_sum: sum.qc,}, {where: {mid: mid}});
+                        await transaction.update(measureTable, { status: checkType, deal_sum: sum.deal, qc_sum: sum.qc }, { where: { mid } });
                     }
                 } else {
                     // 同步标段信息
-                    await transaction.update(measureTable, {times: times+1, status: checkType, deal_sum: sum.deal, qc_sum: sum.qc,}, {where: {mid: mid}});
+                    await transaction.update(measureTable, { times: times + 1, status: checkType, deal_sum: sum.deal, qc_sum: sum.qc }, { where: { mid } });
                     // 拷贝新一次审核流程列表
                     const auditors = await this.getAllDataByCondition({
-                        where: {mid: mid, times: times},
-                        columns: ['tender_id', 'order', 'audit_id', 'mid']
+                        where: { mid, times },
+                        columns: ['tender_id', 'order', 'audit_id', 'mid'],
                     });
                     for (const a of auditors) {
                         a.times = times + 1;
@@ -319,7 +319,7 @@ module.exports = app => {
          * 获取审核人需要审核的标段列表
          *
          * @param auditorId
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getAuditTender(auditorId) {
             const sql = 'SELECT ma.`audit_id`, ma.`times`, ma.`order`, ma.`begin_time`, t.`id`, t.`name`, t.`project_id`, t.`type`, t.`user_id` ' +
@@ -332,4 +332,4 @@ module.exports = app => {
     }
 
     return MeasureAudit;
-};
+};

+ 22 - 22
app/service/measure_bills.js

@@ -28,7 +28,7 @@ module.exports = app => {
          *
          * @param {Number} tenderId - 标段id
          * @param {uuid} mid - 中间计量id
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getBillsDetail(tenderId, mid) {
             const sql = 'SELECT MB.`tender_id`, MB.`mid`, MB.`bid`, L.`code`, L.`name`, L.`full_path` ' +
@@ -44,7 +44,7 @@ module.exports = app => {
          * @param tenderId
          * @param mid
          * @param ids
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          * @private
          */
         async _getUnionBillsData(tenderId, mid, ids) {
@@ -59,13 +59,13 @@ module.exports = app => {
             const sqlParam = [this.ctx.service.ledger.tableName, this.tableName, tenderId, tenderId, mid,
                 this.ctx.service.ledger.tableName, tenderId];
             return await this.db.query(sql, sqlParam);
-        };
+        }
 
         /**
          * 查询中间计量下清单(含父项)
          * @param {Number} tenderId - 标段id
          * @param {uuid} mid - 中间计量id
-         * @returns {Promise<Array>}
+         * @return {Promise<Array>}
          */
         async getBillsDetailWithParent(tenderId, mid) {
             const bills = await this.getBillsDetail(tenderId, mid);
@@ -75,10 +75,10 @@ module.exports = app => {
                     ids = ids.concat(b.full_path.split('.'));
                 }
                 return await this._getUnionBillsData(tenderId, mid, ids);
-                //return await this.ctx.service.ledger.getDataByNodeIds(tenderId, ids);
-            } else {
-                return [];
+                // return await this.ctx.service.ledger.getDataByNodeIds(tenderId, ids);
             }
+            return [];
+
         }
 
         /**
@@ -86,7 +86,7 @@ module.exports = app => {
          * @param {Number} tenderId - 标段id
          * @param {uuid} mid - 计量id
          * @param {Number} bid - 清单id
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getBillsData(tenderId, mid, bid) {
             const sql = 'SELECT MB.`tender_id`, MB.`mid`, MB.`bid`, L.`code`, L.`name`, L.`full_path` ' +
@@ -102,13 +102,13 @@ module.exports = app => {
          * @param {Number} tenderId - 标段id
          * @param {uuid} mid - 计量id
          * @param {Number} bid - 清单id
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getBillsDataWithParent(tenderId, mid, bid) {
             const bills = await this.getBillsData(tenderId, mid, bid);
             const ids = bills.full_path.split('.');
             return await this._getUnionBillsData(tenderId, mid, ids);
-            //return await this.ctx.service.ledger.getDataByNodeIds(tenderId, bills.full_path.split('.'));
+            // return await this.ctx.service.ledger.getDataByNodeIds(tenderId, bills.full_path.split('.'));
         }
 
         /**
@@ -116,13 +116,13 @@ module.exports = app => {
          * @param {Number} tenderId - 标段id
          * @param {uuid} mid - 计量id
          * @param {Number} bid - 清单id
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async addBills(tenderId, mid, bid) {
             const result = await this.db.insert(this.tableName, {
                 tender_id: tenderId,
-                mid: mid,
-                bid: bid,
+                mid,
+                bid,
                 in_time: new Date(),
                 in_user: this.ctx.session.sessionUser.accountId,
             });
@@ -133,13 +133,13 @@ module.exports = app => {
          * @param {Number} tenderId - 标段id
          * @param {uuid} mid - 计量id
          * @param {Number} bid - 清单id
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async removeBills(tenderId, mid, bid) {
             await this.db.delete(this.tableName, {
                 tender_id: tenderId,
-                mid: mid,
-                bid: bid,
+                mid,
+                bid,
             });
         }
 
@@ -149,7 +149,7 @@ module.exports = app => {
          * @param {uuid} mid - 计量id
          * @param {Number} bid - 清单id
          * @param {Object} data - 更新数据
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async updateBills(tenderId, mid, bid, data) {
             const bills = await this.ctx.service.ledger.getDataByNodeId(tenderId, bid);
@@ -162,17 +162,17 @@ module.exports = app => {
             }
             const result = await this.update(data, {
                 tender_id: tenderId,
-                mid: mid,
-                bid: bid,
+                mid,
+                bid,
             });
             if (result) {
                 data.ledger_id = bid;
                 return data;
-            } else {
-                throw '更新数据失败';
             }
+            throw '更新数据失败';
+
         }
     }
 
     return MeasureBills;
-};
+};

+ 9 - 9
app/service/measure_pos.js

@@ -28,7 +28,7 @@ module.exports = app => {
          *
          * @param {Number} tenderId - 标段id
          * @param {uuid} mid - 计量id
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async getPosDetail(tenderId, mid) {
             const sql = 'SELECT MP.`tender_id`, MP.`mid`, MP.`pid`, L.`code`, L.`name`, L.`full_path` ' +
@@ -44,7 +44,7 @@ module.exports = app => {
          * @param tenderId
          * @param mid
          * @param pid
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getPosData(tenderId, mid, pid) {
             const sql = 'SELECT MP.`tender_id`, MP.`mid`, MP.`pid`, L.`code`, L.`name`, L.`full_path` ' +
@@ -61,13 +61,13 @@ module.exports = app => {
          * @param {Number} tenderId - 标段id
          * @param {uuid} mid - 计量id
          * @param {Number} pid -- 计量范围id
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async addPos(tenderId, mid, pid) {
             const result = await this.db.insert(this.tableName, {
                 tender_id: tenderId,
-                mid: mid,
-                pid: pid,
+                mid,
+                pid,
                 in_time: new Date(),
                 in_user: this.ctx.session.sessionUser.accountId,
             });
@@ -80,16 +80,16 @@ module.exports = app => {
          * @param {Number} tenderId - 标段id
          * @param {uuid} mid - 计量id
          * @param {Number} pid -- 计量范围id
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async removePos(tenderId, mid, pid) {
             await this.db.delete(this.tableName, {
                 tender_id: tenderId,
-                mid: mid,
-                pid: pid,
+                mid,
+                pid,
             });
         }
     }
 
     return MeasurePos;
-};
+};

+ 11 - 12
app/service/notice_push.js

@@ -1,4 +1,4 @@
-'use strict';
+'use strict'
 
 /**
  * 推送表模型
@@ -9,9 +9,7 @@
  */
 
 module.exports = app => {
-
     class NoticePush extends app.BaseService {
-
         /**
          * 构造函数
          *
@@ -19,8 +17,8 @@ module.exports = app => {
          * @return {void}
          */
         constructor(ctx) {
-            super(ctx);
-            this.tableName = 'notice';
+            super(ctx)
+            this.tableName = 'notice'
         }
 
         /**
@@ -28,7 +26,7 @@ module.exports = app => {
          * @param {Number} id 推送记录id
          */
         async set(id) {
-            await this.update({ is_read: 1 }, { id });
+            await this.update({ is_read: 1 }, { id })
         }
 
         /**
@@ -37,18 +35,19 @@ module.exports = app => {
          * @param {Integer} uid - 查询人id
          */
         async getNotice(pid, uid) {
-            let notice =  await this.db.select(this.tableName, {
+            let notice = await this.db.select(this.tableName, {
                 where: { pid, uid },
                 orders: [['create_time', 'desc']],
-                limit: 10, offset: 0
-            });
+                limit: 10,
+                offset: 0
+            })
             notice = notice.map(v => {
                 const extra = JSON.parse(v.content)
                 delete v.content
                 return { ...v, ...extra }
             })
-            return notice;
+            return notice
         }
     }
-    return NoticePush;
-};
+    return NoticePush
+}

+ 6 - 3
app/service/pos.js

@@ -26,7 +26,8 @@ module.exports = app => {
             return await this.db.select(this.tableName, {
                 where: condition,
                 columns: ['id', 'tid', 'lid', 'name', 'quantity', 'position', 'drawing_code', 'sgfh_qty', 'sjcl_qty',
-                    'qtcl_qty', 'in_time', 'porder', 'add_stage', 'sgfh_expr', 'sjcl_expr', 'qtcl_expr', 'real_qty'],
+                    'qtcl_qty', 'in_time', 'porder', 'add_stage', 'sgfh_expr', 'sjcl_expr', 'qtcl_expr', 'real_qty',
+                    'dagl_status', 'gxby_status'],
                 order: [['porder', 'ASC']],
             });
         }
@@ -34,7 +35,7 @@ module.exports = app => {
         async getPosDataWithAddStageOrder(condition) {
             const sql = 'SELECT p.id, p.tid, p.lid, p.name, p.quantity, p.position, p.drawing_code,' +
                 '    p.sgfh_qty, p.sjcl_qty, p.qtcl_qty, p.porder, p.add_stage, p.add_times, p.add_user, s.order As add_stage_order,' +
-                '    p.sgfh_expr, p.sjcl_expr, p.qtcl_expr, p.real_qty' +
+                '    p.sgfh_expr, p.sjcl_expr, p.qtcl_expr, p.real_qty, p.gxby_status, p.dagl_status' +
                 '  FROM ' + this.tableName + ' p ' +
                 '  LEFT JOIN ' + this.ctx.service.stage.tableName + ' s' +
                 '  ON p.add_stage = s.id'
@@ -44,7 +45,9 @@ module.exports = app => {
 
         async getPosDataByIds(ids) {
             if (ids instanceof Array && ids.length > 0) {
-                const sql = 'SELECT id, tid, lid, name, quantity, position, drawing_code, sgfh_qty, sjcl_qty, qtcl_qty, add_stage, add_times, add_user, sgfh_expr, sjcl_expr, qtcl_expr, real_qty' +
+                const sql = 'SELECT id, tid, lid, name, quantity, position, drawing_code,' +
+                    '    sgfh_qty, sjcl_qty, qtcl_qty, add_stage, add_times, add_user, sgfh_expr, sjcl_expr, qtcl_expr, real_qty,' +
+                    '    dagl_status, gxby_status' +
                     '  FROM ' + this.tableName +
                     '  WHERE id in (' + this.ctx.helper.getInArrStrSqlFilter(ids) + ')';
                 return await this.db.query(sql, []);

+ 54 - 1
app/service/project_account.js

@@ -13,6 +13,8 @@ const crypto = require('crypto');
 const SSO = require('../lib/sso');
 const SMS = require('../lib/sms');
 const SmsAliConst = require('../const/sms_alitemplate');
+const thirdPartyConst = require('../const/third_party');
+
 module.exports = app => {
 
     class ProjectAccount extends app.BaseService {
@@ -100,7 +102,7 @@ module.exports = app => {
          * 账号登录
          *
          * @param {Object} data - 表单post数据
-         * @param {Number} loginType - 登录类型 1 | 2
+         * @param {Number} loginType - 登录类型 1(sso登录) | 2(正常或副密码登录) | 3(接口登录或微信登录)
          * @return {Boolean} - 返回登录结果
          */
         async accountLogin(data, loginType) {
@@ -225,6 +227,19 @@ module.exports = app => {
                         // permission,
                         // cooperation,
                     };
+
+                    const thirdParty = await this.db.get('zh_s2b_proj', { pid: projectInfo.id });
+                    if (thirdParty) {
+                        thirdParty.gxby_option = thirdParty.gxby_option ? JSON.parse(thirdParty.gxby_option) : null;
+                        projectInfo.gxby = thirdParty.gxby;
+                        projectInfo.gxby_status = thirdParty.gxby_option && thirdParty.gxby_option.status
+                            ? thirdParty.gxby_option.status : thirdPartyConst.gxby;
+
+                        thirdParty.dagl_option = thirdParty.dagl_option ? JSON.parse(thirdParty.dagl_option): null;
+                        projectInfo.dagl = thirdParty.dagl;
+                        projectInfo.dagl_status = thirdParty.dagl_option && thirdParty.dagl_option.status
+                            ? thirdParty.dagl_option.status : thirdPartyConst.dagl;
+                    }
                     this.ctx.session.sessionProject = projectInfo;
                     this.ctx.session.sessionProjectList = projectList;
                 }
@@ -702,6 +717,44 @@ module.exports = app => {
         }
 
         /**
+         * 查询过虑
+         *
+         * @param {Object} data - 筛选表单中的get数据
+         * @return {void}
+         */
+        searchFilter(data, projectId) {
+            this.initSqlBuilder();
+            const columns = ['id', 'account', 'name', 'company', 'role', 'mobile', 'auth_mobile', 'telephone', 'enable', 'is_admin', 'account_group', 'bind'];
+            this.sqlBuilder.columns = columns;
+
+            this.sqlBuilder.setAndWhere('project_id', {
+                value: projectId,
+                operate: '=',
+            });
+
+            // 名字筛选
+            if (data.keyword !== undefined && data.keyword !== '') {
+                this.sqlBuilder.setNewOrWhere([{
+                    field: 'account',
+                    value: this.db.escape('%' + data.keyword + '%'),
+                    operate: 'like',
+                }, {
+                    field: 'name',
+                    value: this.db.escape('%' + data.keyword + '%'),
+                    operate: 'like',
+                }, {
+                    field: 'company',
+                    value: this.db.escape('%' + data.keyword + '%'),
+                    operate: 'like',
+                }, {
+                    field: 'mobile',
+                    value: this.db.escape('%' + data.keyword + '%'),
+                    operate: 'like',
+                }]);
+            }
+        }
+
+        /**
          * 账号绑定微信
          *
          * @param {String} id - 账号id

+ 2 - 2
app/service/report.js

@@ -141,11 +141,11 @@ module.exports = app => {
                             runnableKey.push(filter);
                             break;
                         case 'mem_material':
-                            runnableRst.push(service.rptGatherMemory.getMaterial(params.tender_id, params.material_order, memFieldKeys[filter]));
+                            runnableRst.push(service.reportMemory.getMaterial(params.tender_id, params.material_order, memFieldKeys[filter]));
                             runnableKey.push(filter);
                             break;
                         case 'mem_material_gl':
-                            runnableRst.push(service.rptGatherMemory.getMaterialGl(params.tender_id, params.material_order, memFieldKeys[filter]));
+                            runnableRst.push(service.reportMemory.getMaterialGl(params.tender_id, params.material_order, memFieldKeys[filter]));
                             runnableKey.push(filter);
                             break;
                         case 'mem_stage_sum_bills':

+ 265 - 99
app/service/revise_audit.js

@@ -11,6 +11,7 @@
 const auditConst = require('../const/audit').revise;
 const smsTypeConst = require('../const/sms_type');
 const SmsAliConst = require('../const/sms_alitemplate');
+const wxConst = require('../const/wechat_template');
 const pushType = require('../const/audit').pushType;
 
 module.exports = app => {
@@ -32,10 +33,11 @@ module.exports = app => {
          * @param {Number} reviseId - 修订id
          * @param {Number} auditorId - 审核人id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getAuditor(reviseId, auditorId, times = 1) {
-            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
                 'FROM ?? AS la, ?? AS pa ' +
                 'WHERE la.`rid` = ? and la.`audit_id` = ? and la.`times` = ?' +
                 '    and la.`audit_id` = pa.`id`';
@@ -48,13 +50,18 @@ module.exports = app => {
          *
          * @param {Number} reviseId - 修订id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getAuditors(reviseId, times = 1) {
-            const sql = 'SELECT la.`audit_id`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`,' +
+            const sql =
+                'SELECT la.`audit_id`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`,' +
                 '    pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`' +
-                '  FROM ' + this.tableName + ' AS la ' +
-                '  INNER JOIN ' + this.ctx.service.projectAccount.tableName + ' AS pa ON la.`rid` = ? and la.`times` = ? and la.`audit_id` = pa.`id`' +
+                '  FROM ' +
+                this.tableName +
+                ' AS la ' +
+                '  INNER JOIN ' +
+                this.ctx.service.projectAccount.tableName +
+                ' AS pa ON la.`rid` = ? and la.`times` = ? and la.`audit_id` = pa.`id`' +
                 '  ORDER BY la.`audit_order`';
             const sqlParam = [reviseId, times];
             return await this.db.query(sql, sqlParam);
@@ -65,10 +72,11 @@ module.exports = app => {
          *
          * @param {Number} rid - 修订id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getAuditors2ReviseList(rid, times = 1) {
-            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
                 'FROM ?? AS la, ?? AS pa, (SELECT `audit_id`,(@i:=@i+1) as `sort` FROM ??, (select @i:=0) as it WHERE `rid` = ? AND `times` = ? GROUP BY `audit_id`) as g ' +
                 'WHERE la.`rid` = ? and la.`times` = ? and la.`audit_id` = pa.`id` and g.`audit_id` = la.`audit_id` order by la.`audit_order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, rid, times, rid, times];
@@ -87,10 +95,11 @@ module.exports = app => {
          *
          * @param {Number} reviseId - 修订id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getCurAuditor(reviseId, times = 1) {
-            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
                 'FROM ?? AS la, ?? AS pa ' +
                 'WHERE la.`rid` = ? and la.`status` = ? and la.`times` = ?' +
                 '    and la.`audit_id` = pa.`id`';
@@ -103,7 +112,7 @@ module.exports = app => {
          *
          * @param {Number} reviseId - 修订id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<number>}
+         * @return {Promise<number>}
          */
         async getNewOrder(reviseId, times = 1) {
             const sql = 'SELECT Max(??) As max_order FROM ?? Where `rid` = ? and `times` = ?';
@@ -118,7 +127,7 @@ module.exports = app => {
          * @param {Object} revise - 修订
          * @param {Number} auditorId - 审核人id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<number>}
+         * @return {Promise<number>}
          */
         async addAuditor(revise, auditorId) {
             const times = revise.times ? revise.times : 1;
@@ -126,14 +135,14 @@ module.exports = app => {
             const data = {
                 tender_id: revise.tid,
                 audit_id: auditorId,
-                times: times,
+                times,
                 audit_order: newOrder,
                 status: auditConst.status.uncheck,
                 rid: revise.id,
                 in_time: new Date(),
             };
             const result = await this.db.insert(this.tableName, data);
-            return result.effectRows = 1;
+            return (result.effectRows = 1);
         }
 
         /**
@@ -142,14 +151,14 @@ module.exports = app => {
          * @param {Number} reviseId - 修订id
          * @param {Number} auditorId - 审核人id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          * @private
          */
         async _syncOrderByDelete(transaction, reviseId, order, times) {
             this.initSqlBuilder();
             this.sqlBuilder.setAndWhere('rid', {
                 value: this.db.escape(reviseId),
-                operate: '='
+                operate: '=',
             });
             this.sqlBuilder.setAndWhere('audit_order', {
                 value: order,
@@ -175,13 +184,13 @@ module.exports = app => {
          * @param {Object} revise - 修订
          * @param {Number} auditorId - 审核人id
          * @param {Number} times - 第几次审批
-         * @returns {Promise<boolean>}
+         * @return {Promise<boolean>}
          */
         async deleteAuditor(revise, auditorId) {
             const times = revise.times ? revise.times : 1;
             const transaction = await this.db.beginTransaction();
             try {
-                const condition = {rid: revise.id, audit_id: auditorId, times: times};
+                const condition = { rid: revise.id, audit_id: auditorId, times };
                 const auditor = await this.getDataByCondition(condition);
                 if (!auditor) {
                     throw '该审核人不存在';
@@ -189,7 +198,7 @@ module.exports = app => {
                 await this._syncOrderByDelete(transaction, revise.id, auditor.audit_order, times);
                 await transaction.delete(this.tableName, condition);
                 await transaction.commit();
-            } catch(err) {
+            } catch (err) {
                 await transaction.rollback();
                 throw err;
             }
@@ -201,10 +210,10 @@ module.exports = app => {
          *
          * @param {Object} revise - 修订
          * @param {Number} times - 第几次审批
-         * @returns {Promise<boolean>}
+         * @return {Promise<boolean>}
          */
         async start(revise, times = 1) {
-            const audit = await this.getDataByCondition({rid: revise.id, times: times, audit_order: 1});
+            const audit = await this.getDataByCondition({ rid: revise.id, times, audit_order: 1 });
             if (!audit) throw '审核人信息错误';
             const time = new Date();
 
@@ -214,12 +223,17 @@ module.exports = app => {
             const transaction = await this.db.beginTransaction();
             try {
                 await transaction.update(this.tableName, {
-                    id: audit.id, status: auditConst.status.checking, begin_time: time,
-                    bills_file: revise.bills_file, pos_file: revise.pos_file,
+                    id: audit.id,
+                    status: auditConst.status.checking,
+                    begin_time: time,
+                    bills_file: revise.bills_file,
+                    pos_file: revise.pos_file,
                 });
                 const reviseData = {
-                    id: revise.id, status: auditConst.status.checking,
-                    bills_file: billsHis, pos_file: posHis,
+                    id: revise.id,
+                    status: auditConst.status.checking,
+                    bills_file: billsHis,
+                    pos_file: posHis,
                 };
                 if (revise.times === 1) {
                     reviseData.begin_time = time;
@@ -230,16 +244,27 @@ module.exports = app => {
                 // 下一人
                 // await this.ctx.helper.sendUserSms(audit.audit_id, smsTypeConst.const.XD,
                 //     smsTypeConst.judge.approval.toString(), '台帐修订需要您审批,请登录系统处理。');
-                await this.ctx.helper.sendAliSms(audit.audit_id, smsTypeConst.const.XD,
-                    smsTypeConst.judge.approval.toString(), SmsAliConst.template.revise_check);
+                await this.ctx.helper.sendAliSms(audit.audit_id, smsTypeConst.const.XD, smsTypeConst.judge.approval.toString(), SmsAliConst.template.revise_check);
+                // 微信模板通知
+                const wechatData = {
+                    status: wxConst.status.check,
+                    tips: wxConst.tips.check,
+                    begin_time: Date.parse(time),
+                };
+                await this.ctx.helper.sendWechat(audit.audit_id, smsTypeConst.const.XD, smsTypeConst.judge.approval.toString(), wxConst.template.revise, wechatData);
                 // 其他参与人
-                const auditList = await this.getAuditors(revise.tid, times);
+                const auditList = await this.getAuditors(revise.id, times);
                 const users = this._.pull(this._.map(auditList, 'user_id'), audit.id);
                 // await this.ctx.helper.sendUserSms(users, smsTypeConst.const.XD,
                 //     smsTypeConst.judge.result.toString(), '台账修订已上报。');
-                await this.ctx.helper.sendAliSms(users, smsTypeConst.const.XD,
-                    smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_report);
-
+                await this.ctx.helper.sendAliSms(users, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_report);
+                // 微信模板通知
+                const wechatData2 = {
+                    status: wxConst.status.report,
+                    tips: wxConst.tips.report,
+                    begin_time: Date.parse(time),
+                };
+                await this.ctx.helper.sendWechat(audit.audit_id, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), wxConst.template.revise, wechatData2);
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
@@ -250,28 +275,34 @@ module.exports = app => {
 
         async _replaceLedgerByRevise(transaction, revise) {
             const sqlParam = [revise.tid];
-            await transaction.delete(this.ctx.service.ledger.tableName, {tender_id: revise.tid});
-            const bSql = 'Insert Into ' + this.ctx.service.ledger.tableName +
+            await transaction.delete(this.ctx.service.ledger.tableName, { tender_id: revise.tid });
+            const bSql =
+                'Insert Into ' +
+                this.ctx.service.ledger.tableName +
                 '  (id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
                 '     quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
                 '     sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id, is_tp,' +
-                '     sgfh_expr, sjcl_expr, qtcl_expr)' +
+                '     sgfh_expr, sjcl_expr, qtcl_expr, gxby_status, dagl_status)' +
                 '  Select id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
                 '      quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
                 '      sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id, is_tp, ' +
-                '      sgfh_expr, sjcl_expr, qtcl_expr' +
-                '  From ' + this.ctx.service.reviseBills.tableName +
+                '      sgfh_expr, sjcl_expr, qtcl_expr, gxby_status, dagl_status' +
+                '  From ' +
+                this.ctx.service.reviseBills.tableName +
                 '  Where `tender_id` = ?';
             await transaction.query(bSql, sqlParam);
-            await transaction.delete(this.ctx.service.pos.tableName, {tid: revise.tid});
-            const pSql = 'Insert Into ' + this.ctx.service.pos.tableName +
+            await transaction.delete(this.ctx.service.pos.tableName, { tid: revise.tid });
+            const pSql =
+                'Insert Into ' +
+                this.ctx.service.pos.tableName +
                 '  (id, tid, lid, name, drawing_code, quantity, add_stage, add_times, add_user,' +
                 '     sgfh_qty, sjcl_qty, qtcl_qty, crid, porder, position, ' +
-                '     sgfh_expr, sjcl_expr, qtcl_expr)' +
+                '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty, gxby_status, dagl_status)' +
                 '  Select id, tid, lid, name, drawing_code, quantity, add_stage, add_times, add_user,' +
                 '     sgfh_qty, sjcl_qty, qtcl_qty, crid, porder, position,' +
-                '     sgfh_expr, sjcl_expr, qtcl_expr' +
-                '  From ' + this.ctx.service.revisePos.tableName +
+                '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty, gxby_status, dagl_status' +
+                '  From ' +
+                this.ctx.service.revisePos.tableName +
                 '  Where `tid` = ?';
             await transaction.query(pSql, sqlParam);
         }
@@ -282,77 +313,156 @@ module.exports = app => {
          * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
          * @param {String} opinion - 审批意见
          * @param {Number} times - 第几次审批
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async check(revise, checkType, opinion, times = 1) {
             if (checkType !== auditConst.status.checked && checkType !== auditConst.status.checkNo) throw '提交数据错误';
-            const audit = await this.getDataByCondition({rid: revise.id, times: times, status: auditConst.status.checking});
+            const audit = await this.getDataByCondition({
+                rid: revise.id,
+                times,
+                status: auditConst.status.checking,
+            });
             if (!audit) throw '审核数据错误';
             const pid = this.ctx.session.sessionProject.id;
-            const noticeContent = await this.getNoticeContent(pid, audit.tender_id, audit.rid)
             const transaction = await this.db.beginTransaction();
             try {
+                const auditList = await this.getAuditors(revise.id, times);
+                // 审核通过添加到推送表
+                const noticeContent = await this.getNoticeContent(pid, audit.tender_id, audit.rid, audit.audit_id);
+                const records = [
+                    {
+                        pid,
+                        type: pushType.revise,
+                        uid: revise.uid,
+                        status: checkType,
+                        content: noticeContent,
+                    },
+                ];
+                auditList.forEach(audit => {
+                    records.push({
+                        pid,
+                        type: pushType.revise,
+                        uid: audit.audit_id,
+                        status: checkType,
+                        content: noticeContent,
+                    });
+                });
+                await transaction.insert('zh_notice', records);
+
                 // 整理当前流程审核人状态更新
                 const time = new Date();
                 // 更新当前审核流程
                 await transaction.update(this.tableName, {
-                    id: audit.id, status: checkType, opinion, end_time: time,
+                    id: audit.id,
+                    status: checkType,
+                    opinion,
+                    end_time: time,
                 });
                 if (checkType === auditConst.status.checked) {
-                    const nextAudit = await this.getDataByCondition({ rid: revise.id, times, audit_order: audit.audit_order + 1 });
+                    const nextAudit = await this.getDataByCondition({
+                        rid: revise.id,
+                        times,
+                        audit_order: audit.audit_order + 1,
+                    });
                     // 无下一审核人表示,审核结束
                     if (nextAudit) {
-                        await transaction.update(this.tableName, { id: nextAudit.id, status: auditConst.status.checking, begin_time: time });
+                        await transaction.update(this.tableName, {
+                            id: nextAudit.id,
+                            status: auditConst.status.checking,
+                            begin_time: time,
+                        });
 
                         // 短信通知-需要审批提醒功能
                         // 下一人
                         // await this.ctx.helper.sendUserSms(nextAudit.user_id, smsTypeConst.const.XD,
                         //     smsTypeConst.judge.approval.toString(), '台帐修订需要您审批,请登录系统处理。');
-                        await this.ctx.helper.sendAliSms(nextAudit.user_id, smsTypeConst.const.XD,
-                            smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result, { status: SmsAliConst.status.success });
+                        await this.ctx.helper.sendAliSms(nextAudit.user_id, smsTypeConst.const.XD, smsTypeConst.judge.approval.toString(), SmsAliConst.template.revise_check);
+                        // 微信模板通知
+                        const wechatData = {
+                            status: wxConst.status.check,
+                            tips: wxConst.tips.check,
+                            begin_time: Date.parse(revise.begin_time),
+                        };
+                        await this.ctx.helper.sendWechat(nextAudit.user_id, smsTypeConst.const.XD, smsTypeConst.judge.approval.toString(), wxConst.template.revise, wechatData);
                         // 其他参与人
-                        const auditList = await this.getAuditors(revise.tid, times);
                         const users = this._.pull(this._.map(auditList, 'user_id'), audit.id);
                         users.push(revise.uid);
                         // await this.ctx.helper.sendUserSms(users, smsTypeConst.const.XD,
                         //     smsTypeConst.judge.result.toString(), '台账修订审批通过。');
-                        await this.ctx.helper.sendAliSms(users, smsTypeConst.const.XD,
-                            smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result2, { status: SmsAliConst.status.success });
+                        await this.ctx.helper.sendAliSms(users, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result2, {
+                            status: SmsAliConst.status.success,
+                        });
+                        // 微信模板通知
+                        const wechatData2 = {
+                            status: wxConst.status.success,
+                            tips: wxConst.tips.success,
+                            begin_time: Date.parse(revise.begin_time),
+                        };
+                        await this.ctx.helper.sendWechat(users, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), wxConst.template.revise, wechatData2);
                     } else {
                         // 同步修订信息
-                        await transaction.update(this.ctx.service.ledgerRevise.tableName, { id: revise.id, status: checkType, end_time: time });
+                        await transaction.update(this.ctx.service.ledgerRevise.tableName, {
+                            id: revise.id,
+                            status: checkType,
+                            end_time: time,
+                        });
                         // 最新一期跟台账相关的缓存数据应过期
                         const lastStage = await this.ctx.service.stage.getLastestStage(revise.tid, true);
                         const cacheTime = new Date();
-                        if (lastStage) await transaction.update(this.ctx.service.stage.tableName,
-                            {id: lastStage.id, cache_time_l: cacheTime, cache_time_r: cacheTime});
+                        if (lastStage) {
+                            await transaction.update(this.ctx.service.stage.tableName, {
+                                id: lastStage.id,
+                                cache_time_l: cacheTime,
+                                cache_time_r: cacheTime,
+                            });
+                        }
                         // 拷贝修订数据至台账
                         await this._replaceLedgerByRevise(transaction, revise);
-                        const sum = await this.ctx.service.reviseBills.addUp({tender_id: revise.tid/*, is_leaf: true*/});
+                        const sum = await this.ctx.service.reviseBills.addUp({
+                            tender_id: revise.tid, /* , is_leaf: true*/
+                        });
                         await transaction.update(this.ctx.service.tender.tableName, {
-                            id: revise.tid, total_price: sum.total_price, deal_tp: sum.deal_tp
+                            id: revise.tid,
+                            total_price: sum.total_price,
+                            deal_tp: sum.deal_tp,
                         });
-                        // 审核结束,将原报添加到推送表
-                        // const noticeContent = await this.getNoticeContent(pid, audit.tender_id, audit.rid)
-                        await transaction.insert('zh_notice', { pid, type: pushType.revise, uid: revise.uid, status: auditConst.status.checked, is_read: 0, content: noticeContent });
                         // 短信通知-审批通过提醒功能
                         // 下一人
                         // const msg = '台账修订审批通过,请登录系统处理。';
                         // await this.ctx.helper.sendUserSms(revise.uid, smsTypeConst.const.XD,
                         //     smsTypeConst.judge.result.toString(), msg);
-                        await this.ctx.helper.sendAliSms(revise.uid, smsTypeConst.const.XD,
-                            smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result, { status: SmsAliConst.status.success });
+                        await this.ctx.helper.sendAliSms(revise.uid, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result, {
+                            status: SmsAliConst.status.success,
+                        });
+                        // 微信模板通知
+                        const wechatData = {
+                            status: wxConst.status.success,
+                            tips: wxConst.tips.success,
+                            begin_time: Date.parse(revise.begin_time),
+                        };
+                        await this.ctx.helper.sendWechat(revise.uid, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), wxConst.template.revise, wechatData);
                         // 其他参与人
-                        const auditList = await this.getAuditors(revise.tid, times);
                         const users = this._.pull(this._.map(auditList, 'user_id'), audit.id);
                         // await this.ctx.helper.sendUserSms(users, smsTypeConst.const.XD,
                         //     smsTypeConst.judge.result.toString(), '台账修订审批通过。');
-                        await this.ctx.helper.sendAliSms(users, smsTypeConst.const.XD,
-                            smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result2, { status: SmsAliConst.status.success });
+                        await this.ctx.helper.sendAliSms(users, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result2, {
+                            status: SmsAliConst.status.success,
+                        });
+                        // 微信模板通知
+                        const wechatData2 = {
+                            status: wxConst.status.success,
+                            tips: wxConst.tips.success,
+                            begin_time: Date.parse(revise.begin_time),
+                        };
+                        await this.ctx.helper.sendWechat(users, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), wxConst.template.revise, wechatData2);
                     }
                 } else {
                     // 同步修订信息
-                    await transaction.update(this.ctx.service.ledgerRevise.tableName, { id: revise.id, times: times + 1, status: checkType });
+                    await transaction.update(this.ctx.service.ledgerRevise.tableName, {
+                        id: revise.id,
+                        times: times + 1,
+                        status: checkType,
+                    });
                     // 拷贝新一次审核流程列表
                     const auditors = await this.getAllDataByCondition({
                         where: { rid: revise.id, times },
@@ -369,16 +479,35 @@ module.exports = app => {
                     // 下一人
                     // await this.ctx.helper.sendUserSms(revise.uid, smsTypeConst.const.XD,
                     //     smsTypeConst.judge.result.toString(), '台账修订审批退回,请登录系统处理。');
-                    await this.ctx.helper.sendAliSms(revise.uid, smsTypeConst.const.XD,
-                        smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result, { status: SmsAliConst.status.back });
+                    await this.ctx.helper.sendAliSms(revise.uid, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result, {
+                        status: SmsAliConst.status.back,
+                    });
+                    // 微信模板通知
+                    const wechatData = {
+                        status: wxConst.status.back,
+                        tips: wxConst.tips.back,
+                        begin_time: Date.parse(revise.begin_time),
+                    };
+                    await this.ctx.helper.sendWechat(revise.uid, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), wxConst.template.revise, wechatData);
                     // 其他参与人
                     // await this.ctx.helper.sendUserSms(this._.map(auditors, 'user_id'),
                     //     smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), '台账修订审批退回。');
-                    await this.ctx.helper.sendAliSms(this._.map(auditors, 'user_id'), smsTypeConst.const.XD,
-                        smsTypeConst.judge.result.toString(), SmsAliConst.template.revise_result2, { status: SmsAliConst.status.back });
-
-                    // 审核结束,将原报添加到推送表
-                    await transaction.insert('zh_notice', { pid, type: pushType.revise, uid: revise.uid, status: auditConst.status.checkNo, is_read: 0, content: noticeContent });
+                    await this.ctx.helper.sendAliSms(
+                        this._.map(auditors, 'user_id'),
+                        smsTypeConst.const.XD,
+                        smsTypeConst.judge.result.toString(),
+                        SmsAliConst.template.revise_result2,
+                        {
+                            status: SmsAliConst.status.back,
+                        }
+                    );
+                    // 微信模板通知
+                    const wechatData2 = {
+                        status: wxConst.status.back,
+                        tips: wxConst.tips.back,
+                        begin_time: Date.parse(revise.begin_time),
+                    };
+                    await this.ctx.helper.sendWechat(this._.map(auditors, 'user_id'), smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), wxConst.template.revise, wechatData2);
                 }
 
                 await transaction.commit();
@@ -392,10 +521,11 @@ module.exports = app => {
          * 获取审核人需要审核的标段列表
          *
          * @param auditorId
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getAuditRevise(auditorId) {
-            const sql = 'SELECT ra.`audit_id`, ra.`times`, ra.`audit_order`, ra.`begin_time`, ra.`end_time`,' +
+            const sql =
+                'SELECT ra.`audit_id`, ra.`times`, ra.`audit_order`, ra.`begin_time`, ra.`end_time`,' +
                 '    r.id, r.corder, r.uid, r.status, r.content,' +
                 '    t.id As t_id, t.`name` As t_name, t.`project_id` As t_pid, t.`type` As t_type, t.`user_id` As t_uid, t.`status` As t_status, ' +
                 '    p.name As audit_name, p.role As audit_role, p.company As audit_company' +
@@ -404,7 +534,7 @@ module.exports = app => {
                 '  Left Join ' + this.ctx.service.tender.tableName + ' AS t On r.tid = t.id' +
                 '  Left Join ' + this.ctx.service.projectAccount.tableName + ' As p On ra.audit_id = p.id' +
                 '  WHERE r.`valid` != 0 and ((ra.`audit_id` = ? and ra.`status` = ?) OR' +
-                '    (r.`uid` = ? and r.`status` = ? and ra.`status` = ? and ra.`times` = (r.`times`-1)))';
+                '    (r.`uid` = ? and r.`status` = ? and ra.`status` = ? and ra.`times` = (r.`times`-1))) ORDER BY ra.`begin_time` DESC';
             const sqlParam = [auditorId, auditConst.status.checking, auditorId, auditConst.status.checkNo, auditConst.status.checkNo];
             return await this.db.query(sql, sqlParam);
         }
@@ -414,7 +544,7 @@ module.exports = app => {
          * @param {Integer} pid - 项目id
          * @param {Integer} uid - 查询人id
          * @param {Date} noticeTime - 查询事件
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getNoticeRevise(pid, uid, noticeTime) {
             // const sql = 'SELECT * FROM (SELECT ra.`audit_id`, ra.`times`, ra.`audit_order`, ra.`end_time`, ra.`status`,' +
@@ -430,16 +560,17 @@ module.exports = app => {
             //     '  ORDER By new_t.`end_time`';
             // const sqlParam = [this.ctx.service.tender.tableName, auditorId, this.tableName, auditorId, noticeTime, projectId];
             // return await this.db.query(sql, sqlParam);
-            let notice =  await this.db.select('zh_notice', {
+            let notice = await this.db.select('zh_notice', {
                 where: { pid, type: pushType.revise, uid },
                 orders: [['create_time', 'desc']],
-                limit: 10, offset: 0
+                limit: 10,
+                offset: 0,
             });
             notice = notice.map(v => {
-                const extra = JSON.parse(v.content)
-                delete v.content
-                return { ...v, ...extra }
-            })
+                const extra = JSON.parse(v.content);
+                delete v.content;
+                return { ...v, ...extra };
+            });
             return notice;
         }
 
@@ -448,15 +579,17 @@ module.exports = app => {
          * @param {Number} pid 项目id
          * @param {Number} tid 台账id
          * @param {Number} rid 修订id
+         * @param {Number} uid 审核人id
          */
-        async getNoticeContent(pid, tid, rid) {
-            const noticeSql = 'SELECT * FROM (SELECT ' +
+        async getNoticeContent(pid, tid, rid, uid) {
+            const noticeSql =
+                'SELECT * FROM (SELECT ' +
                 '  t.`id` As `tid`, t.`name`, r.`corder`, pa.`name` As `su_name`, pa.role As `su_role`' +
                 '  FROM (SELECT * FROM ?? WHERE `id` = ? ) As t' +
                 '  LEFT JOIN ?? As r On r.`id` = ? ' +
-                '  LEFT JOIN ?? As pa ON t.`user_id` = pa.`id`' +
+                '  LEFT JOIN ?? As pa ON pa.`id` = ? ' +
                 '  WHERE  t.`project_id` = ? ) as new_t GROUP BY new_t.`tid`';
-            const noticeSqlParam = [this.ctx.service.tender.tableName, tid, this.ctx.service.ledgerRevise.tableName, rid, this.ctx.service.projectAccount.tableName, pid];
+            const noticeSqlParam = [this.ctx.service.tender.tableName, tid, this.ctx.service.ledgerRevise.tableName, rid, this.ctx.service.projectAccount.tableName, uid, pid];
             const content = await this.db.query(noticeSql, noticeSqlParam);
             return content.length ? JSON.stringify(content[0]) : '';
         }
@@ -474,24 +607,27 @@ module.exports = app => {
             let sql = '';
             let sqlParam = '';
             switch (status) {
-                case auditConst.status.checking :
-                case auditConst.status.checked :
-                case auditConst.status.checkNoPre :
-                    sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`rid`, la.`audit_order` ' +
+                case auditConst.status.checking:
+                case auditConst.status.checked:
+                case auditConst.status.checkNoPre:
+                    sql =
+                        'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`rid`, la.`audit_order` ' +
                         'FROM ?? AS la, ?? AS pa ' +
                         'WHERE la.`rid` = ? and la.`status` = ? and la.`audit_id` = pa.`id` order by la.`times` desc, la.`audit_order` desc';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, rid, status];
                     auditor = await this.db.queryOne(sql, sqlParam);
                     break;
-                case auditConst.status.checkNo :
-                    sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`rid`, la.`audit_order` ' +
+                case auditConst.status.checkNo:
+                    sql =
+                        'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`rid`, la.`audit_order` ' +
                         'FROM ?? AS la, ?? AS pa ' +
                         'WHERE la.`rid` = ? and la.`status` = ? and la.`times` = ? and la.`audit_id` = pa.`id` order by la.`times` desc, la.`audit_order` desc';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, rid, auditConst.status.checkNo, parseInt(times) - 1];
                     auditor = await this.db.queryOne(sql, sqlParam);
                     break;
-                case auditConst.status.uncheck :
-                default:break;
+                case auditConst.status.uncheck:
+                default:
+                    break;
             }
             return auditor;
         }
@@ -500,10 +636,11 @@ module.exports = app => {
          * 获取审核人流程列表
          *
          * @param auditorId
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getAuditGroupByList(rid, times) {
-            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`rid`, la.`audit_order` ' +
+            const sql =
+                'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`rid`, la.`audit_order` ' +
                 'FROM ?? AS la, ?? AS pa ' +
                 'WHERE la.`rid` = ? and la.`times` = ? and la.`audit_id` = pa.`id` GROUP BY la.`audit_id` ORDER BY la.`audit_order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, rid, times];
@@ -513,9 +650,38 @@ module.exports = app => {
             // return await this.db.query(sql, sqlParam);
         }
 
+        /**
+         * 获取审核人流程列表(包括原报)
+         * @param {Number} rid 修订id
+         * @param {Number} times 审核次数
+         * @return {Promise<Array>} 查询结果集(包括原报)
+         */
+        async getAuditorsWithOwner(rid, times = 1) {
+            const result = await this.getAuditGroupByList(rid, times);
+            const sql =
+                'SELECT pa.`id` As aid, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As rid, 0 As `order`' +
+                '  FROM ' +
+                this.ctx.service.ledgerRevise.tableName +
+                ' As s' +
+                '  LEFT JOIN ' +
+                this.ctx.service.projectAccount.tableName +
+                ' As pa' +
+                '  ON s.uid = pa.id' +
+                '  WHERE s.id = ?';
+            const sqlParam = [times, rid, rid];
+            const user = await this.db.queryOne(sql, sqlParam);
+            result.unshift(user);
+            return result;
+        }
+
         async getAllAuditors(tenderId) {
-            const sql = 'SELECT ra.audit_id, ra.tender_id FROM ' + this.tableName + ' ra' +
-                '  LEFT JOIN ' + this.ctx.service.tender.tableName + ' t On ra.tender_id = t.id' +
+            const sql =
+                'SELECT ra.audit_id, ra.tender_id FROM ' +
+                this.tableName +
+                ' ra' +
+                '  LEFT JOIN ' +
+                this.ctx.service.tender.tableName +
+                ' t On ra.tender_id = t.id' +
                 '  WHERE t.id = ?' +
                 '  GROUP BY ra.audit_id';
             const sqlParam = [tenderId];

+ 27 - 27
app/service/stage.js

@@ -36,8 +36,7 @@ module.exports = app => {
          */
         async getDataById(id) {
             const result = await this.db.get(this.tableName, { id });
-            if (result)
-                result.tp_history = result.tp_history ? JSON.parse(result.tp_history) : [];
+            if (result) { result.tp_history = result.tp_history ? JSON.parse(result.tp_history) : []; }
             return result;
         }
 
@@ -49,8 +48,7 @@ module.exports = app => {
          */
         async getDataByCondition(condition) {
             const result = await this.db.get(this.tableName, condition);
-            if (result)
-                result.tp_history = result.tp_history ? JSON.parse(result.tp_history) : [];
+            if (result) { result.tp_history = result.tp_history ? JSON.parse(result.tp_history) : []; }
             return result;
         }
 
@@ -73,7 +71,9 @@ module.exports = app => {
             stage.auditors = await this.ctx.service.stageAudit.getAuditors(stage.id, stage.times);
             stage.curAuditor = await this.ctx.service.stageAudit.getCurAuditor(stage.id, stage.times);
 
-            const accountId = this.ctx.session.sessionUser.accountId, auditorIds = this._.map(stage.auditors, 'aid'), shareIds = [];
+            const accountId = this.ctx.session.sessionUser.accountId,
+                auditorIds = this._.map(stage.auditors, 'aid'),
+                shareIds = [];
             const permission = this.ctx.session.sessionUser.permission;
             if (accountId === stage.user_id) { // 原报
                 if (stage.curAuditor) {
@@ -98,7 +98,7 @@ module.exports = app => {
                     stage.curOrder = this._.max(this._.map(stage.auditors, 'order'));
                 } else if (stage.status === status.checkNo) {
                     const audit = await this.service.stageAudit.getDataByCondition({
-                        sid: stage.id, times: stage.times - 1, status: status.checkNo
+                        sid: stage.id, times: stage.times - 1, status: status.checkNo,
                     });
                     stage.curOrder = audit.order;
                 } else {
@@ -125,7 +125,7 @@ module.exports = app => {
             if (!time) {
                 time = stage.in_time ? stage.in_time : new Date();
             }
-            stage.cacheTime = time.getTime();//this.ctx.stage.readOnly ? (this.ctx.stage.cache_time_r).getTime(): (this.ctx.stage.cache_time_l).getTime();
+            stage.cacheTime = time.getTime();// this.ctx.stage.readOnly ? (this.ctx.stage.cache_time_r).getTime(): (this.ctx.stage.cache_time_l).getTime();
 
             return stage;
         }
@@ -142,7 +142,7 @@ module.exports = app => {
          * 获取 最新一期 期计量
          * @param tenderId
          * @param includeUnCheck
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getLastestStage(tenderId, includeUnCheck = false) {
             this.initSqlBuilder();
@@ -166,7 +166,7 @@ module.exports = app => {
         /**
          * 获取 最新一期 审批完成的 期计量
          * @param tenderId
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getLastestCompleteStage(tenderId) {
             this.initSqlBuilder();
@@ -188,7 +188,7 @@ module.exports = app => {
         /**
          * 获取标段下的期列表(报表选择用,只需要一些key信息,不需要计算详细数据,也懒得一个个字段写了,用*来处理)
          * @param tenderId
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async getValidStagesShort(tenderId) {
             const sql = 'select * from zh_stage where tid = ? order by zh_stage.order';
@@ -199,7 +199,7 @@ module.exports = app => {
         /**
          * 获取某一期信息(报表用)
          * @param stageId
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async getStageById(stageId) {
             const sql = 'select * from zh_stage where id = ? order by zh_stage.order';
@@ -221,10 +221,10 @@ module.exports = app => {
                     await this.update({
                         check_calc: false,
                         contract_tp: stage.contract_tp, qc_tp: stage.qc_tp,
-                        yf_tp: stage.yf_tp, sf_tp: stage.sf_tp
-                    }, {id: stage.id});
+                        yf_tp: stage.yf_tp, sf_tp: stage.sf_tp,
+                    }, { id: stage.id });
                 } else if (stage.tp_history) {
-                    const his = this.ctx.helper._.find(stage.tp_history, {times: stage.curTimes, order: stage.curOrder});
+                    const his = this.ctx.helper._.find(stage.tp_history, { times: stage.curTimes, order: stage.curOrder });
                     // console.log(his);
                     if (his) {
                         stage.contract_tp = his.contract_tp;
@@ -240,7 +240,7 @@ module.exports = app => {
         /**
          * 获取标段下的全部计量期,按倒序
          * @param tenderId
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async getValidStages(tenderId) {
             const stages = await this.db.select(this.tableName, {
@@ -275,7 +275,7 @@ module.exports = app => {
                     stage.tp = this.ctx.helper.add(stage.contract_tp, stage.qc_tp);
                     stage.end_tp = this.ctx.helper.add(stage.pre_tp, stage.tp);
                 } else {
-                    const his = this.ctx.helper._.find(stage.tp_history, {times: stage.curTimes, order: stage.curOrder});
+                    const his = this.ctx.helper._.find(stage.tp_history, { times: stage.curTimes, order: stage.curOrder });
                     if (his) {
                         stage.contract_tp = his.contract_tp;
                         stage.qc_tp = his.qc_tp;
@@ -290,7 +290,7 @@ module.exports = app => {
                 if (s.yf_tp && s.sf_tp === 0) {
                     const sf = await this.ctx.service.stagePay.getHistorySf(s);
                     if (sf && s.readOnly) {
-                        await this.ctx.service.stage.update({ sf_tp: sf.tp, pre_sf_tp: sf.pre_tp}, {id: s.id});
+                        await this.ctx.service.stage.update({ sf_tp: sf.tp, pre_sf_tp: sf.pre_tp }, { id: s.id });
                     }
                     s.sf_tp = sf.tp;
                 }
@@ -303,7 +303,7 @@ module.exports = app => {
          * @param tenderId - 标段id
          * @param date - 计量年月
          * @param period - 开始-截止日期
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async addStage(tenderId, date, period) {
             const stages = await this.getAllDataByCondition({
@@ -387,7 +387,7 @@ module.exports = app => {
          * @param {Number} order - 第N期
          * @param {String} date - 计量年月
          * @param {String} period - 开始-截止时间
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async saveStage(tenderId, order, date, period) {
             await this.db.update(this.tableName, {
@@ -401,7 +401,7 @@ module.exports = app => {
          * @param {Number} tenderId - 标段id
          * @param {Number} order - 期序号
          * @param {Number} data - 中间计量生成规则
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async buildDetailData(tenderId, order, data) {
             const conn = await this.db.beginTransaction();
@@ -417,7 +417,7 @@ module.exports = app => {
 
         /**
          * 获取 当期的 计算基数
-         * @returns {Promise<any>}
+         * @return {Promise<any>}
          */
         async getStagePayCalcBase(stage, tenderInfo) {
             const calcBase = JSON.parse(JSON.stringify(payConst.calcBase));
@@ -461,12 +461,12 @@ module.exports = app => {
         }
 
         async updateCheckCalcFlag(stage, check) {
-            const result = await this.db.update(this.tableName, {id: stage.id, check_calc: check});
+            const result = await this.db.update(this.tableName, { id: stage.id, check_calc: check });
             return result.affectedRows === 1;
         }
 
         async updateCacheTime(sid) {
-            const result = await this.db.update(this.tableName, {id: sid, cache_time_l: new Date()});
+            const result = await this.db.update(this.tableName, { id: sid, cache_time_l: new Date() });
             return result.affectedRows === 1;
         }
 
@@ -474,14 +474,14 @@ module.exports = app => {
          * 删除计量期
          *
          * @param {Number} id - 期Id
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async deleteStage(id) {
             const transaction = await this.db.beginTransaction();
             try {
                 const stageInfo = await this.getDataById(id);
                 await transaction.delete(this.tableName, { id });
-                await transaction.delete(this.ctx.service.pos.tableName, {add_stage: id});
+                await transaction.delete(this.ctx.service.pos.tableName, { add_stage: 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 });
@@ -539,7 +539,7 @@ module.exports = app => {
 
         /**
          * 获取 多期的 计算基数 -(材料调差调用)
-         * @returns {Promise<any>}
+         * @return {Promise<any>}
          */
         async getMaterialCalcBase(stage_list, tenderInfo) {
             const calcBase = JSON.parse(JSON.stringify(payConst.calcBase));
@@ -585,7 +585,7 @@ module.exports = app => {
         /**
          * 获取必要的stage信息调用curTimes, curOrder, id , times, curAuditor(材料调差)
          * @param stage_id_list
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async getStageMsgByStageId(stage_id_list) {
             const list = [];

+ 17 - 4
app/service/stage_att.js

@@ -62,12 +62,21 @@ module.exports = app => {
          * @return {void}
          */
         async getDataByTenderIdAndStageId(tid, sid) {
-            const sql = 'SELECT att.id, att.lid, att.uid, att.filename, att.fileext, att.filesize, att.re_upload, att.remark, att.in_time,' +
+            const { ctx } = this;
+            const sql = 'SELECT att.id, att.lid, att.uid, att.filepath, att.filename, att.fileext, att.filesize, att.re_upload, att.remark, att.in_time,' +
                 ' pa.name as `username`, leg.name as `lname`, leg.code as `code`, leg.ledger_id as `ledger_id`, leg.b_code as `b_code`' +
                 ' FROM ?? AS att,?? AS pa,?? AS leg' +
                 ' WHERE leg.id = att.lid AND pa.id = att.uid AND att.tid = ? AND att.sid = ? ORDER BY att.id DESC';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.ctx.service.ledger.tableName, tid, sid];
-            return await this.db.query(sql, sqlParam);
+            const result = await this.db.query(sql, sqlParam);
+            return result.map(item => {
+                if (!ctx.helper.canPreview(item.fileext)) {
+                    item.filepath = `/tender/${ctx.tender.id}/measure/stage/${ctx.params.order}/download/file/${item.id}`;
+                } else {
+                    item.filepath = '/' + item.filepath;
+                }
+                return item;
+            });
         }
 
         /**
@@ -77,12 +86,16 @@ module.exports = app => {
          * @return {void}
          */
         async getDataByFid(id) {
-            const sql = 'SELECT att.id, att.lid, att.uid, att.filename, att.re_upload, att.fileext, att.filesize, att.remark, att.in_time,' +
+            const { ctx } = this;
+            const sql = 'SELECT att.id, att.lid, att.uid, att.filepath, att.filename, att.re_upload, att.fileext, att.filesize, att.remark, att.in_time,' +
                 ' pa.name as `username`, leg.name as `lname`, leg.code as `code`, leg.ledger_id as `ledger_id`,leg.b_code as `b_code`' +
                 ' FROM ?? AS att,?? AS pa,?? AS leg' +
                 ' WHERE leg.id = att.lid AND pa.id = att.uid AND att.id = ? ORDER BY att.in_time DESC';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.ctx.service.ledger.tableName, id];
-            return await this.db.queryOne(sql, sqlParam);
+            const result = await this.db.queryOne(sql, sqlParam);
+            if (!ctx.helper.canPreview(result.fileext)) result.filepath = `/tender/${ctx.tender.id}/measure/stage/${ctx.params.order}/download/file/${result.id}`;
+            else result.filepath = '/' + result.filepath;
+            return result;
         }
     }
 

+ 318 - 232
app/service/stage_audit.js

@@ -12,6 +12,7 @@ const auditConst = require('../const/audit').stage;
 const smsTypeConst = require('../const/sms_type');
 const SMS = require('../lib/sms');
 const SmsAliConst = require('../const/sms_alitemplate');
+const wxConst = require('../const/wechat_template');
 const payConst = require('../const/deal_pay');
 const pushType = require('../const/audit').pushType;
 
@@ -37,7 +38,8 @@ module.exports = app => {
          * @return {Promise<*>}
          */
         async getAuditor(stageId, auditorId, times = 1) {
-            const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+            const sql =
+                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
                 'FROM ?? AS la, ?? AS pa ' +
                 'WHERE la.`sid` = ? and la.`aid` = ? and la.`times` = ?' +
                 '    and la.`aid` = pa.`id`';
@@ -54,9 +56,11 @@ module.exports = app => {
          * @return {Promise<*>}
          */
         async getAuditors(stageId, times = 1, order_sort = 'asc') {
-            const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
+            const sql =
+                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
                 'FROM ?? AS la, ?? AS pa, (SELECT `aid`,(@i:=@i+1) as `sort` FROM ??, (select @i:=0) as it WHERE `sid` = ? AND `times` = ? GROUP BY `aid`) as g ' +
-                'WHERE la.`sid` = ? and la.`times` = ? and la.`aid` = pa.`id` and g.`aid` = la.`aid` order by la.`order` ' + order_sort;
+                'WHERE la.`sid` = ? and la.`times` = ? and la.`aid` = pa.`id` and g.`aid` = la.`aid` order by la.`order` ' +
+                order_sort;
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, stageId, times, stageId, times];
             const result = await this.db.query(sql, sqlParam);
             const sql2 = 'SELECT COUNT(a.`aid`) as num FROM (SELECT `aid` FROM ?? WHERE `sid` = ? AND `times` = ? GROUP BY `aid`) as a';
@@ -69,8 +73,13 @@ module.exports = app => {
         }
 
         async getAllAuditors(tenderId) {
-            const sql = 'SELECT sa.aid, sa.tid FROM ' + this.tableName + ' sa' +
-                '  LEFT JOIN ' + this.ctx.service.tender.tableName + ' t On sa.tid = t.id' +
+            const sql =
+                'SELECT sa.aid, sa.tid FROM ' +
+                this.tableName +
+                ' sa' +
+                '  LEFT JOIN ' +
+                this.ctx.service.tender.tableName +
+                ' t On sa.tid = t.id' +
                 '  WHERE t.id = ?' +
                 '  GROUP BY  sa.aid';
             const sqlParam = [tenderId];
@@ -86,7 +95,8 @@ module.exports = app => {
          * @return {Promise<*>}
          */
         async getStatusName(stageId) {
-            const sql = 'SELECT pa.`name` ' +
+            const sql =
+                'SELECT pa.`name` ' +
                 'FROM ?? AS sa, ?? AS pa ' +
                 'WHERE sa.`sid` = ?' +
                 '    and sa.`aid` = pa.`id` and sa.`status` != ? ORDER BY sa.`times` DESC, sa.`order` DESC';
@@ -102,7 +112,8 @@ module.exports = app => {
          * @return {Promise<*>}
          */
         async getCurAuditor(stageId, times = 1) {
-            const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+            const sql =
+                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
                 'FROM ?? AS la, ?? AS pa ' +
                 'WHERE la.`sid` = ? and la.`status` = ? and la.`times` = ?' +
                 '    and la.`aid` = pa.`id`';
@@ -143,7 +154,7 @@ module.exports = app => {
                 status: auditConst.status.uncheck,
             };
             const result = await this.db.insert(this.tableName, data);
-            return result.effectRows = 1;
+            return (result.effectRows = 1);
         }
 
         /**
@@ -220,7 +231,11 @@ module.exports = app => {
 
             const transaction = await this.db.beginTransaction();
             try {
-                await transaction.update(this.tableName, { id: audit.id, status: auditConst.status.checking, begin_time: new Date() });
+                await transaction.update(this.tableName, {
+                    id: audit.id,
+                    status: auditConst.status.checking,
+                    begin_time: new Date(),
+                });
                 // 计算原报最终数据
                 const [yfPay, sfPay] = await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
                 // 复制一份下一审核人数据
@@ -231,14 +246,16 @@ module.exports = app => {
                 // 更新期数据
                 const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
                 this.ctx.stage.tp_history.push({
-                    times: this.ctx.stage.curTimes, order: 0,
+                    times: this.ctx.stage.curTimes,
+                    order: 0,
                     contract_tp: tpData.contract_tp,
                     qc_tp: tpData.qc_tp,
                     yf_tp: yfPay.tp,
                     sf_tp: sfPay.tp,
                 });
                 await transaction.update(this.ctx.service.stage.tableName, {
-                    id: stageId, status: auditConst.status.checking,
+                    id: stageId,
+                    status: auditConst.status.checking,
                     contract_tp: tpData.contract_tp,
                     qc_tp: tpData.qc_tp,
                     yf_tp: yfPay.tp,
@@ -264,9 +281,21 @@ module.exports = app => {
                 //     }
                 // }
                 const stageInfo = await this.ctx.service.stage.getDataById(audit.sid);
-                const shenpiUrl = await this.ctx.helper.urlToShort('http://' + this.ctx.request.header.host + '/wap/tender/' + this.ctx.tender.id + '/stage/' + stageInfo.order);
-                await this.ctx.helper.sendAliSms(audit.aid, smsTypeConst.const.JL,
-                    smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, { qi: stageInfo.order, code: shenpiUrl });
+                const shenpiUrl = await this.ctx.helper.urlToShort(this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/stage/' + stageInfo.order);
+                await this.ctx.helper.sendAliSms(audit.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, {
+                    qi: stageInfo.order,
+                    code: shenpiUrl,
+                });
+
+                // 微信模板通知
+                const wechatData = {
+                    wap_url: shenpiUrl,
+                    qi: stageInfo.order,
+                    status: wxConst.status.check,
+                    tips: wxConst.tips.check,
+                    code: this.ctx.session.sessionProject.code,
+                };
+                await this.ctx.helper.sendWechat(audit.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), wxConst.template.stage, wechatData);
 
                 // todo 更新标段tender状态 ?
                 await transaction.commit();
@@ -287,23 +316,35 @@ module.exports = app => {
             const nextAudit = await this.getDataByCondition({ sid: stageId, times, order: audit.order + 1 });
             const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
 
-
             const transaction = await this.db.beginTransaction();
 
             try {
                 // 添加推送
-                const noticeContent = await this.getNoticeContent(pid, audit.tid, stageId)
-                const records = [{ pid, type: pushType.stage, uid: this.ctx.stage.user_id, status: auditConst.status.checkNo, content: noticeContent }]
-                this.ctx.stage.auditors.forEach( audit => {
-                    records.push({ pid, type: pushType.stage, uid: audit.aid, status: auditConst.status.checkNo, content: noticeContent })
-                })
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, stageId, audit.aid);
+                const auditors = await this.getAuditGroupByListWithOwner(stageId, times);
+                const records = [];
+                auditors.forEach(audit => {
+                    records.push({
+                        pid,
+                        type: pushType.stage,
+                        uid: audit.aid,
+                        status: auditConst.status.checked,
+                        content: noticeContent,
+                    });
+                });
                 await transaction.insert('zh_notice', records);
 
-                await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
+                await transaction.update(this.tableName, {
+                    id: audit.id,
+                    status: checkData.checkType,
+                    opinion: checkData.opinion,
+                    end_time: time,
+                });
                 // 计算并合同支付最终数据
                 const [yfPay, sfPay] = await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
                 this.ctx.stage.tp_history.push({
-                    times, order: audit.order,
+                    times,
+                    order: audit.order,
                     contract_tp: tpData.contract_tp,
                     qc_tp: tpData.qc_tp,
                     yf_tp: yfPay.tp,
@@ -317,10 +358,15 @@ module.exports = app => {
                     await this.ctx.service.stageBonus.updateHistory(this.ctx.stage, transaction);
                     await this.ctx.service.stageOther.updateHistory(this.ctx.stage, transaction);
                     // 流程至下一审批人
-                    await transaction.update(this.tableName, { id: nextAudit.id, status: auditConst.status.checking, begin_time: time });
+                    await transaction.update(this.tableName, {
+                        id: nextAudit.id,
+                        status: auditConst.status.checking,
+                        begin_time: time,
+                    });
                     // 同步 期信息
                     await transaction.update(this.ctx.service.stage.tableName, {
-                        id: stageId, status: auditConst.status.checking,
+                        id: stageId,
+                        status: auditConst.status.checking,
                         contract_tp: tpData.contract_tp,
                         qc_tp: tpData.qc_tp,
                         yf_tp: yfPay.tp,
@@ -347,19 +393,29 @@ module.exports = app => {
                     //     }
                     // }
                     const stageInfo = await this.ctx.service.stage.getDataById(nextAudit.sid);
-                    const shenpiUrl = await this.ctx.helper.urlToShort('http://' + this.ctx.request.header.host + '/wap/tender/' + this.ctx.tender.id + '/stage/' + stageInfo.order);
-                    await this.ctx.helper.sendAliSms(nextAudit.aid, smsTypeConst.const.JL,
-                        smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, { qi: stageInfo.order, code: shenpiUrl });
+                    const shenpiUrl = await this.ctx.helper.urlToShort(this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/stage/' + stageInfo.order);
+                    await this.ctx.helper.sendAliSms(nextAudit.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, {
+                        qi: stageInfo.order,
+                        code: shenpiUrl,
+                    });
+                    // 微信模板通知
+                    const wechatData = {
+                        wap_url: shenpiUrl,
+                        qi: stageInfo.order,
+                        status: wxConst.status.check,
+                        tips: wxConst.tips.check,
+                        code: this.ctx.session.sessionProject.code,
+                    };
+                    await this.ctx.helper.sendWechat(nextAudit.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), wxConst.template.stage, wechatData);
                 } else {
                     // 本期结束
                     // 生成截止本期数据 final数据
-                    console.time('generatePre');
                     await this.ctx.service.stageBillsFinal.generateFinalData(transaction, this.ctx.tender, this.ctx.stage);
                     await this.ctx.service.stagePosFinal.generateFinalData(transaction, this.ctx.tender, this.ctx.stage);
-                    console.timeEnd('generatePre');
                     // 同步 期信息
                     await transaction.update(this.ctx.service.stage.tableName, {
-                        id: stageId, status: checkData.checkType,
+                        id: stageId,
+                        status: checkData.checkType,
                         contract_tp: tpData.contract_tp,
                         qc_tp: tpData.qc_tp,
                         yf_tp: yfPay.tp,
@@ -397,9 +453,22 @@ module.exports = app => {
                     //     const content = '【纵横计量支付】' + ptmsg + '第' + stageInfo.order + '期,审批通过。';
                     //     sms.send(mobile_array, content);
                     // }
-                    const users = this._.pull(this._.map(auditList, 'aid'), stageInfo.user_id);
-                    await this.ctx.helper.sendAliSms(users, smsTypeConst.const.JL,
-                        smsTypeConst.judge.result.toString(), SmsAliConst.template.stage_result, { qi: stageInfo.order, status: SmsAliConst.status.success });
+                    const users = this._.uniq(this._.concat(this._.map(auditList, 'aid'), stageInfo.user_id));
+                    await this.ctx.helper.sendAliSms(users, smsTypeConst.const.JL, smsTypeConst.judge.result.toString(), SmsAliConst.template.stage_result, {
+                        qi: stageInfo.order,
+                        status: SmsAliConst.status.success,
+                    });
+
+                    // 微信模板通知
+                    const shenpiUrl = await this.ctx.helper.urlToShort(this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/stage/' + stageInfo.order);
+                    const wechatData = {
+                        wap_url: shenpiUrl,
+                        qi: stageInfo.order,
+                        status: wxConst.status.success,
+                        tips: wxConst.tips.success,
+                        code: this.ctx.session.sessionProject.code,
+                    };
+                    await this.ctx.helper.sendWechat(users, smsTypeConst.const.JL, smsTypeConst.judge.result.toString(), wxConst.template.stage, wechatData);
                 }
                 await transaction.commit();
             } catch (err) {
@@ -430,26 +499,47 @@ module.exports = app => {
             const transaction = await this.db.beginTransaction();
             try {
                 // 添加推送
-                const noticeContent = await this.getNoticeContent(pid, audit.tid, stageId)
-                const records = [{ pid, type: pushType.stage, uid: this.ctx.stage.user_id, status: auditConst.status.checkNo, content: noticeContent }]
-                auditors.forEach( audit => {
-                    records.push({ pid, type: pushType.stage, uid: audit.aid, status: auditConst.status.checkNo, content: noticeContent })
-                })
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, stageId, audit.aid);
+                const records = [
+                    {
+                        pid,
+                        type: pushType.stage,
+                        uid: this.ctx.stage.user_id,
+                        status: auditConst.status.checkNo,
+                        content: noticeContent,
+                    },
+                ];
+                auditors.forEach(audit => {
+                    records.push({
+                        pid,
+                        type: pushType.stage,
+                        uid: audit.aid,
+                        status: auditConst.status.checkNo,
+                        content: noticeContent,
+                    });
+                });
                 await transaction.insert('zh_notice', records);
 
                 // 计算并合同支付最终数据
                 const [yfPay, sfPay] = await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
                 this.ctx.stage.tp_history.push({
-                    times, order: audit.order,
+                    times,
+                    order: audit.order,
                     contract_tp: tpData.contract_tp,
                     qc_tp: tpData.qc_tp,
                     yf_tp: yfPay.tp,
                     sf_tp: sfPay.tp,
                 });
-                await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
+                await transaction.update(this.tableName, {
+                    id: audit.id,
+                    status: checkData.checkType,
+                    opinion: checkData.opinion,
+                    end_time: time,
+                });
                 // 同步 期信息
                 await transaction.update(this.ctx.service.stage.tableName, {
-                    id: stageId, status: checkData.checkType,
+                    id: stageId,
+                    status: checkData.checkType,
                     contract_tp: tpData.contract_tp,
                     qc_tp: tpData.qc_tp,
                     times: times + 1,
@@ -496,9 +586,22 @@ module.exports = app => {
                 //     const content = '【纵横计量支付】' + ptmsg + '第' + stageInfo.order + '期,审批退回。';
                 //     sms.send(mobile_array, content);
                 // }
-                const users = this._.pull(this._.map(auditList, 'aid'), stageInfo.user_id);
-                await this.ctx.helper.sendAliSms(users, smsTypeConst.const.JL,
-                    smsTypeConst.judge.result.toString(), SmsAliConst.template.stage_result, { qi: stageInfo.order, status: SmsAliConst.status.back });
+                const users = this._.uniq(this._.concat(this._.map(auditList, 'aid'), stageInfo.user_id));
+                await this.ctx.helper.sendAliSms(users, smsTypeConst.const.JL, smsTypeConst.judge.result.toString(), SmsAliConst.template.stage_result, {
+                    qi: stageInfo.order,
+                    status: SmsAliConst.status.back,
+                });
+                // 微信模板通知
+                const shenpiUrl = await this.ctx.helper.urlToShort(this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/stage/' + stageInfo.order);
+                const wechatData = {
+                    wap_url: shenpiUrl,
+                    qi: stageInfo.order,
+                    status: wxConst.status.back,
+                    tips: wxConst.tips.back,
+                    code: this.ctx.session.sessionProject.code,
+                };
+                await this.ctx.helper.sendWechat(users, smsTypeConst.const.JL, smsTypeConst.judge.result.toString(), wxConst.template.stage, wechatData);
+
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
@@ -506,7 +609,7 @@ module.exports = app => {
             }
         }
 
-        async _checkNoPre(stageId, checkData, times) {
+        async _checkNoPre(pid, stageId, checkData, times) {
             const time = new Date();
             // 整理当前流程审核人状态更新
             const audit = await this.getDataByCondition({ sid: stageId, times, status: auditConst.status.checking });
@@ -525,19 +628,33 @@ module.exports = app => {
             const transaction = await this.db.beginTransaction();
             try {
                 // 添加推送
-                const noticeContent = await this.getNoticeContent(pid, audit.tid, stageId)
-                const records = [{ pid, type: pushType.stage, uid: this.ctx.stage.user_id, status: auditConst.status.checkNoPre, content: noticeContent }]
-                auditors2.forEach( audit => {
-                    records.push({ pid, type: pushType.stage, uid: audit.aid, status: auditConst.status.checkNoPre, content: noticeContent })
-                })
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, stageId, audit.aid);
+                const records = [
+                    {
+                        pid,
+                        type: pushType.stage,
+                        uid: this.ctx.stage.user_id,
+                        status: auditConst.status.checkNoPre,
+                        content: noticeContent,
+                    },
+                ];
+                auditors2.forEach(audit => {
+                    records.push({
+                        pid,
+                        type: pushType.stage,
+                        uid: audit.aid,
+                        status: auditConst.status.checkNoPre,
+                        content: noticeContent,
+                    });
+                });
 
                 await transaction.insert('zh_notice', records);
 
-
                 // 计算并合同支付最终数据
                 const [yfPay, sfPay] = await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
                 this.ctx.stage.tp_history.push({
-                    times, order: audit.order,
+                    times,
+                    order: audit.order,
                     contract_tp: tpData.contract_tp,
                     qc_tp: tpData.qc_tp,
                     yf_tp: yfPay.tp,
@@ -554,7 +671,12 @@ module.exports = app => {
                     tp_history: JSON.stringify(this.ctx.stage.tp_history),
                     cache_time_r: this.ctx.stage.cache_time_l,
                 });
-                await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
+                await transaction.update(this.tableName, {
+                    id: audit.id,
+                    status: checkData.checkType,
+                    opinion: checkData.opinion,
+                    end_time: time,
+                });
                 // 顺移气候审核人流程顺序
                 this.initSqlBuilder();
                 this.sqlBuilder.setAndWhere('sid', { value: this.ctx.stage.id, operate: '=' });
@@ -565,13 +687,21 @@ module.exports = app => {
                 // 上一审批人,当前审批人 再次添加至流程
                 const newAuditors = [];
                 newAuditors.push({
-                    tid: audit.tid, sid: audit.sid, aid: preAuditor.aid,
-                    times: audit.times, order: audit.order + 1, 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,
+                    tid: audit.tid,
+                    sid: audit.sid,
+                    aid: audit.aid,
+                    times: audit.times,
+                    order: audit.order + 2,
+                    status: auditConst.status.uncheck,
                 });
                 await transaction.insert(this.tableName, newAuditors);
                 // 计算该审批人最终数据
@@ -584,7 +714,8 @@ module.exports = app => {
 
                 // 同步 期信息
                 await transaction.update(this.ctx.service.stage.tableName, {
-                    id: stageId, status: checkData.checkType,
+                    id: stageId,
+                    status: checkData.checkType,
                     cache_time_r: this.ctx.stage.cache_time_l,
                 });
                 // 添加短信通知-需要审批提醒功能
@@ -605,9 +736,20 @@ module.exports = app => {
                 //     }
                 // }
                 const stageInfo = await this.ctx.service.stage.getDataById(audit.sid);
-                const shenpiUrl = await this.ctx.helper.urlToShort('http://' + this.ctx.request.header.host + '/wap/tender/' + this.ctx.tender.id + '/stage/' + stageInfo.order);
-                await this.ctx.helper.sendAliSms(preAuditor.aid, smsTypeConst.const.JL,
-                    smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, { qi: stageInfo.order, code: shenpiUrl });
+                const shenpiUrl = await this.ctx.helper.urlToShort(this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/stage/' + stageInfo.order);
+                await this.ctx.helper.sendAliSms(preAuditor.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, {
+                    qi: stageInfo.order,
+                    code: shenpiUrl,
+                });
+                // 微信模板通知
+                const wechatData = {
+                    wap_url: shenpiUrl,
+                    qi: stageInfo.order,
+                    status: wxConst.status.check,
+                    tips: wxConst.tips.check,
+                    code: this.ctx.session.sessionProject.code,
+                };
+                await this.ctx.helper.sendWechat(preAuditor.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), wxConst.template.stage, wechatData);
 
                 await transaction.commit();
             } catch (err) {
@@ -642,125 +784,11 @@ module.exports = app => {
                     await this._checkNo(pid, stageId, checkData, times);
                     break;
                 case auditConst.status.checkNoPre:
-                    await this._checkNoPre(stageId, checkData, times);
+                    await this._checkNoPre(pid, stageId, checkData, times);
                     break;
                 default:
                     throw '无效审批操作';
             }
-
-            // const transaction = await this.db.beginTransaction();
-            // try {
-            //     // 更新当前审核流程
-            //     await transaction.update(this.tableName, {id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time});
-            //     if (checkData.checkType === auditConst.status.checked) { // 审批通过
-            //         const nextAudit = await this.getDataByCondition({sid: stageId, times: times, order: audit.order + 1});
-            //         // 无下一审核人表示,审核结束
-            //         if (nextAudit) {
-            //             // 计算该审批人最终数据
-            //             await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
-            //             // 复制一份下一审核人数据
-            //             await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, nextAudit.order, transaction);
-            //             // 流程至下一审批人
-            //             await transaction.update(this.tableName, {id: nextAudit.id, status: auditConst.status.checking, begin_time: time});
-            //             // 同步 期信息
-            //             const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
-            //             await transaction.update(this.ctx.service.stage.tableName, {
-            //                 id: stageId, status: auditConst.status.checking,
-            //                 contract_tp: tpData.contract_tp,
-            //                 qc_tp: tpData.qc_tp,
-            //             });
-            //         } else {
-            //             // 本期结束
-            //             // 生成截止本期数据 final数据
-            //             await this.ctx.service.stageBillsFinal.generateFinalData(transaction, this.ctx.tender, this.ctx.stage);
-            //             await this.ctx.service.stagePosFinal.generateFinalData(transaction, this.ctx.tender, this.ctx.stage);
-            //             // 计算并合同支付最终数据
-            //             await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
-            //             // 同步 期信息
-            //             const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
-            //             await transaction.update(this.ctx.service.stage.tableName, {
-            //                 id: stageId, status: checkData.checkType,
-            //                 contract_tp: tpData.contract_tp,
-            //                 qc_tp: tpData.qc_tp,
-            //             });
-            //         }
-            //     } else if (checkData.checkType === auditConst.status.checkNo) { // 审批退回 原报, times+1
-            //         // 同步 期信息
-            //         const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
-            //         await transaction.update(this.ctx.service.stage.tableName, {
-            //             id: stageId, status: checkData.checkType,
-            //             contract_tp: tpData.contract_tp,
-            //             qc_tp: tpData.qc_tp,
-            //             times: times + 1,
-            //         });
-            //         // 拷贝新一次审核流程列表
-            //         // const auditors = await this.getAllDataByCondition({
-            //         //     where: {sid: stageId, times: times},
-            //         //     columns: ['tid', 'sid', 'aid', 'order']
-            //         // });
-            //         const sql = 'SELECT `tid`, `sid`, `aid`, `order` FROM ?? WHERE `sid` = ? and `times` = ? GROUP BY `aid`';
-            //         const sqlParam = [this.tableName, stageId, times];
-            //         const auditors = await this.db.query(sql, sqlParam);
-            //         let order = 1;
-            //         for (const a of auditors) {
-            //             a.times = times + 1;
-            //             a.order = order;
-            //             a.status = auditConst.status.uncheck;
-            //             order++;
-            //         }
-            //         await transaction.insert(this.tableName, auditors);
-            //         // 计算该审批人最终数据
-            //         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);
-            //     } else if (checkData.checkType === auditConst.status.checkNoPre) { // 审批退回 上一审批人
-            //         // 同步 期信息
-            //         const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
-            //         await transaction.update(this.ctx.service.stage.tableName, {
-            //             id: stageId, status: checkData.checkType,
-            //             contract_tp: tpData.contract_tp,
-            //             qc_tp: tpData.qc_tp,
-            //         });
-            //         // 将当前审批人 与 上一审批人再次添加至流程,顺移其后审批人流程顺序
-            //         if (audit.order > 1) {
-            //             // 顺移气候审核人流程顺序
-            //             this.initSqlBuilder();
-            //             this.sqlBuilder.setAndWhere('sid', { value: this.ctx.stage.id, operate: '=', });
-            //             this.sqlBuilder.setAndWhere('order', { value: audit.order, operate: '>', });
-            //             this.sqlBuilder.setUpdateData('order', { value: 2, selfOperate: '+', });
-            //             const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
-            //             const data = await transaction.query(sql, sqlParam);
-            //
-            //             // 上一审批人,当前审批人 再次添加至流程
-            //             const preAuditor = await this.getDataByCondition({sid: stageId, times: times, order: audit.order - 1});
-            //             const newAuditors = [];
-            //             newAuditors.push({
-            //                 tid: preAuditor.tid, sid: preAuditor.sid, aid: preAuditor.aid,
-            //                 times: preAuditor.times, order: preAuditor.order + 2, 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
-            //             });
-            //             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);
-            //         } else {
-            //             throw '审核数据错误';
-            //         }
-            //     } else {
-            //         throw '无效审批操作';
-            //     }
-            //
-            //     await transaction.commit();
-            // } catch (err) {
-            //     await transaction.rollback();
-            //     throw err;
-            // }
         }
 
         /**
@@ -772,7 +800,14 @@ module.exports = app => {
         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];
+            const audit = (
+                await this.getAllDataByCondition({
+                    where: { sid: stageId, times },
+                    orders: [['order', 'desc']],
+                    limit: 1,
+                    offset: 0,
+                })
+            )[0];
             if (!audit || audit.order < 1) {
                 throw '审核数据错误';
             }
@@ -781,13 +816,23 @@ module.exports = app => {
                 // 当前审批人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: '',
+                    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,
+                    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);
@@ -805,7 +850,8 @@ module.exports = app => {
                 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,
+                    id: stageId,
+                    status: auditConst.status.checking,
                     cache_time_r: this.ctx.stage.cache_time_l,
                 });
 
@@ -826,9 +872,21 @@ module.exports = app => {
                 //     }
                 // }
                 const stageInfo = await this.ctx.service.stage.getDataById(audit.sid);
-                const shenpiUrl = await this.ctx.helper.urlToShort('http://' + this.ctx.request.header.host + '/wap/tender/' + this.ctx.tender.id + '/stage/' + stageInfo.order);
-                await this.ctx.helper.sendAliSms(audit.aid, smsTypeConst.const.JL,
-                    smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, { qi: stageInfo.order, code: shenpiUrl });
+                const shenpiUrl = await this.ctx.helper.urlToShort(this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/stage/' + stageInfo.order);
+                await this.ctx.helper.sendAliSms(audit.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, {
+                    qi: stageInfo.order,
+                    code: shenpiUrl,
+                });
+                // 微信模板通知
+                const wechatData = {
+                    wap_url: shenpiUrl,
+                    qi: stageInfo.order,
+                    status: wxConst.status.check,
+                    tips: wxConst.tips.check,
+                    code: this.ctx.session.sessionProject.code,
+                };
+                await this.ctx.helper.sendWechat(audit.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), wxConst.template.stage, wechatData);
+
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
@@ -843,13 +901,23 @@ module.exports = app => {
          * @return {Promise<*>}
          */
         async getAuditStage(auditorId) {
-            const sql = 'SELECT sa.`aid`, sa.`times`, sa.`order`, sa.`begin_time`, sa.`end_time`, sa.`tid`, sa.`sid`,' +
-                        '    s.`order` As `sorder`, s.`status` As `sstatus`,' +
-                        '    t.`name`, t.`project_id`, t.`type`, t.`user_id` ' +
-                        '  FROM ?? AS sa, ?? AS s, ?? As t ' +
-                        '  WHERE ((sa.`aid` = ? and sa.`status` = ?) OR (s.`user_id` = ? and sa.`status` = ? and s.`status` = ? and sa.`times` = (s.`times`-1)))' +
-                        '    and sa.`sid` = s.`id` and sa.`tid` = t.`id`';
-            const sqlParam = [this.tableName, this.ctx.service.stage.tableName, this.ctx.service.tender.tableName, auditorId, auditConst.status.checking, auditorId, auditConst.status.checkNo, auditConst.status.checkNo];
+            const sql =
+                'SELECT sa.`aid`, sa.`times`, sa.`order`, sa.`begin_time`, sa.`end_time`, sa.`tid`, sa.`sid`,' +
+                '    s.`order` As `sorder`, s.`status` As `sstatus`,' +
+                '    t.`name`, t.`project_id`, t.`type`, t.`user_id` ' +
+                '  FROM ?? AS sa, ?? AS s, ?? As t ' +
+                '  WHERE ((sa.`aid` = ? and sa.`status` = ?) OR (s.`user_id` = ? and sa.`status` = ? and s.`status` = ? and sa.`times` = (s.`times`-1)))' +
+                '    and sa.`sid` = s.`id` and sa.`tid` = t.`id` ORDER BY sa.`begin_time` DESC';
+            const sqlParam = [
+                this.tableName,
+                this.ctx.service.stage.tableName,
+                this.ctx.service.tender.tableName,
+                auditorId,
+                auditConst.status.checking,
+                auditorId,
+                auditConst.status.checkNo,
+                auditConst.status.checkNo,
+            ];
             return await this.db.query(sql, sqlParam);
         }
 
@@ -875,16 +943,17 @@ module.exports = app => {
             // const sqlParam = [this.ctx.service.tender.tableName, uid, this.tableName, uid, this.ctx.service.stage.tableName, this.tableName,
             //     this.ctx.service.projectAccount.tableName, time, pid];
             // return await this.db.query(sql, sqlParam);
-            let notice =  await this.db.select('zh_notice', {
+            let notice = await this.db.select('zh_notice', {
                 where: { pid, type: pushType.stage, uid },
                 orders: [['create_time', 'desc']],
-                limit: 10, offset: 0
+                limit: 10,
+                offset: 0,
             });
             notice = notice.map(v => {
-                const extra = JSON.parse(v.content)
-                delete v.content
-                return { ...v, ...extra }
-            })
+                const extra = JSON.parse(v.content);
+                delete v.content;
+                return { ...v, ...extra };
+            });
             return notice;
         }
 
@@ -896,13 +965,14 @@ module.exports = app => {
          * @param {Number} uid 审核人id
          */
         async getNoticeContent(pid, tid, sid, uid) {
-            const noticeSql = 'SELECT * FROM (SELECT ' +
+            const noticeSql =
+                'SELECT * FROM (SELECT ' +
                 '  t.`id` As `tid`, t.`name`, s.`order`, pa.`name` As `su_name`, pa.role As `su_role`' +
                 '  FROM (SELECT * FROM ?? WHERE `id` = ? ) As t' +
                 '  LEFT JOIN ?? As s On s.`id` =  ?' +
                 '  LEFT JOIN ?? As pa ON pa.`id` = ?' +
                 '  WHERE  t.`project_id` = ? ) as new_t GROUP BY new_t.`tid`';
-            const noticeSqlParam = [this.ctx.service.tender.tableName, tid, this.ctx.service.stage.tableName, sid,  this.ctx.service.projectAccount.tableName, uid, pid];
+            const noticeSqlParam = [this.ctx.service.tender.tableName, tid, this.ctx.service.stage.tableName, sid, this.ctx.service.projectAccount.tableName, uid, pid];
             const content = await this.db.query(noticeSql, noticeSqlParam);
             return content.length ? JSON.stringify(content[0]) : '';
         }
@@ -914,7 +984,8 @@ module.exports = app => {
          * @return {Promise<*>}
          */
         async getAuditGroupByList(stageId, times) {
-            const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`aid`, la.`order` ' +
+            const sql =
+                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`aid`, la.`order` ' +
                 'FROM ?? AS la, ?? AS pa ' +
                 'WHERE la.`sid` = ? and la.`times` = ? and la.`aid` = pa.`id` GROUP BY la.`aid` ORDER BY la.`order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, times];
@@ -932,9 +1003,14 @@ module.exports = app => {
          */
         async getAuditGroupByListWithOwner(stageId, times) {
             const result = await this.getAuditGroupByList(stageId, times);
-            const sql = 'SELECT pa.`id` As aid, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As sid, 0 As `order`' +
-                '  FROM ' + this.ctx.service.stage.tableName + ' As s' +
-                '  LEFT JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa' +
+            const sql =
+                'SELECT pa.`id` As aid, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As sid, 0 As `order`' +
+                '  FROM ' +
+                this.ctx.service.stage.tableName +
+                ' As s' +
+                '  LEFT JOIN ' +
+                this.ctx.service.projectAccount.tableName +
+                ' As pa' +
                 '  ON s.user_id = pa.id' +
                 '  WHERE s.id = ?';
             const sqlParam = [times, stageId, stageId];
@@ -966,7 +1042,7 @@ module.exports = app => {
                 newAuditors.push(na);
             }
             const result = await transaction.insert(this.tableName, newAuditors);
-            return result.effectRows = auditors.length;
+            return (result.effectRows = auditors.length);
         }
 
         /**
@@ -982,24 +1058,27 @@ module.exports = app => {
             let sql = '';
             let sqlParam = '';
             switch (status) {
-                case auditConst.status.checking :
-                case auditConst.status.checked :
-                case auditConst.status.checkNoPre :
-                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order` ' +
+                case auditConst.status.checking:
+                case auditConst.status.checked:
+                case auditConst.status.checkNoPre:
+                    sql =
+                        'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order` ' +
                         'FROM ?? AS la, ?? AS pa ' +
                         'WHERE la.`sid` = ? and la.`status` = ? and la.`aid` = pa.`id` order by la.`times` desc, la.`order` desc';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, status];
                     auditor = await this.db.queryOne(sql, sqlParam);
                     break;
-                case auditConst.status.checkNo :
-                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order` ' +
+                case auditConst.status.checkNo:
+                    sql =
+                        'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order` ' +
                         'FROM ?? AS la, ?? AS pa ' +
                         'WHERE la.`sid` = ? and la.`status` = ? and la.`times` = ? and la.`aid` = pa.`id` order by la.`times` desc, la.`order` desc';
                     sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, auditConst.status.checkNo, parseInt(times) - 1];
                     auditor = await this.db.queryOne(sql, sqlParam);
                     break;
-                case auditConst.status.uncheck :
-                default:break;
+                case auditConst.status.uncheck:
+                default:
+                    break;
             }
             return auditor;
         }
@@ -1012,17 +1091,12 @@ module.exports = app => {
          * @return {Promise<boolean>}
          */
         async getStageAudit(stageId, times = 1) {
-            const sql = 'SELECT a1.aid, a1.begin_time, a1.end_time, a1.status, a1.opinion ' +
-                'FROM ?? AS a1 ' +
-                'WHERE a1.`sid` = ? and a1.`times` = ? ' +
-                'ORDER BY a1.order'
-            ;
+            const sql = 'SELECT a1.aid, a1.begin_time, a1.end_time, a1.status, a1.opinion ' + 'FROM ?? AS a1 ' + 'WHERE a1.`sid` = ? and a1.`times` = ? ' + 'ORDER BY a1.order';
             const sqlParam = [this.tableName, stageId, times];
             const rst = await this.db.query(sql, sqlParam);
             return rst;
         }
 
-
         /**
          * 取待审批期列表(wap用)
          *
@@ -1030,7 +1104,8 @@ module.exports = app => {
          * @return {Promise<*>}
          */
         async getAuditStageByWap(auditorId) {
-            const sql = 'SELECT sa.`aid`, sa.`times`, sa.`begin_time`, sa.`end_time`, sa.`tid`, sa.`sid`,' +
+            const sql =
+                'SELECT sa.`aid`, sa.`times`, sa.`begin_time`, sa.`end_time`, sa.`tid`, sa.`sid`,' +
                 // '    s.`order` As `sorder`, s.`status` As `sstatus`, s.`s_time`, s.`contract_tp`, s.`qc_tp`, s.`pre_contract_tp`, s.`pre_qc_tp`, s.`yf_tp`, s.`pre_yf_tp`, ' +
                 '    s.*,' +
                 '    t.`name`, t.`project_id`, t.`type`, t.`user_id`,' +
@@ -1038,7 +1113,14 @@ module.exports = app => {
                 '  FROM ?? AS sa, ?? AS s, ?? As t, ?? AS ti ' +
                 '  WHERE sa.`aid` = ? and sa.`status` = ?' +
                 '    and sa.`sid` = s.`id` and sa.`tid` = t.`id` and ti.`tid` = t.`id`';
-            const sqlParam = [this.tableName, this.ctx.service.stage.tableName, this.ctx.service.tender.tableName, this.ctx.service.tenderInfo.tableName, auditorId, auditConst.status.checking];
+            const sqlParam = [
+                this.tableName,
+                this.ctx.service.stage.tableName,
+                this.ctx.service.tender.tableName,
+                this.ctx.service.tenderInfo.tableName,
+                auditorId,
+                auditConst.status.checking,
+            ];
             return await this.db.query(sql, sqlParam);
         }
 
@@ -1052,13 +1134,13 @@ module.exports = app => {
          */
         async _timesDelete(sid, times, transaction) {
             // 审批流程
-            await transaction.delete(this.tableName, { sid: sid, times: times });
-            await transaction.delete(this.ctx.service.pos.tableName, {add_stage: sid, add_times: times});
-            await transaction.delete(this.ctx.service.stageBills.tableName, { sid: sid, times: times });
-            await transaction.delete(this.ctx.service.stagePos.tableName, { sid: sid, times: times });
-            await transaction.delete(this.ctx.service.stageDetail.tableName, { sid: sid, times: times });
-            await transaction.delete(this.ctx.service.stageChange.tableName, { sid: sid, stimes: times });
-            await transaction.delete(this.ctx.service.stagePay.tableName, { sid: sid, stimes: times });
+            await transaction.delete(this.tableName, { sid, times });
+            await transaction.delete(this.ctx.service.pos.tableName, { add_stage: sid, add_times: times });
+            await transaction.delete(this.ctx.service.stageBills.tableName, { sid, times });
+            await transaction.delete(this.ctx.service.stagePos.tableName, { sid, times });
+            await transaction.delete(this.ctx.service.stageDetail.tableName, { sid, times });
+            await transaction.delete(this.ctx.service.stageChange.tableName, { sid, stimes: times });
+            await transaction.delete(this.ctx.service.stagePay.tableName, { sid, stimes: times });
             await transaction.delete(this.ctx.service.pay.tableName, { csid: sid, cstimes: times });
             // 其他台账
             await this.ctx.service.stageJgcl.deleteStageTimesData(sid, times, transaction);
@@ -1101,15 +1183,19 @@ module.exports = app => {
                 // 计算缓存
                 const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
                 // 计算并合同支付最终数据
-                const lastAudit = await this.getDataByCondition({sid: this.ctx.stage.id, times: nowTimes - 1, status: auditConst.status.checkNo});
+                const lastAudit = await this.getDataByCondition({
+                    sid: this.ctx.stage.id,
+                    times: nowTimes - 1,
+                    status: auditConst.status.checkNo,
+                });
                 if (!lastAudit) throw '审批数据错误';
 
                 await this.ctx.service.stagePay.copyStagePays4DeleteTimes(this.ctx.stage, nowTimes, 0, lastAudit.times, lastAudit.order, transaction);
                 const stagePay = await this.ctx.service.stagePay.getAuditorStageData(this.ctx.stage.id, lastAudit.times, lastAudit.order);
-                const yfPay = stagePay.find(function (x) {
+                const yfPay = stagePay.find(function(x) {
                     return x.ptype === payConst.payType.yf;
                 });
-                const sfPay = stagePay.find(function (x) {
+                const sfPay = stagePay.find(function(x) {
                     return x.ptype === payConst.payType.sf;
                 });
                 // 同步 期信息

+ 6 - 0
app/service/tender.js

@@ -132,6 +132,9 @@ module.exports = app => {
                     // 参与审批 材料调差 的标段
                     '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
                     '        t.id IN ( SELECT ma.`tid` FROM ?? AS ma WHERE ma.`aid` = ? GROUP BY ma.`tid`))' +
+                    // 参与审批 预付款 的标段
+                    '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
+                    '        t.id IN ( SELECT ad.`tid` FROM ?? AS ad WHERE ad.`audit_id` = ? GROUP BY ad.`tid`))' +
                     // 未参与,但可见的标段
                     ') ORDER BY CONVERT(t.`name` USING GBK) ASC';
                 sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, session.sessionProject.id, session.sessionUser.accountId,
@@ -140,6 +143,7 @@ module.exports = app => {
                     this.ctx.service.changeAudit.tableName, session.sessionUser.accountId,
                     this.ctx.service.reviseAudit.tableName, session.sessionUser.accountId,
                     this.ctx.service.materialAudit.tableName, session.sessionUser.accountId,
+                    this.ctx.service.advanceAudit.tableName, session.sessionUser.accountId,
                 ];
             }
             const list = await this.db.query(sql, sqlParam);
@@ -309,6 +313,8 @@ module.exports = app => {
 
                 await transaction.delete(this.ctx.service.changeAtt.tableName, { tid: id });
                 await transaction.delete(this.ctx.service.materialFile.tableName, { tid: id });
+
+                await transaction.delete(this.ctx.service.advanceFile.tableName, { tid: id });
                 await transaction.commit();
                 return true;
             } catch (err) {

+ 25 - 20
app/service/tender_info.js

@@ -34,17 +34,17 @@ module.exports = app => {
          * @param tenderId - 标段Id
          * @param projectId - 项目Id
          * @param transaction - 事务
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async addTenderInfo(tenderId, projectId, transaction) {
             const info = JSON.parse(JSON.stringify(defaultInfo));
             info.tid = tenderId;
             info.pid = projectId;
             for (const pi of parseInfo) {
-                info[pi] = JSON.stringify(info[pi])
+                info[pi] = JSON.stringify(info[pi]);
             }
             for (const pi of arrayInfo) {
-                info[pi] = JSON.stringify(info[pi])
+                info[pi] = JSON.stringify(info[pi]);
             }
 
             if (transaction) {
@@ -58,10 +58,10 @@ module.exports = app => {
         /**
          * 获取标段相关信息
          * @param tenderId
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async getTenderInfo(tenderId) {
-            let info = await this.getDataByCondition({tid: tenderId});
+            let info = await this.getDataByCondition({ tid: tenderId });
             // 兼容不存在info的情况
             if (!info) {
                 info = await this.addTenderInfo(tenderId, this.ctx.session.sessionProject.id);
@@ -82,7 +82,7 @@ module.exports = app => {
         /**
          * 获取标段相关信息(报表用)
          * @param tenderId
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async getTenderInfoEx(tenderId) {
             const sql = 'select t2.name, t1.* from zh_tender_info t1 inner join zh_tender t2 on t2.id = t1.tid where t1.tid = ?';
@@ -110,7 +110,7 @@ module.exports = app => {
          * 保存标段相关信息
          *
          * @param data
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async saveTenderInfo(tenderId, data) {
             for (const di in data) {
@@ -118,14 +118,14 @@ module.exports = app => {
                     data[di] = JSON.stringify(data[di]);
                 }
             }
-            await this.db.update(this.tableName, data, {where: {tid: tenderId}});
+            await this.db.update(this.tableName, data, { where: { tid: tenderId } });
         }
 
         async savePrecision(tenderId, newPrecision, oldPrecision, decimal) {
             const changePrecision = [];
             const units = await this.ctx.service.ledger.getTenderUsedUnits(tenderId);
             const defUnits = this._.map(newPrecision, 'units');
-            const otherUnits = units.filter(function (u) {return defUnits.indexOf(u) === -1});
+            const otherUnits = units.filter(function(u) { return defUnits.indexOf(u) === -1; });
             let changeUnits = [];
             for (const prop in newPrecision) {
                 if (oldPrecision[prop]) {
@@ -148,15 +148,18 @@ module.exports = app => {
             if (changeUnits.length > 0) {
                 const bills = await this.ctx.service.ledger.getAllDataByCondition({
                     columns: ['id', 'unit', 'unit_price', 'sgfh_qty', 'sjcl_qty', 'qtcl_qty', 'deal_qty'],
-                    where: {tender_id: tenderId, unit: changeUnits, is_leaf: true}
+                    where: { tender_id: tenderId, unit: changeUnits, is_leaf: true },
                 });
                 const pos = changeUnits.length > 0 ? await this.ctx.service.pos.getPosDataByUnits(tenderId, changeUnits) : [];
 
                 for (const b of bills) {
                     const precision = this.ctx.helper.findPrecision(newPrecision, b.unit);
-                    const bPos = this._.filter(pos, {lid: b.id});
+                    const bPos = this._.filter(pos, { lid: b.id });
                     if (bPos.length > 0) {
-                        let sgfh_qty = 0, sjcl_qty = 0, qtcl_qty = 0, quantity = 0;
+                        let sgfh_qty = 0,
+                            sjcl_qty = 0,
+                            qtcl_qty = 0,
+                            quantity = 0;
                         for (const p of bPos) {
                             this.ctx.helper.checkFieldPrecision(p, ['sgfh_qty', 'sjcl_qty', 'qtcl_qty'], precision.value);
                             p.quantity = this.ctx.helper.add(this.ctx.helper.add(p.sgfh_qty, p.sjcl_qty), p.qtcl_qty);
@@ -183,7 +186,7 @@ module.exports = app => {
                 const transaction = await this.db.beginTransaction();
                 try {
                     await transaction.update(this.tableName,
-                        {precision: JSON.stringify(newPrecision)}, {where: {tid: tenderId}});
+                        { precision: JSON.stringify(newPrecision) }, { where: { tid: tenderId } });
                     if (bills.length > 0) await transaction.updateRows(this.ctx.service.ledger.tableName, bills);
                     if (pos.length > 0) await transaction.updateRows(this.ctx.service.pos.tableName, pos);
                     await transaction.commit();
@@ -193,16 +196,18 @@ module.exports = app => {
                 }
             } else {
                 await this.db.update(this.tableName,
-                    {precision: JSON.stringify(newPrecision)}, {where: {tid: tenderId}});
+                    { precision: JSON.stringify(newPrecision) }, { where: { tid: tenderId } });
             }
         }
 
         async saveDecimal(tenderId, newDecimal, oldDecimal) {
-            const changeBills = [], calcUp = newDecimal.up < oldDecimal.up, calcTp = newDecimal.tp !== oldDecimal.tp;
+            const changeBills = [],
+                calcUp = newDecimal.up < oldDecimal.up,
+                calcTp = newDecimal.tp !== oldDecimal.tp;
             if (calcUp || calcTp) {
                 const bills = await this.ctx.service.ledger.getAllDataByCondition({
                     columns: ['id', 'unit_price', 'sgfh_qty', 'sjcl_qty', 'qtcl_qty', 'deal_qty', 'quantity'],
-                    where: {tender_id: tenderId, is_leaf: true}
+                    where: { tender_id: tenderId, is_leaf: true },
                 });
                 for (const b of bills) {
                     const cb = { id: b.id };
@@ -219,19 +224,19 @@ module.exports = app => {
                 const transaction = await this.db.beginTransaction();
                 try {
                     await transaction.update(this.tableName,
-                        {decimal: JSON.stringify(newDecimal)}, { where: { tid: tenderId }});
+                        { decimal: JSON.stringify(newDecimal) }, { where: { tid: tenderId } });
                     await transaction.updateRows(this.ctx.service.ledger.tableName, changeBills);
                     await transaction.commit();
-                } catch(error) {
+                } catch (error) {
                     await transaction.rollback();
                     throw error;
                 }
             } else {
                 await this.db.update(this.tableName,
-                    {decimal: JSON.stringify(newDecimal)}, { where: { tid: tenderId }});
+                    { decimal: JSON.stringify(newDecimal) }, { where: { tid: tenderId } });
             }
         }
     }
 
     return TenderInfo;
-};
+};

+ 24 - 0
app/view/advance/audit_btn.ejs

@@ -0,0 +1,24 @@
+
+<% if (ctx.advance.status === auditConst.status.uncheck) { %>
+<a href="#sub-sp" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm">上报审批</a>
+<% } else if (ctx.advance.status === auditConst.status.checking) { %>
+    <% if (ctx.advance.curAuditor && ctx.advance.curAuditor.audit_id === ctx.session.sessionUser.accountId) { %>
+        <a id="sp-done-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sp-done" class="btn btn-success btn-sm">审批通过</a>
+        <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm">审批退回</a>
+    <% } else { %>
+        <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm sp-list-btn">审批中</a>
+    <% } %>
+<% } else if (ctx.advance.status === auditConst.status.checked) { %>
+    <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm sp-list-btn">审批完成</a>
+<% } else if (ctx.advance.status === auditConst.status.checkNo) { %>
+    <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-muted sp-list-btn">审批退回</a>
+    <% if (ctx.session.sessionUser.accountId === ctx.advance.uid) { %>
+        <a href="#sub-sp" data-target="#sub-sp" data-toggle="modal"  class="btn btn-primary btn-sm">重新上报</a>
+    <% } %>
+<% } else if (ctx.advance.status === auditConst.status.checkNoPre) { %>
+    <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-muted sp-list-btn">审批退回</a>
+    <% if (ctx.session.sessionUser.accountId === ctx.advance.curAuditor.audit_id) { %>
+        <a id="sp-done-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sp-done" class="btn btn-success btn-sm">审批通过</a>
+        <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm">审批退回</a>
+    <% } %>
+<% } %>

+ 397 - 0
app/view/advance/detail.ejs

@@ -0,0 +1,397 @@
+<% 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="d-inline-block">
+                    第<%- advance.order %>期
+                </div>
+            </div>
+            <div id="au-btn">
+                <% include ./audit_btn.ejs %>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0">
+                <div class="col-xl-8 mx-auto">
+                    <h4 class="mt-3 text-center mb-3">第<%- advance.order %>期
+                        <%- advance.type === auditConst.type.start ? '开工' : '材料' %>预付款</h4>
+                    <table class="table table-bordered">
+                        <thead>
+                            <tr>
+                                <th colspan="4" class="text-center">
+                                    签约<%- advance.type === auditConst.type.start ? '开工' : '材料' %>预付款:<%- ctx.helper.formatMoney(advancePayTotal) %>
+                                    元
+                                </th>
+                            </tr>
+                        </thead>
+                        <tbody id="pay-content">
+                            <% if(isEdited && ctx.session.sessionUser.accountId === ctx.advance.uid) { %>
+                            <tr>
+                                <th width="150">支付比例</th>
+                                <td class="text-right">
+                                    <div class="input-group input-group-sm">
+                                        <input type="number" class="pay-input form-control nospin text-right"
+                                            max="<%- max_pr %>" min="1" placeholder="请填写支付比例,将自动计算本期金额" data-type="0"
+                                            value="<%- advance.cur_amount && ctx.helper.mul(ctx.helper.div(advance.cur_amount, advancePayTotal), 100, 2) || 0 %>">
+                                        <div class="input-group-append"><span class="input-group-text">%</span></div>
+                                    </div>
+                                </td>
+                                <th width="150">本期金额</th>
+                                <td class="text-right">
+                                    <div class="input-group input-group-sm">
+                                        <input type="number" class="pay-input form-control nospin text-right" min="1"
+                                            placeholder="请填写本期金额,将自动计算支付比例" data-type="1"
+                                            value="<%- advance.cur_amount && advance.cur_amount.toFixed(decimal) %>">
+                                        <div class="input-group-append"><span class="input-group-text">元</span></div>
+                                    </div>
+                                </td>
+                            </tr>
+                            <% } else {%>
+                            <tr>
+                                <th width="150">支付比例</th>
+                                <td class="text-right">
+                                    <%- advance.cur_amount && ctx.helper.mul(ctx.helper.div(advance.cur_amount, advancePayTotal), 100, 2) || 0 %>%
+                                </td>
+                                <th width="150">本期金额</th>
+                                <td class="text-right">
+                                    <%- ctx.helper.formatMoney((advance.cur_amount || 0), ',', decimal) %>
+                                </td>
+                            </tr>
+                            <% } %>
+                            <tr>
+                                <th>截止上期</th>
+                                <td class="text-right" id="p_total1">
+                                    <%- ctx.helper.formatMoney((prevAdvance && prevAdvance.prev_total_amount || 0), ',', decimal) %>
+                                </td>
+                                <th>截止本期金额</th>
+                                <td class="text-right" id="p_total2">
+                                    <%- ctx.helper.formatMoney((prevAdvance && prevAdvance.prev_total_amount + advance.cur_amount || (advance.cur_amount || 0)), ',', decimal) %>
+                                </td>
+                            </tr>
+                            <tr>
+                                <th>备注</th>
+                                <td colspan="3">
+                                    <textarea id="ad-remark" class="form-control form-control-sm"
+                                        <%- isEdited && ctx.session.sessionUser.accountId === ctx.advance.uid ? '' : 'disabled' %>></textarea>
+                                </td>
+                            </tr>
+                        </tbody>
+                    </table>
+                    <table class="table table-bordered mt-3">
+                        <thead>
+                            <tr>
+                                <th colspan="3" class="text-center">附件</th>
+                            </tr>
+                        </thead>
+                        <tbody id="file-content">
+                        </tbody>
+                    </table>
+                    <% if(isEdited && ctx.session.sessionUser.accountId === ctx.advance.uid) { %>
+                    <table class="table table-bordered mt-3">
+                        <thead>
+                            <tr>
+                                <th colspan="2" class="text-center">审批流程</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <tr>
+                                <td width="30%">
+                                    <div class="card">
+                                        <ul class="list-group list-group-flush" id="auditors2">
+                                            <% auditors.forEach((item, idx) => { %>
+                                            <% if (idx === 0) { %>
+                                            <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                                <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                                <small class="text-muted"><%- item.role %></small>
+                                            </li>
+                                            <% } else if(idx === auditors.length -1 && idx !== 0) { %>
+                                            <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                                <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                                <small class="text-muted"><%- item.role %></small>
+                                            </li>
+                                            <% } else {%>
+                                            <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                                <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                                <small class="text-muted"><%- item.role %></small>
+                                            </li>
+                                            <% } %>
+                                            <% }) %>
+                                        </ul>
+                                    </div>
+                                </td>
+                                <td width="70%">
+                                    <div class="dropdown">
+                                        <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button"
+                                            id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true"
+                                            aria-expanded="false">
+                                            添加审批流程
+                                        </button>
+                                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton"
+                                            style="width:220px">
+                                            <div class="mb-2 p-2"><input class="form-control form-control-sm"
+                                                    placeholder="姓名/手机 检索" id="gr-search" autocomplete="off"></div>
+                                            <dl class="list-unstyled book-list">
+                                                <% accountGroup.forEach((group, idx) => { %>
+                                                    <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>" data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                                                    <div class="dd-content" data-toggleid="<%- idx %>">
+                                                        <% group.groupList.forEach(item => { %>
+                                                            <% if (item.id !== ctx.session.sessionUser.accountId) { %>
+                                                                <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>" >
+                                                                    <p class="mb-0 d-flex">
+                                                                        <span class="text-primary"><%- item.name %></span>
+                                                                        <span class="ml-auto"><%- item.mobile %></span></p>
+                                                                    <span class="text-muted"><%- item.role %></span>
+                                                                </dd>
+                                                            <% } %>
+                                                        <% });%>
+                                                    </div>
+                                                <% }) %>
+                                            </dl>
+                                        </div>
+                                    </div>
+
+                                    <div class="card mt-3">
+                                        <div class="card-header">
+                                            审批流程
+                                        </div>
+                                        <ul class="list-group list-group-flush" id="auditors">
+                                            <% ctx.advance.auditors && ctx.advance.auditors.forEach((item, idx) => { %>
+                                            <li class="list-group-item" auditorId="<%- item.audit_id %>">
+                                                <a href="" class="text-danger pull-right">移除</a><%- idx+1 %>
+                                                <%- item.name %>
+                                                <small class="text-muted"><%- item.role %> </small>
+                                                <p class="m-0 ml-2">
+                                                    <small class="text-muted"><%- item.company %></small>
+                                                </p>
+                                            </li>
+                                            <% }) %>
+                                        </ul>
+                                    </div>
+                                </td>
+                            </tr>
+                        </tbody>
+                    </table>
+                    <% } else {%>
+                    <table class="table table-bordered mt-3">
+                        <thead>
+                            <tr>
+                                <th colspan="2" class="text-center">审批流程</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <tr>
+                                <td width="30%">
+                                    <div class="card">
+                                        <ul class="list-group list-group-flush">
+                                            <% auditors.forEach((item, idx) => { %>
+                                            <% if (idx === 0) { %>
+                                            <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                                <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                                <small class="text-muted"><%- item.role %></small>
+                                                <span class="pull-right">原报</span>
+                                            </li>
+                                            <% } else if(idx === auditors.length -1 && idx !== 0) { %>
+                                            <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                                <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                                <small class="text-muted"><%- item.role %></small>
+                                                <span class="pull-right">终审</span>
+                                            </li>
+                                            <% } else {%>
+                                            <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                                <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                                <small class="text-muted"><%- item.role %></small>
+                                                <span
+                                                    class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
+                                            </li>
+                                            <% } %>
+                                            <% }) %>
+                                        </ul>
+                                    </div>
+                                </td>
+                                <td width="70%">
+                                    <% auditHistory.forEach((auditors, idx) => { %>
+                                    <div class="<%- idx < auditHistory.length - 1 ? 'fold-card' : '' %>">
+                                        <div class="text-center text-muted"
+                                            <%- idx === auditHistory.length - 1 ? `id="end-target"` : "" %>>
+                                            <%- idx === auditHistory.length - 1 ? 1 : idx+1 %>#</div>
+                                        <ul class="timeline-list list-unstyled mt-2">
+                                            <% auditors.forEach((auditor, index) => { %>
+                                            <% if (index === 0) { %>
+                                            <li class="timeline-list-item pb-2">
+                                                <div class="timeline-item-date">
+                                                    <%- ctx.helper.formatDate(auditor.create_time) %>
+                                                </div>
+                                                <div class="timeline-item-tail"></div>
+                                                <div class="timeline-item-icon bg-success text-light">
+                                                    <i class="fa fa-caret-down"></i>
+                                                </div>
+                                                <div class="timeline-item-content">
+                                                    <div class="card">
+                                                        <div class="card-body p-3">
+                                                            <div class="card-text">
+                                                                <p class="mb-1"><span
+                                                                        class="h5"><%- advance.user.name %></span><span
+                                                                        class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                                </p>
+                                                                <p class="text-muted mb-0"><%- advance.user.role %></p>
+                                                            </div>
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </li>
+                                            <li class="timeline-list-item pb-2">
+                                                <div class="timeline-item-date">
+                                                    <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                </div>
+                                                <% if(index < auditors.length - 1) { %>
+                                                <div class="timeline-item-tail"></div>
+                                                <% } %>
+                                                <% if(auditor.status === auditConst.status.checked) { %>
+                                                <div class="timeline-item-icon bg-success text-light">
+                                                    <i class="fa fa-check"></i>
+                                                </div>
+                                                <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                                <div class="timeline-item-icon bg-warning text-light">
+                                                    <i class="fa fa-level-up"></i>
+                                                </div>
+                                                <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                <div class="timeline-item-icon bg-warning text-light">
+                                                    <i class="fa fa-ellipsis-h"></i>
+                                                </div>
+                                                <% } else {%>
+                                                <div class="timeline-item-icon bg-secondary text-light">
+                                                </div>
+                                                <% } %>
+                                                <div class="timeline-item-content">
+                                                    <div class="card">
+                                                        <div class="card-body p-3">
+                                                            <div class="card-text">
+                                                                <p class="mb-1"><span
+                                                                        class="h5"><%- auditor.name %></span><span
+                                                                        class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                </p>
+                                                                <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                            </div>
+                                                        </div>
+                                                        <!--审批意见-->
+                                                        <% if (auditor.opinion) { %>
+                                                        <div class="card-body p-3 border-top">
+                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                        </div>
+                                                        <% } %>
+                                                    </div>
+                                                </div>
+                                            </li>
+                                            <% } else {%>
+                                            <li class="timeline-list-item pb-2">
+                                                <div class="timeline-item-date">
+                                                    <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                </div>
+                                                <% if(index < auditors.length - 1) { %>
+                                                <div class="timeline-item-tail"></div>
+                                                <% } %>
+                                                <% if(auditor.status === auditConst.status.checked) { %>
+                                                <div class="timeline-item-icon bg-success text-light">
+                                                    <i class="fa fa-check"></i>
+                                                </div>
+                                                <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                                <div class="timeline-item-icon bg-warning text-light">
+                                                    <i class="fa fa-level-up"></i>
+                                                </div>
+                                                <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                <div class="timeline-item-icon bg-warning text-light">
+                                                    <i class="fa fa-ellipsis-h"></i>
+                                                </div>
+                                                <% } else { %>
+                                                <div class="timeline-item-icon bg-secondary text-light">
+                                                </div>
+                                                <% } %>
+                                                <div class="timeline-item-content">
+                                                    <div class="card">
+                                                        <div class="card-body p-3">
+                                                            <div class="card-text">
+                                                                <p class="mb-1"><span
+                                                                        class="h5"><%- auditor.name %></span>
+                                                                    <span
+                                                                        class="pull-right
+                                                                                    <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                        <%- auditor.status === auditConst.status.checkNo ? advance.user.name : '' %>
+                                                                        <%- auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : '' %>
+                                                                    </span>
+                                                                </p>
+                                                                <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                            </div>
+                                                        </div>
+                                                        <!--审批意见-->
+                                                        <% if (auditor.opinion) { %>
+                                                        <div class="card-body p-3 border-top">
+                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                        </div>
+                                                        <% } %>
+                                                    </div>
+                                                </div>
+                                            </li>
+                                            <% } %>
+                                            <% }) %>
+                                        </ul>
+                                    </div>
+                                    <!-- 展开/收起历史流程 -->
+                                    <% if(idx === auditHistory.length - 1 && auditHistory.length !== 1) { %>
+                                    <div class="text-right"><a href="javascript: void(0);" id="fold-btn"
+                                            data-target="show" data-idx="<%- idx + 1 %>">展开历史审批流程</a></div>
+                                    <% } %>
+                                    <% }) %>
+                                </td>
+                            </tr>
+                        </tbody>
+                    </table>
+                    <% } %>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const cur_uid = parseInt('<%- ctx.session.sessionUser.accountId %>');
+    const decimal = parseInt('<%- decimal %>');
+    const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
+    const advance = JSON.parse('<%- JSON.stringify(advance) %>');
+    const prevAdvance = JSON.parse('<%- JSON.stringify(prevAdvance) %>');
+    const isEdited = JSON.parse('<%- isEdited %>');
+    const advancePayTotal = parseFloat('<%- advancePayTotal %>');
+    const preUrl = '<%- preUrl %>';
+    const fileList = JSON.parse('<%- JSON.stringify(fileList) %>') || [];
+    const whiteList = JSON.parse('<%- JSON.stringify(whiteList) %>');
+    const curAuditor = JSON.parse('<%- JSON.stringify(ctx.advance.curAuditor) %>');
+
+    // 展开历史审核记录
+    $('td #fold-btn').click(function () {
+        const type = $(this).data('target')
+        const auditCard = $(this).parent().parent()
+        if (type === 'show') {
+            $(this).data('target', 'hide')
+            auditCard.find('.fold-card').slideDown('swing', () => {
+                auditCard.find('#end-target').text($(this).data('idx') + '#')
+                auditCard.find('#fold-btn').text('收起历史审核记录')
+            })
+        } else {
+            $(this).data('target', 'show')
+            auditCard.find('.fold-card').slideUp('swing', () => {
+                auditCard.find('#end-target').text('1#')
+                auditCard.find('#fold-btn').text('展开历史审核记录')
+            })
+        }
+    });
+    // 处理换行
+    advance.remark && $('#ad-remark').html(advance.remark.replace(/<br\/>/g, '\n').replace(/' '/, '\s'));
+</script>
+<% if(isEdited && ctx.session.sessionUser.accountId === ctx.advance.uid) { %>
+<script>
+    const accountList = JSON.parse('<%- JSON.stringify(accountList) %>');
+    const accountGroup = JSON.parse('<%- JSON.stringify(accountGroup) %>');
+</script>
+<% } %>
+

+ 99 - 0
app/view/advance/index.ejs

@@ -0,0 +1,99 @@
+<% 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="d-inline-block">
+                    <div class="btn-group">
+                        <a class="btn btn-sm btn-light <%- type === 0 ? 'active' : '' %>" href="/tender/<%- ctx.tender.id %>/advance">
+                            开工预付款<%- type === 0 && latestOrder ? `(第${latestOrder.order}期)` : '' %>
+                        </a>
+                        <a class="btn btn-sm btn-light <%- type === 1 ? 'active' : '' %>" href="/tender/<%- ctx.tender.id %>/advance/material">
+                            材料预付款<%- type === 1 && latestOrder ? `(第${latestOrder.order}期)` : '' %>
+                        </a>
+                    </div>
+                </div>
+                <div class="d-inline-block ml-2">
+                    签约<%- type === 0 ? '开工' : '材料' %>预付款
+                    <b><%- ctx.helper.formatMoney(advancePayTotal) %></b> 元
+                </div>
+                <div class="d-inline-block ml-4" style="width:300px">
+                    <div class="progress">
+                        <div class="progress-bar bg-success" style="width: <%- progress.p_ratio%>%;" data-placement="bottom"
+                            data-toggle="tooltip" data-original-title="截止上期金额:¥<%- ctx.helper.formatMoney(progress.p_amount, ',', decimal) %>"><%- progress.p_ratio.toFixed() %>%</div>
+                        <div class="progress-bar bg-info" style="width:<%- progress.c_ratio%>%;" data-placement="bottom"
+                            data-toggle="tooltip" data-original-title="本期金额:¥<%- ctx.helper.formatMoney(progress.c_amount, ',', decimal) %>"><%- progress.c_ratio.toFixed() %>%</div>
+                        <div class="progress-bar bg-gray" style="width:<%- progress.s_ratio%>%;" data-placement="bottom"
+                            data-toggle="tooltip" data-original-title="未完成:¥<%- ctx.helper.formatMoney(progress.s_amount, ',', decimal) %>"><%- progress.s_ratio.toFixed() %>%</div>
+                    </div>
+                </div>
+            </div>
+            <div>
+                <% if(showAddBtn) { %>
+                    <form action="<%- preUrl %>" method="POST">
+                        <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
+                        <button type="submit" class="btn btn-primary btn-sm pull-right">开始新一期</button>
+                    </form>
+                    <!-- <a id="advance_add" href="" class="btn btn-primary btn-sm pull-right"></a> -->
+                <% } %>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0" >
+                <table class="table table-bordered">
+                    <thead>
+                        <tr>
+                            <th>期数</th>
+                            <th class="text-center" width="10%">支付比例</th>
+                            <th class="text-center" width="15%">本期金额</th>
+                            <th class="text-center" width="15%">截止上期</th>
+                            <th class="text-center" width="15%">截止本期金额</th>
+                            <th class="text-center">附件</th>
+                            <th class="text-center">进度</th>
+                            <th class="text-center" width="100">操作</th>
+                        </tr>
+                    </thead>
+                    <tbody id="advanceList">
+                        <% advanceList.forEach(item => { %>
+                            <tr>
+                                <td><a href="/tender/<%- ctx.tender.id %>/advance/<%- item.id %>/detail" data-id="<%- item.id %>">第<%- item.order %>期</a></td>
+                                <td><%- item.cur_amount && ctx.helper.mul(ctx.helper.div(item.cur_amount, advancePayTotal), 100).toFixed(2) || 0%>%</td>
+                                <td class="text-right"><%- ctx.helper.formatMoney(item.cur_amount, ',', decimal)%></td>
+                                <td class="text-right"><%- ctx.helper.formatMoney(item.prev_amount, ',', decimal)%></td>
+                                <td class="text-right"><%- ctx.helper.formatMoney(item.prev_total_amount, ',', decimal)%></td>
+                                <td><a class="btn btn-sm" href="#file" data-toggle="modal" data-target="#file" data-id="<%- item.id %>"><i
+                                            class="fa fa-paperclip"></i> <%- item.fileList.length %></a></td>
+                                <td>
+                                    <% if (item.curAuditor) { %>
+                                        <a href="#sp-list" data-toggle="modal" data-target="#sp-list" data-vid="<%- item.id %>"><%- item.curAuditor.name %><%if (item.curAuditor.role !== '' && item.curAuditor.role !== null) { %>-<%- item.curAuditor.role %><% } %></a>
+                                    <% } %>
+                                    <span class="<%- auditConst.statusClass[item.status] %>">
+                                        <%- auditConst.statusString[item.status] %>
+                                    </span>
+                                </td>
+                                <td>
+                                    <% if((item.status === auditConst.status.uncheck || item.status === auditConst.status.checkNo) && item.uid === ctx.session.sessionUser.accountId) { %>
+                                        <a href="/tender/<%- ctx.tender.id %>/advance/<%- item.id %>/detail" class="btn btn-primary btn-sm">编辑</a>
+                                    <% } else if(item.status === auditConst.status.checking && item.curAuditor.audit_id === ctx.session.sessionUser.accountId) {%>
+                                        <a href="/tender/<%- ctx.tender.id %>/advance/<%- item.id %>/detail" class="btn btn-success btn-sm">审批</a>
+                                    <% } else {%>
+                                        <span class="<%- auditConst.statusClass[item.status] %>"><%- auditConst.statusString[item.status] %></span>
+                                    <% } %>
+                                </td>
+                            </tr>
+                        <% }) %>
+                    </tbody>
+                </table>
+            </div>
+        </div>
+    </div>
+</div>
+</div>
+<script>
+    const type = parseInt('<%- type %>');
+    const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
+    const advanceList = JSON.parse('<%- JSON.stringify(advanceList) %>');
+    const decimal = parseInt('<%- decimal %>');
+</script>

+ 66 - 0
app/view/advance/modal.ejs

@@ -0,0 +1,66 @@
+
+<!--附件-->
+<div class="modal fade" id="file" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">附件</h5>
+            </div>
+            <div class="modal-body">
+                <div class="modal-height-500">
+                    <table class="table table-sm table-bordered" id="file-content">
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <!-- <button type="button" class="btn btn-primary">确定</button> -->
+            </div>
+        </div>
+    </div>
+</div>
+<!--审批流程/结果-->
+<div class="modal fade" id="sp-list" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">审批流程</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-4">
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush" id="auditor-list">
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto" id="audit-list">
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    // 展开历史审核记录
+    $('#audit-list').on('click', 'a', function() {
+        const type = $(this).data('target')
+        const auditCard = $(this).parent().parent()
+        if (type === 'show') {
+            $(this).data('target', 'hide')
+            auditCard.find('.fold-card').slideDown('swing', () => {
+                auditCard.find('#end-target').text($(this).data('idx') + '#')
+                auditCard.find('#fold-btn').text('收起历史审核记录')
+            })
+        } else {
+            $(this).data('target', 'show')
+            auditCard.find('.fold-card').slideUp('swing', () => {
+                auditCard.find('#end-target').text('1#')
+                auditCard.find('#fold-btn').text('展开历史审核记录')
+            })
+        }
+    });
+</script>

+ 738 - 0
app/view/advance/modal_audit.ejs

@@ -0,0 +1,738 @@
+<!--上报审批-->
+<div class="modal fade" id="sub-sp" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">上报审批</h5>
+            </div>
+            <div class="modal-body">
+                <h5 id="tm-success" style="display: none;">
+                    确认上报第<%- advance.order %>期<%- advance.type === 0 ? '开工' : '材料' %>预付款?</h5>
+                <h5 id="tm-fail" class="text-danger">无法上报,请设置审批流程。</h5>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <!--可以上报显示 确认上报 按钮-->
+                <button id="tm-submit" style="display: none;" type="button" class="btn btn-sm btn-primary">确认上报</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--添加附件-->
+<div class="modal fade" id="addfujian">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="myModalLabel">上传附件</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <p>大小限制:30MB,支持office等文档格式、图片格式、压缩包格式</p>
+                <!-- <p><a href="javascript: void(0);" class="btn btn-primary" id="file-modal-target">选择文件</a></p> -->
+                <input type="file" id="file-modal" multiple="multiple">
+            </div>
+            <div class="modal-footer">
+                <button id="file-cancel" type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <button id="file-ok" type="button" class="btn btn-primary">添加</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--审批流程/结果-->
+<div class="modal fade" id="sp-list" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">审批流程</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-4">
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush">
+                                <% auditors.forEach((item, idx) => { %>
+                                <% if (idx === 0) { %>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right">原报</span>
+                                </li>
+                                <% } else if(idx === auditors.length -1 && idx !== 0) { %>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right">终审</span>
+                                </li>
+                                <% } else {%>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
+                                </li>
+                                <% } %>
+                                <% }) %>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto">
+                        <% auditHistory.forEach((auditors, idx) => { %>
+                        <div class="<%- idx < auditHistory.length - 1 ? 'fold-card' : '' %>">
+                            <div class="text-center text-muted"
+                                <%- idx === auditHistory.length - 1 ? `id="end-target"` : "" %>>
+                                <%- idx === auditHistory.length - 1 ? 1 : idx+1 %>#</div>
+                            <ul class="timeline-list list-unstyled mt-2">
+                                <% auditors.forEach((auditor, index) => { %>
+                                <% if (index === 0) { %>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.create_time) %>
+                                    </div>
+                                    <div class="timeline-item-tail"></div>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-caret-down"></i>
+                                    </div>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span
+                                                            class="h5"><%- advance.user.name %></span><span
+                                                            class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- advance.user.role %></p>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </li>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.end_time) %>
+                                    </div>
+                                    <% if(index < auditors.length - 1) { %>
+                                    <div class="timeline-item-tail"></div>
+                                    <% } %>
+                                    <% if(auditor.status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-check"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-level-up"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-ellipsis-h"></i>
+                                    </div>
+                                    <% } else {%>
+                                    <div class="timeline-item-icon bg-secondary text-light">
+                                    </div>
+                                    <% } %>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
+                                                            class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                </div>
+                                            </div>
+
+                                            <!--审批意见-->
+                                            <% if (auditor.opinion) { %>
+                                            <div class="card-body p-3 border-top">
+                                                <p style="margin: 0;"><%- auditor.opinion %></p>
+                                            </div>
+                                            <% } %>
+                                        </div>
+                                    </div>
+                                </li>
+                                <% } else {%>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.end_time) %>
+                                    </div>
+                                    <% if(index < auditors.length - 1) { %>
+                                    <div class="timeline-item-tail"></div>
+                                    <% } %>
+                                    <% if(auditor.status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-check"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-level-up"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-ellipsis-h"></i>
+                                    </div>
+                                    <% } else { %>
+                                    <div class="timeline-item-icon bg-secondary text-light">
+                                    </div>
+                                    <% } %>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span>
+                                                        <span
+                                                            class="pull-right
+                                                                            <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                            <%- auditor.status === auditConst.status.checkNo ? advance.user.name : '' %>
+                                                            <%- auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : '' %>
+                                                        </span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                </div>
+                                            </div>
+                                            <!--审批意见-->
+                                            <% if (auditor.opinion) { %>
+                                            <div class="card-body p-3 border-top">
+                                                <p style="margin: 0;"><%- auditor.opinion %></p>
+                                            </div>
+                                            <% } %>
+                                        </div>
+                                    </div>
+                                </li>
+                                <% } %>
+                                <% }) %>
+                            </ul>
+                        </div>
+                        <!-- 展开/收起历史流程 -->
+                        <% if(idx === auditHistory.length - 1 && auditHistory.length !== 1) { %>
+                            <div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
+                                    data-idx="<%- idx + 1 %>">展开历史审批流程</a>
+                            </div>
+                        <% } %>
+                        <% }) %>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% if (ctx.advance.status === auditConst.status.checking) { %>
+    <% if (ctx.advance.curAuditor && ctx.advance.curAuditor.audit_id === ctx.session.sessionUser.accountId) { %>
+        <!--审批通过-->
+        <div class="modal fade sp-location-list" id="sp-done" data-backdrop="static">
+            <div class="modal-dialog modal-lg" role="document">
+                <form class="modal-content" action="<%- preUrl %>/audit/check" method="post" onsubmit="return auditCheck(0);">
+                    <div class="modal-header">
+                        <h5 class="modal-title">审批通过</h5>
+                    </div>
+                    <div class="modal-body">
+                        <div class="row">
+                            <div class="col-4">
+                                <div class="card mt-3">
+                                    <ul class="list-group list-group-flush">
+                                        <% auditors.forEach((item, idx) => { %>
+                                        <% if (idx === 0) { %>
+                                        <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                            <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                            <small class="text-muted"><%- item.role %></small>
+                                            <span class="pull-right">原报</span>
+                                        </li>
+                                        <% } else if(idx === auditors.length -1 && idx !== 0) { %>
+                                        <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                            <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                            <small class="text-muted"><%- item.role %></small>
+                                            <span class="pull-right">终审</span>
+                                        </li>
+                                        <% } else {%>
+                                        <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                            <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                            <small class="text-muted"><%- item.role %></small>
+                                            <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
+                                        </li>
+                                        <% } %>
+                                        <% }) %>
+                                    </ul>
+                                </div>
+                            </div>
+                            <div class="col-8 modal-height-500" style="overflow: auto">
+                                <% auditHistory.forEach((auditors, idx) => { %>
+                                <div class="<%- idx < auditHistory.length - 1 ? 'fold-card' : '' %>">
+                                    <div class="text-center text-muted"
+                                        <%- idx === auditHistory.length - 1 ? `id="end-target"` : "" %>>
+                                        <%- idx === auditHistory.length - 1 ? 1 : idx+1 %>#</div>
+                                    <ul class="timeline-list list-unstyled mt-2">
+                                        <% auditors.forEach((auditor, index) => { %>
+                                        <% if (index === 0) { %>
+                                        <li class="timeline-list-item pb-2">
+                                            <div class="timeline-item-date">
+                                                <%- ctx.helper.formatDate(auditor.create_time) %>
+                                            </div>
+                                            <div class="timeline-item-tail"></div>
+                                            <div class="timeline-item-icon bg-success text-light">
+                                                <i class="fa fa-caret-down"></i>
+                                            </div>
+                                            <div class="timeline-item-content">
+                                                <div class="card">
+                                                    <div class="card-body p-3">
+                                                        <div class="card-text">
+                                                            <p class="mb-1"><span
+                                                                    class="h5"><%- advance.user.name %></span><span
+                                                                    class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                            </p>
+                                                            <p class="text-muted mb-0"><%- advance.user.role %></p>
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </li>
+                                        <li class="timeline-list-item pb-2">
+                                            <div class="timeline-item-date">
+                                                <%- ctx.helper.formatDate(auditor.end_time) %>
+                                            </div>
+                                            <% if(index < auditors.length - 1) { %>
+                                            <div class="timeline-item-tail"></div>
+                                            <% } %>
+                                            <% if(auditor.status === auditConst.status.checked) { %>
+                                            <div class="timeline-item-icon bg-success text-light">
+                                                <i class="fa fa-check"></i>
+                                            </div>
+                                            <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                            <div class="timeline-item-icon bg-warning text-light">
+                                                <i class="fa fa-level-up"></i>
+                                            </div>
+                                            <% } else if(auditor.status === auditConst.status.checking) { %>
+                                            <div class="timeline-item-icon bg-warning text-light">
+                                                <i class="fa fa-ellipsis-h"></i>
+                                            </div>
+                                            <% } else {%>
+                                            <div class="timeline-item-icon bg-secondary text-light">
+                                            </div>
+                                            <% } %>
+                                            <div class="timeline-item-content">
+                                                <div class="card">
+                                                    <div class="card-body p-3">
+                                                        <div class="card-text">
+                                                            <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
+                                                                    class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                            </p>
+                                                            <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                        </div>
+                                                    </div>
+                                                    <!--审批意见-->
+                                                    <% if(auditor.status !== auditConst.status.uncheck) { %>
+                                                    <div class="card-body p-3 border-top">
+                                                        <% if (ctx.advance.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                        <label>审批意见<b class="text-danger">*</b></label>
+                                                        <textarea class="form-control form-control-sm"
+                                                            name="opinion">同意</textarea>
+                                                        <% } else { %>
+                                                        <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                        <% } %>
+                                                    </div>
+                                                    <% } %>
+                                                </div>
+                                            </div>
+                                        </li>
+                                        <% } else {%>
+                                        <li class="timeline-list-item pb-2">
+                                            <div class="timeline-item-date">
+                                                <%- ctx.helper.formatDate(auditor.end_time) %>
+                                            </div>
+                                            <% if(index < auditors.length - 1) { %>
+                                            <div class="timeline-item-tail"></div>
+                                            <% } %>
+                                            <% if(auditor.status === auditConst.status.checked) { %>
+                                            <div class="timeline-item-icon bg-success text-light">
+                                                <i class="fa fa-check"></i>
+                                            </div>
+                                            <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                            <div class="timeline-item-icon bg-warning text-light">
+                                                <i class="fa fa-level-up"></i>
+                                            </div>
+                                            <% } else if(auditor.status === auditConst.status.checking) { %>
+                                            <div class="timeline-item-icon bg-warning text-light">
+                                                <i class="fa fa-ellipsis-h"></i>
+                                            </div>
+                                            <% } else { %>
+                                            <div class="timeline-item-icon bg-secondary text-light">
+                                            </div>
+                                            <% } %>
+                                            <div class="timeline-item-content">
+                                                <div class="card">
+                                                    <div class="card-body p-3">
+                                                        <div class="card-text">
+                                                            <p class="mb-1"><span class="h5"><%- auditor.name %></span>
+                                                                <span
+                                                                    class="pull-right
+                                                                                    <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                    <%- auditor.status === auditConst.status.checkNo ? advance.user.name : '' %>
+                                                                    <%- auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : '' %>
+                                                                </span>
+                                                            </p>
+                                                            <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                        </div>
+                                                    </div>
+                                                    <!--审批意见-->
+                                                    <% if(auditor.status !== auditConst.status.uncheck) { %>
+                                                    <div class="card-body p-3 border-top">
+                                                        <% if (ctx.advance.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                        <label>审批意见<b class="text-danger">*</b></label>
+                                                        <textarea class="form-control form-control-sm"
+                                                            name="opinion">同意</textarea>
+                                                        <% } else { %>
+                                                        <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                        <% } %>
+                                                    </div>
+                                                    <% } %>
+                                                </div>
+                                            </div>
+                                        </li>
+                                        <% } %>
+                                        <% }) %>
+                                    </ul>
+                                </div>
+                                <!-- 展开/收起历史流程 -->
+                                <% if(idx === auditHistory.length - 1 && auditHistory.length !== 1) { %>
+                                <div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
+                                        data-idx="<%- idx + 1 %>">展开历史审批流程</a></div>
+                                <% } %>
+                                <% }) %>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                        <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+                        <input type="hidden" name="checkType" value="<%= auditConst.status.checked %>" />
+                        <button type="submit" class="btn btn-success btn-sm">确认通过</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+        <!--审批退回-->
+        <div class="modal fade sp-location-list" id="sp-back" data-backdrop="static">
+            <div class="modal-dialog modal-lg" role="document">
+                <form class="modal-content modal-lg" action="<%- preUrl %>/audit/check" method="post"
+                    onsubmit="return auditCheck(1);">
+                    <div class="modal-header">
+                        <h5 class="modal-title">审批退回</h5>
+                    </div>
+                    <div class="modal-body">
+                        <div class="row">
+                            <div class="col-4">
+                                <div class="card mt-3">
+                                    <ul class="list-group list-group-flush">
+                                        <% auditors.forEach((item, idx) => { %>
+                                        <% if (idx === 0) { %>
+                                        <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                            <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                            <small class="text-muted"><%- item.role %></small>
+                                            <span class="pull-right">原报</span>
+                                        </li>
+                                        <% } else if(idx === auditors.length -1 && idx !== 0) { %>
+                                        <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                            <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                            <small class="text-muted"><%- item.role %></small>
+                                            <span class="pull-right">终审</span>
+                                        </li>
+                                        <% } else {%>
+                                        <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                            <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                            <small class="text-muted"><%- item.role %></small>
+                                            <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
+                                        </li>
+                                        <% } %>
+                                        <% }) %>
+                                    </ul>
+                                </div>
+                            </div>
+                            <div class="col-8 modal-height-500" style="overflow: auto">
+                                <% auditHistory.forEach((auditors, idx) => { %>
+                                <div class="<%- idx < auditHistory.length - 1 ? 'fold-card' : '' %>">
+                                    <div class="text-center text-muted"
+                                        <%- idx === auditHistory.length - 1 ? `id="end-target"` : "" %>>
+                                        <%- idx === auditHistory.length - 1 ? 1 : idx+1 %>#</div>
+                                    <ul class="timeline-list list-unstyled mt-2">
+                                        <% auditors.forEach((auditor, index) => { %>
+                                        <% if (index === 0) { %>
+                                        <li class="timeline-list-item pb-2">
+                                            <div class="timeline-item-date">
+                                                <%- ctx.helper.formatDate(auditor.create_time) %>
+                                            </div>
+                                            <div class="timeline-item-tail"></div>
+                                            <div class="timeline-item-icon bg-success text-light">
+                                                <i class="fa fa-caret-down"></i>
+                                            </div>
+                                            <div class="timeline-item-content">
+                                                <div class="card">
+                                                    <div class="card-body p-3">
+                                                        <div class="card-text">
+                                                            <p class="mb-1"><span
+                                                                    class="h5"><%- advance.user.name %></span><span
+                                                                    class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                            </p>
+                                                            <p class="text-muted mb-0"><%- advance.user.role %></p>
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </li>
+                                        <li class="timeline-list-item pb-2">
+                                            <div class="timeline-item-date">
+                                                <%- ctx.helper.formatDate(auditor.end_time) %>
+                                            </div>
+                                            <% if(index < auditors.length - 1) { %>
+                                            <div class="timeline-item-tail"></div>
+                                            <% } %>
+                                            <% if(auditor.status === auditConst.status.checked) { %>
+                                            <div class="timeline-item-icon bg-success text-light">
+                                                <i class="fa fa-check"></i>
+                                            </div>
+                                            <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                            <div class="timeline-item-icon bg-warning text-light">
+                                                <i class="fa fa-level-up"></i>
+                                            </div>
+                                            <% } else if(auditor.status === auditConst.status.checking) { %>
+                                            <div class="timeline-item-icon bg-warning text-light">
+                                                <i class="fa fa-ellipsis-h"></i>
+                                            </div>
+                                            <% } else {%>
+                                            <div class="timeline-item-icon bg-secondary text-light">
+                                            </div>
+                                            <% } %>
+                                            <div class="timeline-item-content">
+                                                <div class="card">
+                                                    <div class="card-body p-3">
+                                                        <div class="card-text">
+                                                            <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
+                                                                    class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                            </p>
+                                                            <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                        </div>
+                                                    </div>
+
+                                                    <!--审批意见-->
+                                                    <% if(auditor.times === advance.times && auditor.status !== auditConst.status.uncheck) { %>
+                                                    <div class="card-body p-3 border-top">
+                                                        <% if (ctx.advance.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                        <label>审批意见<b class="text-danger">*</b></label>
+                                                        <textarea class="form-control form-control-sm"
+                                                            name="opinion">不同意</textarea>
+                                                        <% if (ctx.advance.curAuditor.audit_id === auditor.audit_id) { %>
+                                                        <div id="reject-process" class="alert alert-warning"
+                                                            style="margin-top: 15px;">
+                                                            <div class="form-check form-check-inline">
+                                                                <input class="form-check-input" type="radio" name="checkType"
+                                                                    id="inlineRadio1" value="<%- auditConst.status.checkNo %>">
+                                                                <label class="form-check-label" for="inlineRadio1">退回原报
+                                                                    <%- ctx.advance.user.name %></label>
+                                                            </div>
+                                                            <% if (auditor.order > 1 && auditor.audit_id !== auditors[0].audit_id) { %>
+                                                            <div class="form-check form-check-inline">
+                                                                <input class="form-check-input" type="radio" name="checkType"
+                                                                    id="inlineRadio2"
+                                                                    value="<%- auditConst.status.checkNoPre %>">
+                                                                <label class="form-check-label" for="inlineRadio2">退回上一审批人
+                                                                    <%- auditors[index-1].name %></label>
+                                                            </div>
+                                                            <% } %>
+                                                        </div>
+                                                        <% } %>
+                                                        <% } else if(auditor.status === auditConst.status.checked){ %>
+                                                        <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                        <% } %>
+                                                    </div>
+                                                    <% } %>
+                                                </div>
+                                            </div>
+                                        </li>
+                                        <% } else {%>
+                                        <li class="timeline-list-item pb-2">
+                                            <div class="timeline-item-date">
+                                                <%- ctx.helper.formatDate(auditor.end_time) %>
+                                            </div>
+                                            <% if(index < auditors.length - 1) { %>
+                                            <div class="timeline-item-tail"></div>
+                                            <% } %>
+                                            <% if(auditor.status === auditConst.status.checked) { %>
+                                            <div class="timeline-item-icon bg-success text-light">
+                                                <i class="fa fa-check"></i>
+                                            </div>
+                                            <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                            <div class="timeline-item-icon bg-warning text-light">
+                                                <i class="fa fa-level-up"></i>
+                                            </div>
+                                            <% } else if(auditor.status === auditConst.status.checking) { %>
+                                            <div class="timeline-item-icon bg-warning text-light">
+                                                <i class="fa fa-ellipsis-h"></i>
+                                            </div>
+                                            <% } else { %>
+                                            <div class="timeline-item-icon bg-secondary text-light">
+                                            </div>
+                                            <% } %>
+                                            <div class="timeline-item-content">
+                                                <div class="card">
+                                                    <div class="card-body p-3">
+                                                        <div class="card-text">
+                                                            <p class="mb-1"><span class="h5"><%- auditor.name %></span>
+                                                                <span
+                                                                    class="pull-right
+                                                                                    <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                    <%- auditor.status === auditConst.status.checkNo ? advance.user.name : '' %>
+                                                                    <%- auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : '' %>
+                                                                </span>
+                                                            </p>
+                                                            <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                        </div>
+                                                    </div>
+                                                    <!--审批意见-->
+                                                    <% if(auditor.times === advance.times && auditor.status !== auditConst.status.uncheck) { %>
+                                                    <div class="card-body p-3 border-top">
+                                                        <% if (ctx.advance.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                        <label>审批意见<b class="text-danger">*</b></label>
+                                                        <textarea class="form-control form-control-sm"
+                                                            name="opinion">不同意</textarea>
+                                                            <% if (ctx.advance.curAuditor.audit_id === auditor.audit_id) { %>
+                                                                <div id="reject-process" class="alert alert-warning"
+                                                                    style="margin-top: 15px;">
+                                                                    <div class="form-check form-check-inline">
+                                                                        <input class="form-check-input" type="radio" name="checkType"
+                                                                            id="inlineRadio1" value="<%- auditConst.status.checkNo %>">
+                                                                        <label class="form-check-label" for="inlineRadio1">退回原报
+                                                                            <%- ctx.advance.user.name %></label>
+                                                                    </div>
+                                                                    <% if (auditor.order > 1 && auditor.audit_id !== auditors[0].audit_id) { %>
+                                                                    <div class="form-check form-check-inline">
+                                                                        <input class="form-check-input" type="radio" name="checkType"
+                                                                            id="inlineRadio2"
+                                                                            value="<%- auditConst.status.checkNoPre %>">
+                                                                        <label class="form-check-label" for="inlineRadio2">退回上一审批人
+                                                                            <%- auditors[index-1].name %></label>
+                                                                    </div>
+                                                                    <% } %>
+                                                                </div>
+                                                                <% } %>
+                                                        <% } else { %>
+                                                        <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                        <% } %>
+
+                                                    </div>
+
+                                                    <% } %>
+                                                </div>
+                                            </div>
+                                        </li>
+                                        <% } %>
+                                        <% }) %>
+                                    </ul>
+                                </div>
+                                <!-- 展开/收起历史流程 -->
+                                <% if(idx === auditHistory.length - 1 && auditHistory.length !== 1) { %>
+                                <div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
+                                        data-idx="<%- idx + 1 %>">展开历史审批流程</a></div>
+                                <% } %>
+                                <% }) %>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                        <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+                        <button type="submit" class="btn btn-warning btn-sm">确认退回</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+    <% } %>
+<% } %>
+
+<script>
+    $('.sp-list-btn').click(function () {
+        const type = $(this).data('type')
+        if (type === 'hide') {
+            $('.sp-list-item').hide()
+            $('.modal-title').text('审批流程')
+        } else {
+            $('.sp-list-item').show()
+            $('.modal-title').text('重新上报')
+        }
+    });
+
+    function auditCheck(i) {
+        const inlineRadio1 = $('#inlineRadio1:checked').val()
+        const inlineRadio2 = $('#inlineRadio2:checked').val()
+        const opinion = $('textarea[name="opinion"]').eq(i).val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' ');
+        $('textarea[name="opinion"]').eq(i).val(opinion);
+        if (i === 1) {
+            if (!inlineRadio1 && !inlineRadio2) {
+                if (!$('#warning-text').length) {
+                    $('#reject-process').prepend('<p id="warning-text" style="color: red; margin: 0;">请选择退回流程</p>');
+                }
+                return false;
+            }
+            if ($('#warning-text').length) $('#warning-text').remove()
+        }
+        return true;
+    }
+    $('.sp-location-list').on('shown.bs.modal', function () {
+        const scrollBox = $(this).find('div[class="col-8 modal-height-500"]');
+        const bdiv = (scrollBox.offset() && scrollBox.offset().top) || 0;
+        scrollBox.scrollTop(0);
+        const hdiv = divSearch($(this).find('textarea')) ? $(this).find('textarea') : null;
+        const hdheight = hdiv ? hdiv.parents('.timeline-item-content').offset().top : null;
+        if (hdiv && scrollBox.length && scrollBox[0].scrollHeight > 390 && hdheight - bdiv > 390) {
+            scrollBox.scrollTop(hdheight - bdiv);
+        }
+    });
+    function divSearch(div) {
+        if (div.length > 0) {
+            return true;
+        }
+        return false;
+    }
+
+    function start() {
+        const pay_ratio = parseInt($(`.pay-input[data-type=0]`).val())
+        const cur_amount = parseInt($(`.pay-input[data-type=1]`).val())
+        if (!pay_ratio || !cur_amount) {
+            toastr.error('请填写本期金额!')
+            return false
+        }
+        const prev_amount = prevAdvance && prevAdvance.prev_total_amount || 0
+        const prev_total_amount = ZhCalc.add(cur_amount, prev_amount)
+        const remark = filterText($('#ad-remark').val())
+        const data = { pay_ratio, cur_amount, prev_amount, prev_total_amount, remark, status: auditConst.status.checking }
+        postData('<%- preUrl %>/audit/start', data, (data) => {
+            window.location.reload()
+        }, () => {
+            window.location.reload()
+        })
+        return false
+    }
+
+    function filterText(text) {
+        if (!text) return null
+        return text.replace(/(\r\n)|(\n)/g, '<br/>').replace(/\s/g, ' ')
+    }
+
+    // 展开历史审核记录
+    $('.modal-body #fold-btn').click(function () {
+        const type = $(this).data('target')
+        const auditCard = $(this).parent().parent()
+        if (type === 'show') {
+            $(this).data('target', 'hide')
+            auditCard.find('.fold-card').slideDown('swing', () => {
+                auditCard.find('#end-target').text($(this).data('idx') + '#')
+                auditCard.find('#fold-btn').text('收起历史审核记录')
+            })
+        } else {
+            $(this).data('target', 'show')
+            auditCard.find('.fold-card').slideUp('swing', () => {
+                auditCard.find('#end-target').text('1#')
+                auditCard.find('#fold-btn').text('展开历史审核记录')
+            })
+        }
+    });
+</script>

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

@@ -717,7 +717,7 @@
                 <% for (const [index,att] of attList.entries()) { %>
                 <tr>
                     <td><%- index+1 %></td>
-                    <td><a href="/change/download/file/<%- att.id %>"><%- att.filename %><%- att.fileext %></a></td>
+                    <td><a href="<%- att.filepath %>" target="_black"><%- att.filename %><%- att.fileext %></a></td>
                     <td><%- ctx.helper.bytesToSize(att.filesize) %></td>
                     <td><%- moment(att.in_time * 1000).format('YYYY-MM-DD') %></td>
                     <td>

+ 48 - 3
app/view/dashboard/index.ejs

@@ -28,7 +28,7 @@
                     <div class="card">
                         <div class="card-header">需要你处理</div>
                         <div class="card-body">
-                            <% if (auditTenders.length !== 0 || auditRevise.length !== 0 || auditStages.length !== 0 || auditChanges.length !== 0 || auditMaterial.length !== 0) { %>
+                            <% if (auditTenders.length !== 0 || auditRevise.length !== 0 || auditStages.length !== 0 || auditChanges.length !== 0 || auditMaterial.length !== 0 || auditAdvance.length !== 0) { %>
                                 <ul class="list-unstyled m-0">
                                     <% for (const t of auditTenders) { %>
                                         <% if (t.ledger_status === acLedger.status.checking) { %>
@@ -145,6 +145,35 @@
                                             </li>
                                         <% } %>
                                     <% } %>
+                                    <% for (const am of auditAdvance) { %>
+                                        <% if (am.mstatus !== acAdvance.status.checkNo) { %>
+                                            <li class="media pb-3 mb-3 border-bottom-1">
+                                                <div class="media-body">
+                                                    <div class="row">
+                                                        <div class="col-auto"><span class="badge badge-warning">预付款</span></div>
+                                                        <div class="col-6"><a href="/tender/<%- am.tid %>"><%- am.name %></a> 第<%- am.morder %>期</div>
+                                                        <div class="col-3 ml-auto text-right pl-0"><a href="/tender/<%- am.tid %>/advance/<%- am.vid %>/detail" class="btn btn-sm btn-outline-primary">审批</a></div>
+                                                    </div>
+                                                    <p class="mt-1 mb-0"><%- ctx.session.sessionUser.name %><small class="ml-1 text-muted"><%- (role ? '- ' + role : '') %></small>
+                                                        <span class="pull-right text-muted"><%- am.create_time.toLocaleString() %></span>
+                                                    </p>
+                                                </div>
+                                            </li>
+                                        <% } else { %>
+                                            <li class="media pb-3 mb-3 border-bottom-1">
+                                                <div class="media-body">
+                                                    <div class="row">
+                                                        <div class="col-auto"><span class="badge badge-warning">预付款</span></div>
+                                                        <div class="col-6"><a href="/tender/<%- am.tid %>"><%- am.name %></a> 第<%- am.morder %>期</div>
+                                                        <div class="col-3 ml-auto text-right pl-0"><a href="/tender/<%- am.tid %>/advance/<%- am.vid %>/detail" class="btn btn-sm btn-outline-primary">重新上报</a></div>
+                                                    </div>
+                                                    <p class="mt-1 mb-0"><%- ctx.session.sessionUser.name %><small class="ml-1 text-muted"><%- (role ? '- ' + role : '') %></small>
+                                                        <span class="pull-right text-muted"><%- am.end_time.toLocaleString() %></span>
+                                                    </p>
+                                                </div>
+                                            </li>
+                                        <% } %>
+                                    <% } %>
                             </ul>
                             <% } else { %>
                             <!--没有处理信息-->
@@ -198,7 +227,7 @@
                                                     <div class="row">
                                                         <div class="col-auto"><span class="badge badge-info">台帐审批</span></div>
                                                         <div class="col-6">
-                                                            <a data-id="<%- notice.id %>"href="/tender/<%- notice.tid %>/ledger"><%- notice.name %></a> 台帐<%- acLedger.statusString[notice.status]%>
+                                                            <a data-id="<%- notice.id %>"href="/tender/<%- notice.tid %>/ledger"><%- notice.name %></a> <%- acLedger.statusString[notice.status]%>
                                                         </div>
                                                     </div>
                                                     <p class="mt-1 mb-0"><%- notice.su_name %><small class="ml-1 text-muted"><%- (notice.su_role ? '- ' + notice.su_role : '') %></small>
@@ -222,7 +251,7 @@
                                                     </p>
                                                 </div>
                                             </li>
-                                        <% } else { %>
+                                        <% } else if(notice.type === pushType.change){ %>
                                             <li class="media pb-3 mb-3 border-bottom-1">
                                                 <div class="media-body">
                                                     <div class="row">
@@ -238,6 +267,22 @@
                                                     </p>
                                                 </div>
                                             </li>
+                                        <% } else if(notice.type === pushType.advance) { %>
+                                            <li class="media pb-3 mb-3 border-bottom-1">
+                                                <div class="media-body">
+                                                    <div class="row">
+                                                        <div class="col-auto"><span class="badge badge-warning">预付款</span></div>
+                                                        <div class="col-6">
+                                                            <a href="/tender/<%- notice.tid %>"><%- notice.name %></a>
+                                                            <a href="/tender/<%- notice.tid %>/advance/<%- notice.vid %>/detail">第<%- notice.order %>期</a>
+                                                            <%- acAdvance.statusString[notice.status]%>
+                                                        </div>
+                                                    </div>
+                                                    <p class="mt-1 mb-0"><%- notice.su_name %><small class="ml-1 text-muted"><%- (notice.su_role ? '- ' + notice.su_role : '') %></small>
+                                                        <span class="pull-right text-muted"><%- ctx.helper.formatFullDate(notice.create_time) %></span>
+                                                    </p>
+                                                </div>
+                                            </li>
                                         <% } %>
                                     <% } %>
                                 </ul>

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

@@ -22,16 +22,16 @@
             <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 mr-1">退回意见</a>
+                    <a href="#sp-list"  data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm pull-right text-dark mr-1 sp-list-btn">退回意见</a>
                 <% } else if (tender.ledger_status === auditConst.status.checking) { %>
                     <% if (curAuditor.audit_id === ctx.session.sessionUser.accountId) { %>
                         <a href="#sp-done" data-toggle="modal" data-target="#sp-done" class="btn btn-success btn-sm pull-right mr-1">审批通过</a>
                         <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm pull-right mr-1">审批退回</a>
                     <% } else {%>
-                        <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm pull-right text-dark mr-1">审批中</a>
+                        <a href="#sp-list"  data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm pull-right text-dark mr-1 sp-list-btn">审批中</a>
                     <% } %>
                 <% } else if (tender.ledger_status === auditConst.status.checked) { %>
-                <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-success btn-sm pull-right text-dark mr-1">审批通过</a>
+                <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-success btn-sm pull-right text-dark mr-1 sp-list-btn">审批通过</a>
                 <% } %>
             </div>
         </div>
@@ -108,4 +108,14 @@
         key: 'ledger-pos',
         colWidth: true,
     };
-</script>
+    $('.sp-list-btn').click(function () {
+        const type = $(this).data('type')
+        if (type === 'hide') {
+            $('.sp-list-item').hide()
+            $('.modal-title').text('审批流程')
+        } else {
+            $('.sp-list-item').show()
+            $('.modal-title').text('重新上报')
+        }
+    });
+</script>

+ 504 - 317
app/view/ledger/audit_modal.ejs

@@ -1,8 +1,8 @@
 <% if (tender.ledger_status === auditConst.status.checking && curAuditor.audit_id === ctx.session.sessionUser.accountId) { %>
 <!--审批通过-->
-<div class="modal fade" id="sp-done" data-backdrop="static">
+<div class="modal fade sp-location-list" id="sp-done" data-backdrop="static">
     <div class="modal-dialog modal-lg" role="document">
-        <form class="modal-content" action="/tender/<%- tender.id %>/ledger/audit/check" method="post" onsubmit="return auditCheck(0);">
+        <form class="modal-content" action="<%- preUrl %>/ledger/audit/check" method="post" onsubmit="return auditCheck(0);">
             <div class="modal-header">
                 <h5 class="modal-title">审批通过</h5>
             </div>
@@ -11,122 +11,173 @@
                     <div class="col-4">
                         <div class="card mt-3">
                             <ul class="list-group list-group-flush">
-                                <li class="list-group-item">
-                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- user.name %>  <small class="text-muted"><%- user.role %></small><span class="pull-right">原报</span>
+                                <% auditors.forEach((item, idx) => { %>
+                                <% if (idx === 0) { %>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right">原报</span>
+                                </li>
+                                <% } else if(idx === auditors.length -1 && idx !== 0) { %>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right">终审</span>
+                                </li>
+                                <% } else {%>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
                                 </li>
-                                <% for (let i = 0; i < auditors.length; i++) { %>
-                                    <li class="list-group-item">
-                                        <% if (i < auditors.length - 1) { %>
-                                            <i class="fa fa-chevron-circle-down"></i> <%- auditors[i].name %>  <small class="text-muted"><%- auditors[i].role %></small><span class="pull-right"><%= ctx.helper.transFormToChinese(i+1)%>审</span>
-                                        <% } else {%>
-                                            <i class="fa fa fa-stop-circle"></i> <%- auditors[i].name %>  <small class="text-muted"><%- auditors[i].role %></small><span class="pull-right">终审</span>
-                                        <% } %>
-                                    </li>
                                 <% } %>
+                                <% }) %>
                             </ul>
                         </div>
                     </div>
                     <div class="col-8 modal-height-500" style="overflow: auto">
-                        <% for (const ah of auditHistory) { %>
-                            <div class="card mt-3">
-                                <ul class="list-group list-group-flush">
-                                    <% for (let iA = 0; iA < ah.length; iA++) { %>
-                                        <% if (iA === 0) { %>
-                                            <li class="list-group-item">
-                                                <h5 class="card-title">
-                                                    <i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- user.name %> <small class="text-muted"><%- user.role %></small><span class="pull-right">原报</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <span class="text-success"><small><%- ah[iA].begin_time.toLocaleDateString() %></small> <% if (auditHistory.indexOf(ah) > 0) { %>重新<% } %>上报</span>
-                                                </div>
-                                            </li>
-                                            <li class="list-group-item">
-                                                <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><span class="pull-right"><%= ah[iA].sort === ah[iA].max_sort ? '终' : ctx.helper.transFormToChinese(ah[iA].sort) %>审</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <% if (ah[iA].status !== auditConst.status.uncheck) { %>
-                                                        <span class="<%- auditConst.statusClass[ah[iA].status] %>"><% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %><small><%- ah[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                    <% } %>
-                                                    <p class="card-text"><%- ah[iA].opinion %></p>
-                                                </div>
-                                            </li>
-                                        <% } else if (iA === ah.length - 1) { %>
-                                            <li class="list-group-item">
-                                                <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><span class="pull-right">终审</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <% if (ah[iA].status !== auditConst.status.uncheck) { %>
-                                                        <span class="<%- auditConst.statusClass[ah[iA].status] %>"><% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %><small><%- ah[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                    <% } %>
-                                                    <p class="card-text"><%- ah[iA].opinion %></p>
+                        <% auditHistory.forEach((auditors, idx) => { %>
+                        <div class="<%- idx < auditHistory.length - 1 ? 'fold-card' : '' %>">
+                            <div class="text-center text-muted"
+                                <%- idx === auditHistory.length - 1 ? `id="end-target"` : "" %>>
+                                <%- idx === auditHistory.length - 1 ? 1 : idx+1 %>#</div>
+                            <ul class="timeline-list list-unstyled mt-2">
+                                <% auditors.forEach((auditor, index) => { %>
+                                <% if (index === 0) { %>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.begin_time) %>
+                                    </div>
+                                    <div class="timeline-item-tail"></div>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-caret-down"></i>
+                                    </div>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span
+                                                            class="h5"><%- user.name %></span><span
+                                                            class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- user.role %></p>
                                                 </div>
-                                            </li>
-                                        <% } else { %>
-                                            <li class="list-group-item">
-                                                <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><span class="pull-right"><%= ah[iA].sort === ah[iA].max_sort ? '终' : ctx.helper.transFormToChinese(ah[iA].sort) %>审</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <% if (ah[iA].status !== auditConst.status.uncheck) { %>
-                                                        <span class="<%- auditConst.statusClass[ah[iA].status] %>"><% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %><small><%- ah[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                    <% } %>
-                                                    <p class="card-text"><%- ah[iA].opinion %></p>
-                                                </div>
-                                            </li>
-                                        <% } %>
-                                    <% } %>
-                                </ul>
-                            </div>
-                        <% } %>
-                        <% if (tender.ledger_status === auditConst.status.checking || tender.ledger_status === auditConst.status.checked) {%>
-                            <div class="card mt-3">
-                                <ul class="list-group list-group-flush">
-                                    <li class="list-group-item">
-                                        <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- user.name %> <small class="text-muted"><%- user.role %></small><span class="pull-right">原报</span></h5>
-                                        <div class="ml-3">
-                                            <span class="text-success"><small><%- auditors[0].begin_time.toLocaleDateString() %></small> <% if (tender.ledger_times > 1) { %>重新<% } %>上报</span>
+                                            </div>
                                         </div>
-                                    </li>
-                                    <% for (let iA = 0; iA < auditors.length; iA++) { %>
-                                        <li class="list-group-item">
-                                            <% if (auditors[iA].status === auditConst.status.checked) { %>
-                                                <h5 class="card-title">
-                                                    <i class="<%- (iA < auditors.length - 1 ? 'fa fa-chevron-circle-down text-success' : 'fa fa-stop-circle text-success') %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small><span class="pull-right"><%= auditors[iA].sort === auditors[iA].max_sort ? '终' : ctx.helper.transFormToChinese(auditors[iA].sort) %>审</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <span class="text-success"><small><%- auditors[iA].end_time.toLocaleDateString() %></small> 审批通过</span>
-                                                    <p class="card-text"><%- auditors[iA].opinion %></p>
-                                                </div>
-                                            <% } else if (auditors[iA].stauts == auditConst.status.checking) { %>
-                                                <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><span class="pull-right"><%= auditors[iA].sort === auditors[iA].max_sort ? '终' : ctx.helper.transFormToChinese(auditors[iA].sort) %>审</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <span>审批中</span>
-                                                    <p class="card-text"><%- auditors[iA].opinion %></p>
-                                                </div>
-                                            <% } 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><span class="pull-right"><%= auditors[iA].sort === auditors[iA].max_sort ? '终' : ctx.helper.transFormToChinese(auditors[iA].sort) %>审</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <p class="card-text"><%- auditors[iA].opinion %></p>
+                                    </div>
+                                </li>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.end_time) %>
+                                    </div>
+                                    <% if(index < auditors.length - 1) { %>
+                                    <div class="timeline-item-tail"></div>
+                                    <% } %>
+                                    <% if(auditor.status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-check"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-level-up"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-ellipsis-h"></i>
+                                    </div>
+                                    <% } else {%>
+                                    <div class="timeline-item-icon bg-secondary text-light">
+                                    </div>
+                                    <% } %>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
+                                                            class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
                                                 </div>
+                                            </div>
+                                            <!--审批意见-->
+                                            <% if(auditor.status !== auditConst.status.uncheck) { %>
+                                            <div class="card-body p-3 border-top">
+                                                <% if (tender.ledger_times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                <label>审批意见<b class="text-danger">*</b></label>
+                                                <textarea class="form-control form-control-sm"
+                                                    name="opinion">同意</textarea>
+                                                <% } else { %>
+                                                <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                <% } %>
+                                            </div>
                                             <% } %>
-                                            <% if (auditors[iA].status === auditConst.status.checked) { %>
-                                            <% } else if (auditors[iA].status === auditConst.status.checking) { %>
-                                                <div class="form-group">
-                                                    <label>审批意见<b class="text-danger">*</b></label>
-                                                    <textarea class="form-control form-control-sm" name="opinion">同意</textarea>
+                                        </div>
+                                    </div>
+                                </li>
+                                <% } else {%>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.end_time) %>
+                                    </div>
+                                    <% if(index < auditors.length - 1) { %>
+                                    <div class="timeline-item-tail"></div>
+                                    <% } %>
+                                    <% if(auditor.status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-check"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-level-up"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-ellipsis-h"></i>
+                                    </div>
+                                    <% } else { %>
+                                    <div class="timeline-item-icon bg-secondary text-light">
+                                    </div>
+                                    <% } %>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span>
+                                                        <span
+                                                            class="pull-right
+                                                                            <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                            <%- auditor.status === auditConst.status.checkNo ? user.name : '' %>
+                                                            <%- auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : '' %>
+                                                        </span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
                                                 </div>
+                                            </div>
+                                            <!--审批意见-->
+                                            <% if(auditor.status !== auditConst.status.uncheck) { %>
+                                            <div class="card-body p-3 border-top">
+                                                <% if (tender.ledger_times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                <label>审批意见<b class="text-danger">*</b></label>
+                                                <textarea class="form-control form-control-sm"
+                                                    name="opinion">同意</textarea>
+                                                <% } else { %>
+                                                <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                <% } %>
+                                            </div>
                                             <% } %>
-                                        </li>
-                                    <% } %>
-                                </ul>
-                            </div>
+                                        </div>
+                                    </div>
+                                </li>
+                                <% } %>
+                                <% }) %>
+                            </ul>
+                        </div>
+                        <!-- 展开/收起历史流程 -->
+                        <% if(idx === auditHistory.length - 1 && auditHistory.length !== 1) { %>
+                        <div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
+                                data-idx="<%- idx + 1 %>">展开历史审批流程</a></div>
                         <% } %>
+                        <% }) %>
                     </div>
                 </div>
             </div>
@@ -134,15 +185,16 @@
                 <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
                 <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
                 <input type="hidden" name="checkType" value="<%= auditConst.status.checked %>" />
-                <button type="submit" class="btn btn-success btn-sm" >确认通过</button>
+                <button type="submit" class="btn btn-success btn-sm">确认通过</button>
             </div>
         </form>
     </div>
 </div>
 <!--审批退回-->
-<div class="modal fade" id="sp-back" data-backdrop="static">
+<div class="modal fade sp-location-list" id="sp-back" data-backdrop="static">
     <div class="modal-dialog modal-lg" role="document">
-        <form class="modal-content" action="/tender/<%- tender.id %>/ledger/audit/check" method="post" onsubmit="return auditCheck(1);">
+        <form class="modal-content modal-lg" action="<%- preUrl %>/ledger/audit/check" method="post"
+            onsubmit="return auditCheck(1);">
             <div class="modal-header">
                 <h5 class="modal-title">审批退回</h5>
             </div>
@@ -151,123 +203,176 @@
                     <div class="col-4">
                         <div class="card mt-3">
                             <ul class="list-group list-group-flush">
-                                <li class="list-group-item">
-                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- user.name %>  <small class="text-muted"><%- user.role %></small><span class="pull-right">原报</span>
+                                <% auditors.forEach((item, idx) => { %>
+                                <% if (idx === 0) { %>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right">原报</span>
+                                </li>
+                                <% } else if(idx === auditors.length -1 && idx !== 0) { %>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right">终审</span>
+                                </li>
+                                <% } else {%>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
                                 </li>
-                                <% for (let i = 0; i < auditors.length; i++) { %>
-                                    <li class="list-group-item">
-                                        <% if (i < auditors.length - 1) { %>
-                                            <i class="fa fa-chevron-circle-down"></i> <%- auditors[i].name %>  <small class="text-muted"><%- auditors[i].role %></small><span class="pull-right"><%= ctx.helper.transFormToChinese(i+1)%>审</span>
-                                        <% } else {%>
-                                            <i class="fa fa fa-stop-circle"></i> <%- auditors[i].name %>  <small class="text-muted"><%- auditors[i].role %></small><span class="pull-right">终审</span>
-                                        <% } %>
-                                    </li>
                                 <% } %>
+                                <% }) %>
                             </ul>
                         </div>
                     </div>
                     <div class="col-8 modal-height-500" style="overflow: auto">
-                        <% for (const ah of auditHistory) { %>
-                            <div class="card mt-3">
-                                <ul class="list-group list-group-flush">
-                                    <% for (let iA = 0; iA < ah.length; iA++) { %>
-                                        <% if (iA === 0) { %>
-                                            <li class="list-group-item">
-                                                <h5 class="card-title">
-                                                    <i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- user.name %> <small class="text-muted"><%- user.role %></small><span class="pull-right">原报</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <span class="text-success"><small><%- ah[iA].begin_time.toLocaleDateString() %></small> <% if (auditHistory.indexOf(ah) > 0) { %>重新<% } %>上报</span>
-                                                </div>
-                                            </li>
-                                            <li class="list-group-item">
-                                                <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><span class="pull-right"><%= ah[iA].sort === ah[iA].max_sort ? '终' : ctx.helper.transFormToChinese(ah[iA].sort) %>审</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <% if (ah[iA].status !== auditConst.status.uncheck) { %>
-                                                        <span class="<%- auditConst.statusClass[ah[iA].status] %>"><% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %><small><%- ah[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                    <% } %>
-                                                    <p class="card-text"><%- ah[iA].opinion %></p>
-                                                </div>
-                                            </li>
-                                        <% } else if (iA === ah.length - 1) { %>
-                                            <li class="list-group-item">
-                                                <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><span class="pull-right">终审</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <% if (ah[iA].status !== auditConst.status.uncheck) { %>
-                                                        <span class="<%- auditConst.statusClass[ah[iA].status] %>"><% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %><small><%- ah[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                    <% } %>
-                                                    <p class="card-text"><%- ah[iA].opinion %></p>
+                        <% auditHistory.forEach((auditors, idx) => { %>
+                        <div class="<%- idx < auditHistory.length - 1 ? 'fold-card' : '' %>">
+                            <div class="text-center text-muted"
+                                <%- idx === auditHistory.length - 1 ? `id="end-target"` : "" %>>
+                                <%- idx === auditHistory.length - 1 ? 1 : idx+1 %>#</div>
+                            <ul class="timeline-list list-unstyled mt-2">
+                                <% auditors.forEach((auditor, index) => { %>
+                                <% if (index === 0) { %>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.begin_time) %>
+                                    </div>
+                                    <div class="timeline-item-tail"></div>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-caret-down"></i>
+                                    </div>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span
+                                                            class="h5"><%- user.name %></span><span
+                                                            class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- user.role %></p>
                                                 </div>
-                                            </li>
-                                        <% } else { %>
-                                            <li class="list-group-item">
-                                                <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><span class="pull-right"><%= ah[iA].sort === ah[iA].max_sort ? '终' : ctx.helper.transFormToChinese(ah[iA].sort) %>审</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <% if (ah[iA].status !== auditConst.status.uncheck) { %>
-                                                        <span class="<%- auditConst.statusClass[ah[iA].status] %>"><% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %><small><%- ah[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                    <% } %>
-                                                    <p class="card-text"><%- ah[iA].opinion %></p>
-                                                </div>
-                                            </li>
-                                        <% } %>
-                                    <% } %>
-                                </ul>
-                            </div>
-                        <% } %>
-                        <% if (tender.ledger_status === auditConst.status.checking || tender.ledger_status === auditConst.status.checked) {%>
-                            <div class="card mt-3">
-                                <ul class="list-group list-group-flush">
-                                    <li class="list-group-item">
-                                        <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- user.name %> <small class="text-muted"><%- user.role %></small><span class="pull-right">原报</span></h5>
-                                        <div class="ml-3">
-                                            <span class="text-success"><small><%- auditors[0].begin_time.toLocaleDateString() %></small> <% if (tender.ledger_times > 1) { %>重新<% } %>上报</span>
+                                            </div>
                                         </div>
-                                    </li>
-                                    <% for (let iA = 0; iA < auditors.length; iA++) { %>
-                                        <li class="list-group-item">
-                                            <% if (auditors[iA].status === auditConst.status.checked) { %>
-                                                <h5 class="card-title">
-                                                    <i class="<%- (iA < auditors.length - 1 ? 'fa fa-chevron-circle-down text-success' : 'fa fa-stop-circle text-success') %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small><span class="pull-right"><%= auditors[iA].sort === auditors[iA].max_sort ? '终' : ctx.helper.transFormToChinese(auditors[iA].sort) %>审</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <span class="text-success"><small><%- auditors[iA].end_time.toLocaleDateString() %></small> 审批通过</span>
-                                                    <p class="card-text"><%- auditors[iA].opinion %></p>
-                                                </div>
-                                            <% } else if (auditors[iA].stauts == auditConst.status.checking) { %>
-                                                <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><span class="pull-right"><%= auditors[iA].sort === auditors[iA].max_sort ? '终' : ctx.helper.transFormToChinese(auditors[iA].sort) %>审</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <span>审批中</span>
-                                                    <p class="card-text"><%- auditors[iA].opinion %></p>
-                                                </div>
-                                            <% } 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><span class="pull-right"><%= auditors[iA].sort === auditors[iA].max_sort ? '终' : ctx.helper.transFormToChinese(auditors[iA].sort) %>审</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <p class="card-text"><%- auditors[iA].opinion %></p>
+                                    </div>
+                                </li>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.end_time) %>
+                                    </div>
+                                    <% if(index < auditors.length - 1) { %>
+                                    <div class="timeline-item-tail"></div>
+                                    <% } %>
+                                    <% if(auditor.status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-check"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-level-up"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-ellipsis-h"></i>
+                                    </div>
+                                    <% } else {%>
+                                    <div class="timeline-item-icon bg-secondary text-light">
+                                    </div>
+                                    <% } %>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
+                                                            class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
                                                 </div>
+                                            </div>
+
+                                            <!--审批意见-->
+                                            <% if(auditor.times === tender.ledger_times && auditor.status !== auditConst.status.uncheck) { %>
+                                            <div class="card-body p-3 border-top">
+                                                <% if (tender.ledger_times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                <label>审批意见<b class="text-danger">*</b></label>
+                                                <textarea class="form-control form-control-sm"
+                                                    name="opinion">不同意</textarea>
+                                                <% } else if(auditor.status === auditConst.status.checked){ %>
+                                                <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                <% } %>
+                                            </div>
                                             <% } %>
-                                            <% if (auditors[iA].status === auditConst.status.checked) { %>
-                                            <% } else if (auditors[iA].status === auditConst.status.checking) { %>
-                                                <div class="form-group">
-                                                    <label>审批意见<b class="text-danger">*</b></label>
-                                                    <textarea class="form-control form-control-sm" name="opinion">不同意</textarea>
+                                        </div>
+                                    </div>
+                                </li>
+                                <% } else {%>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.end_time) %>
+                                    </div>
+                                    <% if(index < auditors.length - 1) { %>
+                                    <div class="timeline-item-tail"></div>
+                                    <% } %>
+                                    <% if(auditor.status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-check"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-level-up"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-ellipsis-h"></i>
+                                    </div>
+                                    <% } else { %>
+                                    <div class="timeline-item-icon bg-secondary text-light">
+                                    </div>
+                                    <% } %>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span>
+                                                        <span
+                                                            class="pull-right
+                                                                            <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                            <%- auditor.status === auditConst.status.checkNo ? user.name : '' %>
+                                                            <%- auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : '' %>
+                                                        </span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
                                                 </div>
-                                                <div class="alert alert-warning">审批退回,将直接退回给原报人。</div>
+                                            </div>
+                                            <!--审批意见-->
+                                            <% if(auditor.times === tender.ledger_times && auditor.status !== auditConst.status.uncheck) { %>
+                                            <div class="card-body p-3 border-top">
+                                                <% if (tender.ledger_times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                <label>审批意见<b class="text-danger">*</b></label>
+                                                <textarea class="form-control form-control-sm"
+                                                    name="opinion">不同意</textarea>
+                                                <% } else { %>
+                                                <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                <% } %>
+
+                                            </div>
+
                                             <% } %>
-                                        </li>
-                                    <% } %>
-                                </ul>
-                            </div>
+                                        </div>
+                                    </div>
+                                </li>
+                                <% } %>
+                                <% }) %>
+                            </ul>
+                        </div>
+                        <!-- 展开/收起历史流程 -->
+                        <% if(idx === auditHistory.length - 1 && auditHistory.length !== 1) { %>
+                        <div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
+                                data-idx="<%- idx + 1 %>">展开历史审批流程</a></div>
                         <% } %>
+                        <% }) %>
                     </div>
                 </div>
             </div>
@@ -275,7 +380,7 @@
                 <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
                 <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
                 <input type="hidden" name="checkType" value="<%= auditConst.status.checkNo %>" />
-                <button type="submit" class="btn btn-warning btn-sm" >确认退回</button>
+                <button type="submit" class="btn btn-warning btn-sm">确认退回</button>
             </div>
         </form>
     </div>
@@ -286,140 +391,222 @@
     <div class="modal-dialog modal-lg" role="document">
         <div class="modal-content">
             <div class="modal-header">
-                <h5 class="modal-title">审批流程</h5>
+                <h5 class="modal-title"><%- tender.ledger_status === auditConst.status.checked ? '审批流程' : '重新上报' %></h5>
             </div>
             <div class="modal-body">
                 <div class="row">
                     <div class="col-4">
+                        <% if(tender.ledger_status === auditConst.status.checkNo) { %>
+                            <a class="sp-list-item" href="#sub-sp" data-toggle="modal" data-target="#sub-sp" id="hideSp">修改审批流程</a>
+                        <% } %>
                         <div class="card mt-3">
                             <ul class="list-group list-group-flush">
-                                <li class="list-group-item">
-                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- user.name %>  <small class="text-muted"><%- user.role %></small><span class="pull-right">原报</span>
+                                <% auditors.forEach((item, idx) => { %>
+                                <% if (idx === 0) { %>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right">原报</span>
                                 </li>
-                                <% for (let i = 0; i < auditors.length; i++) { %>
-                                <li class="list-group-item">
-                                    <% if (i < auditors.length - 1) { %>
-                                    <i class="fa fa-chevron-circle-down"></i> <%- auditors[i].name %>  <small class="text-muted"><%- auditors[i].role %></small><span class="pull-right"><%= ctx.helper.transFormToChinese(i+1)%>审</span>
-                                    <% } else {%>
-                                    <i class="fa fa fa-stop-circle"></i> <%- auditors[i].name %>  <small class="text-muted"><%- auditors[i].role %></small><span class="pull-right">终审</span>
-                                    <% } %>
+                                <% } else if(idx === auditors.length -1 && idx !== 0) { %>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right">终审</span>
+                                </li>
+                                <% } else {%>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
                                 </li>
                                 <% } %>
+                                <% }) %>
                             </ul>
                         </div>
                     </div>
                     <div class="col-8 modal-height-500" style="overflow: auto">
-                        <% for (const ah of auditHistory) { %>
-                        <div class="card mt-3">
-                            <ul class="list-group list-group-flush">
-                                <% for (let iA = 0; iA < ah.length; iA++) { %>
-                                    <% if (iA === 0) { %>
-                                        <li class="list-group-item">
-                                            <h5 class="card-title">
-                                                <i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- user.name %> <small class="text-muted"><%- user.role %></small><span class="pull-right">原报</span>
-                                            </h5>
-                                            <div class="ml-3">
-                                                <span class="text-success"><small><%- ah[iA].begin_time.toLocaleDateString() %></small> <% if (auditHistory.indexOf(ah) > 0) { %>重新<% } %>上报</span>
-                                            </div>
-                                        </li>
-                                        <li class="list-group-item">
-                                            <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><span class="pull-right"><%= ah[iA].sort === ah[iA].max_sort ? '终' : ctx.helper.transFormToChinese(ah[iA].sort) %>审</span>
-                                            </h5>
-                                            <div class="ml-3">
-                                                <% if (ah[iA].status !== auditConst.status.uncheck) { %>
-                                                    <span class="<%- auditConst.statusClass[ah[iA].status] %>"><% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %><small><%- ah[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                <% } %>
-                                                <p class="card-text"><%- ah[iA].opinion %></p>
+                        <% auditHistory.forEach((auditors, idx) => { %>
+                        <div class="<%- idx < auditHistory.length - 1 ? 'fold-card' : '' %>">
+                            <div class="text-center text-muted"
+                                <%- idx === auditHistory.length - 1 ? `id="end-target"` : "" %>>
+                                <%- idx === auditHistory.length - 1 ? 1 : idx+1 %>#</div>
+                            <ul class="timeline-list list-unstyled mt-2">
+                                <% auditors.forEach((auditor, index) => { %>
+                                <% if (index === 0) { %>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.begin_time) %>
+                                    </div>
+                                    <div class="timeline-item-tail"></div>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-caret-down"></i>
+                                    </div>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span
+                                                            class="h5"><%- user.name %></span><span
+                                                            class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- user.role %></p>
+                                                </div>
                                             </div>
-                                        </li>
-                                    <% } else if (iA === ah.length - 1) { %>
-                                        <li class="list-group-item">
-                                            <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><span class="pull-right">终审</span>
-                                            </h5>
-                                            <div class="ml-3">
-                                                <% if (ah[iA].status !== auditConst.status.uncheck) { %>
-                                                    <span class="<%- auditConst.statusClass[ah[iA].status] %>"><% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %><small><%- ah[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                <% } %>
-                                                <p class="card-text"><%- ah[iA].opinion %></p>
+                                        </div>
+                                    </div>
+                                </li>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.end_time) %>
+                                    </div>
+                                    <% if(index < auditors.length - 1) { %>
+                                    <div class="timeline-item-tail"></div>
+                                    <% } %>
+                                    <% if(auditor.status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-check"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-level-up"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-ellipsis-h"></i>
+                                    </div>
+                                    <% } else {%>
+                                    <div class="timeline-item-icon bg-secondary text-light">
+                                    </div>
+                                    <% } %>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
+                                                            class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                </div>
                                             </div>
-                                        </li>
-                                    <% } else { %>
-                                        <li class="list-group-item">
-                                            <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><span class="pull-right"><%= ah[iA].sort === ah[iA].max_sort ? '终' : ctx.helper.transFormToChinese(ah[iA].sort) %>审</span>
-                                            </h5>
-                                            <div class="ml-3">
-                                                <% if (ah[iA].status !== auditConst.status.uncheck) { %>
-                                                    <span class="<%- auditConst.statusClass[ah[iA].status] %>"><% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %><small><%- ah[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                <% } %>
-                                                <p class="card-text"><%- ah[iA].opinion %></p>
+
+                                            <!--审批意见-->
+                                            <% if (auditor.opinion) { %>
+                                            <div class="card-body p-3 border-top">
+                                                <p style="margin: 0;"><%- auditor.opinion %></p>
                                             </div>
-                                        </li>
-                                    <% } %>
-                                <% } %>
-                            </ul>
-                        </div>
-                        <% } %>
-                        <% if (tender.ledger_status === auditConst.status.checking || tender.ledger_status === auditConst.status.checked) {%>
-                        <div class="card mt-3">
-                            <ul class="list-group list-group-flush">
-                                <% for (let iA = 0; iA < auditors.length; iA++) { %>
-                                    <% if (iA === 0) { %>
-                                    <li class="list-group-item">
-                                        <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- user.name %> <small class="text-muted"><%- user.role %></small><span class="pull-right">原报</span></h5>
-                                        <div class="ml-3">
-                                            <span class="text-success"><small><%- auditors[iA].begin_time.toLocaleDateString() %></small> <% if (tender.ledger_times > 1) { %>重新<% } %>上报</span>
-                                        </div>
-                                    </li>
-                                    <li class="list-group-item">
-                                        <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><span class="pull-right"><%- auditors[iA].sort === auditors[iA].max_sort ? '终' : ctx.helper.transFormToChinese(auditors[iA].sort) %>审</span>
-                                        </h5>
-                                        <div class="ml-3">
-                                            <% if (auditors[iA].status !== auditConst.status.uncheck) { %>
-                                                <span class="<%- auditConst.statusClass[auditors[iA].status] %>"> <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo) { %><small><%- auditors[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
                                             <% } %>
-                                            <p class="card-text"><%- auditors[iA].opinion %></p>
                                         </div>
-                                    </li>
-                                    <% } else if (iA === auditors.length - 1) { %>
-                                    <li class="list-group-item">
-                                        <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><span class="pull-right">终审</span>
-                                        </h5>
-                                        <div class="ml-3">
-                                            <% if (auditors[iA].status !== auditConst.status.uncheck) { %>
-                                                <span class="<%- auditConst.statusClass[auditors[iA].status] %>"> <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo) { %><small><%- auditors[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                            <% } %>
-                                            <p class="card-text"><%- auditors[iA].opinion %></p>
-                                        </div>
-                                    </li>
+                                    </div>
+                                </li>
+                                <% } else {%>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.end_time) %>
+                                    </div>
+                                    <% if(index < auditors.length - 1) { %>
+                                    <div class="timeline-item-tail"></div>
+                                    <% } %>
+                                    <% if(auditor.status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-check"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-level-up"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-ellipsis-h"></i>
+                                    </div>
                                     <% } else { %>
-                                    <li class="list-group-item">
-                                        <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><span class="pull-right"><%- auditors[iA].sort === auditors[iA].max_sort ? '终' : ctx.helper.transFormToChinese(auditors[iA].sort) %>审</span>
-                                        </h5>
-                                        <div class="ml-3">
-                                            <% if (auditors[iA].status !== auditConst.status.uncheck) { %>
-                                                <span class="<%- auditConst.statusClass[auditors[iA].status] %>"> <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo) { %><small><%- auditors[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
+                                    <div class="timeline-item-icon bg-secondary text-light">
+                                    </div>
+                                    <% } %>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span>
+                                                        <span
+                                                            class="pull-right
+                                                                            <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                            <%- auditor.status === auditConst.status.checkNo ? user.name : '' %>
+                                                            <%- auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : '' %>
+                                                        </span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                </div>
+                                            </div>
+                                            <!--审批意见-->
+                                            <% if (auditor.opinion) { %>
+                                            <div class="card-body p-3 border-top">
+                                                <p style="margin: 0;"><%- auditor.opinion %></p>
+                                            </div>
                                             <% } %>
-                                            <p class="card-text"><%- auditors[iA].opinion %></p>
                                         </div>
-                                    </li>
-                                    <% } %>
+                                    </div>
+                                </li>
                                 <% } %>
+                                <% }) %>
                             </ul>
                         </div>
+                        <!-- 展开/收起历史流程 -->
+                        <% if(idx === auditHistory.length - 1 && auditHistory.length !== 1) { %>
+                            <div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
+                                    data-idx="<%- idx + 1 %>">展开历史审批流程</a>
+                            </div>
                         <% } %>
+                        <% }) %>
                     </div>
                 </div>
             </div>
-            <div class="modal-footer">
+            <form class="modal-footer" method="post" action="<%- preUrl %>/ledger/audit/start" onsubmit="return checkAuditorFrom()">
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
                 <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-            </div>
+                <% if(tender.ledger_status === auditConst.status.checkNo && ctx.session.sessionUser.accountId === tender.user_id) { %>
+                    <button class="btn btn-primary btn-sm sp-list-item" type="submit">确认上报</button>
+                <% } %>
+            </form>
         </div>
     </div>
 </div>
 <% } %>
+<script>
+    const cur_uid  = parseInt('<%- ctx.session.sessionUser.accountId %>');
+    $('.sp-location-list').on('shown.bs.modal', function () {
+        const scrollBox = $(this).find('div[class="col-8 modal-height-500"]');
+        const bdiv = (scrollBox.offset() && scrollBox.offset().top) || 0;
+        scrollBox.scrollTop(0);
+        const hdiv = divSearch($(this).find('textarea')) ? $(this).find('textarea') : null;
+        const hdheight = hdiv ? hdiv.parents('.timeline-item-content').offset().top : null;
+        if (hdiv && scrollBox.length && scrollBox[0].scrollHeight > 390 && hdheight - bdiv > 390) {
+            scrollBox.scrollTop(hdheight - bdiv);
+        }
+    });
+    function divSearch(div) {
+        if (div.length > 0) {
+            return true;
+        }
+        return false;
+    }
+
+    // 展开历史审核记录
+    $('.modal-body #fold-btn').click(function () {
+        const type = $(this).data('target')
+        const auditCard = $(this).parent().parent()
+        if (type === 'show') {
+            $(this).data('target', 'hide')
+            auditCard.find('.fold-card').slideDown('swing', () => {
+                auditCard.find('#end-target').text($(this).data('idx') + '#')
+                auditCard.find('#fold-btn').text('收起历史审核记录')
+            })
+        } else {
+            $(this).data('target', 'show')
+            auditCard.find('.fold-card').slideUp('swing', () => {
+                auditCard.find('#end-target').text('1#')
+                auditCard.find('#fold-btn').text('展开历史审核记录')
+            })
+        }
+    });
+</script>

+ 22 - 5
app/view/ledger/explode.ejs

@@ -40,23 +40,23 @@
                     </div>
                 </div>
                 <div class="d-inline-block ml-3">
-                    <!--<a id="exportLedger" class="btn btn-primary btn-sm" href="/tender/<%- ctx.tender.id %>/ledger/download/台账分解.xlsx">导出台账Excel</a>-->
                     <a id="exportLedger" class="btn btn-primary btn-sm" href="javascript: void(0)">导出台账Excel</a>
+                    <a class="btn btn-sm btn-primary" href="#ledger-check-modal" data-toggle="modal" data-target="#ledger-check-modal">数据检查</a>
                 </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>
+                    <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm pull-right text-dark sp-list-btn">审批退回</a>
                 <% } else if (tender.ledger_status === auditConst.status.checking) { %>
-                    <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm pull-right text-dark">审批中</a>
+                    <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm pull-right text-dark sp-list-btn">审批中</a>
                 <% } else if (tender.ledger_status === auditConst.status.checked) { %>
-                    <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm pull-right">审批完成</a>
+                    <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm pull-right sp-list-btn">审批完成</a>
                 <% } %>
                 <% if (ctx.session.sessionUser.accountId === tender.user_id) { %>
                     <% if (tender.ledger_status === auditConst.status.uncheck) { %>
                         <a href="#sub-sp" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm pull-right">上报审批</a>
                     <% } else if (tender.ledger_status === auditConst.status.checkNo) { %>
-                        <a href="#sp-list2" data-toggle="modal" data-target="#sp-list2" class="btn btn-primary btn-sm pull-right">重新上报</a>
+                        <a href="#sp-list" data-type="show" data-toggle="modal" data-target="#sp-list" class="btn btn-primary btn-sm pull-right sp-list-btn" style="margin-right: 5px;">重新上报</a>
                     <% } %>
                 <% } %>
             </div>
@@ -146,6 +146,8 @@
                     </div>
                     <div id="error-list" class="tab-pane">
                     </div>
+                    <div id="check-list" class="tab-pane">
+                    </div>
                 </div>
             </div>
         </div>
@@ -167,10 +169,14 @@
                 <li class="nav-item">
                     <a class="nav-link" content="#error-list" id="error-list-tab" href="javascript: void(0);" style="display: none;">错误列表</a>
                 </li>
+                <li class="nav-item">
+                    <a class="nav-link" content="#check-list" id="check-list-tab" href="javascript: void(0);" style="display: none;">数据检查</a>
+                </li>
             </ul>
         </div>
     </div>
 </div>
+<script src="/public/js/moment/moment.min.js"></script>
 <script type="text/javascript">
     const readOnly = <%- ctx.tender.ledgerReadOnly %>;
     const tender = JSON.parse('<%- JSON.stringify(tender) %>');
@@ -188,9 +194,20 @@
         key: 'ledger-pos',
         colWidth: true,
     };
+    $('.sp-list-btn').click(function () {
+        const type = $(this).data('type')
+        if (type === 'hide') {
+            $('.sp-list-item').hide()
+            $('.modal-title').text('审批流程')
+        } else {
+            $('.sp-list-item').show()
+            $('.modal-title').text('重新上报')
+        }
+    });
 </script>
 <% if ((tender.ledger_status === auditConst.status.uncheck || tender.ledger_status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === tender.user_id) { %>
 <script>
     const accountList = JSON.parse('<%- JSON.stringify(accountList) %>');
+    const accountGroup = JSON.parse('<%- JSON.stringify(accountGroup) %>');
 </script>
 <% } %>

+ 234 - 251
app/view/ledger/explode_modal.ejs

@@ -93,23 +93,30 @@
                 <h5 class="modal-title">上报审批</h5>
             </div>
             <div class="modal-body">
-                <div class="form-group">
-                    <label>选择审批人</label>
-                    <div class="input-group">
-                        <div class="input-group-prepend">
-                            <select class="form-control form-control-sm" id="account_group">
-                                <option value="0">所有分组</option>
-                                <% for (const dw in accountGroup) { %>
-                                    <option value="<%= dw %>"><%= accountGroup[dw] %></option>
-                                <% } %>
-                            </select>
-                        </div>
-                        <select class="form-control form-control-sm" id="account_list">
-                            <option value="0">选择审批人</option>
-                            <% for (const account of accountList) { %>
-                                <option value="<%= account.id %>"><%= account.name %><% if (account.role !== '') { %>(<%= account.role %>)<% } %><% if (account.company !== '') { %> -<%= account.company %><% } %></option>
-                            <% } %>
-                        </select>
+                <div class="dropdown">
+                    <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button"
+                        id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true"
+                        aria-expanded="false">
+                        添加审批流程
+                    </button>
+                    <div class="dropdown-menu" aria-labelledby="dropdownMenuButton"
+                        style="width:220px">
+                        <div class="mb-2 p-2"><input class="form-control form-control-sm"
+                                placeholder="姓名/手机 检索" id="gr-search" autocomplete="off"></div>
+                        <dl class="list-unstyled book-list">
+                            <% accountGroup.forEach((group, idx) => { %>
+                                <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>" data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                                <div class="dd-content" data-toggleid="<%- idx %>">
+                                    <% group.groupList.forEach(item => { %>
+                                        <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>" >
+                                            <p class="mb-0 d-flex"><span class="text-primary"><%- item.name %></span><span
+                                                    class="ml-auto"><%- item.mobile %></span></p>
+                                            <span class="text-muted"><%- item.role %></span>
+                                        </dd>
+                                    <% });%>
+                                </div>
+                            <% }) %>
+                        </dl>
                     </div>
                 </div>
                 <div class="card mt-3">
@@ -129,7 +136,7 @@
                     </div>
                 </div>
             </div>
-            <form class="modal-footer" method="post" action="/tender/<%- tender.id %>/ledger/audit/start" name="audit-start">
+            <form class="modal-footer" method="post" action="<%- preUrl %>/ledger/audit/start" onsubmit="return checkAuditorFrom()">
                 <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
                 <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
                 <button class="btn btn-primary btn-sm" type="submit" >确认上报</button>
@@ -138,278 +145,254 @@
     </div>
 </div>
 <% } %>
-<% if (tender.ledger_status === auditConst.status.checkNo && ctx.session.sessionUser.accountId === tender.user_id) { %>
-<!--审批流程/结果-->
-<div class="modal fade" id="sp-list2" data-backdrop="static">
-    <div class="modal-dialog modal-lg" role="document">
-        <div class="modal-content">
-            <div class="modal-header">
-                <h5 class="modal-title">重新上报</h5>
-            </div>
-            <div class="modal-body">
-                <div class="row">
-                    <div class="col-4 modal-height-500" style="overflow: auto">
-                        <a href="#sub-sp" data-toggle="modal" data-target="#sub-sp" id="hideSp">修改审批流程</a>
-                        <div class="card mt-3">
-                            <ul class="list-group list-group-flush">
-                                <li class="list-group-item">
-                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- user.name %>  <small class="text-muted"><%- user.role %></small><span class="pull-right">原报</span>
-                                </li>
-                            </ul>
-                            <ul class="list-group list-group-flush" id="auditors-list">
-                                <% for (let i = 0; i < auditorList.length; i++) { %>
-                                    <li class="list-group-item" data-auditid="<%- auditorList[i].audit_id %>">
-                                        <% if (i < auditorList.length - 1) { %>
-                                            <i class="fa fa-chevron-circle-down"></i> <%- auditorList[i].name %>  <small class="text-muted"><%- auditorList[i].role %></small><span class="pull-right"><%= ctx.helper.transFormToChinese(i+1)%>审</span>
-                                        <% } else {%>
-                                            <i class="fa fa fa-stop-circle"></i> <%- auditorList[i].name %>  <small class="text-muted"><%- auditorList[i].role %></small><span class="pull-right">终审</span>
-                                        <% } %>
-                                    </li>
-                                <% } %>
-                            </ul>
-                        </div>
-                    </div>
-                    <div class="col-8 modal-height-500" style="overflow: auto">
-                        <% for (const ah of auditHistory) { %>
-                            <div class="card mt-3">
-                                <ul class="list-group list-group-flush">
-                                    <% for (let iA = 0; iA < ah.length; iA++) { %>
-                                        <% if (iA === 0) { %>
-                                            <li class="list-group-item">
-                                                <h5 class="card-title">
-                                                    <i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- user.name %> <small class="text-muted"><%- user.role %></small><span class="pull-right">原报</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <span class="text-success"><small><%- ah[iA].begin_time.toLocaleDateString() %></small> <% if (auditHistory.indexOf(ah) > 0) { %>重新<% } %>上报</span>
-                                                </div>
-                                            </li>
-                                            <li class="list-group-item">
-                                                <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><span class="pull-right"><%= ah[iA].sort === ah[iA].max_sort ? '终' : ctx.helper.transFormToChinese(ah[iA].sort) %>审</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <% if (ah[iA].status !== auditConst.status.uncheck) { %>
-                                                        <span class="<%- auditConst.statusClass[ah[iA].status] %>"><% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %><small><%- ah[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                    <% } %>
-                                                    <p class="card-text"><%- ah[iA].opinion %></p>
-                                                </div>
-                                            </li>
-                                        <% } else if (iA === ah.length - 1) { %>
-                                            <li class="list-group-item">
-                                                <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><span class="pull-right">终审</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <% if (ah[iA].status !== auditConst.status.uncheck) { %>
-                                                        <span class="<%- auditConst.statusClass[ah[iA].status] %>"><% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %><small><%- ah[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                    <% } %>
-                                                    <p class="card-text"><%- ah[iA].opinion %></p>
-                                                </div>
-                                            </li>
-                                        <% } else { %>
-                                            <li class="list-group-item">
-                                                <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><span class="pull-right"><%= ah[iA].sort === ah[iA].max_sort ? '终' : ctx.helper.transFormToChinese(ah[iA].sort) %>审</span>
-                                                </h5>
-                                                <div class="ml-3">
-                                                    <% if (ah[iA].status !== auditConst.status.uncheck) { %>
-                                                        <span class="<%- auditConst.statusClass[ah[iA].status] %>"><% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %><small><%- ah[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                    <% } %>
-                                                    <p class="card-text"><%- ah[iA].opinion %></p>
-                                                </div>
-                                            </li>
-                                        <% } %>
-                                    <% } %>
-                                </ul>
-                            </div>
-                        <% } %>
-                        <% if (tender.ledger_status === auditConst.status.checkNo) {%>
-                            <div class="card mt-3">
-                                <ul class="list-group list-group-flush">
-                                    <li class="list-group-item">
-                                        <h5 class="card-title">
-                                            <i class="fa fa-play-circle fa-rotate-90"></i> <%- user.name %> <small class="text-muted"><%- user.role %></small><span class="pull-right">原报</span>
-                                        </h5>
-                                        <div class="ml-3">
-                                            <span>重新上报中</span>
-                                        </div>
-                                    </li>
-                                </ul>
-                                <ul class="list-group list-group-flush" id="auditors-list2">
-                                    <% for (let iA = 0; iA < auditorList.length; iA++) { %>
-                                        <% if (iA === auditorList.length - 1) { %>
-                                            <li class="list-group-item" data-auditid="<%- auditorList[iA].audit_id %>">
-                                                <h5 class="card-title">
-                                                    <i class="fa fa-stop-circle"></i> <%- auditorList[iA].name %> <small class="text-muted"><%- auditorList[iA].role %></small><span class="pull-right">终审</span>
-                                                </h5>
-                                            </li>
-                                        <% } else { %>
-                                            <li class="list-group-item" data-auditid="<%- auditorList[iA].audit_id %>">
-                                                <h5 class="card-title">
-                                                    <i class="fa fa-chevron-circle-down"></i> <%- auditorList[iA].name %> <small class="text-muted"><%- auditorList[iA].role %></small><span class="pull-right"><%= ctx.helper.transFormToChinese(iA+1) %>审</span>
-                                                </h5>
-                                            </li>
-                                        <% } %>
-                                    <% } %>
-                                </ul>
-                            </div>
-                        <% } %>
-                    </div>
-                </div>
-            </div>
-            <form class="modal-footer" action="/tender/<%- tender.id %>/ledger/audit/start" method="post" name="audit-start">
-                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
-                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-                <button type="submit" class="btn btn-primary btn-sm">确认上报</button>
-            </form>
-        </div>
-    </div>
-</div>
-<% } %>
 <% if ((tender.ledger_status !== auditConst.status.unCheck) || (tender.ledger_times > 1)) { %>
 <!--审批流程/结果-->
 <div class="modal fade" id="sp-list" data-backdrop="static">
     <div class="modal-dialog modal-lg" role="document">
         <div class="modal-content">
             <div class="modal-header">
-                <h5 class="modal-title">审批流程</h5>
+                <h5 class="modal-title"><%- tender.ledger_status === auditConst.status.checking ? '审批流程' : '重新上报' %></h5>
             </div>
             <div class="modal-body">
                 <div class="row">
                     <div class="col-4">
+                        <% if(tender.ledger_status === auditConst.status.checkNo) { %>
+                            <a class="sp-list-item" href="#sub-sp" data-toggle="modal" data-target="#sub-sp" id="hideSp">修改审批流程</a>
+                        <% } %>
                         <div class="card mt-3">
                             <ul class="list-group list-group-flush">
-                                <li class="list-group-item">
-                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- user.name %>  <small class="text-muted"><%- user.role %></small><span class="pull-right">原报</span>
+                                <% auditors.forEach((item, idx) => { %>
+                                <% if (idx === 0) { %>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right">原报</span>
                                 </li>
-                                <% for (let i = 0; i < auditors.length; i++) { %>
-                                <li class="list-group-item">
-                                    <% if (i < auditors.length - 1) { %>
-                                    <i class="fa fa-chevron-circle-down"></i> <%- auditors[i].name %>  <small class="text-muted"><%- auditors[i].role %></small><span class="pull-right"><%= ctx.helper.transFormToChinese(i+1) %>审</span>
-                                    <% } else {%>
-                                    <i class="fa fa fa-stop-circle"></i> <%- auditors[i].name %>  <small class="text-muted"><%- auditors[i].role %></small><span class="pull-right">终审</span>
-                                    <% } %>
+                                <% } else if(idx === auditors.length -1 && idx !== 0) { %>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right">终审</span>
+                                </li>
+                                <% } else {%>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
                                 </li>
                                 <% } %>
+                                <% }) %>
                             </ul>
                         </div>
                     </div>
                     <div class="col-8 modal-height-500" style="overflow: auto">
-                        <% for (const ah of auditHistory) { %>
-                        <div class="card mt-3">
-                            <ul class="list-group list-group-flush">
-                                <% for (let iA = 0; iA < ah.length; iA++) { %>
-                                    <% if (iA === 0) { %>
-                                        <li class="list-group-item">
-                                            <h5 class="card-title">
-                                                <i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- user.name %> <small class="text-muted"><%- user.role %></small><span class="pull-right">原报</span>
-                                            </h5>
-                                            <div class="ml-3">
-                                                <span class="text-success"><small><%- ah[iA].begin_time.toLocaleDateString() %></small> <% if (auditHistory.indexOf(ah) > 0) { %>重新<% } %>上报</span>
+                        <% auditHistory.forEach((auditors, idx) => { %>
+                        <div class="<%- idx < auditHistory.length - 1 ? 'fold-card' : '' %>">
+                            <div class="text-center text-muted"
+                                <%- idx === auditHistory.length - 1 ? `id="end-target"` : "" %>>
+                                <%- idx === auditHistory.length - 1 ? 1 : idx+1 %>#</div>
+                            <ul class="timeline-list list-unstyled mt-2">
+                                <% auditors.forEach((auditor, index) => { %>
+                                <% if (index === 0) { %>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.begin_time) %>
+                                    </div>
+                                    <div class="timeline-item-tail"></div>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-caret-down"></i>
+                                    </div>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span
+                                                            class="h5"><%- user.name %></span><span
+                                                            class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- user.role %></p>
+                                                </div>
                                             </div>
-                                        </li>
-                                        <li class="list-group-item">
-                                            <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><span class="pull-right"><%= ah[iA].sort === ah[iA].max_sort ? '终' : ctx.helper.transFormToChinese(ah[iA].sort) %>审</span>
-                                            </h5>
-                                            <div class="ml-3">
-                                                <% if (ah[iA].status !== auditConst.status.uncheck) { %>
-                                                    <span class="<%- auditConst.statusClass[ah[iA].status] %>"><% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %><small><%- ah[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                <% } %>
-                                                <p class="card-text"><%- ah[iA].opinion %></p>
+                                        </div>
+                                    </div>
+                                </li>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.end_time) %>
+                                    </div>
+                                    <% if(index < auditors.length - 1) { %>
+                                    <div class="timeline-item-tail"></div>
+                                    <% } %>
+                                    <% if(auditor.status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-check"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-level-up"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-ellipsis-h"></i>
+                                    </div>
+                                    <% } else {%>
+                                    <div class="timeline-item-icon bg-secondary text-light">
+                                    </div>
+                                    <% } %>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
+                                                            class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                </div>
                                             </div>
-                                        </li>
-                                    <% } else if (iA === ah.length - 1) { %>
-                                        <li class="list-group-item">
-                                            <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><span class="pull-right">终审</span>
-                                            </h5>
-                                            <div class="ml-3">
-                                                <% if (ah[iA].status !== auditConst.status.uncheck) { %>
-                                                    <span class="<%- auditConst.statusClass[ah[iA].status] %>"><% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %><small><%- ah[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                <% } %>
-                                                <p class="card-text"><%- ah[iA].opinion %></p>
+
+                                            <!--审批意见-->
+                                            <% if (auditor.opinion) { %>
+                                            <div class="card-body p-3 border-top">
+                                                <p style="margin: 0;"><%- auditor.opinion %></p>
                                             </div>
-                                        </li>
+                                            <% } %>
+                                        </div>
+                                    </div>
+                                </li>
+                                <% } else {%>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.end_time) %>
+                                    </div>
+                                    <% if(index < auditors.length - 1) { %>
+                                    <div class="timeline-item-tail"></div>
+                                    <% } %>
+                                    <% if(auditor.status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-check"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-level-up"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-ellipsis-h"></i>
+                                    </div>
                                     <% } else { %>
-                                        <li class="list-group-item">
-                                            <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><span class="pull-right"><%= ah[iA].sort === ah[iA].max_sort ? '终' : ctx.helper.transFormToChinese(ah[iA].sort) %>审</span>
-                                            </h5>
-                                            <div class="ml-3">
-                                                <% if (ah[iA].status !== auditConst.status.uncheck) { %>
-                                                    <span class="<%- auditConst.statusClass[ah[iA].status] %>"><% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %><small><%- ah[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                <% } %>
-                                                <p class="card-text"><%- ah[iA].opinion %></p>
-                                            </div>
-                                        </li>
+                                    <div class="timeline-item-icon bg-secondary text-light">
+                                    </div>
                                     <% } %>
-                                <% } %>
-                            </ul>
-                        </div>
-                        <% } %>
-                        <% if (tender.ledger_status === auditConst.status.checking || tender.ledger_status === auditConst.status.checked) {%>
-                        <div class="card mt-3">
-                            <ul class="list-group list-group-flush">
-                                <% for (let iA = 0; iA < auditors.length; iA++) { %>
-                                    <% if (iA === 0) { %>
-                                        <li class="list-group-item">
-                                            <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- user.name %> <small class="text-muted"><%- user.role %></small><span class="pull-right">原报</span></h5>
-                                            <div class="ml-3">
-                                                <span class="text-success"><small><%- auditors[iA].begin_time.toLocaleDateString() %></small> <% if (tender.ledger_times > 1) { %>重新<% } %>上报</span>
-                                            </div>
-                                        </li>
-                                        <li class="list-group-item">
-                                            <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><span class="pull-right"><%- auditors[iA].sort === auditors[iA].max_sort ? '终' : ctx.helper.transFormToChinese(auditors[iA].sort) %>审</span>
-                                            </h5>
-                                            <div class="ml-3">
-                                                <% if (auditors[iA].status !== auditConst.status.uncheck) { %>
-                                                    <span class="<%- auditConst.statusClass[auditors[iA].status] %>"> <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo) { %><small><%- auditors[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                <% } %>
-                                                <p class="card-text"><%- auditors[iA].opinion %></p>
-                                            </div>
-                                        </li>
-                                    <% } else if (iA === auditors.length - 1) { %>
-                                        <li class="list-group-item">
-                                            <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><span class="pull-right">终审</span>
-                                            </h5>
-                                            <div class="ml-3">
-                                                <% if (auditors[iA].status !== auditConst.status.uncheck) { %>
-                                                    <span class="<%- auditConst.statusClass[auditors[iA].status] %>"> <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo) { %><small><%- auditors[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                <% } %>
-                                                <p class="card-text"><%- auditors[iA].opinion %></p>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span>
+                                                        <span
+                                                            class="pull-right
+                                                                            <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                            <%- auditor.status === auditConst.status.checkNo ? user.name : '' %>
+                                                            <%- auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : '' %>
+                                                        </span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                </div>
                                             </div>
-                                        </li>
-                                    <% } else { %>
-                                        <li class="list-group-item">
-                                            <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><span class="pull-right"><%- auditors[iA].sort === auditors[iA].max_sort ? '终' : ctx.helper.transFormToChinese(auditors[iA].sort) %>审</span>
-                                            </h5>
-                                            <div class="ml-3">
-                                                <% if (auditors[iA].status !== auditConst.status.uncheck) { %>
-                                                    <span class="<%- auditConst.statusClass[auditors[iA].status] %>"> <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo) { %><small><%- auditors[iA].end_time.toLocaleDateString() %></small> <% } %><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
-                                                <% } %>
-                                                <p class="card-text"><%- auditors[iA].opinion %></p>
+                                            <!--审批意见-->
+                                            <% if (auditor.opinion) { %>
+                                            <div class="card-body p-3 border-top">
+                                                <p style="margin: 0;"><%- auditor.opinion %></p>
                                             </div>
-                                        </li>
-                                    <% } %>
+                                            <% } %>
+                                        </div>
+                                    </div>
+                                </li>
                                 <% } %>
+                                <% }) %>
                             </ul>
                         </div>
+                        <!-- 展开/收起历史流程 -->
+                        <% if(idx === auditHistory.length - 1 && auditHistory.length !== 1) { %>
+                            <div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
+                                    data-idx="<%- idx + 1 %>">展开历史审批流程</a>
+                            </div>
                         <% } %>
+                        <% }) %>
                     </div>
                 </div>
             </div>
-            <div class="modal-footer">
+            <form class="modal-footer" method="post" action="<%- preUrl %>/ledger/audit/start" onsubmit="return checkAuditorFrom()">
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
                 <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-            </div>
+                <% if(tender.ledger_status === auditConst.status.checkNo && ctx.session.sessionUser.accountId === tender.user_id) { %>
+                    <button class="btn btn-primary btn-sm sp-list-item" type="submit">确认上报</button>
+                <% } %>
+            </form>
         </div>
     </div>
 </div>
+<script>
+    const cur_uid  = parseInt('<%- ctx.session.sessionUser.accountId %>');
+    $('.sp-location-list').on('shown.bs.modal', function () {
+        const scrollBox = $(this).find('div[class="col-8 modal-height-500"]');
+        const bdiv = (scrollBox.offset() && scrollBox.offset().top) || 0;
+        scrollBox.scrollTop(0);
+        const hdiv = divSearch($(this).find('textarea')) ? $(this).find('textarea') : null;
+        const hdheight = hdiv ? hdiv.parents('.timeline-item-content').offset().top : null;
+        if (hdiv && scrollBox.length && scrollBox[0].scrollHeight > 390 && hdheight - bdiv > 390) {
+            scrollBox.scrollTop(hdheight - bdiv);
+        }
+    });
+    function divSearch(div) {
+        if (div.length > 0) {
+            return true;
+        }
+        return false;
+    }
+
+    // 检查上报情况
+    function checkAuditorFrom () {
+        if ($('#auditors li').length === 0) {
+            toast('请先选择审批人,再上报数据', 'error', 'exclamation-circle');
+            return false;
+        }
+        $('#hide-all').show();
+    }
+
+    $('#hideSp').click(function () {
+        $('#sp-list').modal('hide');
+    });
+    $('a[f-target]').click(function () {
+        $($(this).attr('f-target')).modal('show');
+    });
+
+    // 多层modal关闭后的滚动bug修复
+    $('#sp-list').on('hidden.bs.modal', function (e) {
+        $(document.body).addClass('modal-open');
+    });
+
+    // 展开历史审核记录
+    $('.modal-body #fold-btn').click(function () {
+        const type = $(this).data('target')
+        const auditCard = $(this).parent().parent()
+        if (type === 'show') {
+            $(this).data('target', 'hide')
+            auditCard.find('.fold-card').slideDown('swing', () => {
+                auditCard.find('#end-target').text($(this).data('idx') + '#')
+                auditCard.find('#fold-btn').text('收起历史审核记录')
+            })
+        } else {
+            $(this).data('target', 'show')
+            auditCard.find('.fold-card').slideUp('swing', () => {
+                auditCard.find('#end-target').text('1#')
+                auditCard.find('#fold-btn').text('展开历史审核记录')
+            })
+        }
+    });
+</script>
 <% } %>
 <% include ../shares/merge_peg_modal.ejs %>
 <% include ../shares/import_excel_modal.ejs %>
 <% include ../shares/delete_hint_modal.ejs %>
 <% include ../shares/check_data_modal.ejs %>
+<% include ../shares/ledger_check_modal.ejs %>

+ 2 - 2
app/view/material/audit_btn.ejs

@@ -13,9 +13,9 @@
     <% } else if (ctx.material.status === auditConst.status.checked) { %>
         <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm btn-block">审批完成</a>
     <% } else if (ctx.material.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>
+        <a href="#sp-list"  data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm btn-block text-muted sp-list-btn">审批退回</a>
         <% if (ctx.session.sessionUser.accountId === ctx.material.user_id) { %>
-            <a id="sp-list2-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sp-list2" class="btn btn-primary btn-sm btn-block">重新上报</a>
+            <a href="#sp-list" data-type="show" data-toggle="modal" data-target="#sp-list"  class="btn btn-primary btn-sm btn-block sp-list-btn">重新上报</a>
         <% } %>
     <% } %>
 </div>

Файловите разлики са ограничени, защото са твърде много
+ 592 - 752
app/view/material/audit_modal.ejs


+ 40 - 16
app/view/material/info.ejs

@@ -5,13 +5,18 @@
             <% include ./material_sub_mini_menu.ejs %>
             <div>
                 <% if ((material.status === auditConst.status.uncheck || material.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === material.user_id) { %>
-                <div class="d-inline-block">
-                    <a href="javascript: void(0);" id="add" class="btn btn-sm btn-light text-primary" 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);" id="del" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除材料"><i class="fa fa-remove" aria-hidden="true"></i></a>
-                </div>
+                    <div class="d-inline-block">
+                        <a href="javascript: void(0);" id="add" class="btn btn-sm btn-light text-primary" 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);" id="del" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除材料"><i class="fa fa-remove" aria-hidden="true"></i></a>
+                    </div>
                 <% } %>
                 <div class="d-inline-block">
                     本期调差计量期:第<span class="mx-2"><%= material.s_order.split(',').join(',') %></span>期
+                    <span id="qi-month">
+                        <% if (months.length > 0) { %>
+                            <span class="mx-2 text-muted">/</span>本期月信息价:<% let monthstr = ''; for (const m of months) { monthstr += parseInt(m.split('-')[1]) + '月,'} %><%- monthstr.substring(0,monthstr.length-1) %>
+                        <% } %>
+                    </span>
                 </div>
             </div>
             <div class="ml-auto">
@@ -22,7 +27,7 @@
         <div class="c-header p-0">
         </div>
         <div class="row w-100 sub-content">
-            <div id="main-view" class="c-body" style="width: 100%">
+            <div id="main-view" class="c-body col-8" style="width: 100%">
                 <!--上部分-->
                 <div class="sjs-height-1" id="material-spread">
                 </div>
@@ -35,17 +40,17 @@
                             </div>
                             <select class="form-control form-control-sm col-1" id="changeRate">
                                 <% if (!material.readOnly) { %>
-                                <option value="9" <% if(material.rate === 9) { %>selected<% } %>>9%</option>
-                                <option value="10" <% if(material.rate === 10) { %>selected<% } %>>10%</option>
-                                <option value="11" <% if(material.rate === 11) { %>selected<% } %>>11%</option>
+                                    <option value="9" <% if(material.rate === 9) { %>selected<% } %>>9%</option>
+                                    <option value="10" <% if(material.rate === 10) { %>selected<% } %>>10%</option>
+                                    <option value="11" <% if(material.rate === 11) { %>selected<% } %>>11%</option>
                                 <% } else { %>
-                                <option value="<%= material.rate %>" selected><%= material.rate %>%</option>
+                                    <option value="<%= material.rate %>" selected><%= material.rate %>%</option>
                                 <% } %>
                             </select>
                         </div>
                     </div>
                     <div class="sp-wrap">
-                        <div class="col-4 p-0">
+                        <div class="col-6 p-0">
                             <table class="table table-sm table-bordered">
                                 <tr><th></th><th>本期金额</th><th>截止本期金额</th></tr>
                                 <tr id="tp_set"><td>材料价差费用</td><td><%= material.m_tp !== null ? ctx.helper.round(material.m_tp, 2) : null %></td><td><%= material.m_tp !== null || material.pre_tp !== null ? ctx.helper.round(ctx.helper.add(material.pre_tp, material.m_tp), 2) : null %></td></tr>
@@ -55,6 +60,22 @@
                     </div>
                 </div>
             </div>
+            <div class="c-body col-4">
+                <div class="tab-content" style="width: 100%">
+                    <div id="qianyue" class="tab-pane active">
+                        <div class="sjs-bar-1">
+                            <% if ((material.status === auditConst.status.uncheck || material.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === material.user_id) {%>
+                            <div class="pb-1">
+                                <a href="#add-month" data-toggle="modal" data-target="#add-month" class="btn btn-sm btn-primary">创建月信息价</a>
+                                <a href="#remove-month" data-toggle="modal" data-target="#remove-month" class="btn btn-sm btn-outline-danger">移除月信息价</a>
+                            </div>
+                            <% } %>
+                        </div>
+                        <div id="material-month-spread">
+                        </div>
+                    </div>
+                </div>
+            </div>
         </div>
     </div>
 </div>
@@ -62,13 +83,13 @@
     <img src="/public/images/ellipsis_horizontal.png" id="ellipsis-icon" />
     <img src="/public/images/icon-ok.png" id="icon-ok" />
 </div>
-<link rel="stylesheet" href="/public/css/jquery-ui/datepicker.css">
-<script src="/public/js/jquery-ui/datepicker.js"></script>
-<script src="/public/js/jquery-ui/datepicker-zh-CN.js"></script>
+<!--<link rel="stylesheet" href="/public/css/jquery-ui/datepicker.css">-->
+<!--<script src="/public/js/jquery-ui/datepicker.js"></script>-->
+<!--<script src="/public/js/jquery-ui/datepicker-zh-CN.js"></script>-->
 <% if ((material.status === auditConst.status.uncheck || material.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === material.user_id) {%>
-<script>
-    const accountList = JSON.parse('<%- JSON.stringify(accountList) %>');
-</script>
+    <script>
+        const accountList = JSON.parse('<%- JSON.stringify(accountList) %>');
+    </script>
 <% } %>
 <script>
     const materialType = JSON.parse('<%- materialType %>');
@@ -80,4 +101,7 @@
     const pre_tp = <%= material.pre_tp !== null ? material.pre_tp : 0 %>;
     const pre_tp_hs = <%= pre_tp_hs !== null ? pre_tp_hs : 0 %>;
     const calcBase = JSON.parse('<%- JSON.stringify(calcBase) %>');
+    const months = JSON.parse('<%- JSON.stringify(months) %>');
+    let monthsList = JSON.parse('<%- JSON.stringify(monthsList) %>');
+    console.log(monthsList);
 </script>

+ 47 - 0
app/view/material/info_modal.ejs

@@ -32,4 +32,51 @@
         </div>
     </div>
 </div>
+<!--创建月信息价-->
+<div class="modal fade" id="add-month" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">创建月信息价</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label>信息价月份</label>
+                    <input class="datepicker-here form-control" id="months" placeholder="点击选择年月" data-view="months" data-min-view="months" data-date-format="yyyy-MM" data-language="zh" type="text">
+                </div>
+                <!--首次创建-->
+                <div class="alert alert-primary">创建月信息价后,「本期信息价」 「单价」列将无法自行填写,而是从「月信息价」中获取,如果有多个「月信息价」,将取平均值。</div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary" id="make-month">确认添加</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--移除月信息价-->
+<div class="modal fade" id="remove-month" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">移除月信息价</h5>
+            </div>
+            <div class="modal-body">
+                <div id="show_month">
+                    <% for (const m of months) { %>
+                        <div class="custom-control custom-checkbox mb-2">
+                            <input type="checkbox" name="del_month" value="<%- m %>" class="custom-control-input" id="month_<%- m %>">
+                            <label class="custom-control-label" for="month_<%- m %>"><%- m %>月</label>
+                        </div>
+                    <% } %>
+                </div>
+                <div class="alert alert-warning">移除后,对应月份信息价将删除,本期调差数据也将变动,请确认移除。</div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-danger" id="del-month">确认移除</button>
+            </div>
+        </div>
+    </div>
+</div>
 <% include ./audit_modal.ejs %>

+ 1 - 58
app/view/material/modal.ejs

@@ -70,67 +70,10 @@
                     <div class="col-4">
                         <div class="card mt-3">
                             <ul class="list-group list-group-flush" id="auditor-list">
-                                <li class="list-group-item"><i class="fa fa fa-play-circle fa-rotate-90"></i> 布尔  <small class="text-muted">施工</small></li>
-                                <li class="list-group-item"><i class="fa fa-chevron-circle-down"></i> 张三  <small class="text-muted">监理</small></li>
-                                <li class="list-group-item"><i class="fa fa-chevron-circle-down"></i> 王五 <small class="text-muted">监理</small></li>
-                                <li class="list-group-item"><i class="fa fa fa-stop-circle"></i> 李四 <small class="text-muted">监理</small></li>
                             </ul>
                         </div>
                     </div>
-                    <div class="col-8 modal-height-500" style="overflow: auto" id="auditor-list2">
-                        <div class="card mt-3">
-                            <ul class="list-group list-group-flush">
-                                <li class="list-group-item">
-                                    <span class="text-success pull-right">上报</span>
-                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> 布尔 <small class="text-muted">施工</small></h5>
-                                    <p class="card-text">2017-11-25</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-success pull-right">审批通过</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 张三 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批意见。2017-11-25</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-success pull-right">审批通过</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 王五 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批通过。2017-11-26</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-warning pull-right">审批退回 布尔</span>
-                                    <h5 class="card-title"><i class="fa fa-stop-circle text-warning"></i> 李四 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批退回,审批意见文本。2017-11-27</p>
-                                </li>
-                            </ul>
-                        </div>
-                        <!--退回原报重新上报-->
-                        <div class="card mt-3">
-                            <ul class="list-group list-group-flush">
-                                <li class="list-group-item">
-                                    <span class="text-success pull-right">重新上报</span>
-                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> 布尔 <small class="text-muted">施工</small></h5>
-                                    <p class="card-text">2017-12-01</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-success pull-right">审批通过</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 张三 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批通过 2017-12-02</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-warning pull-right">审批退回 张三</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-warning"></i> 王五 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批退回 2017-12-02</p>
-                                </li>
-                                <!--王五退回上一审批人 张三,张三重新审批-->
-                                <li class="list-group-item">
-                                    <span class="pull-right">审批中</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down"></i> 张三 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text"></p>
-                                </li>
-                                <li class="list-group-item">
-                                    <h5 class="card-title"><i class="fa fa-stop-circle"></i> 李四 <small class="text-muted">监理</small></h5>
-                                </li>
-                            </ul>
-                        </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto" id="audit-list">
                     </div>
                 </div>
             </div>

+ 20 - 59
app/view/measure/stage_modal.ejs

@@ -42,67 +42,10 @@
                     <div class="col-4">
                         <div class="card mt-3">
                             <ul class="list-group list-group-flush" id="auditor-list">
-                                <li class="list-group-item"><i class="fa fa fa-play-circle fa-rotate-90"></i> 布尔  <small class="text-muted">施工</small></li>
-                                <li class="list-group-item"><i class="fa fa-chevron-circle-down"></i> 张三  <small class="text-muted">监理</small></li>
-                                <li class="list-group-item"><i class="fa fa-chevron-circle-down"></i> 王五 <small class="text-muted">监理</small></li>
-                                <li class="list-group-item"><i class="fa fa fa-stop-circle"></i> 李四 <small class="text-muted">监理</small></li>
                             </ul>
                         </div>
                     </div>
-                    <div class="col-8 modal-height-500" style="overflow: auto" id="auditor-list2">
-                        <div class="card mt-3">
-                            <ul class="list-group list-group-flush">
-                                <li class="list-group-item">
-                                    <span class="text-success pull-right">上报</span>
-                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> 布尔 <small class="text-muted">施工</small></h5>
-                                    <p class="card-text">2017-11-25</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-success pull-right">审批通过</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 张三 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批意见。2017-11-25</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-success pull-right">审批通过</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 王五 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批通过。2017-11-26</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-warning pull-right">审批退回 布尔</span>
-                                    <h5 class="card-title"><i class="fa fa-stop-circle text-warning"></i> 李四 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批退回,审批意见文本。2017-11-27</p>
-                                </li>
-                            </ul>
-                        </div>
-                        <!--退回原报重新上报-->
-                        <div class="card mt-3">
-                            <ul class="list-group list-group-flush">
-                                <li class="list-group-item">
-                                    <span class="text-success pull-right">重新上报</span>
-                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> 布尔 <small class="text-muted">施工</small></h5>
-                                    <p class="card-text">2017-12-01</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-success pull-right">审批通过</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 张三 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批通过 2017-12-02</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-warning pull-right">审批退回 张三</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-warning"></i> 王五 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批退回 2017-12-02</p>
-                                </li>
-                                <!--王五退回上一审批人 张三,张三重新审批-->
-                                <li class="list-group-item">
-                                    <span class="pull-right">审批中</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down"></i> 张三 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text"></p>
-                                </li>
-                                <li class="list-group-item">
-                                    <h5 class="card-title"><i class="fa fa-stop-circle"></i> 李四 <small class="text-muted">监理</small></h5>
-                                </li>
-                            </ul>
-                        </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto" id="audit-list">
                     </div>
                 </div>
             </div>
@@ -172,13 +115,31 @@
             toastr.error('请选择开始-截止日期');
             return false;
         }
-    }
+    };
     <% } %>
     $('.datepicker-here').datepicker({
         autoClose: true,
     });
     const tenderId = '<%- ctx.tender.id %>';
     const auditConst = JSON.parse('<%- auditConst2 %>');
+
+    $('#audit-list').on('click', 'a', function() {
+    const type = $(this).data('target')
+    const auditCard = $(this).parent().parent()
+    if (type === 'show') {
+        $(this).data('target', 'hide')
+        auditCard.find('.fold-card').slideDown('swing', () => {
+            auditCard.find('#end-target').text($(this).data('idx') + '#')
+            auditCard.find('#fold-btn').text('收起历史审核记录')
+        })
+    } else {
+        $(this).data('target', 'show')
+        auditCard.find('.fold-card').slideUp('swing', () => {
+            auditCard.find('#end-target').text('1#')
+            auditCard.find('#fold-btn').text('展开历史审核记录')
+        })
+    }
+});
 </script>
 <script src="/public/js/moment/moment.min.js"></script>
 <script src="/public/js/measure_stage.js"></script>

+ 2 - 0
app/view/profile/sms.ejs

@@ -50,6 +50,7 @@
                             <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) { %>
+                            <% if (smsType[s].sms) { %>
                             <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>-->
@@ -64,6 +65,7 @@
                                 </div>
                             </div>
                             <% } %>
+                            <% } %>
                             <input name="type" value="1" type="hidden">
                             <button type="submit" class="btn btn-primary btn-sm">确认修改</button>
                         </form>

+ 2 - 0
app/view/profile/wechat.ejs

@@ -37,6 +37,7 @@
                             <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
                             <% const user_wxType = accountData.wx_type !== '' ? JSON.parse(accountData.wx_type) : null; %>
                             <% for (const s in smsType) { %>
+                            <% if (smsType[s].wechat) { %>
                             <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>-->
@@ -51,6 +52,7 @@
                                 </div>
                             </div>
                             <% } %>
+                            <% } %>
                             <input name="type" value="0" type="hidden">
                             <button type="submit" class="btn btn-primary btn-sm">确认修改</button>
                         </form>

+ 2 - 2
app/view/revise/history.ejs

@@ -105,9 +105,9 @@
     </div>
 </div>
 <script>
-    const posSpreadSetting = JSON.parse('<%- JSON.stringify(posSpread) %>');   
+    const posSpreadSetting = JSON.parse('<%- JSON.stringify(posSpread) %>');
     const readOnly = <%- readOnly %>;
     const isTz = <%- ctx.tender.data.measure_type === measureType.tz.value %>;
     const billsSpreadSetting = JSON.parse('<%- JSON.stringify(ledgerSpread) %>');
     const thousandth = <%- ctx.tender.info.display.thousandth %>;
-</script>
+</script>

+ 198 - 0
app/view/revise/history_modal.ejs

@@ -0,0 +1,198 @@
+<!--审批流程/结果-->
+<div class="modal fade" id="sp-list" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">审批流程</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-4">
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush">
+                                <% auditors.forEach((item, idx) => { %>
+                                <% if (idx === 0) { %>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right">原报</span>
+                                </li>
+                                <% } else if(idx === auditors.length -1 && idx !== 0) { %>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right">终审</span>
+                                </li>
+                                <% } else {%>
+                                <li class="list-group-item" data-auditorId="<%- item.audit_id %>">
+                                    <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                    <small class="text-muted"><%- item.role %></small>
+                                    <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
+                                </li>
+                                <% } %>
+                                <% }) %>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto">
+                        <% auditHistory.forEach((auditors, idx) => { %>
+                        <div class="<%- idx < auditHistory.length - 1 ? 'fold-card' : '' %>">
+                            <div class="text-center text-muted"
+                                <%- idx === auditHistory.length - 1 ? `id="end-target"` : "" %>>
+                                <%- idx === auditHistory.length - 1 ? 1 : idx+1 %>#</div>
+                            <ul class="timeline-list list-unstyled mt-2">
+                                <% auditors.forEach((auditor, index) => { %>
+                                <% if (index === 0) { %>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.begin_time) %>
+                                    </div>
+                                    <div class="timeline-item-tail"></div>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-caret-down"></i>
+                                    </div>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span
+                                                            class="h5"><%- user.name %></span><span
+                                                            class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- user.role %></p>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </li>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.end_time) %>
+                                    </div>
+                                    <% if(index < auditors.length - 1) { %>
+                                    <div class="timeline-item-tail"></div>
+                                    <% } %>
+                                    <% if(auditor.status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-check"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-level-up"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-ellipsis-h"></i>
+                                    </div>
+                                    <% } else {%>
+                                    <div class="timeline-item-icon bg-secondary text-light">
+                                    </div>
+                                    <% } %>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
+                                                            class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                </div>
+                                            </div>
+
+                                            <!--审批意见-->
+                                            <% if (auditor.opinion) { %>
+                                            <div class="card-body p-3 border-top">
+                                                <p style="margin: 0;"><%- auditor.opinion %></p>
+                                            </div>
+                                            <% } %>
+                                        </div>
+                                    </div>
+                                </li>
+                                <% } else {%>
+                                <li class="timeline-list-item pb-2">
+                                    <div class="timeline-item-date">
+                                        <%- ctx.helper.formatDate(auditor.end_time) %>
+                                    </div>
+                                    <% if(index < auditors.length - 1) { %>
+                                    <div class="timeline-item-tail"></div>
+                                    <% } %>
+                                    <% if(auditor.status === auditConst.status.checked) { %>
+                                    <div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-check"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-level-up"></i>
+                                    </div>
+                                    <% } else if(auditor.status === auditConst.status.checking) { %>
+                                    <div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-ellipsis-h"></i>
+                                    </div>
+                                    <% } else { %>
+                                    <div class="timeline-item-icon bg-secondary text-light">
+                                    </div>
+                                    <% } %>
+                                    <div class="timeline-item-content">
+                                        <div class="card">
+                                            <div class="card-body p-3">
+                                                <div class="card-text">
+                                                    <p class="mb-1"><span class="h5"><%- auditor.name %></span>
+                                                        <span
+                                                            class="pull-right
+                                                                            <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                            <%- auditor.status === auditConst.status.checkNo ? user.name : '' %>
+                                                            <%- auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : '' %>
+                                                        </span>
+                                                    </p>
+                                                    <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                </div>
+                                            </div>
+                                            <!--审批意见-->
+                                            <% if (auditor.opinion) { %>
+                                            <div class="card-body p-3 border-top">
+                                                <p style="margin: 0;"><%- auditor.opinion %></p>
+                                            </div>
+                                            <% } %>
+                                        </div>
+                                    </div>
+                                </li>
+                                <% } %>
+                                <% }) %>
+                            </ul>
+                        </div>
+                        <!-- 展开/收起历史流程 -->
+                        <% if(idx === auditHistory.length - 1 && auditHistory.length !== 1) { %>
+                            <div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
+                                    data-idx="<%- idx + 1 %>">展开历史审批流程</a>
+                            </div>
+                        <% } %>
+                        <% }) %>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    // 展开历史审核记录
+    $('.modal-body #fold-btn').click(function () {
+        const type = $(this).data('target')
+        const auditCard = $(this).parent().parent()
+        if (type === 'show') {
+            $(this).data('target', 'hide')
+            auditCard.find('.fold-card').slideDown('swing', () => {
+                auditCard.find('#end-target').text($(this).data('idx') + '#')
+                auditCard.find('#fold-btn').text('收起历史审核记录')
+            })
+        } else {
+            $(this).data('target', 'show')
+            auditCard.find('.fold-card').slideUp('swing', () => {
+                auditCard.find('#end-target').text('1#')
+                auditCard.find('#fold-btn').text('展开历史审核记录')
+            })
+        }
+    });
+</script>

+ 23 - 4
app/view/revise/info.ejs

@@ -41,6 +41,9 @@
                         <input type="text" class="form-control form-control-sm m-0" id="bills-expr" readonly="">
                     </div>
                 </div>
+                <div class="d-inline-block ml-3">
+                    <a class="btn btn-sm btn-primary" href="#ledger-check-modal" data-toggle="modal" data-target="#ledger-check-modal">数据检查</a>
+                </div>
             </div>
             <% } %>
             <div class="ml-auto">
@@ -48,9 +51,9 @@
                 <a href="#sub-sp" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm pull-right mr-1">上报审批</a>
                 <% } %>
                 <% if (revise.status === audit.status.checkNo) { %>
-                <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm pull-right mr-1">审批退回</a>
+                <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm pull-right mr-1 sp-list-btn">审批退回</a>
                     <% if (revise.uid === ctx.session.sessionUser.accountId) { %>
-                    <a href="#sub-sp" data-toggle="modal" data-target="#sub-sp2" class="btn btn-primary btn-sm pull-right mr-1">重新上报</a>
+                    <a href="#sp-list" data-type="show" data-toggle="modal" data-target="#sp-list" class="btn btn-primary btn-sm pull-right mr-1 sp-list-btn">重新上报</a>
                     <% } %>
                 <% } %>
                 <% if (revise.status === audit.status.checking) { %>
@@ -61,7 +64,7 @@
                     <% } %>
                 <% } %>
                 <% if (revise.status === audit.status.checked) { %>
-                <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm pull-right mr-1">审批完成</a>
+                <a href="#sp-list" data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm pull-right mr-1 sp-list-btn">审批完成</a>
                 <% } %>
             </div>
         </div>
@@ -178,6 +181,8 @@
                     </div>
                     <div id="error-list" class="tab-pane">
                     </div>
+                    <div id="check-list" class="tab-pane">
+                    </div>
                 </div>
             </div>
         </div>
@@ -206,14 +211,28 @@
                 <li class="nav-item">
                     <a class="nav-link" content="#error-list" id="error-list-tab" href="javascript: void(0);" style="display: none;">错误列表</a>
                 </li>
+                <li class="nav-item">
+                    <a class="nav-link" content="#check-list" id="check-list-tab" href="javascript: void(0);" style="display: none;">数据检查</a>
+                </li>
             </ul>
         </div>
     </div>
 </div>
+<script src="/public/js/moment/moment.min.js"></script>
 <script>
     const readOnly = <%- readOnly %>;
     const isTz = <%- ctx.tender.data.measure_type === measureType.tz.value %>;
     const billsSpreadSetting = JSON.parse('<%- JSON.stringify(ledgerSpread) %>');
     const posSpreadSetting = JSON.parse('<%- JSON.stringify(posSpread) %>');
     const thousandth = <%- ctx.tender.info.display.thousandth %>;
-</script>
+    $('.sp-list-btn').click(function () {
+        const type = $(this).data('type')
+        if (type === 'hide') {
+            $('.sp-list-item').hide()
+            $('.modal-title').text('审批流程')
+        } else {
+            $('.sp-list-item').show()
+            $('.modal-title').text('重新上报')
+        }
+    });
+</script>

Файловите разлики са ограничени, защото са твърде много
+ 779 - 506
app/view/revise/info_modal.ejs


+ 193 - 120
app/view/revise/modal.ejs

@@ -28,67 +28,10 @@
                     <div class="col-4">
                         <div class="card mt-3">
                             <ul class="list-group list-group-flush" id="auditor-list">
-                                <li class="list-group-item"><i class="fa fa fa-play-circle fa-rotate-90"></i> 布尔  <small class="text-muted">施工</small></li>
-                                <li class="list-group-item"><i class="fa fa-chevron-circle-down"></i> 张三  <small class="text-muted">监理</small></li>
-                                <li class="list-group-item"><i class="fa fa-chevron-circle-down"></i> 王五 <small class="text-muted">监理</small></li>
-                                <li class="list-group-item"><i class="fa fa fa-stop-circle"></i> 李四 <small class="text-muted">监理</small></li>
                             </ul>
                         </div>
                     </div>
-                    <div class="col-8 modal-height-500" style="overflow: auto" id="auditor-list2">
-                        <div class="card mt-3">
-                            <ul class="list-group list-group-flush">
-                                <li class="list-group-item">
-                                    <span class="text-success pull-right">上报</span>
-                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> 布尔 <small class="text-muted">施工</small></h5>
-                                    <p class="card-text">2017-11-25</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-success pull-right">审批通过</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 张三 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批意见。2017-11-25</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-success pull-right">审批通过</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 王五 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批通过。2017-11-26</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-warning pull-right">审批退回 布尔</span>
-                                    <h5 class="card-title"><i class="fa fa-stop-circle text-warning"></i> 李四 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批退回,审批意见文本。2017-11-27</p>
-                                </li>
-                            </ul>
-                        </div>
-                        <!--退回原报重新上报-->
-                        <div class="card mt-3">
-                            <ul class="list-group list-group-flush">
-                                <li class="list-group-item">
-                                    <span class="text-success pull-right">重新上报</span>
-                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> 布尔 <small class="text-muted">施工</small></h5>
-                                    <p class="card-text">2017-12-01</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-success pull-right">审批通过</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 张三 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批通过 2017-12-02</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-warning pull-right">审批退回 张三</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-warning"></i> 王五 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批退回 2017-12-02</p>
-                                </li>
-                                <!--王五退回上一审批人 张三,张三重新审批-->
-                                <li class="list-group-item">
-                                    <span class="pull-right">审批中</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down"></i> 张三 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text"></p>
-                                </li>
-                                <li class="list-group-item">
-                                    <h5 class="card-title"><i class="fa fa-stop-circle"></i> 李四 <small class="text-muted">监理</small></h5>
-                                </li>
-                            </ul>
-                        </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto" id="audit-list">
                     </div>
                 </div>
             </div>
@@ -139,81 +82,211 @@
 
     $(function () {
         // 获取审批流程
-        $('a[data-target="#sp-list" ]').on('click', function () {
+        $('a[data-target="#sp-list"]').on('click', function () {
             const data = {
                 id: $(this).attr('lr-id'),
             };
             postData('<%- preUrl + "/revise/auditors" %>', data, function (result) {
-                const reviseAuditor = result.reviseAuditor;
-                const auditors = result.auditors;
-                const auditHistory = result.auditHistory;
-                // 生成左边列表流程
-                const lefthtml = [];
-                lefthtml.push('<li class="list-group-item"><i class="fa fa fa-play-circle fa-rotate-90"></i> '+ reviseAuditor.name +'  <small class="text-muted">'+ reviseAuditor.role +'</small><span class="pull-right">原报</span></li>');
-                for (const [index,a] of auditors.entries()) {
-                    if (index+1 === auditors.length) {
-                        lefthtml.push('<li class="list-group-item"><i class="fa fa-stop-circle"></i> '+ a.name +'  <small class="text-muted">'+ a.role +'</small><span class="pull-right">终审</span></li>');
+                const { auditHistory, auditors, user } = result
+                let auditorsHTML = ''
+                let historyHTML = ''
+                auditors.forEach((auditor, idx) => {
+                    if (idx === 0) {
+                        auditorsHTML += `<li class="list-group-item">
+                            <i class="fa fa fa-play-circle fa-rotate-90"></i> ${auditor.name}
+                            <small class="text-muted">${auditor.role}</small>
+                            <span class="pull-right">原报</span>
+                        </li>`
+                    } else if(idx === auditors.length -1 && idx !== 0) {
+                        auditorsHTML += `<li class="list-group-item">
+                            <i class="fa fa fa-stop-circle"></i> ${auditor.name}
+                            <small class="text-muted">${auditor.role}</small>
+                            <span class="pull-right">终审</span>
+                        </li>`
                     } else {
-                        lefthtml.push('<li class="list-group-item"><i class="fa fa-chevron-circle-down"></i> '+ a.name +'  <small class="text-muted">'+ a.role +'</small><span class="pull-right">' + transFormToChinese(index+1) + '审</span></li>');
+                        auditorsHTML += `<li class="list-group-item">
+                            <i class="fa fa-chevron-circle-down"></i> ${auditor.name}
+                            <small class="text-muted">${auditor.role}</small>
+                            <span class="pull-right">${transFormToChinese(idx)}审</span>
+                        </li>`
                     }
-                }
-                $('#auditor-list').html(lefthtml.join(''));
+                })
+                $('#auditor-list').empty()
+                $('#auditor-list').append(auditorsHTML)
+                auditHistory.forEach((auditors, idx) => {
+                    historyHTML += `<div class="${idx < auditHistory.length - 1 ? 'fold-card' : ''}">
+                    <div class="text-center text-muted"
+                        ${idx === auditHistory.length - 1 ? 'id="end-target"' : ""}>
+                        ${idx === auditHistory.length - 1 ? 1 : idx + 1}#</div>
+                    <ul class="timeline-list list-unstyled mt-2">`
+                    auditors.forEach((auditor, index) => {
+                        if (index === 0) {
+                            historyHTML += `<li class="timeline-list-item pb-2">
+                                <div class="timeline-item-date">
+                                    ${formatDate(auditor.begin_time)}
+                                </div>
+                                <div class="timeline-item-tail"></div>
+                                <div class="timeline-item-icon bg-success text-light">
+                                    <i class="fa fa-caret-down"></i>
+                                </div>
+                                <div class="timeline-item-content">
+                                    <div class="card">
+                                        <div class="card-body p-3">
+                                            <div class="card-text">
+                                                <p class="mb-1"><span
+                                                        class="h5">${user.name}</span><span
+                                                        class="pull-right text-success">${idx !== 0 ? '重新' : ''}上报审批</span>
+                                                </p>
+                                                <p class="text-muted mb-0">${user.role}</p>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </li>
+                            <li class="timeline-list-item pb-2">
+                                <div class="timeline-item-date">
+                                    ${formatDate(auditor.end_time)}
+                                </div>`
 
-                // 生成右边列表流程
-                const righthtml = [];
-                for(const ah of auditHistory) {
-                    righthtml.push('<div class="card mt-3"><ul class="list-group list-group-flush">');
-                    for (let iA = 0; iA < ah.length; iA++) {
-                        if (iA === 0) {
-                            righthtml.push('<li class="list-group-item">');
-                            righthtml.push('<h5 class="card-title">');
-                            righthtml.push('<i class="fa fa-play-circle fa-rotate-90 text-success"></i> '+ reviseAuditor.name +' <small class="text-muted">'+ reviseAuditor.role +'</small><span class="pull-right">原报</span></h5>');
-                            righthtml.push('<div class="ml-3">');
-                            righthtml.push('<span class="text-success"><small>' + (ah[iA].begin_time ? moment(ah[iA].begin_time).format('YYYY-MM-DD') : '') + '</small> '+ (auditHistory.indexOf(ah) > 0 ? '重新' : '') + '上报</span></div></li>');
-                            righthtml.push('<li class="list-group-item">');
-                            righthtml.push('<h5 class="card-title"><i class="fa '+ (iA === ah.length - 1 ? 'fa-stop-circle ' : 'fa-chevron-circle-down ') + auditConst.statusClass[ah[iA].status] +'"></i> '+ ah[iA].name +' <small class="text-muted">'+ ah[iA].role +'</small><span class="pull-right">' + (ah[iA].sort === ah[iA].max_sort ? '终' : transFormToChinese(ah[iA].sort)) + '审</span></h5>');
-                            righthtml.push('<div class="ml-3">');
-                            if (ah[iA].status !== auditConst.status.uncheck) {
-                                let timeHtml = '';
-                                if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) {
-                                    timeHtml = '<small>'+ (ah[iA].end_time ? moment(ah[iA].end_time).format('YYYY-MM-DD') : '') +'</small> ';
+                                if(index < auditors.length - 1) {
+                                    historyHTML += `<div class="timeline-item-tail"></div>`
                                 }
-                                righthtml.push('<span class="' + auditConst.statusClass[ah[iA].status] +'">'+ timeHtml + auditConst.statusString[ah[iA].status] + (ah[iA].status === auditConst.status.checkNo ? ' ' + reviseAuditor.name : '') + '</span>');
-                            }
-                            righthtml.push('<p class="card-text">'+ (ah[iA].opinion !== null ? ah[iA].opinion : '') +'</p></div>');
-                            righthtml.push('</li>');
-                        } else if (iA === ah.length - 1) {
-                            righthtml.push('<li class="list-group-item">');
-                            righthtml.push('<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><span class="pull-right">终审</span></h5>');
-                            righthtml.push('<div class="ml-3">');
-                            if (ah[iA].status !== auditConst.status.uncheck) {
-                                let timeHtml = '';
-                                if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) {
-                                    timeHtml = '<small>'+ (ah[iA].end_time ? moment(ah[iA].end_time).format('YYYY-MM-DD') : '') +'</small> ';
+                                if(auditor.status === auditConst.status.checked) {
+                                    historyHTML += `<div class="timeline-item-icon bg-success text-light">
+                                        <i class="fa fa-check"></i>
+                                    </div>`
+
+                                } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {
+                                    historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-level-up"></i>
+                                    </div>`
+                                } else if(auditor.status === auditConst.status.checking) {
+                                    historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                        <i class="fa fa-ellipsis-h"></i>
+                                    </div>`
+                                } else {
+                                    historyHTML += `<div class="timeline-item-icon bg-secondary text-light"></div>`
+
                                 }
-                                righthtml.push('<span class="' + auditConst.statusClass[ah[iA].status] +'">' + timeHtml + auditConst.statusString[ah[iA].status] + (ah[iA].status === auditConst.status.checkNo ? ' ' + reviseAuditor.name : '') + '</span>');
-                            }
-                            righthtml.push('<p class="card-text">'+ (ah[iA].opinion !== null ? ah[iA].opinion : '') +'</p></div>');
-                            righthtml.push('</li>');
-                        } else {
-                            righthtml.push('<li class="list-group-item">');
-                            righthtml.push('<h5 class="card-title"><i class="fa '+ (iA === ah.length - 1 ? 'fa-stop-circle ' : 'fa-chevron-circle-down ') + auditConst.statusClass[ah[iA].status] +'"></i> '+ ah[iA].name +' <small class="text-muted">'+ ah[iA].role +'</small><span class="pull-right">' + (ah[iA].sort === ah[iA].max_sort ? '终' : transFormToChinese(ah[iA].sort)) + '审</span></h5>');
-                            righthtml.push('<div class="ml-3">');
-                            if (ah[iA].status !== auditConst.status.uncheck) {
-                                let timeHtml = '';
-                                if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) {
-                                    timeHtml = '<small>'+ (ah[iA].end_time ? moment(ah[iA].end_time).format('YYYY-MM-DD') : '') +'</small> ';
+                                historyHTML += `<div class="timeline-item-content">
+                                    <div class="card">
+                                        <div class="card-body p-3">
+                                            <div class="card-text">
+                                                <p class="mb-1"><span class="h5">${auditor.name}</span><span
+                                                        class="pull-right ${auditConst.statusClass[auditor.status]}">${auditConst.statusString[auditor.status]}</span>
+                                                </p>
+                                                <p class="text-muted mb-0">${auditor.role}</p>
+                                            </div>
+                                        </div>`
+                                if (auditor.opinion) {
+                                historyHTML += `<div class="card-body p-3 border-top">
+                                        <p style="margin: 0;">${auditor.opinion}</p>
+                                    </div>`
                                 }
-                                righthtml.push('<span class="' + auditConst.statusClass[ah[iA].status] +'">'+ timeHtml + auditConst.statusString[ah[iA].status] + (ah[iA].status === auditConst.status.checkNo ? ' ' + reviseAuditor.name : '') + '</span>');
+                                historyHTML += `</div></div></li>`
+                        } else {
+                            historyHTML += `<li class="timeline-list-item pb-2">
+                            <div class="timeline-item-date">
+                                ${formatDate(auditor.end_time)}
+                            </div>`
+
+                            if(index < auditors.length - 1) {
+                                historyHTML += `<div class="timeline-item-tail"></div>`
                             }
-                            righthtml.push('<p class="card-text">'+ (ah[iA].opinion !== null ? ah[iA].opinion : '') +'</p></div>');
-                            righthtml.push('</li>');
+                            if(auditor.status === auditConst.status.checked) {
+                                historyHTML += `<div class="timeline-item-icon bg-success text-light">
+                                    <i class="fa fa-check"></i>
+                                </div>`
+                            } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {
+                                historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                    <i class="fa fa-level-up"></i>
+                                </div>`
+
+                            } else if(auditor.status === auditConst.status.checking) {
+                                historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                    <i class="fa fa-ellipsis-h"></i>
+                                </div>`
+                            } else {
+                                historyHTML += `<div class="timeline-item-icon bg-secondary text-light"></div>`
+                            }
+                            historyHTML += `<div class="timeline-item-content">
+                            <div class="card">
+                                <div class="card-body p-3">
+                                    <div class="card-text">
+                                        <p class="mb-1"><span class="h5">${auditor.name}</span>
+                                            <span
+                                                class="pull-right
+                                                                ${auditConst.statusClass[auditor.status]}">${auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''}
+                                                ${auditor.status === auditConst.status.checkNo ? user.name : ''}
+                                                ${auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : ''}
+                                            </span>
+                                        </p>
+                                        <p class="text-muted mb-0">${auditor.role}</p>
+                                    </div>
+                                </div>`
+
+                            if (auditor.opinion) {
+                            historyHTML += `<div class="card-body p-3 border-top">
+                                <p style="margin: 0;">${auditor.opinion} </p>
+                            </div>`
+                            }
+                            historyHTML += `</div></div></li>`
                         }
+                    })
+                    historyHTML += '</ul></div>'
+                    if(idx === auditHistory.length - 1 && auditHistory.length !== 1) {
+                        historyHTML += `<div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
+                        data-idx="${idx + 1}">展开历史审批流程</a></div>`
                     }
-                    righthtml.push('</ul></div>');
-                }
-                $('#auditor-list2').html(righthtml.join(''));
+                })
+                $('#audit-list').empty()
+                $('#audit-list').append(historyHTML)
             })
         });
-    })
+    });
+    function formatDate(date) {
+        if (!date) return '';
+        date = new Date(date)
+        const year = date.getFullYear();
+        let mon = date.getMonth() + 1;
+        let day = date.getDate();
+        let hour = date.getHours();
+        let minute = date.getMinutes();
+        let scond = date.getSeconds();
+        if (mon < 10) {
+            mon = '0' + mon.toString();
+        }
+        if (day < 10) {
+            day = '0' + day.toString();
+        }
+        if (hour < 10) {
+            hour = '0' + hour.toString();
+        }
+        if (minute < 10) {
+            minute = '0' + minute.toString();
+        }
+        if (scond < 10) {
+            scond = '0' + scond.toString();
+        }
+        return `${year}<span>${mon}-${day}</span><span>${hour}:${minute}:${scond}</span>`;
+    };
+
+    $('#audit-list').on('click', 'a', function() {
+        const type = $(this).data('target')
+        const auditCard = $(this).parent().parent()
+        console.log('auditCard', auditCard)
+        if (type === 'show') {
+            $(this).data('target', 'hide')
+            auditCard.find('.fold-card').slideDown('swing', () => {
+                auditCard.find('#end-target').text($(this).data('idx') + '#')
+                auditCard.find('#fold-btn').text('收起历史审核记录')
+            })
+        } else {
+            $(this).data('target', 'show')
+            auditCard.find('.fold-card').slideUp('swing', () => {
+                auditCard.find('#end-target').text('1#')
+                auditCard.find('#fold-btn').text('展开历史审核记录')
+            })
+        }
+    });
 </script>

+ 9 - 1
app/view/setting/user.ejs

@@ -3,7 +3,7 @@
     <div class="panel-title">
         <div class="title-main">
             <h2>账号管理
-                <% if (projectData.max_user !== accountData.length) { %>
+                <% if (projectData.max_user !== user_total) { %>
                 <a href="#ver" data-toggle="modal" data-target="#add-user" class="btn btn-primary btn-sm pull-right">添加账号</a>
                 <% } else { %>
                 <a href="#add-unpass" data-toggle="modal" data-target="#add-unpass" class="btn btn-primary btn-sm pull-right">添加账号(受限)</a>
@@ -17,6 +17,14 @@
                 <nav class="nav nav-tabs m-3" role="tablist">
                     <a class="nav-item nav-link active" href="/setting/user">账号列表</a>
                     <a class="nav-item nav-link" href="/setting/user/permission/set">账号权限</a>
+                    <div class="ml-auto">
+                        <form class="input-group input-group-sm" method="get">
+                            <input type="text" class="form-control" value="<%- keyword %>" name="keyword" placeholder="账号/姓名/单位/手机 搜索" aria-label="账号/姓名/单位/手机 搜索" aria-describedby="button-addon2">
+                            <div class="input-group-append">
+                                <button class="btn btn-outline-primary" type="submit" id="button-addon2"><i class="fa fa-search"></i></button>
+                            </div>
+                        </form>
+                    </div>
                 </nav>
                 <div class="tab-content m-3">
                     <div id="user-list" class="tab-pane active">

+ 201 - 0
app/view/shares/ledger_check_modal.ejs

@@ -0,0 +1,201 @@
+
+<!--数据检查-->
+<div class="modal fade" id="ledger-check-modal" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">数据检查</h5>
+            </div>
+            <div class="modal-body">
+                <p>数据检查,将检查罗列台帐中以下内容:</p>
+                <div class="card mb-2 p-2 border-success" id="check-sibling">
+                    <div class="d-flex justify-content-between">
+                        项目节、清单同层
+                        <span class="text-muted" name="check-status">待检查</span>
+                    </div>
+                </div>
+                <div class="card mb-2 p-2 border-success" id="check-empty-code">
+                    <div class="d-flex justify-content-between">
+                        项目节、清单编号同时为空
+                        <!-- <span class="text-muted">待检查</span> -->
+                        <!-- <span class="text-muted" title="检查中"><i class="fa fa-spinner fa-spin"></i></span> -->
+                        <span class="text-success" title="完成" name="check-status"><i class="fa fa-check"></i></span>
+                    </div>
+                </div>
+                <div class="card mb-2 p-2 border-success" id="check-calc">
+                    <div class="d-flex justify-content-between">
+                        清单数量不等于计量单元之和
+                        <!-- <span class="text-muted">待检查</span> -->
+                        <!-- <span class="text-muted" title="检查中"><i class="fa fa-spinner fa-spin"></i></span> -->
+                        <span class="text-success" title="完成" name="check-status"><i class="fa fa-check"></i></span>
+                    </div>
+                </div>
+                <div class="card mb-2 p-2 border-success" id="check-zero">
+                    <div class="d-flex justify-content-between">
+                        清单数量或单价为0
+                        <!-- <span class="text-muted">待检查</span> -->
+                        <!-- <span class="text-muted" title="检查中"><i class="fa fa-spinner fa-spin"></i></span> -->
+                        <span class="text-success" title="完成" name="check-status"><i class="fa fa-check"></i></span>
+                    </div>
+                </div>
+                 <a href="javascript: void(0);" class="btn btn-sm btn-block btn-primary" id="ledger-check-begin">开始检查</a>
+                 <a href="#" class="btn btn-sm btn-block btn-primary disabled" id="ledger-check-waiting">检查中,请等待...</a>
+                <p class="text-center text-success" id="ledger-check-hint">检查完成,现在您可以查看结果。</p>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <!--检查完有结果显示按钮-->
+                <button type="button" class="btn btn-sm btn-primary" id="ledger-check-show">查看结果</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const ledgerCheckType = {
+        sibling: {value: 1, text: '项目节、清单同层'},
+        empty_code: {value: 2, text: '项目节、清单编号同时为空'},
+        calc: {value: 3, text: '清单数量不等于计量单元之和'},
+        zero: {value: 4, text: '清单数量或单价为0'},
+    };
+    const LedgerChecker = function (setting) {
+        const ledger = setting.ledgerTree, ledgerPos = setting.ledgerPos;
+
+        const initWaitingModal = function () {
+            $('.card', '#ledger-check-modal').removeClass('border-success');
+            $('[name=check-status]', '#ledger-check-modal').removeClass('text-success').addClass('text-muted').html('待检查');
+            $('#ledger-check-begin').show();
+            $('#ledger-check-waiting').hide();
+            $('#ledger-check-hint').hide();
+            $('#ledger-check-show').hide();
+        }
+
+        const doSomeCheck = function (selector, checkFun) {
+            const checkStatus = $('[name=check-status]', selector);
+            checkStatus.html('<i class="fa fa-spinner fa-spin"></i>');
+            const result = checkFun(ledger);
+            checkStatus.removeClass('text-muted').addClass('text-success').html('<i class="fa fa-check"></i>');
+            $(selector).addClass('border-success');
+            return result;
+        }
+
+        const ledgerCheckUtil = {
+            checkSibling: function (ledgerTree) {
+                const error = [];
+                for (const node of ledgerTree.nodes) {
+                    if (!node.children || node.children.length === 0) continue;
+                    let hasXmj, hasGcl;
+                    for (const child of node.children) {
+                        if (child.b_code) hasXmj = true;
+                        if (!child.b_code) hasGcl = true;
+                    }
+                    if (hasXmj && hasGcl) error.push(node);
+                }
+                return error;
+            },
+            checkCodeEmpty: function (ledgerTree) {
+                const error = [];
+                const checkNodeCode = function (node) {
+                    if ((!node.code || node.code === '') && (!node.b_code || node.b_code === '')) error.push(node);
+                    if (node.children && node.children.length > 0) {
+                        for (const child of node.children) {
+                            checkNodeCode(child);
+                        }
+                    }
+                }
+                for (const topLevel of ledgerTree.children) {
+                    if (topLevel.node_type !== 1) continue;
+
+                    checkNodeCode(topLevel);
+                }
+                return error;
+            },
+            checkCalc: function (ledgerTree) {
+                const error = [];
+                for (const node of ledgerTree.nodes) {
+                    if (node.children && node.children.length > 0) continue;
+
+                    const nodePos = ledgerPos.getLedgerPos(node.id);
+                    if (!nodePos || nodePos.length === 0) continue;
+
+                    const checkData = {
+                        sgfh_qty: node.sgfh_qty || 0, qtcl_qty: node.qtcl_qty || 0, sjcl_qty: node.sjcl_qty || 0,
+                        quantity: node.quantity || 0
+                    };
+                    const calcData = {
+                        sgfh_qty: 0, qtcl_qty: 0, sjcl_qty: 0,
+                        quantity: 0
+                    };
+                    for (const np of nodePos) {
+                        calcData.sgfh_qty = ZhCalc.add(calcData.sgfh_qty, np.sgfh_qty) || 0;
+                        calcData.qtcl_qty = ZhCalc.add(calcData.qtcl_qty, np.qtcl_qty) || 0;
+                        calcData.sjcl_qty = ZhCalc.add(calcData.sjcl_qty, np.sjcl_qty) || 0;
+                        calcData.quantity = ZhCalc.add(calcData.quantity, np.quantity) || 0;
+                    }
+                    if (!_.isMatch(checkData, calcData)) error.push(node);
+                }
+                return error;
+            },
+            checkZero: function (ledgerTree) {
+                const error = [];
+                for (const node of ledgerTree.nodes) {
+                    if ((!node.b_code || node.b_code === '')) continue;
+                    if (node.children && node.children.length > 0) continue;
+
+                    if ((checkZero(node.sgfh_qty) && checkZero(node.qtcl_qty) && checkZero(node.sjcl_qty)
+                        && checkZero(node.deal_qty) && checkZero(node.quantity))
+                        || checkZero(node.unit_price)) error.push(node);
+                }
+                return error;
+            },
+        };
+
+        const assignWarningData = function (nodes, checkType, warningData) {
+            for (const node of nodes) {
+                warningData.push({
+                    type: checkType,
+                    ledger_id: node.ledger_id,
+                    code: node.code,
+                    b_code: node.b_code,
+                    name: node.name,
+                })
+            }
+        }
+
+
+        const checkData = function () {
+            $('#ledger-check-begin').hide();
+            $('#ledger-check-waiting').show();
+            const checkData = {
+                check_time: new Date(),
+                warning_data: [],
+            }
+            const sibling = doSomeCheck('#check-sibling', ledgerCheckUtil.checkSibling) || [];
+            assignWarningData(sibling, ledgerCheckType.sibling.value, checkData.warning_data);
+            const empty_code = doSomeCheck('#check-empty-code', ledgerCheckUtil.checkCodeEmpty) || [];
+            assignWarningData(empty_code, ledgerCheckType.empty_code.value, checkData.warning_data);
+            const calc = doSomeCheck('#check-calc', ledgerCheckUtil.checkCalc) || [];
+            assignWarningData(calc, ledgerCheckType.calc.value, checkData.warning_data);
+            const zero = doSomeCheck('#check-zero', ledgerCheckUtil.checkZero) || [];
+            assignWarningData(zero, ledgerCheckType.zero.value, checkData.warning_data);
+
+            $('#ledger-check-waiting').hide();
+            setting.checkList.clearCheckData();
+            if (checkData.warning_data.length > 0) {
+                $('#ledger-check-hint').html('检查完成,现在您可以查看结果。').show();
+                $('#ledger-check-show').show();
+                setting.checkList.loadCheckData(checkData);
+            } else {
+                setting.checkList.hide();
+                $('#ledger-check-show').hide();
+                $('#ledger-check-hint').html('检查完成,没有任何问题。').show();
+            }
+        }
+
+        $('#ledger-check-begin').bind('click', checkData);
+        $('#ledger-check-modal').bind('show.bs.modal', initWaitingModal);
+        $('#ledger-check-show').bind('click', function () {
+            $('#ledger-check-modal').modal('hide');
+            setting.checkList.show();
+        });
+    }
+</script>

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


Някои файлове не бяха показани, защото твърде много файлове са промени