Pārlūkot izejas kodu

Merge remote-tracking branch 'remotes/origin/uat'

MaiXinRong 4 gadi atpakaļ
vecāks
revīzija
4947d10771
100 mainītis faili ar 9017 papildinājumiem un 2858 dzēšanām
  1. 14 1
      app/base/base_bills_service.js
  2. 1 0
      app/const/account_permission.js
  3. 444 444
      app/const/audit.js
  4. 9 4
      app/const/material.js
  5. 51 0
      app/const/shenpi.js
  6. 6 6
      app/const/sms_alitemplate.js
  7. 3 3
      app/const/tender.js
  8. 9 1
      app/const/tender_info.js
  9. 12 7
      app/controller/advance_controller.js
  10. 97 7
      app/controller/change_controller.js
  11. 21 4
      app/controller/deal_bills_controller.js
  12. 10 8
      app/controller/ledger_audit_controller.js
  13. 32 4
      app/controller/ledger_controller.js
  14. 81 19
      app/controller/material_controller.js
  15. 3 3
      app/controller/measure_controller.js
  16. 164 23
      app/controller/report_controller.js
  17. 182 24
      app/controller/revise_controller.js
  18. 104 46
      app/controller/stage_controller.js
  19. 138 0
      app/controller/tender_controller.js
  20. 15 0
      app/extend/context.js
  21. 27 0
      app/extend/helper.js
  22. 6 1
      app/lib/analysis_excel.js
  23. 2 2
      app/lib/wechat.js
  24. 26 0
      app/middleware/advance_check.js
  25. 75 0
      app/middleware/change_audit_check.js
  26. 67 0
      app/middleware/ledger_audit_check.js
  27. 25 0
      app/middleware/material_check.js
  28. 70 0
      app/middleware/revise_audit_check.js
  29. 25 0
      app/middleware/stage_check.js
  30. 71 11
      app/public/css/main.css
  31. 1208 0
      app/public/css/main_s.css
  32. 51 21
      app/public/js/advance_audit.js
  33. 254 197
      app/public/js/change_detail.js
  34. 110 56
      app/public/js/change_set.js
  35. 1 1
      app/public/js/global.js
  36. 46 48
      app/public/js/ledger.js
  37. 1 1
      app/public/js/ledger_audit.js
  38. 138 121
      app/public/js/login.js
  39. 32 2
      app/public/js/material.js
  40. 62 43
      app/public/js/material_audit.js
  41. 85 14
      app/public/js/material_exponent.js
  42. 79 24
      app/public/js/material_file.js
  43. 9 9
      app/public/js/material_list.js
  44. 26 17
      app/public/js/path_tree.js
  45. 2 18
      app/public/js/revise.js
  46. 462 0
      app/public/js/revise_compare.js
  47. 230 0
      app/public/js/revise_gcl_compare.js
  48. 15 5
      app/public/js/shares/cs_tools.js
  49. 450 0
      app/public/js/shares/gcl_gather_compare.js
  50. 7 1
      app/public/js/shares/sjs_setting.js
  51. 559 0
      app/public/js/shenpi.js
  52. 64 23
      app/public/js/spreadjs_rela/spreadjs_zh.js
  53. 255 73
      app/public/js/stage.js
  54. 52 54
      app/public/js/stage_audit.js
  55. 1 1
      app/public/js/stage_change.js
  56. 21 12
      app/public/js/stage_im.js
  57. 2 2
      app/public/js/stage_pay.js
  58. 1 1
      app/public/js/tender_list_info.js
  59. 1 1
      app/public/report/js/rpt_custom.js
  60. 28 0
      app/public/report/js/rpt_main.js
  61. 12 3
      app/public/report/js/rpt_preview_common.js
  62. 6 8
      app/public/report/js/rpt_print.js
  63. 5 1
      app/reports/rpt_component/helper/jpc_helper_discrete.js
  64. 2 2
      app/reports/rpt_component/helper/jpc_helper_flow_tab.js
  65. 82 5
      app/reports/rpt_component/jpc_flow_tab.js
  66. 1 0
      app/reports/rpt_component/jpc_value_define.js
  67. 6 1
      app/reports/util/rpt_calculation_data_util.js
  68. 160 127
      app/reports/util/rpt_excel_util.js
  69. 1 1
      app/reports/util/rpt_pdf_util.js
  70. 1 1
      app/reports/util/rpt_tmp_file_sweep.js
  71. 23 10
      app/router.js
  72. 80 15
      app/service/advance_audit.js
  73. 47 5
      app/service/change.js
  74. 136 76
      app/service/change_att.js
  75. 100 0
      app/service/change_audit.js
  76. 1 1
      app/service/ledger.js
  77. 79 14
      app/service/ledger_audit.js
  78. 16 0
      app/service/ledger_revise.js
  79. 127 137
      app/service/login_logging.js
  80. 85 19
      app/service/material_audit.js
  81. 2 1
      app/service/material_exponent.js
  82. 127 75
      app/service/material_file.js
  83. 4 4
      app/service/pos.js
  84. 88 19
      app/service/revise_audit.js
  85. 1 1
      app/service/revise_bills.js
  86. 13 3
      app/service/revise_pos.js
  87. 164 0
      app/service/shenpi_audit.js
  88. 7 0
      app/service/stage.js
  89. 148 95
      app/service/stage_att.js
  90. 82 15
      app/service/stage_audit.js
  91. 45 14
      app/service/stage_change.js
  92. 4 1
      app/service/stage_pay.js
  93. 117 1
      app/service/stage_pos.js
  94. 10 0
      app/service/tender.js
  95. 54 10
      app/service/tender_info.js
  96. 389 382
      app/view/advance/detail.ejs
  97. 100 100
      app/view/advance/index.ejs
  98. 28 8
      app/view/change/info.ejs
  99. 622 340
      app/view/change/info_modal.ejs
  100. 0 0
      app/view/dashboard/index.ejs

+ 14 - 1
app/base/base_bills_service.js

@@ -21,6 +21,19 @@ const math = require('mathjs');
 
 class BaseBillsSerivce extends TreeService {
 
+    constructor (ctx, setting, relaPosName) {
+        super(ctx, setting);
+        this.relaPosService = relaPosName;
+    }
+
+    set relaPosService(posName) {
+        this._posName = posName;
+    }
+
+    get relaPosService() {
+        return this.ctx.service[this._posName];
+    }
+
     // 继承方法
     clearParentingData(data) {
         data.unit_price = null;
@@ -649,7 +662,7 @@ class BaseBillsSerivce extends TreeService {
             }
             this._cacheMaxLid(tid, maxId);
             if (pastePosData.length > 0) {
-                await this.transaction.insert(this.ctx.service.pos.tableName, pastePosData);
+                await this.transaction.insert(this.relaPosService.tableName, pastePosData);
             }
             await this.transaction.commit();
         } catch (err) {

+ 1 - 0
app/const/account_permission.js

@@ -28,6 +28,7 @@ const permission = {
         children: [
             { title: '创建标段', value: 1 },
             { title: '查阅所有标段', value: 2 },
+            { title: '维护签约清单', value: 3, hint: '开启该选项,台账审批通过后,可上传签约清单', hintIcon: 'fa-question-circle' },
         ],
     },
     // cooperation: {

+ 444 - 444
app/const/audit.js

@@ -1,444 +1,444 @@
-'use strict';
-
-/**
- *
- *
- * @author Mai
- * @date
- * @version
- */
-// 台账审批流程
-const ledger = (function() {
-    const status = {
-        uncheck: 1, // 待上报
-        checking: 2, // 待审批|审批中
-        checked: 3, // 审批通过
-        checkNo: 4, // 审批退回
-    };
-
-    const statusString = [];
-    statusString[status.uncheck] = '';
-    statusString[status.checking] = '审批中';
-    statusString[status.checked] = '审批完成';
-    statusString[status.checkNo] = '审批退回';
-
-    const statusClass = [];
-    statusClass[status.uncheck] = '';
-    statusClass[status.checking] = '';
-    statusClass[status.checked] = 'text-success';
-    statusClass[status.checkNo] = 'text-warning';
-
-    // 标段概况页
-    // 描述文本
-    const auditString = [];
-    auditString[status.uncheck] = '';
-    auditString[status.checking] = '审批中';
-    auditString[status.checked] = '审批通过';
-    auditString[status.checkNo] = '审批退回';
-    // 文字样式
-    const auditStringClass = [];
-    auditStringClass[status.uncheck] = '';
-    auditStringClass[status.checking] = 'text-warning';
-    auditStringClass[status.checked] = 'text-success';
-    auditStringClass[status.checkNo] = 'text-warning';
-
-    // 金额概况
-
-    const tiStatusString = [];
-    tiStatusString[status.uncheck] = '未上报';
-    tiStatusString[status.checking] = '审批中';
-    tiStatusString[status.checked] = '审批通过';
-    tiStatusString[status.checkNo] = '审批退回';
-    const tiStatusStringClass = [];
-    tiStatusStringClass[status.uncheck] = '';
-    tiStatusStringClass[status.checking] = 'text-warning';
-    tiStatusStringClass[status.checked] = 'text-success';
-    tiStatusStringClass[status.checkNo] = 'text-warning';
-    return { status, statusString, statusClass, auditString, auditStringClass, tiStatusString, tiStatusStringClass };
-})();
-
-// 台账修订 审批流程
-const revise = (function() {
-    const status = {
-        uncheck: 1, // 待上报
-        checking: 2, // 待审批|审批中
-        checked: 3, // 审批通过
-        checkNo: 4, // 审批退回
-    };
-    const statusString = [];
-    statusString[status.uncheck] = '草稿';
-    statusString[status.checking] = '审批中';
-    statusString[status.checked] = '审批通过';
-    statusString[status.checkNo] = '审批退回';
-
-    const statusClass = [];
-    statusClass[status.uncheck] = '';
-    statusClass[status.checking] = '';
-    statusClass[status.checked] = 'text-success';
-    statusClass[status.checkNo] = 'text-warning';
-
-    // 标段概况页
-    // 描述文本
-    const auditString = [];
-    auditString[status.uncheck] = '';
-    auditString[status.checking] = '审批中';
-    auditString[status.checked] = '审批通过';
-    auditString[status.checkNo] = '审批退回';
-    // 文字样式
-    const auditStringClass = [];
-    auditStringClass[status.uncheck] = '';
-    auditStringClass[status.checking] = 'text-warning';
-    auditStringClass[status.checked] = 'text-success';
-    auditStringClass[status.checkNo] = 'text-warning';
-    // 描述文本
-    const auditProgress = [];
-    auditProgress[status.uncheck] = '草稿';
-    auditProgress[status.checking] = '审批中';
-    auditProgress[status.checked] = '审批通过';
-    auditProgress[status.checkNo] = '审批退回';
-    // 样式
-    const auditProgressClass = [];
-    auditProgressClass[status.uncheck] = '';
-    auditProgressClass[status.checking] = 'text-warning';
-    auditProgressClass[status.checked] = 'text-success';
-    auditProgressClass[status.checkNo] = 'text-warning';
-    return { status, statusString, statusClass, auditString, auditStringClass, auditProgress, auditProgressClass };
-})();
-
-// 期审批流程
-const stage = (function() {
-    // 流程状态
-    const status = {
-        uncheck: 1, // 待上报
-        checking: 2, // 待审批|审批中
-        checked: 3, // 审批通过
-        checkNo: 4, // 审批退回原报
-        checkNoPre: 5, // 审批退回上一人
-        checkAgain: 6, // 重新审批 // 该状态仅可用于,终审退回时,修改原终审的审批状态,并同时新增一条新的终审审批中记录
-    };
-
-    // 流程状态提示
-    const statusString = [];
-    statusString[status.uncheck] = '待上报';
-    statusString[status.checking] = '审批中';
-    statusString[status.checked] = '审批通过';
-    statusString[status.checkNo] = '审批退回';
-    statusString[status.checkNoPre] = '审批退回';
-    statusString[status.checkAgain] = '重新审批';
-
-    // 流程状态样式
-    const statusClass = [];
-    statusClass[status.uncheck] = '';
-    statusClass[status.checking] = '';
-    statusClass[status.checked] = 'text-success';
-    statusClass[status.checkNo] = 'text-warning';
-    statusClass[status.checkNoPre] = 'text-warning';
-    statusClass[status.checkAgain] = 'text-warning';
-
-    /**
-     * 期列表,审批状态一列
-     */
-    // 按钮
-    const statusButton = [];
-    statusButton[status.uncheck] = '待上报';
-    statusButton[status.checking] = '审批';
-    statusButton[status.checked] = '';
-    statusButton[status.checkNo] = '重新上报';
-    statusButton[status.checkNoPre] = '重新审批';
-    statusButton[status.checkAgain] = '重新审批';
-    // 按钮样式
-    const statusButtonClass = [];
-    statusButtonClass[status.uncheck] = 'btn-primary';
-    statusButtonClass[status.checking] = 'btn-success';
-    statusButtonClass[status.checked] = '';
-    statusButtonClass[status.checkNo] = 'btn-warning';
-    statusButtonClass[status.checkNoPre] = 'btn-warning';
-    statusButtonClass[status.checkAgain] = 'btn-warning';
-    // 描述文本
-    const auditString = [];
-    auditString[status.uncheck] = '';
-    auditString[status.checking] = '审批中';
-    auditString[status.checked] = '审批通过';
-    auditString[status.checkNo] = '审批退回';
-    auditString[status.checkNoPre] = '审批退回';
-    auditString[status.checkAgain] = '重新审批';
-    // 文字样式
-    const auditStringClass = [];
-    auditStringClass[status.uncheck] = '';
-    auditStringClass[status.checking] = 'text-warning';
-    auditStringClass[status.checked] = 'text-success';
-    auditStringClass[status.checkNo] = 'text-warning';
-    auditStringClass[status.checkNoPre] = 'text-warning';
-    auditStringClass[status.checkAgain] = 'text-warning';
-    /* ------------------------------------------------------- */
-
-    /**
-     * 期列表,审批进度一列
-     */
-    // 描述文本
-    const auditProgress = [];
-    auditProgress[status.uncheck] = '待上报';
-    auditProgress[status.checking] = '审批中';
-    auditProgress[status.checked] = '审批通过';
-    auditProgress[status.checkNo] = '审批退回';
-    auditProgress[status.checkNoPre] = '审批退回';
-    auditProgress[status.checkAgain] = '重新审批';
-    // 样式
-    const auditProgressClass = [];
-    auditProgressClass[status.uncheck] = '';
-    auditProgressClass[status.checking] = 'text-warning';
-    auditProgressClass[status.checked] = 'text-success';
-    auditProgressClass[status.checkNo] = 'text-warning';
-    auditProgressClass[status.checkNoPre] = 'text-warning';
-    auditProgressClass[status.checkAgain] = 'text-warning';
-    /* ------------------------------------------------------- */
-
-    const tiStatusString = [];
-    tiStatusString[status.uncheck] = '待上报';
-    tiStatusString[status.checking] = '审批中';
-    tiStatusString[status.checked] = '审批通过';
-    tiStatusString[status.checkNo] = '审批退回';
-    tiStatusString[status.checkNoPre] = '审批中';
-    tiStatusString[status.checkAgain] = '审批中';
-    const tiStatusStringClass = [];
-    tiStatusStringClass[status.uncheck] = '';
-    tiStatusStringClass[status.checking] = 'text-warning';
-    tiStatusStringClass[status.checked] = 'text-success';
-    tiStatusStringClass[status.checkNo] = 'text-warning';
-    tiStatusStringClass[status.checkNoPre] = 'text-warning';
-    tiStatusStringClass[status.checkAgain] = 'text-warning';
-    const backType = {
-        org: 1,
-        pre: 2,
-    };
-    return {
-        status, statusString, statusClass,
-        statusButton, statusButtonClass,
-        auditString, auditStringClass,
-        auditProgress, auditProgressClass,
-        backType,
-        timesLen: 100,
-        tiStatusString, tiStatusStringClass
-    };
-})();
-
-// 变更令状态
-const status = {
-    uncheck: 1, // 待上报
-    checking: 2, // 审批中
-    checked: 3, // 审批完成
-    // checkNo: 4,     // 审批终止
-    back: 5, // 重新上报
-    backnew: 6, // 退回
-};
-const statusButton = [];
-statusButton[status.uncheck] = '上报';
-statusButton[status.checking] = '审批';
-statusButton[status.checked] = '';
-// statusButton[status.checkNo] = '';
-statusButton[status.back] = '重新上报';
-statusButton[status.backnew] = '审批';
-
-const statusButtonClass = [];
-statusButtonClass[status.uncheck] = 'btn-primary';
-statusButtonClass[status.checking] = 'btn-success';
-statusButtonClass[status.checked] = '';
-// statusButtonClass[status.checkNo] = '';
-statusButtonClass[status.back] = 'btn-warning';
-statusButtonClass[status.backnew] = 'btn-success';
-
-const statusString = [];
-statusString[status.uncheck] = '';
-statusString[status.checking] = '审批中';
-statusString[status.checked] = '审批通过';
-// statusString[status.checkNo] = '终止';
-statusString[status.back] = '审批退回';
-statusString[status.backnew] = '审批退回';
-
-const statusClass = [];
-statusClass[status.uncheck] = '';
-statusClass[status.checking] = 'text-warning';
-statusClass[status.checked] = 'text-success';
-// statusClass[status.checkNo] = 'text-danger';
-statusClass[status.back] = 'text-warning';
-statusClass[status.backnew] = 'text-warning';
-
-/* ------------------------------------------------------- */
-
-// 变更令审批人状态
-const auditStatus = {
-    uncheck: 1, // 待审批
-    checking: 2, // 审批中或者原报人待上报
-    checked: 3, // 审批通过或者原报人上报完成
-    // checkNo: 4,     // 审批终止
-    back: 5, // 退回到原报人重新上报
-    backnew: 6, // 退回到上一个审批人
-    checkAgain: 7, // 重新审批
-};
-
-const auditStatusString = [];
-auditStatusString[auditStatus.uncheck] = '待上报';
-auditStatusString[auditStatus.checking] = '审批中';
-auditStatusString[auditStatus.checked] = '审批通过';
-// auditStatusString[auditStatus.checkNo] = '审批终止';
-auditStatusString[auditStatus.back] = '退回';
-auditStatusString[auditStatus.backnew] = '审批退回';
-auditStatusString[auditStatus.checkAgain] = '重新审批';
-
-const auditStatusClass = [];
-auditStatusClass[auditStatus.uncheck] = '';
-auditStatusClass[auditStatus.checking] = 'text-warning';
-auditStatusClass[auditStatus.checked] = 'text-success';
-// auditStatusClass[auditStatus.checkNo] = 'text-danger';
-auditStatusClass[auditStatus.back] = 'text-warning';
-auditStatusClass[auditStatus.backnew] = 'text-warning';
-auditStatusClass[auditStatus.checkAgain] = 'text-warning';
-
-/* ------------------------------------------------------- */
-
-const filter = {
-    status: {
-        pending: 1,
-        uncheck: 5,
-        checking: 2,
-        checked: 3,
-        // checkNo: 4,
-    },
-    statusString: [],
-};
-filter.statusString[filter.status.pending] = '待处理';
-filter.statusString[filter.status.uncheck] = '待上报';
-filter.statusString[filter.status.checking] = '进行中';
-filter.statusString[filter.status.checked] = '已完成';
-// filter.statusString[filter.status.checkNo] = '终止';
-
-// 材料调差审批流程
-const material = (function() {
-    const status = {
-        uncheck: 1, // 待上报
-        checking: 2, // 待审批|审批中
-        checked: 3, // 审批通过
-        checkNo: 4, // 审批退回原报
-        checkNoPre: 5, // 审批退回上一人
-        checkAgain: 6, // 终审退回  --该状态仅可用于,终审退回时,修改原终审的审批状态,并同时新增一条新的终审审批中记录
-    };
-    // 流程状态提示
-    const statusString = [];
-    statusString[status.uncheck] = '待上报';
-    statusString[status.checking] = '审批中';
-    statusString[status.checked] = '审批通过';
-    statusString[status.checkNo] = '审批退回';
-    statusString[status.checkNoPre] = '审批退回';
-    statusString[status.checkAgain] = '重新审批';
-
-    // 流程状态样式
-    const statusClass = [];
-    statusClass[status.uncheck] = '';
-    statusClass[status.checking] = '';
-    statusClass[status.checked] = 'text-success';
-    statusClass[status.checkNo] = 'text-warning';
-    statusClass[status.checkNoPre] = 'text-warning';
-    statusClass[status.checkAgain] = 'text-warning';
-
-    // 按钮
-    const statusButton = [];
-    statusButton[status.uncheck] = '待上报';
-    statusButton[status.checking] = '审批';
-    statusButton[status.checked] = '';
-    statusButton[status.checkNo] = '重新上报';
-
-    // 按钮样式
-    const statusButtonClass = [];
-    statusButtonClass[status.uncheck] = 'btn-primary';
-    statusButtonClass[status.checking] = 'btn-success';
-    statusButtonClass[status.checked] = '';
-    statusButtonClass[status.checkNo] = 'btn-warning';
-
-    // 描述文本
-    const auditProgress = [];
-    auditProgress[status.uncheck] = '待上报';
-    auditProgress[status.checking] = '审批中';
-    auditProgress[status.checked] = '审批通过';
-    auditProgress[status.checkNo] = '审批退回';
-    // 样式
-    const auditProgressClass = [];
-    auditProgressClass[status.uncheck] = '';
-    auditProgressClass[status.checking] = 'text-warning';
-    auditProgressClass[status.checked] = 'text-success';
-    auditProgressClass[status.checkNo] = 'text-warning';
-    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,
-    stage: 2,
-    change: 3,
-    revise: 4,
-    ledger: 5,
-    advance: 6,
-};
-
-module.exports = {
-    ledger,
-    stage,
-    revise,
-    material,
-    flow: {
-        status,
-        statusString,
-        statusButton,
-        statusButtonClass,
-        statusClass,
-        auditStatus,
-        auditStatusString,
-        auditStatusClass,
-    },
-    filter,
-    pushType,
-    advance,
-};
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+// 台账审批流程
+const ledger = (function() {
+    const status = {
+        uncheck: 1, // 待上报
+        checking: 2, // 待审批|审批中
+        checked: 3, // 审批通过
+        checkNo: 4, // 审批退回
+    };
+
+    const statusString = [];
+    statusString[status.uncheck] = '';
+    statusString[status.checking] = '审批中';
+    statusString[status.checked] = '审批完成';
+    statusString[status.checkNo] = '审批退回';
+
+    const statusClass = [];
+    statusClass[status.uncheck] = '';
+    statusClass[status.checking] = '';
+    statusClass[status.checked] = 'text-success';
+    statusClass[status.checkNo] = 'text-warning';
+
+    // 标段概况页
+    // 描述文本
+    const auditString = [];
+    auditString[status.uncheck] = '';
+    auditString[status.checking] = '审批中';
+    auditString[status.checked] = '审批通过';
+    auditString[status.checkNo] = '审批退回';
+    // 文字样式
+    const auditStringClass = [];
+    auditStringClass[status.uncheck] = '';
+    auditStringClass[status.checking] = 'text-warning';
+    auditStringClass[status.checked] = 'text-success';
+    auditStringClass[status.checkNo] = 'text-warning';
+
+    // 金额概况
+
+    const tiStatusString = [];
+    tiStatusString[status.uncheck] = '未上报';
+    tiStatusString[status.checking] = '审批中';
+    tiStatusString[status.checked] = '审批通过';
+    tiStatusString[status.checkNo] = '审批退回';
+    const tiStatusStringClass = [];
+    tiStatusStringClass[status.uncheck] = '';
+    tiStatusStringClass[status.checking] = 'text-warning';
+    tiStatusStringClass[status.checked] = 'text-success';
+    tiStatusStringClass[status.checkNo] = 'text-warning';
+    return { status, statusString, statusClass, auditString, auditStringClass, tiStatusString, tiStatusStringClass };
+})();
+
+// 台账修订 审批流程
+const revise = (function() {
+    const status = {
+        uncheck: 1, // 待上报
+        checking: 2, // 待审批|审批中
+        checked: 3, // 审批通过
+        checkNo: 4, // 审批退回
+    };
+    const statusString = [];
+    statusString[status.uncheck] = '草稿';
+    statusString[status.checking] = '审批中';
+    statusString[status.checked] = '审批通过';
+    statusString[status.checkNo] = '审批退回';
+
+    const statusClass = [];
+    statusClass[status.uncheck] = '';
+    statusClass[status.checking] = '';
+    statusClass[status.checked] = 'text-success';
+    statusClass[status.checkNo] = 'text-warning';
+
+    // 标段概况页
+    // 描述文本
+    const auditString = [];
+    auditString[status.uncheck] = '';
+    auditString[status.checking] = '审批中';
+    auditString[status.checked] = '审批通过';
+    auditString[status.checkNo] = '审批退回';
+    // 文字样式
+    const auditStringClass = [];
+    auditStringClass[status.uncheck] = '';
+    auditStringClass[status.checking] = 'text-warning';
+    auditStringClass[status.checked] = 'text-success';
+    auditStringClass[status.checkNo] = 'text-warning';
+    // 描述文本
+    const auditProgress = [];
+    auditProgress[status.uncheck] = '草稿';
+    auditProgress[status.checking] = '审批中';
+    auditProgress[status.checked] = '审批通过';
+    auditProgress[status.checkNo] = '审批退回';
+    // 样式
+    const auditProgressClass = [];
+    auditProgressClass[status.uncheck] = '';
+    auditProgressClass[status.checking] = 'text-warning';
+    auditProgressClass[status.checked] = 'text-success';
+    auditProgressClass[status.checkNo] = 'text-warning';
+    return { status, statusString, statusClass, auditString, auditStringClass, auditProgress, auditProgressClass };
+})();
+
+// 期审批流程
+const stage = (function() {
+    // 流程状态
+    const status = {
+        uncheck: 1, // 待上报
+        checking: 2, // 待审批|审批中
+        checked: 3, // 审批通过
+        checkNo: 4, // 审批退回原报
+        checkNoPre: 5, // 审批退回上一人
+        checkAgain: 6, // 重新审批 // 该状态仅可用于,终审退回时,修改原终审的审批状态,并同时新增一条新的终审审批中记录
+    };
+
+    // 流程状态提示
+    const statusString = [];
+    statusString[status.uncheck] = '待上报';
+    statusString[status.checking] = '审批中';
+    statusString[status.checked] = '审批通过';
+    statusString[status.checkNo] = '审批退回';
+    statusString[status.checkNoPre] = '审批退回';
+    statusString[status.checkAgain] = '重新审批';
+
+    // 流程状态样式
+    const statusClass = [];
+    statusClass[status.uncheck] = '';
+    statusClass[status.checking] = '';
+    statusClass[status.checked] = 'text-success';
+    statusClass[status.checkNo] = 'text-warning';
+    statusClass[status.checkNoPre] = 'text-warning';
+    statusClass[status.checkAgain] = 'text-warning';
+
+    /**
+     * 期列表,审批状态一列
+     */
+    // 按钮
+    const statusButton = [];
+    statusButton[status.uncheck] = '待上报';
+    statusButton[status.checking] = '审批';
+    statusButton[status.checked] = '';
+    statusButton[status.checkNo] = '重新上报';
+    statusButton[status.checkNoPre] = '重新审批';
+    statusButton[status.checkAgain] = '重新审批';
+    // 按钮样式
+    const statusButtonClass = [];
+    statusButtonClass[status.uncheck] = 'btn-primary';
+    statusButtonClass[status.checking] = 'btn-success';
+    statusButtonClass[status.checked] = '';
+    statusButtonClass[status.checkNo] = 'btn-warning';
+    statusButtonClass[status.checkNoPre] = 'btn-warning';
+    statusButtonClass[status.checkAgain] = 'btn-warning';
+    // 描述文本
+    const auditString = [];
+    auditString[status.uncheck] = '';
+    auditString[status.checking] = '审批中';
+    auditString[status.checked] = '审批通过';
+    auditString[status.checkNo] = '审批退回';
+    auditString[status.checkNoPre] = '审批退回';
+    auditString[status.checkAgain] = '重新审批';
+    // 文字样式
+    const auditStringClass = [];
+    auditStringClass[status.uncheck] = '';
+    auditStringClass[status.checking] = 'text-warning';
+    auditStringClass[status.checked] = 'text-success';
+    auditStringClass[status.checkNo] = 'text-warning';
+    auditStringClass[status.checkNoPre] = 'text-warning';
+    auditStringClass[status.checkAgain] = 'text-warning';
+    /* ------------------------------------------------------- */
+
+    /**
+     * 期列表,审批进度一列
+     */
+    // 描述文本
+    const auditProgress = [];
+    auditProgress[status.uncheck] = '待上报';
+    auditProgress[status.checking] = '审批中';
+    auditProgress[status.checked] = '审批通过';
+    auditProgress[status.checkNo] = '审批退回';
+    auditProgress[status.checkNoPre] = '审批退回';
+    auditProgress[status.checkAgain] = '重新审批';
+    // 样式
+    const auditProgressClass = [];
+    auditProgressClass[status.uncheck] = '';
+    auditProgressClass[status.checking] = 'text-warning';
+    auditProgressClass[status.checked] = 'text-success';
+    auditProgressClass[status.checkNo] = 'text-warning';
+    auditProgressClass[status.checkNoPre] = 'text-warning';
+    auditProgressClass[status.checkAgain] = 'text-warning';
+    /* ------------------------------------------------------- */
+
+    const tiStatusString = [];
+    tiStatusString[status.uncheck] = '待上报';
+    tiStatusString[status.checking] = '审批中';
+    tiStatusString[status.checked] = '审批通过';
+    tiStatusString[status.checkNo] = '审批退回';
+    tiStatusString[status.checkNoPre] = '审批中';
+    tiStatusString[status.checkAgain] = '审批中';
+    const tiStatusStringClass = [];
+    tiStatusStringClass[status.uncheck] = '';
+    tiStatusStringClass[status.checking] = 'text-warning';
+    tiStatusStringClass[status.checked] = 'text-success';
+    tiStatusStringClass[status.checkNo] = 'text-warning';
+    tiStatusStringClass[status.checkNoPre] = 'text-warning';
+    tiStatusStringClass[status.checkAgain] = 'text-warning';
+    const backType = {
+        org: 1,
+        pre: 2,
+    };
+    return {
+        status, statusString, statusClass,
+        statusButton, statusButtonClass,
+        auditString, auditStringClass,
+        auditProgress, auditProgressClass,
+        backType,
+        timesLen: 100,
+        tiStatusString, tiStatusStringClass,
+    };
+})();
+
+// 变更令状态
+const status = {
+    uncheck: 1, // 待上报
+    checking: 2, // 审批中
+    checked: 3, // 审批完成
+    // checkNo: 4,     // 审批终止
+    back: 5, // 重新上报
+    backnew: 6, // 退回
+};
+const statusButton = [];
+statusButton[status.uncheck] = '上报';
+statusButton[status.checking] = '审批';
+statusButton[status.checked] = '';
+// statusButton[status.checkNo] = '';
+statusButton[status.back] = '重新上报';
+statusButton[status.backnew] = '审批';
+
+const statusButtonClass = [];
+statusButtonClass[status.uncheck] = 'btn-primary';
+statusButtonClass[status.checking] = 'btn-success';
+statusButtonClass[status.checked] = '';
+// statusButtonClass[status.checkNo] = '';
+statusButtonClass[status.back] = 'btn-warning';
+statusButtonClass[status.backnew] = 'btn-success';
+
+const statusString = [];
+statusString[status.uncheck] = '未上报';
+statusString[status.checking] = '审批中';
+statusString[status.checked] = '审批通过';
+// statusString[status.checkNo] = '终止';
+statusString[status.back] = '审批退回';
+statusString[status.backnew] = '审批退回';
+
+const statusClass = [];
+statusClass[status.uncheck] = '';
+statusClass[status.checking] = 'text-warning';
+statusClass[status.checked] = 'text-success';
+// statusClass[status.checkNo] = 'text-danger';
+statusClass[status.back] = 'text-warning';
+statusClass[status.backnew] = 'text-warning';
+
+/* ------------------------------------------------------- */
+
+// 变更令审批人状态
+const auditStatus = {
+    uncheck: 1, // 待审批
+    checking: 2, // 审批中或者原报人待上报
+    checked: 3, // 审批通过或者原报人上报完成
+    // checkNo: 4,     // 审批终止
+    back: 5, // 退回到原报人重新上报
+    backnew: 6, // 退回到上一个审批人
+    checkAgain: 7, // 重新审批
+};
+
+const auditStatusString = [];
+auditStatusString[auditStatus.uncheck] = '待上报';
+auditStatusString[auditStatus.checking] = '审批中';
+auditStatusString[auditStatus.checked] = '审批通过';
+// auditStatusString[auditStatus.checkNo] = '审批终止';
+auditStatusString[auditStatus.back] = '退回';
+auditStatusString[auditStatus.backnew] = '审批退回';
+auditStatusString[auditStatus.checkAgain] = '重新审批';
+
+const auditStatusClass = [];
+auditStatusClass[auditStatus.uncheck] = '';
+auditStatusClass[auditStatus.checking] = 'text-warning';
+auditStatusClass[auditStatus.checked] = 'text-success';
+// auditStatusClass[auditStatus.checkNo] = 'text-danger';
+auditStatusClass[auditStatus.back] = 'text-warning';
+auditStatusClass[auditStatus.backnew] = 'text-warning';
+auditStatusClass[auditStatus.checkAgain] = 'text-warning';
+
+/* ------------------------------------------------------- */
+
+const filter = {
+    status: {
+        pending: 1,
+        uncheck: 5,
+        checking: 2,
+        checked: 3,
+        // checkNo: 4,
+    },
+    statusString: [],
+};
+filter.statusString[filter.status.pending] = '待处理';
+filter.statusString[filter.status.uncheck] = '待上报';
+filter.statusString[filter.status.checking] = '进行中';
+filter.statusString[filter.status.checked] = '已完成';
+// filter.statusString[filter.status.checkNo] = '终止';
+
+// 材料调差审批流程
+const material = (function() {
+    const status = {
+        uncheck: 1, // 待上报
+        checking: 2, // 待审批|审批中
+        checked: 3, // 审批通过
+        checkNo: 4, // 审批退回原报
+        checkNoPre: 5, // 审批退回上一人
+        checkAgain: 6, // 终审退回  --该状态仅可用于,终审退回时,修改原终审的审批状态,并同时新增一条新的终审审批中记录
+    };
+    // 流程状态提示
+    const statusString = [];
+    statusString[status.uncheck] = '待上报';
+    statusString[status.checking] = '审批中';
+    statusString[status.checked] = '审批通过';
+    statusString[status.checkNo] = '审批退回';
+    statusString[status.checkNoPre] = '审批退回';
+    statusString[status.checkAgain] = '重新审批';
+
+    // 流程状态样式
+    const statusClass = [];
+    statusClass[status.uncheck] = '';
+    statusClass[status.checking] = '';
+    statusClass[status.checked] = 'text-success';
+    statusClass[status.checkNo] = 'text-warning';
+    statusClass[status.checkNoPre] = 'text-warning';
+    statusClass[status.checkAgain] = 'text-warning';
+
+    // 按钮
+    const statusButton = [];
+    statusButton[status.uncheck] = '待上报';
+    statusButton[status.checking] = '审批';
+    statusButton[status.checked] = '';
+    statusButton[status.checkNo] = '重新上报';
+
+    // 按钮样式
+    const statusButtonClass = [];
+    statusButtonClass[status.uncheck] = 'btn-primary';
+    statusButtonClass[status.checking] = 'btn-success';
+    statusButtonClass[status.checked] = '';
+    statusButtonClass[status.checkNo] = 'btn-warning';
+
+    // 描述文本
+    const auditProgress = [];
+    auditProgress[status.uncheck] = '待上报';
+    auditProgress[status.checking] = '审批中';
+    auditProgress[status.checked] = '审批通过';
+    auditProgress[status.checkNo] = '审批退回';
+    // 样式
+    const auditProgressClass = [];
+    auditProgressClass[status.uncheck] = '';
+    auditProgressClass[status.checking] = 'text-warning';
+    auditProgressClass[status.checked] = 'text-success';
+    auditProgressClass[status.checkNo] = 'text-warning';
+    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,
+    stage: 2,
+    change: 3,
+    revise: 4,
+    ledger: 5,
+    advance: 6,
+};
+
+module.exports = {
+    ledger,
+    stage,
+    revise,
+    material,
+    flow: {
+        status,
+        statusString,
+        statusButton,
+        statusButtonClass,
+        statusClass,
+        auditStatus,
+        auditStatusString,
+        auditStatusClass,
+    },
+    filter,
+    pushType,
+    advance,
+};

+ 9 - 4
app/const/material.js

@@ -28,23 +28,27 @@ const ex_type = [
     { text: '定值', value: 1 },
     { text: '变值', value: 2 },
 ];
-
+const ex_basic_text = {
+    bqht: '所选期合同计量金额',
+    bqbg: '所选期变更计量金额',
+    bqwc: '所选期完成计量金额',
+}
 const ex_calc = [
     {
         code: 'bqht',
-        text: '本期合同计量金额',
+        text: '所选期合同计量金额',
         value: '',
         select: false,
     },
     {
         code: 'bqbg',
-        text: '期变更计量金额',
+        text: '所选期变更计量金额',
         value: '',
         select: false,
     },
     {
         code: 'bqwc',
-        text: '期完成计量金额',
+        text: '所选期完成计量金额',
         value: '',
         select: true,
     },
@@ -61,4 +65,5 @@ module.exports = {
     ex_type,
     is_summary,
     ex_calc,
+    ex_basic_text,
 };

+ 51 - 0
app/const/shenpi.js

@@ -0,0 +1,51 @@
+'use strict';
+
+/**
+ * 审批流程设置
+ *
+ * @author ELlisran
+ * @date 2020/10/20
+ * @version
+ */
+// 审批类型
+const sp_type = {
+    advance: 1,
+    ledger: 2,
+    revise: 3,
+    stage: 4,
+    change: 5,
+    material: 6,
+};
+// const sp_name = [];
+// sp_name[sp_type.advance] = '预付款审批';
+// sp_name[sp_type.ledger] = '台账审批';
+// sp_name[sp_type.revise] = '台账修订';
+// sp_name[sp_type.stage] = '计量期审批';
+// sp_name[sp_type.change] = '工程变更审批';
+// sp_name[sp_type.material] = '材料调差审批';
+
+const sp_lc = [
+    { code: 'advance', type: sp_type.advance, name: '预付款审批' },
+    { code: 'ledger', type: sp_type.ledger, name: '台账审批' },
+    { code: 'revise', type: sp_type.revise, name: '台账修订' },
+    { code: 'stage', type: sp_type.stage, name: '计量期审批' },
+    { code: 'change', type: sp_type.change, name: '工程变更审批' },
+    { code: 'material', type: sp_type.material, name: '材料调差审批' },
+];
+
+const sp_status = {
+    sqspr: 1, // 授权审批人
+    gdspl: 2, // 固定审批流
+    gdzs: 3, // 固定终审
+};
+const sp_status_list = [];
+sp_status_list[sp_status.sqspr] = { status: sp_status.sqspr, name: '授权审批人', msg: '由上报人设置审批流程' };
+sp_status_list[sp_status.gdspl] = { status: sp_status.gdspl, name: '固定审批流', msg: '审批流程固定,上报人只能按照设置好的审批流程进行' };
+sp_status_list[sp_status.gdzs] = { status: sp_status.gdzs, name: '固定终审', msg: '结束审批流为固定人,终审前的审批流程由上报人设置,即授权审批人' };
+
+module.exports = {
+    sp_type,
+    sp_lc,
+    sp_status,
+    sp_status_list,
+};

+ 6 - 6
app/const/sms_alitemplate.js

@@ -15,16 +15,16 @@ const endpoint = 'https://dysmsapi.aliyuncs.com';
 const smsTemplate = {
     yzm: 'SMS_192820305', // 验证码:${code},15分钟内有效。
     mmcz: 'SMS_192825318', // 账号:${account},密码重置为:${password}
-    ledger_check: 'SMS_192825164', // 项目:${project},标段:${number},台需要您审批,请登录系统处理。
-    ledger_result: 'SMS_192830156', // 项目:${project},标段:${number},台审批${status},请登录系统处理。
+    ledger_check: 'SMS_192825164', // 项目:${project},标段:${number},台需要您审批,请登录系统处理。
+    ledger_result: 'SMS_192830156', // 项目:${project},标段:${number},台审批${status},请登录系统处理。
     stage_check: 'SMS_193244645', // 项目:${project},标段:${number},第${qi}期,需要您审批。在线审批https://scn.ink/${code}
     stage_result: 'SMS_192835176', // 项目:${project},标段:${number},第${qi}期,审批${status}。
     change_check: 'SMS_193239611', // 项目:${project},标段:${number},变更:${biangeng},需要您审批。在线审批https://scn.ink/${code}
     change_result: 'SMS_192830143', // 项目:${project},标段:${number},变更:${biangeng},审批${status}。
-    revise_check: 'SMS_193145529', // 项目:${project},标段:${number},台修订需要您审批,请登录系统处理。
-    revise_result: 'SMS_193140537', // 项目:${project},标段:${number},台修订审批${status},请登录系统处理。
-    revise_result2: 'SMS_192985611', // 项目:${project},标段:${number},台修订审批${status}。
-    revise_report: 'SMS_192985614', // 项目:${project},标段:${number},台修订已上报。
+    revise_check: 'SMS_193145529', // 项目:${project},标段:${number},台修订需要您审批,请登录系统处理。
+    revise_result: 'SMS_193140537', // 项目:${project},标段:${number},台修订审批${status},请登录系统处理。
+    revise_result2: 'SMS_192985611', // 项目:${project},标段:${number},台修订审批${status}。
+    revise_report: 'SMS_192985614', // 项目:${project},标段:${number},台修订已上报。
 };
 const smsStatus = {
     back: '退回',

+ 3 - 3
app/const/tender.js

@@ -27,7 +27,7 @@ const infoTableCol = [
     { title: '名称', field: 'name', folderCell: true, },
     { title: '计量期数', field: 'stageCount', },
     { title: '审批状态', field: '', },
-    { title: '0号台合同', field: '', },
+    { title: '0号台合同', field: '', },
     { title: '本期完成', field: '', },
     { title: '截止本期合同', field: '', },
     { title: '截止本期变更', field: '', },
@@ -49,10 +49,10 @@ const manageTableCol = [
 
 const measureType = {
     tz: {
-        value: 1, name: '0号台账模式', title: ' 0号台帐', hint: ' 要求以台帐为计量根本,必须先有完整台帐才能持续计量。',
+        value: 1, name: '0号台账模式', title: ' 0号台账', hint: ' 要求以台账为计量根本,必须先有完整台账才能持续计量。',
     },
     gcl: {
-        value: 2, name: '工程量清单模式', title: ' 工程量清单', hint: ' 仅需要工程量清单,详细台帐在计量过程中逐步添加,最终组成完整台帐。',
+        value: 2, name: '工程量清单模式', title: ' 工程量清单', hint: ' 仅需要工程量清单,详细台账在计量过程中逐步添加,最终组成完整台账。',
     },
 };
 

+ 9 - 1
app/const/tender_info.js

@@ -8,7 +8,7 @@
  * @version
  */
 
-const parseInfo = ['deal_info', 'construction_unit', 'tech_param', 'decimal', 'precision', 'deal_param', 'display', 'pay_account'];
+const parseInfo = ['deal_info', 'construction_unit', 'tech_param', 'decimal', 'precision', 'deal_param', 'display', 'pay_account', 'shenpi'];
 const arrayInfo = ['chapter'];
 const defaultInfo = {
     // 合同信息
@@ -132,6 +132,14 @@ const defaultInfo = {
             phone: '',
         },
     },
+    shenpi: {
+        advance: 1,
+        ledger: 1,
+        revise: 1,
+        stage: 1,
+        change: 1,
+        material: 1,
+    },
 };
 
 module.exports = {

+ 12 - 7
app/controller/advance_controller.js

@@ -1,6 +1,7 @@
 'use strict';
 const accountGroup = require('../const/account_group').group;
 const auditConst = require('../const/audit').advance;
+const shenpiConst = require('../const/shenpi');
 const sendToWormhole = require('stream-wormhole');
 const path = require('path');
 const fs = require('fs');
@@ -73,6 +74,7 @@ module.exports = app => {
         async _getDefaultRenderData(ctx) {
             const data = {
                 auditConst,
+                shenpiConst,
                 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,
@@ -90,13 +92,16 @@ module.exports = app => {
                     const groupList = accountList.filter(item => item.account_group === idx);
                     return { groupName: item, groupList };
                 });
+                data.auditorList = await ctx.service.advanceAudit.getAuditors(ctx.advance.id, ctx.advance.times);
             }
-            // 获取审核人左边列表
-            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;
+            // 获取审核人左边列表
+            data.auditors = ctx.advance.status === auditConst.status.checkNo && ctx.session.sessionUser.accountId !== ctx.advance.uid ?
+                await ctx.service.advanceAudit.getAuditorsWithOwner(ctx.advance.id, times) :
+                await ctx.service.advanceAudit.getAuditorsWithOwner(ctx.advance.id, ctx.advance.times);
             if (times >= 1) {
                 for (let i = 1; i <= times; i++) {
                     auditHistory.push(await ctx.service.advanceAudit.getAuditors(ctx.advance.id, i));
@@ -236,14 +241,14 @@ module.exports = app => {
                 if (exist) {
                     throw '该审核人已存在,请勿重复添加';
                 }
-
-                const result = await ctx.service.advanceAudit.addAuditor(ctx.tender.id, ctx.advance.id, audit_id, ctx.advance.times);
+                const is_gdzs = ctx.tender.info.shenpi.advance === shenpiConst.sp_status.gdzs ? 1 : 0;
+                const result = await ctx.service.advanceAudit.addAuditor(ctx.tender.id, ctx.advance.id, audit_id, ctx.advance.times, is_gdzs);
                 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 };
+                const auditors = await ctx.service.advanceAudit.getAuditorsWithOwner(ctx.advance.id, ctx.advance.times);
+                ctx.body = { err: 0, msg: '', data: auditors };
             } catch (err) {
                 this.log(err);
                 ctx.body = { err: 1, msg: err.toString(), data: null };
@@ -418,7 +423,7 @@ module.exports = app => {
             try {
                 const { id } = JSON.parse(ctx.request.body.data);
                 const fileInfo = await ctx.service.advanceFile.getDataById(id);
-                if (fileInfo) {
+                if (fileInfo || Object.keys(fileInfo).length) {
                     // 先删除文件
                     await fs.unlinkSync(path.resolve(this.app.baseDir, './app', fileInfo.filepath));
                     // 再删除数据库

+ 97 - 7
app/controller/change_controller.js

@@ -16,6 +16,7 @@ const audit = require('../const/audit');
 const codeRuleConst = require('../const/code_rule');
 const changeConst = require('../const/change');
 const accountGroup = require('../const/account_group').group;
+const shenpiConst = require('../const/shenpi');
 // const tenderMenu = require('../../config/menu').tenderMenu;
 
 module.exports = app => {
@@ -268,6 +269,9 @@ module.exports = app => {
                 // 获取附件列表
                 const attList = await ctx.service.changeAtt.getChangeAttachment(ctx.params.cid);
 
+                // 获取其他变更令数据
+                const othersChange = await ctx.service.change.getOthersChange(ctx.params.id, ctx.params.cid);
+
                 // 根据auditStatus获取审批人列表
                 const auditList = await ctx.service.changeAudit.getListByStatus(change, auditStatus);
                 // 获取已选清单
@@ -280,6 +284,7 @@ module.exports = app => {
                     uid: ctx.session.sessionUser.accountId,
                     tender,
                     change,
+                    othersChange,
                     changeConst,
                     auditStatus,
                     auditConst: audit.flow,
@@ -291,24 +296,29 @@ module.exports = app => {
                     tpUnit: ctx.tender.info.decimal.tp,
                     upUnit: ctx.tender.info.decimal.up,
                     authMobile: auth_mobile,
+                    shenpiConst,
                 };
                 // 根据auditStatus状态获取的不同的数据
                 if (auditStatus === 1 || auditStatus === 2) {
                     renderData.changeUnits = changeConst.units;
                     renderData.precision = ctx.tender.info.precision;
-                    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'],
                     });
                     renderData.accountList = accountList;
+                    renderData.accountGroup = accountGroup.map((item, idx) => {
+                        const groupList = accountList.filter(item => item.account_group === idx);
+                        return { groupName: item, groupList };
+                    });
                     // 重新上报获取审批流程
                     if (auditStatus === 2) {
                         const auditList2 = await ctx.service.changeAudit.getListByBack(change.cid, change.times);
                         // 展示页右侧审批流程列表
                         const auditList3 = [];
-                        for (let time = 1; time <= change.times; time++) {
+                        for (let time = 1; time <= change.times - 1; time++) {
                             const auditTimeList = [];
                             let max_sort = 1;
                             for (const al of auditList2) {
@@ -364,11 +374,13 @@ module.exports = app => {
                     renderData.companyList = companyList;
                 } else if (auditStatus === 3 || auditStatus === 4 || auditStatus === 5 || auditStatus === 7) {
                     // 展示页左侧审批流程列表和清单审批列表数据
-                    const auditList2 = await ctx.service.changeAudit.getListGroupByTimes(change.cid, change.times);
+                    const times = change.status === audit.flow.status.back ?
+                        change.times - 1 : change.times;
+                    const auditList2 = await ctx.service.changeAudit.getListGroupByTimes(change.cid, times);
 
                     // 展示页右侧审批流程列表
                     const auditList3 = [];
-                    for (let time = 1; time <= change.times; time++) {
+                    for (let time = 1; time <= times; time++) {
                         const auditTimeList = [];
                         let max_sort = 1;
                         for (const al of auditList) {
@@ -500,9 +512,8 @@ module.exports = app => {
                 renderData.auditList = auditList;
 
                 // 获取是否已存在调用变更令
-                const stageChangeNum = await ctx.service.stageChange.count({ cid: change.cid });
-                const stageChangeNum2 = await ctx.service.stageChange.count({ cid: change.cid, qty: null });
-                renderData.stageChangeNum = stageChangeNum - stageChangeNum2;
+                const changeUsedData = await ctx.service.stageChange.getFinalUsedData(ctx.tender.id, change.cid);
+                renderData.stageChangeNum = this.ctx.helper.sum(changeUsedData.map(x => { return Math.abs(x.used_qty); }));
                 await this.layout('change/info.ejs', renderData, 'change/info_modal.ejs');
             } catch (err) {
                 this.log(err);
@@ -631,6 +642,8 @@ module.exports = app => {
                 const parts = ctx.multipart({ autoFields: true });
                 const files = [];
                 let index = 0;
+                const change = await ctx.service.change.getDataByCondition({ cid: ctx.params.cid });
+                const extra_upload = change.status === audit.flow.status.checked;
                 while ((stream = await parts()) !== undefined) {
                     // 判断用户是否选择上传文件
                     if (!stream.filename) {
@@ -658,6 +671,7 @@ module.exports = app => {
                         fileext: fileInfo.ext,
                         filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size,
                         filepath,
+                        extra_upload,
                     };
                     const result = await ctx.service.changeAtt.save(parts.field, fileData, ctx.session.sessionUser.accountId);
                     if (!result) {
@@ -722,6 +736,50 @@ module.exports = app => {
         }
 
         /**
+         * 批量下载 - 压缩成zip文件返回
+         * @param {Oject} ctx - 全局上下文
+         */
+        async downloadZip(ctx) {
+            try {
+                const fileIds = JSON.parse(ctx.request.query.fileIds);
+                // const { fileIds } = JSON.parse(ctx.request.body.data);
+                // console.log('fileIds', fileIds);
+                const { name: changeName } = await ctx.service.changeAtt.getChangeName(ctx.params.cid);
+                const zipFilename = `${ctx.tender.data.name}-工程变更-${changeName}-附件.zip`;
+                const time = Date.now();
+                const zipPath = `app/public/upload/change/fu_jian_zip${time}.zip`;
+                const size = await ctx.service.changeAtt.compressedFile(fileIds, zipPath);
+
+                // 解决中文无法下载问题
+                const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
+                let disposition = '';
+                if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
+                    disposition = 'attachment; filename=' + encodeURIComponent(zipFilename);
+                } else if (userAgent.indexOf('firefox') >= 0) {
+                    disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(zipFilename) + '"';
+                } else {
+                    /* safari等其他非主流浏览器只能自求多福了 */
+                    disposition = 'attachment; filename=' + new Buffer(zipFilename).toString('binary');
+                }
+                ctx.response.set({
+                    'Content-Type': 'application/octet-stream',
+                    'Content-Disposition': disposition,
+                    'Content-Length': size,
+                });
+                const readStream = fs.createReadStream(path.join(this.app.baseDir, zipPath));
+
+                ctx.body = readStream;
+                // ctx.body = fs.readFileSync(path.resolve(this.app.baseDir, zipPath));
+                readStream.on('close', () => {
+                    if (fs.existsSync(path.resolve(this.app.baseDir, zipPath))) {
+                        fs.unlinkSync(path.resolve(this.app.baseDir, zipPath));
+                    }
+                });
+            } catch (error) {
+                this.log(error);
+            }
+        }
+        /**
          * 删除附件
          * @param {Object} ctx - egg全局变量
          * @return {void}
@@ -734,7 +792,14 @@ module.exports = app => {
             };
             try {
                 const data = JSON.parse(ctx.request.body.data);
+                const change = await ctx.service.change.getDataByCondition({ cid: ctx.params.cid });
                 const fileInfo = await ctx.service.changeAtt.getDataById(data.id);
+                if (!fileInfo || !Object.keys(fileInfo).length) {
+                    throw '该文件不存在';
+                }
+                if (!fileInfo.extra_upload && change.status === audit.flow.status.checked) {
+                    throw '无权限删除';
+                }
                 if (fileInfo !== undefined && fileInfo !== '') {
                     // 先删除文件
                     await fs.unlinkSync(path.join(this.app.baseDir, fileInfo.filepath));
@@ -907,6 +972,31 @@ module.exports = app => {
 
             ctx.body = responseData;
         }
+
+        /**
+         * 拷贝其他变更令
+         * @param {object} ctx - 全局上下文
+         */
+        async copyChange(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: '',
+            };
+            try {
+                const cid = ctx.params.cid;
+                const copy_cid = JSON.parse(ctx.request.body.data);
+                const result = await ctx.service.change.handleCopyChange(cid, copy_cid);
+                if (!result) {
+                    responseData.err = 1;
+                    responseData.msg = '拷贝失败!';
+                }
+            } catch (error) {
+                responseData.err = 1;
+                responseData.msg = error;
+            }
+            ctx.body = responseData;
+        }
     }
 
     return ChangeController;

+ 21 - 4
app/controller/deal_bills_controller.js

@@ -19,6 +19,7 @@ const loadExcelType = {
 };
 const loadType = loadExcelType.display;
 const auditConst = require('../const/audit').ledger;
+const permissionConst = require('../const/account_permission');
 
 module.exports = app => {
     class DealBillsController extends app.BaseController {
@@ -48,6 +49,24 @@ module.exports = app => {
             ctx.body = responseData;
         }
 
+        async checkPermisision(ctx) {
+            if (!ctx.tender.data) throw '标段数据错误';
+
+            const tender = ctx.tender.data;
+            const isUser = tender.user_id === this.ctx.session.sessionUser.accountId;
+            const auditors = await this.service.ledgerAudit.getAuditors(tender.id, tender.ledger_times);
+            const auditorsId = this.ctx.helper._.map(auditors, 'audit_id');
+            const isAuditor  = auditorsId.indexOf(this.ctx.session.sessionUser.accountId) >= 0;
+            const upPermision = this.ctx.session.sessionUser.permission
+                ? this.ctx.session.sessionUser.permission.tender.indexOf('3') >= 0
+                : false;
+            if (!(((tender.ledger_status === auditConst.status.uncheck || tender.ledger_status === auditConst.status.checkNo) && isUser) ||
+                (tender.ledger_status === auditConst.status.checking && isAuditor) ||
+                (tender.ledger_status === auditConst.status.checked && isAuditor && upPermision))) {
+                throw '您无权进行该操作';
+            }
+        }
+
         /**
          * 导入Excel数据
          * @param ctx
@@ -56,6 +75,7 @@ module.exports = app => {
         async loadExcel(ctx) {
             let stream;
             try {
+                await this.checkPermisision(ctx);
                 stream = await ctx.getFileStream();
                 const create_time = Date.parse(new Date()) / 1000;
                 const fileInfo = path.parse(stream.filename);
@@ -165,10 +185,7 @@ module.exports = app => {
          */
         async update(ctx) {
             try {
-                if (!ctx.tender.data) throw '标段数据错误';
-                if (ctx.tender.data.user_id !== ctx.session.sessionUser.accountId ||
-                    (ctx.tender.ledger_status === auditConst.status.checking || ctx.tender.ledger_status === auditConst.status.checked))
-                    throw '您无权进行该操作';
+                await this.checkPermisision(ctx);
                 const data = JSON.parse(ctx.request.body.data);
                 const result = await ctx.service.dealBills.updateDatas(data);
                 ctx.body = { err: 0, msg: '', data: result };

+ 10 - 8
app/controller/ledger_audit_controller.js

@@ -9,6 +9,7 @@
  */
 const auditConst = require('../const/audit').ledger;
 const spreadConst = require('../const/spread');
+const shenpiConst = require('../const/shenpi');
 const measureType = require('../const/tender').measureType;
 
 module.exports = app => {
@@ -74,7 +75,10 @@ module.exports = app => {
                     throw '审核信息错误';
                 }
 
-                const auditors = await ctx.service.ledgerAudit.getAuditorsWithOwner(ctx.tender.id, ctx.tender.data.ledger_times);
+                renderData.user = await ctx.service.projectAccount.getAccountInfoById(ctx.tender.data.user_id);
+                renderData.auditHistory = [];
+                const times = ctx.tender.data.ledger_status === auditConst.status.checkNo ? ctx.tender.data.ledger_times - 1 : ctx.tender.data.ledger_times;
+                const auditors = await ctx.service.ledgerAudit.getAuditorsWithOwner(ctx.tender.id, 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; });
@@ -83,10 +87,6 @@ module.exports = app => {
                         }
                     }
                 }
-
-                renderData.user = await ctx.service.projectAccount.getAccountInfoById(ctx.tender.data.user_id);
-                renderData.auditHistory = [];
-                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));
@@ -138,13 +138,14 @@ module.exports = app => {
                 if (exist) {
                     throw '该审核人已存在,请勿重复添加';
                 }
-
-                const result = await ctx.service.ledgerAudit.addAuditor(ctx.tender.id, id, ctx.tender.data.ledger_times);
+                const is_gdzs = ctx.tender.info.shenpi.ledger === shenpiConst.sp_status.gdzs ? 1 : 0;
+                const result = await ctx.service.ledgerAudit.addAuditor(ctx.tender.id, id, ctx.tender.data.ledger_times, is_gdzs);
                 if (!result) {
                     throw '添加审核人失败';
                 }
 
-                responseData.data = await ctx.service.ledgerAudit.getAuditor(ctx.tender.id, id, ctx.tender.data.ledger_times);
+                // responseData.data = await ctx.service.ledgerAudit.getAuditor(ctx.tender.id, id, ctx.tender.data.ledger_times);
+                responseData.data = await ctx.service.ledgerAudit.getAuditorsWithOwner(ctx.tender.id, ctx.tender.data.ledger_times);
             } catch (err) {
                 responseData.err = 1;
                 responseData.msg = err.toString();
@@ -206,6 +207,7 @@ module.exports = app => {
                 ctx.body = { err: 0, msg: '', data: { url: '/tender/' + ctx.tender.id + '/ledger' } };
             } catch (err) {
                 this.log(err);
+                ctx.session.postError = err.toString();
                 ctx.body = this.ajaxErrorBody(err, '上报失败,请刷新页面重试');
             }
         }

+ 32 - 4
app/controller/ledger_controller.js

@@ -18,6 +18,7 @@ const auditConst = audit.ledger;
 const tenderMenu = require('../../config/menu').tenderMenu;
 const measureType = require('../const/tender').measureType;
 const spreadConst = require('../const/spread');
+const shenpiConst = require('../const/shenpi');
 const externalDataConst = require('../const/external_data.js');
 const fs = require('fs');
 const LzString = require('lz-string');
@@ -56,6 +57,18 @@ module.exports = app => {
                 (tender.ledger_status === auditConst.status.checking || tender.ledger_status === auditConst.status.checked);
         }
 
+        _canUpdateDealBills(tender, auditors) {
+            const isUser = tender.user_id === this.ctx.session.sessionUser.accountId;
+            const auditorsId = this.ctx.helper._.map(auditors, 'audit_id');
+            const isAuditor  = auditorsId.indexOf(this.ctx.session.sessionUser.accountId) >= 0;
+            const upPermission = this.ctx.session.sessionUser.permission
+                ? this.ctx.session.sessionUser.permission.tender.indexOf('3') >= 0
+                : false;
+            return ((tender.ledger_status === auditConst.status.uncheck || tender.ledger_status === auditConst.status.checkNo) && isUser) ||
+                (tender.ledger_status === auditConst.status.checking && isAuditor) ||
+                (tender.ledger_status === auditConst.status.checked && isAuditor && upPermission);
+        }
+
         /**
          * 获取SpreadSetting
          * @private
@@ -119,7 +132,9 @@ module.exports = app => {
                 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 auditors = await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, times);
+                const auditors = tender.data.ledger_status === auditConst.status.checkNo && tender.data.user_id !== ctx.session.sessionUser.accountId ?
+                    await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, times) :
+                    await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, tender.data.ledger_times);
                 const user = await ctx.service.projectAccount.getAccountInfoById(ctx.tender.data.user_id);
                 const auditHistory = [];
                 if (times >= 1) {
@@ -145,6 +160,8 @@ module.exports = app => {
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.explode),
                     stdBills,
                     stdChapters,
+                    dealBillsPermission: this._canUpdateDealBills(tender.data, auditors.filter(x => {return x.audit_order > 0})),
+                    shenpiConst,
                 };
                 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;
@@ -449,9 +466,21 @@ module.exports = app => {
                 const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
                 const posData = this.ctx.tender.data.measure_type === measureType.tz.value
                     ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
-                const data = ctx.helper.checkBillsWithPos(ledgerData, posData,
+                const qtyData = ctx.helper.checkBillsWithPos(ledgerData, posData,
                     ['sgfh_qty', 'qtcl_qty', 'sjcl_qty', 'quantity']);
-                ctx.body = { err: 0, msg: '', data };
+                qtyData.error.forEach(x => { x.errorType = 'qty'; });
+                const tpData = ctx.helper.checkBillsTp(ledgerData, [
+                    {qty: 'sgfh_qty', tp: 'sgfh_tp'}, {qty: 'qtcl_qty', tp: 'qtcl_tp'},
+                    {qty: 'sjcl_qty', tp: 'sjcl_tp'}, {qty: 'quantity', tp: 'total_price'}
+                ], this.ctx.tender.info.decimal);
+                tpData.error.forEach(x => { x.errorType = 'tp'; });
+                ctx.body = { err: 0, msg: '', data: {
+                    error: [...qtyData.error, ...tpData.error],
+                    source: {
+                        bills: [...qtyData.source.bills, ...tpData.source.bills],
+                        pos: [...qtyData.source.pos, ...tpData.source.pos],
+                    },
+                }};
             } catch (err) {
                 this.log(err);
                 ctx.body = this.ajaxErrorBody(err, '检查数据错误');
@@ -603,7 +632,6 @@ module.exports = app => {
                         fileName = path.join(this.app.baseDir, 'app', 'public', 'files', 'template', 'ledger', '导入分项清单EXCEL格式.xls');
                         ctx.body = await fs.readFileSync(fileName);
                     } else if (file === '台账分解.xlsx') {
-                        console.log(file);
                         const create_time = Date.parse(new Date()) / 1000;
                         fileName = this.app.baseDir + '/app/public/files/ledger' + ctx.tender.id + '-' + create_time + '.xlsx';
                         const exportor = new exportExcel.exportLedger2Excel(ctx);

+ 81 - 19
app/controller/material_controller.js

@@ -15,6 +15,7 @@ const tenderConst = require('../const/tender');
 const measureType = tenderConst.measureType;
 const accountGroup = require('../const/account_group').group;
 const materialConst = require('../const/material');
+const shenpiConst = require('../const/shenpi');
 const MaterialCalculator = require('../lib/material_calc');
 const sendToWormhole = require('stream-wormhole');
 const fs = require('fs');
@@ -176,6 +177,7 @@ module.exports = app => {
                 measureType,
                 preUrl: '/tender/' + ctx.tender.id + '/measure/material/' + ctx.params.order,
                 material: ctx.material,
+                shenpiConst,
             };
             if ((ctx.material.status === auditConst.status.uncheck || ctx.material.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ctx.material.user_id) {
                 // data.accountGroup = accountGroup;
@@ -214,7 +216,9 @@ module.exports = app => {
                 }
             }
             // 获取审批流程中左边列表
-            ctx.material.auditors2 = await ctx.service.materialAudit.getAuditorsWithOwner(ctx.material.id, times);
+            ctx.material.auditors2 = ctx.material.status === auditConst.status.checkNo && ctx.material.user_id !== ctx.session.sessionUser.accountId ?
+                await ctx.service.materialAudit.getAuditorsWithOwner(ctx.material.id, times) :
+                await ctx.service.materialAudit.getAuditorsWithOwner(ctx.material.id, ctx.material.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);
             }
@@ -400,12 +404,13 @@ module.exports = app => {
                             return bq.code === item.code;
                         });
                         bq.value = calc.value;
-                        if (calc.code === 'bqwc') {
-                            ctx.material.ex_expr = calc.value.toString() + '*[0-1]';
-                        }
+                        // if (calc.code === 'bqwc') {
+                        //     ctx.material.ex_expr = calc.value.toString() + '*[0-1]';
+                        //     ctx.material.ex_tp = this.ctx.helper.round(this.ctx.helper.mul(calc.value, -1), 2);
+                        // }
                     }
                     // 并更新至调差表中
-                    await ctx.service.material.update({ ex_calc: JSON.stringify(renderData.ex_calc), ex_expr: ctx.material.ex_expr }, { id: ctx.material.id });
+                    await ctx.service.material.update({ ex_calc: JSON.stringify(renderData.ex_calc) }, { id: ctx.material.id });
                 } else {
                     renderData.ex_calc = JSON.parse(ctx.material.ex_calc);
                 }
@@ -738,14 +743,14 @@ module.exports = app => {
                 if (exist) {
                     throw '该审核人已存在,请勿重复添加';
                 }
-
-                const result = await ctx.service.materialAudit.addAuditor(ctx.material.id, id, ctx.material.times);
+                const is_gdzs = ctx.tender.info.shenpi.material === shenpiConst.sp_status.gdzs ? 1 : 0;
+                const result = await ctx.service.materialAudit.addAuditor(ctx.material.id, id, ctx.material.times, is_gdzs);
                 if (!result) {
                     throw '添加审核人失败';
                 }
 
-                const audit = await ctx.service.materialAudit.getAuditor(ctx.material.id, id, ctx.material.times);
-                ctx.body = { err: 0, msg: '', data: audit };
+                const auditors = await ctx.service.materialAudit.getAuditorsWithOwner(ctx.material.id, ctx.material.times);
+                ctx.body = { err: 0, msg: '', data: auditors };
             } catch (err) {
                 this.log(err);
                 ctx.body = { err: 1, msg: err.toString(), data: null };
@@ -838,19 +843,24 @@ module.exports = app => {
             }
         }
 
-        _checkMaterialFileCanModify(ctx) {
+        async _checkMaterialFileCanModify(ctx) {
             // 检查登录用户,是否可操作
             const accountId = ctx.session.sessionUser.accountId;
-            if (!ctx.material.curAuditor) {
-                if (ctx.material.status === auditConst.status.uncheck || ctx.material.status === auditConst.status.checkNo && accountId === ctx.material.user_id) {
-                    return;
-                }
-                throw '该调差期当前您无权操作';
-            } else {
-                if (ctx.material.curAuditor.aid === accountId) return;
+            const auditors = await ctx.service.materialAudit.getAuditorsWithOwner(ctx.material.id, ctx.material.times);
+            // console.log(auditors);
+
+            if (auditors.findIndex(item => item.aid === accountId) === -1) {
                 throw '该调差期当前您无权操作';
             }
-
+            // if (!ctx.material.curAuditor) {
+            //     if (ctx.material.status === auditConst.status.uncheck || ctx.material.status === auditConst.status.checkNo && accountId === ctx.material.user_id) {
+            //         return;
+            //     }
+            //     throw '该调差期当前您无权操作';
+            // } else {
+            //     if (ctx.material.curAuditor.aid === accountId) return;
+            //     throw '该调差期当前您无权操作';
+            // }
         }
         /**
          * 上传附件
@@ -859,13 +869,14 @@ module.exports = app => {
         async upload(ctx) {
             let stream;
             try {
-                this._checkMaterialFileCanModify(ctx);
+                await this._checkMaterialFileCanModify(ctx);
                 const parts = this.ctx.multipart({
                     autoFields: true,
                 });
                 const files = [];
                 const create_time = Date.parse(new Date()) / 1000;
                 let idx = 0;
+                const extra_upload = ctx.material.status === auditConst.status.checked;
                 while ((stream = await parts()) !== undefined) {
                     if (!stream.filename) {
                         // 如果没有传入直接返回
@@ -896,6 +907,7 @@ module.exports = app => {
                         file_size: ctx.helper.bytesToSize(idx === 'isString' ? parts.field.size : parts.field.size[idx]),
                         file_name: file.name,
                         fileext: file.ext,
+                        extra_upload,
                     };
                     return newFile;
                 });
@@ -975,6 +987,12 @@ module.exports = app => {
                 const { data } = ctx.request.body;
                 const { id } = JSON.parse(data);
                 const fileInfo = await ctx.service.materialFile.getMaterialFileById(id);
+                if (!fileInfo || !Object.keys(fileInfo).length) {
+                    throw '该文件不存在';
+                }
+                if (!fileInfo.extra_upload && ctx.material.status === auditConst.status.checked) {
+                    throw '无权限删除';
+                }
                 if (fileInfo) {
                     // 先删除文件
                     await fs.unlinkSync(path.resolve(this.app.baseDir, './app', fileInfo.filepath));
@@ -989,6 +1007,50 @@ module.exports = app => {
                 ctx.body = { err: 1, msg: err.toString(), data: null };
             }
         }
+
+        /**
+         * 批量下载 - 压缩成zip文件返回
+         * @param {Object} ctx - 全局上下文
+         */
+        async downloadZip(ctx) {
+            try {
+                const fileIds = JSON.parse(ctx.request.query.fileIds);
+                // const { fileIds } = JSON.parse(ctx.request.body.data);
+                // console.log('fileIds', fileIds);
+                const zipFilename = `${ctx.tender.data.name}-材料调差-${ctx.params.order}-附件.zip`;
+                const time = Date.now();
+                const zipPath = `app/public/upload/${ctx.tender.id}/tc/fu_jian_zip${time}.zip`;
+                const size = await ctx.service.materialFile.compressedFile(fileIds, zipPath);
+
+                // 解决中文无法下载问题
+                const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
+                let disposition = '';
+                if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
+                    disposition = 'attachment; filename=' + encodeURIComponent(zipFilename);
+                } else if (userAgent.indexOf('firefox') >= 0) {
+                    disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(zipFilename) + '"';
+                } else {
+                    /* safari等其他非主流浏览器只能自求多福了 */
+                    disposition = 'attachment; filename=' + new Buffer(zipFilename).toString('binary');
+                }
+                ctx.response.set({
+                    'Content-Type': 'application/octet-stream',
+                    'Content-Disposition': disposition,
+                    'Content-Length': size,
+                });
+                const readStream = fs.createReadStream(path.join(this.app.baseDir, zipPath));
+
+                ctx.body = readStream;
+                // ctx.body = fs.readFileSync(path.resolve(this.app.baseDir, zipPath));
+                readStream.on('close', () => {
+                    if (fs.existsSync(path.resolve(this.app.baseDir, zipPath))) {
+                        fs.unlinkSync(path.resolve(this.app.baseDir, zipPath));
+                    }
+                });
+            } catch (error) {
+                this.log(error);
+            }
+        }
     }
 
     return MaterialController;

+ 3 - 3
app/controller/measure_controller.js

@@ -142,9 +142,9 @@ module.exports = app => {
                 if (!stage) {
                     throw '提交数据错误';
                 }
-                if (stage.status === auditConst.status.checked) {
-                    throw '该计量期当前不可编辑';
-                }
+                // if (stage.status === auditConst.status.checked) {
+                //     throw '该计量期当前不可编辑';
+                // }
                 if (ctx.session.sessionUser.accountId !== stage.user_id) {
                     throw '您无权修改该数据';
                 }

+ 164 - 23
app/controller/report_controller.js

@@ -7,6 +7,7 @@
 const tenderMenu = require('../../config/menu').tenderMenu;
 const measureType = require('../const/tender').measureType;
 const auditConst = require('../const/audit');
+const shenpiConst = require('../const/shenpi');
 const accountGroup = require('../const/account_group').group;
 const JpcEx = require('../reports/rpt_component/jpc_ex');
 const JV = require('../reports/rpt_component/jpc_value_define');
@@ -212,6 +213,7 @@ module.exports = app => {
                     // pageShow: JSON.stringify(pageShow),
                     pageShow,
                     authMobile: accountInfo.auth_mobile,
+                    shenpiConst,
                 };
                 await this.layout('report/index.ejs', renderData, 'report/rpt_all_popup.ejs');
                 // await this.layout('report/index.ejs', renderData);
@@ -432,16 +434,22 @@ module.exports = app => {
                     );
                 });
             }
+            const stgAudit = (params.stage_status === 3) ? (await ctx.service.stageAudit.getStageAudit(params.stage_id, params.stage_times)) : [];
+            const stgAuditForOrg = (params.stage_status === 3) ? (await ctx.service.stageAudit.getStageAudit(params.stage_id, 1)) : [];
+            const stageList = (params.stage_status === 3) ? (await ctx.service.stage.getValidStagesShort(params.tender_id)) : [];
+
             const roleRelArr = (params.stage_status === 3) ? (await ctx.service.roleRptRel.getRoleRptRelByDetailIds(params.tender_id, params.rpt_ids, params.stage_id)) : [];
             // const roleRel = (params.stage_status === 3) ? (await ctx.service.roleRptRel.getRoleRptRelByDetailIds(params.tender_id, params.rpt_tpl_id)) : [];
             const pageRstArr = await getMultiRptsCommon(ctx, params, JV.OUTPUT_TYPE_NORMAL, this.app.baseDir);
             // console.log('params.stage_status: ' + params.stage_status);
+            // fsUtil.writeObjToFile(pageRstArr, 'D:/GitHome/temp/testBuiltPageResult.js');
             for (const pageRst of pageRstArr) {
                 for (const page of pageRst.items) {
                     page[JV.PROP_WATERMARK_CELLS] = [];
                 }
             }
             if (params.stage_status !== 3 && params.closeWatermark === 0) {
+            // if (params.stage_status !== 3 && params.closeWatermark === 0 && params.option === JV.PAGING_OPTION_NORMAL) {
                 // 加水印(注意:还得看用户设置是否需要加水印)
                 fillWaterMark(pageRstArr);
             }
@@ -459,6 +467,10 @@ module.exports = app => {
                 }
                 // console.log('roleRel.rel_content: ' + roleRel.rel_content);
                 // fsUtil.writeObjToFile(pageRstArr, 'D:/GitHome/temp/testBuiltPageResult.jsp');
+                if (params.stage_status === 3) {
+                    mergeSignAudit(pageRstArr[idx], roleRel, stgAudit);
+                    mergeSignDate(pageRstArr[idx], params.stage_id, roleRel, stgAudit, stgAuditForOrg, stageList);
+                }
                 runnableRst.push(getExcelByPageData(pageRstArr[idx], params.rpt_names[idx], roleRel));
             }
             // fsUtil.writeObjToFile(pageRstArr, 'D:/GitHome/temp/计量导出pageArr.js');
@@ -482,6 +494,10 @@ module.exports = app => {
                 });
             }
             const roleRelArr = (params.stage_status === 3) ? (await ctx.service.roleRptRel.getRoleRptRelByDetailIds(params.tender_id, params.rpt_ids, params.stage_id)) : [];
+            const stgAudit = (params.stage_status === 3) ? (await ctx.service.stageAudit.getStageAudit(params.stage_id, params.stage_times)) : [];
+            const stgAuditForOrg = (params.stage_status === 3) ? (await ctx.service.stageAudit.getStageAudit(params.stage_id, 1)) : [];
+            const stageList = (params.stage_status === 3) ? (await ctx.service.stage.getValidStagesShort(params.tender_id)) : [];
+
             const pageRstArr = await getMultiRptsCommon(ctx, params, JV.OUTPUT_TYPE_NORMAL, this.app.baseDir);
             for (const pageRst of pageRstArr) {
                 for (const page of pageRst.items) {
@@ -489,6 +505,7 @@ module.exports = app => {
                 }
             }
             if (params.stage_status !== 3 && params.closeWatermark === 0) {
+            // if (params.stage_status !== 3 && params.closeWatermark === 0 && params.option === JV.PAGING_OPTION_NORMAL) {
                 // 加水印
                 fillWaterMark(pageRstArr);
             }
@@ -524,6 +541,10 @@ module.exports = app => {
                 for (const roleR of roleRelArr) {
                     if (roleR.rpt_id === params.rpt_ids[idx]) {
                         roleRel = JSON.parse(roleR.rel_content);
+                        if (params.stage_status === 3) {
+                            mergeSignAudit(pageRstArr[idx], roleRel, stgAudit);
+                            mergeSignDate(pageRstArr[idx], params.stage_id, roleRel, stgAudit, stgAuditForOrg, stageList);
+                        }
                         // 这里要做些电子签名的signature_name转换,以防重名
                         reAssignSignatureName(pageRstArr[idx], roleRel, params.rpt_names[idx]);
                         rptRoleRelArr = rptRoleRelArr.concat(roleRel);
@@ -1076,32 +1097,49 @@ function isFileExisted(file) {
 }
 
 function fillWaterMark(pageRstArray) {
+    const orgWaterMarkWidth = 600;
+    const orgWaterMarkHeight = 288;
+    const createWaterCell = function(area) {
+        const w = area[JV.PROP_RIGHT] - area[JV.PROP_LEFT];
+        const h = area[JV.PROP_BOTTOM] - area[JV.PROP_TOP];
+        const left = Math.round(area[JV.PROP_LEFT] + w / 2 - orgWaterMarkWidth / 2);
+        const right = left + orgWaterMarkWidth;
+        const top = Math.round(area[JV.PROP_TOP] + h / 2 - orgWaterMarkHeight / 2);
+        const bottom = top + orgWaterMarkHeight;
+        return {
+            signature_name: JV.SIGNATURE_NAME_DUMMY_WATER_MARK,
+            path: '/public/images/not_Approve.png',
+            sign_path: '/public/images/not_Approve.png',
+            pic: null,
+            control: 'Title',
+            style: 'Default_None',
+            area: {
+                Left: left,
+                Right: right,
+                Top: top,
+                Bottom: bottom,
+            },
+        };
+    };
     for (const pageRst of pageRstArray) {
-        const orgWaterMarkWidth = 600;
-        const orgWaterMarkHeight = 288;
         for (const page of pageRst.items) {
-            const w = page[JV.PROP_PAGE_MERGE_BORDER].Right - page[JV.PROP_PAGE_MERGE_BORDER].Left;
-            const h = page[JV.PROP_PAGE_MERGE_BORDER].Bottom - page[JV.PROP_PAGE_MERGE_BORDER].Top;
-            const left = Math.round(page[JV.PROP_PAGE_MERGE_BORDER].Left + w / 2 - orgWaterMarkWidth / 2);
-            const right = left + orgWaterMarkWidth;
-            const top = Math.round(page[JV.PROP_PAGE_MERGE_BORDER].Top + h / 2 - orgWaterMarkHeight / 2);
-            const bottom = top + orgWaterMarkHeight;
-            const warterCell = {
-                signature_name: JV.SIGNATURE_NAME_DUMMY,
-                path: '/public/images/not_Approve.png',
-                sign_path: '/public/images/not_Approve.png',
-                pic: null,
-                control: 'Title',
-                style: 'Default_None',
-                area: {
-                    Left: left,
-                    Right: right,
-                    Top: top,
-                    Bottom: bottom,
-                },
-            };
             page[JV.PROP_WATERMARK_CELLS] = [];
-            page[JV.PROP_WATERMARK_CELLS].push(warterCell);
+            if (page[JV.PROP_PAGE_MERGE_BORDER]) {
+                const wmCell = createWaterCell(page[JV.PROP_PAGE_MERGE_BORDER]);
+                page[JV.PROP_WATERMARK_CELLS].push(wmCell);
+            } else if (page[JV.PAGE_SPECIAL_MERGE_POS]) {
+                if (page[JV.PAGE_SPECIAL_MERGE_POS][JV.PROP_LEFT]) {
+                    for (let i = 0; i < page[JV.PAGE_SPECIAL_MERGE_POS][JV.PROP_LEFT].length; i++) {
+                        const area = {};
+                        area[JV.PROP_LEFT] = page[JV.PAGE_SPECIAL_MERGE_POS][JV.PROP_LEFT][i];
+                        area[JV.PROP_RIGHT] = page[JV.PAGE_SPECIAL_MERGE_POS][JV.PROP_RIGHT][i];
+                        area[JV.PROP_TOP] = page[JV.PAGE_SPECIAL_MERGE_POS][JV.PROP_TOP][i];
+                        area[JV.PROP_BOTTOM] = page[JV.PAGE_SPECIAL_MERGE_POS][JV.PROP_BOTTOM][i];
+                        const wmCell = createWaterCell(area);
+                        page[JV.PROP_WATERMARK_CELLS].push(wmCell);
+                    }
+                }
+            }
         }
     }
 }
@@ -1205,3 +1243,106 @@ async function createExportRequestJob(ctx) {
         }
     }
 }
+
+function mergeSignAudit(pageData, currRoleRelList, currAuditList) {
+    for (const page of pageData.items) {
+        if (page.signature_audit_cells) {
+            for (const sCell of page.signature_audit_cells) {
+                sCell.Value = ''; // 这里要先清除原有信息
+                for (const role_rel of currRoleRelList) {
+                    if (sCell.signature_name === role_rel.signature_name + '_审核意见') {
+                        let preDate = '';
+                        sCell.Value = '同意'; // 只有选择了签名的,才需要初始化一个默认的意见(之前的逻辑在有多个签名,哪怕只选择了一个,其他的意见都会有默认意见)
+                        for (const audit_rel of currAuditList) {
+                            if (role_rel.acc_id === audit_rel.aid) {
+                                if (audit_rel.end_time > preDate && audit_rel.status === 3) {
+                                    sCell.Value = audit_rel.opinion;
+                                    preDate = audit_rel.end_time;
+                                }
+                                // 不能break,实际会有多个审核意见,以最后一个为准
+                            }
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}
+
+function mergeSignDate(pageData, current_stage_id, currRoleRelList, STAGE_AUDIT, STAGE_AUDIT_ORG, STAGE_LIST) {
+    if (currRoleRelList && currRoleRelList.length > 0 && STAGE_AUDIT && STAGE_AUDIT.length > 0) {
+        for (let rridx = 0; rridx < currRoleRelList.length; rridx++) {
+            const role_rel = currRoleRelList[rridx];
+            if (role_rel.sign_date === undefined || role_rel.sign_date === null || role_rel.sign_date === '') {
+                const dftDate = _getSignDateByAllScenarios(role_rel.acc_id, current_stage_id, STAGE_AUDIT, STAGE_AUDIT_ORG, STAGE_LIST);
+                role_rel.sign_date = dftDate;
+            }
+        }
+    }
+    for (const page of pageData.items) {
+        if (page.signature_date_cells) {
+            for (const sCell of page.signature_date_cells) {
+                sCell.Value = _getSignDateDftName();
+                for (const role_rel of currRoleRelList) {
+                    if (sCell.signature_name === role_rel.signature_name + '_签字日期') {
+                        if (role_rel.sign_date !== '') {
+                            if (typeof role_rel.sign_date === 'string') {
+                                role_rel.sign_date = new Date(role_rel.sign_date);
+                            }
+                            sCell.Value = role_rel.sign_date.Format(role_rel.sign_date_format);
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}
+
+function _getSignDateByAllScenarios(userAccId, current_stage_id, STAGE_AUDIT, STAGE_AUDIT_ORG, STAGE_LIST) {
+    let rst = '';
+    let hasAudit = false;
+    for (const stg_audit of STAGE_AUDIT) {
+        if (stg_audit.aid === userAccId) {
+            hasAudit = true;
+            if (stg_audit.status === 3) {
+                rst = stg_audit.end_time;
+            } else {
+                rst = '';
+            }
+            // break; //因为实际业务中会有反复,所以就不break了,一直判断,以最后一个为准
+        }
+    }
+    let isOrgRpt = false;
+    for (const stg of STAGE_LIST) {
+        if (stg.id === current_stage_id) {
+            if (stg.user_id === userAccId) {
+                isOrgRpt = true;
+            }
+            break;
+        }
+    }
+    if (isOrgRpt && !hasAudit && STAGE_AUDIT_ORG && STAGE_AUDIT_ORG.length > 0) {
+        if (STAGE_AUDIT_ORG[0].begin_time && STAGE_AUDIT_ORG[0].begin_time !== '' && STAGE_AUDIT_ORG[0].begin_time.length > 20) {
+            rst = STAGE_AUDIT_ORG[0].begin_time;
+        }
+    }
+    if (!isOrgRpt && !hasAudit) {
+        // 非审批流程人员以及非原报,则显示期截至时间
+        for (const stg of STAGE_LIST) {
+            if (stg.id === current_stage_id && stg.period) {
+                const period = stg.period.split(' ~ ');
+                if (period.length === 2) {
+                    rst = period[1];
+                }
+            }
+        }
+    }
+    return rst;
+}
+
+function _getSignDateDftName() {
+    return '    年  月  日';
+}
+

+ 182 - 24
app/controller/revise_controller.js

@@ -18,6 +18,7 @@ const accountGroup = require('../const/account_group').group;
 const tenderMenu = require('../../config/menu').tenderMenu;
 const measureType = require('../const/tender').measureType;
 const spreadConst = require('../const/spread');
+const shenpiConst = require('../const/shenpi');
 const fs = require('fs');
 const LzString = require('lz-string');
 
@@ -62,13 +63,15 @@ module.exports = app => {
                 const ledgerRevise = await ctx.service.ledgerRevise.getReviseList(ctx.tender.id);
                 if (ledgerRevise.length > 0) {
                     const revise = ledgerRevise[0];
-                    if (revise.status === audit.revise.status.checked || !revise.valid) {
-                        revise.lastest = true;
-                    } else {
-                        if (ledgerRevise.length > 1) ledgerRevise[1].lastest = true;
-                        // if (revise.status === audit.revise.status.checking) {
-                        //     revise.curAuditor = await ctx.service.reviseAudit.getCurAuditor(revise.id, revise.times);
-                        // }
+                    if (ctx.page === 1) {
+                        if (revise.status === audit.revise.status.checked || !revise.valid) {
+                            revise.lastest = true;
+                        } else {
+                            if (ledgerRevise.length > 1) ledgerRevise[1].lastest = true;
+                            // if (revise.status === audit.revise.status.checking) {
+                            //     revise.curAuditor = await ctx.service.reviseAudit.getCurAuditor(revise.id, revise.times);
+                            // }
+                        }
                     }
                 }
                 for (const lr of ledgerRevise) {
@@ -260,9 +263,11 @@ 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.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 auditors = revise.status === audit.revise.status.checkNo && revise.uid === ctx.session.sessionUser.accountId ?
+                await ctx.service.reviseAudit.getAuditorsWithOwner(revise.id, revise.times) :
+                await ctx.service.reviseAudit.getAuditorsWithOwner(revise.id, times);
             const auditHistory = [];
             if (times >= 1) {
                 for (let i = 1; i <= times; i++) {
@@ -283,6 +288,7 @@ module.exports = app => {
                 auditors,
                 user,
                 auditHistory,
+                shenpiConst,
             };
         }
 
@@ -299,7 +305,7 @@ module.exports = app => {
             renderData.posSpread.readOnly = true;
             renderData.readOnly = true;
             renderData.history = true;
-            renderData.historyRevise = await ctx.service.ledgerRevise.getReviseList(ctx.tender.id);
+            renderData.historyRevise = await ctx.service.ledgerRevise.getAllReviseList(ctx.tender.id);
             await this.layout('revise/info.ejs', renderData, 'revise/info_modal.ejs');
         }
 
@@ -449,19 +455,25 @@ module.exports = app => {
                 const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
                 if (!revise) throw '台账修订数据有误';
 
-                const billsFile = revise.bills_file ? this.ctx.app.config.filePath + revise.bills_file : undefined;
-                const reviseBills = billsFile && fs.existsSync(billsFile)
-                    ? JSON.parse(await fs.readFileSync(billsFile, 'utf8'))
-                    : await ctx.service.reviseBills.getData(ctx.tender.id);
+                const reviseBills = await ctx.service.reviseBills.getData(ctx.tender.id);
+                const revisePos = await ctx.service.revisePos.getData(ctx.tender.id);
 
-                const posFile = revise.pos_file ? this.ctx.app.config.filePath + revise.pos_file : undefined;
-                const revisePos = posFile && fs.existsSync(posFile)
-                    ? JSON.parse(await fs.readFileSync(posFile, 'utf8'))
-                    : await ctx.service.revisePos.getData(ctx.tender.id);
-
-                const data = ctx.helper.checkBillsWithPos(reviseBills, revisePos,
+                const qtyData = ctx.helper.checkBillsWithPos(reviseBills, revisePos,
                     ['sgfh_qty', 'qtcl_qty', 'sjcl_qty', 'quantity']);
-                ctx.body = { err: 0, msg: '', data };
+                qtyData.error.forEach(x => { x.errorType = 'qty'; });
+                const tpData = ctx.helper.checkBillsTp(reviseBills, [
+                    {qty: 'sgfh_qty', tp: 'sgfh_tp'}, {qty: 'qtcl_qty', tp: 'qtcl_tp'},
+                    {qty: 'sjcl_qty', tp: 'sjcl_tp'}, {qty: 'quantity', tp: 'total_price'}
+                ], this.ctx.tender.info.decimal);
+                tpData.error.forEach(x => { x.errorType = 'tp'; });
+                ctx.body = { err: 0, msg: '', data: {
+                        error: [...qtyData.error, ...tpData.error],
+                        source: {
+                            bills: [...qtyData.source.bills, ...tpData.source.bills],
+                            pos: [...qtyData.source.pos, ...tpData.source.pos],
+                        },
+                    }
+                };
             } catch (err) {
                 this.log(err);
                 ctx.body = this.ajaxErrorBody(err, '检查数据错误');
@@ -478,7 +490,7 @@ module.exports = app => {
                 const [ledgerSpread, posSpread] = this._getSpreadSetting(revise);
                 ledgerSpread.readOnly = true;
                 posSpread.readOnly = true;
-                const historyRevise = await ctx.service.ledgerRevise.getReviseList(ctx.tender.id);
+                const historyRevise = await ctx.service.ledgerRevise.getAllReviseList(ctx.tender.id);
                 // 获取审批流程中右边列表
                 const auditHistory = [];
                 const times = revise.status === audit.revise.status.checkNo ? revise.times - 1 : revise.times;
@@ -514,7 +526,7 @@ module.exports = app => {
                 const data = JSON.parse(ctx.request.body.data);
                 if (!data || !data.rid || data.rid === '') throw '查询的台账修订有误';
                 const reviseInfo = await ctx.service.ledgerRevise.getRevise(ctx.tender.id, data.rid);
-                reviseInfo.end_time_str = reviseInfo.end_time ? reviseInfo.end_time.toLocaleString() : '';
+                reviseInfo.end_time_str = reviseInfo.end_time ? ctx.moment(reviseInfo.end_time).format('YYYY-MM-DD HH:mm:ss') : '';
                 ctx.body = { err: 0, msg: '', data: reviseInfo };
             } catch (err) {
                 this.log(err);
@@ -787,10 +799,11 @@ module.exports = app => {
                 const exist = await ctx.service.reviseAudit.getAuditor(revise.id, id, revise.times);
                 if (exist) throw '该审核人已存在,请勿重复添加';
 
-                const result = await ctx.service.reviseAudit.addAuditor(revise, id);
+                const is_gdzs = ctx.tender.info.shenpi.revise === shenpiConst.sp_status.gdzs ? 1 : 0;
+                const result = await ctx.service.reviseAudit.addAuditor(revise, id, is_gdzs);
                 if (!result) throw '添加审核人失败';
 
-                const resultData = await ctx.service.reviseAudit.getAuditor(revise.id, id, revise.times);
+                const resultData = await ctx.service.reviseAudit.getAuditorsWithOwner(revise.id, revise.times);
                 ctx.body = { err: 0, msg: '', data: resultData };
             } catch (err) {
                 this.log(err, '数据错误');
@@ -846,6 +859,7 @@ module.exports = app => {
                 ctx.body = { err: 0, msg: '', data: {} };
             } catch (err) {
                 this.log(err);
+                ctx.session.postError = err.toString();
                 ctx.body = this.ajaxErrorBody(err, '上报失败');
             }
         }
@@ -875,6 +889,150 @@ module.exports = app => {
                 ctx.redirect('/tender/' + ctx.tender.id + '/revise/info');
             }
         }
+
+        async _getLastStage(ctx) {
+            const stages = await ctx.service.stage.getAllDataByCondition({
+                where: {tid: ctx.tender.id},
+                orders: [['order', 'desc']],
+            });
+            if (stages.length > 0) {
+                if (stages[0].status !== audit.stage.status.uncheck || stages[0].user_id === ctx.session.sessionUser.accountId) {
+                    return stages[0]
+                } else if (stages.length > 1) {
+                    return stages[1];
+                }
+            }
+            return null;
+        }
+
+        async compare(ctx) {
+            const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+            if (!revise) throw '台账修订数据有误';
+
+            const lastStage = await this._getLastStage(ctx);
+
+            const renderData = {
+                revise,
+                measureType,
+                lastStage,
+                audit,
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.revise.compare),
+            };
+            await this.layout('revise/compare.ejs', renderData, 'revise/compare_modal.ejs');
+        }
+
+        _loadPreData(curData, preData, field, prefix, relaId, extraFields = []) {
+            if (preData.length === 0) return;
+            for (const pd of preData) {
+                const cd = curData.find(x => {return x[relaId] === pd[relaId]});
+                if (cd) {
+                    for (const f of field) {
+                        cd[prefix + f] = pd[f];
+                    }
+                } else {
+                    const ncd = {id: pd.id};
+                    ncd[relaId] = pd[relaId];
+                    for (const ef of extraFields) {
+                        ncd[ef] = pd[ef];
+                    }
+                    for (const f of field) {
+                        ncd[prefix + f] = pd[f];
+                    }
+                    curData.push(ncd);
+                }
+            }
+        }
+
+        async _loadLastStageBillsData(ctx) {
+            let curStageData;
+            if (ctx.lastStage.readOnly) {
+                curStageData = await ctx.service.stageBills.getAuditorStageData(ctx.tender.id,
+                    ctx.lastStage.id, ctx.lastStage.curTimes, ctx.lastStage.curOrder);
+            } else {
+                curStageData = await ctx.service.stageBills.getLastestStageData(ctx.tender.id, ctx.lastStage.id);
+            }
+            const preStageData = ctx.lastStage.order > 1
+                ? await ctx.service.stageBillsFinal.getFinalData(ctx.tender.data, ctx.lastStage.order - 1)
+                : [];
+            this._loadPreData(curStageData, preStageData, ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], 'pre_', 'lid');
+            return curStageData;
+        }
+
+        async _loadLastStagePosData(ctx) {
+            let curStageData;
+            if (ctx.lastStage.readOnly) {
+                curStageData = await ctx.service.stagePos.getAuditorStageData2(ctx.tender.id,
+                    ctx.lastStage.id, ctx.lastStage.curTimes, ctx.lastStage.curOrder);
+            } else {
+                curStageData = await ctx.service.stagePos.getLastestStageData2(ctx.tender.id, ctx.lastStage.id);
+            }
+            const preStageData = ctx.lastStage.order > 1
+                ? await ctx.service.stagePosFinal.getFinalData(ctx.tender.data, ctx.lastStage.order - 1)
+                : [];
+            this._loadPreData(curStageData, preStageData, ['contract_qty', 'qc_qty'], 'pre_', 'pid', ['lid']);
+            return curStageData;
+        }
+
+        async _loadDataByFilter(ctx, filter) {
+            switch(filter) {
+                case 'bills': return await ctx.service.ledger.getAllDataByCondition({where: {tender_id: ctx.tender.id} });
+                case 'pos': return await ctx.service.pos.getAllDataByCondition({where: {tid: ctx.tender.id} });
+                case 'reviseBills': return await ctx.service.reviseBills.getAllDataByCondition({where: {tender_id: ctx.tender.id}});
+                case 'revisePos': return await ctx.service.revisePos.getAllDataByCondition({where: {tid: ctx.tender.id}});
+                case 'stageBills':
+                case 'stagePos':
+                    if (!ctx.lastStage) ctx.lastStage = await this._getLastStage(ctx);
+                    await this.ctx.service.stage.doCheckStage(ctx.lastStage);
+                    if (filter === 'stageBills') {
+                        return await this._loadLastStageBillsData(ctx);
+                    } else {
+                        return await this._loadLastStagePosData(ctx);
+                    }
+                case 'dealBills': return await ctx.service.dealBills.getAllDataByCondition({where: {tender_id: ctx.tender.id}});
+            }
+        }
+
+        async loadData(ctx) {
+            try {
+                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+                if (!revise) throw '台账修订数据有误';
+
+                const data = JSON.parse(ctx.request.body.data);
+                const filter = data.filter ? data.filter.split(';') : [];
+
+                const responseData = { err: 0, msg: '', data: {} };
+                for (const f of filter) {
+                    if (!f) continue;
+                    responseData.data[f] = await this._loadDataByFilter(ctx, f);
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                this.ctx.helper.log(err);
+                this.ajaxErrorBody(err, '加载数据错误,请刷新页面重试');
+            }
+        }
+
+        async gclCompare(ctx) {
+            const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+            if (!revise) throw '台账修订数据有误';
+
+            const renderData = {
+                revise,
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.revise.gclCompare),
+            };
+            await this.layout('revise/gcl_compare.ejs', renderData);
+        }
+
+        async bwtz(ctx) {
+            const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+            if (!revise) throw '台账修订数据有误';
+
+            const renderData = {
+                revise,
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.revise.bwtz),
+            };
+            await this.layout('revise/bwtz.ejs', renderData);
+        }
     }
 
     return ReviseController;

+ 104 - 46
app/controller/stage_controller.js

@@ -13,6 +13,7 @@ const auditConst = require('../const/audit').stage;
 const changeAudit = require('../const/audit').flow;
 const spreadConst = require('../const/spread');
 const tenderConst = require('../const/tender');
+const shenpiConst = require('../const/shenpi');
 const payConst = require('../const/deal_pay.js');
 const externalDataConst = require('../const/external_data.js');
 const changeConst = require('../const/change');
@@ -37,7 +38,7 @@ module.exports = app => {
             ctx.showProject = true;
             ctx.showTender = true;
             ctx.showTitle = true;
-            ctx.reUploadPermission = false;
+            // ctx.reUploadPermission = false;
         }
 
         /**
@@ -58,6 +59,7 @@ module.exports = app => {
                     gxby: ctx.session.sessionProject.gxby_status,
                     dagl: ctx.session.sessionProject.dagl_status,
                 },
+                shenpiConst,
             };
             if ((ctx.stage.status === auditConst.status.uncheck || ctx.stage.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ctx.stage.user_id) {
                 // data.accountGroup = accountGroup;
@@ -149,8 +151,11 @@ module.exports = app => {
                 }
             }
             // 获取审批流程中左边列表
-            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.auditors2 = ctx.stage.status === auditConst.status.checkNo && ctx.stage.user_id !== ctx.session.sessionUser.accountId ?
+                await ctx.service.stageAudit.getAuditGroupByListWithOwner(ctx.stage.id, times) :
+                await ctx.service.stageAudit.getAuditGroupByListWithOwner(ctx.stage.id, ctx.stage.times);
+            const { status } = ctx.stage;
+            if (status === auditConst.status.uncheck || status === auditConst.status.checkNo) {
                 ctx.stage.auditorList = await ctx.service.stageAudit.getAuditors(ctx.stage.id, ctx.stage.times);
             }
         }
@@ -333,8 +338,20 @@ module.exports = app => {
             try {
                 const ledgerData = await this._getStageLedgerData(ctx);
                 const posData = await this._getStagePosData(ctx);
-                const data = ctx.helper.checkBillsWithPos(ledgerData, posData, ['contract_qty', 'qc_qty']);
-                ctx.body = { err: 0, msg: '', data };
+
+                const qtyData = ctx.helper.checkBillsWithPos(ledgerData, posData, ['contract_qty', 'qc_qty']);
+                qtyData.error.forEach(x => { x.errorType = 'qty'; });
+                const tpData = ctx.helper.checkBillsTp(ledgerData.filter(x => {return !x.is_tp}), [
+                    { qty: 'contract_qty', tp: 'contract_tp' }, { qty: 'qc_qty', tp: 'qc_tp' },
+                ], this.ctx.tender.info.decimal);
+                tpData.error.forEach(x => { x.errorType = 'tp'; });
+                ctx.body = { err: 0, msg: '', data: {
+                    error: [...qtyData.error, ...tpData.error],
+                    source: {
+                        bills: [...qtyData.source.bills, ...tpData.source.bills],
+                        pos: [...qtyData.source.pos, ...tpData.source.pos],
+                    },
+                } };
             } catch (err) {
                 this.log(err);
                 ctx.body = this.ajaxErrorBody(err, '检查数据错误');
@@ -961,14 +978,14 @@ module.exports = app => {
                 if (exist) {
                     throw '该审核人已存在,请勿重复添加';
                 }
-
-                const result = await ctx.service.stageAudit.addAuditor(ctx.stage.id, id, ctx.stage.times);
+                const is_gdzs = ctx.tender.info.shenpi.stage === shenpiConst.sp_status.gdzs ? 1 : 0;
+                const result = await ctx.service.stageAudit.addAuditor(ctx.stage.id, id, ctx.stage.times, is_gdzs);
                 if (!result) {
                     throw '添加审核人失败';
                 }
 
-                const audit = await ctx.service.stageAudit.getAuditor(ctx.stage.id, id, ctx.stage.times);
-                ctx.body = { err: 0, msg: '', data: audit };
+                const auditors = await ctx.service.stageAudit.getAuditGroupByListWithOwner(ctx.stage.id, ctx.stage.times);
+                ctx.body = { err: 0, msg: '', data: auditors };
             } catch (err) {
                 this.log(err);
                 ctx.body = { err: 1, msg: err.toString(), data: null };
@@ -1016,10 +1033,11 @@ 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);
                 ctx.body = { err: 0, msg: '', data: [] };
             } catch (err) {
                 this.log(err);
+                ctx.session.postError = err.toString();
+                // ctx.redirect(ctx.request.header.referer);
                 ctx.body = this.ajaxErrorBody(err, '上报失败');
             }
         }
@@ -1053,20 +1071,11 @@ module.exports = app => {
                 }
 
                 await ctx.service.stageAudit.check(ctx.stage.id, data, ctx.stage.times);
-                const stageOrder = parseInt(ctx.params.order);
-                const stage = await ctx.service.stage.getDataByCondition({
-                    tid: ctx.tender.id,
-                    order: stageOrder,
-                });
-                const auditor = await ctx.service.stageAudit.getCurAuditor(ctx.stage.id, ctx.stage.times);
                 ctx.body = { err: 0, msg: '', data: [] };
-                // ctx.redirect(ctx.request.header.referer);
             } catch (err) {
-                // console.log(err);
                 this.log(err);
                 ctx.body = this.ajaxErrorBody(err, '提交失败');
-                // ctx.session.postError = err.toString();
-                // ctx.redirect(ctx.request.header.referer);
+                ctx.session.postError = err.toString();
             }
         }
         /**
@@ -1257,27 +1266,27 @@ module.exports = app => {
          * 检查当前期当前用户是否在审核列表中,如果是的话允许再次上传附件
          * @param {Object} ctx 上下文
          */
-        _checkStageCanModifyRe(ctx) {
-            // 检查登录用户,是否可操作
-            if (ctx.stage.readOnly) {
-                if (ctx.stage.status === auditConst.status.checked) {
-                    // 当前期状态为完成,且提交人是审核列表中的则可再次上传
-                    if (ctx.stage.user_id === ctx.session.sessionUser.accountId || ctx.stage.auditors.findIndex(auditor => auditor.aid === ctx.session.sessionUser.accountId) !== -1) {
-                        // 再次上传的图片要给个标识,方便给前端进行编辑操作
-                        ctx.reUploadPermission = true;
-                        return;
-                    }
-
-                    throw '该计量期当前您无权操作';
-
-                } else {
-                    throw '该计量期当前您无权操作';
-                }
-            }
-            if (ctx.stage.revising) {
-                throw '台账修订中,请勿修改提交期数据';
-            }
-        }
+        // _checkStageCanModifyRe(ctx) {
+        //     // 检查登录用户,是否可操作
+        //     if (ctx.stage.readOnly) {
+        //         if (ctx.stage.status === auditConst.status.checked) {
+        //             // 当前期状态为完成,且提交人是审核列表中的则可再次上传
+        //             if (ctx.stage.user_id === ctx.session.sessionUser.accountId || ctx.stage.auditors.findIndex(auditor => auditor.aid === ctx.session.sessionUser.accountId) !== -1) {
+        //                 // 再次上传的图片要给个标识,方便给前端进行编辑操作
+        //                 ctx.reUploadPermission = true;
+        //                 return;
+        //             }
+
+        //             throw '该计量期当前您无权操作';
+
+        //         } else {
+        //             throw '该计量期当前您无权操作';
+        //         }
+        //     }
+        //     if (ctx.stage.revising) {
+        //         throw '台账修订中,请勿修改提交期数据';
+        //     }
+        // }
         /**
          * 上传附件
          * @param {Object} ctx - egg全局变量
@@ -1291,11 +1300,12 @@ module.exports = app => {
             };
             let stream;
             try {
-                this._checkStageCanModifyRe(ctx);
+                // this._checkStageCanModifyRe(ctx);
 
                 const parts = ctx.multipart({ autoFields: true });
                 const files = [];
                 let index = 0;
+                const extra_upload = ctx.stage.status === auditConst.status.checked;
                 while ((stream = await parts()) !== undefined) {
                     // 判断用户是否选择上传文件
                     if (!stream.filename) {
@@ -1329,10 +1339,11 @@ module.exports = app => {
                         fileext: fileInfo.ext,
                         filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size,
                         filepath,
+                        extra_upload,
                     };
-                    if (ctx.reUploadPermission) {
-                        fileData.re_upload = 1;
-                    }
+                    // if (ctx.reUploadPermission) {
+                    //     fileData.re_upload = 1;
+                    // }
                     const result = await ctx.service.stageAtt.save(parts.field, fileData, ctx.session.sessionUser.accountId);
                     if (!result) {
                         throw '导入数据库保存失败';
@@ -1390,6 +1401,7 @@ module.exports = app => {
                     }
                 } catch (err) {
                     this.log(err);
+                    console.log(err);
                     this.setMessage(err.toString(), this.messageType.ERROR);
                 }
             }
@@ -1437,10 +1449,16 @@ module.exports = app => {
                 data: '',
             };
             try {
-                this._checkStageCanModifyRe(ctx);
+                // this._checkStageCanModifyRe(ctx);
 
                 const data = JSON.parse(ctx.request.body.data);
                 const fileInfo = await ctx.service.stageAtt.getDataById(data.id);
+                if (!fileInfo || !Object.keys(fileInfo).length) {
+                    throw '该文件不存在';
+                }
+                if (!fileInfo.extra_upload && ctx.stage.status === auditConst.status.checked) {
+                    throw '无权限删除';
+                }
                 if (fileInfo !== undefined && fileInfo !== '') {
                     // 先删除文件
                     await fs.unlinkSync(path.join(this.app.baseDir, fileInfo.filepath));
@@ -1519,6 +1537,46 @@ module.exports = app => {
         }
 
         /**
+         * 批量下载 - 压缩成zip文件返回
+         * @param {Object} ctx - 全局上下文
+         */
+        async downloadZip(ctx) {
+            try {
+                const fileIds = JSON.parse(ctx.request.query.fileIds);
+                const zipFilename = `${ctx.tender.data.name}-计量台账-${ctx.params.order}-附件.zip`;
+                const time = Date.now();
+                const zipPath = `app/public/upload/${ctx.tender.id}/stage/fu_jian_zip${time}.zip`;
+                const size = await ctx.service.stageAtt.compressedFile(fileIds, zipPath);
+
+                // 解决中文无法下载问题
+                const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
+                let disposition = '';
+                if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
+                    disposition = 'attachment; filename=' + encodeURIComponent(zipFilename);
+                } else if (userAgent.indexOf('firefox') >= 0) {
+                    disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(zipFilename) + '"';
+                } else {
+                    /* safari等其他非主流浏览器只能自求多福了 */
+                    disposition = 'attachment; filename=' + new Buffer(zipFilename).toString('binary');
+                }
+                ctx.response.set({
+                    'Content-Type': 'application/octet-stream',
+                    'Content-Disposition': disposition,
+                    'Content-Length': size,
+                });
+                const readStream = fs.createReadStream(path.join(this.app.baseDir, zipPath));
+                ctx.body = readStream;
+                readStream.on('close', () => {
+                    if (fs.existsSync(path.resolve(this.app.baseDir, zipPath))) {
+                        fs.unlinkSync(path.resolve(this.app.baseDir, zipPath));
+                    }
+                });
+            } catch (error) {
+                this.log(error);
+            }
+
+        }
+        /**
          * 合同支付上传附件
          * @param {Object} ctx - egg全局变量
          * @return {void}

+ 138 - 0
app/controller/tender_controller.js

@@ -13,6 +13,8 @@ const codeRuleConst = require('../const/code_rule');
 const settingConst = require('../const/setting.js');
 const tenderMenu = require('../../config/menu').tenderMenu;
 const auditConst = require('../const/audit');
+const shenpiConst = require('../const/shenpi');
+const accountGroup = require('../const/account_group').group;
 const accountPermission = require('../const/account_permission');
 
 module.exports = app => {
@@ -423,8 +425,10 @@ module.exports = app => {
                     p.end_tp = sum;
                     p.end_ratio = ctx.helper.mul(ctx.helper.div(p.end_tp, tender.sum, 4), 100);
                 }
+                const revise = await ctx.service.ledgerRevise.getLastestRevise(tender.id);
                 const renderData = {
                     tender,
+                    revise,
                     tenderInfo: ctx.tender.info,
                     tenderMenu: this.menu.tenderMenu,
                     preUrl: '/tender/' + ctx.tender.id,
@@ -736,6 +740,140 @@ module.exports = app => {
 
             ctx.body = responseData;
         }
+
+        async shenpiSet(ctx) {
+            if (ctx.session.sessionUser.is_admin === 0) {
+                ctx.request.headers.referer ? ctx.redirect(ctx.request.headers.referer) : ctx.redirect('/list');
+            }
+            // 获取所有项目参与者
+            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'],
+            });
+            const accountGroupList = accountGroup.map((item, idx) => {
+                const groupList = accountList.filter(item => item.account_group === idx);
+                return { groupName: item, groupList };
+            });
+            // 获取固定审批流 or 固定终审
+            for (const sp of shenpiConst.sp_lc) {
+                sp.status = ctx.tender.info.shenpi ? ctx.tender.info.shenpi[sp.code] : shenpiConst.sp_status.sqspr;
+                if (sp.status === shenpiConst.sp_status.gdspl) {
+                    sp.auditList = await ctx.service.shenpiAudit.getAuditList(ctx.tender.id, sp.type, sp.status);
+                } else if (sp.status === shenpiConst.sp_status.gdzs) {
+                    sp.audit = await ctx.service.shenpiAudit.getAudit(ctx.tender.id, sp.type, sp.status);
+                }
+            }
+            const tenders = await ctx.service.tender.getList('', null, 1);
+            const removeTenders = [];
+            for (const tender of tenders) {
+                const shenpiInfo = await ctx.service.tenderInfo.getTenderShenpiInfo(tender.id);
+                if (!shenpiInfo) {
+                    removeTenders.push(tender.id);
+                } else {
+                    tender.shenpiInfo = shenpiInfo;
+                    // 获取所有的固定审批流或固定终审
+                    const shenpiauditList = {};
+                    for (const shenpi in tender.shenpiInfo) {
+                        if (tender.shenpiInfo[shenpi] === shenpiConst.sp_status.gdspl) {
+                            const shenpiList = await ctx.service.shenpiAudit.getAllDataByCondition({ where: { tid: tender.id, sp_type: shenpiConst.sp_type[shenpi], sp_status: tender.shenpiInfo[shenpi] } });
+                            const shenpiIdList = ctx.helper._.map(shenpiList, 'audit_id');
+                            shenpiauditList[shenpi] = shenpiIdList.length ? shenpiIdList : null;
+                        } else if (tender.shenpiInfo[shenpi] === shenpiConst.sp_status.gdzs) {
+                            const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition({ tid: tender.id, sp_type: shenpiConst.sp_type[shenpi], sp_status: tender.shenpiInfo[shenpi] });
+                            shenpiauditList[shenpi] = shenpiInfo && shenpiInfo.audit_id ? [shenpiInfo.audit_id] : null;
+                        }
+                    }
+                    tender.shenpiauditList = shenpiauditList;
+                }
+            }
+            if (removeTenders.length > 0) {
+                ctx.helper._.remove(tenders, function(n) {
+                    return removeTenders.indexOf(n.id) !== -1;
+                });
+            }
+            const categoryData = await ctx.service.category.getAllCategory(ctx.session.sessionProject.id);
+            const renderData = {
+                shenpi: shenpiConst,
+                accountList,
+                accountGroup: accountGroupList,
+                tenders,
+                categoryData,
+            };
+            await this._list('tender/shenpi.ejs', renderData, 'tender/shenpi_modal.ejs');
+        }
+
+        async saveTenderInfoShenpi(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data) {
+                    throw '提交数据错误';
+                }
+                // 判断修改权限
+                if (ctx.session.sessionUser.is_admin === 0) {
+                    throw '你没有权限修改审批流程';
+                }
+                // let postData = {};
+                // if (!ctx.tender.info.shenpi) {
+                //     for (const sp of shenpiConst.sp_lc) {
+                //         if (sp.code === data.code) {
+                //             postData[sp.code] = data.status;
+                //         } else {
+                //             postData[sp.code] = shenpiConst.sp_status.sqspr;
+                //         }
+                //     }
+                // } else {
+                const postData = ctx.tender.info.shenpi;
+                postData[data.code] = data.status;
+                // }
+                // console.log(postData);
+                await ctx.service.tenderInfo.saveTenderInfo(ctx.tender.id, { shenpi: postData });
+                let auditList = [];
+                if (data.status === shenpiConst.sp_status.gdspl) {
+                    auditList = await ctx.service.shenpiAudit.getAuditList(ctx.tender.id, shenpiConst.sp_type[data.code], data.status);
+                } else if (data.status === shenpiConst.sp_status.gdzs) {
+                    auditList = await ctx.service.shenpiAudit.getAudit(ctx.tender.id, shenpiConst.sp_type[data.code], data.status);
+                }
+                ctx.body = { err: 0, msg: '', data: auditList };
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '保存审批流程设置失败');
+            }
+        }
+
+        async saveShenpiAudit(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data) {
+                    throw '提交数据错误';
+                }
+                // 判断修改权限
+                if (ctx.session.sessionUser.is_admin === 0) {
+                    throw '你没有权限修改审批流程';
+                }
+                switch (data.type) {
+                    case 'add':
+                        const result = await ctx.service.shenpiAudit.addAudit(data);
+                        if (result) {
+                            throw '添加审批人失败';
+                        }
+                        break;
+                    case 'del':
+                        await ctx.service.shenpiAudit.removeAudit(data);
+                        break;
+                    case 'copy2ot':
+                        await ctx.service.shenpiAudit.copyAudit2otherTender(data);
+                        break;
+                    case 'copy2os':
+                        await ctx.service.shenpiAudit.copyAudit2otherShenpi(data);
+                        break;
+                    default:break;
+                }
+                ctx.body = { err: 0, msg: '' };
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '保存审批流程设置失败');
+            }
+        }
     }
 
     return TenderController;

+ 15 - 0
app/extend/context.js

@@ -0,0 +1,15 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const moment = require('moment');
+
+module.exports = {
+    moment: moment,
+};

+ 27 - 0
app/extend/helper.js

@@ -1176,6 +1176,33 @@ module.exports = {
         return result;
     },
 
+    checkBillsTp(bills, field, decimal) {
+        const result = {
+            error: [],
+            source: {
+                bills: [],
+                pos: [],
+            },
+        };
+        for (const b of bills) {
+            const checkData = {}, calcData = {};
+            for (const f of field) {
+                checkData[f.tp] = b[f.tp] || 0;
+                calcData[f.tp] = this.mul(b.unit_price, b[f.qty], decimal.tp) || 0;
+            }
+            if (!this._.isMatch(checkData, calcData)) {
+                result.error.push({
+                    ledger_id: b.ledger_id,
+                    b_code: b.b_code,
+                    name: b.name,
+                    error: { checkData, calcData },
+                });
+                result.source.bills.push(b);
+            }
+        }
+        return result;
+    },
+
     check18MainCode(code) {
         return /^([0-9]([0-9][0-9])*)?(GD[0-9]{3}([0-9][0-9])*)?$/.test(code);
     },

+ 6 - 1
app/lib/analysis_excel.js

@@ -281,7 +281,11 @@ class ImportBaseTree {
 
     calculateLeafWithPos () {
         for (const node of this.items) {
-            if (node.children && node.children.length > 0) { continue; }
+            if (node.children && node.children.length > 0) {
+                node.unit_price = null;
+                node.quantity = null;
+                node.total_price = null;
+            }
             if (!node.pos || node.pos.length === 0) { continue; }
             node.quantity = this.ctx.helper.sum(_.map(node.pos, 'quantity'));
             if (node.quantity && node.unit_price) {
@@ -353,6 +357,7 @@ class AnalysisExcelTree {
         node.unit = this.ctx.helper.replaceReturn(row[this.colsDef.unit]);
         const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, node.unit);
         node.quantity = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.quantity]), precision.value);
+        node.sgfh_qty = node.quantity;
         node.unit_price = aeUtils.toNumber(row[this.colsDef.unit_price]);
         node.drawing_code = this.ctx.helper.replaceReturn(row[this.colsDef.drawing_code]);
         node.memo = this.ctx.helper.replaceReturn(row[this.colsDef.memo]);

+ 2 - 2
app/lib/wechat.js

@@ -95,7 +95,7 @@ class WX {
                             (data.status === wxConst.status.success ? '审批已通过,查看审批结果' : '审批被退回,查看退回结果');
                         msgData = {
                             first: {
-                                value: '您好,台' + data.tips,
+                                value: '您好,台' + data.tips,
                             },
                             keyword1: {
                                 value: data.projectName,
@@ -118,7 +118,7 @@ class WX {
                                 (data.status === wxConst.status.back ? '审批被退回,查看退回结果' : '审批已上报,查看审批结果'));
                         msgData = {
                             first: {
-                                value: '您好,台修订' + data.tips,
+                                value: '您好,台修订' + data.tips,
                             },
                             keyword1: {
                                 value: data.projectName,

+ 26 - 0
app/middleware/advance_check.js

@@ -8,6 +8,8 @@
  */
 
 const status = require('../const/audit').advance.status;
+const shenpiConst = require('../const/shenpi');
+const _ = require('lodash');
 // const _ = require('lodash')
 
 module.exports = () => {
@@ -50,6 +52,30 @@ module.exports = () => {
             // advance.highOrder = yield this.service.advance.getLastestAdvance(this.tender.id, type, true)
 
             this.advance = advance;
+            // 根据状态判断是否需要更新审批人列表
+            if ((advance.status === status.uncheck || advance.status === status.checkNo) && this.tender.info.shenpi.advance !== shenpiConst.sp_status.sqspr) {
+                const shenpi_status = this.tender.info.shenpi.advance;
+                // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
+                const auditList = yield this.service.advanceAudit.getAllDataByCondition({ where: { vid: advance.id, times: advance.times } });
+                const auditIdList = _.map(auditList, 'audit_id');
+                if (shenpi_status === shenpiConst.sp_status.gdspl) {
+                    const shenpiList = yield this.service.shenpiAudit.getAllDataByCondition({ where: { tid: advance.tid, sp_type: shenpiConst.sp_type.advance, sp_status: shenpi_status } });
+                    const shenpiIdList = _.map(shenpiList, 'audit_id');
+                    // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
+                    if (!_.isEqual(auditIdList, shenpiIdList)) {
+                        yield this.service.advanceAudit.updateNewAuditList(advance, shenpiIdList);
+                    }
+                } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
+                    const shenpiInfo = yield this.service.shenpiAudit.getDataByCondition({ tid: advance.tid, sp_type: shenpiConst.sp_type.advance, sp_status: shenpi_status });
+                    // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
+                    if (shenpiInfo && shenpiInfo.audit_id !== _.last(auditIdList)) {
+                        yield this.service.advanceAudit.updateLastAudit(advance, auditList, shenpiInfo.audit_id);
+                    } else if (!shenpiInfo) {
+                        // 不存在终审人的状态下这里恢复为授权审批人
+                        this.tender.info.shenpi.advance = shenpiConst.sp_status.sqspr;
+                    }
+                }
+            }
             yield next;
         } catch (err) {
             this.helper.log(err);

+ 75 - 0
app/middleware/change_audit_check.js

@@ -0,0 +1,75 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Ellisran
+ * @date 2020/10/15
+ * @version
+ */
+
+const status = require('../const/audit').flow.status;
+const shenpiConst = require('../const/shenpi');
+const _ = require('lodash');
+
+module.exports = options => {
+    /**
+     * 标段校验 中间件
+     * 1. 读取标段数据(包括属性)
+     * 2. 检验用户是否可见标段(不校验具体权限)
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* changeAuditCheck(next) {
+        try {
+            // 获取revise
+            const cid = this.params.cid || this.request.body.cid;
+            if (!cid) {
+                throw '您访问的变更令不存在';
+            }
+            const change = yield this.service.change.getDataByCondition({ cid });
+            if (!change) throw '变更令数据有误';
+            if ((change.status === status.uncheck || change.status === status.back) && this.tender.info.shenpi.change !== shenpiConst.sp_status.sqspr) {
+                const shenpi_status = this.tender.info.shenpi.change;
+                // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
+                const auditList = yield this.service.changeAudit.getAllDataByCondition({ where: { cid: change.cid, times: change.times } });
+                auditList.shift();
+                const auditIdList = _.map(auditList, 'uid');
+                if (shenpi_status === shenpiConst.sp_status.gdspl) {
+                    const shenpiList = yield this.service.shenpiAudit.getAllDataByCondition({ where: { tid: this.tender.id, sp_type: shenpiConst.sp_type.change, sp_status: shenpi_status } });
+                    const shenpiIdList = _.map(shenpiList, 'audit_id');
+                    // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
+                    if (!_.isEqual(auditIdList, shenpiIdList)) {
+                        yield this.service.changeAudit.updateNewAuditList(change, shenpiIdList);
+                    }
+                } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
+                    const shenpiInfo = yield this.service.shenpiAudit.getDataByCondition({ tid: this.tender.id, sp_type: shenpiConst.sp_type.change, sp_status: shenpi_status });
+                    // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
+                    if (shenpiInfo && shenpiInfo.audit_id !== _.last(auditIdList)) {
+                        yield this.service.changeAudit.updateLastAudit(change, auditList, shenpiInfo.audit_id);
+                    } else if (!shenpiInfo) {
+                        // 不存在终审人的状态下这里恢复为授权审批人
+                        this.tender.info.shenpi.change = shenpiConst.sp_status.sqspr;
+                    }
+                }
+            }
+            yield next;
+        } catch (err) {
+            console.log(err);
+            // 输出错误到日志
+            if (err.stack) {
+                this.logger.error(err);
+            } else {
+                this.getLogger('fail').info(JSON.stringify({
+                    error: err,
+                    project: this.session.sessionProject,
+                    user: this.session.sessionUser,
+                    body: this.session.body,
+                }));
+            }
+            // 重定向值标段管理
+            this.redirect(this.request.headers.referer);
+        }
+    };
+};

+ 67 - 0
app/middleware/ledger_audit_check.js

@@ -0,0 +1,67 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Ellisran
+ * @date 2020/10/15
+ * @version
+ */
+
+const status = require('../const/audit').ledger.status;
+const shenpiConst = require('../const/shenpi');
+const _ = require('lodash');
+
+module.exports = options => {
+    /**
+     * 标段校验 中间件
+     * 1. 读取标段数据(包括属性)
+     * 2. 检验用户是否可见标段(不校验具体权限)
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* ledgerAuditCheck(next) {
+        try {
+            if ((this.tender.data.ledger_status === status.uncheck || this.tender.data.ledger_status === status.checkNo) && this.tender.info.shenpi.ledger !== shenpiConst.sp_status.sqspr) {
+                const shenpi_status = this.tender.info.shenpi.ledger;
+                // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
+                const auditList = yield this.service.ledgerAudit.getAllDataByCondition({ where: { tender_id: this.tender.id, times: this.tender.data.ledger_times } });
+                const auditIdList = _.map(auditList, 'audit_id');
+                if (shenpi_status === shenpiConst.sp_status.gdspl) {
+                    const shenpiList = yield this.service.shenpiAudit.getAllDataByCondition({ where: { tid: this.tender.id, sp_type: shenpiConst.sp_type.ledger, sp_status: shenpi_status } });
+                    const shenpiIdList = _.map(shenpiList, 'audit_id');
+                    // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
+                    if (!_.isEqual(auditIdList, shenpiIdList)) {
+                        yield this.service.ledgerAudit.updateNewAuditList(this.tender.data, shenpiIdList);
+                    }
+                } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
+                    const shenpiInfo = yield this.service.shenpiAudit.getDataByCondition({ tid: this.tender.id, sp_type: shenpiConst.sp_type.ledger, sp_status: shenpi_status });
+                    // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
+                    if (shenpiInfo && shenpiInfo.audit_id !== _.last(auditIdList)) {
+                        yield this.service.ledgerAudit.updateLastAudit(this.tender.data, auditList, shenpiInfo.audit_id);
+                    } else if (!shenpiInfo) {
+                        // 不存在终审人的状态下这里恢复为授权审批人
+                        this.tender.info.shenpi.ledger = shenpiConst.sp_status.sqspr;
+                    }
+                }
+            }
+            yield next;
+        } catch (err) {
+            console.log(err);
+            // 输出错误到日志
+            if (err.stack) {
+                this.logger.error(err);
+            } else {
+                this.getLogger('fail').info(JSON.stringify({
+                    error: err,
+                    project: this.session.sessionProject,
+                    user: this.session.sessionUser,
+                    body: this.session.body,
+                }));
+            }
+            // 重定向值标段管理
+            this.redirect(this.request.headers.referer);
+        }
+    };
+};

+ 25 - 0
app/middleware/material_check.js

@@ -8,6 +8,7 @@
  */
 
 const status = require('../const/audit').material.status;
+const shenpiConst = require('../const/shenpi');
 const _ = require('lodash');
 
 module.exports = options => {
@@ -103,6 +104,30 @@ module.exports = options => {
             // 调差的readOnly 指表格和页面只能看不能改,和审批无关
             material.readOnly = !((material.status === status.uncheck || material.status === status.checkNo) && accountId === material.user_id);
             this.material = material;
+            // 根据状态判断是否需要更新审批人列表
+            if ((material.status === status.uncheck || material.status === status.checkNo) && this.tender.info.shenpi.material !== shenpiConst.sp_status.sqspr) {
+                const shenpi_status = this.tender.info.shenpi.material;
+                // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
+                const auditList = yield this.service.materialAudit.getAllDataByCondition({ where: { mid: material.id, times: material.times } });
+                const auditIdList = _.map(auditList, 'aid');
+                if (shenpi_status === shenpiConst.sp_status.gdspl) {
+                    const shenpiList = yield this.service.shenpiAudit.getAllDataByCondition({ where: { tid: material.tid, sp_type: shenpiConst.sp_type.material, sp_status: shenpi_status } });
+                    const shenpiIdList = _.map(shenpiList, 'audit_id');
+                    // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
+                    if (!_.isEqual(auditIdList, shenpiIdList)) {
+                        yield this.service.materialAudit.updateNewAuditList(material, shenpiIdList);
+                    }
+                } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
+                    const shenpiInfo = yield this.service.shenpiAudit.getDataByCondition({ tid: material.tid, sp_type: shenpiConst.sp_type.material, sp_status: shenpi_status });
+                    // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
+                    if (shenpiInfo && shenpiInfo.audit_id !== _.last(auditIdList)) {
+                        yield this.service.materialAudit.updateLastAudit(material, auditList, shenpiInfo.audit_id);
+                    } else if (!shenpiInfo) {
+                        // 不存在终审人的状态下这里恢复为授权审批人
+                        this.tender.info.shenpi.material = shenpiConst.sp_status.sqspr;
+                    }
+                }
+            }
             yield next;
         } catch (err) {
             console.log(err);

+ 70 - 0
app/middleware/revise_audit_check.js

@@ -0,0 +1,70 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Ellisran
+ * @date 2020/10/15
+ * @version
+ */
+
+const status = require('../const/audit').revise.status;
+const shenpiConst = require('../const/shenpi');
+const _ = require('lodash');
+
+module.exports = options => {
+    /**
+     * 标段校验 中间件
+     * 1. 读取标段数据(包括属性)
+     * 2. 检验用户是否可见标段(不校验具体权限)
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* reviseAuditCheck(next) {
+        try {
+            // 获取revise
+            const revise = yield this.service.ledgerRevise.getLastestRevise(this.tender.id);
+            if (!revise) throw '台账修订数据有误';
+            if ((revise.status === status.uncheck || revise.status === status.checkNo) && this.tender.info.shenpi.revise !== shenpiConst.sp_status.sqspr) {
+                const shenpi_status = this.tender.info.shenpi.revise;
+                // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
+                const auditList = yield this.service.reviseAudit.getAllDataByCondition({ where: { tender_id: this.tender.id, times: revise.times } });
+                const auditIdList = _.map(auditList, 'audit_id');
+                if (shenpi_status === shenpiConst.sp_status.gdspl) {
+                    const shenpiList = yield this.service.shenpiAudit.getAllDataByCondition({ where: { tid: this.tender.id, sp_type: shenpiConst.sp_type.revise, sp_status: shenpi_status } });
+                    const shenpiIdList = _.map(shenpiList, 'audit_id');
+                    // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
+                    if (!_.isEqual(auditIdList, shenpiIdList)) {
+                        yield this.service.reviseAudit.updateNewAuditList(revise, shenpiIdList);
+                    }
+                } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
+                    const shenpiInfo = yield this.service.shenpiAudit.getDataByCondition({ tid: this.tender.id, sp_type: shenpiConst.sp_type.revise, sp_status: shenpi_status });
+                    // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
+                    if (shenpiInfo && shenpiInfo.audit_id !== _.last(auditIdList)) {
+                        yield this.service.reviseAudit.updateLastAudit(revise, auditList, shenpiInfo.audit_id);
+                    } else if (!shenpiInfo) {
+                        // 不存在终审人的状态下这里恢复为授权审批人
+                        this.tender.info.shenpi.revise = shenpiConst.sp_status.sqspr;
+                    }
+                }
+            }
+            yield next;
+        } catch (err) {
+            console.log(err);
+            // 输出错误到日志
+            if (err.stack) {
+                this.logger.error(err);
+            } else {
+                this.getLogger('fail').info(JSON.stringify({
+                    error: err,
+                    project: this.session.sessionProject,
+                    user: this.session.sessionUser,
+                    body: this.session.body,
+                }));
+            }
+            // 重定向值标段管理
+            this.redirect(this.request.headers.referer);
+        }
+    };
+};

+ 25 - 0
app/middleware/stage_check.js

@@ -9,6 +9,7 @@
 
 const status = require('../const/audit').stage.status;
 const reviseStatus = require('../const/audit').revise.status;
+const shenpiConst = require('../const/shenpi');
 const _ = require('lodash');
 
 module.exports = options => {
@@ -114,6 +115,30 @@ module.exports = options => {
             const lastRevise = yield this.service.ledgerRevise.getLastestRevise(this.tender.id);
             stage.revising = (lastRevise && lastRevise.status !== reviseStatus.checked) || false;
             this.stage = stage;
+            // 根据状态判断是否需要更新审批人列表
+            if ((stage.status === status.uncheck || stage.status === status.checkNo) && this.tender.info.shenpi.stage !== shenpiConst.sp_status.sqspr) {
+                const shenpi_status = this.tender.info.shenpi.stage;
+                // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
+                const auditList = yield this.service.stageAudit.getAllDataByCondition({ where: { sid: stage.id, times: stage.times } });
+                const auditIdList = _.map(auditList, 'aid');
+                if (shenpi_status === shenpiConst.sp_status.gdspl) {
+                    const shenpiList = yield this.service.shenpiAudit.getAllDataByCondition({ where: { tid: stage.tid, sp_type: shenpiConst.sp_type.stage, sp_status: shenpi_status } });
+                    const shenpiIdList = _.map(shenpiList, 'audit_id');
+                    // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
+                    if (!_.isEqual(auditIdList, shenpiIdList)) {
+                        yield this.service.stageAudit.updateNewAuditList(stage, shenpiIdList);
+                    }
+                } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
+                    const shenpiInfo = yield this.service.shenpiAudit.getDataByCondition({ tid: stage.tid, sp_type: shenpiConst.sp_type.stage, sp_status: shenpi_status });
+                    // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
+                    if (shenpiInfo && shenpiInfo.audit_id !== _.last(auditIdList)) {
+                        yield this.service.stageAudit.updateLastAudit(stage, auditList, shenpiInfo.audit_id);
+                    } else if (!shenpiInfo) {
+                        // 不存在终审人的状态下这里恢复为授权审批人
+                        this.tender.info.shenpi.stage = shenpiConst.sp_status.sqspr;
+                    }
+                }
+            }
             yield next;
         } catch (err) {
             this.helper.log(err);

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

@@ -90,6 +90,60 @@ font-size: .875rem;
 .group-tab .btn-light.active{
   cursor: default;
 }
+.text-info-50{
+  color: #bee5eb;
+}
+.text-danger-50{
+  color: #ed969e;
+}
+.text-success-50{
+  color: #c3e6cb;
+}
+.text-primary-50{
+  color: #cce5ff;
+}
+.text-warning-50{
+  color: #ffddc5;
+}
+.text-secondary-50{
+  color: #dcdcdc;
+}
+.bg-info-50{
+  background-color: #d0f6fd;
+}
+.bg-danger-50{
+  background-color: #f8d7da;
+}
+.bg-success-50{
+  background-color: #d4edda;
+}
+.bg-primary-50{
+  background-color: #cce5ff;
+}
+.bg-warning-50{
+  background-color: #ffddc5;
+}
+.bg-secondary-50{
+  background-color: #dcdcdc;
+}
+.border-info-50{
+  border:1px solid #9be6f4;
+}
+.border-danger-50{
+  border:1px solid #f5c6cb;
+}
+.border-success-50{
+  border:1px solid #c3e6cb;
+}
+.border-primary-50{
+  border:1px solid #b8daff;
+}
+.border-warning-50{
+  border:1px solid #fbc7a3;
+}
+.border-secondary-50{
+  border:1px solid #ccc;
+}
 /*在谷歌下移除input[number]的上下箭头*/
 input.nospin[type='number']::-webkit-outer-spin-button,
 input.nospin[type='number']::-webkit-inner-spin-button{
@@ -108,23 +162,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;
@@ -133,7 +187,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;
@@ -156,7 +210,7 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
   margin-top:-3px;
 }
 .sjs-bottom{
-  height:400px;
+  height:250px;
 }
 .sjs-bottom-2{
   height:360px;
@@ -705,7 +759,7 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
   font-size: 14px
 }
 .bd-toc {
-
+  
     position: sticky;
     top:3rem;
     height: calc(100vh - 10rem);
@@ -860,7 +914,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;
@@ -1083,7 +1137,7 @@ a.maintain-icon .fa{
     }
 }
 
-a.maintain-icon:hover .fa{
+a.maintain-icon:hover .fa{ 
     animation-iteration-count:0
 }
 /*审批列表*/
@@ -1146,3 +1200,9 @@ overflow-y: auto;
 .fold-card {
   display: none;
 }
+.att-file-btn {
+  display: none;
+}
+.list-table tr:hover .att-file-btn{
+  display: inline-block;
+}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1208 - 0
app/public/css/main_s.css


+ 51 - 21
app/public/js/advance_audit.js

@@ -93,25 +93,47 @@ $(document).ready(function () {
     $('dl').on('click', 'dd', function () {
         const id = parseInt($(this).data('id'))
         if (id !== 0) {
-            postData(preUrl + '/audit/add', { auditorId: id }, (data) => {
+            postData(preUrl + '/audit/add', { auditorId: id }, (datas) => {
                 // <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 html = [];
+                // 如果是重新上报,添加到重新上报列表中
+                const auditorshtml = [];
+                for (const [index,data] of datas.entries()) {
+                    if (index !== 0) {
+                        html.push('<li class="list-group-item" auditorId="'+ data.audit_id +'">');
+                        if (shenpi_status === shenpiConst.sp_status.sqspr || (shenpi_status === shenpiConst.sp_status.gdzs && index+1 !== datas.length)) {
+                            html.push('<a href="javascript: void(0)" class="text-danger pull-right">移除</a>');
+                        }
+                        html.push('<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>`);
+                    }
+                    // 添加新审批人流程修改
+                    auditorshtml.push('<li class="list-group-item" data-auditorid="' + data.audit_id + '">');
+                    auditorshtml.push('<i class="fa ' + (index+1 === datas.length ? 'fa-stop-circle' : 'fa-chevron-circle-down') + '"></i> ');
+                    auditorshtml.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
+                    if (index === 0) {
+                        auditorshtml.push('<span class="pull-right">原报</span>');
+                    } else if (index+1 === datas.length) {
+                        auditorshtml.push('<span class="pull-right">终审</span>');
+                    } else {
+                        auditorshtml.push('<span class="pull-right">'+ transFormToChinese(index) +'审</span>');
+                    }
+                    auditorshtml.push('</li>');
                 }
-                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)
+                $('#auditors').html(html.join(''));
+                $('#auditors2').html(auditorshtml.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)
@@ -133,9 +155,16 @@ $(document).ready(function () {
                 $('span', aLi).text(rst.order + ' ' + rst.name + ' ')
             }
             // 删除左边审核人
-            $(`#auditors2 li[data-auditorId='${data.auditorId}']`).remove()
+            $(`#auditors2 li[data-auditorid='${data.auditorId}']`).remove();
+            if ($('#auditors2 li').length !== 0 && !$('#auditors2 li i').hasClass('fa-stop-circle')) {
+                $('#auditors2 li').eq($('#auditors2 li').length-1).children('i')
+                    .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
+            }
+            for (let i = 0; i < $('#auditors2 li').length; i++) {
+                $('#auditors2 li').eq(i).find('.pull-right').text(i === 0 ? '原报' : (i+1 === $('#auditors2 li').length ? '终' : transFormToChinese(i)) + '审');
+            }
         })
-    })
+    });
 
     $('#au-btn').on('click','a', function() {
         const content = $(this).data('target')
@@ -213,11 +242,12 @@ $(document).ready(function () {
             // 截止本期金额文案更新
             $('#p_total2').text(formatMoney(ZhCalc.add(cur_amount, p_amount), ',', total && total.length || 0) + '元')
         } else {
+            // 支付比例转化
+            val = fixedToSub(val)
             if (val.toFixed(1) === max.toFixed(1)) {
                 val = max
             }
-            // 支付比例转化
-            $(this).val(fixedToSub(val)) // 重新赋值限制只有两位小数
+            $(this).val(val) // 重新赋值限制只有两位小数
             const cur_m_input = $(`.pay-input[data-type=${reverse(type)}]`)
             cur_amount = ZhCalc.round(ZhCalc.mul(advancePayTotal, ZhCalc.div(val, 100)), decimal)
             pay_ratio = val

+ 254 - 197
app/public/js/change_detail.js

@@ -1,197 +1,254 @@
-'use strict';
-
-/**
- * 变更令详细页js
- *
- * @author EllisRan.
- * @date 2018/11/22
- * @version
- */
-
-$.event.special.valuechange = {
-    teardown: function (namespaces) {
-        $(this).unbind('.valuechange');
-    },
-
-    handler: function (e) {
-        $.event.special.valuechange.triggerChanged($(this));
-    },
-
-    add: function (obj) {
-        $(this).on('keyup.valuechange cut.valuechange paste.valuechange input.valuechange', obj.selector, $.event.special.valuechange.handler)
-    },
-
-    triggerChanged: function (element) {
-        var current = element[0].contentEditable === 'true' ? element.html() : element.val()
-            , previous = typeof element.data('previous') === 'undefined' ? element[0].defaultValue : element.data('previous');
-        if (current !== previous) {
-            element.trigger('valuechange', [element.data('previous')]);
-            element.data('previous', current);
-        }
-    }
-};
-
-$(document).ready(() => {
-    // tab切换
-    $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
-        const tab = $(this).attr('aria-controls');
-        $('.show_title').hide();
-        $('#'+ tab +'_title').show();
-        if (tab === 'bills' && $('#bills').hasClass('first-bill-pane')) {
-            if (table) {
-                table.destroy();
-            }
-            table = $('.table-list').removeAttr('width').DataTable(billsTable);
-            if (!$('.change-detail-checkbox').is(':checked')) {
-                const column = table.column(3);
-                column.visible(!column.visible());
-            }
-            $('#bills').removeClass('first-bill-pane');
-        }
-    });
-
-    // 上传附件
-    $('#upload-file-btn').click(function () {
-        const files = $('#upload-file')[0].files;
-        const formData = new FormData();
-        formData.append('cid', $('#changeId').val());
-        formData.append('tid', $('#tenderId').val());
-        for (const file of files) {
-            if (file === undefined) {
-                toastr.error('未选择上传文件!');
-                return false;
-            }
-            const filesize = file.size;
-            if (filesize > 30 * 1024 * 1024) {
-                toastr.error('文件大小过大!');
-                return false;
-            }
-            const fileext = '.' + file.name.toLowerCase().split('.').splice(-1)[0];
-            if (whiteList.indexOf(fileext) === -1) {
-                toastr.error('只能上传指定格式的附件!');
-                return false;
-            }
-            formData.append('size', filesize);
-            formData.append('file[]', file);
-        }
-        postDataWithFile('/change/upload/file', formData, function (data) {
-            $('#addfujian').modal('hide');
-            let html = '';
-            let index = $('#attList tr').length + 1;
-            for (const fileInfo of data) {
-                html += '<tr> ' +
-                    '<td>' + index + '</td> ' +
-                    `<td><a href="javascript: void(0);" class="file-atn" f-id="${fileInfo.id}">${fileInfo.filename}${fileInfo.fileext}</a></td>`+
-                    '<td>' + fileInfo.filesize + '</td> ' +
-                    '<td>' + fileInfo.in_time + '</td> ' +
-                    '<td> <a class="btn btn-light btn-sm delete-file" data-attid="' + fileInfo.id + '"  title="删除附件"><span class="fa fa-trash text-danger"></span></a> </td> ' +
-                    '</tr>';
-                ++index;
-            }
-            $('#attList').append(html);
-        }, function () {
-
-        });
-        $('#upload-file').val('');
-
-    });
-
-    // 删除附件
-    $('body').on('click', '.delete-file', function () {
-        let attid = $(this).data('attid');
-        console.log(attid);
-        let self = $(this);
-        const data = {id: attid};
-        postData('/change/delete/file', data, function (result) {
-            self.parents('tr').remove();
-            // 重新排序
-            let newsort = 1;
-            $('#attList tr').each(function(){
-                $(this).children('td').eq(0).text(newsort);
-                newsort++;
-            });
-        });
-    });
-    // /change/download/file/
-    $('#attList').on('click', '.file-atn', function() {
-        const id = $(this).attr('f-id')
-        postData(`/change/download/file/${id}`, {}, (data) => {
-            const { filepath } = data
-            $('#file-upload').attr('href', filepath)
-            $('#file-upload')[0].click()
-        })
-    })
-    //
-    const cca = getLocalCache('change-checkbox-account-' + accountId);
-    if (cca !== null && cca !== undefined) {
-        $('#customCheck1').prop('checked', cca !== 'false');
-    }
-    // 变更详情展示和隐藏
-    $('.change-detail-checkbox').on('click', function (e) {
-        if($(e.target).is('label')){
-            return;
-        }
-        let column = table.column(3);
-        // 设置用户项目本地记录展示和隐藏情况
-        setLocalCache('change-checkbox-account-'+ accountId, $(this).is(':checked'));
-        column.visible(!column.visible());
-    })
-
-    // 重新审批获取手机验证码
-    // 获取验证码
-    let isPosting = false;
-    $("#get-code").click(function() {
-        if (isPosting) {
-            return false;
-        }
-        const btn = $(this);
-
-        $.ajax({
-            url: '/profile/code?_csrf=' + csrf,
-            type: 'post',
-            data: { mobile: authMobile, type: 'shenpi' },
-            dataTye: 'json',
-            error: function() {
-                isPosting = false;
-            },
-            beforeSend: function() {
-                isPosting = true;
-            },
-            success: function(response) {
-                isPosting = false;
-                if (response.err === 0) {
-                    codeSuccess(btn);
-                    $("input[name='code']").removeAttr('readonly');
-                    $("#re-shenpi-btn").removeAttr('disabled');
-                } else {
-                    toast(response.msg, 'error');
-                }
-            }
-        });
-    });
-});
-/**
- * 获取成功后的操作
- *
- * @param {Object} btn - 点击的按钮
- * @return {void}
- */
-function codeSuccess(btn) {
-    let counter = 60;
-    btn.addClass('disabled').text('重新获取 ' + counter + 'S');
-    btn.parent().siblings('input').removeAttr('readonly').attr('placeholder', '输入短信中的6位验证码');
-    const bindBtn = $("#bind-btn");
-    bindBtn.removeClass('btn-secondary disabled').addClass('btn-primary');
-
-    const countDown = setInterval(function() {
-        const countString = counter - 1 <= 0 ? '' : ' ' + (counter - 1) + 'S';
-        // 倒数结束后
-        if (countString === '') {
-            clearInterval(countDown);
-            btn.removeClass('disabled');
-        }
-        const text = '重新获取' + countString;
-        btn.text(text);
-        counter -= 1;
-    }, 1000);
-}
+'use strict';
+
+/**
+ * 变更令详细页js
+ *
+ * @author EllisRan.
+ * @date 2018/11/22
+ * @version
+ */
+
+$.event.special.valuechange = {
+    teardown: function (namespaces) {
+        $(this).unbind('.valuechange');
+    },
+
+    handler: function (e) {
+        $.event.special.valuechange.triggerChanged($(this));
+    },
+
+    add: function (obj) {
+        $(this).on('keyup.valuechange cut.valuechange paste.valuechange input.valuechange', obj.selector, $.event.special.valuechange.handler)
+    },
+
+    triggerChanged: function (element) {
+        var current = element[0].contentEditable === 'true' ? element.html() : element.val()
+            , previous = typeof element.data('previous') === 'undefined' ? element[0].defaultValue : element.data('previous');
+        if (current !== previous) {
+            element.trigger('valuechange', [element.data('previous')]);
+            element.data('previous', current);
+        }
+    }
+};
+
+$(document).ready(() => {
+    // tab切换
+    $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
+        const tab = $(this).attr('aria-controls');
+        $('.show_title').hide();
+        $('#'+ tab +'_title').show();
+        if (tab === 'bills' && $('#bills').hasClass('first-bill-pane')) {
+            if (table) {
+                table.destroy();
+            }
+            table = $('.table-list').removeAttr('width').DataTable(billsTable);
+            if (!$('.change-detail-checkbox').is(':checked')) {
+                const column = table.column(3);
+                column.visible(!column.visible());
+            }
+            $('#bills').removeClass('first-bill-pane');
+        }
+    });
+
+    $('#add-bj').on('click', 'input[type="checkbox"]', function () {
+        const isCheck = $(this).prop('checked')
+        if (isCheck) {
+            $('#add-bj input[type="checkbox"]').each(function () {
+                $(this).prop('checked', false)
+            })
+            $(this).prop('checked', true)
+        }
+    })
+    $('#bg-copy').click(function() {
+        const cid = $('#add-bj input:checked').data('id')
+        postData(window.location.pathname + '/copy', cid, function () {
+            window.location.reload()
+        })
+    })
+    // 上传附件
+    $('#upload-file-btn').click(function () {
+        const files = $('#upload-file')[0].files;
+        const formData = new FormData();
+        formData.append('cid', $('#changeId').val());
+        formData.append('tid', $('#tenderId').val());
+        for (const file of files) {
+            if (file === undefined) {
+                toastr.error('未选择上传文件!');
+                return false;
+            }
+            const filesize = file.size;
+            if (filesize > 30 * 1024 * 1024) {
+                toastr.error('文件大小过大!');
+                return false;
+            }
+            const fileext = '.' + file.name.toLowerCase().split('.').splice(-1)[0];
+            if (whiteList.indexOf(fileext) === -1) {
+                toastr.error('只能上传指定格式的附件!');
+                return false;
+            }
+            formData.append('size', filesize);
+            formData.append('file[]', file);
+        }
+        if (auditList.findIndex(item => item.uid === parseInt(accountId)) === -1) {
+            return toastr.error('暂无权限上传!')
+        }
+        postDataWithFile(window.location.pathname + '/file/upload', formData, function (data) {
+            $('#addfujian').modal('hide');
+            let html = '';
+            let index = $('#attList tr').length + 1;
+            for (const fileInfo of data) {
+                html += '<tr> ' +
+                    `<td width="25"><input type="checkbox" class="check-file" file-id=${fileInfo.id}></td>` +
+                    '<td>' + index + '</td> ' +
+                    `<td><a href="javascript: void(0);" class="file-atn" f-id="${fileInfo.id}">${fileInfo.filename}${fileInfo.fileext}</a></td>`+
+                    '<td>' + fileInfo.filesize + '</td> ' +
+                    '<td>' + fileInfo.in_time + '</td> ' +
+                    `<td><a href="/change/download/file/${fileInfo.id}" class="btn btn-light btn-sm" title="下载"><span class="fa fa-download text-primary"></span></a>`+
+                    ( auditStatus === 4 ?
+                        fileInfo.extra_upload ? `<a class="btn btn-light btn-sm delete-file" data-attid="${fileInfo.id}"  title="删除附件"><span class="fa fa-trash text-danger"></span></a>` : ''
+                        : ` <a class="btn btn-light btn-sm delete-file" data-attid="${fileInfo.id}"  title="删除附件"><span class="fa fa-trash text-danger"></span></a>`)+
+                    `</td>`+
+                    // '<td> <a class="btn btn-light btn-sm delete-file" data-attid="' + fileInfo.id + '"  title="删除附件"><span class="fa fa-trash text-danger"></span></a> </td> ' +
+                    '</tr>';
+                ++index;
+            }
+            $('#attList').append(html);
+        }, function () {
+
+        });
+        $('#upload-file').val('');
+
+    });
+
+    // 删除附件
+    $('body').on('click', '.delete-file', function () {
+        let attid = $(this).data('attid');
+        let self = $(this);
+        const data = {id: attid};
+        postData(window.location.pathname + '/file/delete', data, function (result) {
+            self.parents('tr').remove();
+            // 重新排序
+            let newsort = 1;
+            $('#attList tr').each(function(){
+                $(this).children('td').eq(1).text(newsort);
+                newsort++;
+            });
+        });
+    });
+    // /change/download/file/
+    $('#attList').on('click', '.file-atn', function() {
+        const id = $(this).attr('f-id')
+        postData(`/change/download/file/${id}`, {}, (data) => {
+            const { filepath } = data
+            $('#file-upload').attr('href', filepath)
+            $('#file-upload')[0].click()
+        })
+    })
+
+    $('#attList').on('click', '.check-file', function() {
+
+        const checkedList = $('#attList').find('input:checked')
+        const childs = $('#attList').children().length
+        const checkBox = $('#check-all-file')
+        if (checkedList.length === childs) {
+            checkBox.prop("checked", true)
+        } else {
+            checkBox.prop("checked", false)
+        }
+    })
+    $('#check-all-file').click(function() {
+        const isCheck = $(this).is(':checked')
+        $('#attList').children().each(function() {
+            $(this).find('input:checkbox').prop("checked", isCheck)
+        })
+    });
+
+    $('#bach-download').click(function() {
+        const fileIds = []
+        $( '#attList .check-file:checked').each(function() {
+            const fileId = $(this).attr('file-id')
+            fileId && fileIds.push(fileId)
+        })
+
+        if (fileIds.length) {
+            const tid = $('#tenderId').val()
+            const cid = $('#changeId').val()
+            $('#downloadZip').attr('href', `/tender/${tid}/change/${cid}/download/compresse-file?fileIds=${JSON.stringify(fileIds)}`);
+            $('#downloadZip')[0].click();
+        }
+    });
+
+    //
+    const cca = getLocalCache('change-checkbox-account-' + accountId);
+    if (cca !== null && cca !== undefined) {
+        $('#customCheck1').prop('checked', cca !== 'false');
+    }
+    // 变更详情展示和隐藏
+    $('.change-detail-checkbox').on('click', function (e) {
+        if($(e.target).is('label')){
+            return;
+        }
+        let column = table.column(3);
+        // 设置用户项目本地记录展示和隐藏情况
+        setLocalCache('change-checkbox-account-'+ accountId, $(this).is(':checked'));
+        column.visible(!column.visible());
+    })
+
+    // 重新审批获取手机验证码
+    // 获取验证码
+    let isPosting = false;
+    $("#get-code").click(function() {
+        if (isPosting) {
+            return false;
+        }
+        const btn = $(this);
+
+        $.ajax({
+            url: '/profile/code?_csrf=' + csrf,
+            type: 'post',
+            data: { mobile: authMobile, type: 'shenpi' },
+            dataTye: 'json',
+            error: function() {
+                isPosting = false;
+            },
+            beforeSend: function() {
+                isPosting = true;
+            },
+            success: function(response) {
+                isPosting = false;
+                if (response.err === 0) {
+                    codeSuccess(btn);
+                    $("input[name='code']").removeAttr('readonly');
+                    $("#re-shenpi-btn").removeAttr('disabled');
+                } else {
+                    toast(response.msg, 'error');
+                }
+            }
+        });
+    });
+});
+/**
+ * 获取成功后的操作
+ *
+ * @param {Object} btn - 点击的按钮
+ * @return {void}
+ */
+function codeSuccess(btn) {
+    let counter = 60;
+    btn.addClass('disabled').text('重新获取 ' + counter + 'S');
+    btn.parent().siblings('input').removeAttr('readonly').attr('placeholder', '输入短信中的6位验证码');
+    const bindBtn = $("#bind-btn");
+    bindBtn.removeClass('btn-secondary disabled').addClass('btn-primary');
+
+    const countDown = setInterval(function() {
+        const countString = counter - 1 <= 0 ? '' : ' ' + (counter - 1) + 'S';
+        // 倒数结束后
+        if (countString === '') {
+            clearInterval(countDown);
+            btn.removeClass('disabled');
+        }
+        const text = '重新获取' + countString;
+        btn.text(text);
+        counter -= 1;
+    }, 1000);
+}

+ 110 - 56
app/public/js/change_set.js

@@ -236,17 +236,72 @@ $(document).ready(() => {
         toastr.success('已还原到上次保存状态');
     });
 
-    // 审批人分组选择
-    $('#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>';
+    let timer = null
+    let oldSearchVal = null
+    // 获取审核相关url
+    function getUrlPre () {
+        const path = window.location.pathname.split('/');
+        return _.take(path, 6).join('/');
+    }
+
+    $('#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 {
+                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 !== changesUid) {
+                                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
     });
 
     $('#hideSp').click(function () {
@@ -254,50 +309,56 @@ $(document).ready(() => {
     });
 
     // 添加到审批流程中
-    $('body').on('change', '#account_list', function () {
-        let id = $(this).val();
-        id = parseInt(id);
-        if (id !== 0) {
-            let auditListIdData = [];
+    $('dl').on('click', 'dd', function () {
+        const id = parseInt($(this).data('id'));
+        if (id) {
+            const auditListIdData = [];
             $('#auditList li').each(function () {
-                let aid = $(this).data('auditid');
+                const aid = $(this).data('auditid');
                 auditListIdData.push(aid);
             });
             if (!in_array(auditListIdData, id)) {
-                const accountInfo = accountList.find(function (item) {
-                    return item.id === id;
-                });
-                const user = accountInfo.id + '/%/' + accountInfo.name + '/%/' + accountInfo.role + '/%/' + accountInfo.company;
-                const addhtml = '<li class="list-group-item" data-auditmsg="' + user + '"' +
-                    'data-auditid="' + accountInfo.id + '" >' +
-                    '<a href="javascript:void(0);" class="text-danger pull-right remove_audit_btn">移除</a>' +
-                    '<span>' + (auditListIdData.length+1) + '</span> ' + accountInfo.name + '  <small class="text-muted">' + accountInfo.role + '</small>' +
-                    '<p class="m-0 ml-2"><small class="text-muted">' + accountInfo.company + '</small></p>' +
-                    '</li>';
-                $('#auditList').append(addhtml);
-
-                // 重新上报时。令其它的审批人流程图标转换
-                $('#shenpi-audit-list li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
-                $('#shenpi-audit-list2 li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
-                // 添加新审批人
-                const addhtml1 = '<li class="list-group-item" data-auditid="' + accountInfo.id + '" >' +
-                    '<i class="fa fa-stop-circle"></i> ' +
-                    accountInfo.name + ' <small class="text-muted">' + accountInfo.role + '</small><span class="pull-right">终审</span>' +
-                    '</li>';
-                const addhtml2 = '<li class="list-group-item" data-auditid="' + accountInfo.id + '" >' +
-                    '<h5 class="card-title"><i class="fa fa-stop-circle"></i> ' +
-                    accountInfo.name + ' <small class="text-muted">' + accountInfo.role + '</small><span class="pull-right">终审</span>' +
-                    '</h5></li>';
-                for (let i = 0; i < $('#shenpi-audit-list li').length; i++) {
-                    $('#shenpi-audit-list li').eq(i).find('.pull-right').text(transFormToChinese(i) + '审');
-                    $('#shenpi-audit-list2 li').eq(i).find('.pull-right').text(transFormToChinese(i) + '审');
+                if (shenpi_status === shenpiConst.sp_status.gdzs) {
+                    auditListIdData.splice(-1,0,id);
+                } else {
+                    auditListIdData.push(id);
+                }
+                const html = [];
+                const auditorshtml = [];
+                auditListIdData.unshift(changesUid);
+                for (const [index,ids] of auditListIdData.entries()) {
+                    const accountInfo = _.find(accountList, { 'id': ids });
+                    if (index !== 0) {
+                        const user = accountInfo.id + '/%/' + accountInfo.name + '/%/' + accountInfo.role + '/%/' + accountInfo.company;
+                        html.push('<li class="list-group-item" data-auditmsg="' + user + '" data-auditid="'+ ids +'">');
+                        if (shenpi_status === shenpiConst.sp_status.sqspr || (shenpi_status === shenpiConst.sp_status.gdzs && index+1 !== auditListIdData.length)) {
+                            html.push('<a href="javascript:void(0);" class="text-danger pull-right remove_audit_btn">移除</a>');
+                        }
+                        html.push('<span>');
+                        html.push(index + ' ');
+                        html.push('</span> ');
+                        html.push(accountInfo.name + ' ');
+                        html.push('<small class="text-muted">');
+                        html.push(accountInfo.role);
+                        html.push('</small>');
+                        html.push('<p class="m-0 ml-2"><small class="text-muted">' + accountInfo.company + '</small></p>');
+                        html.push('</li>');
+                    }
+                    // 添加新审批人流程修改
+                    auditorshtml.push('<li class="list-group-item" ' + (index !== 0 ? 'data-auditid="' + accountInfo.id + '"' : '') + '>');
+                    auditorshtml.push('<i class="fa ' + (index+1 === auditListIdData.length ? 'fa-stop-circle' : 'fa-chevron-circle-down') + '"></i> ');
+                    auditorshtml.push(accountInfo.name + ' <small class="text-muted">' + accountInfo.role + '</small>');
+                    if (index === 0) {
+                        auditorshtml.push('<span class="pull-right">原报</span>');
+                    } else if (index+1 === auditListIdData.length) {
+                        auditorshtml.push('<span class="pull-right">终审</span>');
+                    } else {
+                        auditorshtml.push('<span class="pull-right">'+ transFormToChinese(index) +'审</span>');
+                    }
+                    auditorshtml.push('</li>');
                 }
-                $('#shenpi-audit-list li').eq(0).find('.pull-right').text('原报');
-                $('#shenpi-audit-list2 li').eq(0).find('.pull-right').text('原报');
-                $('#shenpi-audit-list li i').eq(0).removeClass('fa-chevron-circle-down').addClass('fa-play-circle');
-                $('#shenpi-audit-list2 li i').eq(0).removeClass('fa-chevron-circle-down').addClass('fa-play-circle');
-                $('#shenpi-audit-list').append(addhtml1);
-                $('#shenpi-audit-list2').append(addhtml2);
+                $('#auditList').html(html.join(''));
+                $('#shenpi-audit-list').html(auditorshtml.join(''));
             } else {
                 toastr.error('审批流程中已存在该用户!');
             }
@@ -320,21 +381,14 @@ $(document).ready(() => {
         // 重新上报时。移除审批流程
         // 令最后一个图标转换
         $('#shenpi-audit-list li[data-auditid="' + uid + '"]').remove();
-        $('#shenpi-audit-list2 li[data-auditid="' + uid + '"]').remove();
         if ($('#shenpi-audit-list li').length !== 0 && !$('#shenpi-audit-list li i').hasClass('fa-stop-circle')) {
             $('#shenpi-audit-list li').eq($('#shenpi-audit-list li').length-1).children('i')
                 .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
-            $('#shenpi-audit-list2 li').eq($('#shenpi-audit-list2 li').length-1).children('i')
-                .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
         }
         for (let i = 0; i < $('#shenpi-audit-list li').length; i++) {
-            $('#shenpi-audit-list li').eq(i).find('.pull-right').text((i+1 === $('#shenpi-audit-list li').length ? '终' : transFormToChinese(i+1)) + '审');
-            $('#shenpi-audit-list2 li').eq(i).find('.pull-right').text((i+1 === $('#shenpi-audit-list2 li').length ? '终' : transFormToChinese(i+1)) + '审');
+            $('#shenpi-audit-list li').eq(i).find('.pull-right').text(i === 0 ? '原报' : (i+1 === $('#shenpi-audit-list li').length ? '终' : transFormToChinese(i)) + '审');
         }
-        $('#shenpi-audit-list li').eq(0).find('.pull-right').text('原报');
-        $('#shenpi-audit-list2 li').eq(0).find('.pull-right').text('原报');
         $('#shenpi-audit-list li i').eq(0).removeClass('fa-chevron-circle-down').addClass('fa-play-circle');
-        $('#shenpi-audit-list2 li i').eq(0).removeClass('fa-chevron-circle-down').addClass('fa-play-circle');
     });
 
     // 打开签约清单modal并删除之前的操作

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

@@ -21,7 +21,7 @@ function autoFlashHeight(){
     $(".sjs-height-0").height($(window).height()-cHeader-90+53);
     $(".sjs-height-1").height($(window).height()-cHeader-bcontent-90+53);
     $(".sjs-height-2").height($(window).height()-cHeader-sBarz-120);
-    $(".sjs-height-3").height($(window).height()-cHeader-sBar-492+25);/*492*/
+    $(".sjs-height-3").height($(window).height()-cHeader-sBar-492+25+150);/*492*/
     $(".sjs-height-4").height($(window).height()-cHeader-pBarz-110+75);
     $(".sjs-height-5").height($(window).height()-cHeader-sBar-492+55);/*492*/
     $(".sjs-height-6").height($(window).height()-cHeader-34-sBar1);

+ 46 - 48
app/public/js/ledger.js

@@ -706,8 +706,8 @@ $(document).ready(function() {
                                 const exprInfo = getExprInfo(colSetting.field);
                                 if (exprInfo) {
                                     data[exprInfo.expr] = value;
+                                    bPaste = true;
                                 }
-                                bPaste = true;
                             } catch(err) {
                                 toastMessageUniq(hint.invalidExpr);
                             }
@@ -2127,7 +2127,7 @@ $(document).ready(function() {
             defaultRowHeight: 21,
             headerFont: '12px 微软雅黑',
             font: '12px 微软雅黑',
-            headColWidth: [0],
+            headColWidth: [30],
             selectedBackColor: '#fffacd',
         },
         cellDoubleClick: stdLibCellDoubleClick,
@@ -2158,7 +2158,7 @@ $(document).ready(function() {
             defaultRowHeight: 21,
             headerFont: '12px 微软雅黑',
             font: '12px 微软雅黑',
-            headColWidth: [0],
+            headColWidth: [30],
             selectedBackColor: '#fffacd',
         },
         cellDoubleClick: stdLibCellDoubleClick,
@@ -2206,7 +2206,7 @@ $(document).ready(function() {
                         defaultRowHeight: 21,
                         headerFont: '12px 微软雅黑',
                         font: '12px 微软雅黑',
-                        headColWidth: [0],
+                        headColWidth: [30],
                         selectedBackColor: '#fffacd',
                         readOnly: true,
                     });
@@ -2990,44 +2990,38 @@ $(document).ready(function() {
     $('dl').on('click', 'dd', function () {
         const auditorId = parseInt($(this).data('id'))
         if (auditorId) {
-            postData('/tender/' + getTenderId() + '/ledger/audit/add', { auditorId }, (data) => {
+            postData('/tender/' + getTenderId() + '/ledger/audit/add', { auditorId }, (datas) => {
                 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.audit_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(''));
-
                 // 如果是重新上报,添加到重新上报列表中
                 const auditorshtml = [];
-                // 重新上报时。令其它的审批人流程图标转换
-                $('#auditors-list li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
-                for (let i = 0; i < $('#auditors-list li').length; i++) {
-                    $('#auditors-list li').eq(i).find('.pull-right').text(transFormToChinese(i+1) + '审');
-                    $('#auditors-list2 li').eq(i).find('.pull-right').text(transFormToChinese(i+1) + '审');
-                }
-                // 添加新审批人
-                auditorshtml.push('<li class="list-group-item" data-auditid="' + data.audit_id + '">');
-                auditorshtml.push('<i class="fa fa-stop-circle"></i> ');
-                auditorshtml.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
-                auditorshtml.push('<span class="pull-right">终审</span>');
-                auditorshtml.push('</li>');
-                $('#auditors-list').append(auditorshtml.join(''));
-
-                const auditorshtml2 = [];
-                // 重新上报时。令其它的审批人流程图标转换
-                $('#auditors-list2 li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
-                // 添加新审批人
-                auditorshtml2.push('<li class="list-group-item" data-auditid="' + data.audit_id + '">');
-                auditorshtml2.push('<h5 class="card-title"><i class="fa fa-stop-circle"></i> ');
-                auditorshtml2.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
-                auditorshtml2.push('<span class="pull-right">终审</span>');
-                auditorshtml2.push('</h5></li>');
-                $('#auditors-list2').append(auditorshtml2.join(''));
+                for (const [index,data] of datas.entries()) {
+                    if (index !== 0) {
+                        html.push('<li class="list-group-item" auditorId="' + data.audit_id + '">');
+                        if (shenpi_status === shenpiConst.sp_status.sqspr || (shenpi_status === shenpiConst.sp_status.gdzs && index+1 !== datas.length)) {
+                            html.push('<a href="javascript: void(0)" class="text-danger pull-right">移除</a>');
+                        }
+                        html.push('<span>');
+                        html.push(data.audit_order + ' ');
+                        html.push(data.name + ' ');
+                        html.push('</span>');
+                        html.push('<small class="text-muted">');
+                        html.push(data.role);
+                        html.push('</small></li>');
+                    }
+                    auditorshtml.push('<li class="list-group-item" ' + (index !== 0 ? 'data-auditorid="' + data.audit_id + '"' : '') + '>');
+                    auditorshtml.push('<i class="fa ' + (index+1 === datas.length ? 'fa-stop-circle' : 'fa-chevron-circle-down') + '"></i> ');
+                    auditorshtml.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
+                    if (index === 0) {
+                        auditorshtml.push('<span class="pull-right">原报</span>');
+                    } else if (index+1 === datas.length) {
+                        auditorshtml.push('<span class="pull-right">终审</span>');
+                    } else {
+                        auditorshtml.push('<span class="pull-right">'+ transFormToChinese(index) +'审</span>');
+                    }
+                    auditorshtml.push('</li>');
+                }
+                $('#auditors').html(html.join(''));
+                $('#auditors-list').html(auditorshtml.join(''));
             });
         }
     });
@@ -3045,19 +3039,19 @@ $(document).ready(function() {
 
             // 如果是重新上报
             // 令最后一个图标转换
-            $('#auditors-list li[data-auditid="' + data.auditorId + '"]').remove();
+            $('#auditors-list li[data-auditorid="' + data.auditorId + '"]').remove();
             if ($('#auditors-list li').length !== 0 && !$('#auditors-list li i').hasClass('fa-stop-circle')) {
                 $('#auditors-list li').eq($('#auditors-list li').length-1).children('i')
                     .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
             }
-            $('#auditors-list2 li[data-auditid="' + data.auditorId + '"]').remove();
-            if ($('#auditors-list2 li').length !== 0 && !$('#auditors-list2 li i').hasClass('fa-stop-circle')) {
-                $('#auditors-list2 li').eq($('#auditors-list2 li').length-1).children('i')
-                    .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
-            }
+            // $('#auditors-list2 li[data-auditid="' + data.auditorId + '"]').remove();
+            // if ($('#auditors-list2 li').length !== 0 && !$('#auditors-list2 li i').hasClass('fa-stop-circle')) {
+            //     $('#auditors-list2 li').eq($('#auditors-list2 li').length-1).children('i')
+            //         .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
+            // }
             for (let i = 0; i < $('#auditors-list li').length; i++) {
-                $('#auditors-list li').eq(i).find('.pull-right').text((i+1 === $('#auditors-list li').length ? '终' : transFormToChinese(i+1)) + '审')
-                $('#auditors-list2 li').eq(i).find('.pull-right').text((i+1 === $('#auditors-list2 li').length ? '终' : transFormToChinese(i+1)) + '审')
+                $('#auditors-list li').eq(i).find('.pull-right').text(i === 0 ? '原报' : (i+1 === $('#auditors-list li').length ? '终' : transFormToChinese(i)) + '审')
+                // $('#auditors-list2 li').eq(i).find('.pull-right').text((i+1 === $('#auditors-list2 li').length ? '终' : transFormToChinese(i+1)) + '审')
             }
         });
     });
@@ -3219,7 +3213,11 @@ $(document).ready(function() {
 // 检查上报情况
 function checkAuditorFrom () {
     if ($('#auditors li').length === 0) {
-        toastr.error('请先选择审批人,再上报数据');
+        if(shenpi_status === shenpiConst.sp_status.gdspl) {
+            toastr.error('请联系管理员添加审批人');
+        } else {
+            toastr.error('请先选择审批人,再上报数据');
+        }
         return false;
     } else {
         $('#hide-all').show();

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

@@ -173,7 +173,7 @@ $(document).ready(() => {
                     defaultRowHeight: 21,
                     headerFont: '12px 微软雅黑',
                     font: '12px 微软雅黑',
-                    headColWidth: [0],
+                    headColWidth: [30],
                     selectedBackColor: '#fffacd',
                 });
                 dealBills.loadData();

+ 138 - 121
app/public/js/login.js

@@ -1,121 +1,138 @@
-$(document).ready(function() {
-    const lSPName = getLocalCache('project_name');
-    const lSPCode = getLocalCache('project_code');
-    if (lSPName !== null) {
-        $('#project_name').text(lSPName);
-        $('#project').val(lSPCode);
-        $('#forget-project').val(lSPCode);
-        $('#account').focus();
-    }
-    $("#login-tab a[data-toggle='tab']").on('shown.bs.tab', function () {
-        let type = $(this).data('type');
-        type = parseInt(type);
-        type = isNaN(type) || type <= 0 ? 1 : type;
-        $("input[name='type']:hidden").val(type);
-    });
-    // $('#username').blur(function () {
-    //     let account = $(this).val();
-    //     // 判断输入的邮箱/手机是否格式正确
-    //     if(/^1[3456789]\d{9}$/.test(account) || /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(account)) {
-    //         $('#error-msg').hide();
-    //     } else {
-    //         $('#error-msg').show();
-    //         $('#error-msg').text('账号格式有误');
-    //     }
-    // })
-    $('#project').blur(function () {
-        if ($(this).val() == '') {
-            $('#project_name').text('');
-            $('#forget-project').val('');
-            removeLocalCache('project_code');
-            removeLocalCache('project_name');
-        } else {
-            const pcode = getLocalCache('project_code');
-            if ($(this).val() !== pcode) {
-                const pc = $(this).val();
-                $.ajax({
-                    type: 'get',
-                    url: '/project/name',
-                    data: { code: pc },
-                    dataType: 'json',
-                    success: function (result) {
-                        setLocalCache('project_code', pc);
-                        if (result.err === 1) {
-                            $('#project_name').text('');
-                            $('#forget-project').val('');
-                            console.log(result.msg);
-                            toast(result.msg, 'error', 'exclamation-circle');
-                            removeLocalCache('project_name');
-                        } else {
-                            setLocalCache('project_name', result.data);
-                            $('#project_name').text(result.data);
-                            $('#forget-project').val(pc);
-                        }
-                    }
-                })
-            }
-        }
-    });
-
-    $('#forget-btn').click(function () {
-        let flag = true;
-        if ($('#forget-project').val() == '') {
-            $('#forget-project').addClass('is-invalid');
-            $('#forget-project').siblings('div.invalid-feedback').html('项目编号不能为空。');
-            flag = false;
-        }
-        if ($('#forget-name').val() == '') {
-            $('#forget-name').addClass('is-invalid');
-            $('#forget-name').siblings('div.invalid-feedback').html('登录账号不能为空。');
-            flag = false;
-        }
-        if(flag) {
-            $.ajax({
-                type: 'post',
-                url: '/reset/password',
-                data: { code: $('#forget-project').val(), name: $('#forget-name').val() },
-                dataType: 'json',
-                beforeSend: function(xhr) {
-                    let csrfToken = csrf;
-                    xhr.setRequestHeader('x-csrf-token', csrfToken);
-                },
-                success: function (result) {
-                    if (result.err === 1) {
-                        if (result.index === 1) {
-                            $('#forget-project').addClass('is-invalid');
-                            $('#forget-project').siblings('div.invalid-feedback').html(result.msg);
-                        } else if (result.index === 2) {
-                            $('#forget-name').addClass('is-invalid');
-                            $('#forget-name').siblings('div.invalid-feedback').html(result.msg);
-                        } else {
-                            toast(result.msg, 'error');
-                        }
-                    } else {
-                        $('#fg-password-done').find('b').eq(0).text(result.data.name);
-                        $('#fg-password-done').find('b').eq(1).text(result.data.account);
-                        $('#fg-password-done').find('b').eq(2).text(result.data.mobile);
-                        setLocalCache('project_name', result.data.pName);
-                        setLocalCache('project_code', $('#forget-project').val());
-                        $('#fg-password').modal('hide');
-                        $('#fg-password-done').modal('show');
-                        $('#account').val($('#forget-name').val());
-                        $('#project').val($('#forget-project').val());
-                        $('#project_name').text(result.data.pName);
-                        $('#forget-name').val('');
-                    }
-                }
-            })
-        }
-    })
-
-    $('input').focus(function () {
-        if($(this).hasClass('is-invalid')) {
-            $(this).removeClass('is-invalid');
-            $(this).siblings('div.invalid-feedback').html('');
-        }
-    });
-
-    $('#focus-pwd').click(function () {
-        $('#project-password').focus();
-    })
-});
+$(document).ready(function() {
+    // const lSPName = getLocalCache('project_name');
+    const lSPCode = getLocalCache('project_code');
+    if (lSPCode !== null) {
+        // $('#project_name').text(lSPName);
+        $('#project').val(lSPCode);
+        $('#forget-project').val(lSPCode);
+        $('#account').focus();
+
+        $.ajax({
+            type: 'get',
+            url: '/project/name',
+            data: { code: lSPCode },
+            dataType: 'json',
+            success: function (result) {
+                if (result.err === 1) {
+                    $('#project_name').text('');
+                    $('#forget-project').val('');
+                    toast(result.msg, 'error', 'exclamation-circle');
+                } else {
+                    $('#project_name').text(result.data);
+                }
+            }
+        })
+    }
+
+    $("#login-tab a[data-toggle='tab']").on('shown.bs.tab', function () {
+        let type = $(this).data('type');
+        type = parseInt(type);
+        type = isNaN(type) || type <= 0 ? 1 : type;
+        $("input[name='type']:hidden").val(type);
+    });
+    // $('#username').blur(function () {
+    //     let account = $(this).val();
+    //     // 判断输入的邮箱/手机是否格式正确
+    //     if(/^1[3456789]\d{9}$/.test(account) || /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(account)) {
+    //         $('#error-msg').hide();
+    //     } else {
+    //         $('#error-msg').show();
+    //         $('#error-msg').text('账号格式有误');
+    //     }
+    // })
+    $('#project').blur(function () {
+        if ($(this).val() == '') {
+            $('#project_name').text('');
+            $('#forget-project').val('');
+            removeLocalCache('project_code');
+            removeLocalCache('project_name');
+        } else {
+            const pcode = getLocalCache('project_code');
+            if ($(this).val() !== pcode) {
+                const pc = $(this).val();
+                $.ajax({
+                    type: 'get',
+                    url: '/project/name',
+                    data: { code: pc },
+                    dataType: 'json',
+                    success: function (result) {
+                        setLocalCache('project_code', pc);
+                        if (result.err === 1) {
+                            $('#project_name').text('');
+                            $('#forget-project').val('');
+                            console.log(result.msg);
+                            toast(result.msg, 'error', 'exclamation-circle');
+                            removeLocalCache('project_name');
+                        } else {
+                            setLocalCache('project_name', result.data);
+                            $('#project_name').text(result.data);
+                            $('#forget-project').val(pc);
+                        }
+                    }
+                })
+            }
+        }
+    });
+
+    $('#forget-btn').click(function () {
+        let flag = true;
+        if ($('#forget-project').val() == '') {
+            $('#forget-project').addClass('is-invalid');
+            $('#forget-project').siblings('div.invalid-feedback').html('项目编号不能为空。');
+            flag = false;
+        }
+        if ($('#forget-name').val() == '') {
+            $('#forget-name').addClass('is-invalid');
+            $('#forget-name').siblings('div.invalid-feedback').html('登录账号不能为空。');
+            flag = false;
+        }
+        if(flag) {
+            $.ajax({
+                type: 'post',
+                url: '/reset/password',
+                data: { code: $('#forget-project').val(), name: $('#forget-name').val() },
+                dataType: 'json',
+                beforeSend: function(xhr) {
+                    let csrfToken = csrf;
+                    xhr.setRequestHeader('x-csrf-token', csrfToken);
+                },
+                success: function (result) {
+                    if (result.err === 1) {
+                        if (result.index === 1) {
+                            $('#forget-project').addClass('is-invalid');
+                            $('#forget-project').siblings('div.invalid-feedback').html(result.msg);
+                        } else if (result.index === 2) {
+                            $('#forget-name').addClass('is-invalid');
+                            $('#forget-name').siblings('div.invalid-feedback').html(result.msg);
+                        } else {
+                            toast(result.msg, 'error');
+                        }
+                    } else {
+                        $('#fg-password-done').find('b').eq(0).text(result.data.name);
+                        $('#fg-password-done').find('b').eq(1).text(result.data.account);
+                        $('#fg-password-done').find('b').eq(2).text(result.data.mobile);
+                        setLocalCache('project_name', result.data.pName);
+                        setLocalCache('project_code', $('#forget-project').val());
+                        $('#fg-password').modal('hide');
+                        $('#fg-password-done').modal('show');
+                        $('#account').val($('#forget-name').val());
+                        $('#project').val($('#forget-project').val());
+                        $('#project_name').text(result.data.pName);
+                        $('#forget-name').val('');
+                    }
+                }
+            })
+        }
+    })
+
+    $('input').focus(function () {
+        if($(this).hasClass('is-invalid')) {
+            $(this).removeClass('is-invalid');
+            $(this).siblings('div.invalid-feedback').html('');
+        }
+    });
+
+    $('#focus-pwd').click(function () {
+        $('#project-password').focus();
+    })
+});

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

@@ -232,8 +232,9 @@ $(document).ready(() => {
             postData(window.location.pathname + '/save', {type: 'add'}, function (result) {
                 if (result) {
                     materialBillsData.push(result);
-                    sheet.addRows(materialBillsData.length - 1, 1);
-                    SpreadJsObj.reLoadRowData(sheet, materialBillsData.length - 1);
+                    // sheet.addRows(materialBillsData.length - 1, 1);
+                    // SpreadJsObj.reLoadRowData(sheet, materialBillsData.length - 1);
+                    SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialBillsData);
                     sheet.setSelection(materialBillsData.length - 1, 0, 1, 1);
                     materialSpreadObj.refreshActn();
                     // 月信息价需要同时添加空白的list
@@ -710,6 +711,35 @@ $(document).ready(() => {
             }
         },
         deletePress: function (sheet) {
+            // if (sheet.zh_setting && sheet.zh_data) {
+            //     const sel = sheet.getSelections()[0];
+            //     if (!sel) return;
+            //
+            //     const col = sheet.zh_setting.cols[sel.col];
+            //     const select = SpreadJsObj.getSelectObject(sheet);
+            //     const orgValue = select[col.field];
+            //     if (sel.colCount > 1 || sel.rowCount > 1) {
+            //         toastr.warning('请勿同时删除多行或多列数据');
+            //     }
+            //     console.log(sel, select, col);
+                // if (orgValue === null || col.field === 'type' || col.field === 'is_summary') {
+                //     return;
+                // }
+                // select[col.field] = null;
+                // select.calc_num = materialExponentCol.getValue.calc_num(select);
+                // console.log(select);
+                // 更新至服务器
+                // postData(window.location.pathname + '/save', { type:'update', updateData: select }, function (result) {
+                //     ex_tp = result.ex_tp;
+                //     ex_expr = result.ex_expr;
+                //     resetExTpTable();
+                //     SpreadJsObj.reLoadRowData(sheet, sel.row);
+                //     materialExponentData.splice(sel.row, 1, select);
+                // }, function () {
+                //     select[col.field] = orgValue;
+                //     SpreadJsObj.reLoadRowData(sheet, sel.row);
+                // });
+            // }
             return;
         },
         clipboardPasted(e, info) {

+ 62 - 43
app/public/js/material_audit.js

@@ -79,46 +79,61 @@ $(document).ready(function () {
 
     // 添加到审批流程中
     $('dl').on('click', 'dd', function () {
-        const id = parseInt($(this).data('id'))
+        const id = parseInt($(this).data('id'));
         if (id) {
-            postData(getUrlPre() + '/audit/add', { auditorId: id }, (data) => {
+            postData(getUrlPre() + '/audit/add', { auditorId: id }, (datas) => {
                 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(''));
-
                 // 如果是重新上报,添加到重新上报列表中
                 const auditorshtml = [];
-                // 重新上报时。令其它的审批人流程图标转换
-                $('#auditors-list li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
-                for (let i = 0; i < $('#auditors-list li').length; i++) {
-                    $('#auditors-list li').eq(i).find('.pull-right').text(transFormToChinese(i+1) + '审');
-                    $('#auditors-list2 li').eq(i).find('.pull-right').text(transFormToChinese(i+1) + '审');
+                for (const [index,data] of datas.entries()) {
+                    if (index !== 0) {
+                        html.push('<li class="list-group-item" auditorId="'+ data.aid +'">');
+                        if (shenpi_status === shenpiConst.sp_status.sqspr || (shenpi_status === shenpiConst.sp_status.gdzs && index+1 !== datas.length)) {
+                            html.push('<a href="javascript: void(0)" class="text-danger pull-right">移除</a>');
+                        }
+                        html.push('<span>');
+                        html.push(data.order + ' ');
+                        html.push(data.name + ' ');
+                        html.push('</span>');
+                        html.push('<small class="text-muted">');
+                        html.push(data.role);
+                        html.push('</small></li>');
+                    }
+                    // 添加新审批人流程修改
+                    auditorshtml.push('<li class="list-group-item" data-auditorid="' + data.aid + '">');
+                    auditorshtml.push('<i class="fa ' + (index+1 === datas.length ? 'fa-stop-circle' : 'fa-chevron-circle-down') + '"></i> ');
+                    auditorshtml.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
+                    if (index === 0) {
+                        auditorshtml.push('<span class="pull-right">原报</span>');
+                    } else if (index+1 === datas.length) {
+                        auditorshtml.push('<span class="pull-right">终审</span>');
+                    } else {
+                        auditorshtml.push('<span class="pull-right">'+ transFormToChinese(index) +'审</span>');
+                    }
+                    auditorshtml.push('</li>');
                 }
-                // 添加新审批人
-                auditorshtml.push('<li class="list-group-item" data-auditid="' + data.aid + '">');
-                auditorshtml.push('<i class="fa fa-stop-circle"></i> ');
-                auditorshtml.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
-                auditorshtml.push('<span class="pull-right">终审</span>');
-                auditorshtml.push('</li>');
-                $('#auditors-list').append(auditorshtml.join(''));
+                $('#auditors').html(html.join(''));
+
 
-                const auditorshtml2 = [];
                 // 重新上报时。令其它的审批人流程图标转换
-                $('#auditors-list2 li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
-                // 添加新审批人
-                auditorshtml2.push('<li class="list-group-item" data-auditid="' + data.aid + '">');
-                auditorshtml2.push('<h5 class="card-title"><i class="fa fa-stop-circle"></i> ');
-                auditorshtml2.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
-                auditorshtml2.push('<span class="pull-right">终审</span>');
-                auditorshtml2.push('</h5></li>');
-                $('#auditors-list2').append(auditorshtml2.join(''));
+                // $('#auditors-list li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
+                // for (let i = 0; i < $('#auditors-list li').length; i++) {
+                //     $('#auditors-list li').eq(i).find('.pull-right').text(transFormToChinese(i+1) + '审');
+                //     $('#auditors-list2 li').eq(i).find('.pull-right').text(transFormToChinese(i+1) + '审');
+                // }
+
+                $('#auditors-list').html(auditorshtml.join(''));
+
+                // const auditorshtml2 = [];
+                // // 重新上报时。令其它的审批人流程图标转换
+                // $('#auditors-list2 li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
+                // // 添加新审批人
+                // auditorshtml2.push('<li class="list-group-item" data-auditid="' + data.aid + '">');
+                // auditorshtml2.push('<h5 class="card-title"><i class="fa fa-stop-circle"></i> ');
+                // auditorshtml2.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
+                // auditorshtml2.push('<span class="pull-right">终审</span>');
+                // auditorshtml2.push('</h5></li>');
+                // $('#auditors-list2').append(auditorshtml2.join(''));
             });
         }
     });
@@ -137,19 +152,19 @@ $(document).ready(function () {
 
             // 如果是重新上报
             // 令最后一个图标转换
-            $('#auditors-list li[data-auditid="' + data.auditorId + '"]').remove();
+            $('#auditors-list li[data-auditorid="' + data.auditorId + '"]').remove();
             if ($('#auditors-list li').length !== 0 && !$('#auditors-list li i').hasClass('fa-stop-circle')) {
                 $('#auditors-list li').eq($('#auditors-list li').length-1).children('i')
                     .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
             }
-            $('#auditors-list2 li[data-auditid="' + data.auditorId + '"]').remove();
-            if ($('#auditors-list2 li').length !== 0 && !$('#auditors-list2 li i').hasClass('fa-stop-circle')) {
-                $('#auditors-list2 li').eq($('#auditors-list2 li').length-1).children('i')
-                    .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
-            }
-            for (let i = 0; i < $('#auditors-list').length; i++) {
-                $('#auditors-list').eq(i).find('.pull-right').text((i+1 === $('#auditors-list').length ? '终' : transFormToChinese(i+1)) + '审');
-                $('#auditors-list2').eq(i).find('.pull-right').text((i+1 === $('#auditors-list2').length ? '终' : transFormToChinese(i+1)) + '审');
+            // $('#auditors-list2 li[data-auditid="' + data.auditorId + '"]').remove();
+            // if ($('#auditors-list2 li').length !== 0 && !$('#auditors-list2 li i').hasClass('fa-stop-circle')) {
+            //     $('#auditors-list2 li').eq($('#auditors-list2 li').length-1).children('i')
+            //         .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
+            // }
+            for (let i = 0; i < $('#auditors-list li').length; i++) {
+                $('#auditors-list li').eq(i).find('.pull-right').text(i === 0 ? '原报' : (i+1 === $('#auditors-list li').length ? '终' : transFormToChinese(i)) + '审');
+                // $('#auditors-list2').eq(i).find('.pull-right').text((i+1 === $('#auditors-list2').length ? '终' : transFormToChinese(i+1)) + '审');
             }
         });
     });
@@ -169,7 +184,11 @@ $(document).ready(function () {
 // 检查上报情况
 function checkAuditorFrom () {
     if ($('#auditors li').length === 0) {
-        toast('请先选择审批人,再上报数据', 'error', 'exclamation-circle');
+        if(shenpi_status === shenpiConst.sp_status.gdspl) {
+            toastr.error('请联系管理员添加审批人');
+        } else {
+            toastr.error('请先选择审批人,再上报数据');
+        }
         return false;
     }
     $('#hide-all').show();

+ 85 - 14
app/public/js/material_exponent.js

@@ -20,7 +20,8 @@ function resetExTpTable() {
     $('#tp_set').find('td').eq(4).text(ZhCalc.round(ZhCalc.add(ex_pre_tp, ex_tp), 2));
     $('#rate_set').find('td').eq(3).text(bqhs !== 0 ? bqhs : '');
     $('#rate_set').find('td').eq(4).text(jzbqhs !== 0 ? jzbqhs : '');
-    $('#ex_expr').html(ex_expr);
+    // $('#ex_expr').html(ex_expr);
+    $('#ex_expr').attr('data-original-title', '本期价差:' + (ex_expr ? ex_expr : ''));
 }
 $(document).ready(() => {
     autoFlashHeight();
@@ -50,7 +51,7 @@ $(document).ready(() => {
     const materialExponentBase = {
         isUsed: function (data) {
             if (data.type === 2) {
-                return data.mid === materialID || data.weight_num === null;
+                return data.mid === materialID || data.basic_price === null || data.basic_price === 0;
             } else {
                 return false;
             }
@@ -80,7 +81,8 @@ $(document).ready(() => {
                 return !(!readOnly && data.type === 2);
             },
             isConstant: function (data) {
-                return !(!readOnly && materialExponentBase.isConstant(data));
+                // return !(!readOnly && materialExponentBase.isConstant(data));
+                return readOnly;
             }
         },
     };
@@ -204,8 +206,14 @@ $(document).ready(() => {
             if (info.sheet.zh_setting) {
                 const select = SpreadJsObj.getSelectObject(info.sheet);
                 const col = info.sheet.zh_setting.cols[info.col];
-                if (materialExponentCol.readOnly.isEdit(select)) {
-                    return;
+                if (col.field === 'weight_num') {
+                    if(materialExponentCol.readOnly.isConstant(select)) {
+                        return;
+                    }
+                } else {
+                    if(materialExponentCol.readOnly.isEdit(select)) {
+                        return
+                    }
                 }
                 if (col.field === 'is_summary') {
                     if (info.sheet.isEditing()) {
@@ -226,7 +234,60 @@ $(document).ready(() => {
             }
         },
         deletePress: function (sheet) {
-            return;
+            if (!sheet.zh_setting || readOnly || sheet.zh_setting.readOnly) return;
+            // materialExponentBase.isUsed(select)
+            if (sheet.zh_setting && sheet.zh_data) {
+                const sel = sheet.getSelections()[0];
+                if (!sel) return;
+                const sortData = sheet.zh_data || [];
+                const data = [];
+                for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
+                    // let bPaste = true;
+                    const exData = sortData[iRow];
+                    const materialExData = { id: sortData[iRow].id };
+                    for (let iCol = 0; iCol < sel.colCount; iCol++) {
+                        const curCol = sel.col + iCol;
+                        const colSetting = sheet.zh_setting.cols[curCol];
+                        if(colSetting.readOnly(exData)) continue;
+                        if (!colSetting) continue;
+                        const orgValue = sortData[iRow][colSetting.field];
+                        if(orgValue === null) {
+                            // bPaste = false;
+                            continue;
+                        }
+                        if (exData.type === materialType.ex_type[0].value && colSetting.field !== 'weight_num') {
+                            // bPaste = false;
+                            continue;
+                        }
+                        if (colSetting.field === 'type' || colSetting.field === 'calc_num' || colSetting.field === 'is_summary'){
+                            // bPaste = false;
+                            continue;
+                        }
+                        materialExData[colSetting.field] = null;
+                        sortData[iRow][colSetting.field] = null;
+                    }
+                    if (exData.type !== materialType.ex_type[0].value) materialExData.calc_num = materialExponentCol.getValue.calc_num(sortData[iRow]);
+                    const arr = Object.keys(materialExData);
+                    if (arr.length !== 1) {
+                        data.push(materialExData);
+                    }
+                }
+                if (data.length === 0) {
+                    SpreadJsObj.reLoadSheetData(sheet);
+                    return;
+                }
+                // 更新至服务器
+                postData(window.location.pathname + '/save', { type:'paste', updateData: data }, function (result) {
+                    materialExponentData = result.info;
+                    SpreadJsObj.loadSheetData(materialExponentSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialExponentData);
+                    ex_tp = result.ex_tp;
+                    ex_expr = result.ex_expr;
+                    resetExTpTable();
+                }, function () {
+                    SpreadJsObj.reLoadSheetData(sheet);
+                    return;
+                });
+            }
         },
         clipboardPasted(e, info) {
             const hint = {
@@ -301,7 +362,6 @@ $(document).ready(() => {
                             continue;
                         }
                         const total_weight = ZhCalc.add(ZhCalc.sub(_.sumBy(materialExponentData, 'weight_num'), parseFloat(orgValue)), num);
-                        console.log(total_weight, _.sumBy(materialExponentData, 'weight_num'), orgValue, num);
                         if (total_weight > 1) {
                             toastMessageUniq(getPasteHint(hint.weightNumberCan, hintRow));
                             bPaste = false;
@@ -342,14 +402,16 @@ $(document).ready(() => {
     };
     materialExponentSpreadObj.refreshActn();
     materialExponentSpread.bind(spreadNS.Events.SelectionChanged, materialExponentSpreadObj.selectionChanged);
-    materialExponentSpread.bind(spreadNS.Events.ClipboardPasted, materialExponentSpreadObj.clipboardPasted);
-    SpreadJsObj.addDeleteBind(materialExponentSpread, materialExponentSpreadObj.deletePress);
+
+
 
     if (!readOnly) {
         $('#add').click(materialExponentSpreadObj.add);
         $('#del').click(materialExponentSpreadObj.del);
         materialExponentSpread.bind(spreadNS.Events.EditEnded, materialExponentSpreadObj.editEnded);
         materialExponentSpread.bind(spreadNS.Events.ButtonClicked, materialExponentSpreadObj.buttonClicked);
+        materialExponentSpread.bind(spreadNS.Events.ClipboardPasted, materialExponentSpreadObj.clipboardPasted);
+        SpreadJsObj.addDeleteBind(materialExponentSpread, materialExponentSpreadObj.deletePress);
         // 右键菜单
         $.contextMenu({
             selector: '#material-exponent-spread',
@@ -423,7 +485,7 @@ $(document).ready(() => {
         callback: function () {
             materialExponentSpread.refresh();
             const width = (($('#right-view').width()/$('#right-view').parent('div').width())*100).toFixed();
-            // setLocalCache('material_month_' + materialID, width);
+            setLocalCache('material_exponent_' + materialID, width);
         }
     });
 
@@ -439,16 +501,25 @@ $(document).ready(() => {
             showSideTools(tab.hasClass('active'));
             if (tab.attr('content') === '#base-tab') {
                 const width = (($('#right-view').width()/$('#right-view').parent('div').width())*100).toFixed();
-                // setLocalCache('material_month_' + materialID, width);
-                // materialMonthSpread.refresh();
+                setLocalCache('material_exponent_' + materialID, width);
             }
         } else {
-            // removeLocalCache('material_month_' + materialID);
+            removeLocalCache('material_exponent_' + materialID);
             tab.removeClass('active');
             tabPanel.removeClass('active');
             showSideTools(tab.hasClass('active'));
         }
-        // materialSpread.refresh();
         materialExponentSpread.refresh();
     });
+    // 根据浏览器记录展开收起
+    if (getLocalCache('material_exponent_' + materialID)) {
+        const tab = $('.right-nav a[content="#base-tab"]'), tabPanel = $(tab.attr('content'));
+        $('a', '.side-menu').removeClass('active');
+        $('.tab-content .tab-select-show').removeClass('active');
+        tab.addClass('active');
+        tabPanel.addClass('active');
+        $('#right-view').width(getLocalCache('material_exponent_' + materialID) + '%');
+        showSideTools(tab.hasClass('active'));
+        materialExponentSpread.refresh();
+    }
 });

+ 79 - 24
app/public/js/material_file.js

@@ -46,22 +46,21 @@ $(document).ready(function () {
         }
     })
     // 选择/未选所有期列表
-    $('#file-checkbox').click(function() {
-        getAllList()
-    })
+    // $('#file-checkbox').click(function() {
+    //     getAllList()
+    // })
 
     // 删除附件
     $('body').on('click', '.delete-file', function () {
         let attid = $(this).data('attid');
         const data = {id: attid};
-        postData('/tender/measure/material/file/delete', data, function () {
+        postData(window.location.pathname + '/delete', data, function () {
             const idx = fileData.findIndex(file => file.id === parseInt(attid))
             idx !== -1 && fileData.splice(idx, 1)
             const curPageNo = parseInt($('#file-pagination > li[class="page-item active"] > a').text()) || 1
             if ($('#file-list tr').length === 1) {
                 getAllList(curPageNo - 1);
             } else {
-
                 getAllList(curPageNo);
             }
             // self.parents('tr').remove();
@@ -100,23 +99,40 @@ $(document).ready(function () {
 
             curPageNo !== $(this).text() && getAllList(parseInt($(this).text()))
         }
+    });
+
+    $('.dropdown-item').click(function() {
+        const type = $('#dropdownMenuButton').attr('btn-type')
+        if (type === 'curr') {
+            $(this).text('当前期')
+            $('#dropdownMenuButton').text('所有期')
+            $('#dropdownMenuButton').attr('btn-type', 'all')
+        } else {
+            $(this).text('所有期')
+            $('#dropdownMenuButton').text('当前期')
+            $('#dropdownMenuButton').attr('btn-type', 'curr')
+        }
+        getAllList()
     })
     // 生成所有附件列表
     function getAllList(currPageNum = 1) {
+        const isCheckAll = $('#dropdownMenuButton').attr('btn-type') === 'all'
          // 未选中checkbox,需要过滤出来当前期的数据
-        const filterFileData = fileData && fileData.filter(file => file.mid === parseInt(mid) && file.tid === parseInt(tid))
-        const total = calcCount(filterFileData);
+        const filterFileData = fileData && isCheckAll ? fileData : fileData.filter(file => file.mid === parseInt(mid) && file.tid === parseInt(tid))
+        const total = calcCount();
         // 总页数
         const pageNum = Math.ceil(total/pageCount);
         // 当前页附件内容
-        const currPageAttData = fileData && $('#file-checkbox').is(':checked') ? fileData.slice((currPageNum-1)*pageCount, currPageNum*pageCount) : filterFileData.map((v, index) => {
+        const currPageAttData = fileData && isCheckAll ? fileData.map((v, index) => {
+            return {...v, index }
+        }).slice((currPageNum-1)*pageCount, currPageNum*pageCount)
+        : filterFileData.map((v, index) => {
             return {...v, index }
         }).slice((currPageNum-1)*pageCount, currPageNum*pageCount);
-
         renderHtml(currPageAttData)
         // 渲染分页器
         renderPagination(currPageNum, pageNum)
-    }
+    };
 
 
     function renderPagination(pageNo, pageSize) {
@@ -185,55 +201,94 @@ $(document).ready(function () {
         }
 
         $('.page-next').before(html)
-    }
+    };
 
     function renderHtml(list) {
         let html = '';
+        $('#check-all-file').prop("checked", false)
         list.forEach(fileInfo => {
             html += `<tr style="height: 31px;">
+            <td width="25"><input type="checkbox" class="check-file" file-id=${fileInfo.id}></td>
             <td>${fileInfo.index + 1}</td>
             <td><a href="/${fileInfo.filepath}" target="_blank">${fileInfo.file_name}</a></td>
             <td>${fileInfo.file_size}</td>
             <td>第${fileInfo.s_order}期</td>
             <td>${fileInfo.upload_time}</td>`
-            if (fileInfo.canDel ) {
+            if (fileInfo.showDel ) {
                 html += `<td>
-                <a class="btn btn-light btn-sm delete-file" data-attid="${fileInfo.id}" title="删除附件">
+                <a href="/${fileInfo.filepath}" class="btn btn-light btn-sm" title="下载"><span class="fa fa-download text-primary"></span></a>
+                <a href="javascript:void(0);" class="btn btn-light btn-sm delete-file" data-attid="${fileInfo.id}" title="删除附件">
                 <span class="fa fa-trash text-danger"></span>
                 </a>
                 </td></tr>`
             } else {
-                html += `<td></td></tr>`
+                html += `<td><a href="/${fileInfo.filepath}" class="btn btn-light btn-sm" title="下载"><span class="fa fa-download text-primary"></span></a></td></tr>`
             }
         })
         $('#file-list').empty();
         $('#file-list').append(html);
-    }
+    };
+
+    $('#file-list').on('click', '.check-file', function() {
+        const checkedList = $('#file-list').find('input:checked')
+        const childs = $('#file-list').children().length
+        const checkBox = $('#check-all-file')
+        if (checkedList.length === childs) {
+            checkBox.prop("checked", true)
+        } else {
+            checkBox.prop("checked", false)
+        }
+    })
+    $('#check-all-file').click(function() {
+        const isCheck = $(this).is(':checked')
+        $('#file-list').children().each(function() {
+            $(this).find('input:checkbox').prop("checked", isCheck)
+        })
+    });
 
+    $('#bach-download').click(function() {
+        const fileIds = []
+        $( '#file-list .check-file:checked').each(function() {
+            const fileId = $(this).attr('file-id')
+            fileId && fileIds.push(fileId)
+        })
+        // console.log('fileIds', fileIds)
+
+        if (fileIds.length) {
+            // postData( `/tender/${tid}/measure/material/${order}/file/download/compresse-file`, { fileIds })
+            $('#downloadZip').attr('href', `/tender/${tid}/measure/material/${order}/file/download/compresse-file?fileIds=${JSON.stringify(fileIds)}`);
+            $('#downloadZip')[0].click();
+        }
+    });
     function handleFileList(fileList) {
         fileData = fileList.map((file, index) => {
             let showDel = false
             // 只判断当前期,因为以往期都是只读的
             if (file.mid === parseInt(mid) && file.tid === parseInt(tid) && file.user_id === parseInt(cur_uid)) {
-                if (!curAuditor) {
-                    materialStatus === auditConst.status.uncheck && parseInt(cur_uid) === materialUid && (showDel = true)
-                    materialStatus === auditConst.status.checkNo && parseInt(cur_uid) === materialUid && (showDel = true)
+                if (materialStatus === auditConst.status.checked) {
+                    showDel = Boolean(file.extra_upload )
                 } else {
-                    curAuditor.aid === parseInt(cur_uid) && (showDel = true)
+                    showDel = true
                 }
+                // if (!curAuditor) {
+                //     materialStatus === auditConst.status.uncheck && parseInt(cur_uid) === materialUid && (showDel = true)
+                //     materialStatus === auditConst.status.checkNo && parseInt(cur_uid) === materialUid && (showDel = true)
+                // } else {
+                //     curAuditor.aid === parseInt(cur_uid) && (showDel = true)
+                // }
             }
-            return showDel ? {...file, canDel: true, index} : {...file, index}
+            return {...file, index, showDel}
         })
-    }
+    };
 
     function calcCount() {
         // 附件总数
         let total = fileData && fileData.length;
-        if(!$('#file-checkbox').is(':checked')) {
+        if($('#dropdownMenuButton').attr('btn-type') === 'curr') {
             total = fileData && fileData.filter(file => file.mid === parseInt(mid) && file.tid === parseInt(tid)).length
         }
         return total
-    }
+    };
 
     $.subMenu({
         menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
@@ -273,4 +328,4 @@ function validateFiles(files) {
         }
         return true
     })
-}
+};

+ 9 - 9
app/public/js/material_list.js

@@ -131,15 +131,15 @@ $(document).ready(() => {
     const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
     const ledgerSpreadSetting = {
         cols: [
-            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 150, formatter: '@'},
-            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 250, formatter: '@'},
-            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 120, formatter: '@'},
-            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 120, type: 'Number'},
-            {title: '本期计量数量|合同', colSpan: '3|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 120, type: 'Number'},
-            {title: '|数量变更', colSpan: '|1', rowSpan: '|1', field: 'qc_qty', hAlign: 2, width: 120, type: 'Number'},
-            {title: '|小计', colSpan: '|1', rowSpan: '|1', field: 'gather_qty', hAlign: 2, width: 120, type: 'Number'},
-            {title: '本期完成金额', colSpan: '1', rowSpan: '2', field: 'gather_tp', hAlign: 2, width: 150, type: 'Number'},
-            {title: '本期价差', colSpan: '1', rowSpan: '2', field: 'total_jiacha', hAlign:3, width: 150, type: 'Number'}
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 90, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 220, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 80, formatter: '@'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 110, type: 'Number'},
+            {title: '本期计量数量|合同', colSpan: '3|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 110, type: 'Number'},
+            {title: '|数量变更', colSpan: '|1', rowSpan: '|1', field: 'qc_qty', hAlign: 2, width: 110, type: 'Number'},
+            {title: '|小计', colSpan: '|1', rowSpan: '|1', field: 'gather_qty', hAlign: 2, width: 110, type: 'Number'},
+            {title: '本期完成金额', colSpan: '1', rowSpan: '2', field: 'gather_tp', hAlign: 2, width: 110, type: 'Number'},
+            {title: '本期价差', colSpan: '1', rowSpan: '2', field: 'total_jiacha', hAlign:3, width: 110, type: 'Number'}
         ],
         emptyRows: 0,
         headRows: 2,

+ 26 - 17
app/public/js/path_tree.js

@@ -1460,31 +1460,40 @@ const createNewPathTree = function (type, setting) {
             return this._newId++;
         }
 
-        loadCompareNode(node, parent, loadFun) {
-            const siblings = parent ? parent.children : this.children;
-            let cur = siblings.find(function (x) {
-                return node.b_code
-                    ? x.b_code === node.b_code && x.name === node.name && x.unit === node.unit && x.unit_price === node.unit_price
-                    : x.code === node.code && x.name === node.name;
-            });
+        findCompareNode(node, parent) {
+            if (this.setting.findNode) {
+                return this.setting.findNode(this, node, parent);
+            } else {
+                const siblings = parent ? parent.children : this.children;
+                return siblings.find(function (x) {
+                    return node.b_code
+                        ? x.b_code === node.b_code && x.name === node.name && x.unit === node.unit && x.unit_price === node.unit_price
+                        : x.code === node.code && x.name === node.name;
+                });
+            }
+        }
+
+        loadCompareNode(source, node, parent, loadFun) {
+            let cur = this.findCompareNode(node, parent);
             if (!cur) {
+                const siblings = parent ? parent.children : this.children;
                 const id = this.newId;
                 cur = {
-                    id: id,
-                    pid: parent ? parent.id : this.setting.rootId,
-                    full_path: parent ? parent.full_path + '-' + id : '' + id,
-                    level: parent ? parent.level + 1 : 1,
-                    order: siblings.length + 1,
-                    children: [],
+                    children: [], pos: [],
                     code: node.code, b_code: node.b_code, name: node.name,
                     unit: node.unit, unit_price: node.unit_price,
                 };
+                cur[this.setting.id] = id;
+                cur[this.setting.pid] = parent ? parent[this.setting.id] : this.setting.rootId;
+                cur[this.setting.full_path] = parent ? parent[this.setting.full_path] + '-' + id : '' + id;
+                cur[this.setting.level] = parent ? parent[this.setting.level] + 1 : 1;
+                cur[this.setting.order] = siblings.length + 1;
                 siblings.push(cur);
                 this.datas.push(cur);
             }
-            loadFun(cur, node);
+            loadFun(cur, node, source);
             for (const c of node.children) {
-                this.loadCompareNode(c, cur, loadFun);
+                this.loadCompareNode(source, c, cur, loadFun);
             }
         }
     
@@ -1504,9 +1513,8 @@ const createNewPathTree = function (type, setting) {
     
         loadCompareTree(data, loadFun) {
             for (const c of data.billsTree.children) {
-                this.loadCompareNode(c, null, loadFun);
+                this.loadCompareNode(data, c, null, loadFun);
             }
-            // todo load Pos Data;
         }
     
         calculateDiffer() {
@@ -1528,6 +1536,7 @@ const createNewPathTree = function (type, setting) {
             }
             this.generateSortNodes();
             this.calculateDiffer();
+            if (this.setting.afterLoad) this.setting.afterLoad(this);
         }
     }
 

+ 2 - 18
app/public/js/revise.js

@@ -1521,21 +1521,6 @@ $(document).ready(() => {
                 });
             }
         },
-        clipboardPasting: function (e, info) {
-            if (!info.sheet.zh_setting) {
-                info.cancel = true;
-                return;
-            }
-            const range = info.cellRange;
-            for (let iRow = range.row; iRow < range.row + range.rowCount; iRow++) {
-                const posData = info.sheet.zh_data[iRow];
-                if (posData && posData.used) {
-                    toastr.warning('"' + pos.name +'"已计量,请勿修改');
-                    info.cancel = true;
-                    return;
-                }
-            }
-        },
         /**
          * 粘贴单元格响应事件
          * @param e
@@ -1694,7 +1679,6 @@ $(document).ready(() => {
 
         posSpread.bind(spreadNS.Events.EditStarting, posSpreadObj.editStarting);
         posSpread.bind(spreadNS.Events.EditEnded, posSpreadObj.editEnded);
-        posSpread.bind(spreadNS.Events.ClipboardPasting, posSpreadObj.clipboardPasting);
         posSpread.bind(spreadNS.Events.ClipboardPasted, posSpreadObj.clipboardPasted);
         SpreadJsObj.addDeleteBind(posSpread, posSpreadObj.deletePress);
 
@@ -2361,7 +2345,7 @@ $(document).ready(() => {
             defaultRowHeight: 21,
             headerFont: '12px 微软雅黑',
             font: '12px 微软雅黑',
-            headColWidth: [0],
+            headColWidth: [30],
             selectedBackColor: '#fffacd',
         },
         cellDoubleClick: stdLibCellDoubleClick,
@@ -2392,7 +2376,7 @@ $(document).ready(() => {
             defaultRowHeight: 21,
             headerFont: '12px 微软雅黑',
             font: '12px 微软雅黑',
-            headColWidth: [0],
+            headColWidth: [30],
             selectedBackColor: '#fffacd',
         },
         cellDoubleClick: stdLibCellDoubleClick,

+ 462 - 0
app/public/js/revise_compare.js

@@ -0,0 +1,462 @@
+'use strict';
+
+/**
+ * 台账修订页面js
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const billsCompareField = [
+    'code', 'b_code', 'name', 'unit', 'unit_price', 'dgn_qty1', 'dgn_qty2', 'dgn_price',
+    'sgfh_qty', 'sgfh_tp', 'sjcl_qty', 'sjcl_tp', 'qtcl_qty', 'qtcl_tp', 'quantity', 'total_price',
+    'deal_qty', 'deal_tp'
+];
+const posCompareField = [
+    'name', 'position', 'sgfh_qty', 'sjcl_qty', 'qtcl_qty', 'quantity'
+];
+
+$(document).ready(() => {
+    let searchLedger;
+    autoFlashHeight();
+    // 初始化spread
+    const billsSpread = SpreadJsObj.createNewSpread($('#bills-spread')[0]);
+    const billsSheet = billsSpread.getActiveSheet();
+    sjsSettingObj.setFxTreeStyle(billsSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    if (thousandth) {
+        sjsSettingObj.setTpThousandthFormat(billsSpreadSetting);
+        sjsSettingObj.setTpColsThousandthFormat(billsSpreadSetting.extraCols);
+    }
+    billsSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
+        // 增
+        if (data.differ.indexOf('add') >= 0) return '#ffddc5';
+        // 删
+        if (data.differ.indexOf('del') >= 0) return '#DCDCDC';
+        // 结构变动
+        if (data.differ.indexOf('tree') >= 0) return '#d0f6fd';
+        // 修改计算或文字
+        if (data.differ.indexOf('calc') >= 0 || data.differ.indexOf('pos-add') >= 0 || data.differ.indexOf('pos-del') >= 0 || data.differ.indexOf('pos-calc') >= 0) return '#f8d7da';
+        if (data.differ.indexOf('info') >= 0 || data.differ.indexOf('pos-info') >= 0) return '#d4edda';
+        // 层次结构
+        if (data.level === 2) {
+            return '#C4CAFB';
+        } else if ((!data.b_code || data.b_code === '') && data.level > 2) {
+            return '#DFE8F9';
+        } else {
+            return defaultColor;
+        }
+    };
+    SpreadJsObj.initSheet(billsSheet, billsSpreadSetting);
+
+    const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
+    const posSheet = posSpread.getActiveSheet();
+    sjsSettingObj.setGridSelectStyle(posSpreadSetting);
+    if (thousandth) {
+        sjsSettingObj.setTpThousandthFormat(posSpreadSetting);
+        sjsSettingObj.setTpColsThousandthFormat(posSpreadSetting.extraCols);
+    }
+
+    posSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
+        if (!data || !data.differ) return defaultColor;
+        // 增
+        if (data.differ.indexOf('add') >= 0) return '#ffddc5';
+        // 删
+        if (data.differ.indexOf('del') >= 0) return '#DCDCDC';
+        // 修改计算或文字
+        if (data.differ.indexOf('calc') >= 0) return '#f8d7da';
+        if (data.differ.indexOf('info') >= 0) return '#d4edda';
+    };
+    SpreadJsObj.initSheet(posSheet, posSpreadSetting);
+
+    const posSearch = $.posSearch({selector: '#pos-search', searchSpread: posSpread});
+
+    // 初始化 节点树结构
+    const treeSetting = {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        full_path: 'full_path',
+        rootId: -1,
+        keys: ['id', 'tender_id', 'ledger_id'],
+        calcFields: [
+            'org_sgfh_tp', 'org_sjcl_tp', 'org_qtcl_tp', 'org_total_price', 'org_deal_tp',
+            'new_sgfh_tp', 'new_sjcl_tp', 'new_qtcl_tp', 'new_total_price', 'new_deal_tp',
+            'contract_tp', 'qc_tp', 'pre_contract_tp', 'pre_qc_tp'
+        ],
+        findNode: function (tree, node, parent) {
+            const sameId = tree.datas.find(x => {return x.id === node.id});
+            if (sameId) {
+                const pid = parent ? parent[tree.setting.id] : tree.setting.rootId;
+                if (sameId[tree.setting.pid] !== pid) {
+                    sameId.changeParent = true;
+                    sameId.org_parent = parent;
+                }
+                return sameId;
+            } else {
+                const siblings = parent ? parent.children : tree.children;
+                return siblings.find(function (x) {
+                    return node.b_code
+                        ? x.b_code === node.b_code && x.name === node.name && x.unit === node.unit && x.unit_price === node.unit_price
+                        : x.code === node.code && x.name === node.name;
+                });
+            }
+        },
+        loadInfo1: function (node, sourceNode, source) {
+            for (const f of billsCompareField) {
+                node['new_' + f] = sourceNode[f];
+            }
+            node.id = sourceNode.id;
+            node.isNew = true;
+            const posRange = source.pos.getLedgerPos(sourceNode.id);
+            if (posRange && posRange.length > 0) {
+                if (!node.pos) node.pos = [];
+                for (const p of posRange) {
+                    let nP = _.find(node.pos, {id: p.id});
+                    if (!nP) {
+                        nP = {id: p.id};
+                        node.pos.push(nP);
+                    }
+                    for (const f of posCompareField) {
+                        nP['new_' + f] = p[f];
+                    }
+                    nP.isNew = true;
+                }
+            }
+        },
+        loadInfo2: function (node, sourceNode, source) {
+            for (const f of billsCompareField) {
+                node['org_' + f] = sourceNode[f];
+            }
+            node.isOrg = true;
+            const posRange = source.pos.getLedgerPos(sourceNode.id);
+            if (posRange && posRange.length > 0) {
+                if (!node.pos) node.pos = [];
+                for (const p of posRange) {
+                    let nP = _.find(node.pos, {id: p.id});
+                    if (!nP) {
+                        nP = {id: p.id};
+                        node.pos.push(nP);
+                    }
+                    for (const f of posCompareField) {
+                        nP['org_' + f] = p[f];
+                    }
+                    nP.isOrg = true;
+                }
+            }
+        },
+        afterLoad: function (tree) {
+            for (const data of tree.datas) {
+                data.differ = [];
+                data.differ_str = [];
+                for (const p of data.pos) {
+                    p.differ = [];
+                    p.differ_str = [];
+                    if (p.isNew && !p.isOrg) {
+                        p.differ.push('add');
+                        if (data.differ.indexOf('pos-add') === -1) data.differ.push('pos-add');
+                        p.differ_str.push('增');
+                    } else if (!p.isNew && p.isOrg) {
+                        p.differ.push('del');
+                        if (data.differ.indexOf('pos-del') === -1) data.differ.push('pos-del');
+                        p.differ_str.push('删');
+                    } else {
+                        const orgPosCalc = getCompare(p, compareFields.posCalc, 'org_', 0);
+                        const newPosCalc = getCompare(p, compareFields.posCalc, 'new_', 0);
+                        if (!_.isMatch(orgPosCalc, newPosCalc)) {
+                            p.differ.push('calc');
+                            if (data.differ.indexOf('pos-calc') === -1) data.differ.push('pos-calc');
+                            p.differ_str.push('量改');
+                        }
+                        const orgPosInfo = getCompare(p, compareFields.posInfo, 'org_', 0);
+                        const newPosInfo = getCompare(p, compareFields.posInfo, 'new_', 0);
+                        if (!_.isMatch(orgPosInfo, newPosInfo)) {
+                            p.differ.push('info');
+                            if (data.differ.indexOf('pos-info') === -1) data.differ.push('pos-info');
+                            p.differ_str.push('文改');
+                        }
+                    }
+                }
+                if (data.isNew && !data.isOrg) {
+                    data.differ.push('add');
+                    data.differ_str.push('增');
+                } else if (!data.isNew && data.isOrg) {
+                    data.differ.push('del');
+                    data.differ_str.push('删');
+                } else {
+                    if (data.changeParent) data.differ.push('tree');
+                    if (!data.children || data.children.length === 0) {
+                        const orgCalc = getCompare(data, compareFields.leafCalc, 'org_', 0);
+                        const newCalc = getCompare(data, compareFields.leafCalc, 'new_', 0);
+                        if (!_.isMatch(newCalc, orgCalc)) data.differ.push('calc');
+                    } else {
+                        const orgCalc = getCompare(data, compareFields.parentCalc, 'org_', 0);
+                        const newCalc = getCompare(data, compareFields.parentCalc, 'new_', 0);
+                        if (!_.isMatch(newCalc, orgCalc)) data.differ.push('calc');
+                    }
+                    const orgInfo = getCompare(data, compareFields.info, 'org_', '');
+                    const newInfo = getCompare(data, compareFields.info, 'new_', '');
+                    if (!_.isMatch(newInfo, orgInfo)) data.differ.push('info');
+
+                    if (data.differ.length > 0) data.differ_str.push('改');
+                }
+            }
+        }
+    };
+    if (!isTz) {
+        treeSetting.calcFields.push('deal_tp');
+    }
+    treeSetting.calcFun = function (node) {
+        if (!node.children || node.children.length === 0) {
+            // node.gather_qty = ZhCalc.add(node.contract_qty, node.qc_qty);
+            // node.pre_gather_qty = ZhCalc.add(node.pre_contract_qty, node.pre_qc_qty);
+            node.end_contract_qty = ZhCalc.add(node.contract_qty, node.pre_contract_qty);
+            node.end_qc_qty = ZhCalc.add(node.qc_qty, node.pre_qc_qty);
+            // node.end_gather_qty = ZhCalc.add(node.end_contract_qty, node.end_qc_qty);
+        }
+        // node.gather_tp = ZhCalc.add(node.contract_tp, node.qc_tp);
+        // node.pre_gather_tp = ZhCalc.add(node.pre_contract_tp, node.pre_qc_tp);
+        node.end_contract_tp = ZhCalc.add(node.contract_tp, node.pre_contract_tp);
+        node.end_qc_tp = ZhCalc.add(node.qc_tp, node.pre_qc_tp);
+        // node.end_gather_tp = ZhCalc.add(node.end_contract_tp, node.end_qc_tp);
+        node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
+    };
+    const billsTree = createNewPathTree('compare', treeSetting);
+
+    // 清单 相关方法&绑定spreadjs事件
+    const billsTreeSpreadObj = {
+        selectionChanged: function (e, info) {
+            if (info.newSelections) {
+                if (!info.oldSelections || info.newSelections[0].row !== info.oldSelections[0].row) {
+                    posSpreadObj.loadCurPosData();
+                    posSearch.search($('#pos-keyword').val());
+                }
+            }
+        },
+    };
+    billsSpread.bind(spreadNS.Events.SelectionChanged, billsTreeSpreadObj.selectionChanged);
+
+    // 计量单元 相关方法&绑定spreadjs事件
+    const posSpreadObj = {
+        /**
+         * 加载计量单元 根据当前台账选择节点
+         */
+        loadCurPosData: function () {
+            const node = SpreadJsObj.getSelectObject(billsSheet);
+            if (node) {
+                SpreadJsObj.loadSheetData(posSheet, 'data', node.pos || []);
+            } else {
+                SpreadJsObj.loadSheetData(posSheet, 'data', []);
+            }
+        },
+    };
+
+    // 加载清单&计量单元数据
+    postData('/tender/' + window.location.pathname.split('/')[2] + '/revise/load', {filter: 'bills;pos;reviseBills;revisePos'}, function (result) {
+        const tenderTreeSetting = {
+            id: 'ledger_id',
+            pid: 'ledger_pid',
+            order: 'order',
+            level: 'level',
+            rootId: -1,
+            keys: ['id', 'tender_id', 'ledger_id'],
+            calcFields: ['deal_tp', 'sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price'],
+        };
+        const reviseLedger = {
+            billsTree: createNewPathTree('ledger', tenderTreeSetting),
+            pos: new PosData({ id: 'id', ledgerId: 'lid', }),
+        };
+        reviseLedger.billsTree.loadDatas(result.reviseBills);
+        reviseLedger.pos.loadDatas(result.revisePos);
+        const orgLedger = {
+            billsTree: createNewPathTree('ledger', tenderTreeSetting),
+            pos: new PosData({ id: 'id', ledgerId: 'lid', }),
+        };
+        orgLedger.billsTree.loadDatas(result.bills);
+        orgLedger.pos.loadDatas(result.pos);
+
+        billsTree.loadCompareData(reviseLedger, orgLedger);
+        treeCalc.calculateAll(billsTree);
+        SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, billsTree);
+
+        posSpreadObj.loadCurPosData();
+    }, null);
+    $.divResizer({
+        select: '#revise-resize',
+        callback: function () {
+            billsSpread.refresh();
+            let bcontent = $(".bcontent-wrap") ? $(".bcontent-wrap").height() : 0;
+            $(".sp-wrap").height(bcontent-30);
+            posSpread.refresh();
+        }
+    });
+
+    $.divResizer({
+        select: '#revise-right-spr',
+        callback: function () {
+            billsSpread.refresh();
+            if (posSpread) {
+                posSpread.refresh();
+            }
+            if (searchLedger) {
+                searchLedger.spread.refresh();
+            }
+        }
+    });
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+            billsSpread.refresh();
+            if (posSpread) {
+                posSpread.refresh();
+            }
+            if (searchLedger) {
+                searchLedger.spread.refresh();
+            }
+        }
+    });
+
+    // 展开收起标准节点
+    $('a', '#side-menu').bind('click', function (e) {
+        e.preventDefault();
+        const tab = $(this), tabPanel = $(tab.attr('content'));
+        // 展开工具栏、切换标签
+        if (!tab.hasClass('active')) {
+            const close = $('.active', '#side-menu').length === 0;
+            $('a', '#side-menu').removeClass('active');
+            tab.addClass('active');
+            $('.tab-content .tab-pane').removeClass('active');
+            tabPanel.addClass('active');
+            showSideTools(tab.hasClass('active'));
+            if (tab.attr('content') === '#search') {
+                if (!searchLedger) {
+                    searchLedger = $.billsSearch({
+                        selector: '#search',
+                        searchSpread: billsSpread,
+                        resultSpreadSetting: {
+                            cols: [
+                                {title: '项目节编号', field: 'code', hAlign: 0, width: 90, formatter: '@', readOnly: true},
+                                {title: '清单编号', field: 'b_code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
+                                {title: '名称', field: 'name', width: 150, hAlign: 0, formatter: '@', readOnly: true},
+                                {title: '单位', field: 'unit', width: 50, hAlign: 1, formatter: '@', readOnly: true},
+                            ],
+                            emptyRows: 0,
+                            headRows: 1,
+                            headRowHeight: [32],
+                            headColWidth: [30],
+                            defaultRowHeight: 21,
+                            headerFont: '12px 微软雅黑',
+                            font: '12px 微软雅黑',
+                            selectedBackColor: '#fffacd',
+                        },
+                        afterLocated: function () {
+                            posSpreadObj.loadCurPosData();
+                        }
+                    });
+                }
+                searchLedger.spread.refresh();
+            }
+        }
+        else {// 收起工具栏
+            tab.removeClass('active');
+            tabPanel.removeClass('active');
+            showSideTools(tab.hasClass('active'));
+        }
+        billsSpread.refresh();
+        if (posSpread) {
+            posSpread.refresh();
+        }
+    });
+
+    // 显示层次
+    (function (select, sheet) {
+        $(select).click(function () {
+            if (!sheet.zh_tree) return;
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            switch (tag) {
+                case "1":
+                case "2":
+                case "3":
+                case "4":
+                case "5":
+                    tree.expandByLevel(parseInt(tag));
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "last":
+                    tree.expandByCustom(() => { return true; });
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "leafXmj":
+                    tree.expandToLeafXmj();
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "differ":
+                    tree.expandByCustom(function (x) {
+                        const posterity = tree.getPosterity(x);
+                        if (posterity.length === 0) return x.differ.length > 0;
+                        for (const p of posterity) {
+                            if (p.differ.length > 0) return true;
+                        }
+                        return false;
+                    });
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+            }
+        });
+    })('a[name=showLevel]', billsSheet);
+
+    // 加载计量数据
+    $('#load-stage-ok').click(function (x) {
+        postData('/tender/' + window.location.pathname.split('/')[2] + '/revise/load', {filter: 'stageBills;stagePos'}, function (result) {
+            const col = billsSpreadSetting.cols.length;
+            billsSpreadSetting.cols = billsSpreadSetting.cols.concat(billsSpreadSetting.extraCols);
+            SpreadJsObj.reinitSheetHeader(billsSheet);
+            for (const b of result.stageBills) {
+                const node = billsTree.nodes.find(x => {return x.id === b.lid});
+                if (!node) continue;
+
+                for (const f of ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'pre_contract_qty', 'pre_contract_tp', 'pre_qc_qty', 'pre_qc_tp']) {
+                    node[f] = b[f];
+                }
+            }
+            treeCalc.calculateAll(billsTree);
+            SpreadJsObj.reloadColData(billsSheet, col, billsSpreadSetting.extraCols.length);
+
+            posSpreadSetting.cols = posSpreadSetting.cols.concat(posSpreadSetting.extraCols);
+            SpreadJsObj.reinitSheetHeader(posSheet);
+            // todo 加载并计算计量单元期数据
+            for (const p of result.stagePos) {
+                const node = billsTree.nodes.find(x => {return x.id === p.lid});
+                if (!node) continue;
+                const pos = node.pos.find(x => {return x.id === p.pid});
+                if (!pos) continue;
+
+                pos.contract_qty = p.contract_qty;
+                pos.qc_qty = p.qc_qty;
+                //pos.gather_qty = ZhCalc.add(pos.contract_qty, pos.qc_qty);
+                pos.pre_contract_qty = p.pre_contract_qty;
+                pos.pre_qc_qty = p.pre_qc_qty;
+                //pos.pre_gather_qty = ZhCalc.add(pos.pre_contract_qty, pos.pre_qc_qty);
+                pos.end_contract_qty = ZhCalc.add(pos.contract_qty, pos.pre_contract_qty);
+                pos.end_qc_qty = ZhCalc.add(pos.qc_qty, pos.pre_qc_qty);
+                //pos.end_gather_qty = ZhCalc.add(pos.gather_qty, pos.pre_gather_qty);
+            }
+            posSpreadObj.loadCurPosData();
+            $('#load-stage-btn').hide();
+            $('#load-stage').modal('hide');
+        });
+    });
+});
+

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

@@ -0,0 +1,230 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2020/10/28
+ * @version
+ */
+const showTools = function (show) {
+    const left = $('#left-view'), right = $('#right-view'), parent = left.parent();
+    if (show) {
+        right.show();
+        autoFlashHeight();
+        /**
+         * right.show()后, parent被撑开成2倍left.height, 导致parent.width减少了10px
+         * 第一次left.width调整后,parent的缩回left.height, 此时parent.width又增加了10px
+         * 故需要通过最终的parent.width再计算一次left.width
+         *
+         * Q: 为什么不通过先计算left.width的宽度,以避免计算两次left.width?
+         * A: 右侧工具栏不一定显示,当右侧工具栏显示过一次后,就必须使用parent和right来计算left.width
+         *
+         */
+            //left.css('width', parent.width() - right.outerWidth());
+            //left.css('width', parent.width() - right.outerWidth());
+        const percent = 100 - right.outerWidth() /parent.width() * 100;
+        left.css('width', percent + '%');
+    } else {
+        right.hide();
+        left.css('width', '100%');
+    }
+};
+
+$(document).ready(() => {
+    let compareTag = $('input[name=compareTag]:checked').val();
+    showTools(true);
+    autoFlashHeight();
+    const gclSpread = SpreadJsObj.createNewSpread($('#gcl-spread')[0]);
+    const gclSpreadSetting = {
+        cols: [
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 120, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', cellType: 'unit'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 80, type: 'Number'},
+            {title: '签约清单|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_bills_qty', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_bills_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '台账修订|数量', colSpan: '2|1', rowSpan: '1|1', field: 'new_quantity', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'new_total_price', hAlign: 2, width: 80, type: 'Number'},
+            {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'org_quantity', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'org_total_price', hAlign: 2, width: 80, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        headColWidth: [30],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+        localCache: {
+            key: 'ledger-gather-gcl',
+            colWidth: true,
+        },
+        getColor: function (sheet, data, row, col, defaultColor) {
+            if (!data) return '#ffffff';
+            // if ((col.field === 'deal_bills_qty' || col.field === 'deal_bills_tp') && data.deal_differ_qty)
+            //     return '#f8d7da';
+            // if ((col.field === 'org_quantity' || col.field === 'org_total_price') && data.differ_qty)
+            //     return '#f8d7da';
+            if (compareTag === 'deal' && data.deal_differ_qty) return '#f8d7da';
+            if (compareTag === 'org' && data.differ_qty) return '#f8d7da';
+            return data.differ ? '#FFE699' : '#ffffff';
+        }
+    };
+    if (thousandth) sjsSettingObj.setTpThousandthFormat(gclSpreadSetting);
+    SpreadJsObj.initSheet(gclSpread.getActiveSheet(), gclSpreadSetting);
+    const gclSheet = gclSpread.getActiveSheet();
+    const leafXmjSpread = SpreadJsObj.createNewSpread($('#leaf-xmj-spread')[0]);
+    const leafXmjSpreadSetting = {
+        cols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 120, formatter: '@'},
+            {title: '修订台账|数量', colSpan: '1', rowSpan: '1|1', field: 'new_quantity', hAlign: 2, width: 80, type: 'Number'},
+            {title: '原台账|数量', colSpan: '1', rowSpan: '1|1', field: 'org_quantity', hAlign: 2, width: 80, type: 'Number'},
+            {title: '单位工程', colSpan: '1', rowSpan: '2', field: 'dwgc', hAlign: 0, width: 100, formatter: '@'},
+            {title: '分部工程', colSpan: '1', rowSpan: '2', field: 'fbgc', hAlign: 0, width: 100, formatter: '@'},
+            {title: '分项工程', colSpan: '1', rowSpan: '2', field: 'fxgc', hAlign: 0, width: 100, formatter: '@'},
+            {title: '细目', colSpan: '1', rowSpan: '2', field: 'jldy', hAlign: 0, width: 100, formatter: '@'},
+            {title: '计量单元', colSpan: '1', rowSpan: '2', field: 'bwmx', hAlign: 0, width: 100, formatter: '@'},
+            {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 120, formatter: '@'},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [25, 25],
+        headColWidth: [30],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+        localCache: {
+            key: 'ledger-gather-leafXmj',
+            colWidth: true,
+        },
+    };
+    if (thousandth) sjsSettingObj.setTpThousandthFormat(leafXmjSpreadSetting);
+    SpreadJsObj.initSheet(leafXmjSpread.getActiveSheet(), leafXmjSpreadSetting);
+    const leafXmjSheet = leafXmjSpread.getActiveSheet();
+
+    let gclData = [];
+    // 获取项目节数据
+    function loadLeafXmjData(iGclRow) {
+        const gcl = gclData[iGclRow];
+        SpreadJsObj.resetTopAndSelect(leafXmjSheet);
+        if (gcl) {
+            SpreadJsObj.loadSheetData(leafXmjSheet, SpreadJsObj.DataType.Data, gcl.leafXmjs);
+        } else {
+            SpreadJsObj.loadSheetData(leafXmjSheet, SpreadJsObj.DataType.Data, []);
+        }
+    }
+    // 切换清单行,读取所属项目节数据
+    gclSpread.getActiveSheet().bind(spreadNS.Events.SelectionChanged, function (e, info) {
+        const iNewRow = info.newSelections[0].row;
+        if (!info.oldSelections || iNewRow !== info.oldSelections[0].row) {
+            loadLeafXmjData(iNewRow);
+        }
+    });
+
+    function generateChapterHtml(data) {
+        const html = [];
+        if (data) {
+            for (const d of data) {
+                if (['1000', '1100', '1200', '1300'].indexOf(d.code) >= 0) {
+                    if (checkZero(d.total_price) && checkZero(d.deal_bills_tp)) {
+                        continue;
+                    }
+                }
+                html.push('<tr>');
+                if (d.code) {
+                    html.push('<td>', d.code, '</td>');
+                    html.push('<td>', d.name, '</td>');
+                } else {
+                    html.push('<td colspan="2">', d.name, '</td>');
+                }
+                html.push('<td class="text-right">', d.deal_bills_tp ? d.deal_bills_tp : '', '</td>');
+                html.push('<td class="text-right">', d.new_total_price ? d.new_total_price : '', '</td>');
+                html.push('<td class="text-right">', d.org_total_price ? d.org_total_price : '', '</td>');
+                html.push('</tr>');
+            }
+        }
+        $('#chapter-list').html(html.join(''));
+    }
+
+    postData('/tender/' + window.location.pathname.split('/')[2] + '/revise/load', {filter: 'bills;pos;reviseBills;revisePos;dealBills'}, function (data) {
+        const setting = {
+            tree: {
+                id: 'ledger_id',
+                pid: 'ledger_pid',
+                order: 'order',
+                level: 'level',
+                rootId: -1,
+                keys: ['id', 'tender_id', 'ledger_id'],
+                stageId: 'id',
+            },
+            pos: { id: 'id', ledgerId: 'lid' },
+            billsFields: ['quantity', 'total_price'],
+            posFields: ['quantity'],
+            chapterFields: ['total_price'],
+        };
+        gclCompareModel.init(gclData, chapter);
+        setting.prefix = 'new_';
+        gclCompareModel.gatherLedgerData(data.reviseBills, data.revisePos, setting);
+        setting.prefix = 'org_';
+        gclCompareModel.gatherLedgerData(data.bills, data.pos, setting);
+        gclCompareModel.gatherDealBills(data.dealBills);
+        gclCompareModel.checkDiffer();
+        SpreadJsObj.loadSheetData(gclSheet, SpreadJsObj.DataType.Data, gclData);
+        loadLeafXmjData(0);
+
+        const chapterData = gclCompareModel.chapterData();
+        generateChapterHtml(chapterData);
+    }, null, true);
+
+    // 展开收起附件
+    $('a', '.right-nav').bind('click', function () {
+        const tab = $(this), tabPanel = $(tab.attr('content'));
+        if (!tab.hasClass('active')) {
+            $('a', '.side-menu').removeClass('active');
+            $('.tab-content .tab-select-show').removeClass('active');
+            tab.addClass('active');
+            tabPanel.addClass('active');
+            showTools(tab.hasClass('active'));
+        } else {
+            tab.removeClass('active');
+            tabPanel.removeClass('active');
+            showTools(tab.hasClass('active'));
+        }
+        gclSpread.refresh();
+        leafXmjSpread.refresh();
+    });
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+            gclSpread.refresh();
+            leafXmjSpread.refresh();
+        }
+    });
+    $.divResizer({
+        select: '#main-resize',
+        callback: function () {
+            gclSpread.refresh();
+            let bcontent = $(".bcontent-wrap") ? $(".bcontent-wrap").height() : 0;
+            $(".sp-wrap").height(bcontent-30);
+            leafXmjSpread.refresh();
+        }
+    });
+    $('input[name=compareTag]').change(() => {
+        compareTag = $('input[name=compareTag]:checked').val();
+        SpreadJsObj.reLoadSheetData(gclSheet);
+    });
+});

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

@@ -57,9 +57,19 @@ const showSideTools = function (show) {
         if (!setting.spreadSetting) {
             setting.spreadSetting = {
                 cols: [
-                    {title: '行号', field: 'serialNo', width: 80, formatter: '@'},
-                    {title: '清单编号', field: 'b_code', width: 150, formatter: '@'},
-                    {title: '清单名称', field: 'name', width: 230, formatter: '@'},
+                    {title: '行号', field: 'serialNo', width: 50, formatter: '@'},
+                    {
+                        title: '错误类型', field: 'errorType', width: 60, formatter: '@',
+                        getValue: function (x) {
+                            switch (x.errorType) {
+                                case 'qty': return '数量';
+                                case 'tp': return '金额';
+                                default: return '';
+                            }
+                        }
+                    },
+                    {title: '清单编号', field: 'b_code', width: 135, formatter: '@'},
+                    {title: '清单名称', field: 'name', width: 215, formatter: '@'},
                 ],
                 emptyRows: 0,
                 headRows: 1,
@@ -300,7 +310,7 @@ const showSideTools = function (show) {
                     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'));
+                $('#' + 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));
@@ -469,7 +479,7 @@ const showSideTools = function (show) {
             }
             filter.push('</select>');
             // filter.push('<div class="input-group-prepend">');
-            // filter.push('<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">台</button>');
+            // filter.push('<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">台</button>');
             // filter.push('<div class="dropdown-menu">');
             // filter.push('<a class="dropdown-item" href="javascripty: void(0)" id="search-bills">台账</a>');
             // filter.push('<a class="dropdown-item" href="javascripty: void(0)" id="search-over">超计</a>');

+ 450 - 0
app/public/js/shares/gcl_gather_compare.js

@@ -0,0 +1,450 @@
+'use strict';
+
+/**
+ *
+ * 清单汇总对比(需使用 decimal.min.js, zh_calc.js, path_tree.js, lodash.js)
+ *
+ * @author Mai
+ * @date 2020/10/28
+ * @version
+ */
+
+const gclCompareModel = (function () {
+    const leafXmjs = [], mergeChar = ';';
+    let gclList, gclChapter, otherChapter;
+    let ledgerSetting, gsTree;
+
+    function gatherfields(obj, src, fields, prefix = '') {
+        if (obj && src) {
+            for (const f of fields) {
+                obj[prefix + f] = ZhCalc.add(obj[prefix + f], src[f]);
+            }
+        }
+    }
+    /**
+     * 新建 清单汇总节点
+     * @param node - 最底层 工程量清单节点
+     * @returns {obj}
+     */
+    function newGclNode(node) {
+        const gcl = {
+            b_code: node.b_code,
+            name: node.name,
+            unit: node.unit,
+            unit_price: node.unit_price,
+            leafXmjs: [],
+        };
+        gclList.push(gcl);
+        return gcl;
+    }
+
+    /**
+     * 获取清单汇总节点
+     *
+     * @param node - 最底层清单节点
+     * @returns {*}
+     */
+    function getGclNode(node) {
+        const gcl = gclList.find(function (g) {
+            return g.b_code === node.b_code &&
+                (g.name || node.name ? g.name === node.name : true) &&
+                (g.unit || node.unit ? g.unit === node.unit : true) &&
+                checkZero(ZhCalc.sub(g.unit_price, node.unit_price));
+        });
+        if (gcl) {
+            return gcl
+        } else {
+            return newGclNode(node);
+        }
+    }
+
+    /**
+     * 检查 text 是否是Peg
+     * e.g. K123+000(true) Kab+123(false) K123.234+234(false) K12+324.234(true)
+     *
+     * @param text
+     * @returns {*}
+     * @constructor
+     */
+    function CheckPeg(text) {
+        const pegReg = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+        return pegReg.test(text);
+    }
+
+    /**
+     * 获取 桩号节点
+     * @param node - 检索起始节点
+     * @returns {*}
+     */
+    function getPegNode (node) {
+        if (node) {
+            if (CheckPeg(node.name)) {
+                return node;
+            } else {
+                const parent = gsTree.getParent(node);
+                return parent ? getPegNode(parent) : null;
+            }
+        }
+    }
+
+    /**
+     * 获取节点的第N层父节点
+     *
+     * @param node - 节点(检索起点)
+     * @param level - 第N层
+     * @returns {*}
+     */
+    function getNodeByLevel(node, level) {
+        let cur = node;
+        while (cur && cur.level > level) {
+            cur = gsTree.getParent(cur);
+        }
+        return cur;
+    }
+
+    /**
+     * 获取 单位工程
+     *
+     * @param xmj - 计量单元(最底层项目节)
+     * @returns {string}
+     */
+    function getDwgc(peg, xmj) {
+        if (peg) {
+            return peg.name;
+        } else {
+            const node = getNodeByLevel(xmj, 2);
+            return node ? node.name : '';
+        }
+    }
+
+    /**
+     * 获取 分部工程
+     *
+     * @param peg - 桩号节点
+     * @param xmj - 计量单元(最底层项目节)
+     * @returns {string}
+     */
+    function getFbgc(peg, xmj) {
+        if (peg && peg.id !== xmj.id) {
+            const node = getNodeByLevel(xmj, peg.level + 1);
+            return node ? node.name : '';
+        } else {
+            const node = getNodeByLevel(xmj, 3);
+            return node ? node.name : '';
+        }
+    }
+
+    /**
+     * 获取 分项工程
+     *
+     * @param peg - 桩号节点
+     * @param xmj - 计量单元(最底层项目节)
+     * @returns {string}
+     */
+    function getFxgc(peg, xmj) {
+        if (!peg) {
+            const node = getNodeByLevel(xmj, 4);
+            return node ? node.name : '';
+        } else if (peg.id === xmj.id) {
+            if (xmj.level > 4) {
+                let value = '';
+                for (let level = 4; level < xmj.level; level++) {
+                    const node = getNodeByLevel(xmj, level);
+                    value = value === '' ? node.name : value + mergeChar + node.name;
+                }
+                return value;
+            } else {
+                return '';
+            }
+        } else {
+            if (peg.level + 2 < xmj.level) {
+                let value = '';
+                for (let level = peg.level + 2; level < xmj.level; level++) {
+                    const node = getNodeByLevel(xmj, level);
+                    value = value === '' ? node.name : value + mergeChar + node.name;
+                }
+                return value;
+            } else {
+                return '';
+            }
+        }
+    }
+
+    /**
+     * 新建 最底层项目节 缓存数据
+     * @param leafXmj
+     * @returns {{id, code: *|string[], jldy, fbgc: string, fxgc: string, dwgc: string, bwmx: string, drawing_code: string}}
+     */
+    function newCacheLeafXmj(leafXmj) {
+        const peg = getPegNode(leafXmj);
+        const cacheLX = {
+            id: leafXmj.id,
+            code: leafXmj.code,
+            jldy: leafXmj.name,
+            fbgc: getFbgc(peg, leafXmj),
+            fxgc: getFxgc(peg, leafXmj),
+            dwgc: getDwgc(peg, leafXmj),
+            drawing_code: leafXmj.drawing_code,
+        };
+        leafXmjs.push(cacheLX);
+        return cacheLX;
+    }
+
+    /**
+     * 获取缓存的最底层项目节数据
+     *
+     * @param leafXmj - 最底层项目节
+     * @returns {*}
+     */
+    function getCacheLeafXmj(leafXmj) {
+        const cacheLX = leafXmjs.find(function (lx) {
+            return lx.id === leafXmj.id;
+        });
+        if (!cacheLX) {
+            return newCacheLeafXmj(leafXmj);
+        } else {
+            return cacheLX;
+        }
+    }
+
+    /**
+     * 汇总节点
+     * @param node - 最底层 工程量清单 节点
+     * @param leafXmj - 所属 最底层 项目节
+     */
+    function loadGatherGclNode(node, leafXmj, gsPos) {
+        const gcl = getGclNode(node);
+        gatherfields(gcl, node, ledgerSetting.billsFields, ledgerSetting.prefix);
+        const cacheLeafXmj = getCacheLeafXmj(leafXmj);
+        const posRange = gsPos.getLedgerPos(node.id);
+        const detail = posRange && posRange.length > 0 ? posRange : [node];
+        for (const d of detail) {
+            const lx = gcl.leafXmjs.find(x => {return x.id === leafXmj.id && (x.mx_id === d.id || x.gcl_id === d.id)});
+            if (lx) {
+                gatherfields(lx, d, ledgerSetting.posFields, ledgerSetting.prefix);
+            } else {
+                const dx = _.assign({}, cacheLeafXmj);
+                gatherfields(dx, d, ledgerSetting.posFields, ledgerSetting.prefix);
+                dx.gcl_id = node.id;
+                if (d.name !== node.name) {
+                    dx.bwmx = d.name;
+                    dx.mx_id = d.id;
+                }
+                if (d.drawing_code) {
+                    dx.drawing_code = d.drawing_code;
+                }
+                gcl.leafXmjs.push(dx);
+            }
+        }
+    }
+
+    /**
+     * (递归)汇总树节点
+     * @param nodes - 汇总节点列表
+     * @param leafXmj - 汇总节点所属的底层项目节
+     */
+    function recursiveGatherGclData(nodes, leafXmj, gsPos) {
+        for (const node of nodes) {
+            if (node.b_code) {
+                if (node.children.length > 0) {
+                    recursiveGatherGclData(node.children, leafXmj, gsPos);
+                } else {
+                    loadGatherGclNode(node, leafXmj, gsPos);
+                }
+            } else if (node.children.length > 0) {
+                recursiveGatherGclData(node.children, node, gsPos);
+            }
+        }
+    }
+
+    function _getCalcChapter(chapter) {
+        const gclChapter = [], otherChapter = [];
+        let serialNo = 1;
+        for (const c of chapter) {
+            const cc = { code: c.code, name: c.name, cType: 1 };
+            cc.serialNo = serialNo++;
+            cc.filter = '^[^0-9]*' + c.code.substr(0, c.code.length - 2) + '[0-9]{2}-';
+            gclChapter.push(cc);
+        }
+        gclChapter.push({ name: '未计入章节清单合计', cType: 21, serialNo: serialNo++, });
+        otherChapter.push({ name: '清单小计(A)', cType: 11, serialNo: serialNo++ });
+        otherChapter.push({ name: '非清单项费用(B)', cType: 31, serialNo: serialNo++ });
+        otherChapter.push({ name: '合计(C=A+B)', cType: 41, serialNo: serialNo });
+        return [gclChapter, otherChapter];
+    }
+
+    function _gatherChapterFields(chapter, data, fields) {
+        for (const f of fields) {
+            chapter[f] = ZhCalc.add(chapter[f], data[f]);
+        }
+    }
+
+    function _getGclChapter(chapter, data) {
+        for (const c of chapter) {
+            if (c.filter) {
+                const reg = new RegExp(c.filter);
+                if (reg.test(data.b_code)) {
+                    return c;
+                }
+            } else {
+                return c;
+            }
+        }
+    }
+
+    function _gatherChapter() {
+        for (const d of gsTree.datas) {
+            if (d.children && d.children.length > 0) continue;
+
+            for (const c of otherChapter) {
+                if (c.cType === 41) {
+                    gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
+                } else if (c.cType === 31 && (!d.b_code || d.b_code === '')) {
+                    gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
+                } else if (c.cType === 11 && (d.b_code)) {
+                    gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
+                }
+            }
+            if (d.b_code) {
+                const c = _getGclChapter(gclChapter, d);
+                gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
+            }
+        }
+    }
+
+    function init (gclData, chapter) {
+        gclList = gclData;
+        [gclChapter, otherChapter] = _getCalcChapter(chapter);
+    }
+
+    /**
+     *
+     * @param bills - 项目节+清单数据
+     * @param pos - 计量单元数据
+     * @param setting 配置
+     *  e.g.
+     *  {
+     *      tree: {
+     *          id: 'ledger_id',
+     *          pid: 'ledger_pid',
+     *          order: 'order',
+     *          level: 'level',
+     *          rootId: -1,
+     *          keys: ['id', 'tender_id', 'ledger_id'],
+     *          stageId: 'id',
+     *      },
+     *      pos: { id: 'id', ledgerId: 'lid', },
+     *      billsFields: ['quantity', 'total_price', 'deal_qty', 'deal_tp'],
+     *      posFields: ['quantity'],
+     *      chapterFields: [new_total_price],
+     *      prefix: 'org' // 'new'
+     *  }
+     */
+    function gatherLedgerData(bills, pos, setting) {
+        ledgerSetting = setting;
+        try {
+            if (leafXmjs.length > 0) leafXmjs.length = 0;
+            gsTree = createNewPathTree('ledger', setting.tree);
+            gsTree.loadDatas(bills);
+            const gsPos = new PosData(setting.pos);
+            gsPos.loadDatas(pos);
+            recursiveGatherGclData(gsTree.children, null, gsPos);
+            _gatherChapter();
+        } catch(err) {
+            console.log(err);
+        }
+        ledgerSetting = null;
+    }
+
+    function compareCode(str1, str2, symbol = '-') {
+        if (!str1) {
+            return 1;
+        } else if (!str2) {
+            return -1;
+        }
+
+        function compareSubCode(code1, code2) {
+            if (numReg.test(code1)) {
+                if (numReg.test(code2)) {
+                    return parseInt(code1) - parseInt(code2);
+                } else {
+                    return -1
+                }
+            } else {
+                if (numReg.test(code2)) {
+                    return 1;
+                } else {
+                    return code1 === code2 ? 0 : (code1 < code2 ? -1 : 1); //code1.localeCompare(code2);
+                }
+            }
+        }
+        const numReg = /^[0-9]+$/;
+        const aCodes = str1.split(symbol), bCodes = str2.split(symbol);
+        for (let i = 0, iLength = Math.min(aCodes.length, bCodes.length); i < iLength; ++i) {
+            const iCompare = compareSubCode(aCodes[i], bCodes[i]);
+            if (iCompare !== 0) {
+                return iCompare;
+            }
+        }
+        return aCodes.length - bCodes.length;
+    }
+
+    /**
+     *
+     * @param data {Array} - 签约清单数据
+     * @returns <void>
+     */
+    function gatherDealBills(data) {
+        if (data instanceof Array && data.length > 0) {
+            for (const node of data) {
+                node.b_code = node.code;
+                const gcl = getGclNode(node);
+                if (!node.quantity || !node.unit_price) continue;
+                gcl.deal_bills_qty = node.quantity || 0;
+                gcl.deal_bills_tp = node.total_price || 0;
+
+                for (const c of otherChapter) {
+                    if (c.cType === 41 || c.cType === 11) {
+                        c.deal_bills_tp = ZhCalc.add(c.deal_bills_tp, node.total_price);
+                    }
+                }
+                const c = _getGclChapter(gclChapter, node);
+                c.deal_bills_tp = ZhCalc.add(c.deal_bills_tp, node.total_price);
+            }
+        }
+    }
+
+    /**
+     * 检查汇总完的工程量清单中,同编号 & 不同名称/单位/单价 的清单并标记
+     */
+    function checkDiffer() {
+        gclList.sort((a, b) => {
+            return compareCode(a.b_code, b.b_code);
+        });
+        for (const gcl of gclList) {
+            gcl.differ = false;
+        }
+        for (const [i, gcl] of gclList.entries()) {
+            gcl.differ_qty = ZhCalc.sub(gcl.new_quantity, gcl.org_quantity, 6);
+            gcl.deal_differ_qty = ZhCalc.sub(gcl.new_quantity, gcl.deal_bills_qty, 6);
+            if (i === gclList.length - 1) continue;
+            const next = gclList[i+1];
+            if (gcl.b_code === next.b_code) {
+                if (gcl.name !== next.name || gcl.unit !== next.unit || !checkZero(gcl.unit_price - next.unit_price)) {
+                    gcl.differ = true;
+                    next.differ = true;
+                }
+            }
+        }
+    }
+
+    function chapterData () {
+        return gclChapter.concat(otherChapter);
+    }
+
+    return {
+        init,
+        gatherLedgerData, gatherDealBills, checkDiffer,
+        chapterData
+    };
+})();

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

@@ -52,6 +52,12 @@ const sjsSettingObj = (function () {
             if (col) col.formatter = '#,##0.######';
         }
     };
+    const setTpColsThousandthFormat = function (cols, fields = []) {
+        for (const col of cols) {
+            if (col.field === 'total_price' || col.field.indexOf('tp') >= 0 || fields.indexOf(col.field) >= 0)
+                col.formatter = '#,##0.######';
+        }
+    };
 
     const setPropValue = function (setting, fields, prop, value) {
         for (const f of fields) {
@@ -61,5 +67,5 @@ const sjsSettingObj = (function () {
             if (col) col[prop] = value;
         }
     };
-    return {setFxTreeStyle, FxTreeStyle, setGridSelectStyle, setTpThousandthFormat, setThousandthFormat, setPropValue};
+    return {setFxTreeStyle, FxTreeStyle, setGridSelectStyle, setTpThousandthFormat, setThousandthFormat, setTpColsThousandthFormat, setPropValue};
 })();

+ 559 - 0
app/public/js/shenpi.js

@@ -0,0 +1,559 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Ellisran
+ * @date 2020/10/09
+ * @version
+ */
+const tenderTree = [];
+let parentId = 0;
+// 查询方法
+function findNode (key, value, arr) {
+    for (const a of arr) {
+        if (a[key] && a[key] === value) {
+            return a;
+        }
+    }
+}
+// 初始化TenderTree数据
+function initTenderTree () {
+    const levelCategory = category.filter(function (c) {
+        return c.level && c.level > 0;
+    });
+    function findCategoryNode(cid, value, array) {
+        for (const a of array) {
+            if (a.cid === cid && a.vid === value) {
+                return a;
+            }
+        }
+    }
+    function getCategoryNode(category, value, parent, i = null) {
+        const array = parent ?  parent.children : tenderTree;
+        let cate = findCategoryNode(category.id, value, array);
+        if (!cate) {
+            const cateValue = findNode('id', value, category.value);
+            if (!cateValue) return null;
+            cate = {
+                cid: category.id,
+                vid: value,
+                name: cateValue.value,
+                children: [],
+                level: i ? i : category.level,
+                sort_id: ++parentId,
+            };
+            array.push(cate);
+        }
+        return cate;
+    }
+    function loadTenderCategory (tender) {
+        let tenderCategory = null;
+        for (const [index,lc] of levelCategory.entries()) {
+            const tenderCate = findNode('cid', lc.id, tender.category);
+            if (tenderCate) {
+                tenderCategory = getCategoryNode(lc, tenderCate.value, tenderCategory);
+            } else {
+                if (index === 0 && tender.category) {
+                    for (const [i,c] of tender.category.entries()) {
+                        const cate = findNode('id', c.cid, category);
+                        tenderCategory = getCategoryNode(cate, c.value, tenderCategory, i+1);
+                    }
+                }
+                return tenderCategory;
+            }
+        }
+        return tenderCategory;
+    }
+    function calculateTender(tender) {
+        if (tender.lastStage) {
+            tender.gather_tp = ZhCalc.add(tender.lastStage.contract_tp, tender.lastStage.qc_tp);
+            tender.end_contract_tp = ZhCalc.add(tender.lastStage.pre_contract_tp, tender.lastStage.contract_tp);
+            tender.end_qc_tp = ZhCalc.add(tender.lastStage.pre_qc_tp, tender.lastStage.qc_tp);
+            tender.end_gather_tp = ZhCalc.add(tender.end_contract_tp, tender.end_qc_tp);
+            tender.pre_gather_tp = ZhCalc.add(tender.lastStage.pre_contract_tp, tender.lastStage.pre_qc_tp);
+            tender.yf_tp = ZhCalc.add(tender.lastStage.yf_tp);
+            tender.end_yf_tp = ZhCalc.add(tender.lastStage.pre_yf_tp, tender.yf_tp);
+        }
+    }
+    tenderTree.splice(0, tenderTree.length);
+    for (const t of tenders) {
+        calculateTender(t);
+        t.valid = true;
+        delete t.level;
+        if (t.category && levelCategory.length > 0) {
+            const parent = loadTenderCategory(t);
+            if (parent) {
+                t.level = parent.level + 1;
+                parent.children.push(t);
+            } else {
+                tenderTree.push(t);
+            }
+        } else {
+            tenderTree.push(t);
+        }
+    }
+}
+function recursiveGetTenderNodeHtml (node, arr, pid, this_code, this_status, aidList = []) {
+    const html = [];
+    html.push('<tr pid="' + pid + '">');
+    // 名称
+    html.push('<td class="in-' + node.level + '">');
+    if (node.cid) {
+        html.push('<i class="fa fa-folder-o"></i> ', node.name);
+    } else {
+        html.push('<span class="text-muted mr-2">');
+        html.push(arr.indexOf(node) === arr.length - 1 ? '└' : '├');
+        html.push('</span>');
+        //html.push('<a href="/tender/' + node.id + '">', node[c.field], '</a>');
+        html.push('<a href="javascript: void(0)" id="' + node.id + '">', node.name, '</a>');
+    }
+    html.push('</td>');
+    // 创建人
+    // html.push('<td>', sp_status_list[node.shenpiInfo[shenpi_type]].name, '</td>');
+    html.push('<td>');
+    if (!node.cid) {
+        let auditList = [];
+        let tender_status = 1;
+        if(cur_tenderid === node.id) {
+            html.push(sp_status_list[this_status].name);
+            auditList = aidList;
+            tender_status = this_status;
+        } else {
+            html.push(sp_status_list[node.shenpiInfo[this_code]].name);
+            auditList = node.shenpiauditList[this_code];
+            tender_status = node.shenpiInfo[this_code];
+        }
+        if(tender_status === sp_status.gdspl || tender_status === sp_status.gdzs) {
+            const nameList = [];
+            if(auditList) {
+                for (const uid of auditList) {
+                    const user = _.find(accountList, { id: uid });
+                    nameList.push(user.name);
+                }
+            }
+            html.push('<i class="fa fa-question-circle text-primary" data-container="body" data-toggle="tooltip" data-placement="bottom" ' +
+                'data-original-title="'+ (nameList.length > 0 ? nameList.join('-') : '') +'"></i>');
+        }
+    }
+    html.push('</td>');
+    html.push('<td>');
+    if (!node.cid) {
+        html.push('<input data-tid="'+ node.id +'" type="checkbox"'+ (cur_tenderid === node.id ? ' checked disabled' : '') +'>');
+    }
+    html.push('</td>');
+    html.push('</tr>');
+    if (node.children) {
+        for (const c of node.children) {
+            html.push(recursiveGetTenderNodeHtml(c, node.children, node.sort_id, this_code, this_status, aidList));
+        }
+    }
+    return html.join('');
+}
+// 根据TenderTree数据获取Html代码
+function getTenderTreeHtml (this_code, this_status, aidList = []) {
+    if (tenderTree.length > 0) {
+        const html = [];
+        html.push('<table class="table table-hover table-bordered">');
+        html.push('<thead>', '<tr>');
+        html.push('<th>名称</th>');
+        html.push('<th width="100">审批流程</th>');
+        html.push('<th width="40">选择</th>');
+        html.push('</tr>', '</thead>');
+        parentId = 0;
+        for (const t of tenderTree) {
+            html.push(recursiveGetTenderNodeHtml(t, tenderTree, '', this_code, this_status, aidList));
+        }
+        html.push('</table>');
+        return html.join('');
+    } else {
+        return EmptyTenderHtml.join('');
+    }
+}
+
+function getShenpiHtml (this_code) {
+    const html = [];
+    html.push('<table class="table table-hover table-bordered">');
+    html.push('<thead>', '<tr>');
+    html.push('<th>名称</th>');
+    html.push('<th width="100">审批流程</th>');
+    html.push('<th width="40">选择</th>');
+    html.push('</tr>', '</thead>');
+    for (const sp of sp_lc) {
+        html.push('<tr>');
+        html.push('<td>', sp.name, '</td>');
+        html.push('<td>');
+        const this_status = parseInt($('.' + sp.code + '_div').children('.lc-show').siblings('.form-group').find('input:checked').val());
+        html.push(sp_status_list[this_status].name);
+        if(this_status !== sp_status.sqspr) {
+            const nameList = [];
+            const aid_num = $('.' + sp.code + '_div').children('.lc-show').children('ul').find('.remove-audit').length;
+            const aidList = [];
+            for (let i = 0; i < aid_num; i++) {
+                const aid = parseInt($('.' + sp.code + '_div').children('.lc-show').children('ul').find('.remove-audit').eq(i).data('id'));
+                aidList.push(aid);
+            }
+            if(aidList.length > 0) {
+                for (const uid of aidList) {
+                    const user = _.find(accountList, { id: uid });
+                    nameList.push(user.name);
+                }
+            }
+            html.push('<i class="fa fa-question-circle text-primary" data-container="body" data-toggle="tooltip" data-placement="bottom" ' +
+                'data-original-title="'+ (nameList.length > 0 ? nameList.join('-') : '') +'"></i>');
+        }
+        html.push('</td>');
+        html.push('<td>', this_code !== sp.code ? '<input type="checkbox" data-code="'+ sp.code +'">' : '', '</td>');
+        html.push('</tr>');
+    }
+    html.push('</table>');
+    return html.join('');
+}
+$(document).ready(function () {
+    let timer = null;
+    let oldSearchVal = null;
+    const needYB = ['ledger', 'revise', 'change'];
+    $('body').on('input propertychange', '.gr-search', function(e) {
+        oldSearchVal = e.target.value;
+        timer && clearTimeout(timer);
+        timer = setTimeout(() => {
+            const newVal = $(this).val();
+            const code = $(this).attr('data-code');
+            let html = '';
+            if (newVal && newVal === oldSearchVal) {
+                accountList.filter(item => item && (item.id !== cur_uid || (item.id === cur_uid && needYB.indexOf(code) !== -1)) && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1))).forEach(item => {
+                    html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                        <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                class="ml-auto">${item.mobile || ''}</span></p>
+                        <span class="text-muted">${item.role || ''}</span>
+                    </dd>`
+                });
+                $('#' + code + '_dropdownMenu .book-list').empty();
+                $('#' + code + '_dropdownMenu .book-list').append(html);
+            } else {
+                if (!$('#' + code + '_dropdownMenu .acc-btn').length) {
+                    accountGroup.forEach((group, idx) => {
+                        if (!group) return;
+                        html += `<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="${idx}" data-type="hide"><i class="fa fa-plus-square"></i>
+                        </a> ${group.groupName}</dt>
+                        <div class="dd-content" data-toggleid="${idx}">`;
+                        group.groupList.forEach(item => {
+                            if ((item.id !== cur_uid || (item.id === cur_uid && needYB.indexOf(code) !== -1))) {
+                                html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                                    <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                            class="ml-auto">${item.mobile || ''}</span></p>
+                                    <span class="text-muted">${item.role || ''}</span>
+                                </dd>`;
+                            }
+                        });
+                        html += '</div>';
+                    });
+                    $('#' + code + '_dropdownMenu .book-list').empty();
+                    $('#' + code + '_dropdownMenu .book-list').append(html);
+                }
+            }
+        }, 400);
+    });
+
+    // 添加审批流程按钮逻辑
+    $('body').on('click', '.book-list dt', function () {
+        const idx = $(this).find('.acc-btn').attr('data-groupid');
+        const type = $(this).find('.acc-btn').attr('data-type');
+        if (type === 'hide') {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).show(() => {
+                $(this).children().find('i').removeClass('fa-plus-square').addClass('fa-minus-square-o');
+                $(this).find('.acc-btn').attr('data-type', 'show');
+
+            })
+        } else {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).hide(() => {
+                $(this).children().find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square');
+                $(this).find('.acc-btn').attr('data-type', 'hide');
+            })
+        }
+        return false;
+    });
+
+    // 更改审批流程状态
+    $('.form-check input').on('change', function () {
+        // 获取所有审批的checked值并更新
+        const this_status = parseInt($(this).val());
+        const this_code = $(this).data('code');
+        const spt = sp_status_list[this_status];
+        $(this).parents('.form-group').siblings('.alert-warning').text(spt.name + ':' + spt.msg);
+        // 拼接post json
+        const prop = {
+            code: this_code,
+            status: this_status
+        };
+        const _self = $(this);
+        postData('/tender/' + cur_tenderid + '/shenpi/save', prop, function (data) {
+            if (this_status === sp_status.sqspr) {
+                _self.parents('.form-group').siblings('.lc-show').html('');
+            } else if (this_status === sp_status.gdspl) {
+                let addhtml = '<ul class="list-unstyled">\n';
+                if (data.length !== 0) {
+                    for(const [i, audit] of data.entries()) {
+                        addhtml += makeAudit(audit, transFormToChinese(i+1));
+                    }
+                    addhtml += '<li class="pl-3"><a href="javascript:void(0);" class="add-audit"><i class="fa fa-plus"></i> 添加流程</a></li>';
+                } else {
+                    addhtml += makeSelectAudit(this_code, '一');
+                }
+                addhtml += '</ul>\n';
+                _self.parents('.form-group').siblings('.lc-show').html(addhtml);
+            } else if (this_status === sp_status.gdzs) {
+                let addhtml = '<ul class="list-unstyled">\n' +
+                    '                                        <li class="d-flex justify-content-start mb-3">\n' +
+                    '                                            <span class="col-auto">授权审批人</span>\n' +
+                    '                                            <span class="col-7">\n' +
+                    '                                                <span class="d-inline-block"></span>\n' +
+                    '                                            </span>\n' +
+                    '                                        </li>\n';
+                addhtml += data ? makeAudit(data) : makeSelectAudit(this_code);
+                addhtml += '</ul>\n';
+                _self.parents('.form-group').siblings('.lc-show').html(addhtml);
+            }
+        });
+    });
+
+    // 选中审批人
+    $('body').on('click', 'dl dd', function () {
+        const id = parseInt($(this).data('id'));
+        if (id) {
+            const user = _.find(accountList, function (item) {
+                return item.id === id;
+            });
+            const this_status = parseInt($(this).parents('.lc-show').siblings('.form-group').find('input:checked').val());
+            const this_code = $(this).parents('.lc-show').siblings('.form-group').find('input:checked').data('code');
+            if (this_status === sp_status.gdspl) {
+                // 判断是否已存在审批人
+                const aid_num = $(this).parents('ul').find('.remove-audit').length;
+                for (let i = 0; i < aid_num; i++) {
+                    const aid = parseInt($(this).parents('ul').find('.remove-audit').eq(i).data('id'));
+                    if (aid === id) {
+                        toastr.warning('该审核人已存在,请勿重复添加');
+                        return;
+                    }
+                }
+
+            }
+            const prop = {
+                status: this_status,
+                code: sp_type[this_code],
+                audit_id: id,
+                type: 'add',
+            };
+            const _self = $(this);
+            postData('/tender/' + cur_tenderid + '/shenpi/audit/save', prop, function (data) {
+                if (this_status === sp_status.gdspl) {
+                    _self.parents('ul').append('<li class="pl-3"><a href="javascript:void(0);" class="add-audit"><i class="fa fa-plus"></i> 添加流程</a></li>');
+                }
+                _self.parents('.spr-span').html('<span class="d-inline-block"></span>\n' +
+                    '<span class="d-inline-block"><span class="badge badge-light">'+ user.name +' <a href="javascript:void(0);" class="remove-audit btn-sm text-danger px-1" title="移除" data-id="'+ user.id +'"><i class="fa fa-remove"></i></a></span> </span>');
+            });
+        }
+    });
+
+    // 移除审批人
+    $('body').on('click', '.remove-audit', function () {
+        const id = parseInt($(this).data('id'));
+        const this_status = parseInt($(this).parents('.lc-show').siblings('.form-group').find('input:checked').val());
+        const this_code = $(this).parents('.lc-show').siblings('.form-group').find('input:checked').data('code');
+        const prop = {
+            status: this_status,
+            code: sp_type[this_code],
+            audit_id: id,
+            type: 'del',
+        };
+        const _self = $(this);
+        postData('/tender/' + cur_tenderid + '/shenpi/audit/save', prop, function (data) {
+            if (this_status === sp_status.gdspl) {
+                const _selflc = _self.parents('.lc-show');
+                _self.parents('li').remove();
+                const aid_num = parseInt(_selflc.children('ul').find('li.d-flex').length);
+                if (aid_num === 0) {
+                    let addhtml = '<ul class="list-unstyled">\n';
+                    addhtml += makeSelectAudit(this_code, '一');
+                    addhtml += '</ul>\n';
+                    _selflc.html(addhtml);
+                } else {
+                    for (let i = 0; i < aid_num; i++) {
+                        _selflc.find('li.d-flex').eq(i).find('.col-auto').text(transFormToChinese(i+1) + '审');
+                    }
+                }
+            } else if (this_status === sp_status.gdzs) {
+                let addhtml = '<ul class="list-unstyled">\n' +
+                    '                                        <li class="d-flex justify-content-start mb-3">\n' +
+                    '                                            <span class="col-auto">授权审批人</span>\n' +
+                    '                                            <span class="col-7">\n' +
+                    '                                                <span class="d-inline-block"></span>\n' +
+                    '                                            </span>\n' +
+                    '                                        </li>\n';
+                addhtml += makeSelectAudit(this_code);
+                addhtml += '</ul>\n';
+                _self.parents('.lc-show').html(addhtml);
+            }
+        })
+    });
+
+    // 固定审批流-添加流程
+    $('body').on('click', '.add-audit', function () {
+        const num = $(this).parents('ul').children('li').length;
+        const this_code = $(this).parents('.lc-show').siblings('.form-group').find('input:checked').data('code');
+        const addhtml = makeSelectAudit(this_code, transFormToChinese(num));
+        $(this).parents('ul').append(addhtml);
+        $(this).parents('li').remove();
+    });
+
+    // 审批流程-审批人html 生成
+    function makeAudit(audit, i = '终') {
+        return '<li class="d-flex justify-content-start mb-3">\n' +
+            '                                            <span class="col-auto">'+ i +'审</span>\n' +
+            '                                            <span class="col-7 spr-span">\n' +
+            '                                            <span class="d-inline-block"></span>\n' +
+            '                                            <span class="d-inline-block"><span class="badge badge-light">'+ audit.name +' <a href="javascript:void(0);" class="remove-audit btn-sm text-danger px-1" title="移除" data-id="'+ audit.audit_id +'"><i class="fa fa-remove"></i></a></span> </span>\n' +
+            '                                            </span>\n' +
+            '                                        </li>';
+    }
+
+    // 审批流程-选择审批人html 生成
+    function makeSelectAudit(code, i = '终') {
+        let divhtml = '';
+        accountGroup.forEach((group, idx) => {
+            let didivhtml = '';
+            if(group) {
+                group.groupList.forEach(item => {
+                    didivhtml += (item.id !== cur_uid || (item.id === cur_uid && needYB.indexOf(code) !== -1)) ? '<dd class="border-bottom p-2 mb-0 " data-id="' + item.id + '" >\n' +
+                        '<p class="mb-0 d-flex"><span class="text-primary">' + item.name + '</span><span\n' +
+                        '                                                                                class="ml-auto">' + item.mobile + '</span></p>\n' +
+                        '                                                                    <span class="text-muted">' + item.role + '</span>\n' +
+                        '                                                                    </dd>\n' : '';
+                });
+                divhtml += '<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="' + idx + '" data-type="hide"><i class="fa fa-plus-square"></i></a> ' + group.groupName + '</dt>\n' +
+                    '                                                                <div class="dd-content" data-toggleid="' + idx + '">\n' + didivhtml +
+                    '                                                                </div>\n';
+            }
+        });
+        let html = '<li class="d-flex justify-content-start mb-3">\n' +
+            '                                            <span class="col-auto">' + i + '审</span>\n' +
+            '                                            <span class="col-7 spr-span">\n' +
+            '                                            <span class="d-inline-block">\n' +
+            '                                                <div class="dropdown text-right">\n' +
+            '                                                    <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="' + code + '_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">\n' +
+            '                                                        选择审批人\n' +
+            '                                                    </button>\n' +
+            '                                                    <div class="dropdown-menu dropdown-menu-right" id="' + code + '_dropdownMenu" aria-labelledby="' + code + '_dropdownMenuButton" style="width:220px">\n' +
+            '                                                        <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"\n' +
+            '                                                                                     placeholder="姓名/手机 检索" autocomplete="off" data-code="' + code + '"></div>\n' +
+            '                                                        <dl class="list-unstyled book-list">\n' + divhtml +
+            '                                                        </dl>\n' +
+            '                                                    </div>\n' +
+            '                                                </div>\n' +
+            '                                            </span>\n' +
+            '                                        </span>\n' +
+            '                                        </li>';
+        return html;
+    }
+
+
+    initTenderTree();
+
+    $('.set-otherTender').on('click', function () {
+        const this_code = $(this).data('code');
+        const this_status = parseInt($(this).siblings('.lc-show').siblings('.form-group').find('input:checked').val());
+        const aid_num = $(this).siblings('.lc-show').children('ul').find('.remove-audit').length;
+        const aidList = [];
+        for (let i = 0; i < aid_num; i++) {
+            const aid = parseInt($(this).siblings('.lc-show').children('ul').find('.remove-audit').eq(i).data('id'));
+            aidList.push(aid);
+        }
+        const html = getTenderTreeHtml(this_code, this_status, aidList);
+        $('#shenpi-name').text($(this).data('name'));
+        $('#shenpi_code').val(this_code);
+        $('#shenpi_status').val(this_status);
+        $('#shenpi_auditors').val(aidList.join(','));
+        $('#tender-list').html(html);
+        setTimeout(function () { $("#tender-list [data-toggle='tooltip']").tooltip(); },800);
+    });
+
+    $('#save-other-tender').click(function () {
+        $(this).attr('disabled', true);
+        const num = $('#tender-list input:checked').length;
+        if (num < 2) {
+            toastr.warning('请选择需要设置审批同步的标段');
+            return;
+        }
+        const data = {
+            type: 'copy2ot',
+            status: parseInt($('#shenpi_status').val()),
+            code: $('#shenpi_code').val(),
+        };
+        if(data.status !== shenpi_status.gdspl) {
+            data.aidList = $('#shenpi_auditors').val();
+        }
+        // 获取已选中的标段
+        const tenderList = [];
+        for (let i = 0; i < num; i++) {
+            const tid = parseInt($('#tender-list input:checked').eq(i).data('tid'));
+            if (tid !== cur_tenderid) {
+                tenderList.push(tid);
+            }
+        }
+        data.tidList = tenderList.join(',');
+        postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function () {
+            toastr.success('设置成功');
+            setTimeout(function () {
+                window.location.reload();
+            }, 1000)
+        })
+    });
+
+    $('.set-otherShenpi').on('click', function () {
+        const this_code = $(this).data('code');
+        const this_status = parseInt($(this).siblings('.lc-show').siblings('.form-group').find('input:checked').val());
+        const aid_num = $(this).siblings('.lc-show').children('ul').find('.remove-audit').length;
+        const aidList = [];
+        for (let i = 0; i < aid_num; i++) {
+            const aid = parseInt($(this).siblings('.lc-show').children('ul').find('.remove-audit').eq(i).data('id'));
+            aidList.push(aid);
+        }
+        const html = getShenpiHtml(this_code);
+        $('#shenpi-name2').text($(this).data('name'));
+        $('#shenpi_code2').val(this_code);
+        $('#shenpi_status2').val(this_status);
+        $('#shenpi_auditors2').val(aidList.join(','));
+        $('#shenpi-list').html(html);
+        setTimeout(function () { $("#shenpi-list [data-toggle='tooltip']").tooltip(); },800);
+    });
+
+    $('#save-other-shenpi').click(function () {
+        $(this).attr('disabled', true);
+        const num = $('#shenpi-list input:checked').length;
+        if (num < 1) {
+            toastr.warning('请选择需要设置审批同步的流程');
+            return;
+        }
+        const data = {
+            type: 'copy2os',
+            status: parseInt($('#shenpi_status2').val()),
+            code: $('#shenpi_code2').val(),
+        };
+        if(data.status !== shenpi_status.gdspl) {
+            data.aidList = $('#shenpi_auditors2').val();
+        }
+        // 获取已选中的标段
+        const shenpiList = [];
+        for (let i = 0; i < num; i++) {
+            const code = $('#shenpi-list input:checked').eq(i).data('code');
+            shenpiList.push(code);
+        }
+        data.shenpiList = shenpiList.join(',');
+        postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function () {
+            toastr.success('设置成功');
+            setTimeout(function () {
+                window.location.reload();
+            }, 1000)
+        })
+    });
+});

+ 64 - 23
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -403,6 +403,55 @@ const SpreadJsObj = {
             this.endMassOperation(sheet);
         }
     },
+    _loadCellData: function (sheet, data, iRow, iCol) {
+        if (!data || !sheet.zh_setting) return;
+
+        const col = sheet.zh_setting.cols[iCol];
+        const cell = sheet.getCell(iRow, iCol);
+
+        if (col.getValue && Object.prototype.toString.apply(col.getValue) === "[object Function]") {
+            cell.value(col.getValue(data));
+        } else if (col.field !== '' && data[col.field]) {
+            cell.value(data[col.field]);
+        }
+
+        let font = sheet.getDefaultStyle().font;
+        if (col.font) {
+            font = col.font;
+        }
+        if (sheet.zh_setting.tree.getFont && Object.prototype.toString.apply(sheet.zh_setting.tree.getFont) === "[object Function]") {
+            font = sheet.zh_setting.tree.getFont(sheet, data, iRow, col, font);
+        }
+        cell.font(font);
+
+        if (col.foreColor) {
+            if (Object.prototype.toString.apply(col.foreColor) === "[object Function]") {
+                cell.foreColor(col.foreColor(data, sheet.getDefaultStyle().foreColor));
+            } else {
+                cell.foreColor(col.foreColor);
+            }
+        }
+
+        if (col.readOnly && Object.prototype.toString.apply(col.readOnly) === "[object Function]") {
+            cell.locked(col.readOnly(data) || sheet.zh_setting.readOnly || false).vAlign(1).hAlign(col.hAlign);
+        } else {
+            cell.locked(col.readOnly || sheet.zh_setting.readOnly || false).vAlign(1).hAlign(col.hAlign);
+        }
+
+        if(col.type === 'Number') {
+            if (col.formatter) {
+                cell.formatter(SpreadJsObj.Formatter.getNumberFormatter(col.formatter))
+            } else {
+                cell.formatter(SpreadJsObj.Formatter.getNumberFormatter('0.######'));
+            }
+        } else if (col.formatter) {
+            cell.formatter(col.formatter);
+        }
+
+        cell.backColor(SpreadJsObj._getBackColor(sheet, data, iRow, col));
+
+        cell.setBorder(sheet.borderLine, {all: true});
+    },
     _getBackColor: function (sheet, data, row, col) {
         let backColor = sheet.getDefaultStyle().backColor;
         let sels = sheet.getSelections();
@@ -655,6 +704,7 @@ const SpreadJsObj = {
             });
             this.endMassOperation(sheet);
         } catch (err) {
+            console.log(err);
             this.endMassOperation(sheet);
         }
     },
@@ -728,6 +778,13 @@ const SpreadJsObj = {
             this.endMassOperation(sheet);
         }
     },
+    reloadColData: function (sheet, col, count = 1) {
+        const cols = [];
+        for (let i = 0; i < count; i++) {
+            cols.push(col+i);
+        }
+        this.reLoadColsData(sheet, cols);
+    },
     /**
      * 重新加载部分列数据
      * @param {GC.Spread.Sheets.Worksheet} sheet
@@ -742,30 +799,14 @@ const SpreadJsObj = {
             for (const iCol of cols) {
                 // 清空原单元格数据
                 sheet.clear(-1, iCol, -1, 1, spreadNS.SheetArea.viewport, spreadNS.StorageType.data);
-                const col = sheet.zh_setting.cols[iCol];
 
-                sortData.forEach(function (data, i) {
-                    // 设置值
-                    const cell = sheet.getCell(i, iCol);
-                    if (col.field !== '' && data[col.field]) {
-                        cell.value(data[col.field]).locked(col.readOnly || sheet.zh_setting.readOnly || false).vAlign(1).hAlign(col.hAlign);
-                    } else {
-                        cell.locked(col.readOnly || sheet.zh_setting.readOnly || false).vAlign(1).hAlign(col.hAlign);
-                    }
-                    // 设置单元格格式
-                    if(col.type === 'Number') {
-                        if (col.formatter) {
-                            cell.formatter(SpreadJsObj.Formatter.getNumberFormatter(col.formatter))
-                        } else {
-                            cell.formatter(SpreadJsObj.Formatter.getNumberFormatter('0.######'));
-                        }
-                    } else if (col.formatter) {
-                        cell.formatter(col.formatter);
-                    }
-                });
+                for (const [iRow, data] of sortData.entries()) {
+                    this._loadCellData(sheet, data, iRow, iCol);
+                }
             }
             this.endMassOperation(sheet);
         } catch (err) {
+            console.log(err);
             this.endMassOperation(sheet);
         }
     },
@@ -1367,11 +1408,11 @@ const SpreadJsObj = {
                         const validWidth = $(window).width() - (pos.x + hitinfo.x + indent) - borderIndent;
                         const textWidth = this.getTextDisplayWidth(hitinfo, text, "9pt Arial");
                         if (validWidth >= maxHintWidth || textWidth <= validWidth) {
-                            $(div).text(text).css("top", pos.y + hitinfo.y + indent).css("left", pos.x + hitinfo.x + indent);
+                            $(div).html(text).css("top", pos.y + hitinfo.y + indent).css("left", pos.x + hitinfo.x + indent);
                         } else if (textWidth > maxHintWidth) {
-                            $(div).text(text).css("top", pos.y + hitinfo.y + indent).css("left", pos.x + hitinfo.x - indent - maxHintWidth);
+                            $(div).html(text).css("top", pos.y + hitinfo.y + indent).css("left", pos.x + hitinfo.x - indent - maxHintWidth);
                         } else {
-                            $(div).text(text).css("top", pos.y + hitinfo.y + indent).css("left", pos.x + hitinfo.x - indent - textWidth);
+                            $(div).html(text).css("top", pos.y + hitinfo.y + indent).css("left", pos.x + hitinfo.x - indent - textWidth);
                         }
                         this._toolTipElement = div;
                         $(div).show("fast");

+ 255 - 73
app/public/js/stage.js

@@ -103,7 +103,7 @@ function initTreeColSettingEvents(setting) {
 // 生成所有附件列表
 function getAllList(currPageNum = 1) {
     // 每页最多几个附件
-    const pageCount = 11;
+    const pageCount = 15;
     // 附件总数
     const total = attData.length;
     // 总页数
@@ -113,8 +113,16 @@ function getAllList(currPageNum = 1) {
     // 当前页附件内容
     const currPageAttData = attData.slice((currPageNum-1)*pageCount, currPageNum*pageCount);
     let html = '';
+    // '/tender/' + tender.id + '/measure/stage/' + stage.order + '/download/file/' + att.id
     for(const att of currPageAttData) {
-        html += '<tr ><td><a href="javascript:void(0)" file-id="'+ att.id +'">'+ att.filename + att.fileext +'</a></td><td>'+ att.username +'</td></tr>';
+        html += `<tr>
+        <td width="25"><input type="checkbox" class="check-file" file-id=${att.id}></td>
+        <td>
+        <div class="d-flex">
+            <a href="javascript:void(0)" class="pl-0 col-11 att-file-name" file-id=${att.id}>${att.filename}${att.fileext}</a>
+            <a href="/tender/${tender.id}/measure/stage/${stage.order}/download/file/${att.id}" class="col-1 pl-0 att-file-btn"><i class="fa fa-download"></i></a>
+        </div>
+        </td><td>${att.username}</td></tr>`
     }
     $('#alllist-table').html(html);
     $('#alllist-table').on('click', 'tr', function() {
@@ -127,7 +135,15 @@ function getNodeList(node) {
     let html = '';
     for(const att of attData) {
         if (node === att.lid) {
-            html += '<tr><td><a href="javascript:void(0)" file-id="'+ att.id +'">'+ att.filename + att.fileext +'</a></td><td>'+ att.username +'</td></tr>';
+            // html += '<tr><td><a href="javascript:void(0)" file-id="'+ att.id +'">'+ att.filename + att.fileext +'</a></td><td>'+ att.username +'</td></tr>';
+            html += `<tr>
+            <td width="25"><input type="checkbox" class="check-file" file-id=${att.id}></td>
+            <td>
+            <div class="d-flex">
+                <a href="javascript:void(0)" class="pl-0 col-11 att-file-name" file-id=${att.id}>${att.filename}${att.fileext}</a>
+                <a href="/tender/${tender.id}/measure/stage/${stage.order}/download/file/${att.id}" class="col-1 pl-0 att-file-btn"><i class="fa fa-download"></i></a>
+            </div>
+            </td><td>${att.username}</td></tr>`
         }
     }
     $('#nodelist-table').html(html);
@@ -946,7 +962,7 @@ $(document).ready(() => {
             }
         },
         measureAllPosInNode(node, ratio = 1) {
-            const posterity = stageTree.getPosterity(node)
+            const posterity = stageTree.getPosterity(node);
             const data = {updateType: 'update', updateData: []};
             for (const p of posterity) {
                 if (p.children && p.children.length > 0) continue;
@@ -1073,6 +1089,48 @@ $(document).ready(() => {
                 }
             }
         },
+        measureByBatch: function (posNames, ratio, apply2sibling) {
+            if (posNames.length <= 0) return;
+            if (ratio <= 0) return;
+            const fRatio = ZhCalc.div(ratio, 100);
+
+            const sheet = slSpread.getActiveSheet();
+            const node = SpreadJsObj.getSelectObject(sheet);
+            const parent = stageTree.getParent(node);
+            const nodes = apply2sibling === true ? (parent ? parent.children : stageTree.children) : [node];
+            const data = {updateType: 'batchUpdate', updateData: []};
+            for (const node of nodes) {
+                const posRange = stagePos.getLedgerPos(node.id);
+                if (!posRange || posRange <= 0) continue;
+
+                for (const p of posRange) {
+                    if (posNames.indexOf(p.name) < 0) continue;
+                    data.updateData.push({
+                        pid: p.id, lid: p.lid,
+                        contract_qty: ZhCalc.mul(p.quantity, fRatio),
+                        contract_expr: `${p.quantity}*${ratio}%`,
+                    });
+                }
+            }
+            postData(window.location.pathname + '/update', {pos: data}, function (result) {
+                if (result.pos) {
+                    stagePos.updateDatas(result.pos.pos);
+                    stagePos.loadCurStageData(result.pos.curStageData);
+                }
+                const nodes = stageTree.loadPostStageData(result.ledger);
+                stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
+                stagePosSpreadObj.loadCurPosData();
+                if (detail) {
+                    detail.loadStagePosUpdateData(result);
+                } else {
+                    stageIm.loadUpdatePosData(result);
+                }
+                $('#calc-by-ratio').modal('hide');
+            }, function () {
+                stagePosSpreadObj.loadCurPosData();
+                $('#calc-by-ratio').modal('hide');
+            });
+        }
     };
     slSpread.bind(spreadNS.Events.EditEnded, stageTreeSpreadObj.editEnded);
     slSpread.bind(spreadNS.Events.SelectionChanged, stageTreeSpreadObj.selectionChanged);
@@ -1149,7 +1207,7 @@ $(document).ready(() => {
                     const [leafUsedBills, usedPos] = stageIm.getFirstUsed(node);
                     if (leafUsedBills) {
                         if (!$('#zhongjian').hasClass('active')) {
-                            const tab = $('#zhongjian'), tabPanel = $(tab.attr('content'));
+                            const tab = $('#zhongjiantab'), tabPanel = $(tab.attr('content'));
                             $('a', '.side-menu').removeClass('active');
                             $('.tab-content .tab-select-show').removeClass('active');
                             tab.addClass('active');
@@ -1657,63 +1715,89 @@ $(document).ready(() => {
             });
         });
     }
-    if (!checkTzMeasureType()) {
-        const mergePeg = NewMergePeg({ callback: stagePosSpreadObj.addPegs});
-        $.contextMenu({
-            selector: '#stage-pos',
-            build: function ($trigger, e) {
-                const target = SpreadJsObj.safeRightClickSelection($trigger, e, spSpread);
-                return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
-            },
-            items: {
-                'del': {
-                    name: '删除',
-                    icon: 'fa-remove',
-                    disabled: function (key, opt) {
-                        const sheet = spSpread.getActiveSheet();
-                        if (sheet.zh_data && !readOnly) {
-                            const selection = sheet.getSelections();
-                            if (selection && selection[0]) {
-                                let valid = sheet.zh_data.length < selection[0].row + selection[0].rowCount;
-                                for (let iRow = 0; iRow < selection[0].rowCount; iRow++) {
-                                    const posData = sheet.zh_data[selection[0].row + iRow];
-                                    if (posData) {
-                                        if (posData.add_stage_order < stage.order || ZhCalc.isNonZero(posData.gather_qty) || ZhCalc.isNonZero(posData.end_gather_qty)) {
-                                            valid = true;
-                                            break;
-                                        }
-                                    } else {
+
+    const mergePeg = NewMergePeg({ callback: stagePosSpreadObj.addPegs});
+    $.contextMenu({
+        selector: '#stage-pos',
+        build: function ($trigger, e) {
+            const target = SpreadJsObj.safeRightClickSelection($trigger, e, spSpread);
+            return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+        },
+        items: {
+            'del': {
+                name: '删除',
+                icon: 'fa-remove',
+                visible: function (key, opt) {
+                    return !checkTzMeasureType();
+                },
+                disabled: function (key, opt) {
+                    const sheet = spSpread.getActiveSheet();
+                    if (sheet.zh_data && !readOnly) {
+                        const selection = sheet.getSelections();
+                        if (selection && selection[0]) {
+                            let valid = sheet.zh_data.length < selection[0].row + selection[0].rowCount;
+                            for (let iRow = 0; iRow < selection[0].rowCount; iRow++) {
+                                const posData = sheet.zh_data[selection[0].row + iRow];
+                                if (posData) {
+                                    if (posData.add_stage_order < stage.order || ZhCalc.isNonZero(posData.gather_qty) || ZhCalc.isNonZero(posData.end_gather_qty)) {
                                         valid = true;
                                         break;
                                     }
+                                } else {
+                                    valid = true;
+                                    break;
                                 }
-                                return valid;
-                            } else {
-                                return true;
                             }
+                            return valid;
                         } else {
                             return true;
                         }
-                    },
-                    callback: function (key, opt) {
-                        stagePosSpreadObj.deletePos(spSpread.getActiveSheet());
+                    } else {
+                        return true;
                     }
                 },
-                'merge-peg': {
-                    name: '合并起讫桩号',
-                    disabled: function (key, opt) {
-                        const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
-                        return _.isNil(node) || _.isNil(node.b_code) || node.b_code === '';
-                    },
-                    callback: function (key, opt) {
-                        mergePeg.show();
+                callback: function (key, opt) {
+                    stagePosSpreadObj.deletePos(spSpread.getActiveSheet());
+                }
+            },
+            'merge-peg': {
+                name: '合并起讫桩号',
+                visible: function (key, opt) {
+                    return !checkTzMeasureType();
+                },
+                disabled: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    return _.isNil(node) || _.isNil(node.b_code) || node.b_code === '';
+                },
+                callback: function (key, opt) {
+                    mergePeg.show();
+                }
+            },
+            'calcByRatio': {
+                name: '按比例计量',
+                visible: function (key, opt) {
+                    const data = spSpread.getActiveSheet().zh_data;
+                    return data && data.length > 0;
+                },
+                callback: function (key, opt) {
+                    $('#cbr-ratio').val('');
+                    $('#apply2sibling')[0].checked = false;
+                    $('#cbr-check-all')[0].checked = false;
+                    const html = [];
+                    for (const [i, p] of spSpread.getActiveSheet().zh_data.entries()) {
+                        html.push('<tr>');
+                        html.push('<td>', i+1, '</td>');
+                        html.push('<td>', p.name, '</td>');
+                        html.push('<td>', p.quantity, '</td>');
+                        html.push('<td><input type="checkbox" pos-name="' + p.name + '"></td></td>');
+                        html.push('</tr>');
                     }
+                    $('#cbr-pos-list').html(html.join(''));
+                    $('#calc-by-ratio').modal('show');
                 }
             }
-        })
-    } else {
-        SpreadJsObj.forbiddenSpreadContextMenu('#stage-pos', spSpread);
-    }
+        }
+    });
     $.subMenu({
         menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
         toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
@@ -2484,6 +2568,29 @@ $(document).ready(() => {
         }
         _initModifyDetail() {
             const self = this;
+            // 重置
+            $('#reset-detail-ok').click(function () {
+                const data = SpreadJsObj.getSelectObject(self.spread.getActiveSheet());
+                if (!data.uuid) return;
+                const updateData = {
+                    lid: data.lid, pid: data.pid,
+                    uuid: data.uuid,
+                    custom_define: [],
+                };
+                for (const f of data.custom_define) {
+                    if (stageIm.resetFields.indexOf(f) >= 0) {
+                        updateData[f] = null;
+                    } else {
+                        updateData.custom_define.push(f);
+                    }
+                }
+                updateData.custom_define = updateData.custom_define.join(',');
+                $('#reset-detail-modal').modal('hide');
+                postData(window.location.pathname + '/detail/save', updateData, function (result) {
+                    stageIm.loadUpdateDetailData(result);
+                    self.reLoadDetailData();
+                });
+            });
             // 编辑
             $('#edit-detail').click(function () {
                 $(this).hide();
@@ -2688,11 +2795,7 @@ $(document).ready(() => {
                     if (data.uuid) {
                         updateData.uuid = data.uuid;
                     }
-                    if (data.custom_define.indexOf('calc_img') === -1) {
-                        updateData.custom_define = data.custom_define;
-                        updateData.custom_define.push('calc_img');
-                        updateData.custom_define = updateData.custom_define.join(',');
-                    }
+
                     updateData.img = canvas.toDataURL('image/png');
                     updateData.imgInfo = itemInfo;
                     updateData.calc_img_remark = img_remark;
@@ -2776,7 +2879,14 @@ $(document).ready(() => {
         }
         reLoadDetailData() {
             const data = SpreadJsObj.getSelectObject(this.spread.getActiveSheet());
+            $('#reset-detail').hide();
             if (data) {
+                for (const f of data.custom_define) {
+                    if (stageIm.resetFields.indexOf(f) >= 0) {
+                        $('#reset-detail').show();
+                        break;
+                    }
+                }
                 $('#edit-detail').show();
                 $('#modify-img').show();
             } else {
@@ -3064,8 +3174,6 @@ $(document).ready(() => {
                     $('.zhongjian-msg').height($('#zhongjian .sjs-bottom').height());
                 } else {
                     detail.spread.refresh();
-                    $('#zhongjian .sjs-bottom').height('400px');
-                    $('.zhongjian-msg').height($('#zhongjian .sjs-bottom').height());
                 }
             }
             if (tab.attr('content') === '#checked-change') {
@@ -3101,16 +3209,18 @@ $(document).ready(() => {
         const tabPanel = $(this).attr('fujian-content');
         if (tabPanel !== 'syfujian') {
             $('#showPage').hide();
+            $('#bach-download').prop('type', 'curr');
         } else {
             $('#showPage').show();
+            $('#bach-download').prop('type', 'all')
         }
         $('#showAttachment').hide();
     });
     // 上传附件
     $('#upload-file-btn').click(function () {
-        if (curAuditor && curAuditor.aid !== cur_uid) {
-            return toastr.error('当前操作没有权限!');
-        }
+        // if (curAuditor && curAuditor.aid !== cur_uid) {
+        //     return toastr.error('当前操作没有权限!');
+        // }
         const files = $('#upload-file')[0].files;
         const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
         const formData = new FormData();
@@ -3147,7 +3257,7 @@ $(document).ready(() => {
     });
 
     // 获取附件信息
-    $('body').on('click', '.list-table a', function () {
+    $('.list-table').on('click', '.att-file-name', function () {
         const fid = $(this).attr('file-id');
         if ($('#showAttachment').attr('file-id') === fid && !$('#showAttachment').is(":hidden")) {
             return;
@@ -3163,22 +3273,22 @@ $(document).ready(() => {
             $('#show-att tr').eq(0).children('td').text(att.filename + att.fileext);
             const name = att.code !== null && att.code !== '' ? att.code : (att.b_code !== null ? att.b_code : '');
             $('#show-att tr').eq(1).children('td').text($.trim(name + ' ' + att.lname));
-            $('#show-att tr').eq(2).find('a').attr('href', '/tender/' + tender.id + '/measure/stage/' + stage.order + '/download/file/' + att.id);
+            // $('#show-att tr').eq(2).find('a').attr('href', '/tender/' + tender.id + '/measure/stage/' + stage.order + '/download/file/' + att.id);
             // $('#show-att tr').eq(2).find('a').attr('href', att.filepath);
-            $('#show-att tr').eq(3).children('td').eq(0).text(att.username);
-            $('#show-att tr').eq(3).children('td').eq(1).text(att.in_time);
-            $('#show-att tr').eq(4).children('td').text(att.remark);
+            $('#show-att tr').eq(2).children('td').eq(0).text(att.username);
+            $('#show-att tr').eq(2).children('td').eq(1).text(att.in_time);
+            $('#show-att tr').eq(3).children('td').text(att.remark);
             // 附件uid等于当前用户id, 附件上传本人
             if (parseInt(cur_uid) === att.uid) {
                 $('#btn-att').show();
-                let showDel = false;
-                if (!curAuditor) {
-                    stage.status === auditConst.status.checked && parseInt(att.re_upload) && (showDel = true)
-                    stage.status === auditConst.status.uncheck && parseInt(cur_uid) === stage.user_id && (showDel = true)
-                    stage.status === auditConst.status.checkNo && parseInt(cur_uid) === stage.user_id && (showDel = true)
-                } else {
-                    curAuditor.aid === parseInt(cur_uid) && (showDel = true)
-                }
+                const showDel = stage.status === auditConst.status.checked ? Boolean(att.extra_upload) : true;
+                // if (!curAuditor) {
+                //     stage.status === auditConst.status.checked && parseInt(att.re_upload) && (showDel = true)
+                //     stage.status === auditConst.status.uncheck && parseInt(cur_uid) === stage.user_id && (showDel = true)
+                //     stage.status === auditConst.status.checkNo && parseInt(cur_uid) === stage.user_id && (showDel = true)
+                // } else {
+                //     curAuditor.aid === parseInt(cur_uid) && (showDel = true)
+                // }
                 if (showDel) $('#btn-att a').eq(3).show();
                 // $('#btn-att a').eq(3).show();
                 $('#btn-att a').eq(2).hide();
@@ -3253,7 +3363,7 @@ $(document).ready(() => {
                 $('#show-att tr').eq(0).children('td').text(data.filename + data.fileext);
                 const name = data.code !== null && data.code !== '' ? data.code : (data.b_code !== null ? data.b_code : '');
                 $('#show-att tr').eq(1).children('td').text($.trim(name + ' ' + data.lname));
-                $('#show-att tr').eq(2).find('a').attr('href', '/tender/' + tender.id + '/measure/stage/' + stage.order + '/download/file/' + data.id);
+                // $('#show-att tr').eq(2).find('a').attr('href', '/tender/' + tender.id + '/measure/stage/' + stage.order + '/download/file/' + data.id);
                 $('#show-att tr').eq(3).children('td').eq(0).text(data.username);
                 $('#show-att tr').eq(3).children('td').eq(1).text(data.in_time);
                 $('#show-att tr').eq(4).children('td').text(data.remark);
@@ -3332,11 +3442,54 @@ $(document).ready(() => {
         if (status === 'pre' && lastPageNum > 1) {
             getAllList(lastPageNum-1);
             $('#showAttachment').hide();
+            $('#syfujian .check-all-file').prop('checked', false)
         } else if (status === 'next' && lastPageNum < totalPageNum) {
             getAllList(lastPageNum+1);
             $('#showAttachment').hide();
+            $('#syfujian .check-all-file').prop('checked', false)
         }
     });
+
+    // 批量下载
+    $('#bach-download').click(function() {
+        const fileIds = [];
+        const type = $(this).prop('type');
+        let node = ''
+        if (type === 'curr') {
+            node = '#nodelist-table .check-file:checked'
+        } else {
+            node = '#alllist-table .check-file:checked'
+        }
+        $(node).each(function() {
+            const fileId = $(this).attr('file-id');
+            fileId && fileIds.push(fileId);
+        });
+
+        if (fileIds.length) {
+            const url = `/tender/${tender.id}/measure/stage/${stage.order}/download/compresse-file?fileIds=${JSON.stringify(fileIds)}`;
+            $('#zipDown').attr('href', url);
+            $("#zipDown")[0].click();
+        }
+    });
+
+    // 监听附件check是否选中
+    $('.list-table').on('click', '.check-file', function() {
+        const checkedList = $(this).parents('.list-table').children().find('input:checked');
+        const childs = $(this).parents('.list-table').children().length;
+        const checkBox = $(this).parents('.list-table').parent().find('.check-all-file');
+        if (checkedList.length === childs) {
+            checkBox.prop("checked", true);
+        } else {
+            checkBox.prop("checked", false);
+        }
+    });
+    $('.check-all-file').click(function() {
+        const isCheck = $(this).is(':checked');
+        $(this).parents('table').find('.list-table').each(function() {
+            $(this).find('input:checkbox').prop("checked", isCheck);
+        })
+    });
+
     // 显示层次
     (function (select, sheet) {
         $(select).click(function () {
@@ -3531,4 +3684,33 @@ $(document).ready(() => {
 
         SpreadExcelObj.exportSimpleXlsxSheet(setting, data, $('.sidebar-title').attr('data-original-title') + "计量台账.xlsx");
     });
+
+    $('#cbr-check-all').click(function () {
+        if (this.checked) {
+            $('input', '#cbr-pos-list').attr('checked', 'checked');
+        } else {
+            $('input', '#cbr-pos-list').removeAttr('checked');
+        }
+    });
+    $('#cbr-ok').click(() => {
+        const ratio = parseInt($('#cbr-ratio').val());
+        if (!ratio) {
+            toastr.warning('请输入计量比例');
+            return;
+        } else if (ratio < 1) {
+            toastr.warning('计量比例不可小于1');
+            return;
+        } else if (ratio > 100) {
+            toastr.warning('计量比例不可大于100');
+            return;
+        }
+
+        const apply2sibling = $('#apply2sibling')[0].checked;
+        const posName = _.map($('input:checked', '#cbr-pos-list'), function (x) {return $(x).attr('pos-name')});
+        if (posName.length === 0) {
+            toastr.warning('请勾选需要按计量比例的计量单元');
+            return;
+        }
+        stageTreeSpreadObj.measureByBatch(posName, ratio, apply2sibling);
+    })
 });

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

@@ -114,59 +114,53 @@ $(document).ready(function () {
     //     });
     // });
     // 审批人分组选择
-    $('#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) && account.id !== parseInt(userID)) {
-                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);
-    });
+    // $('#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) && account.id !== parseInt(userID)) {
+    //             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);
+    // });
     // 添加到审批流程中
     $('dl').on('click', 'dd', function () {
         const id = parseInt($(this).data('id'));
         if (id !== 0) {
-            postData(getUrlPre() + '/audit/add', { auditorId: id }, (data) => {
+            postData(getUrlPre() + '/audit/add', { auditorId: id }, (datas) => {
                 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(''));
-
                 // 如果是重新上报,添加到重新上报列表中
                 const auditorshtml = [];
-                // 重新上报时。令其它的审批人流程图标转换
-                $('#auditors-list li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
-                for (let i = 0; i < $('#auditors-list li').length; i++) {
-                    $('#auditors-list li').eq(i).find('.pull-right').text(transFormToChinese(i+1) + '审');
-                    $('#auditors-list2 li').eq(i).find('.pull-right').text(transFormToChinese(i+1) + '审');
+                for (const [index,data] of datas.entries()) {
+                    if (index !== 0) {
+                        html.push('<li class="list-group-item" auditorId="'+ data.aid +'">');
+                        if (shenpi_status === shenpiConst.sp_status.sqspr || (shenpi_status === shenpiConst.sp_status.gdzs && index+1 !== datas.length)) {
+                            html.push('<a href="javascript: void(0)" class="text-danger pull-right">移除</a>');
+                        }
+                        html.push('<span>');
+                        html.push(data.order + ' ');
+                        html.push(data.name + ' ');
+                        html.push('</span>');
+                        html.push('<small class="text-muted">');
+                        html.push(data.role);
+                        html.push('</small></li>');
+                    }
+                    auditorshtml.push('<li class="list-group-item" data-auditorid="' + data.aid + '">');
+                    auditorshtml.push('<i class="fa ' + (index+1 === datas.length ? 'fa-stop-circle' : 'fa-chevron-circle-down') + '"></i> ');
+                    auditorshtml.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
+                    if (index === 0) {
+                        auditorshtml.push('<span class="pull-right">原报</span>');
+                    } else if (index+1 === datas.length) {
+                        auditorshtml.push('<span class="pull-right">终审</span>');
+                    } else {
+                        auditorshtml.push('<span class="pull-right">'+ transFormToChinese(index) +'审</span>');
+                    }
+                    auditorshtml.push('</li>');
                 }
-                // 添加新审批人
-                auditorshtml.push('<li class="list-group-item" data-auditid="' + data.aid + '">');
-                auditorshtml.push('<i class="fa fa-stop-circle"></i> ');
-                auditorshtml.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
-                auditorshtml.push('<span class="pull-right">终审</span>');
-                auditorshtml.push('</li>');
-                $('#auditors-list').append(auditorshtml.join(''));
-
-                const auditorshtml2 = [];
-                // 重新上报时。令其它的审批人流程图标转换
-                $('#auditors-list2 li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
-                // 添加新审批人
-                auditorshtml2.push('<li class="list-group-item" data-auditid="' + data.aid + '">');
-                auditorshtml2.push('<h5 class="card-title"><i class="fa fa-stop-circle"></i> ');
-                auditorshtml2.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
-                auditorshtml2.push('<span class="pull-right">终审</span>');
-                auditorshtml2.push('</h5></li>');
-                $('#auditors-list2').append(auditorshtml2.join(''));
+                $('#auditors').html(html.join(''));
+                $('#auditors-list').html(auditorshtml.join(''));
             });
         }
     });
@@ -185,19 +179,19 @@ $(document).ready(function () {
 
             // 如果是重新上报
             // 令最后一个图标转换
-            $('#auditors-list li[data-auditid="' + data.auditorId + '"]').remove();
+            $('#auditors-list li[data-auditorid="' + data.auditorId + '"]').remove();
             if ($('#auditors-list li').length !== 0 && !$('#auditors-list li i').hasClass('fa-stop-circle')) {
                 $('#auditors-list li').eq($('#auditors-list li').length-1).children('i')
                     .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
             }
-            $('#auditors-list2 li[data-auditid="' + data.auditorId + '"]').remove();
-            if ($('#auditors-list2 li').length !== 0 && !$('#auditors-list2 li i').hasClass('fa-stop-circle')) {
-                $('#auditors-list2 li').eq($('#auditors-list2 li').length-1).children('i')
-                    .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
-            }
+            // $('#auditors-list2 li[data-auditid="' + data.auditorId + '"]').remove();
+            // if ($('#auditors-list2 li').length !== 0 && !$('#auditors-list2 li i').hasClass('fa-stop-circle')) {
+                // $('#auditors-list2 li').eq($('#auditors-list2 li').length-1).children('i')
+                //     .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
+            // }
             for (let i = 0; i < $('#auditors-list li').length; i++) {
-                $('#auditors-list li').eq(i).find('.pull-right').text((i+1 === $('#auditors-list li').length ? '终' : transFormToChinese(i+1)) + '审');
-                $('#auditors-list2 li').eq(i).find('.pull-right').text((i+1 === $('#auditors-list2 li').length ? '终' : transFormToChinese(i+1)) + '审');
+                $('#auditors-list li').eq(i).find('.pull-right').text(i === 0 ? '原报' : (i+1 === $('#auditors-list li').length ? '终' : transFormToChinese(i)) + '审');
+                // $('#auditors-list2 li').eq(i).find('.pull-right').text((i+1 === $('#auditors-list2 li').length ? '终' : transFormToChinese(i+1)) + '审');
             }
         });
     });
@@ -252,7 +246,11 @@ $(document).ready(function () {
 // 检查上报情况
 function checkAuditorFrom () {
     if ($('#auditors li').length === 0) {
-        toastr.error('请先选择审批人,再上报数据');
+        if(shenpi_status === shenpiConst.sp_status.gdspl) {
+            toastr.error('请联系管理员添加审批人');
+        } else {
+            toastr.error('请先选择审批人,再上报数据');
+        }
         return false;
     }
     $('#hide-all').show();

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

@@ -131,7 +131,7 @@ $(document).ready(() => {
             {title: '相关台账|项目节编号', colSpan: '5|1', rowSpan: '1|1', field: 'leaf_xmj_code', hAlign: 0, width: 120, formatter: '@', readOnly: true},
             {title: '|名称', colSpan: '|1', rowSpan: '1', field: 'leaf_xmj_name', hAlign: 0, width: 150, formatter: '@', readOnly: true},
             {title: '|计量单元', colSpan: '|1', rowSpan: '1', field: 'p_name', hAlign: 0, width: 150, type: 'Number', readOnly: true},
-            {title: '|0号台数量', colSpan: '|1', rowSpan: '1', field: 'f_qty', hAlign: 2, width: 80, formatter: '@', readOnly: true},
+            {title: '|0号台数量', colSpan: '|1', rowSpan: '1', field: 'f_qty', hAlign: 2, width: 80, formatter: '@', readOnly: true},
             {title: '|本期变更数量', colSpan: '|1', rowSpan: '1', field: 'qty', hAlign: 2, width: 80, type: 'Number', readOnly: true},
         ],
         emptyRows: 0,

+ 21 - 12
app/public/js/stage_im.js

@@ -9,7 +9,8 @@
  */
 
 const stageIm = (function () {
-    const imFields = ['uuid', 'doc_code', 'peg', 'bw', 'xm', 'drawing_code', 'calc_memo', 'calc_img', 'calc_img_org','calc_img_remark', 'position', 'jldy'];
+    const imFields = ['uuid', 'peg', 'bw', 'xm', 'drawing_code', 'calc_memo', 'position', 'jldy'];
+    const resetFields = ['peg', 'bw', 'xm', 'drawing_code', 'calc_memo', 'position', 'jldy'];
     const splitChar = '-';
     const mergeChar = ';';
     let stage, imType, decimal, details, changes, ImData, pre;
@@ -254,10 +255,22 @@ const stageIm = (function () {
     }
 
     function loadCustomDetail(im, detail) {
-        im.custom_define = detail.custom_define ? detail.custom_define.split(',') : imFields;
+        if (!im.org) {
+            im.org = {};
+            for (const f of resetFields) {
+                im.org[f] = im[f];
+            }
+        }
+        im.custom_define_exist = im.custom_define !== null;
+        im.custom_define = detail.custom_define ? detail.custom_define.split(',') : [];
         _.assignInWith(im, detail, function (oV, sV, key) {
-            return (im.custom_define.indexOf(key) > -1 && sV !== undefined && sV !== null) ? sV : oV;
+            return im.custom_define_exist
+                ? (im.custom_define.indexOf(key) > -1 ? sV : ((resetFields.indexOf(key) > -1) ? im.org[key] : oV))
+                : (imFields.indexOf(key) > -1 ? sV : ((resetFields.indexOf(key) > -1) ? im.org[key] : oV));
         });
+        im.uuid = detail.uuid;
+        im.doc_code = detail.doc_code;
+        im.calc_img = detail.calc_img;
         im.calc_img_org = detail.calc_img_org;
         im.calc_img_remark = detail.calc_img_remark;
     }
@@ -497,7 +510,6 @@ const stageIm = (function () {
                 im.bw = getZlNormalBw(node, peg);
                 im.xm = node.name;
             }
-            checkCustomDetail(im);
             //if (!stage.im_gather || !node.check) {
                 generateTzGclBillsData(node, im);
             //}
@@ -548,7 +560,6 @@ const stageIm = (function () {
                 custom_define: [],
                 source: [{id: node.ledger_id, code: node.code, b_code: node.b_code}],
             };
-            checkCustomDetail(imDefault);
             for (const p of posterity) {
                 if (p.children && p.children.length > 0) continue;
                 if (!p.b_code || p.b_code === '') continue;
@@ -573,7 +584,6 @@ const stageIm = (function () {
                                 source: [],
                             };
                             nodeImData.push(im);
-                            checkCustomDetail(im);
                         }
                         im.source.push({id: p.ledger_id, code: p.code, b_code: p.b_code});
 
@@ -725,7 +735,6 @@ const stageIm = (function () {
                     im.drawing_code = getDrawingCode(p);
                 }
                 nodeImData.push(im);
-                checkCustomDetail(im);
                 ImData.push(im);
             }
             im.source.push({id: p.ledger_id, code: p.code, b_code: p.b_code});
@@ -766,7 +775,6 @@ const stageIm = (function () {
                         source: [{id: p.ledger_id, code: p.code, b_code: p.b_code}],
                     };
                     im.calc_memo = '本期计量:' + (checkZero(im.jl) ? 0 : im.jl) + ' ' + im.unit;
-                    checkCustomDetail(im);
                     ImData.push(im);
                     if (pp.qc_qty && pp.qc_qty !== 0) {
                         for (const c of changes) {
@@ -794,7 +802,6 @@ const stageIm = (function () {
                     source: [{id: p.ledger_id, code: p.code, b_code: p.b_code}],
                 };
                 im.calc_memo = '本期计量:' + (checkZero(im.jl) ? 0 : im.jl) + ' ' + im.unit;
-                checkCustomDetail(im);
                 ImData.push(im);
                 if (p.qc_qty && p.qc_qty !== 0) {
                     for (const c of changes) {
@@ -857,7 +864,8 @@ const stageIm = (function () {
             }
             getCalcMemo(im);
             getChangeInfo(im);
-            im.im_code = pre + getNumberFormat(stage.order, 2) + splitChar + getNumberFormat(i + 1, 3)
+            im.im_code = pre + getNumberFormat(stage.order, 2) + splitChar + getNumberFormat(i + 1, 3);
+            checkCustomDetail(im);
         }
         return ImData;
     }
@@ -867,8 +875,8 @@ const stageIm = (function () {
         for (const d of datas) {
             const detail = _.find(details, {uuid: d.uuid});
             if (detail) {
-                _.assignInWith(detail, d, function (oV, sV, key) {
-                    return imFields.indexOf(key) > -1 && !_.isUndefined(sV) && !_.isNull(sV) ? sV : oV;
+                _.assignInWith(detail, d, function (oV, sV) {
+                    return !_.isUndefined(sV) ? sV : oV;
                 });
                 detail.custom_define = d.custom_define;
             } else {
@@ -988,5 +996,6 @@ const stageIm = (function () {
         getFirstUsed: getFirstUsed,
         getRelaXmj: getRelaXmj,
         getRelaImData: getRelaImData,
+        resetFields,
     }
 })();

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

@@ -813,8 +813,8 @@ $(document).ready(() => {
                         if (col.field === 'tp') {
                             updateData.pid = node.pid;
                             const [valid, msg] = payBase.isSF(node)
-                                ? paySpreadObj._checkSfExpr(validText, data.updateData)
-                                : paySpreadObj._checkExpr(validText, data.updateData);
+                                ? paySpreadObj._checkSfExpr(validText, updateData)
+                                : paySpreadObj._checkExpr(validText, updateData);
                             if (!valid) {
                                 toastr.warning(msg);
                                 SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());

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

@@ -386,7 +386,7 @@ function getTenderTreeHtml () {
         html.push('<th class="text-center" style="width: 6%">', '计量进度', '</th>');
         html.push('<th class="text-center" style="width: 6%">', '当前流程', '</th>');
         html.push('<th class="text-center" style="width: 8%">', '上一流程审批时间', '</th>');
-        html.push('<th class="text-center" style="width: 6%">', '0号台', '</th>');
+        html.push('<th class="text-center" style="width: 6%">', '0号台', '</th>');
         html.push('<th class="text-center" style="width: 6%">', '本期完成', '</th>');
         html.push('<th class="text-center" style="width: 6%">', '截止本期合同', '</th>');
         html.push('<th class="text-center" style="width: 6%">', '截止本期变更', '</th>');

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

@@ -360,7 +360,7 @@ const rptCustomObj = (function () {
 
     const resetAuditSelect = function () {
         const selObj = $('select', '#audit-select-list');
-        const data = { audit_select: [] };
+        const data = { audit_select: [], closeWatermark: getCloseWatermark() };
         getCommonParams(data);
         for (const s of selObj) {
             const sf = stageFlow[s.selectedIndex];

+ 28 - 0
app/public/report/js/rpt_main.js

@@ -184,6 +184,13 @@ let zTreeOprObj = {
         document.getElementById("cfg_rpt_vertical_line").checked = cfg.showVerticalLine;
         document.getElementById("cfg_rpt_fill_zero").checked = cfg.fillZero;
         document.getElementById("cfg_rpt_narrow").checked = cfg.isNarrow;
+        document.getElementById("cfg_rpt_close_warter_mark").checked = cfg.closeWarterMark;
+        if (PAGE_SHOW['closeWatermark'] === 0) {
+            $("#cfg_rpt_close_warter_mark_div")[0].style.display = '';
+        } else {
+            $("#cfg_rpt_close_warter_mark_div")[0].style.display = 'none';
+        }
+        document.getElementById("cfg_rpt_continuous").checked = cfg.continuousOutput;
     },
     extractRptCfg: function (cfg) {
         cfg.margins.Left = $("#elementMargin_Left")[0].value;
@@ -299,7 +306,9 @@ let zTreeOprObj = {
             params.stage_order = getStageOrder();
             params.stage_times = getStageTimes();
             params.material_order = getMaterialOrder();
+            params.closeWatermark = getCloseWatermark();
             // me.requestNormalReport(params);
+            localStorage[CUST_NAME + '_custCfg'] = JSON.stringify(CUST_CFG);
 
             const gather_select = customSelects.gather_select.find(function (x) {
                 return x.id === me.currentNode.refId;
@@ -545,9 +554,11 @@ let rptControlObj = {
             let refRptTplIds = [], rpt_sheet_names = [];
             rptControlObj.getTplIdsCommon(refRptTplIds, rpt_sheet_names);
             let params = rptControlObj.creatCommonExportParam(refRptTplIds);
+            params.closeWatermark = getCloseWatermarkForExcel();
             await rptCustomObj.getCustomSelect(params);
             params.rpt_names = rpt_sheet_names;
             params.rptName = TENDER_NAME;
+            params.option = getExcelOutputOption();
             let chkNodes = zTreeOprObj.treeObj.getCheckedNodes(true);
             if (chkNodes.length > 0) {
                 delete params.orientation; // 打印时有勾选的话,不需要提供方向
@@ -572,10 +583,13 @@ let rptControlObj = {
             let rpt_names = [];
             rptControlObj.getTplIdsCommon(refRptTplIds, rpt_names);
             let params = rptControlObj.creatCommonExportParam(refRptTplIds);
+            params.closeWatermark = getCloseWatermarkForExcel();
             await rptCustomObj.getCustomSelect(params);
             params.isOneSheet = true;
             params.rpt_names = rpt_names;
             params.rptName = 'All';
+            // 测试连续输出
+            params.option = getExcelOutputOption();
             let chkNodes = zTreeOprObj.treeObj.getCheckedNodes(true);
             if (chkNodes.length > 0) {
                 delete params.orientation; // 打印时有勾选的话,不需要提供方向
@@ -912,3 +926,17 @@ function getStageTimes() {
 function getCloseWatermark() {
     return PAGE_SHOW['closeWatermark'];
 }
+function getCloseWatermarkForExcel() {
+    if (PAGE_SHOW['closeWatermark'] === 1 || CUST_CFG.closeWarterMark) {
+        return 1;
+    } else {
+        return 0;
+    }
+}
+function getExcelOutputOption() {
+    if (CUST_CFG.continuousOutput) {
+        return 'infinity'; // 连续输出方式
+    } else {
+        return 'normal'; // 目前是普通方式
+    }
+}

+ 12 - 3
app/public/report/js/rpt_preview_common.js

@@ -45,7 +45,7 @@ function printPageLoading() {
 
 function showPreviewData(svgArr, actAreaArr, scaleFactor, pageSize, orientation, orgPixelSize) {
     let orgHeight = 793, orgWidth = 1122;
-    let DFT_MARGIN = 5;
+    let DFT_MARGIN = 2;
     if (pageSize === 'A3') {
         orgHeight = 1122;
         orgWidth = 793 * 2;
@@ -62,17 +62,26 @@ function showPreviewData(svgArr, actAreaArr, scaleFactor, pageSize, orientation,
 
     for (let i = 0; i < svgArr.length; i++) {
         let offsetHeight = 0, offsetWidth = 0;
+        let paddingStr = '';
         if (actAreaArr) {
+            paddingStr = `padding: ${actAreaArr[i].Top}px 0px 0px ${actAreaArr[i].Left}px`;
+            if (actAreaArr[i].Right > actAreaArr[i].Bottom) {
+                //横向,需要重新调整padding
+                paddingStr = `padding: ${actAreaArr[i].Left}px 0px 0px ${orgHeight - actAreaArr[i].Bottom}px`;
+            }
             offsetWidth = actAreaArr[i].Left + (pageWidth - actAreaArr[i].Right) - DFT_MARGIN;
             offsetHeight = actAreaArr[i].Top + (pageHeight - actAreaArr[i].Bottom) - DFT_MARGIN;
             if (orgPixelSize[0] > orgPixelSize[1]) {
                 //横向强制改纵向(系统是以纵向为准),那么计算offset的方式会有所不同
                 offsetWidth = actAreaArr[i].Top + (pageWidth - actAreaArr[i].Bottom) - DFT_MARGIN;
                 //横向转纵向时,还得考虑左上角转右上角的Left与Top之间的差,否则坐标还是会有偏差
-                offsetHeight = actAreaArr[i].Left + (pageHeight - actAreaArr[i].Right) - (actAreaArr[i].Left - actAreaArr[i].Top) - DFT_MARGIN;
+                // offsetHeight = actAreaArr[i].Left + (pageHeight - actAreaArr[i].Right) - (actAreaArr[i].Left - actAreaArr[i].Top) - DFT_MARGIN;
+                //新方式下,没那么复杂了
+                offsetHeight = actAreaArr[i].Left + (pageHeight - actAreaArr[i].Right) - DFT_MARGIN;
             }
         }
-        let div = $('<div class="pageBreak"></div>');
+        // let div = $('<div class="pageBreak"></div>');
+        let div = $(`<div class="pageBreak" style="${paddingStr}"></div>`);
         div.append($(svgArr[i].join("")));
         $(div).find("svg").each(function(cIdx,elementSvg){
             elementSvg.setAttribute('height', pageHeight - offsetHeight);

+ 6 - 8
app/public/report/js/rpt_print.js

@@ -180,10 +180,9 @@ function buildSignatureCellSvg(cell, styles, controls, pageMergeBorder, rptMerge
     ;
     let HtoVStr = "";
     if (isHtoV) {
-        //HtoVStr = ` transform="translate(`+ pixelSize[1] + `,0) rotate(90)"`;
-        // HtoVStr = ` transform="translate(`+ (actArea.Bottom - actArea.Top + 5) + `,0) rotate(90)"`;
-        HtoVStr = ` transform="translate(`+ (actArea.Bottom - actArea.Top + 5) + `,` + (actArea.Left - actArea.Top ) + `) rotate(90)"`;
-        //console.log(actArea);
+        // HtoVStr = ` transform="translate(`+ (actArea.Bottom - actArea.Top + 5) + `,` + (actArea.Left - actArea.Top ) + `) rotate(90)"`;
+        //引用了padding后,top坐标不用考虑offset了
+        HtoVStr = ` transform="translate(${(actArea.Bottom - actArea.Top + 2)},0) rotate(90)"`;
     }
     if (style) {
         let leftBS = getActualBorderStyle(cell, styles, mergeBandStyle, (pageMergeBorder)?pageMergeBorder:rptMergeBorder[JV.PROP_AREA], JV.PROP_LEFT);
@@ -244,10 +243,9 @@ function buildCellSvg(cell, fonts, styles, controls, pageMergeBorder, rptMergeBo
     ;
     let HtoVStr = "";
     if (isHtoV) {
-        //HtoVStr = ` transform="translate(`+ pixelSize[1] + `,0) rotate(90)"`;
-        // HtoVStr = ` transform="translate(`+ (actArea.Bottom - actArea.Top + 5) + `,0) rotate(90)"`;
-        HtoVStr = ` transform="translate(`+ (actArea.Bottom - actArea.Top + 5) + `,` + (actArea.Left - actArea.Top ) + `) rotate(90)"`;
-        //console.log(actArea);
+        // HtoVStr = ` transform="translate(`+ (actArea.Bottom - actArea.Top + 5) + `,` + (actArea.Left - actArea.Top ) + `) rotate(90)"`;
+        //引用了padding后,top坐标不用考虑offset了
+        HtoVStr = ` transform="translate(${(actArea.Bottom - actArea.Top + 2)},0) rotate(90)"`;
     }
     if (style) {
         let leftBS = getActualBorderStyle(cell, styles, mergeBandStyle, (pageMergeBorder)?pageMergeBorder:rptMergeBorder[JV.PROP_AREA], JV.PROP_LEFT);

+ 5 - 1
app/reports/rpt_component/helper/jpc_helper_discrete.js

@@ -96,7 +96,11 @@ const JpcDiscreteHelper = {
                                         const signatureItem = { signature_name: JV.SIGNATURE_NAME_DUMMY, path: item[JV.PROP_VALUE], pic: null }; // 这里的pic数据在指标中里已经处理过了
                                         signatureItem[JV.PROP_CONTROL] = item[[JV.PROP_CONTROL]];
                                         signatureItem[JV.PROP_STYLE] = item[[JV.PROP_STYLE]];
-                                        signatureItem[JV.PROP_AREA] = item[JV.PROP_AREA];
+                                        signatureItem[JV.PROP_AREA] = {};
+                                        signatureItem[JV.PROP_AREA][JV.PROP_LEFT] = item[JV.PROP_AREA][JV.PROP_LEFT];
+                                        signatureItem[JV.PROP_AREA][JV.PROP_RIGHT] = item[JV.PROP_AREA][JV.PROP_RIGHT];
+                                        signatureItem[JV.PROP_AREA][JV.PROP_TOP] = item[JV.PROP_AREA][JV.PROP_TOP];
+                                        signatureItem[JV.PROP_AREA][JV.PROP_BOTTOM] = item[JV.PROP_AREA][JV.PROP_BOTTOM];
                                         signatureRst.push(signatureItem);
                                         item[JV.PROP_VALUE] = '';
                                     }

+ 2 - 2
app/reports/rpt_component/helper/jpc_helper_flow_tab.js

@@ -19,7 +19,7 @@ let JpcFlowTabHelper = {
         }
         return rst;
     },
-    getActualContentAreaHeight: function(bands, rptTpl, segments, page, isEx) {
+    getActualContentAreaHeight: function(bands, rptTpl, segments, rowAmt, page, isEx) {
         let rst = 1;
         let FLOW_INFO_STR = (!isEx)?JV.NODE_FLOW_INFO:JV.NODE_FLOW_INFO_EX;
         let tab = rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT];
@@ -33,7 +33,7 @@ let JpcFlowTabHelper = {
                 maxFieldMeasure = (band.Bottom - band.Top) * rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT][JV.PROP_CMN_HEIGHT] / JV.HUNDRED_PERCENT;
             }
             if (segments.length >= page) {
-                rst = segments[page - 1].length * maxFieldMeasure;
+                rst = rowAmt * maxFieldMeasure;
             }
         }
         return rst;

+ 82 - 5
app/reports/rpt_component/jpc_flow_tab.js

@@ -15,6 +15,7 @@ const fsUtil = require('../public/fsUtil');
 const strUtil = require('../public/stringUtil');
 const fontWidthMap = require('./helper/jpc_helper_font_width');
 const bc = require('../../lib/base_calc.js');
+const SEG_PAGE_SPLIT_STR = '`';
 
 const JpcFlowTabSrv = function() {};
 JpcFlowTabSrv.prototype.createNew = function() {
@@ -184,6 +185,8 @@ JpcFlowTabSrv.prototype.createNew = function() {
         me.signatureRst = [];
         me.signatureDateRst = [];
         me.signatureAuditRst = [];
+
+        me.hasBreakFlowArr = [];
     };
     JpcFlowTabResult.sorting = function(rptTpl, dataObj, dataSeq, $CURRENT_RPT) {
         const me = this;
@@ -515,24 +518,87 @@ JpcFlowTabSrv.prototype.createNew = function() {
             followTabEx.setupGroupingData(rptTpl, dataObj, $CURRENT_RPT);
         }
         me.sumSeg(dataObj, $CURRENT_RPT); // 考虑到实际会有离散指标的汇总,这些离散指标数据是通过计算式后得来的,这种情况下,不适宜在sorting阶段进行汇总统计,现统一挪到这里处理
+        // const _chkHasBreakFlow = function(segIdx, pageIdx) {
+        //     if (rptTpl[JV.NODE_FLOW_INFO][JV.NODE_DISCRETE_INFO]) {
+        //         for (const discrete of rptTpl[JV.NODE_FLOW_INFO][JV.NODE_DISCRETE_INFO]) {
+        //             if (discrete[JV.PROP_DISCRETE_FIELDS] && discrete[JV.PROP_DISCRETE_FIELDS].length > 0) {
+        //                 for (const df of discrete[JV.PROP_DISCRETE_FIELDS]) {
+        //                     const map_data_field = JE.F(df[JV.PROP_FIELD_ID], $CURRENT_RPT);
+        //                     if (map_data_field) {
+        //                         const val = JE.getFieldValue(map_data_field, dataObj, segIdx, '');
+        //                         if (typeof val === 'string') {
+        //                             if (val.indexOf(SEG_PAGE_SPLIT_STR) >= 0) {
+        //                                 me.hasBreakFlowArr[pageIdx][0] = true;
+        //                                 break;
+        //                             }
+        //                         }
+        //                     }
+        //                     if (me.hasBreakFlowArr[pageIdx][0]) break;
+        //                 }
+        //             }
+        //             if (me.hasBreakFlowArr[pageIdx][0]) break;
+        //         }
+        //     }
+        // };
+        const bands = JpcBand.createNew(rptTpl, defProperties);
         if (me.paging_option === JV.PAGING_OPTION_INFINITY) {
             rst = me.segments.length;
-            const pageStatus = [true, true, false, true, true, true, false, false];
+            const pageStatus = [true, true, false, true, true, true, true, true];
+            const grpPageInfo = {};
+            let pageIdx = 0;
             for (let segIdx = 0; segIdx < me.segments.length; segIdx++) {
+                /*
+                me.hasBreakFlowArr[pageIdx] = [];
+                me.hasBreakFlowArr[pageIdx][0] = false;
+                me.hasBreakFlowArr[pageIdx][1] = segIdx;
+                _chkHasBreakFlow(segIdx, pageIdx);
+                //*/
+                // 备注:有break flow的情况下 连续输出 要考虑段(seg)的多页输出问题
                 if (segIdx === me.segments.length - 1) {
                     pageStatus[JV.STATUS_REPORT_END] = true;
                 }
                 if (segIdx > 0) {
                     pageStatus[JV.STATUS_REPORT_START] = false;
                 }
+                const grpSeqInfo = (me.group_node_info) ? me.group_node_info[segIdx] : null;
+                const grpRecAmt = (grpSeqInfo) ? (grpSeqInfo.length * me.group_lines_amt) : 0;
+                grpPageInfo[JV.PROP_SEG_GRP_IDX] = 0;
+                grpPageInfo[JV.PROP_INSERTED_GRP_REC] = 0;
+                grpPageInfo[JV.PROP_PRE_ADD_GRP_REC_INFO] = [];
+                grpPageInfo[JV.PROP_GRP_LINES] = me.group_lines_amt;
+                JpcBandHelper.setBandArea(bands, rptTpl, pageStatus, !me.isEx, me.isEx);
+                maxRowRec = JpcFlowTabHelper.getMaxRowsPerPage(bands, rptTpl, me.isEx);
+                me.setupAutoHeightData(bands, segIdx, rptTpl, dataObj, $CURRENT_RPT, defProperties, outputType);
+                let adHocAutoHeightAmt = 0;
+                if (me.auto_height_info.length > 0) {
+                    for (let loop = 0; loop < me.auto_height_info[segIdx].length; loop++) {
+                        adHocAutoHeightAmt += (me.auto_height_info[segIdx][loop] - 1);
+                    }
+                }
                 me.pageStatusLst.push(pageStatus.slice(0));
                 pageIdx++;
-                const grpSeqInfo = (me.group_node_info) ? me.group_node_info[segIdx] : null;
-                private_addPageValue(me.dispValueIdxLst, me.segments[segIdx], grpSeqInfo, 0, me.segments[segIdx].length, me.page_seg_map, segIdx, pageIdx, null, false, me.auto_height_info, 0);
+                const rowAmt = Math.max(maxRowRec, me.segments[segIdx].length + adHocAutoHeightAmt);
+                /*
+                if (me.hasBreakFlowArr[pageIdx][0]) {
+                    let accAmt = 0;
+                    rowAmt = maxRowRec;
+                    while (accAmt < me.segments[segIdx].length + adHocAutoHeightAmt) {
+                        me.hasBreakFlowArr[pageIdx][0] = true;
+                        me.hasBreakFlowArr[pageIdx][1] = segIdx;
+                        private_addPageValue(me.dispValueIdxLst, me.segments[segIdx], grpSeqInfo, 0, rowAmt, me.page_seg_map, segIdx, pageIdx, grpPageInfo, false, me.auto_height_info, 0);
+                        pageIdx++;
+                        accAmt += maxRowRec;
+                        // remark: 需要再细化
+                    }
+                } else {
+                    private_addPageValue(me.dispValueIdxLst, me.segments[segIdx], grpSeqInfo, 0, rowAmt, me.page_seg_map, segIdx, pageIdx, grpPageInfo, false, me.auto_height_info, 0);
+                    pageIdx++;
+                }
+                //*/
+                private_addPageValue(me.dispValueIdxLst, me.segments[segIdx], grpSeqInfo, 0, rowAmt, me.page_seg_map, segIdx, pageIdx, grpPageInfo, false, me.auto_height_info, 0);
             }
             // 目前不支持flowTabEx
         } else {
-            const bands = JpcBand.createNew(rptTpl, defProperties);
             const pageStatus = [true, true, false, true, false, false, false, false];
             if (me.isEx) {
                 pageStatus[JV.STATUS_REPORT_START] = false;
@@ -799,10 +865,21 @@ JpcFlowTabSrv.prototype.createNew = function() {
             const segIdx = page - 1;
             // 1 calculate the band position
             JpcBandHelper.setBandArea(bands, rptTpl, me.pageStatusLst[page - 1], !me.isEx, me.isEx);
+            const maxRowRec = JpcFlowTabHelper.getMaxRowsPerPage(bands, rptTpl, me.isEx);
             // 2. then reset the band height
             const tab = rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_CONTENT];
             const flowContentBand = bands[tab[JV.PROP_BAND_NAME]];
-            const actH = JpcFlowTabHelper.getActualContentAreaHeight(bands, rptTpl, me.segments, page, me.isEx);
+            let adHocAutoHeightAmt = 0;
+            if (me.auto_height_info.length > 0) {
+                for (let loop = 0; loop < me.auto_height_info[segIdx].length; loop++) {
+                    adHocAutoHeightAmt += (me.auto_height_info[segIdx][loop] - 1);
+                }
+            }
+            const rowAmt = Math.max(maxRowRec, me.segments[segIdx].length + adHocAutoHeightAmt); // 如不满一页,则需要填充满一页
+            // if (me.hasBreakFlowArr[segIdx]) {
+            //     rowAmt = maxRowRec;
+            // }
+            const actH = JpcFlowTabHelper.getActualContentAreaHeight(bands, rptTpl, me.segments, rowAmt, page, me.isEx);
             const offsetY = actH - (flowContentBand.Bottom - flowContentBand.Top);
             JpcBandHelper.resetBandPos(rptTpl[JV.NODE_BAND_COLLECTION], bands, flowContentBand, 0, offsetY);
             // 2.1 Content-Tab

+ 1 - 0
app/reports/rpt_component/jpc_value_define.js

@@ -227,6 +227,7 @@ module.exports = {
     RUN_TYPE_BEFORE_GROUP_TEXT_OUT: 'before_group_text_output',
 
     SIGNATURE_NAME_DUMMY: 'dummy_pic',
+    SIGNATURE_NAME_DUMMY_WATER_MARK: 'dummy_pic_warter_mark',
 
     PAGE_STATUS: ['EveryPage', 'FirstPage', 'LastPage', 'SegmentStart', 'SegmentEnd', 'Group', 'CrossRowEnd', 'CrossColEnd'],
 

+ 6 - 1
app/reports/util/rpt_calculation_data_util.js

@@ -945,8 +945,13 @@ function ext_getSplitProperty(dataKey, propKey, splitChar, index, dftValue) {
             const splitArr = rst[idx].split(splitChar);
             if (splitArr.length > index) {
                 rst[idx] = splitArr[index];
+                if (rst[idx].trim() === '' && index === 1) {
+                    rst[idx] = splitArr[0]; // 把业务带进来了 !_!
+                }
             } else {
-                rst[idx] = dftValue;
+                if (index === 1) {
+                    rst[idx] = splitArr[0]; // 把业务带进来了 !_!
+                } else rst[idx] = dftValue;
             }
         }
     }

+ 160 - 127
app/reports/util/rpt_excel_util.js

@@ -10,8 +10,7 @@ const jpcCmnHelper = require('../rpt_component/helper/jpc_helper_common');
 const DPI = jpcCmnHelper.getScreenDPI()[0];
 const fsUtil = require('../public/fsUtil');
 const dftHeadXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
-const uuidV1 = require('uuid/v1');
-const atob = require('atob');
+const uuidV1 = require('uuid').v1;
 
 function writeContentTypes(sheets, isSinglePage, hasSignature) {
     const rst = [];
@@ -335,21 +334,32 @@ function _setupPgBrks(pageData) {
     pageData.pageBreaks = [];
     let pgBrkIdx = 0;
     const pgBrk = [];
-    function _insertMaxBottom(pCells, targetBottomArr) {
+    function _insertMaxBottom(pCells, targetBottomArr, chkTop) {
         if (pCells) {
             for (const cell of pCells) {
-                if (targetBottomArr.indexOf(cell[JV.PROP_AREA][JV.PROP_BOTTOM]) < 0) {
-                    targetBottomArr.push(cell[JV.PROP_AREA][JV.PROP_BOTTOM]);
+                let cb = parseFloat(cell[JV.PROP_AREA][JV.PROP_BOTTOM]);
+                if (targetBottomArr.indexOf(cb) < 0) {
+                    targetBottomArr.push(cb);
+                }
+                if (chkTop) {
+                    cb = parseFloat(cell[JV.PROP_AREA][JV.PROP_TOP]);
+                    if (targetBottomArr.indexOf(cb) < 0) {
+                        targetBottomArr.push(cb);
+                    }
                 }
             }
         }
     }
-    for (const page of pageData.items) {
+    // for (const page of pageData.items) {
+    // for (let i = 0; i < pageData.items.length - 1; i++) {
+    for (let i = 0; i < pageData.items.length; i++) {
+        const page = pageData.items[i];
         const maxBottomArr = [];
-        _insertMaxBottom(page.cells, maxBottomArr);
-        _insertMaxBottom(page.signature_cells, maxBottomArr);
-        _insertMaxBottom(page.signature_date_cells, maxBottomArr);
-        _insertMaxBottom(page.signature_audit_cells, maxBottomArr);
+        _insertMaxBottom(page[JV.PROP_CELLS], maxBottomArr);
+        _insertMaxBottom(page[JV.PROP_SIGNATURE_CELLS], maxBottomArr);
+        _insertMaxBottom(page[JV.PROP_SIGNATURE_DATE_CELLS], maxBottomArr);
+        _insertMaxBottom(page[JV.PROP_SIGNATURE_AUDIT_CELLS], maxBottomArr);
+        _insertMaxBottom(page[JV.PROP_WATERMARK_CELLS], maxBottomArr, true);
         pgBrkIdx += maxBottomArr.length;
         pgBrk.push(pgBrkIdx);
     }
@@ -813,9 +823,9 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj, ap
         let cnt = 0;
         rst.push('<mergeCells count="?">');
         const startIdx = rst.length - 1;
-        const self_setMergedCells = function(theData, theYPos, offsetY) {
-            for (let i = 0; i < theData.cells.length; i++) {
-                const cell = theData.cells[i];
+        const self_setMergedCells = function(cells, theYPos, offsetY) {
+            for (let i = 0; i < cells.length; i++) {
+                const cell = cells[i];
                 const idxR = xPos.indexOf(cell[JV.PROP_AREA][JV.PROP_RIGHT]);
                 const idxL = xPos.indexOf(cell[JV.PROP_AREA][JV.PROP_LEFT]);
                 const idxB = theYPos.indexOf(cell[JV.PROP_AREA][JV.PROP_BOTTOM]);
@@ -827,13 +837,19 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj, ap
             }
         };
         if (sheetData) {
-            self_setMergedCells(sheetData, yPos, 0);
+            // self_setMergedCells(sheetData, yPos, 0);
+            self_setMergedCells(sheetData[JV.PROP_CELLS], yPos, 0);
+            if (sheetData[JV.PROP_SIGNATURE_DATE_CELLS]) self_setMergedCells(sheetData[JV.PROP_SIGNATURE_DATE_CELLS], yPos, 0);
+            if (sheetData[JV.PROP_SIGNATURE_AUDIT_CELLS]) self_setMergedCells(sheetData[JV.PROP_SIGNATURE_AUDIT_CELLS], yPos, 0);
         } else {
             let osY = 0;
             for (let i = 0; i < pageData.items.length; i++) {
                 const shtItemData = pageData.items[i];
                 const tmpPos = yMultiPos[i];
-                self_setMergedCells(shtItemData, tmpPos, osY);
+                // self_setMergedCells(shtItemData, tmpPos, osY);
+                self_setMergedCells(shtItemData[JV.PROP_CELLS], tmpPos, osY);
+                if (shtItemData[JV.PROP_SIGNATURE_DATE_CELLS]) self_setMergedCells(shtItemData[JV.PROP_SIGNATURE_DATE_CELLS], tmpPos, osY);
+                if (shtItemData[JV.PROP_SIGNATURE_AUDIT_CELLS]) self_setMergedCells(shtItemData[JV.PROP_SIGNATURE_AUDIT_CELLS], tmpPos, osY);
                 osY += tmpPos.length - 2;
             }
         }
@@ -846,6 +862,37 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj, ap
         const spanX = xPos.length - 2;
         let cellIdx = 0;
         let h = 0;
+        const self_create_cell = function(cells, rows, theYPos) {
+            let rowIdx1 = 0;
+            let colIdx1 = 0;
+            let rowIdx2 = 0;
+            let colIdx2 = 0;
+            for (let cIdx = 0; cIdx < cells.length; cIdx++) {
+                const styleIdx = private_getStyleId(cells[cIdx]);
+                rowIdx1 = theYPos.indexOf(cells[cIdx][JV.PROP_AREA][JV.PROP_TOP]);
+                colIdx1 = xPos.indexOf(cells[cIdx][JV.PROP_AREA][JV.PROP_LEFT]);
+                let cellObj = rows[rowIdx1 - 1].items[colIdx1 - 1];
+                cellObj.s = styleIdx;
+                cellObj.isBlank = false;
+                if (!(strUtil.isEmptyString(cells[cIdx][JV.PROP_VALUE]))) {
+                    const valIdx = private_getSharedStrIdx(cells[cIdx][JV.PROP_VALUE]);
+                    cellObj.v = valIdx;
+                }
+
+                rowIdx2 = theYPos.indexOf(cells[cIdx][JV.PROP_AREA][JV.PROP_BOTTOM]);
+                colIdx2 = xPos.indexOf(cells[cIdx][JV.PROP_AREA][JV.PROP_RIGHT]);
+                if ((rowIdx2 - rowIdx1 > 1) || (colIdx2 - colIdx1 > 1)) {
+                    for (let i = 0; i < rowIdx2 - rowIdx1; i++) {
+                        for (let j = 0; j < colIdx2 - colIdx1; j++) {
+                            if (i === 0 && j === 0) continue;
+                            cellObj = rows[rowIdx1 - 1 + i].items[colIdx1 - 1 + j];
+                            cellObj.s = styleIdx;
+                            cellObj.isBlank = true;
+                        }
+                    }
+                }
+            }
+        };
         const self_setDataEx = function(theShtData, theYPos, rowOffset) {
             const rows = [];
             // 1. build full set of blank rows/cells
@@ -867,36 +914,13 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj, ap
                 }
             }
             // 2. then fill up the cell style-ids and values
-            let rowIdx1 = 0;
-            let colIdx1 = 0;
-            let rowIdx2 = 0;
-            let colIdx2 = 0;
-            // let colIdxStr = '';
-            for (let cIdx = 0; cIdx < theShtData.cells.length; cIdx++) {
-                const styleIdx = private_getStyleId(theShtData.cells[cIdx]);
-                rowIdx1 = theYPos.indexOf(theShtData.cells[cIdx][JV.PROP_AREA][JV.PROP_TOP]);
-                colIdx1 = xPos.indexOf(theShtData.cells[cIdx][JV.PROP_AREA][JV.PROP_LEFT]);
-                let cellObj = rows[rowIdx1 - 1].items[colIdx1 - 1];
-                cellObj.s = styleIdx;
-                cellObj.isBlank = false;
-                if (!(strUtil.isEmptyString(theShtData.cells[cIdx][JV.PROP_VALUE]))) {
-                    const valIdx = private_getSharedStrIdx(theShtData.cells[cIdx][JV.PROP_VALUE]);
-                    cellObj.v = valIdx;
-                }
+            self_create_cell(theShtData[JV.PROP_CELLS], rows, theYPos);
+            self_create_cell(theShtData[JV.PROP_SIGNATURE_DATE_CELLS], rows, theYPos);
+            self_create_cell(theShtData[JV.PROP_SIGNATURE_AUDIT_CELLS], rows, theYPos);
+            // fsUtil.writeObjToFile(newPageData, 'D:/GitHome/ConstructionOperation/tmp/combinedHeader.js');
+            // console.log('theShtData[JV.PROP_SIGNATURE_AUDIT_CELLS]: ');
+            // console.log(theShtData[JV.PROP_SIGNATURE_AUDIT_CELLS]);
 
-                rowIdx2 = theYPos.indexOf(theShtData.cells[cIdx][JV.PROP_AREA][JV.PROP_BOTTOM]);
-                colIdx2 = xPos.indexOf(theShtData.cells[cIdx][JV.PROP_AREA][JV.PROP_RIGHT]);
-                if ((rowIdx2 - rowIdx1 > 1) || (colIdx2 - colIdx1 > 1)) {
-                    for (let i = 0; i < rowIdx2 - rowIdx1; i++) {
-                        for (let j = 0; j < colIdx2 - colIdx1; j++) {
-                            if (i === 0 && j === 0) continue;
-                            cellObj = rows[rowIdx1 - 1 + i].items[colIdx1 - 1 + j];
-                            cellObj.s = styleIdx;
-                            cellObj.isBlank = true;
-                        }
-                    }
-                }
-            }
             // 3. then fill up rst
             for (let i = 0; i < rows.length; i++) {
                 rst.push('<row r="' + (i + 1 + rowOffset) + '" spans="1:' + spanX + '" ht="' + rows[i].height + '" customHeight="1">');
@@ -919,6 +943,11 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj, ap
             // current sheet data
             currentPageMergePos = sheetData[JV.PAGE_SPECIAL_MERGE_POS];
             currentMergeBorder = sheetData[JV.PROP_PAGE_MERGE_BORDER];
+            if (hasSignature && sheetData[JV.PROP_SIGNATURE_CELLS] && sheetData[JV.PROP_SIGNATURE_CELLS].length > 0) {
+                // 有签名情况下,还是有必要创建一个dummy cell的(画框用)
+                _createDummyCell(sheetData[JV.PROP_SIGNATURE_CELLS], sheetData);
+            }
+
             self_setDataEx(sheetData, yPos, 0);
         } else {
             // total data in one sheet
@@ -929,6 +958,10 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj, ap
                 currentMergeBorder = shtItemData[JV.PROP_PAGE_MERGE_BORDER];
                 const tmpPos = yMultiPos[i];
                 cellIdx = 0;
+                if (hasSignature && shtItemData[JV.PROP_SIGNATURE_CELLS] && shtItemData[JV.PROP_SIGNATURE_CELLS].length > 0) {
+                    // 有签名情况下,还是有必要创建一个dummy cell的(画框用)
+                    _createDummyCell(shtItemData[JV.PROP_SIGNATURE_CELLS], shtItemData);
+                }
                 self_setDataEx(shtItemData, tmpPos, rowOffset);
                 rowOffset += tmpPos.length - 2;
             }
@@ -970,16 +1003,6 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj, ap
     }
     rst.push('<pageSetup ' + pStr + ' fitToWidth="0" fitToHeight="0" orientation="' + orientationStr + '" />');
     rst.push('<headerFooter alignWithMargins="0"/>');
-    if (hasSignature && signSheetIdxArr[sheetIdx]) {
-        // let rIdx = 1;
-        // for (let ssIdx = 0; ssIdx < signSheetIdxArr.length; ssIdx++) {
-        //     if (signSheetIdxArr[ssIdx]) {
-        //         if (ssIdx < sheetIdx) rIdx++
-        //         else break;
-        //     }
-        // }
-        rst.push('<drawing r:id="rId1"/>');
-    }
     // 插入分页符---------------------------
     if (pageData.pageBreaks) {
         let pgBrks = [];
@@ -997,6 +1020,17 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj, ap
         }
     }
     // 插入分页符结束---------------------------
+    // 图形要放在最后,否则excel会报错(但WPS不会)
+    if (hasSignature && signSheetIdxArr[sheetIdx]) {
+        // let rIdx = 1;
+        // for (let ssIdx = 0; ssIdx < signSheetIdxArr.length; ssIdx++) {
+        //     if (signSheetIdxArr[ssIdx]) {
+        //         if (ssIdx < sheetIdx) rIdx++
+        //         else break;
+        //     }
+        // }
+        rst.push('<drawing r:id="rId1"/>');
+    }
     rst.push('</worksheet>');
     return rst;
 }
@@ -1027,10 +1061,14 @@ function writeDrawings(pageData, signKeyArr, signPathArr, isSinglePage, signShee
     const rst = [];
     // console.log('isSinglePage: ' + isSinglePage);
     if (isSinglePage) {
+        // console.log('singlePage! ');
         rst.push(writeDrawing(pageData, null, signKeyArr[0]));
     } else {
+        // console.log('pageData! ');
         for (let i = 0; i < pageData.items.length; i++) {
             if (signSheetIdxArr[i]) {
+                // console.log('signKeyArr[' + i + ']');
+                // console.log(signKeyArr[i]);
                 rst.push(writeDrawing(pageData, pageData.items[i], signKeyArr[i]));
             }
         }
@@ -1133,7 +1171,10 @@ function writeDrawing(pageData, sheetData, subSignKeyArr) {
     preAnalyzePos(pageData, sheetData, xPos, yPos, yMultiPos);
     rst.push('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>');
     rst.push('<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">');
+    // console.log('subSignKeyArr');
+    // console.log(subSignKeyArr);
     if (sheetData) {
+        // console.log('sheetData');
         let startPicIdx = 2;
         for (const sCell of sheetData[JV.PROP_SIGNATURE_CELLS]) {
             if (subSignKeyArr.indexOf(sCell.signature_name) >= 0) {
@@ -1148,6 +1189,8 @@ function writeDrawing(pageData, sheetData, subSignKeyArr) {
             }
         }
     } else {
+        // console.log('pageData.items[0][JV.PROP_SIGNATURE_CELLS]');
+        // console.log(pageData.items[1][JV.PROP_SIGNATURE_CELLS]);
         // total data in one sheet
         let rowOffset = 0;
         for (let i = 0; i < pageData.items.length; i++) {
@@ -1158,6 +1201,11 @@ function writeDrawing(pageData, sheetData, subSignKeyArr) {
                 if (subSignKeyArr.indexOf(sCell.signature_name) >= 0) {
                     private_setSheetDrawingCellData(sCell, tmpPos, startPicIdx, rowOffset);
                     startPicIdx++;
+                // } else {
+                //     console.log('index < 0 subSignKeyArr: ');
+                //     console.log(subSignKeyArr);
+                //     console.log('sCell.signature_name');
+                //     console.log(sCell.signature_name);
                 }
             }
             for (const sCell of shtItemData[JV.PROP_WATERMARK_CELLS]) {
@@ -1241,21 +1289,6 @@ function createDummySignatureCell(pageData) {
         const page = pageData.items[pageIdx];
         if (page[JV.PROP_SIGNATURE_CELLS] && page[JV.PROP_SIGNATURE_CELLS].length > 0) {
             _createDummyCell(page[JV.PROP_SIGNATURE_CELLS], page);
-            // for (const signature of page[JV.PROP_SIGNATURE_CELLS]) {
-            //     const dummyCell = {
-            //         font: 'Content',
-            //         control: 'Default',
-            //         style: 'Default_None',
-            //         Value: '',
-            //         area: { Left: 0, Right: 0, Top: 0, Bottom: 0 }
-            //     };
-            //     if (signature.style) dummyCell.style = signature.style;
-            //     dummyCell[JV.PROP_AREA][JV.PROP_LEFT] = signature[JV.PROP_AREA][JV.PROP_LEFT];
-            //     dummyCell[JV.PROP_AREA][JV.PROP_RIGHT] = signature[JV.PROP_AREA][JV.PROP_RIGHT];
-            //     dummyCell[JV.PROP_AREA][JV.PROP_TOP] = signature[JV.PROP_AREA][JV.PROP_TOP];
-            //     dummyCell[JV.PROP_AREA][JV.PROP_BOTTOM] = signature[JV.PROP_AREA][JV.PROP_BOTTOM];
-            //     page[JV.PROP_CELLS].push(dummyCell);
-            // }
         }
         if (page[JV.PROP_SIGNATURE_DATE_CELLS] && page[JV.PROP_SIGNATURE_DATE_CELLS].length > 0) {
             _createDummyCell(page[JV.PROP_SIGNATURE_DATE_CELLS], page);
@@ -1266,7 +1299,7 @@ function createDummySignatureCell(pageData) {
     }
 }
 
-function _checkAndSetSignatureCache(pageData, signKeyArr, signPathArr, roleRel, signSheetIdxArr) {
+function _checkAndSetSignatureCache(pageData, signKeyArr, signPathArr, roleRel, signSheetIdxArr, isSinglePage) {
     // 备注:电子签名是以图形的方式处理,一页可以有多个签名,多页的签名基本是引用同样的图片,在这里先处理一下,后期统一引用。
     //      另:以后的图片(在电子签名(signature_cells)以外的图片)会单独处理(如计算草图、水印等)
     let rst = false;
@@ -1281,8 +1314,16 @@ function _checkAndSetSignatureCache(pageData, signKeyArr, signPathArr, roleRel,
     // for (const page of pageData.items) {
     for (let pageIdx = 0; pageIdx < pageData.items.length; pageIdx++) {
         const page = pageData.items[pageIdx];
-        signKeyArr.push([]);
-        signPathArr.push([]);
+        if (isSinglePage) {
+            if (pageIdx === 0) {
+                signKeyArr.push([]);
+                signPathArr.push([]);
+            }
+        } else {
+            signKeyArr.push([]);
+            signPathArr.push([]);
+        }
+        const curSignIdx = signKeyArr.length - 1;
         signSheetIdxArr[pageIdx] = false;
         // console.log('page index: ' + pageIdx);
         // console.log(page[JV.PROP_SIGNATURE_CELLS]);
@@ -1296,12 +1337,12 @@ function _checkAndSetSignatureCache(pageData, signKeyArr, signPathArr, roleRel,
                 //     rst = true;
                 //     signSheetIdxArr[pageIdx] = true;
                 // }
-                if (signKeyArr[pageIdx].indexOf(signature.signature_name) < 0) {
+                if (signKeyArr[curSignIdx].indexOf(signature.signature_name) < 0) {
                     if (signature.pic) {
                         const signPath = { path: null, pic: null };
-                        signPathArr[pageIdx].push(signPath);
+                        signPathArr[curSignIdx].push(signPath);
                         signPath.pic = signature.pic; // 历史报表
-                        signKeyArr[pageIdx].push(signature.signature_name);
+                        signKeyArr[curSignIdx].push(signature.signature_name);
                         rst = true;
                         signSheetIdxArr[pageIdx] = true;
                     } else {
@@ -1310,16 +1351,16 @@ function _checkAndSetSignatureCache(pageData, signKeyArr, signPathArr, roleRel,
                                 // console.log('signature.signature_name: ' + signature.signature_name);
                                 if (role.sign_pic) {
                                     const signPath = { path: null, pic: null };
-                                    signPathArr[pageIdx].push(signPath);
+                                    signPathArr[curSignIdx].push(signPath);
                                     signPath.pic = role.sign_pic;
-                                    signKeyArr[pageIdx].push(signature.signature_name);
+                                    signKeyArr[curSignIdx].push(signature.signature_name);
                                     rst = true;
                                     signSheetIdxArr[pageIdx] = true;
                                 } else if (role.sign_path) {
                                     const signPath = { path: null, pic: null };
-                                    signPathArr[pageIdx].push(signPath);
+                                    signPathArr[curSignIdx].push(signPath);
                                     signPath.path = role.sign_path;
-                                    signKeyArr[pageIdx].push(signature.signature_name);
+                                    signKeyArr[curSignIdx].push(signature.signature_name);
                                     rst = true;
                                     signSheetIdxArr[pageIdx] = true;
                                 }
@@ -1333,15 +1374,17 @@ function _checkAndSetSignatureCache(pageData, signKeyArr, signPathArr, roleRel,
         if (page[JV.PROP_WATERMARK_CELLS] && page[JV.PROP_WATERMARK_CELLS].length > 0) {
             // 水印,单独处理
             const waterMarkCell = page[JV.PROP_WATERMARK_CELLS][0];
-            // console.log('waterMarkCell');
-            // console.log(waterMarkCell);
-            const signPath = { path: null, pic: null };
-            signPathArr[pageIdx].push(signPath);
-            signPath.pic = waterMarkCell.pic;
-            signPath.path = waterMarkCell.path;
-            signKeyArr[pageIdx].push(waterMarkCell.signature_name);
-            rst = true;
-            signSheetIdxArr[pageIdx] = true;
+            if (signKeyArr[curSignIdx].indexOf(waterMarkCell.signature_name) < 0) {
+                // console.log('waterMarkCell');
+                // console.log(waterMarkCell);
+                const signPath = { path: null, pic: null };
+                signPathArr[curSignIdx].push(signPath);
+                signPath.pic = waterMarkCell.pic;
+                signPath.path = waterMarkCell.path;
+                signKeyArr[curSignIdx].push(waterMarkCell.signature_name);
+                rst = true;
+                signSheetIdxArr[pageIdx] = true;
+            }
         }
     }
     return rst;
@@ -1375,8 +1418,8 @@ module.exports = {
         resetDummuySignature(pageData, thisRoleRel); // 把草图转换一下roleRel
         // console.log(thisRoleRel);
 
-        const hasSignature = false; // 暂时不支持电子签名、草图导出excel
-        // const hasSignature = _checkAndSetSignatureCache(pageData, signKeyArr, signPathArr, thisRoleRel, signSheetIdxArr); // 因草图的关系,thisRoleRel是否为null就不是充要的条件
+        // const hasSignature = false; // 暂时不支持电子签名、草图导出excel
+        const hasSignature = _checkAndSetSignatureCache(pageData, signKeyArr, signPathArr, thisRoleRel, signSheetIdxArr, isSinglePage); // 因草图的关系,thisRoleRel是否为null就不是充要的条件
         // console.log('signKeyArr');
         // console.log(signKeyArr);
         // console.log('signPathArr');
@@ -1456,6 +1499,7 @@ module.exports = {
             // 5.2
             const zip_drawings = zip_xl.folder('drawings');
             data = writeDrawings(pageData, signKeyArr, signPathArr, isSinglePage, signSheetIdxArr);
+            // data = writeDrawings(pageData, signKeyArr, signPathArr, false, signSheetIdxArr); // 备注:这里根本不需要考虑是否single page
             // console.log('isSinglePage: ' + isSinglePage);
             // console.log(data);
             for (let psIdx = 0; psIdx < data.length; psIdx++) {
@@ -1586,9 +1630,25 @@ module.exports = {
                 // 加分页符(分页符要在外部处理好)-------------------
                 _setupPgBrks(pageDataArray[i]);
                 // 加分页符结束-------------------
+                let maxY = 0;
+                let minY = 100000;
+                const checkAndResetCellYPos = function(cells) {
+                    if (cells && cells.length > 0) {
+                        for (const cell of cells) {
+                            if (maxY < cell[JV.PROP_AREA][JV.PROP_BOTTOM]) {
+                                maxY = cell[JV.PROP_AREA][JV.PROP_BOTTOM];
+                            }
+                            if (minY > cell[JV.PROP_AREA][JV.PROP_TOP]) {
+                                minY = cell[JV.PROP_AREA][JV.PROP_TOP];
+                            }
+                            cell[JV.PROP_AREA][JV.PROP_BOTTOM] += offsetY;
+                            cell[JV.PROP_AREA][JV.PROP_TOP] += offsetY;
+                        }
+                    }
+                };
                 for (let j = 0; j < pageDataArray[i].items.length; j++) {
-                    let maxY = 0;
-                    let minY = 100000;
+                    maxY = 0;
+                    minY = 100000;
                     if (pageDataArray[i].items[j][JV.PAGE_SPECIAL_MERGE_POS]) {
                         let pos = pageDataArray[i].items[j][JV.PAGE_SPECIAL_MERGE_POS][JV.PROP_TOP][0] + offsetY;
                         mergeBand[JV.PROP_TOP].push(pos);
@@ -1604,43 +1664,16 @@ module.exports = {
                         mergeBand[JV.PROP_BOTTOM].push(pageDataArray[i][JV.BAND_PROP_MERGE_BAND][JV.PROP_BOTTOM] + offsetY);
                     }
                     // 1.2.1 重新设置普通cells的Top Bottom坐标
-                    for (const cell of pageDataArray[i].items[j].cells) {
-                    // for (let k = 0; k < pageDataArray[i].items[j].cells.length; k++) {
-                        if (maxY < cell[JV.PROP_AREA][JV.PROP_BOTTOM]) {
-                            maxY = cell[JV.PROP_AREA][JV.PROP_BOTTOM];
-                        }
-                        if (minY > cell[JV.PROP_AREA][JV.PROP_TOP]) {
-                            minY = cell[JV.PROP_AREA][JV.PROP_TOP];
-                        }
-                        cell[JV.PROP_AREA][JV.PROP_BOTTOM] += offsetY;
-                        cell[JV.PROP_AREA][JV.PROP_TOP] += offsetY;
-                    }
+                    checkAndResetCellYPos(pageDataArray[i].items[j][JV.PROP_CELLS]);
                     // 1.2.2 重新设置电子签名cells的Top Bottom坐标
-                    for (const cell of pageDataArray[i].items[j].signature_cells) {
-                        if (maxY < cell[JV.PROP_AREA][JV.PROP_BOTTOM]) {
-                            maxY = cell[JV.PROP_AREA][JV.PROP_BOTTOM];
-                        }
-                        if (minY > cell[JV.PROP_AREA][JV.PROP_TOP]) {
-                            minY = cell[JV.PROP_AREA][JV.PROP_TOP];
-                        }
-                        // console.log('before');
-                        // console.log(cell);
-                        cell[JV.PROP_AREA][JV.PROP_BOTTOM] += offsetY;
-                        cell[JV.PROP_AREA][JV.PROP_TOP] += offsetY;
-                        // console.log('after');
-                        // console.log(cell);
-                    }
+                    checkAndResetCellYPos(pageDataArray[i].items[j][JV.PROP_SIGNATURE_CELLS]);
                     // 1.2.3 重新设置电子签名日期cells的Top Bottom坐标
-                    for (const cell of pageDataArray[i].items[j].signature_date_cells) {
-                        if (maxY < cell[JV.PROP_AREA][JV.PROP_BOTTOM]) {
-                            maxY = cell[JV.PROP_AREA][JV.PROP_BOTTOM];
-                        }
-                        if (minY > cell[JV.PROP_AREA][JV.PROP_TOP]) {
-                            minY = cell[JV.PROP_AREA][JV.PROP_TOP];
-                        }
-                        cell[JV.PROP_AREA][JV.PROP_BOTTOM] += offsetY;
-                        cell[JV.PROP_AREA][JV.PROP_TOP] += offsetY;
-                    }
+                    checkAndResetCellYPos(pageDataArray[i].items[j][JV.PROP_SIGNATURE_DATE_CELLS]);
+                    // 1.2.4 重置电子签名审核意见cells的Top Bottom坐标
+                    checkAndResetCellYPos(pageDataArray[i].items[j][JV.PROP_SIGNATURE_AUDIT_CELLS]);
+                    // 1.2.5 重置水印的Top Bottom坐标
+                    checkAndResetCellYPos(pageDataArray[i].items[j][JV.PROP_WATERMARK_CELLS]);
+
                     const bottomGap = Math.round((pageDataArray[i][JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE][1] - parseFloat(pageDataArray[i][JV.NODE_PAGE_INFO][JV.NODE_MARGINS][JV.PROP_BOTTOM]) / 2.54) * DPI) - maxY;
                     offsetY += (maxY - minY);
                     if (bottomGap > 10) {

+ 1 - 1
app/reports/util/rpt_pdf_util.js

@@ -11,7 +11,7 @@ const fs = require('fs');
 const jpcCmnHelper = require('../rpt_component/helper/jpc_helper_common');
 const DPI = jpcCmnHelper.getScreenDPI()[0] * PDF_SCALE;
 const JV = require('../rpt_component/jpc_value_define');
-const uuidV1 = require('uuid/v1');
+const uuidV1 = require('uuid').v1;
 
 let fontUtil = require('./rpt_font_util');
 

+ 1 - 1
app/reports/util/rpt_tmp_file_sweep.js

@@ -21,7 +21,7 @@ function chkIsRemoveType(file) {
 
 const jobObj = {
     started: false,
-    createJob: function(rule, rootPath) {
+    createJob(rule, rootPath) {
         let localRule = rule;
         if (!localRule) {
             // setup schedule rule

+ 23 - 10
app/router.js

@@ -9,6 +9,9 @@ module.exports = app => {
     const projectManagerCheck = app.middlewares.projectManagerCheck();
     // 标段读取中间件
     const tenderCheck = app.middlewares.tenderCheck();
+    const ledgerAuditCheck = app.middlewares.ledgerAuditCheck();
+    const reviseAuditCheck = app.middlewares.reviseAuditCheck();
+    const changeAuditCheck = app.middlewares.changeAuditCheck();
     const uncheckTenderCheck = app.middlewares.uncheckTenderCheck();
     // 期读取中间件
     const stageCheck = app.middlewares.stageCheck();
@@ -107,6 +110,9 @@ 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/shenpi', sessionAuth, tenderCheck, 'tenderController.shenpiSet');
+    app.post('/tender/:id/shenpi/save', sessionAuth, tenderCheck, 'tenderController.saveTenderInfoShenpi');
+    app.post('/tender/:id/shenpi/audit/save', sessionAuth, tenderCheck, 'tenderController.saveShenpiAudit');
 
     // 预付款
     app.get('/tender/:id/advance', sessionAuth, tenderCheck, 'advanceController.index');
@@ -128,7 +134,7 @@ module.exports = app => {
     app.get('/tender/:id/cooperation', sessionAuth, tenderCheck, 'tenderController.tenderCooperation');
 
     // 台账管理相关
-    app.get('/tender/:id/ledger', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.explode');
+    app.get('/tender/:id/ledger', sessionAuth, tenderCheck, uncheckTenderCheck, ledgerAuditCheck, 'ledgerController.explode');
     app.post('/tender/:id/ledger/load', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.loadExplodeData');
     app.post('/tender/:id/ledger/get-children', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.getChildren');
     app.post('/tender/:id/ledger/update', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.update');
@@ -143,7 +149,7 @@ module.exports = app => {
     app.get('/tender/:id/ledger/audit', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerAuditController.index');
     app.post('/tender/:id/ledger/audit/add', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerAuditController.add');
     app.post('/tender/:id/ledger/audit/delete', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerAuditController.remove');
-    app.post('/tender/:id/ledger/audit/start', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerAuditController.start');
+    app.post('/tender/:id/ledger/audit/start', sessionAuth, tenderCheck, uncheckTenderCheck, ledgerAuditCheck, 'ledgerAuditController.start');
     app.post('/tender/:id/ledger/audit/check', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerAuditController.check');
 
     // 部位台账
@@ -162,13 +168,17 @@ module.exports = app => {
     // app.post('/tender/:id/revise/deal2sgfh', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.deal2sgfh');
 
     // 台账修订页面
-    app.get('/tender/:id/revise/info', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.info');
+    app.get('/tender/:id/revise/info', sessionAuth, tenderCheck, uncheckTenderCheck, reviseAuditCheck, 'reviseController.info');
     app.post('/tender/:id/revise/auditors', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.reviseAuditors');
     app.post('/tender/:id/revise/info/load', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.loadInfoData');
     app.post('/tender/:id/revise/info/update', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.update');
     app.post('/tender/:id/revise/info/upload-excel/:ueType', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.uploadExcel');
     app.post('/tender/:id/revise/info/check', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.checkData');
 
+    app.get('/tender/:id/revise/compare', sessionAuth, tenderCheck, uncheckTenderCheck, reviseAuditCheck, 'reviseController.compare');
+    app.get('/tender/:id/revise/gcl-compare', sessionAuth, tenderCheck, uncheckTenderCheck, reviseAuditCheck, 'reviseController.gclCompare');
+    app.post('/tender/:id/revise/load', sessionAuth, tenderCheck, uncheckTenderCheck, reviseAuditCheck, 'reviseController.loadData');
+
     // 查看修订数据
     app.get('/tender/:id/revise/history', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.history');
     app.post('/tender/:id/revise/history/load', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.loadHistoryData');
@@ -177,7 +187,7 @@ module.exports = app => {
     // 修订审批
     app.post('/tender/:id/revise/audit/add', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.addAuditor');
     app.post('/tender/:id/revise/audit/remove', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.removeAuditor');
-    app.post('/tender/:id/revise/audit/start', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.start');
+    app.post('/tender/:id/revise/audit/start', sessionAuth, tenderCheck, uncheckTenderCheck, reviseAuditCheck, 'reviseController.start');
     app.post('/tender/:id/revise/audit/check', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.check');
 
     // 签约清单
@@ -217,6 +227,7 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/delete/file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.deleteFile');
     app.post('/tender/:id/measure/stage/:order/save/file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.saveFile');
     app.post('/tender/:id/measure/stage/:order/check/file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.checkFile');
+    app.get('/tender/:id/measure/stage/:order/download/compresse-file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.downloadZip');
 
     // 中间计量
     app.get('/tender/:id/measure/stage/:order/detail', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.detail');
@@ -299,21 +310,22 @@ module.exports = app => {
     app.post('/tender/:id/change/newCode', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.newCode');
     app.post('/tender/:id/change/add', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.add');
     app.post('/tender/:id/change/defaultBills', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.defaultBills');
-    app.get('/tender/:id/change/:cid/info', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.info');
-    app.post('/change/upload/file', sessionAuth, 'changeController.uploadFile');
+    app.get('/tender/:id/change/:cid/info', sessionAuth, tenderCheck, uncheckTenderCheck, changeAuditCheck, 'changeController.info');
+    app.post('/tender/:id/change/:cid/info/file/upload', sessionAuth, 'changeController.uploadFile');
     app.get('/change/download/file/:id', sessionAuth, 'changeController.downloadFile');
     app.post('/change/download/file/:id', sessionAuth, 'changeController.checkFile');
-    app.post('/change/delete/file', sessionAuth, 'changeController.deleteFile');
+    app.post('/tender/:id/change/:cid/info/file/delete', sessionAuth, 'changeController.deleteFile');
+    app.get('/tender/:id/change/:cid/download/compresse-file', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.downloadZip');
     app.post('/tender/:id/change/delete', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.delete');
     app.post('/tender/:id/change/bills', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.bills');
 
-    app.post('/tender/:id/change/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.save');
+    app.post('/tender/:id/change/save', sessionAuth, tenderCheck, uncheckTenderCheck, changeAuditCheck, 'changeController.save');
 
     app.post('/tender/:id/change/approval', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.approval');
     app.post('/tender/:id/change/check/again', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.checkAgain');
 
     app.post('/tender/:id/change/:cid/check/codeRepeat', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.checkCodeRepeat');
-
+    app.post('/tender/:id/change/:cid/info/copy', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.copyChange');
     // 变更单位管理
     app.post('/change/update/company', sessionAuth, 'changeController.updateCompany');
 
@@ -344,7 +356,8 @@ module.exports = app => {
     app.post('/tender/:id/measure/material/:order/file/upload', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.upload');
     app.get('/tender/:id/measure/material/:order/file/:fid/download', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.downloadFile');
     app.post('/tender/:id/measure/material/:order/file/find', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.getCurMatericalFiles');
-    app.post('/tender/measure/material/file/delete', sessionAuth, 'materialController.deleteFile');
+    app.post('/tender/:id/measure/material/:order/file/delete', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.deleteFile');
+    app.get('/tender/:id/measure/material/:order/file/download/compresse-file', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.downloadZip');
 
     // 个人账号相关
     app.get('/profile/info', sessionAuth, 'profileController.info');

+ 80 - 15
app/service/advance_audit.js

@@ -1,6 +1,7 @@
 'use strict';
 
 const auditConst = require('../const/audit').advance;
+const shenpiConst = require('../const/shenpi');
 const pushType = require('../const/audit').pushType;
 module.exports = app => {
     class AdvanceAudit extends app.BaseService {
@@ -69,18 +70,29 @@ module.exports = app => {
          * @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;
+        async addAuditor(tid, vid, audit_id, times = 1, is_gdzs = 0) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                let newOrder = await this.getNewOrder(vid, times);
+                // 判断是否存在固定终审,存在则newOrder - 1并使终审order+1
+                newOrder = is_gdzs === 1 ? newOrder - 1 : newOrder;
+                if (is_gdzs) await this._syncOrderByDelete(transaction, vid, newOrder, times, '+');
+                const record = {
+                    tid,
+                    vid,
+                    audit_id,
+                    times,
+                    order: newOrder,
+                    status: auditConst.status.uncheck,
+                };
+                const result = await transaction.insert(this.tableName, record);
+                await transaction.commit();
+                return result && result.affectedRows === 1;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return false;
         }
 
         /**
@@ -117,7 +129,7 @@ module.exports = app => {
          * @return {Promise<*>} 查询结果集
          * @private
          */
-        async _syncOrderByDelete(transaction, vid, order, times) {
+        async _syncOrderByDelete(transaction, vid, order, times, selfOperate = '-') {
             this.initSqlBuilder();
             this.sqlBuilder.setAndWhere('vid', {
                 value: this.db.escape(vid),
@@ -133,7 +145,7 @@ module.exports = app => {
             });
             this.sqlBuilder.setUpdateData('order', {
                 value: 1,
-                selfOperate: '-',
+                selfOperate: selfOperate,
             });
             const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
             const data = await transaction.query(sql, sqlParam);
@@ -204,7 +216,11 @@ module.exports = app => {
         async start(vid, times = 1, data) {
             const audit = await this.getDataByCondition({ vid, times, order: 1 });
             if (!audit) {
-                throw '请先选择审批人,再上报数据';
+                if(this.ctx.tender.info.shenpi.advance === shenpiConst.sp_status.gdspl) {
+                    throw '请联系管理员添加审批人';
+                } else {
+                    throw '请先选择审批人,再上报数据';
+                }
             }
             const transaction = await this.db.beginTransaction();
             try {
@@ -494,6 +510,55 @@ module.exports = app => {
             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);
         }
+
+        async updateNewAuditList(advance, newIdList) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先删除旧的审批流,再添加新的
+                await transaction.delete(this.tableName, { vid: advance.id, times: advance.times });
+                const newAuditors = [];
+                let order = 1;
+                for (const aid of newIdList) {
+                    newAuditors.push({
+                        tid: advance.tid, vid: advance.id, audit_id: aid,
+                        times: advance.times, order, status: auditConst.status.uncheck,
+                    });
+                    order++;
+                }
+                if(newAuditors.length > 0) await transaction.insert(this.tableName, newAuditors);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async updateLastAudit(advance, auditList, lastId) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先判断auditList里的aid是否与lastId相同,相同则删除并重新更新order
+                const idList = this._.map(auditList, 'audit_id');
+                let order = idList.length + 1;
+                if (idList.indexOf(lastId) !== -1) {
+                    await transaction.delete(this.tableName, { vid: advance.id, times: advance.times, audit_id: lastId });
+                    const audit = this._.find(auditList, { 'audit_id': lastId });
+                    // 顺移之后审核人流程顺序
+                    await this._syncOrderByDelete(transaction, advance.id, audit.order, advance.times);
+                    order = order - 1;
+                }
+
+                // 添加终审
+                const newAuditor = {
+                    tid: advance.tid, vid: advance.id, audit_id: lastId,
+                    times: advance.times, order, status: auditConst.status.uncheck,
+                };
+                await transaction.insert(this.tableName, newAuditor);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
     }
     return AdvanceAudit;
 };

+ 47 - 5
app/service/change.js

@@ -11,6 +11,7 @@
 const audit = require('../const/audit');
 const fs = require('fs');
 const path = require('path');
+const changeConst = require('../const/change');
 const smsTypeConst = require('../const/sms_type');
 const SMS = require('../lib/sms');
 const SmsAliConst = require('../const/sms_alitemplate');
@@ -956,13 +957,18 @@ module.exports = app => {
          * @return {Promise<*>} - 可用的变更令列表
          */
         async getValidChanges(tid, bills, pos) {
+            const self = this;
+            const getFilterPart = function(field, value) {
+                return value
+                    ? field + ' = ' + self.db.escape(value)
+                    : self.db.format("(?? = null or ?? = '')", [field, field]);
+            };
             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) +
+            const filter = getFilterPart('cb.code', bills.b_code) +
+                ' And ' + getFilterPart('cb.name', bills.name) +
+                ' And ' + getFilterPart('cb.unit', bills.unit) +
                 ' And cb.`unit_price` = ' + this.db.escape(bills.unit_price) +
-                (pos ? ' And cb.`bwmx` = ' + this.db.escape(pos.name) : '');
+                (pos ? getFilterPart('cb.bwmx', 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, ' +
@@ -1303,6 +1309,42 @@ module.exports = app => {
             const content = await this.db.query(noticeSql, noticeSqlParam);
             return content.length ? JSON.stringify(content[0]) : '';
         }
+
+        /**
+         * 获取当前标段其他变更令
+         * @param {Number} tid - 标段id
+         * @param {Number} cid - 当前变更令
+         */
+        async getOthersChange(tid, cid) {
+            const sql = 'SELECT * FROM ?? WHERE tid = ? AND cid != ? ORDER By `in_time` desc ';
+            const sqlParam = [this.tableName, tid, cid];
+            const data = await this.db.query(sql, sqlParam);
+            const changeClassObj = {};
+            for (const c in changeConst.class) {
+                if (changeConst.class.hasOwnProperty(c)) {
+                    const item = changeConst.class[c];
+                    changeClassObj[item.value] = item.name;
+                }
+            }
+            for (let i = 0; i < data.length; i++) {
+                data[i].class = changeClassObj[data[i].class];
+            }
+            return data;
+        }
+
+        /**
+         * 拷贝变更令至当前变更令
+         * @param {String} cid - 当前变更令
+         * @param {String} copy_cid - 要拷贝的变更令
+         */
+        async handleCopyChange(cid, copy_cid) {
+            // const change = await this.getDataByCondition({ cid });
+            const copyChange = await this.getDataByCondition({ cid: copy_cid });
+            return await this.update({ peg: copyChange.peg, org_name: copyChange.org_name, org_code: copyChange.org_code, new_name: copyChange.new_name, content: copyChange.content, basis: copyChange.basis, expr: copyChange.expr, memo: copyChange.memo, type: copyChange.type,
+                class: copyChange.class, quality: copyChange.quality, company: copyChange.company, charge: copyChange.charge, new_code: copyChange.new_code }, {
+                cid,
+            });
+        }
     }
 
     return Change;

+ 136 - 76
app/service/change_att.js

@@ -1,76 +1,136 @@
-'use strict';
-
-/**
- *
- *  附件
- * @author Ellisran
- * @date 2019/1/11
- * @version
- */
-
-module.exports = app => {
-    class ChangeAtt extends app.BaseService {
-        /**
-         * 构造函数
-         *
-         * @param {Object} ctx - egg全局变量
-         * @return {void}
-         */
-        constructor(ctx) {
-            super(ctx);
-            this.tableName = 'change_attachment';
-        }
-
-        /**
-         * 添加附件
-         * @param {Object} postData - 表单信息
-         * @param {Object} fileData - 文件信息
-         * @param {int} uid - 上传者id
-         * @return {void}
-         */
-        async save(postData, fileData, uid) {
-            const data = {
-                tid: postData.tid,
-                cid: postData.cid,
-                uid,
-            };
-            Object.assign(data, fileData);
-            const result = await this.db.insert(this.tableName, data);
-            return result;
-        }
-
-        /**
-         * 获取 变更令 所有附件
-         * @param {uuid} cid - 变更令id
-         * @return {Promise<void>}
-         */
-        async getChangeAttachment(cid) {
-            const sql = '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 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.replace(/^app|\/app/, '');
-                }
-                return item;
-            });
-        }
-    }
-
-    return ChangeAtt;
-};
+'use strict';
+
+/**
+ *
+ *  附件
+ * @author Ellisran
+ * @date 2019/1/11
+ * @version
+ */
+
+const archiver = require('archiver');
+const path = require('path');
+const fs = require('fs');
+module.exports = app => {
+    class ChangeAtt extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'change_attachment';
+        }
+
+        /**
+         * 添加附件
+         * @param {Object} postData - 表单信息
+         * @param {Object} fileData - 文件信息
+         * @param {int} uid - 上传者id
+         * @return {void}
+         */
+        async save(postData, fileData, uid) {
+            const data = {
+                tid: postData.tid,
+                cid: postData.cid,
+                uid,
+            };
+            Object.assign(data, fileData);
+            const result = await this.db.insert(this.tableName, data);
+            return result;
+        }
+
+        /**
+         * 获取 变更令 所有附件
+         * @param {uuid} cid - 变更令id
+         * @return {Promise<void>}
+         */
+        async getChangeAttachment(cid) {
+            const sql = '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 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.replace(/^app|\/app/, '');
+                }
+                return item;
+            });
+        }
+
+        /**
+         * 将文件压缩成zip,并返回zip文件的路径
+         * @param {array} fileIds - 文件数组id
+         * @param {string} zipPath - 压缩文件存储路径
+         * @return {string} 压缩后的zip文件路径
+         */
+        async compressedFile(fileIds, zipPath) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('id', {
+                value: fileIds,
+                operate: 'in',
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const files = await this.db.query(sql, sqlParam);
+            return new Promise((resolve, reject) => {
+                // 每次开一个新的archiver
+                const ziparchiver = archiver('zip');
+                const outputPath = fs.createWriteStream(path.resolve(this.app.baseDir, zipPath));
+                outputPath.on('error', err => {
+                    reject(err);
+                });
+
+                ziparchiver.pipe(outputPath);
+                files.forEach(item => {
+                    ziparchiver.file(path.resolve(this.app.baseDir, item.filepath), { name: item.filename + item.fileext });
+                });
+
+                // 存档警告
+                ziparchiver.on('warning', function(err) {
+                    if (err.code === 'ENOENT') {
+                        console.warn('stat故障和其他非阻塞错误');
+                    }
+                    reject(err);
+                });
+
+                // 存档出错
+                ziparchiver.on('error', function(err) {
+                    console.log(err);
+                    reject(err);
+                });
+                ziparchiver.finalize();
+                outputPath.on('close', () => {
+                    resolve(ziparchiver.pointer());
+                });
+            });
+        }
+
+        /**
+         * 返回所查询的变更令的名称
+         * @param {string} cid - 变更令id
+         */
+        async getChangeName(cid) {
+            const sql = 'SELECT name FROM ?? WHERE cid = ?';
+            const sqlParam = [this.ctx.service.change.tableName, cid];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+    }
+
+    return ChangeAtt;
+};

+ 100 - 0
app/service/change_audit.js

@@ -355,6 +355,106 @@ module.exports = app => {
             }
             return await this.db.query(sql, sqlParam);
         }
+
+        async updateNewAuditList(change, newIdList) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先删除旧的审批流(除了原报),再添加新的
+                const sql = 'DELETE FROM ?? WHERE `cid`= ? AND `times` = ? AND `usite` != 0';
+                const sqlParam = [this.tableName, change.cid, change.times];
+                await transaction.query(sql, sqlParam);
+
+                const newAuditors = [];
+                let order = 1;
+                let uSort = await transaction.count(this.tableName, { cid: change.cid });
+                for (const aid of newIdList) {
+                    const accountInfo = await this.ctx.service.projectAccount.getDataById(aid);
+                    newAuditors.push({
+                        tid: change.tid, cid: change.cid, uid: aid,
+                        name: accountInfo.name, jobs: accountInfo.role, company: accountInfo.company,
+                        times: change.times, usite: order, usort: uSort, status: audit.flow.auditStatus.uncheck,
+                    });
+                    order++;
+                    uSort++;
+                }
+                await transaction.insert(this.tableName, newAuditors);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async updateLastAudit(change, auditList, lastId) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先判断auditList里的aid是否与lastId相同,相同则删除并重新更新order
+                const idList = this._.map(auditList, 'uid');
+                let order = idList.length + 1;
+                if (idList.indexOf(lastId) !== -1) {
+                    const sql = 'DELETE FROM ?? WHERE `cid`= ? AND `times` = ? AND `uid` = ? AND `usite` != 0';
+                    const sqlParam = [this.tableName, change.cid, change.times, lastId];
+                    await transaction.query(sql, sqlParam);
+                    // await transaction.delete(this.tableName, { cid: change.cid, times: change.times, uid: lastId, usite: 0 });
+                    const user = this._.find(auditList, { 'uid': lastId });
+                    // 顺移之后审核人流程顺序
+                    await this._syncOrderByDelete(transaction, change.cid, user.usite, user.usort, change.times);
+                    order = order - 1;
+                }
+
+                // 添加终审
+                const userInfo = await this.ctx.service.projectAccount.getDataById(lastId);
+                let uSort = await transaction.count(this.tableName, { cid: change.cid });
+                const newAuditor = {
+                    tid: change.tid, cid: change.cid, uid: lastId,
+                    name: userInfo.name, jobs: userInfo.role, company: userInfo.company,
+                    times: change.times, usite: order, usort: uSort, status: audit.flow.auditStatus.uncheck,
+                };
+                await transaction.insert(this.tableName, newAuditor);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+        /**
+         * 移除审核人时,同步其后审核人order
+         * @param transaction - 事务
+         * @param {Number} changeId - 变更令id
+         * @param {Number} usite - 审核人id
+         * @param {Number} usort - 审核人id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<*>}
+         * @private
+         */
+        async _syncOrderByDelete(transaction, changeId, usite, usort, times, selfOperate = '-') {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('cid', {
+                value: this.db.escape(changeId),
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere('usite', {
+                value: usite,
+                operate: '>=',
+            });
+            this.sqlBuilder.setAndWhere('times', {
+                value: times,
+                operate: '=',
+            });
+            this.sqlBuilder.setUpdateData('usite', {
+                value: 1,
+                selfOperate: selfOperate,
+            });
+            this.sqlBuilder.setUpdateData('usort', {
+                value: 1,
+                selfOperate: selfOperate,
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+            const data = await transaction.query(sql, sqlParam);
+
+            return data;
+        }
+
     }
 
     return ChangeAudit;

+ 1 - 1
app/service/ledger.js

@@ -51,7 +51,7 @@ module.exports = app => {
                 fullPath: 'full_path',
                 keyPre: 'ledger_bills_maxLid:',
                 uuid: true,
-            });
+            }, 'pos');
             this.tableName = 'ledger';
         }
 

+ 79 - 14
app/service/ledger_audit.js

@@ -13,6 +13,7 @@ const smsTypeConst = require('../const/sms_type');
 const SMS = require('../lib/sms');
 const SmsAliConst = require('../const/sms_alitemplate');
 const wxConst = require('../const/wechat_template');
+const shenpiConst = require('../const/shenpi');
 const pushType = require('../const/audit').pushType;
 
 module.exports = app => {
@@ -148,17 +149,28 @@ module.exports = app => {
          * @param {Number} times - 第几次审批
          * @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,
-                audit_order: newOrder,
-                status: auditConst.status.uncheck,
-            };
-            const result = await this.db.insert(this.tableName, data);
-            return (result.effectRows = 1);
+        async addAuditor(tenderId, auditorId, times = 1, is_gdzs = 0) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                let newOrder = await this.getNewOrder(tenderId, times);
+                // 判断是否存在固定终审,存在则newOrder - 1并使终审order+1
+                newOrder = is_gdzs === 1 ? newOrder - 1 : newOrder;
+                if (is_gdzs) await this._syncOrderByDelete(transaction, tenderId, newOrder, times, '+');
+                const data = {
+                    tender_id: tenderId,
+                    audit_id: auditorId,
+                    times,
+                    audit_order: newOrder,
+                    status: auditConst.status.uncheck,
+                };
+                const result = await transaction.insert(this.tableName, data);
+                await transaction.commit();
+                return (result.effectRows = 1);
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return false;
         }
 
         /**
@@ -170,7 +182,7 @@ module.exports = app => {
          * @return {Promise<*>}
          * @private
          */
-        async _syncOrderByDelete(transaction, tenderId, order, times) {
+        async _syncOrderByDelete(transaction, tenderId, order, times, selfOperate = '-') {
             this.initSqlBuilder();
             this.sqlBuilder.setAndWhere('tender_id', {
                 value: tenderId,
@@ -186,7 +198,7 @@ module.exports = app => {
             });
             this.sqlBuilder.setUpdateData('audit_order', {
                 value: 1,
-                selfOperate: '-',
+                selfOperate: selfOperate,
             });
             const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
             const data = await transaction.query(sql, sqlParam);
@@ -230,7 +242,11 @@ module.exports = app => {
         async start(tenderId, times = 1) {
             const audit = await this.getDataByCondition({ tender_id: tenderId, times, audit_order: 1 });
             if (!audit) {
-                throw '审核人信息错误';
+                if(this.ctx.tender.info.shenpi.ledger === shenpiConst.sp_status.gdspl) {
+                    throw '请联系管理员添加审批人';
+                } else {
+                    throw '请先选择审批人,再上报数据';
+                }
             }
             const sum = await this.ctx.service.ledger.addUp({ tender_id: tenderId /* , is_leaf: true*/ });
 
@@ -511,6 +527,55 @@ module.exports = app => {
             result.unshift(user);
             return result;
         }
+
+        async updateNewAuditList(tender, newIdList) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先删除旧的审批流,再添加新的
+                await transaction.delete(this.tableName, { tender_id: tender.id, times: tender.ledger_times });
+                const newAuditors = [];
+                let order = 1;
+                for (const aid of newIdList) {
+                    newAuditors.push({
+                        tender_id: tender.id, audit_id: aid,
+                        times: tender.ledger_times, audit_order: order, status: auditConst.status.uncheck,
+                    });
+                    order++;
+                }
+                if(newAuditors.length > 0) await transaction.insert(this.tableName, newAuditors);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async updateLastAudit(tender, auditList, lastId) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先判断auditList里的aid是否与lastId相同,相同则删除并重新更新order
+                const idList = this._.map(auditList, 'audit_id');
+                let order = idList.length + 1;
+                if (idList.indexOf(lastId) !== -1) {
+                    await transaction.delete(this.tableName, { tender_id: tender.id, times: tender.ledger_times, audit_id: lastId });
+                    const audit = this._.find(auditList, { 'audit_id': lastId });
+                    // 顺移之后审核人流程顺序
+                    await this._syncOrderByDelete(transaction, tender.id, audit.audit_order, tender.ledger_times);
+                    order = order - 1;
+                }
+
+                // 添加终审
+                const newAuditor = {
+                    tender_id: tender.id, audit_id: lastId,
+                    times: tender.ledger_times, audit_order: order, status: auditConst.status.uncheck,
+                };
+                await transaction.insert(this.tableName, newAuditor);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
     }
 
     return LedgerAudit;

+ 16 - 0
app/service/ledger_revise.js

@@ -42,6 +42,22 @@ module.exports = app => {
             return await this.db.query(sql, sqlParam);
         }
 
+        /**
+         * 获取全部修订
+         * @param tid
+         * @returns {Promise<*>}
+         */
+        async getAllReviseList (tid) {
+            const sql = 'SELECT lc.id, lc.tid, lc.corder, lc.in_time, lc.uid, lc.begin_time, lc.end_time, lc.times, lc.status, lc.valid,' +
+                '    pa.name As user_name, pa.role As user_role, pa.company As user_company' +
+                '  FROM ' + this.tableName + ' As lc' +
+                '  INNER JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa ON lc.uid = pa.id' +
+                '  WHERE lc.tid = ?' +
+                '  ORDER BY lc.in_time DESC';
+            const sqlParam = [tid];
+            return await this.db.query(sql, sqlParam);
+        }
+
         async getLastestRevise(tid, valid = true) {
             const sql = 'SELECT lc.*,' +
                 '    pa.name As user_name, pa.role As user_role, pa.company As user_company' +

+ 127 - 137
app/service/login_logging.js

@@ -1,137 +1,127 @@
-'use strict';
-
-/**
- * 登录日志-数据模型
- *
- * @author lanjianrong
- * @date 2020/8/31
- * @version
- */
-const UAParser = require('ua-parser-js');
-
-module.exports = app => {
-    class LoginLogging extends app.BaseService {
-        constructor(ctx) {
-            super(ctx);
-            this.tableName = 'login_logging';
-        }
-
-        /**
-         * 创建记录
-         * @param {Object} payload - 载荷
-         */
-        async createLog(payload) {
-            const transaction = await this.db.beginTransaction();
-            try {
-                transaction.insert(this.tableName, payload);
-                await transaction.commit();
-            } catch (error) {
-                await transaction.rollback();
-                throw error;
-            }
-        }
-
-        /**
-         * 创建登录日志
-         * @return {Boolean} 日志是否创建成功
-         * @param {Number} type - 登录类型
-         * @param {Number} status - 是否显示记录
-         */
-        async addLoginLog(type, status) {
-            const { ctx } = this;
-            const ip = ctx.request.ip ? ctx.request.ip : '';
-            const ipInfo = await this.getIpInfoFromApi(ip);
-            const parser = new UAParser(ctx.header['user-agent']);
-            const osInfo = parser.getOS();
-            const cpuInfo = parser.getCPU();
-            const browserInfo = parser.getBrowser();
-            const payload = {
-                os: `${osInfo.name} ${osInfo.version} ${cpuInfo.architecture}`,
-                browser: `${browserInfo.name} ${browserInfo.version}`,
-                ip,
-                address: ipInfo,
-                uid: ctx.session.sessionUser.accountId,
-                pid: ctx.session.sessionProject.id,
-                type,
-                show: status,
-            };
-            return await this.createLog(payload);
-        }
-
-        /**
-         * 根据ip请求获取详细地址
-         * @param {String} a_ip - ip地址
-         * @return {String} 详细地址
-         */
-        async getIpInfoFromApi(a_ip = '') {
-            console.log('a_ip', a_ip);
-            if (!a_ip) return '';
-            if (a_ip === '127.0.0.1' || a_ip === '::1' || a_ip.indexOf('192.168') !== -1) return '服务器本机访问';
-            const { ip = '', region = '', city = '', isp = '' } = await this.sendRequest(a_ip);
-            let address = '';
-            region && (address += region + '省');
-            city && (address += city + '市 ');
-            isp && (address += isp + ' ');
-            ip && (address += `(${ip})`);
-            return address;
-        }
-
-        /**
-         * 发送请求获取详细地址
-         * @param {String} ip - ip地址
-         * @param {Number} max - 最大重试次数
-         * @return {Object} the result of request
-         * @private
-         */
-        async sendRequest(ip, max = 3) {
-            return new Promise(resolve => {
-                const start = () => {
-                    if (max <= 0) {
-                        resolve(); // 已达到最大重试次数,返回空的执行承若
-                    }
-                    max--;
-                    this.ctx.curl(`https://api01.aliyun.venuscn.com/ip?ip=${ip}`, {
-                        dateType: 'json',
-                        encoding: 'utf8',
-                        timeout: 2000,
-                        headers: {
-                            Authorization: 'APPCODE 85c64bffe70445c4af9df7ae31c7bfcc',
-                        },
-                    }).then(({ status, data }) => {
-                        if (status === 200) {
-                            const result = JSON.parse(data.toString()).data;
-                            if (!result.ip) {
-                                start();
-                            } else {
-                                max++;
-                                resolve(result);
-                            }
-                        } else {
-                            max--;
-                            start();
-                        }
-                    }).catch(() => {
-                        start();
-                    });
-                };
-                start();
-            });
-        }
-
-        /**
-         * 获取登录日志
-         * @param {Number} pid - 项目id
-         * @param {Number} uid - 用户id
-         * @return {Promise<Array>} 日志数组
-         */
-        async getLoginLogs(pid, uid) {
-            return this.db.select(this.tableName, {
-                where: { pid, uid, show: 0 },
-                orders: [['create_time', 'desc']],
-                columns: ['browser', 'create_time', 'ip', 'os', 'address'],
-                limit: 10, offset: 0,
-            });
-        }
-    }
-    return LoginLogging;
-};
+'use strict';
+
+/**
+ * 登录日志-数据模型
+ *
+ * @author lanjianrong
+ * @date 2020/8/31
+ * @version
+ */
+const UAParser = require('ua-parser-js');
+
+module.exports = app => {
+    class LoginLogging extends app.BaseService {
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'login_logging';
+        }
+
+        /**
+         * 创建记录
+         * @param {Object} payload - 载荷
+         */
+        async createLog(payload) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                transaction.insert(this.tableName, payload);
+                await transaction.commit();
+            } catch (error) {
+                await transaction.rollback();
+                throw error;
+            }
+        }
+
+        /**
+         * 创建登录日志
+         * @return {Boolean} 日志是否创建成功
+         * @param {Number} type - 登录类型
+         * @param {Number} status - 是否显示记录
+         */
+        async addLoginLog(type, status) {
+            const { ctx } = this;
+            const ip = ctx.request.ip ? ctx.request.ip : '';
+            const ipInfo = await this.getIpInfoFromApi(ip);
+            const parser = new UAParser(ctx.header['user-agent']);
+            const osInfo = parser.getOS();
+            const cpuInfo = parser.getCPU();
+            const browserInfo = parser.getBrowser();
+            const payload = {
+                os: `${osInfo.name} ${osInfo.version} ${cpuInfo.architecture}`,
+                browser: `${browserInfo.name} ${browserInfo.version}`,
+                ip,
+                address: ipInfo,
+                uid: ctx.session.sessionUser.accountId,
+                pid: ctx.session.sessionProject.id,
+                type,
+                show: status,
+            };
+            return await this.createLog(payload);
+        }
+
+        /**
+         * 根据ip请求获取详细地址
+         * @param {String} a_ip - ip地址
+         * @return {String} 详细地址
+         */
+        async getIpInfoFromApi(a_ip = '') {
+            if (!a_ip) return '';
+            if (a_ip === '127.0.0.1' || a_ip === '::1' || a_ip.indexOf('192.168') !== -1) return '服务器本机访问';
+            const { ip = '', region = '', city = '', isp = '' } = await this.sendRequest(a_ip);
+            let address = '';
+            region && (address += region + '省');
+            city && (address += city + '市 ');
+            isp && (address += isp + ' ');
+            ip && (address += `(${ip})`);
+            return address;
+
+        }
+
+        /**
+         * 发送请求获取详细地址
+         * @param {String} ip - ip地址
+         * @return {Object} the result of request
+         * @private
+         */
+        async sendRequest(ip) {
+            return new Promise(resolve => {
+                this.ctx.curl(`https://api01.aliyun.venuscn.com/ip?ip=${ip}`, {
+                    dateType: 'json',
+                    encoding: 'utf8',
+                    timeout: 2000,
+                    headers: {
+                        Authorization: 'APPCODE 85c64bffe70445c4af9df7ae31c7bfcc',
+                    },
+                }).then(({ status, data }) => {
+                    if (status === 200) {
+                        const result = JSON.parse(data.toString()).data;
+                        if (!result.ip) {
+                            resolve({});
+                        } else {
+                            resolve(result);
+                        }
+                    } else {
+                        resolve({});
+                    }
+                }).catch(() => {
+                    resolve({});
+                });
+            });
+        }
+
+        /**
+         * 获取登录日志
+         * @param {Number} pid - 项目id
+         * @param {Number} uid - 用户id
+         * @return {Promise<Array>} 日志数组
+         */
+        async getLoginLogs(pid, uid) {
+            return this.db.select(this.tableName, {
+                where: { pid, uid, show: 0 },
+                orders: [['create_time', 'desc']],
+                columns: ['browser', 'create_time', 'ip', 'os', 'address'],
+                limit: 10, offset: 0,
+            });
+        }
+    }
+    return LoginLogging;
+};

+ 85 - 19
app/service/material_audit.js

@@ -12,6 +12,7 @@ const auditConst = require('../const/audit').material;
 const pushType = require('../const/audit').pushType;
 const smsTypeConst = require('../const/sms_type');
 const wxConst = require('../const/wechat_template');
+const shenpiConst = require('../const/shenpi');
 
 module.exports = app => {
     class MaterialAudit extends app.BaseService {
@@ -103,18 +104,29 @@ module.exports = app => {
          * @param {Number} times - 第几次审批
          * @return {Promise<number>}
          */
-        async addAuditor(materialId, auditorId, times = 1) {
-            const newOrder = await this.getNewOrder(materialId, times);
-            const data = {
-                tid: this.ctx.tender.id,
-                mid: materialId,
-                aid: auditorId,
-                times,
-                order: newOrder,
-                status: auditConst.status.uncheck,
-            };
-            const result = await this.db.insert(this.tableName, data);
-            return result.effectRows = 1;
+        async addAuditor(materialId, auditorId, times = 1, is_gdzs = 0) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                let newOrder = await this.getNewOrder(materialId, times);
+                // 判断是否存在固定终审,存在则newOrder - 1并使终审order+1
+                newOrder = is_gdzs === 1 ? newOrder - 1 : newOrder;
+                if (is_gdzs) await this._syncOrderByDelete(transaction, materialId, newOrder, times, '+');
+                const data = {
+                    tid: this.ctx.tender.id,
+                    mid: materialId,
+                    aid: auditorId,
+                    times,
+                    order: newOrder,
+                    status: auditConst.status.uncheck,
+                };
+                const result = await transaction.insert(this.tableName, data);
+                await transaction.commit();
+                return result.effectRows = 1;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return false;
         }
 
         /**
@@ -126,7 +138,7 @@ module.exports = app => {
          * @return {Promise<*>}
          * @private
          */
-        async _syncOrderByDelete(transaction, materialId, order, times) {
+        async _syncOrderByDelete(transaction, materialId, order, times, selfOperate = '-') {
             this.initSqlBuilder();
             this.sqlBuilder.setAndWhere('mid', {
                 value: materialId,
@@ -142,7 +154,7 @@ module.exports = app => {
             });
             this.sqlBuilder.setUpdateData('order', {
                 value: 1,
-                selfOperate: '-',
+                selfOperate: selfOperate,
             });
             const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
             const data = await transaction.query(sql, sqlParam);
@@ -185,7 +197,12 @@ module.exports = app => {
         async start(materialId, times = 1) {
             const audit = await this.getDataByCondition({ mid: materialId, times, order: 1 });
             if (!audit) {
-                throw '请先选择审批人,再上报数据';
+                if(this.ctx.tender.info.shenpi.material === shenpiConst.sp_status.gdspl) {
+                    throw '请联系管理员添加审批人';
+                } else {
+                    throw '请先选择审批人,再上报数据';
+                }
+
             }
 
             const transaction = await this.db.beginTransaction();
@@ -196,8 +213,8 @@ module.exports = app => {
                 });
                 // 本期一些必要数据(如应耗数量和上期调差金额)插入到material_bills_history表里
                 const materialBillsData = await this.ctx.service.materialBills.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
-                if (materialBillsData.length === 0) {
-                    throw '调差工料不能为空';
+                if (materialBillsData.length === 0 && this.ctx.material.ex_expr === null) {
+                    throw '请至少使用一种调差方式';
                 }
                 const mbhList = [];
                 for (const mb of materialBillsData) {
@@ -223,7 +240,7 @@ module.exports = app => {
                     };
                     mbhList.push(newMbh);
                 }
-                await transaction.insert(this.ctx.service.materialBillsHistory.tableName, mbhList);
+                if(mbhList.length !== 0) await transaction.insert(this.ctx.service.materialBillsHistory.tableName, mbhList);
 
                 const materialExponentData = await this.ctx.service.materialExponent.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
                 const mehList = [];
@@ -243,7 +260,7 @@ module.exports = app => {
                     };
                     mehList.push(newMeh);
                 }
-                await transaction.insert(this.ctx.service.materialExponentHistory.tableName, mehList);
+                if(mehList.length !== 0) await transaction.insert(this.ctx.service.materialExponentHistory.tableName, mehList);
 
                 // 微信模板通知
                 const materialInfo = await this.ctx.service.material.getDataById(materialId);
@@ -846,6 +863,55 @@ module.exports = app => {
             const sqlParam = [tenderId];
             return this.db.query(sql, sqlParam);
         }
+
+        async updateNewAuditList(material, newIdList) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先删除旧的审批流,再添加新的
+                await transaction.delete(this.tableName, { mid: material.id, times: material.times });
+                const newAuditors = [];
+                let order = 1;
+                for (const aid of newIdList) {
+                    newAuditors.push({
+                        tid: material.tid, mid: material.id, aid,
+                        times: material.times, order, status: auditConst.status.uncheck,
+                    });
+                    order++;
+                }
+                if(newAuditors.length > 0) await transaction.insert(this.tableName, newAuditors);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async updateLastAudit(material, auditList, lastId) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先判断auditList里的aid是否与lastId相同,相同则删除并重新更新order
+                const idList = this._.map(auditList, 'aid');
+                let order = idList.length + 1;
+                if (idList.indexOf(lastId) !== -1) {
+                    await transaction.delete(this.tableName, { mid: material.id, times: material.times, aid: lastId });
+                    const audit = this._.find(auditList, { 'aid': lastId });
+                    // 顺移之后审核人流程顺序
+                    await this._syncOrderByDelete(transaction, material.id, audit.order, material.times);
+                    order = order - 1;
+                }
+
+                // 添加终审
+                const newAuditor = {
+                    tid: material.tid, mid: material.id, aid: lastId,
+                    times: material.times, order, status: auditConst.status.uncheck,
+                };
+                await transaction.insert(this.tableName, newAuditor);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
     }
 
     return MaterialAudit;

+ 2 - 1
app/service/material_exponent.js

@@ -212,7 +212,8 @@ module.exports = app => {
                 }
             }
             expr += '-1]';
-            const ex_tp = this.ctx.helper.round(this.ctx.helper.mul(basic_calc, this.ctx.helper.sub(this.ctx.helper.add(constant, sumByChange), 1)), 2);
+            expr = constant !== 0 ? expr : null;
+            const ex_tp = constant !== 0 ? this.ctx.helper.round(this.ctx.helper.mul(basic_calc, this.ctx.helper.sub(this.ctx.helper.add(constant, sumByChange), 1)), 2) : null;
             await transaction.update(this.ctx.service.material.tableName, {
                 id: mid ? mid : this.ctx.material.id,
                 ex_tp,

+ 127 - 75
app/service/material_file.js

@@ -1,75 +1,127 @@
-'use strict';
-const auditConst = require('../const/audit');
-/**
- * 附件表 数据模型
- * @author LanJianRong
- * @date 2020/6/30
- * @version
- */
-
-module.exports = app => {
-    class MaterialFile extends app.BaseService {
-        /**
-         * 构造函数
-         *
-         * @param {Object} ctx - egg全局变量
-         * @return {void}
-         */
-        constructor(ctx) {
-            super(ctx);
-            this.tableName = 'material_file';
-        }
-
-        /**
-         * 获取当前标段(期)所有上传的附件
-         * @param {Number} tid 标段id
-         * @param {Number?} mid 期id
-         * @return {Promise<void>} 数据库查询实例
-         */
-        async getAllMaterialFiles(tid, mid) {
-            const { ctx } = this;
-            const where = { tid };
-            if (mid) where.mid = mid;
-            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;
-            });
-        }
-
-
-        /**
-         * 存储上传的文件信息至数据库
-         * @param {Array} payload 载荷
-         * @return {Promise<void>} 数据库插入执行实例
-         */
-        async saveFileMsgToDb(payload) {
-            return await this.db.insert(this.tableName, payload);
-        }
-
-        /**
-         * 获取单个文件信息
-         * @param {Number} id 文件id
-         * @return {Promise<void>} 数据库查询实例
-         */
-        async getMaterialFileById(id) {
-            return await this.getDataByCondition({ id });
-        }
-
-        /**
-         * 删除附件
-         * @param {Number} id - 附件id
-         * @return {void}
-         */
-        async delete(id) {
-            return await this.deleteById(id);
-        }
-    }
-    return MaterialFile;
-};
-
+'use strict';
+const archiver = require('archiver');
+const path = require('path');
+const fs = require('fs');
+/**
+ * 附件表 数据模型
+ * @author LanJianRong
+ * @date 2020/6/30
+ * @version
+ */
+
+module.exports = app => {
+    class MaterialFile extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'material_file';
+        }
+
+        /**
+         * 获取当前标段(期)所有上传的附件
+         * @param {Number} tid 标段id
+         * @param {Number?} mid 期id
+         * @return {Promise<void>} 数据库查询实例
+         */
+        async getAllMaterialFiles(tid, mid) {
+            const { ctx } = this;
+            const where = { tid };
+            if (mid) where.mid = mid;
+            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;
+            });
+        }
+
+
+        /**
+         * 存储上传的文件信息至数据库
+         * @param {Array} payload 载荷
+         * @return {Promise<void>} 数据库插入执行实例
+         */
+        async saveFileMsgToDb(payload) {
+            return await this.db.insert(this.tableName, payload);
+        }
+
+        /**
+         * 获取单个文件信息
+         * @param {Number} id 文件id
+         * @return {Promise<void>} 数据库查询实例
+         */
+        async getMaterialFileById(id) {
+            return await this.getDataByCondition({ id });
+        }
+
+        /**
+         * 删除附件
+         * @param {Number} id - 附件id
+         * @return {void}
+         */
+        async delete(id) {
+            return await this.deleteById(id);
+        }
+
+        /**
+         * 将文件压缩成zip,并返回zip文件的路径
+         * @param {array} fileIds - 文件数组id
+         * @param {string} zipPath - 压缩文件存储路径
+         * @return {string} 压缩后的zip文件路径
+         */
+        async compressedFile(fileIds, zipPath) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('id', {
+                value: fileIds,
+                operate: 'in',
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const files = await this.db.query(sql, sqlParam);
+            // const paths = files.map(item => {
+            //     return { name: item.filename + item.fileext, path: item.filepath }
+            // })
+            return new Promise((resolve, reject) => {
+                // 每次开一个新的archiver
+                const ziparchiver = archiver('zip');
+                const outputPath = fs.createWriteStream(path.resolve(this.app.baseDir, zipPath));
+                outputPath.on('error', err => {
+                    reject(err);
+                });
+
+                ziparchiver.pipe(outputPath);
+                files.forEach(item => {
+                    ziparchiver.file(path.resolve(this.app.baseDir, 'app', item.filepath), { name: item.file_name });
+                });
+
+                // 存档警告
+                ziparchiver.on('warning', function(err) {
+                    if (err.code === 'ENOENT') {
+                        console.warn('stat故障和其他非阻塞错误');
+                    }
+                    reject(err);
+                });
+
+                // 存档出错
+                ziparchiver.on('error', function(err) {
+                    console.log(err);
+                    reject(err);
+                });
+                ziparchiver.finalize();
+                outputPath.on('close', () => {
+                    resolve(ziparchiver.pointer());
+                });
+            });
+        }
+    }
+    return MaterialFile;
+};
+

+ 4 - 4
app/service/pos.js

@@ -215,8 +215,8 @@ module.exports = app => {
 
                 const transaction = await this.db.beginTransaction();
                 try {
-                    transaction.update(this.tableName, data);
-                    transaction.update(this.ctx.service.ledger.tableName, updateBills);
+                    await transaction.update(this.tableName, data);
+                    await transaction.update(this.ctx.service.ledger.tableName, updateBills);
                     await transaction.commit();
                     updateBills.ledger_id = bills.ledger_id;
                     return {
@@ -288,7 +288,7 @@ module.exports = app => {
             const transaction = await this.db.beginTransaction();
             try {
                 for (const d of data) {
-                    transaction.update(this.tableName, d);
+                    await transaction.update(this.tableName, d);
                 }
                 if (needUpdateBills) await transaction.update(this.ctx.service.ledger.tableName, updateBills);
                 await transaction.commit();
@@ -417,7 +417,7 @@ module.exports = app => {
                         await transaction.update(this.tableName, d);
                     } else {
                         this._completeInsertPosData(tid, d);
-                        transaction.insert(this.tableName, d);
+                        await transaction.insert(this.tableName, d);
                     }
                 }
 

+ 88 - 19
app/service/revise_audit.js

@@ -12,6 +12,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 shenpiConst = require('../const/shenpi');
 const pushType = require('../const/audit').pushType;
 
 module.exports = app => {
@@ -129,20 +130,31 @@ module.exports = app => {
          * @param {Number} times - 第几次审批
          * @return {Promise<number>}
          */
-        async addAuditor(revise, auditorId) {
-            const times = revise.times ? revise.times : 1;
-            const newOrder = await this.getNewOrder(revise.id, times);
-            const data = {
-                tender_id: revise.tid,
-                audit_id: auditorId,
-                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);
+        async addAuditor(revise, auditorId, is_gdzs = 0) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const times = revise.times ? revise.times : 1;
+                let newOrder = await this.getNewOrder(revise.id, times);
+                // 判断是否存在固定终审,存在则newOrder - 1并使终审order+1
+                newOrder = is_gdzs === 1 ? newOrder - 1 : newOrder;
+                if (is_gdzs) await this._syncOrderByDelete(transaction, revise.id, newOrder, times, '+');
+                const data = {
+                    tender_id: revise.tid,
+                    audit_id: auditorId,
+                    times,
+                    audit_order: newOrder,
+                    status: auditConst.status.uncheck,
+                    rid: revise.id,
+                    in_time: new Date(),
+                };
+                const result = await transaction.insert(this.tableName, data);
+                await transaction.commit();
+                return (result.effectRows = 1);
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return false;
         }
 
         /**
@@ -154,7 +166,7 @@ module.exports = app => {
          * @return {Promise<*>}
          * @private
          */
-        async _syncOrderByDelete(transaction, reviseId, order, times) {
+        async _syncOrderByDelete(transaction, reviseId, order, times, selfOperate = '-') {
             this.initSqlBuilder();
             this.sqlBuilder.setAndWhere('rid', {
                 value: this.db.escape(reviseId),
@@ -170,7 +182,7 @@ module.exports = app => {
             });
             this.sqlBuilder.setUpdateData('audit_order', {
                 value: 1,
-                selfOperate: '-',
+                selfOperate: selfOperate,
             });
             const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
             const data = await transaction.query(sql, sqlParam);
@@ -214,7 +226,13 @@ module.exports = app => {
          */
         async start(revise, times = 1) {
             const audit = await this.getDataByCondition({ rid: revise.id, times, audit_order: 1 });
-            if (!audit) throw '审核人信息错误';
+            if (!audit) {
+                if(this.ctx.tender.info.shenpi.revise === shenpiConst.sp_status.gdspl) {
+                    throw '请联系管理员添加审批人';
+                } else {
+                    throw '请先选择审批人,再上报数据';
+                }
+            }
             const time = new Date();
 
             // 拷贝备份台账数据
@@ -243,7 +261,7 @@ module.exports = app => {
                 // 添加短信通知-需要审批提醒功能
                 // 下一人
                 // await this.ctx.helper.sendUserSms(audit.audit_id, smsTypeConst.const.XD,
-                //     smsTypeConst.judge.approval.toString(), '台修订需要您审批,请登录系统处理。');
+                //     smsTypeConst.judge.approval.toString(), '台修订需要您审批,请登录系统处理。');
                 await this.ctx.helper.sendAliSms(audit.audit_id, smsTypeConst.const.XD, smsTypeConst.judge.approval.toString(), SmsAliConst.template.revise_check);
                 // 微信模板通知
                 const wechatData = {
@@ -375,7 +393,7 @@ module.exports = app => {
                         // 短信通知-需要审批提醒功能
                         // 下一人
                         // await this.ctx.helper.sendUserSms(nextAudit.user_id, smsTypeConst.const.XD,
-                        //     smsTypeConst.judge.approval.toString(), '台修订需要您审批,请登录系统处理。');
+                        //     smsTypeConst.judge.approval.toString(), '台修订需要您审批,请登录系统处理。');
                         await this.ctx.helper.sendAliSms(nextAudit.audit_id, smsTypeConst.const.XD, smsTypeConst.judge.approval.toString(), SmsAliConst.template.revise_check);
                         // 微信模板通知
                         const wechatData = {
@@ -683,6 +701,57 @@ module.exports = app => {
             const sqlParam = [tenderId];
             return this.db.query(sql, sqlParam);
         }
+
+        async updateNewAuditList(revise, newIdList) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先删除旧的审批流,再添加新的
+                await transaction.delete(this.tableName, { rid: revise.id, times: revise.times });
+                const newAuditors = [];
+                let order = 1;
+                for (const aid of newIdList) {
+                    newAuditors.push({
+                        tender_id: revise.tid, audit_id: aid,
+                        times: revise.times, audit_order: order, status: auditConst.status.uncheck,
+                        rid: revise.id,in_time: new Date(),
+                    });
+                    order++;
+                }
+                if(newAuditors.length > 0) await transaction.insert(this.tableName, newAuditors);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async updateLastAudit(revise, auditList, lastId) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先判断auditList里的aid是否与lastId相同,相同则删除并重新更新order
+                const idList = this._.map(auditList, 'audit_id');
+                let order = idList.length + 1;
+                if (idList.indexOf(lastId) !== -1) {
+                    await transaction.delete(this.tableName, { rid: revise.id, times: revise.times, audit_id: lastId });
+                    const audit = this._.find(auditList, { 'audit_id': lastId });
+                    // 顺移之后审核人流程顺序
+                    await this._syncOrderByDelete(transaction, revise.id, audit.audit_order, revise.times);
+                    order = order - 1;
+                }
+
+                // 添加终审
+                const newAuditor = {
+                    tender_id: revise.tid, audit_id: lastId,
+                    times: revise.times, audit_order: order, status: auditConst.status.uncheck,
+                    rid: revise.id, in_time: new Date(),
+                };
+                await transaction.insert(this.tableName, newAuditor);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
     }
 
     return ReviseAudit;

+ 1 - 1
app/service/revise_bills.js

@@ -28,7 +28,7 @@ module.exports = app => {
                 fullPath: 'full_path',
                 keyPre: 'revise_bills_maxLid:',
                 uuid: true,
-            });
+            }, 'revisePos');
             this.tableName = 'revise_bills';
         }
 

+ 13 - 3
app/service/revise_pos.js

@@ -52,6 +52,16 @@ module.exports = app => {
             });
         }
 
+        async getPosDataByUnits(tenderId, units) {
+            const sql = 'SELECT p.id, p.lid, p.sgfh_qty, p.sjcl_qty, p.qtcl_qty' +
+                '  FROM ' + this.tableName + ' p' +
+                '  LEFT JOIN ' + this.ctx.service.reviseBills.tableName + ' b' +
+                '  ON p.lid = b.id ' +
+                '  WHERE p.tid = ? and b.unit IN (?)';
+            const sqlParam = [tenderId, units];
+            return await this.db.query(sql, sqlParam);
+        }
+
         async insertLedgerPosData(transaction, tid, rid, bills, data) {
             const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
             const insertDatas = [];
@@ -162,8 +172,8 @@ module.exports = app => {
 
                 const transaction = await this.db.beginTransaction();
                 try {
-                    transaction.update(this.tableName, data);
-                    transaction.update(this.ctx.service.reviseBills.tableName, updateBills);
+                    await transaction.update(this.tableName, data);
+                    await transaction.update(this.ctx.service.reviseBills.tableName, updateBills);
                     await transaction.commit();
                     updateBills.ledger_id = bills.ledger_id;
                     return {
@@ -347,7 +357,7 @@ module.exports = app => {
                     if (d.id) {
                         await transaction.update(this.tableName, d);
                     } else {
-                        this._insertPosData(transaction, d, tid, rid);
+                        await this._insertPosData(transaction, d, tid, rid);
                     }
                 }
                 const info = this.ctx.tender.info;

+ 164 - 0
app/service/shenpi_audit.js

@@ -0,0 +1,164 @@
+'use strict';
+
+/**
+ * 版本数据模型
+ *
+ * @author CaiAoLin
+ * @date 2017/10/25
+ * @version
+ */
+const shenpiConst = require('../const/shenpi');
+
+module.exports = app => {
+
+    class ShenpiAudit extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'shenpi_audit';
+        }
+
+        async getAudit(tid, type, status) {
+            const sql = 'SELECT sp.audit_id, pa.name FROM ?? AS sp LEFT JOIN ?? AS pa ON sp.audit_id = pa.id' +
+                ' WHERE sp.tid = ? AND sp.sp_type = ? AND sp.sp_status = ?';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tid, type, status];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        async getAuditList(tid, type, status) {
+            const sql = 'SELECT sp.audit_id, pa.name FROM ?? AS sp LEFT JOIN ?? AS pa ON sp.audit_id = pa.id' +
+                ' WHERE sp.tid = ? AND sp.sp_type = ? AND sp.sp_status = ? ORDER BY sp.id ASC';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tid, type, status];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        async addAudit(data) {
+            const insertData = {
+                tid: this.ctx.tender.id,
+                sp_type: data.code,
+                sp_status: data.status,
+                audit_id: data.audit_id,
+            };
+            const result = await this.db.insert(this.tableName, insertData);
+            return result.effectRows === 1;
+        }
+
+        async removeAudit(data) {
+            const delData = {
+                tid: this.ctx.tender.id,
+                sp_type: data.code,
+                sp_status: data.status,
+                audit_id: data.audit_id,
+            };
+            return await this.db.delete(this.tableName, delData);
+        }
+
+        async copyAudit2otherTender(data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const shenpi_status = parseInt(data.status);
+                // 1.复制修改当前审批到其他的tender_info里
+                // 2.删除其他的shenpiAudit
+                // 3.添加新的shenpiAudit(还要针对该标段是否为原报进行处理)
+                const tenderInfoUpdateList = [];
+                const tenders = [];
+                for (const tid of data.tidList.split(',')) {
+                    // 获取原报
+                    const tender = await this.ctx.service.tender.getDataById(tid);
+                    if (tender) {
+                        tenders.push({ id: parseInt(tid), user_id: tender.user_id });
+                        const shenpiInfo = await this.ctx.service.tenderInfo.getTenderShenpiInfo(tid);
+                        // 把当前期状态复制到其他标段里
+                        if (shenpiInfo[data.code] !== shenpi_status) {
+                            shenpiInfo[data.code] = shenpi_status;
+                            tenderInfoUpdateList.push({ row: { shenpi: JSON.stringify(shenpiInfo) }, where: { tid: parseInt(tid) } });
+                        }
+                    }
+                }
+                if (tenderInfoUpdateList.length > 0) await transaction.updateRows(this.ctx.service.tenderInfo.tableName, tenderInfoUpdateList);
+                const insertList = [];
+                const needYB = ['ledger', 'revise', 'change'];
+                const canYB = needYB.indexOf(data.code) !== -1;
+                for (const t of tenders) {
+                    if (shenpi_status !== shenpiConst.sp_status.sqspr) {
+                        await transaction.delete(this.tableName, { tid: t.id, sp_type: shenpiConst.sp_type[data.code], sp_status: shenpi_status });
+                        for (const aid of data.aidList.split(',')) {
+                            if (aid && parseInt(aid) !== t.user_id || (parseInt(aid) === t.user_id && canYB)) {
+                                const insertData = {
+                                    tid: t.id,
+                                    sp_type: shenpiConst.sp_type[data.code],
+                                    sp_status: shenpi_status,
+                                    audit_id: parseInt(aid),
+                                };
+                                insertList.push(insertData);
+                            }
+                        }
+                    }
+                }
+                // console.log(tenderInfoUpdateList, insertList);
+                if (insertList.length > 0) await transaction.insert(this.tableName, insertList);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async copyAudit2otherShenpi(data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const shenpi_status = parseInt(data.status);
+                // 1.修改当前审批到它的tender_info里
+                // 2.删除相同审批状态的shenpiAudit
+                // 3.添加新的shenpiAudit(还要针对该标段是否为原报进行处理)
+                const insertList = [];
+                const needYB = ['ledger', 'revise', 'change'];
+                const shenpiInfo = await this.ctx.service.tenderInfo.getTenderShenpiInfo(this.ctx.tender.id);
+                for (const code of data.shenpiList.split(',')) {
+                    // 把当前审批状态复制到其他标段里
+                    if (shenpiInfo[code] !== shenpi_status) {
+                        shenpiInfo[code] = shenpi_status;
+                    }
+                    if (shenpiInfo[code] !== shenpiConst.sp_status.sqspr) {
+                        await transaction.delete(this.tableName, { tid: this.ctx.tender.id, sp_type: shenpiConst.sp_type[code], sp_status: shenpiInfo[code] });
+                        for (const aid of data.aidList.split(',')) {
+                            if (aid && parseInt(aid) !== this.ctx.tender.data.user_id || (parseInt(aid) === this.ctx.tender.data.user_id && needYB.indexOf(code) !== -1)) {
+                                const insertData = {
+                                    tid: this.ctx.tender.id,
+                                    sp_type: shenpiConst.sp_type[code],
+                                    sp_status: shenpi_status,
+                                    audit_id: parseInt(aid),
+                                };
+                                insertList.push(insertData);
+                            }
+                        }
+                    }
+                }
+                await transaction.update(this.ctx.service.tenderInfo.tableName,
+                    {
+                        shenpi: JSON.stringify(shenpiInfo),
+                    },
+                    {
+                        where: {
+                            tid: this.ctx.tender.id,
+                        },
+                    });
+                if (insertList.length > 0) await transaction.insert(this.tableName, insertList);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+    }
+
+    return ShenpiAudit;
+};

+ 7 - 0
app/service/stage.js

@@ -591,6 +591,9 @@ module.exports = app => {
                         const sumGcl = await this.ctx.service.stageBills.getSumTotalPriceGclByMaterial(stage_list, '^[^0-9]*1[0-9]{2}-');
                         cb.value = this.ctx.helper.add(sumGcl.contract_tp, sumGcl.qc_tp);
                         break;
+                    case 'bqyf':
+                        cb.value = this.ctx.helper.roundNum(this._.sumBy(stage_list, 'yf_tp'), 2);
+                        break;
                     default:
                         cb.value = 0;
                 }
@@ -598,6 +601,10 @@ module.exports = app => {
             return calcBase;
         }
 
+        async getSumbqyfByMaterial(stage) {
+
+        }
+
         /**
          * 获取必要的stage信息调用curTimes, curOrder, id , times, curAuditor(材料调差)
          * @param stage_id_list

+ 148 - 95
app/service/stage_att.js

@@ -1,95 +1,148 @@
-'use strict';
-
-/**
- *
- *  附件
- * @author Ellisran
- * @date 2019/1/11
- * @version
- */
-
-module.exports = app => {
-    class StageAtt extends app.BaseService {
-        /**
-         * 构造函数
-         *
-         * @param {Object} ctx - egg全局变量
-         * @return {void}
-         */
-        constructor(ctx) {
-            super(ctx);
-            this.tableName = 'stage_attachment';
-        }
-
-        /**
-         * 添加附件
-         * @param {Object} postData - 表单信息
-         * @param {Object} fileData - 文件信息
-         * @param {int} uid - 上传者id
-         * @return {void}
-         */
-        async save(postData, fileData, uid) {
-            const data = {
-                lid: postData.lid,
-                uid,
-                remark: '',
-            };
-            Object.assign(data, fileData);
-            const result = await this.db.insert(this.tableName, data);
-            return result;
-        }
-
-        /**
-         * 添加附件
-         * @param {Object} postData - 表单信息
-         * @param {Object} fileData - 文件信息
-         * @param {int} uid - 上传者id
-         * @return {void}
-         */
-        async updateByID(postData, fileData) {
-            delete postData.size;
-            const data = {};
-            Object.assign(data, fileData);
-            Object.assign(data, postData);
-            const result = await this.db.update(this.tableName, data);
-            return result.affectedRows === 1;
-        }
-
-        /**
-         * 获取所有附件
-         * @param {int} tid - 标段id
-         * @param {int} sid - 当前期数
-         * @return {void}
-         */
-        async getDataByTenderIdAndStageId(tid, sid) {
-            const { ctx } = this;
-            const sql = 'SELECT att.id, att.lid, att.uid, 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);
-        }
-
-        /**
-         * 获取单个附件
-         * @param {int} tid - 标段id
-         * @param {int} sid - 当前期数
-         * @return {void}
-         */
-        async getDataByFid(id) {
-            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];
-            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.replace(/^app|\/app/, '');
-            return result;
-        }
-    }
-
-    return StageAtt;
-};
+'use strict';
+
+/**
+ *
+ *  附件
+ * @author Ellisran
+ * @date 2019/1/11
+ * @version
+ */
+
+const archiver = require('archiver');
+const path = require('path');
+const fs = require('fs');
+module.exports = app => {
+    class StageAtt extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'stage_attachment';
+        }
+
+        /**
+         * 添加附件
+         * @param {Object} postData - 表单信息
+         * @param {Object} fileData - 文件信息
+         * @param {int} uid - 上传者id
+         * @return {void}
+         */
+        async save(postData, fileData, uid) {
+            const data = {
+                lid: postData.lid,
+                uid,
+                remark: '',
+            };
+            Object.assign(data, fileData);
+            const result = await this.db.insert(this.tableName, data);
+            return result;
+        }
+
+        /**
+         * 添加附件
+         * @param {Object} postData - 表单信息
+         * @param {Object} fileData - 文件信息
+         * @param {int} uid - 上传者id
+         * @return {void}
+         */
+        async updateByID(postData, fileData) {
+            delete postData.size;
+            const data = {};
+            Object.assign(data, fileData);
+            Object.assign(data, postData);
+            const result = await this.db.update(this.tableName, data);
+            return result.affectedRows === 1;
+        }
+
+        /**
+         * 获取所有附件
+         * @param {int} tid - 标段id
+         * @param {int} sid - 当前期数
+         * @return {void}
+         */
+        async getDataByTenderIdAndStageId(tid, sid) {
+            const sql = 'SELECT att.id, att.lid, att.uid, att.filename, att.fileext, att.filesize, att.extra_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);
+        }
+
+        /**
+         * 获取单个附件
+         * @param {int} tid - 标段id
+         * @param {int} sid - 当前期数
+         * @return {void}
+         */
+        async getDataByFid(id) {
+            const { ctx } = this;
+            const sql = 'SELECT att.id, att.lid, att.uid, att.filepath, att.filename, att.extra_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];
+            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.replace(/^app|\/app/, '');
+            return result;
+        }
+
+        /**
+         * 将文件压缩成zip,并返回zip文件的路径
+         * @param {array} fileIds - 文件数组id
+         * @param {string} zipPath - 压缩文件存储路径
+         * @return {string} 压缩后的zip文件路径
+         */
+        async compressedFile(fileIds, zipPath) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('id', {
+                value: fileIds,
+                operate: 'in',
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const files = await this.db.query(sql, sqlParam);
+            // const paths = files.map(item => {
+            //     return { name: item.filename + item.fileext, path: item.filepath }
+            // })
+            return new Promise((resolve, reject) => {
+                // 每次开一个新的archiver
+                const ziparchiver = archiver('zip');
+                const outputPath = fs.createWriteStream(path.resolve(this.app.baseDir, zipPath));
+
+                outputPath.on('error', err => {
+                    reject(err);
+                });
+                ziparchiver.pipe(outputPath);
+                files.forEach(item => {
+                    ziparchiver.file(item.filepath, { name: item.filename + item.fileext });
+                });
+
+                // 存档警告
+                ziparchiver.on('warning', function(err) {
+                    if (err.code === 'ENOENT') {
+                        console.warn('stat故障和其他非阻塞错误');
+                    }
+                    reject(err);
+
+                });
+
+                // 存档出错
+                ziparchiver.on('error', function(err) {
+                    console.log(err);
+                    reject(err);
+                });
+
+                ziparchiver.finalize();
+                outputPath.on('close', () => {
+                    resolve(ziparchiver.pointer());
+                });
+            });
+        }
+    }
+    return StageAtt;
+};

+ 82 - 15
app/service/stage_audit.js

@@ -13,6 +13,7 @@ const smsTypeConst = require('../const/sms_type');
 const SMS = require('../lib/sms');
 const SmsAliConst = require('../const/sms_alitemplate');
 const wxConst = require('../const/wechat_template');
+const shenpiConst = require('../const/shenpi');
 const payConst = require('../const/deal_pay');
 const pushType = require('../const/audit').pushType;
 
@@ -163,18 +164,29 @@ module.exports = app => {
          * @param {Number} times - 第几次审批
          * @return {Promise<number>}
          */
-        async addAuditor(stageId, auditorId, times = 1) {
-            const newOrder = await this.getNewOrder(stageId, times);
-            const data = {
-                tid: this.ctx.tender.id,
-                sid: stageId,
-                aid: auditorId,
-                times,
-                order: newOrder,
-                status: auditConst.status.uncheck,
-            };
-            const result = await this.db.insert(this.tableName, data);
-            return (result.effectRows = 1);
+        async addAuditor(stageId, auditorId, times = 1, is_gdzs = 0) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                let newOrder = await this.getNewOrder(stageId, times);
+                // 判断是否存在固定终审,存在则newOrder - 1并使终审order+1
+                newOrder = is_gdzs === 1 ? newOrder - 1 : newOrder;
+                if (is_gdzs) await this._syncOrderByDelete(transaction, stageId, newOrder, times, '+');
+                const data = {
+                    tid: this.ctx.tender.id,
+                    sid: stageId,
+                    aid: auditorId,
+                    times,
+                    order: newOrder,
+                    status: auditConst.status.uncheck,
+                };
+                const result = await transaction.insert(this.tableName, data);
+                await transaction.commit();
+                return result.effectRows = 1;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return false;
         }
 
         /**
@@ -186,7 +198,7 @@ module.exports = app => {
          * @return {Promise<*>}
          * @private
          */
-        async _syncOrderByDelete(transaction, stageId, order, times) {
+        async _syncOrderByDelete(transaction, stageId, order, times, selfOperate = '-') {
             this.initSqlBuilder();
             this.sqlBuilder.setAndWhere('sid', {
                 value: stageId,
@@ -202,7 +214,7 @@ module.exports = app => {
             });
             this.sqlBuilder.setUpdateData('order', {
                 value: 1,
-                selfOperate: '-',
+                selfOperate: selfOperate,
             });
             const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
             const data = await transaction.query(sql, sqlParam);
@@ -246,7 +258,11 @@ module.exports = app => {
         async start(stageId, times = 1) {
             const audit = await this.getDataByCondition({ sid: stageId, times, order: 1 });
             if (!audit) {
-                throw '请先选择审批人,再上报数据';
+                if(this.ctx.tender.info.shenpi.stage === shenpiConst.sp_status.gdspl) {
+                    throw '请联系管理员添加审批人';
+                } else {
+                    throw '请先选择审批人,再上报数据';
+                }
             }
 
             const transaction = await this.db.beginTransaction();
@@ -1238,6 +1254,57 @@ module.exports = app => {
                 throw err;
             }
         }
+
+        // 固定审批流-更新
+        async updateNewAuditList(stage, newIdList) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先删除旧的审批流,再添加新的
+                await transaction.delete(this.tableName, { sid: stage.id, times: stage.times });
+                const newAuditors = [];
+                let order = 1;
+                for (const aid of newIdList) {
+                    newAuditors.push({
+                        tid: stage.tid, sid: stage.id, aid,
+                        times: stage.times, order, status: auditConst.status.uncheck,
+                    });
+                    order++;
+                }
+                if(newAuditors.length > 0) await transaction.insert(this.tableName, newAuditors);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        // 固定终审-更新
+        async updateLastAudit(stage, auditList, lastId) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先判断auditList里的aid是否与lastId相同,相同则删除并重新更新order
+                const idList = this._.map(auditList, 'aid');
+                let order = idList.length + 1;
+                if (idList.indexOf(lastId) !== -1) {
+                    await transaction.delete(this.tableName, { sid: stage.id, times: stage.times, aid: lastId });
+                    const audit = this._.find(auditList, { 'aid': lastId });
+                    // 顺移之后审核人流程顺序
+                    await this._syncOrderByDelete(transaction, stage.id, audit.order, stage.times);
+                    order = order - 1;
+                }
+
+                // 添加终审
+                const newAuditor = {
+                    tid: stage.tid, sid: stage.id, aid: lastId,
+                    times: stage.times, order, status: auditConst.status.uncheck,
+                };
+                await transaction.insert(this.tableName, newAuditor);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
     }
 
     return StageAudit;

+ 45 - 14
app/service/stage_change.js

@@ -40,13 +40,16 @@ module.exports = app => {
                 '  oc.p_code As c_code, oc.new_code As c_new_code' +
                 '  FROM ' + this.tableName + ' As c ' +
                 '  INNER JOIN ( ' +
-                '    SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `progress`, `lid`, `pid`, `sid` From ' + this.tableName +
+                '    SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `progress`, `lid`, `pid`, `sid`, `cid`, `cbid` From ' + this.tableName +
                 '      WHERE tid = ? And sid = ? And lid = ? And pid = ?' +
-                '      GROUP By `lid`, `pid`' +
+                '      GROUP By `lid`, `pid`, `cid`, `cbid`' +
                 '  ) As m ' +
-                '  ON (c.stimes * ' + timesLen + ' + c.sorder) = m.progress And c.lid = m.lid And c.pid = m.pid And c.`sid` = m.`sid`' +
+                '  ON (c.stimes * ' + timesLen + ' + c.sorder) = m.progress And c.lid = m.lid And c.pid = m.pid And c.`sid` = m.`sid` And c.`cid` = m.`cid` And c.`cbid` = m.`cbid`' +
                 '  LEFT JOIN ' + this.ctx.service.change.tableName + ' As oc' +
-                '  ON c.cid = oc.cid';
+                '  ON c.cid = oc.cid' +
+                '  LEFT JOIN ' + this.ctx.service.changeAuditList.tableName + ' As ocb' +
+                '  ON c.cbid = ocb.id' +
+                '  WHERE not ISNULL(ocb.id)';
             const sqlParam = [tid, sid, lid, pid ? pid : -1];
             return await this.db.query(sql, sqlParam);
         }
@@ -66,13 +69,16 @@ module.exports = app => {
                 '  oc.p_code As c_code, oc.new_code As c_new_code' +
                 '  FROM ' + this.tableName + ' As c ' +
                 '  INNER JOIN ( ' +
-                '    SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `progress`, `lid`, `pid`, `sid` From ' + this.tableName +
+                '    SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `progress`, `lid`, `pid`, `sid`, `cid`, `cbid` From ' + this.tableName +
                 '      WHERE tid = ? And sid = ? And (`stimes` < ? OR (`stimes` = ? AND `sorder` <= ?)) And lid = ? And pid = ?' +
                 '      GROUP By `lid`, `pid`' +
                 '  ) As m ' +
-                '  ON (c.stimes * ' + timesLen + ' + c.sorder) = m.progress And c.lid = m.lid And c.pid = m.pid And c.`sid` = m.`sid`' +
+                '  ON (c.stimes * ' + timesLen + ' + c.sorder) = m.progress And c.lid = m.lid And c.pid = m.pid And c.`sid` = m.`sid` And c.`cid` = m.`cid` And c.`cbid` = m.`cbid`' +
                 '  LEFT JOIN ' + this.ctx.service.change.tableName + ' As oc' +
-                '  ON c.cid = oc.cid';
+                '  ON c.cid = oc.cid' +
+                '  LEFT JOIN ' + this.ctx.service.changeAuditList.tableName + ' As ocb' +
+                '  ON c.cbid = ocb.id' +
+                '  WHERE not ISNULL(ocb.id)';
             const sqlParam = [tid, sid, times, times, order, lid, pid ? pid : -1];
             return await this.db.query(sql, sqlParam);
         }
@@ -82,13 +88,16 @@ module.exports = app => {
                 '  oc.p_code As c_code, oc.new_code As c_new_code' +
                 '  FROM ' + this.tableName + ' As c ' +
                 '  INNER JOIN ( ' +
-                '    SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `progress`, `lid`, `pid`, `sid` From ' + this.tableName +
+                '    SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `progress`, `lid`, `pid`, `sid`, `cid`, `cbid` From ' + this.tableName +
                 '      WHERE tid = ? And sid = ?' +
                 '      GROUP By `lid`, `pid`' +
                 '  ) As m ' +
-                '  ON (c.stimes * ' + timesLen + ' + c.sorder) = m.progress And c.lid = m.lid And c.pid = m.pid And c.`sid` = m.`sid`' +
+                '  ON (c.stimes * ' + timesLen + ' + c.sorder) = m.progress And c.lid = m.lid And c.pid = m.pid And c.`sid` = m.`sid` And c.`cid` = m.`cid` And c.`cbid` = m.`cbid`' +
                 '  LEFT JOIN ' + this.ctx.service.change.tableName + ' As oc' +
-                '  ON c.cid = oc.cid';
+                '  ON c.cid = oc.cid'+
+                '  LEFT JOIN ' + this.ctx.service.changeAuditList.tableName + ' As ocb' +
+                '  ON c.cbid = ocb.id' +
+                '  WHERE not ISNULL(ocb.id)';
             const sqlParam = [tid, sid];
             return await this.db.query(sql, sqlParam);
         }
@@ -98,13 +107,16 @@ module.exports = app => {
                 '  oc.p_code As c_code, oc.new_code As c_new_code' +
                 '  FROM ' + this.tableName + ' As c ' +
                 '  INNER JOIN ( ' +
-                '    SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `progress`, `lid`, `pid`, `sid` From ' + this.tableName +
+                '    SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `progress`, `lid`, `pid`, `sid`, `cid`, `cbid` From ' + this.tableName +
                 '      WHERE tid = ? And sid = ? And (`stimes` < ? OR (`stimes` = ? AND `sorder` <= ?))' +
                 '      GROUP By `lid`, `pid`' +
                 '  ) As m ' +
-                '  ON (c.stimes * ' + timesLen + ' + c.sorder) = m.progress And c.lid = m.lid And c.pid = m.pid And c.`sid` = m.`sid`' +
+                '  ON (c.stimes * ' + timesLen + ' + c.sorder) = m.progress And c.lid = m.lid And c.pid = m.pid And c.`sid` = m.`sid` And c.`cid` = m.`cid` And c.`cbid` = m.`cbid`' +
                 '  LEFT JOIN ' + this.ctx.service.change.tableName + ' As oc' +
-                '  ON c.cid = oc.cid';
+                '  ON c.cid = oc.cid'+
+                '  LEFT JOIN ' + this.ctx.service.changeAuditList.tableName + ' As ocb' +
+                '  ON c.cbid = ocb.id' +
+                '  WHERE not ISNULL(ocb.id)';
             const sqlParam = [tid, sid, times, times, order];
             return await this.db.query(sql, sqlParam);
         }
@@ -276,7 +288,7 @@ module.exports = app => {
         async getUsedData(tid, cid) {
             const lastStage = await this.ctx.service.stage.getLastestStage(tid, true);
             let filter;
-            if (lastStage.id === this.ctx.stage.id) {
+            if (this.ctx.stage && lastStage.id === this.ctx.stage.id) {
                 filter = this.db.format(' And (s.`order` < ? || (s.`order` = ? And sChange.`stimes` <= ? And sChange.`sorder` <= ?))',
                     [lastStage.order, lastStage.order, this.ctx.stage.curTimes, this.ctx.stage.curOrder]);
             } else {
@@ -312,6 +324,25 @@ module.exports = app => {
             return await this.db.query(sql, sqlParam);
         }
 
+        async getFinalUsedData(tid, cid) {
+            const sql = 'SELECT c.lid, c.pid, SUM(c.qty) as used_qty,' +
+                '    cb.tid, cb.cid, cb.id, cb.code, cb.name, cb.unit, cb.unit_price, cb.detail, cb.samount' +
+                '  FROM ' + this.ctx.service.changeAuditList.tableName + ' As cb' +
+                '  LEFT JOIN ' + this.tableName + ' As c ON cb.id = c.cbid ' +
+                '  INNER JOIN (' +
+                '    SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `flow`, `lid`, `pid`, `cbid`, sChange.`sid`, `cid` ' +
+                '      FROM ' + this.tableName + ' As sChange' +
+                '      LEFT JOIN ' + this.ctx.service.stage.tableName + ' As s ON sChange.sid = s.id' +
+                '      WHERE sChange.tid = ? AND cid = ?' +
+                '      GROUP By `lid`, `pid`, `cbid`, sChange.`sid`' +
+                '  ) As m' +
+                '  ON (c.stimes * ' + timesLen + ' + c.sorder) = m.flow And c.`cbid` = m.`cbid` AND c.`sid` = m.`sid` And c.`cid` = m.`cid` And c.`lid` = m.`lid` And c.`pid` = m.`pid`' +
+                '  WHERE cb.cid = ?' +
+                '  GROUP By c.`cbid`';
+            const sqlParam = [tid, cid, cid];
+            return await this.db.query(sql, sqlParam);
+        }
+
         /**
          * 获取 变更令 - 变更清单 当期使用情况
          * @param {Number} sid - 查询期id

+ 4 - 1
app/service/stage_pay.js

@@ -75,6 +75,7 @@ module.exports = app => {
          * @returns {Promise<void>}
          */
         async addInitialStageData(stage, transaction) {
+            const basesReg = new RegExp(payConst.calcBase.map(x => {return '(' + x.code + ')'}).join('|'));
             if (!stage) {
                 throw '初始化期合同支付数据失败';
             }
@@ -101,7 +102,9 @@ module.exports = app => {
                         tid: p.tid, sid: stage.id, pid: p.id,
                         stimes: stage.times, sorder: 0,
                         name: pp.name,
-                        expr: (p.ptype === payConst.payType.normal || p.ptype === payConst.payType.wc) ? pp.expr : null,
+                        expr: (p.ptype === payConst.payType.normal || p.ptype === payConst.payType.sf)
+                            ? (basesReg.test(pp.expr) ? pp.expr : null)
+                            : (p.type === payConst.wc ? pp.expr : null),
                         pause: pp.pause,
                         pre_tp: pp.end_tp,
                         pre_used: pp.pre_used || !this.ctx.helper.checkZero(pp.tp),

+ 117 - 1
app/service/stage_pos.js

@@ -38,7 +38,7 @@ module.exports = app => {
             if (where.lid) {
                 if (where.lid instanceof Array) {
                     whereSql += ' And ' + asTable + 'lid in ('  + this.ctx.helper.getInArrStrSqlFilter(where.lid) + ')';
-                } else if (typeof where.pid === "string") {
+                } else if (typeof where.lid === "string") {
                     whereSql += ' And ' + asTable + 'lid = ' + this.db.escape(where.lid);
                 }
             }
@@ -379,6 +379,120 @@ module.exports = app => {
         }
 
         /**
+         * 更新部位明细数据(仅供updateStageData调用)
+         *
+         * @param transaction - 事务
+         * @param data - 更新数据(允许一次性提交多条)
+         * @returns {Promise<{ledger: Array, pos: Array}>}
+         * @private
+         */
+        async _batchUpdateStagePosData(data) {
+            const datas = data instanceof Array ? data : [data];
+            const result = {
+                ledger: this._.uniq(this._.map(datas, 'lid')),
+                pos: this._.map(datas, 'pid'),
+                stageUpdate: true
+            };
+
+            const orgStagePos = await this.getLastestStageData2(this.ctx.tender.id, this.ctx.stage.id,
+                {pid: this._.map(datas, 'pid')});
+
+            const bills = await this.ctx.service.ledger.getAllDataByCondition({where: {id: result.ledger}});
+            for (const b of bills) {
+                b.precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, b.unit);
+            }
+
+            const updatePosStage = [], insertPosStage = [];
+            for (const d of datas) {
+                const b = this._.find(bills, {id: d.lid});
+
+                const osp = this._.find(orgStagePos, function (p) { return p.pid === d.pid; });
+
+                if (osp && osp.times === this.ctx.stage.curTimes && osp.order === this.ctx.stage.curOrder) {
+                    const sp = {id: osp.id, pid: osp.pid};
+                    if (d.contract_qty !== undefined) {
+                        sp.contract_qty = this.ctx.helper.round(d.contract_qty, b.precision.value);
+                    }
+                    sp.contract_expr = d.contract_expr;
+                    updatePosStage.push(sp);
+                } else {
+                    const sp = {
+                        pid: d.pid, lid: d.lid,
+                        tid: this.ctx.tender.id, sid: this.ctx.stage.id,
+                        said: this.ctx.session.sessionUser.accountId,
+                        times: this.ctx.stage.curTimes, order: this.ctx.stage.curOrder
+                    };
+                    if (d.contract_qty !== undefined || osp) {
+                        sp.contract_qty = d.contract_qty === undefined && osp
+                            ? osp.contract_qty
+                            : this.ctx.helper.round(d.contract_qty, b.precision.value);
+                        sp.contract_expr = d.contract_expr;
+                    }
+                    insertPosStage.push(sp);
+                }
+                result.pos.push(d.pid);
+            }
+            const updateBillsStage = [], insertBillsStage = [], info = this.ctx.tender.info;
+            for (const b of bills) {
+                const stageBills = await this.ctx.service.stageBills.getLastestStageData(b.tender_id, this.ctx.stage.id, b.id);
+
+                const posStage = await this.getLastestStageData2(b.tender_id, this.ctx.stage.id, {lid: b.id});
+                let contract_qty = 0;
+                const newPosRange = insertPosStage.filter(x => {return x.lid === b.id});
+                for (const nps of newPosRange) {
+                    contract_qty = this.ctx.helper.add(contract_qty, nps.contract_qty);
+                }
+                for (const ps of posStage) {
+                    const ips = this._.find(insertPosStage, {pid: ps.pid});
+                    if (!ips) {
+                        const ups = this._.find(updatePosStage, {id: ps.id});
+                        contract_qty = this.ctx.helper.add(contract_qty, ups ? ups.contract_qty : ps.contract_qty);
+                    }
+                }
+                if (stageBills && stageBills.times === this.ctx.stage.curTimes && stageBills.order === this.ctx.stage.curOrder) {
+                    if (contract_qty === stageBills.contract_qty) continue;
+                    updateBillsStage.push({
+                        id: stageBills.id,
+                        contract_qty: contract_qty,
+                        contract_tp: this.ctx.helper.mul(contract_qty, b.unit_price, info.decimal.tp),
+                    });
+                } else {
+                    insertBillsStage.push({
+                        tid: this.ctx.tender.id,
+                        lid: b.id,
+                        sid: this.ctx.stage.id,
+                        times: this.ctx.stage.curTimes,
+                        order: this.ctx.stage.curOrder,
+                        said: this.ctx.session.sessionUser.accountId,
+                        contract_qty: contract_qty,
+                        contract_tp: this.ctx.helper.mul(contract_qty, b.unit_price, info.decimal.tp),
+                    })
+                }
+            }
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                if (updatePosStage.length > 0) {
+                    await transaction.updateRows(this.tableName, updatePosStage);
+                }
+                if (insertPosStage.length > 0) {
+                    await transaction.insert(this.tableName, insertPosStage);
+                }
+                if (updateBillsStage.length > 0) {
+                    await transaction.updateRows(this.ctx.service.stageBills.tableName, updateBillsStage);
+                }
+                if (insertBillsStage.length > 0) {
+                    await transaction.insert(this.ctx.service.stageBills.tableName, insertBillsStage);
+                }
+                await transaction.commit();
+                return result;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
          * 删除部位明细数据(仅供updateStageData调用)
          *
          * @param transaction - 事务
@@ -451,6 +565,8 @@ module.exports = app => {
                 refreshData = await this._addStagePosData(data.updateData);
             } else if (data.updateType === 'update') {
                 refreshData = await this._updateStagePosData(data.updateData);
+            } else if (data.updateType === 'batchUpdate') {
+                refreshData = await this._batchUpdateStagePosData(data.updateData);
             } else if (data.updateType === 'delete') {
                 if (!data.updateData || data.updateData.length === 0) {
                     throw '提交数据错误';

+ 10 - 0
app/service/tender.js

@@ -285,8 +285,16 @@ module.exports = app => {
                 await transaction.delete(this.ctx.service.stageAudit.tableName, { tid: id });
                 await transaction.delete(this.ctx.service.stageBills.tableName, { tid: id });
                 await transaction.delete(this.ctx.service.stagePos.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.stageBillsDgn.tableName, {tid: id});
+                await transaction.delete(this.ctx.service.stageBillsFinal.tableName, {tid: id});
+                await transaction.delete(this.ctx.service.stagePosFinal.tableName, {tid: id});
                 await transaction.delete(this.ctx.service.stageDetail.tableName, { tid: id });
                 await transaction.delete(this.ctx.service.stagePay.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.stageChange.tableName, { tid: id });
+
+                await transaction.delete(this.ctx.service.stageJgcl.tableName, {tid: id});
+                await transaction.delete(this.ctx.service.stageBonus.tableName, {tid: id});
+                await transaction.delete(this.ctx.service.stageOther.tableName, {tid: id});
 
                 await transaction.delete(this.ctx.service.change.tableName, { tid: id });
                 await transaction.delete(this.ctx.service.changeAudit.tableName, { tid: id });
@@ -304,6 +312,8 @@ module.exports = app => {
                 await transaction.delete(this.ctx.service.materialBillsHistory.tableName, { tid: id });
                 await transaction.delete(this.ctx.service.materialList.tableName, { tid: id });
                 await transaction.delete(this.ctx.service.materialListNotjoin.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.materialExponent.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.materialExponentHistory.tableName, { tid: id });
 
 
                 await transaction.delete(this.ctx.service.materialFile.tableName, { tid: id });

+ 54 - 10
app/service/tender_info.js

@@ -13,6 +13,9 @@ const parseInfo = infoConst.parseInfo;
 const arrayInfo = infoConst.arrayInfo;
 const defaultInfo = infoConst.defaultInfo;
 const advanceConst = require('../const/audit').advance;
+const auditConst = require('../const/audit');
+
+
 module.exports = app => {
 
     class TenderInfo extends app.BaseService {
@@ -119,6 +122,24 @@ module.exports = app => {
             await this.db.update(this.tableName, data, { where: { tid: tenderId } });
         }
 
+        async _getLedgerService() {
+            try {
+                if (this.ctx.tender.data.ledger_status === auditConst.ledger.status.checked) {
+                    const stageCount = await this.ctx.service.stage.count({tid: this.ctx.tender.id});
+                    if (stageCount === 0) {
+                        const revise = await this.ctx.service.ledgerRevise.getLastestRevise(this.ctx.tender.id);
+                        if (revise.status === auditConst.revise.status.uncheck || revise.status === auditConst.revise.status.checkNo) {
+                            return [this.ctx.service.reviseBills, this.ctx.service.revisePos];
+                        }
+                    }
+                } else {
+                    return [this.ctx.service.ledger, this.ctx.service.pos];
+                }
+            } catch(err) {
+            }
+            return [];
+        }
+
         async savePrecision(tenderId, newPrecision, oldPrecision, decimal) {
             const changePrecision = [];
             const units = await this.ctx.service.ledger.getTenderUsedUnits(tenderId);
@@ -143,12 +164,14 @@ module.exports = app => {
             }
             changeUnits = this._.flatten(changeUnits);
 
-            if (changeUnits.length > 0) {
-                const bills = await this.ctx.service.ledger.getAllDataByCondition({
+            const [billsService, posService] = await this._getLedgerService();
+
+            if (changeUnits.length > 0 && billsService && posService) {
+                const bills = await billsService.getAllDataByCondition({
                     columns: ['id', 'unit', 'unit_price', 'sgfh_qty', 'sjcl_qty', 'qtcl_qty', 'deal_qty'],
                     where: { tender_id: tenderId, unit: changeUnits, is_leaf: true },
                 });
-                const pos = changeUnits.length > 0 ? await this.ctx.service.pos.getPosDataByUnits(tenderId, changeUnits) : [];
+                const pos = changeUnits.length > 0 ? await posService.getPosDataByUnits(tenderId, changeUnits) : [];
 
                 for (const b of bills) {
                     const precision = this.ctx.helper.findPrecision(newPrecision, b.unit);
@@ -185,8 +208,8 @@ module.exports = app => {
                 try {
                     await transaction.update(this.tableName,
                         { 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);
+                    if (bills.length > 0) await transaction.updateRows(billsService.tableName, bills);
+                    if (pos.length > 0) await transaction.updateRows(posService.tableName, pos);
                     await transaction.commit();
                 } catch (err) {
                     await transaction.rollback();
@@ -198,11 +221,13 @@ module.exports = app => {
             }
         }
 
-        async _reCalcLedger(tenderId, newDecimal, oldDecimal) {
+        async _reCalcLedger(tenderId, billsService, newDecimal, oldDecimal) {
+            if (!billsService) return [];
+
             const changeBills = [];
             const calcUp = newDecimal.up < oldDecimal.up, calcTp = newDecimal.tp !== oldDecimal.tp;
             if (calcUp || calcTp) {
-                const bills = await this.ctx.service.ledger.getAllDataByCondition({
+                const bills = await billsService.getAllDataByCondition({
                     columns: ['id', 'unit_price', 'sgfh_qty', 'sjcl_qty', 'qtcl_qty', 'deal_qty', 'quantity'],
                     where: { tender_id: tenderId, is_leaf: true },
                 });
@@ -227,7 +252,6 @@ module.exports = app => {
             const upDecimal = newDecimal.up, tpDecimal = newDecimal.extra ? newDecimal.extraTp : newDecimal.tp;
             const calcUp = upDecimal < oldDecimal.up,
                 calcTp = tpDecimal < (oldDecimal.extra ? oldDecimal.extraTp : oldDecimal.tp);
-            console.log(calcUp, calcTp);
 
             if (calcUp || calcTp) {
                 const stageJgcl = await this.ctx.service.stageJgcl.getAllDataByCondition({
@@ -281,7 +305,11 @@ module.exports = app => {
                     changeAdvanceBills.push(cb);
                 }
             }
-            const changeBills = await this._reCalcLedger(tenderId, newDecimal, oldDecimal);
+
+            const [billsService] = await this._getLedgerService();
+            console.log(billsService);
+
+            const changeBills = await this._reCalcLedger(tenderId, billsService, newDecimal, oldDecimal);
             const [changeSj, changeSb, changeSo] = await this._reCalcStageExtra(tenderId, newDecimal, oldDecimal);
             if (changeBills.length > 0 ||
                 changeAdvanceBills.length > 0 ||
@@ -290,7 +318,7 @@ module.exports = app => {
                 try {
                     await transaction.update(this.tableName,
                         { decimal: JSON.stringify(newDecimal) }, { where: { tid: tenderId } });
-                    if (changeBills.length > 0) await transaction.updateRows(this.ctx.service.ledger.tableName, changeBills);
+                    if (changeBills.length > 0) await transaction.updateRows(billsService.tableName, changeBills);
                     if (changeSj.length > 0) await transaction.updateRows(this.ctx.service.stageJgcl.tableName, changeSj);
                     if (changeSb.length > 0) await transaction.updateRows(this.ctx.service.stageBonus.tableName, changeSb);
                     if (changeSo.length > 0) await transaction.updateRows(this.ctx.service.stageOther.tableName, changeSo);
@@ -306,6 +334,22 @@ module.exports = app => {
                     { decimal: JSON.stringify(newDecimal) }, { where: { tid: tenderId } });
             }
         }
+
+        /**
+         * 获取标段审批相关信息 (审批设置用)
+         * @param tenderId
+         * @return {Promise<void>}
+         */
+        async getTenderShenpiInfo(tenderId) {
+            const info = await this.getDataByCondition({ tid: tenderId });
+            // 还没选择模式的标段不应该可以选择审批流程设置
+            if (!info) {
+                return false;
+            }
+            const defaultShenpiInfo = JSON.parse(JSON.stringify(defaultInfo.shenpi));
+            const shenpiInfo = !info.shenpi || info.shenpi === null || info.shenpi === '' ? defaultShenpiInfo : JSON.parse(info.shenpi);
+            return shenpiInfo;
+        }
     }
 
     return TenderInfo;

+ 389 - 382
app/view/advance/detail.ejs

@@ -1,382 +1,389 @@
-<% 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, ',', parseFloat(advancePayTotal.toFixed(decimal)).toString().split('.')[1] && parseFloat(advancePayTotal.toFixed(decimal)).toString().split('.')[1].length || 0) %>
-                                    元
-                                </th>
-                            </tr>
-                        </thead>
-                        <tbody id="pay-content">
-                            <tr>
-                                <th width="150" class="text-center">支付比例</th>
-                                <td class="text-right" width="405">
-                                    <div class="input-group input-group-sm">
-                                        <input type="number" class="pay-input form-control nospin text-right"
-                                            max="<%- max_pr %>" min="1" step="0.01" placeholder="请填写支付比例,将自动计算本期金额" data-type="0" <%- isEdited ? '' : 'disabled' %>
-                                            value="<%- advance.pay_ratio && 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" class="text-center">本期金额</th>
-                                <td class="text-right" width="405">
-                                    <div class="input-group input-group-sm">
-                                        <input type="number" class="pay-input form-control nospin text-right" min="1" <%- isEdited  ? '' : 'disabled' %>
-                                            placeholder="请填写本期金额,将自动计算支付比例" data-type="1"
-                                            value="<%- cur_amount %>">
-                                        <div class="input-group-append"><span class="input-group-text">元</span></div>
-                                    </div>
-                                </td>
-                            </tr>
-                            <tr>
-                                <th class="text-center">截止上期</th>
-                                <td class="text-right" id="p_total1" width="405">
-                                    <%- prev_amount %>元
-                                </td>
-                                <th class="text-center">截止本期金额</th>
-                                <td class="text-right" id="p_total2" width="405">
-                                    <%- prev_total_amount %>元
-                                </td>
-                            </tr>
-                            <tr>
-                                <th class="text-center" >备注</th>
-                                <td colspan="3">
-                                    <textarea id="ad-remark" class="form-control form-control-sm" rows="2"
-                                        <%- isEdited ? '' : '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) { %>
-                    <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 text-right">
-                                        <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button"
-                                            id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true"
-                                            aria-expanded="false">
-                                            添加审批流程
-                                        </button>
-                                        <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton"
-                                            style="width:220px">
-                                            <div class="mb-2 p-2"><input class="form-control form-control-sm"
-                                                    placeholder="姓名/手机 检索" id="gr-search" autocomplete="off"></div>
-                                            <dl class="list-unstyled book-list">
-                                                <% accountGroup.forEach((group, idx) => { %>
-                                                    <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>" data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
-                                                    <div class="dd-content" data-toggleid="<%- idx %>">
-                                                        <% group.groupList.forEach(item => { %>
-                                                            <% if (item.id !== ctx.session.sessionUser.accountId) { %>
-                                                                <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>" >
-                                                                    <p class="mb-0 d-flex">
-                                                                        <span class="text-primary"><%- item.name %></span>
-                                                                        <span class="ml-auto"><%- item.mobile %></span></p>
-                                                                    <span class="text-muted"><%- item.role %></span>
-                                                                </dd>
-                                                            <% } %>
-                                                        <% });%>
-                                                    </div>
-                                                <% }) %>
-                                            </dl>
-                                        </div>
-                                    </div>
-
-                                    <div class="card mt-3">
-                                        <div class="card-header">
-                                            审批流程
-                                        </div>
-                                        <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) => { %>
-                                        <!-- 展开/收起历史流程 -->
-                                        <% if(idx === auditHistory.length - 1 && auditHistory.length !== 1) { %>
-                                            <div class="text-right">
-                                                <a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a>
-                                            </div>
-                                        <% } %>
-                                        <div class="<%- idx < auditHistory.length - 1 ? 'fold-card' : '' %>">
-                                            <div class="text-center text-muted" ><%- idx+1 %>#</div>
-                                            <ul class="timeline-list list-unstyled mt-2">
-                                                <% auditors.forEach((auditor, index) => { %>
-                                                <% if (index === 0) { %>
-                                                <li class="timeline-list-item pb-2">
-                                                    <div class="timeline-item-date">
-                                                        <%- ctx.helper.formatDate(auditor.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>
-                                    <% }) %>
-                                </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('#fold-btn').text('收起历史审核记录')
-            })
-        } else {
-            $(this).data('target', 'show')
-            auditCard.find('.fold-card').slideUp('swing', () => {
-                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>
-<% } %>
-
+<% 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, ',', parseFloat(advancePayTotal).toString().split('.')[1] && parseFloat(advancePayTotal).toString().split('.')[1].length || 0) %>
+                                    元
+                                </th>
+                            </tr>
+                        </thead>
+                        <tbody id="pay-content">
+                            <tr>
+                                <th width="150" class="text-center">支付比例</th>
+                                <td class="text-right" width="405">
+                                    <div class="input-group input-group-sm">
+                                        <input type="number" class="pay-input form-control nospin text-right"
+                                            max="<%- max_pr %>" min="1" step="0.01" placeholder="请填写支付比例,将自动计算本期金额" data-type="0" <%- isEdited ? '' : 'disabled' %>
+                                            value="<%- advance.pay_ratio && 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" class="text-center">本期金额</th>
+                                <td class="text-right" width="405">
+                                    <div class="input-group input-group-sm">
+                                        <input type="number" class="pay-input form-control nospin text-right" min="1" <%- isEdited  ? '' : 'disabled' %>
+                                            placeholder="请填写本期金额,将自动计算支付比例" data-type="1"
+                                            value="<%- cur_amount %>">
+                                        <div class="input-group-append"><span class="input-group-text">元</span></div>
+                                    </div>
+                                </td>
+                            </tr>
+                            <tr>
+                                <th class="text-center">截止上期</th>
+                                <td class="text-right" id="p_total1" width="405">
+                                    <%- prev_amount %>元
+                                </td>
+                                <th class="text-center">截止本期金额</th>
+                                <td class="text-right" id="p_total2" width="405">
+                                    <%- prev_total_amount %>元
+                                </td>
+                            </tr>
+                            <tr>
+                                <th class="text-center" >备注</th>
+                                <td colspan="3">
+                                    <textarea id="ad-remark" class="form-control form-control-sm" rows="2"
+                                        <%- isEdited ? '' : '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) { %>
+                    <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 text-right">
+                                        <% if (ctx.tender.info.shenpi.advance !== shenpiConst.sp_status.gdspl) { %>
+                                        <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button"
+                                            id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true"
+                                            aria-expanded="false">
+                                            添加审批流程
+                                        </button>
+                                        <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton"
+                                            style="width:220px">
+                                            <div class="mb-2 p-2"><input class="form-control form-control-sm"
+                                                    placeholder="姓名/手机 检索" id="gr-search" autocomplete="off"></div>
+                                            <dl class="list-unstyled book-list">
+                                                <% accountGroup.forEach((group, idx) => { %>
+                                                    <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>" data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                                                    <div class="dd-content" data-toggleid="<%- idx %>">
+                                                        <% group.groupList.forEach(item => { %>
+                                                            <% if (item.id !== ctx.session.sessionUser.accountId) { %>
+                                                                <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>" >
+                                                                    <p class="mb-0 d-flex">
+                                                                        <span class="text-primary"><%- item.name %></span>
+                                                                        <span class="ml-auto"><%- item.mobile %></span></p>
+                                                                    <span class="text-muted"><%- item.role %></span>
+                                                                </dd>
+                                                            <% } %>
+                                                        <% });%>
+                                                    </div>
+                                                <% }) %>
+                                            </dl>
+                                        </div>
+                                        <% } %>
+                                    </div>
+
+                                    <div class="card mt-3">
+                                        <div class="card-header">
+                                            审批流程
+                                        </div>
+                                        <ul class="list-group list-group-flush" id="auditors">
+                                            <% auditorList.forEach((item, idx) => { %>
+                                            <li class="list-group-item" auditorId="<%- item.audit_id %>">
+                                                <% if (ctx.tender.info.shenpi.advance === shenpiConst.sp_status.sqspr ||
+                                                        (ctx.tender.info.shenpi.advance === shenpiConst.sp_status.gdzs && idx+1 !== auditorList.length)) { %>
+                                                <a href="javascript: void(0)" class="text-danger pull-right">移除</a>
+                                                <% } %>
+                                                <span><%- idx+1 %> <%- item.name %></span>
+                                                <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) => { %>
+                                        <!-- 展开/收起历史流程 -->
+                                        <% if(idx === auditHistory.length - 1 && auditHistory.length !== 1) { %>
+                                            <div class="text-right">
+                                                <a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a>
+                                            </div>
+                                        <% } %>
+                                        <div class="<%- idx < auditHistory.length - 1 ? 'fold-card' : '' %>">
+                                            <div class="text-center text-muted" ><%- idx+1 %>#</div>
+                                            <ul class="timeline-list list-unstyled mt-2">
+                                                <% auditors.forEach((auditor, index) => { %>
+                                                <% if (index === 0) { %>
+                                                <li class="timeline-list-item pb-2">
+                                                    <div class="timeline-item-date">
+                                                        <%- ctx.helper.formatDate(auditor.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>
+                                    <% }) %>
+                                </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('#fold-btn').text('收起历史审核记录')
+            })
+        } else {
+            $(this).data('target', 'show')
+            auditCard.find('.fold-card').slideUp('swing', () => {
+                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) %>');
+    const shenpi_status = <%- ctx.tender.info.shenpi.advance %>;
+    const shenpiConst =  JSON.parse('<%- JSON.stringify(shenpiConst) %>');
+</script>
+<% } %>
+

+ 100 - 100
app/view/advance/index.ejs

@@ -1,100 +1,100 @@
-<% 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 group-tab">
-                        <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, ',', parseFloat(advancePayTotal.toFixed(decimal)).toString().split('.')[1] && parseFloat(advancePayTotal.toFixed(decimal)).toString().split('.')[1].length || 0) %></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, ',', parseFloat(progress.p_amount.toFixed(decimal)).toString().split('.')[1] && parseFloat(progress.p_amount.toFixed(decimal)).toString().split('.')[1].length || 0) %>"><%- 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, ',', progress.c_amount && parseFloat(progress.c_amount.toFixed(decimal)).toString().split('.')[1] && parseFloat(progress.c_amount.toFixed(decimal)).toString().split('.')[1].length || 0) %>"><%- 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, ',', parseFloat(progress.s_amount.toFixed(decimal)).toString().split('.')[1] && parseFloat(progress.s_amount.toFixed(decimal)).toString().split('.')[1].length || 0) %>"><%- 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.pay_ratio %>%</td>
-                                <td class="text-right"><%- item.cur_amount %></td>
-                                <td class="text-right"><%- item.prev_amount %></td>
-                                <td class="text-right"><%- item.prev_total_amount %></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 %>');
-    const advancePayTotal  = parseFloat('<%- advancePayTotal  %>');
-</script>
+<% 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 group-tab">
+                        <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, ',', parseFloat(advancePayTotal).toString().split('.')[1] && parseFloat(advancePayTotal).toString().split('.')[1].length || 0) %></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, ',', parseFloat(progress.p_amount).toString().split('.')[1] && parseFloat(progress.p_amount).toString().split('.')[1].length || 0) %>"><%- 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, ',', progress.c_amount && parseFloat(progress.c_amount).toString().split('.')[1] && parseFloat(progress.c_amount).toString().split('.')[1].length || 0) %>"><%- 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, ',', parseFloat(progress.s_amount).toString().split('.')[1] && parseFloat(progress.s_amount).toString().split('.')[1].length || 0) %>"><%- progress.s_ratio.toFixed() %>%</div>
+                    </div>
+                </div>
+            </div>
+            <div class="ml-auto">
+                <% if(showAddBtn) { %>
+                    <form action="<%- preUrl %>" method="POST">
+                        <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
+                        <button type="submit" class="btn btn-primary btn-sm">开始新一期</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.pay_ratio %>%</td>
+                                <td class="text-right"><%- item.cur_amount %></td>
+                                <td class="text-right"><%- item.prev_amount %></td>
+                                <td class="text-right"><%- item.prev_total_amount %></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 %>');
+    const advancePayTotal  = parseFloat('<%- advancePayTotal  %>');
+</script>

+ 28 - 8
app/view/change/info.ejs

@@ -124,12 +124,22 @@
 <div class="panel-content">
     <div class="panel-title pr-0">
         <div class="title-main d-flex justify-content-between">
-            <h2 id="info_title" class="show_title">变更信息</h2>
+            <!-- <h2 id="info_title" class="show_title">变更信息</h2> -->
+            <h2 id="info_title" class="show_title">
+                <% if(auditStatus === auditConst.status.uncheck || auditStatus === auditConst.status.back) { %>
+                    <div class="d-inline-block">
+                        <a class="btn btn-sm btn-primary" href="#add-bj" data-toggle="modal" data-target="#add-bj">拷贝其他变更令数据</a>
+                    </div>
+                <% } else { %>
+                    <span>变更信息</span>
+                <% } %>
+            </h2>
+
             <div id="bills_title" class="show_title" style="line-height:34px;display: none">
                 <div>
                     <% if (auditStatus === 1 || auditStatus === 2) { %>
                     <div class="d-inline-block">
-                        <a href="#addlist" data-toggle="modal" class="btn btn-sm btn-light text-primary" id="open-list-modal" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="添加清单"><i class="fa fa-plus" aria-hidden="true"></i> 添加台帐清单</a>
+                        <a href="#addlist" data-toggle="modal" class="btn btn-sm btn-light text-primary" id="open-list-modal" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="添加清单"><i class="fa fa-plus" aria-hidden="true"></i> 添加台清单</a>
                     </div>
                     <div class="d-inline-block mr-3">
                         <a href="javascript:void(0);" class="btn btn-sm btn-light text-primary" id="add-white-btn" data-original-title="添加清单"><i class="fa fa-plus" aria-hidden="true"></i> 添加空白清单</a>
@@ -152,13 +162,14 @@
                 </div>
             </div>
             <div id="files_title" class="show_title" style="line-height:34px;display: none">
-                <% if (auditStatus === 1 || auditStatus === 2 || auditStatus === 6) { %>
-                <a href="#addfujian" data-toggle="modal" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="添加清单">
-                    <i class="fa fa-cloud-upload" aria-hidden="true"></i> 上传附件
-                </a>
+                <!-- <% if (auditStatus === 1 || auditStatus === 2 || auditStatus === 6) { %>
+                <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>
                 <% } else { %>
                 <h2 class="change-title">附件</h2>
-                <% } %>
+                <% } %> -->
+                <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>
+                <a href="javascript: void(0);" data-toggle="modal" class="btn btn-sm btn-light text-primary" id="bach-download"><i class="fa fa-download "></i> 批量下载</a>
+                <a href="" id="downloadZip" style="display: none;" download></a>
             </div>
             <input id="tenderId" value="<%= tender.id %>" type="hidden">
             <input id="changeId" value="<%= change.cid %>" type="hidden">
@@ -705,6 +716,7 @@
             <table class="table table-bordered">
                 <thead>
                 <tr>
+                    <td width="25" style="background-color: #e9ecef;"><input type="checkbox" id="check-all-file" ></td>
                     <th width="50">序号</th>
                     <th>名称</th>
                     <th width="90">大小</th>
@@ -716,12 +728,14 @@
                 <% if (attList !== undefined && attList !== '') { %>
                 <% for (const [index,att] of attList.entries()) { %>
                 <tr>
+                    <td width="25"><input type="checkbox" class="check-file" file-id=<%- att.id %>></td>
                     <td><%- index+1 %></td>
                     <td><a href="javascript: void(0);" class="file-atn" f-id="<%- att.id %>"><%- 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>
-                        <% if (att.uid === uid && (auditStatus === 1 || auditStatus === 2 || auditStatus === 6)) { %>
+                        <a href="/change/download/file/<%- att.id %>" class="btn btn-light btn-sm" title="下载"><span class="fa fa-download text-primary"></span></a>
+                        <% if (att.uid === uid && (auditStatus === 4 ? Boolean(att.extra_upload) : true )) { %>
                         <a class="btn btn-light btn-sm delete-file" data-attid="<%- att.id %>" title="删除附件"><span class="fa fa-trash text-danger"></span></a>
                         <% } %>
                     </td>
@@ -741,6 +755,8 @@
     const accountId = '<%- uid %>';
     const ledgeStatus = '<%- tender.ledger_status %>';
     const ledgerConsts = JSON.parse('<%- JSON.stringify(ledgerConsts) %>');
+    const auditStatus = '<%- auditStatus %>'
+    const auditList = JSON.parse('<%- JSON.stringify(auditList) %>');
     autoFlashHeight();
     $('a[href="#sub-ap"').click(function() {
         if (parseInt(ledgeStatus) === ledgerConsts.uncheck) {
@@ -762,7 +778,11 @@
 <script>
     const changeUnits = JSON.parse('<%- JSON.stringify(changeUnits) %>');
     const precision = JSON.parse('<%- JSON.stringify(precision) %>');
+    const accountGroup = JSON.parse('<%- JSON.stringify(accountGroup) %>');
     const accountList = JSON.parse('<%- JSON.stringify(accountList) %>');
+    const shenpi_status = <%- ctx.tender.info.shenpi.change %>;
+    const shenpiConst =  JSON.parse('<%- JSON.stringify(shenpiConst) %>');
+    const changesUid =  <%- change.uid %>;
     const billsTable = {
         columnDefs: [
             { className: 'allwidth1', width: 100, targets: 0 },

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 622 - 340
app/view/change/info_modal.ejs


+ 0 - 0
app/view/dashboard/index.ejs


Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels