Browse Source

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

Tony Kang 2 years ago
parent
commit
45201060d3
100 changed files with 14098 additions and 1478 deletions
  1. 1 1
      Dockerfile
  2. 2 0
      app/base/base_bills_service.js
  3. 1 1
      app/const/account_permission.js
  4. 29 0
      app/const/fun_set.js
  5. 1 0
      app/const/page_show.js
  6. 47 0
      app/const/spec_3f.js
  7. 12 8
      app/const/spread.js
  8. 14 0
      app/const/standard.js
  9. 7 0
      app/const/tender_info.js
  10. 21 4
      app/controller/change_controller.js
  11. 1 1
      app/controller/datacollect_controller.js
  12. 2 0
      app/controller/ledger_audit_controller.js
  13. 4 1
      app/controller/ledger_controller.js
  14. 30 4
      app/controller/login_controller.js
  15. 26 5
      app/controller/material_controller.js
  16. 2 0
      app/controller/measure_controller.js
  17. 61 2
      app/controller/profile_controller.js
  18. 24 0
      app/controller/report_archive_controller.js
  19. 25 2
      app/controller/revise_controller.js
  20. 9 0
      app/controller/schedule_controller.js
  21. 13 1
      app/controller/setting_controller.js
  22. 97 11
      app/controller/stage_controller.js
  23. 61 18
      app/controller/tender_controller.js
  24. 13 9
      app/controller/wap_controller.js
  25. 1 1
      app/extend/helper.js
  26. 1 1
      app/lib/budget_final.js
  27. 140 14
      app/lib/ledger.js
  28. 16 13
      app/lib/pay_calc.js
  29. 112 40
      app/lib/revise_price.js
  30. 157 58
      app/lib/rm/material.js
  31. 1 0
      app/lib/rpt_data_analysis.js
  32. 4 1
      app/lib/spread_setting.js
  33. 30 13
      app/lib/stage_im.js
  34. 27 4
      app/lib/sum_load.js
  35. 3 3
      app/lib/tender_info.js
  36. 1 0
      app/middleware/session_auth.js
  37. 25 14
      app/middleware/stage_check.js
  38. 7 1
      app/middleware/tender_check.js
  39. 19 0
      app/public/css/main.css
  40. 50 9
      app/public/js/change_information.js
  41. 1 1
      app/public/js/change_information_approval.js
  42. 34 12
      app/public/js/change_information_set.js
  43. 17 6
      app/public/js/change_information_show.js
  44. 10 9
      app/public/js/change_revise.js
  45. 9164 0
      app/public/js/export/iconv-lite.js
  46. 673 0
      app/public/js/export/jschardet.min.js
  47. 4 4
      app/public/js/gcl_gather.js
  48. 1 1
      app/public/js/global.js
  49. 27 15
      app/public/js/ledger.js
  50. 1 0
      app/public/js/ledger_audit.js
  51. 19 7
      app/public/js/ledger_check.js
  52. 67 27
      app/public/js/material.js
  53. 310 2
      app/public/js/material_checklist.js
  54. 35 9
      app/public/js/material_list.js
  55. 4 2
      app/public/js/measure_compare.js
  56. 7 4
      app/public/js/measure_material.js
  57. 115 82
      app/public/js/path_tree.js
  58. 46 17
      app/public/js/profile.js
  59. 10 8
      app/public/js/revise.js
  60. 2 2
      app/public/js/revise_gcl_compare.js
  61. 1 0
      app/public/js/revise_history.js
  62. 567 9
      app/public/js/revise_price.js
  63. 8 7
      app/public/js/schedule_stage_tp.js
  64. 56 10
      app/public/js/se_bonus.js
  65. 1 1
      app/public/js/shares/cs_gcl_gather.js
  66. 8 6
      app/public/js/shares/cs_tools.js
  67. 18 1
      app/public/js/shares/gcl_gather_compare.js
  68. 22 1
      app/public/js/shares/sjs_setting.js
  69. 179 517
      app/public/js/shenpi.js
  70. 123 16
      app/public/js/spreadjs_rela/spreadjs_zh.js
  71. 137 151
      app/public/js/stage.js
  72. 10 0
      app/public/js/stage_compare.js
  73. 22 9
      app/public/js/stage_im.js
  74. 18 5
      app/public/js/stage_pay.js
  75. 9 0
      app/public/js/tender.js
  76. 1 2
      app/public/js/tender_showhide.js
  77. 12 0
      app/public/report/js/rpt_custom.js
  78. 71 8
      app/public/report/js/rpt_signature.js
  79. 10 1
      app/router.js
  80. 99 0
      app/service/audit_ass.js
  81. 1 0
      app/service/category_value.js
  82. 7 2
      app/service/change.js
  83. 11 1
      app/service/change_audit_list.js
  84. 2 0
      app/service/cooperation_confirm.js
  85. 3 0
      app/service/ledger_audit.js
  86. 16 10
      app/service/ledger_history.js
  87. 25 9
      app/service/ledger_revise.js
  88. 2 2
      app/service/material.js
  89. 83 39
      app/service/material_bills.js
  90. 94 1
      app/service/material_checklist.js
  91. 148 20
      app/service/material_list.js
  92. 26 5
      app/service/material_stage_bills.js
  93. 26 3
      app/service/project.js
  94. 12 5
      app/service/report.js
  95. 123 7
      app/service/report_memory.js
  96. 6 3
      app/service/revise_audit.js
  97. 2 0
      app/service/revise_price.js
  98. 421 179
      app/service/rpt_gather_memory.js
  99. 74 0
      app/service/spec_msg.js
  100. 0 0
      app/service/spec_pull.js

+ 1 - 1
Dockerfile

@@ -4,7 +4,7 @@ COPY . /app/calc
 
 WORKDIR /app/calc
 
-RUN npm install --registery=https://registery.npm.taobao.org
+RUN npm install --registery=https://registry.npmmirror.com
 
 EXPOSE 7005
 

+ 2 - 0
app/base/base_bills_service.js

@@ -611,6 +611,8 @@ class BaseBillsSerivce extends TreeService {
                     sjcl_expr: d.sjcl_expr,
                     qtcl_expr: d.qtcl_expr,
                     check_calc: 1,
+                    dgn_qty1: d.dgn_qty1,
+                    dgn_qty2: d.dgn_qty2,
                 };
                 for (const c of d.children) {
                     c.ledger_pid = newBills.ledger_id;

+ 1 - 1
app/const/account_permission.js

@@ -78,7 +78,7 @@ const PermissionCheck = {
     check: function (permission, key) {
         if (!permission) return false;
         switch (key) {
-            case 'viewPmData': return permission.tender.indexOf('6') >= 0;
+            case 'viewPmData': return permission && permission.tender ? permission.tender.indexOf('6') >= 0 : false;
             default: return false;
         }
     },

+ 29 - 0
app/const/fun_set.js

@@ -0,0 +1,29 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+const endMonth = [
+    { val: 1, name: '本月' },
+    { val: 2, name: '次月' },
+    // { val: 3, name: '隔月' },
+];
+const parseInfo = ['stage_start'];
+const defaultInfo = {
+    // 合同信息
+    stage_start: {
+        start_day: 0,
+        end_month: 0,
+        end_day: 0,
+    },
+};
+
+module.exports = {
+    parseInfo,
+    endMonth,
+    defaultInfo,
+};

+ 1 - 0
app/const/page_show.js

@@ -47,6 +47,7 @@ const defaultSetting = {
     openChangePlan: 0,
     isPreset: 0,
     isOnlyChecked: 1,
+    openStageStart: 0,
 };
 
 

+ 47 - 0
app/const/spec_3f.js

@@ -0,0 +1,47 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const pushTiming = [
+    { value:'ledger.checked', name: '台账-审批通过' },
+    { value:'stage.checked', name: '期-审批通过' },
+    { value:'stage.flow', name: '期-上报/审批' },
+    { value:'revise.checked', name: '台账修订-审批通过' },
+    { value:'report.file', name: '报表-推送归档' },
+];
+
+const pushOperate = (function () {
+    const result = {};
+    for (const pt of pushTiming) {
+        const path = pt.value.split('.');
+        if (!result[path[0]]) result[path[0]] = {};
+        result[path[0]][path[1]] = pt.value;
+    }
+    return result;
+})();
+
+const specMsgStatus = (function () {
+    const status = { wait: 0, exec: 1, done: 2 };
+    const name = [];
+    name[status.wait] = '等待执行';
+    name[status.exec] = '等待中';
+    name[status.done] = '执行完成';
+    return { status, name };
+})();
+
+const pullClass = [
+    { value: 'gs-da', name: '甘肃档案' },
+];
+const pullType = [
+    { value: 'sync-ledger', name: '同步台账' },
+];
+
+module.exports = {
+    pushTiming, pushOperate, specMsgStatus, pullClass, pullType,
+};

+ 12 - 8
app/const/spread.js

@@ -44,6 +44,7 @@ const withCl = {
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
             {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
+            {title: '费用类别', colSpan: '1', rowSpan: '2', field: 'node_type', hAlign: 0, width: 100, cellType: 'customizeCombo'},
             {title: 'ex_memo2', colSpan: '1', rowSpan: '2', field: 'ex_memo2', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
             {title: 'ex_memo3', colSpan: '1', rowSpan: '2', field: 'ex_memo3', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
         ],
@@ -94,6 +95,7 @@ const withoutCl = {
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sgfh_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
             {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
+            {title: '费用类别', colSpan: '1', rowSpan: '2', field: 'node_type', hAlign: 0, width: 100, cellType: 'customizeCombo'},
             {title: 'ex_memo2', colSpan: '1', rowSpan: '2', field: 'ex_memo2', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
             {title: 'ex_memo3', colSpan: '1', rowSpan: '2', field: 'ex_memo3', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
         ],
@@ -147,6 +149,7 @@ const withClGcl = {
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
             {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
+            {title: '费用类别', colSpan: '1', rowSpan: '2', field: 'node_type', hAlign: 0, width: 100, cellType: 'customizeCombo'},
             {title: 'ex_memo2', colSpan: '1', rowSpan: '2', field: 'ex_memo2', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
             {title: 'ex_memo3', colSpan: '1', rowSpan: '2', field: 'ex_memo3', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
         ],
@@ -176,6 +179,7 @@ const withoutClGcl = {
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sgfh_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
             {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
+            {title: '费用类别', colSpan: '1', rowSpan: '2', field: 'node_type', hAlign: 0, width: 100, cellType: 'customizeCombo'},
             {title: 'ex_memo2', colSpan: '1', rowSpan: '2', field: 'ex_memo2', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
             {title: 'ex_memo3', colSpan: '1', rowSpan: '2', field: 'ex_memo3', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
         ],
@@ -204,8 +208,8 @@ const stageTz = {
             {title: '本期数量变更|数量', colSpan: '3|1', rowSpan: '1|1', field: 'qc_qty', hAlign: 2, width: 60, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'qc_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '|不计价', colSpan: '|1', rowSpan: '|1', field: 'qc_minus_qty', hAlign: 2, width: 60, type: 'Number'},
-            { title: '本期补差|原单价', colSpan: '2|1', rowSpan: '1|1', field: 'org_price', hAlign: 2, width: 60, type: 'Number' },
-            { title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'pc_tp', hAlign: 2, width: 60, type: 'Number' },
+            { title: '本期补差|原单价', colSpan: '2|1', rowSpan: '1|1', field: 'org_price', hAlign: 2, width: 60, type: 'Number', readOnly: true },
+            { title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'pc_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true },
             {title: '本期完成计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'gather_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gather_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '截止本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
@@ -287,8 +291,8 @@ const stageCl = {
             {title: '本期数量变更|数量', colSpan: '3|1', rowSpan: '1|1', field: 'qc_qty', hAlign: 2, width: 60, type: 'Number',},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'qc_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '|不计价', colSpan: '|1', rowSpan: '|1', field: 'qc_minus_qty', hAlign: 2, width: 60, type: 'Number'},
-            { title: '本期补差|原单价', colSpan: '2|1', rowSpan: '1|1', field: 'org_price', hAlign: 2, width: 60, type: 'Number' },
-            { title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'pc_tp', hAlign: 2, width: 60, type: 'Number' },
+            { title: '本期补差|原单价', colSpan: '2|1', rowSpan: '1|1', field: 'org_price', hAlign: 2, width: 60, type: 'Number', readOnly: true },
+            { title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'pc_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true },
             {title: '本期完成计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'gather_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gather_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '截止本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
@@ -370,10 +374,10 @@ const stageNoCl = {
             {title: '本期数量变更|数量', colSpan: '3|1', rowSpan: '1|1', field: 'qc_qty', hAlign: 2, width: 60, type: 'Number',},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'qc_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '|不计价', colSpan: '|1', rowSpan: '|1', field: 'qc_minus_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '本期补差|原单价', colSpan: '2|1', rowSpan: '1|1', field: 'org_price', hAlign: 2, width: 60, type: 'Number', readOnly: true },
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'pc_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '本期完成计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'gather_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gather_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
-            {title: '本期补差|原单价', colSpan: '2|1', rowSpan: '1|1', field: 'org_price', hAlign: 2, width: 60, type: 'Number' },
-            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'pc_tp', hAlign: 2, width: 60, type: 'Number'},
             {title: '截止本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_contract_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '截止本期数量变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_qc_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
@@ -451,10 +455,10 @@ const stageGather = {
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'contract_tp', hAlign: 2, width: 60, type: 'Number'},
             {title: '本期数量变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'qc_qty', hAlign: 2, width: 60, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'qc_tp', hAlign: 2, width: 60, type: 'Number'},
-            {title: '本期完成计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'gather_qty', hAlign: 2, width: 60, type: 'Number'},
-            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gather_tp', hAlign: 2, width: 60, type: 'Number'},
             { title: '本期补差|原单价', colSpan: '2|1', rowSpan: '1|1', field: 'org_price', hAlign: 2, width: 60, type: 'Number' },
             { title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'pc_tp', hAlign: 2, width: 60, type: 'Number' },
+            {title: '本期完成计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'gather_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gather_tp', hAlign: 2, width: 60, type: 'Number'},
             {title: '截止本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_contract_tp', hAlign: 2, width: 60, type: 'Number'},
             {title: '截止本期数量变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_qc_qty', hAlign: 2, width: 60, type: 'Number'},

+ 14 - 0
app/const/standard.js

@@ -16,9 +16,13 @@ const nodeType = [
     {text: '土地拆迁补偿', value: 4},
     {text: '预备费', value: 5},
     {text: '暂列金额', value: 6},
+    {text: '尾工工程', value: 18},
     {text: '计日工', value: 7},
+    {text: '材料调差', value: 15},
     {text: '价差调整', value: 8},
     {text: '索赔', value: 9},
+    {text: '零星工程', value: 16},
+    {text: '报废工程', value: 17},
     {text: '新增费用', value: 10},
     {text: '其他费用', value: 11},
     {text: '回收金额', value: 12},
@@ -32,10 +36,20 @@ const jrg = nodeType.find(x => {
 const zlj = nodeType.find(x => {
     return x.text === '暂列金额';
 });
+const jafTypeName = ['', '计日工', '材料调差', '价差调整', '索赔', '零星工程', '保费工程'];
+const jafType = nodeType.filter(x => {
+    return jafTypeName.indexOf(x.text) >= 0;
+});
+const zljTypeName = ['', '暂列金额', '尾工工程'];
+const zljType = nodeType.filter(x => {
+    return zljTypeName.indexOf(x.text) >= 0;
+});
 
 
 module.exports = {
     nodeType,
+    jafType,
+    zljType,
     jrg,
     zlj,
 };

+ 7 - 0
app/const/tender_info.js

@@ -31,6 +31,7 @@ const defaultInfo = {
             company: '',
             corporation: '',
             date: '',
+            management: '',
         },
         contract2: {
             company: '',
@@ -47,6 +48,11 @@ const defaultInfo = {
             corporation: '',
             date: '',
         },
+        detect: {
+            company: '',
+            corporation: '',
+            date: '',
+        },
     },
     // 技术参数
     tech_param: {
@@ -166,6 +172,7 @@ const defaultInfo = {
         same_code: true,
         sibling: true,
         over: true,
+        banMinusChangeBills: false,
     },
     fun_rela: {
         hintOver: true,

+ 21 - 4
app/controller/change_controller.js

@@ -714,6 +714,7 @@ module.exports = app => {
                                 cl['audit_amount_' + au.uid] = audit_amount[index - 1] ? audit_amount[index - 1] : null;
                             }
                         }
+                        cl.changed_amount = (change.status === audit.flow.status.backnew || change.status === audit.flow.status.checking || change.status === audit.flow.status.checked) && audit_amount !== '' ? audit_amount[audit_amount.length - 1] : cl.camount;
                     }
                     renderData.changeList = changeList;
                     renderData.auditList2 = auditList2;
@@ -763,6 +764,7 @@ module.exports = app => {
                                 cl['audit_amount_' + au.uid] = au.uid === ctx.session.sessionUser.accountId ? cl.spamount : (audit_amount[index - 1] ? audit_amount[index - 1] : null);
                             }
                         }
+                        cl.changed_amount = (change.status === audit.flow.status.backnew || change.status === audit.flow.status.checking || change.status === audit.flow.status.checked) && audit_amount !== '' ? audit_amount[audit_amount.length - 1] : cl.camount;
                     }
                     renderData.changeList = changeList;
                     renderData.changeLedgerList = await ctx.service.changeLedger.getAllDataByCondition({ where: { tender_id: ctx.tender.id } });
@@ -772,10 +774,25 @@ module.exports = app => {
 
                 // 获取是否已存在调用变更令
                 let changeUsedData = await ctx.service.stageChange.getFinalUsedData(ctx.tender.id, change.cid);
-                changeUsedData = ctx.helper._.filter(changeUsedData, function(item) {
+                changeUsedData = ctx.helper._.orderBy(ctx.helper._.filter(changeUsedData, function(item) {
                     return item.qty !== null;
-                })
-                renderData.changeUsedData = changeUsedData;
+                }), ['sorder'], ['desc']);
+                const useChangeUsedData = [];
+                if (changeUsedData.length > 0) { // 防止未创建期时调用
+                    // const stage = await this.ctx.service.stage.getLastestStage(ctx.tender.id, true);
+                    for (const cu of changeUsedData) {
+                        // if (cu.sorder !== 0 ||
+                        //     (cu.sorder === 0 && !(stage.status === audit.stage.status.uncheck || stage.status === audit.stage.status.checkNo))) {
+                        const index = ctx.helper._.findIndex(useChangeUsedData, { cbid: cu.cbid });
+                        if (index !== -1) {
+                            useChangeUsedData[index].qty = ctx.helper.add(useChangeUsedData[index].qty, cu.qty);
+                        } else {
+                            useChangeUsedData.push(cu);
+                        }
+                        // }
+                    }
+                }
+                renderData.changeUsedData = useChangeUsedData;
                 renderData.stageChangeNum = this.ctx.helper.sum(changeUsedData.map(x => { return Math.abs(x.qty); }));
                 await this.layout('change/information.ejs', renderData, 'change/information_modal.ejs');
             } catch (err) {
@@ -854,7 +871,7 @@ module.exports = app => {
                         responseData.data = changeList;
                         break;
                     case 'update_tp':
-                        await ctx.service.change.saveInfo({ total_price: data.updateData });
+                        await ctx.service.change.saveInfo(data.updateData);
                         break;
                     case 'order_by':
                         const result = await ctx.service.change.saveOrderBy(data.updateData, data.newLedgerList);

+ 1 - 1
app/controller/datacollect_controller.js

@@ -126,7 +126,7 @@ module.exports = app => {
                         t.total_price = sum.total_price;
                         t.deal_tp = sum.deal_tp;
                     }
-                    t.change_tp = await ctx.service.change.getChangeTp(t.id);
+                    [t.change_tp, t.change_p_tp, t.change_n_tp] = await ctx.service.change.getChangeTp(t.id);
                     // t.material_tp = await ctx.service.material.getSumMaterial(t.id);
                     // 获取本标段 本月计量期审批通过数目,变更令审批通过数目,台账修订通过数目,材料调差通过数目
                     t.month_stage_num = await ctx.service.stageAudit.getNumByMonth(t.id, startMonth, endMonth);

+ 2 - 0
app/controller/ledger_audit_controller.js

@@ -11,6 +11,7 @@ const auditConst = require('../const/audit').ledger;
 const shenpiConst = require('../const/shenpi');
 const measureType = require('../const/tender').measureType;
 const spreadSetting = require('../lib/spread_setting');
+const stdConst = require('../const/standard');
 
 module.exports = app => {
     class LedgerAuditController extends app.BaseController {
@@ -75,6 +76,7 @@ module.exports = app => {
                 renderData.posSpreadSetting = JSON.stringify(posSpread);
                 renderData.readOnly = true;
                 renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.ledger.audit);
+                renderData.nodeType = stdConst.nodeType;
                 await this.layout('ledger/audit.ejs', renderData, 'ledger/audit_modal.ejs');
             } catch (err) {
                 this.log(err);

+ 4 - 1
app/controller/ledger_controller.js

@@ -130,6 +130,7 @@ module.exports = app => {
 
                 const whiteList = this.ctx.app.config.multipart.whitelist;
                 const categoryData = await this.ctx.service.category.getAllCategory(this.ctx.session.sessionProject.id);
+                const syncLedger = await this.ctx.service.specPull.syncLedger(this.ctx.session.sessionProject.id);
                 const renderData = {
                     tender: tender.data,
                     tenderInfo: tender.info,
@@ -151,6 +152,8 @@ module.exports = app => {
                     dealBillsPermission: this._canUpdateDealBills(tender.data, auditors.filter(x => {return x.audit_order > 0})),
                     shenpiConst,
                     categoryData,
+                    syncLedgerUrl: syncLedger ? `${ctx.app.config.url3f}/${syncLedger.pull_class}/sync-tz/${tender.id}` : '',
+                    nodeType: stdConst.nodeType,
                 };
                 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;
@@ -231,7 +234,7 @@ module.exports = app => {
          * 复制粘贴整块
          *
          * @param ctx
-         * @return {Promise<void>}
+         * @return {Promise<void>
          */
         async _pasteBlock(ctx, data) {
             if ((isNaN(data.id) || data.id <= 0) ||

+ 30 - 4
app/controller/login_controller.js

@@ -26,6 +26,18 @@ module.exports = app => {
                 ctx.redirect('/wap');
                 return;
             }
+            let projectData = null;
+
+            // 判断是否有编号且存在
+            if (ctx.params.code) {
+                const projectInfo = await ctx.service.project.getProjectByCode(ctx.params.code.toString().trim());
+                if (projectInfo) {
+                    projectData = projectInfo;
+                } else {
+                    ctx.redirect('/');
+                    return;
+                }
+            }
 
             const errorMessage = ctx.session.loginError;
             // 显示完删除
@@ -37,6 +49,7 @@ module.exports = app => {
                 await ctx.service.maintain.syncMaintainData();
             }
             const renderData = {
+                projectData,
                 maintainData,
                 maintainConst,
                 errorMessage,
@@ -178,7 +191,11 @@ module.exports = app => {
             } catch (error) {
                 this.log(error);
                 ctx.session.loginError = error;
+                // if (ctx.request.body && ctx.request.body.project && parseInt(ctx.request.body.hide_code)) {
+                //     ctx.redirect('/login/' + ctx.request.body.project);
+                // } else {
                 ctx.redirect('/login');
+                // }
             }
         }
 
@@ -190,8 +207,13 @@ module.exports = app => {
          */
         async logout(ctx) {
             // 删除session并跳转
+            const code = ctx.session.sessionProject.code ? ctx.session.sessionProject.code : null;
             ctx.session = null;
-            ctx.redirect('/');
+            if (code) {
+                ctx.redirect('/login/' + code);
+            } else {
+                ctx.redirect('/');
+            }
         }
 
         /**
@@ -364,7 +386,11 @@ module.exports = app => {
             } catch (error) {
                 this.log(error);
                 ctx.session.loginError = error;
-                ctx.redirect('/login');
+                if (ctx.request.body && ctx.request.body.code) {
+                    ctx.redirect('/login/' + ctx.request.body.code);
+                } else {
+                    ctx.redirect('/login');
+                }
             }
         }
 
@@ -482,7 +508,7 @@ module.exports = app => {
           }
           ctx.body = response;
         }
-        
+
         /**
          * 项目管理授权页面
          *
@@ -558,7 +584,7 @@ module.exports = app => {
           }
           ctx.body = response
         }
-      
+
     }
 
     return LoginController;

+ 26 - 5
app/controller/material_controller.js

@@ -377,8 +377,8 @@ module.exports = app => {
         async info(ctx) {
             try {
                 await this._getMaterialAuditViewData(ctx);
-                const renderData = await this._getDefaultRenderData(ctx);
                 await this._setEditTaxPermission(ctx);
+                const renderData = await this._getDefaultRenderData(ctx);
                 renderData.materialBillsData = await this._getMaterialBillsData(ctx);
                 // 取对应期的截取上期的调差金额和应耗数量
                 if (ctx.material.highOrder !== ctx.material.order) {
@@ -442,8 +442,8 @@ module.exports = app => {
         async list(ctx) {
             try {
                 await this._getMaterialAuditViewData(ctx);
-                const renderData = await this._getDefaultRenderData(ctx);
                 await this._setEditListPermission(ctx);
+                const renderData = await this._getDefaultRenderData(ctx);
                 // 根据期判断需要获取的工料信息值表
                 const searchsql = { tid: ctx.tender.id };
                 let midList = [];
@@ -506,18 +506,36 @@ module.exports = app => {
                 responseData.data.pos = await ctx.service.pos.getPosData({ tid: ctx.tender.id });
                 // 获取所选期数据并合并相加同类清单项
                 if (ctx.material.is_stage_self && data.sid) {
-                    responseData.data.curLedgerData = await ctx.service.stageBills.getStagesData(ctx.tender.id, data.sid.toString());
+                    const curLedgerPcData = await ctx.service.stageBillsPc.getStagesData(ctx.tender.id, data.sid.toString());
+                    const curLedgerData = await ctx.service.stageBills.getStagesData(ctx.tender.id, data.sid.toString());
+                    for (const cl of curLedgerData) {
+                        const clpc = ctx.helper._.find(curLedgerPcData, { lid: cl.lid });
+                        ctx.helper._.assign(cl, clpc);
+                    }
+                    responseData.data.curLedgerData = curLedgerData;
                     responseData.data.curPosData = await ctx.service.stagePos.getStagesData(ctx.tender.id, data.sid.toString(), 'list');
                     const ledgerListData = [];
                     const posListData = [];
                     for (const s of ctx.material.stage_id.split(',')) {
-                        ledgerListData.push(await ctx.service.stageBills.getStagesData(ctx.tender.id, s.toString()));
+                        const curLedgerPcData = await ctx.service.stageBillsPc.getStagesData(ctx.tender.id, s.toString());
+                        const curLedgerData = await ctx.service.stageBills.getStagesData(ctx.tender.id, s.toString());
+                        for (const cl of curLedgerData) {
+                            const clpc = ctx.helper._.find(curLedgerPcData, { lid: cl.lid });
+                            ctx.helper._.assign(cl, clpc);
+                        }
+                        ledgerListData.push(curLedgerData);
                         posListData.push(await ctx.service.stagePos.getStagesData(ctx.tender.id, s.toString(), 'list'));
                     }
                     responseData.data.ledgerListData = ledgerListData;
                     responseData.data.posListData = posListData;
                 } else {
-                    responseData.data.curLedgerData = await ctx.service.stageBills.getStagesData(ctx.tender.id, ctx.material.stage_id);
+                    const curLedgerPcData = await ctx.service.stageBillsPc.getStagesData(ctx.tender.id, ctx.material.stage_id);
+                    const curLedgerData = await ctx.service.stageBills.getStagesData(ctx.tender.id, ctx.material.stage_id);
+                    for (const cl of curLedgerData) {
+                        const clpc = ctx.helper._.find(curLedgerPcData, { lid: cl.lid });
+                        ctx.helper._.assign(cl, clpc);
+                    }
+                    responseData.data.curLedgerData = curLedgerData;
                     responseData.data.curPosData = await ctx.service.stagePos.getStagesData(ctx.tender.id, ctx.material.stage_id, 'list');
                 }
                 // 获取清单设置已选清单
@@ -1551,6 +1569,9 @@ module.exports = app => {
                     case 'resetChecklist':
                         responseData.data = await ctx.service.materialChecklist.resetData(data.pushData, data.removeData, data.updateData);
                         break;
+                    case 'exportCB':
+                        responseData.data = await ctx.service.materialChecklist.addExportCB(data.addChecklist, data.addBillsList);
+                        break;
                     default: throw '参数有误';
                 }
 

+ 2 - 0
app/controller/measure_controller.js

@@ -46,6 +46,7 @@ module.exports = app => {
                     auditConst2: JSON.stringify(auditConst),
                 };
                 renderData.stages = await ctx.service.stage.getValidStages(ctx.tender.id);
+                if (renderData.stages.length > 0) await this.ctx.service.stage.checkStageGatherData(renderData.stages[0], this.ctx.session.sessionUser.is_admin);
                 for (const s of renderData.stages) {
                     // s.curAuditor = null;
                     // 根据期状态返回展示用户
@@ -246,6 +247,7 @@ module.exports = app => {
                     result.main = {};
                     result.main.ledger = await ctx.service.ledger.getData(ctx.tender.id);
                     result.main.pos = await ctx.service.pos.getPosData({ tid: ctx.tender.id });
+                    result.main.deal = await ctx.service.dealBills.getAllDataByCondition({ where: { tender_id: ctx.tender.id } });
                 }
                 if (data.stages) {
                     for (const order of data.stages) {

+ 61 - 2
app/controller/profile_controller.js

@@ -364,10 +364,16 @@ module.exports = app => {
                     if (!accountData.stamp_path) {
                         throw '不存在签章';
                     }
+                    const stamp_path_list = accountData.stamp_path.split('!;!');
+                    const spIndex = ctx.helper._.indexOf(stamp_path_list, data.src);
+                    if (spIndex === -1) {
+                        throw '不存在此签章';
+                    }
                     // 删除oss文件
-                    await ctx.app.fujianOss.delete(ctx.app.config.fujianOssFolder + accountData.stamp_path);
+                    await ctx.app.fujianOss.delete(ctx.app.config.fujianOssFolder + stamp_path_list[spIndex]);
+                    stamp_path_list.splice(spIndex, 1);
                     // 删除库
-                    result = await ctx.service.projectAccount.update({ stamp_path: null }, { id: sessionUser.accountId });
+                    result = await ctx.service.projectAccount.update({ stamp_path: stamp_path_list.length === 0 ? null : stamp_path_list.join('!;!') }, { id: sessionUser.accountId });
                 } else {
                     if (accountData.sign_path === '') {
                         throw '不存在签名';
@@ -462,6 +468,59 @@ module.exports = app => {
         }
 
         /**
+         * 上传签章图(多选)
+         *
+         * @param {object} ctx - egg全局变量
+         * @return {void}
+         */
+        async stampUpload(ctx) {
+            const responseData = {
+                err: 0, msg: '', data: null,
+            };
+            let stream;
+            try {
+                const parts = ctx.multipart({ autoFields: true });
+                const paths = [];
+                let index = 0;
+                while ((stream = await parts()) !== undefined) {
+                    // 判断用户是否选择上传文件
+                    if (!stream.filename) {
+                        throw '请选择上传的文件!';
+                    }
+                    const fileInfo = path.parse(stream.filename);
+                    const create_time = Date.parse(new Date()) / 1000;
+                    const filepath = `app/public/upload/sign/profile/qianzhang_${create_time + index.toString() + fileInfo.ext}`;
+                    // await ctx.helper.saveStreamFile(stream, path.resolve(this.app.baseDir, filepath));
+                    await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + filepath, stream);
+                    if (stream) {
+                        await sendToWormhole(stream);
+                    }
+                    paths.push(filepath);
+                    ++index;
+                }
+                // 获取账号数据
+                const accountData = await ctx.service.projectAccount.getDataByCondition({ id: ctx.session.sessionUser.accountId });
+                const stamp_path = accountData.stamp_path ? accountData.stamp_path.split('!;!') : [];
+                const stamp_path_list = ctx.helper._.concat(stamp_path, paths);
+                const result = await ctx.service.projectAccount.update({ stamp_path: stamp_path_list.join('!;!') }, { id: ctx.session.sessionUser.accountId });
+                if (result) {
+                    responseData.data = { stamp_path: stamp_path_list };
+                } else {
+                    throw '添加数据库失败';
+                }
+            } catch (err) {
+                this.log(err);
+                // 失败需要消耗掉stream 以防卡死
+                if (stream) {
+                    await sendToWormhole(stream);
+                }
+                responseData.err = 1;
+                responseData.msg = err.toString();
+            }
+            ctx.body = responseData;
+        }
+
+        /**
          * 账号安全
          *
          * @param {object} ctx - egg全局变量

+ 24 - 0
app/controller/report_archive_controller.js

@@ -16,6 +16,7 @@ const signConst = require('../const/sign');
 const shenpiConst = require('../const/shenpi');
 const accountGroup = require('../const/account_group').group;
 const sendToWormhole = require('stream-wormhole');
+const pushOperate = require('../const/spec_3f').pushOperate;
 
 module.exports = app => {
     class ReportArchiveController extends app.BaseController {
@@ -116,6 +117,7 @@ module.exports = app => {
                 const groupList = accountList.filter(item => item.account_group === idx);
                 return { groupName: item, groupList };
             });
+            const needFileMsg = await this.ctx.service.specMsg.tenderNeedMsg(this.ctx.session.sessionProject.id, this.ctx.tender.id, pushOperate.report.file);
 
             //
             const renderData = {
@@ -144,6 +146,7 @@ module.exports = app => {
                 accountGroup: newAccountGroup,
                 accountList,
                 isAdmin,
+                needFileMsg,
             };
             if (stage_id === -1) {
                 await this.layout('report/index_archive.ejs', renderData, 'report/archive_popup.ejs');
@@ -787,6 +790,27 @@ module.exports = app => {
             }
             return response;
         }
+
+        async sendFileMsg(ctx) {
+            try {
+                if (this.ctx.session.sessionUser.accountId !== ctx.tender.data.user_id) throw '您无权操作';
+
+                const needFileMsg = await this.ctx.service.specMsg.tenderNeedMsg(this.ctx.session.sessionProject.id, this.ctx.tender.id, pushOperate.report.file);
+                if (!needFileMsg) throw '该标段暂不可进行该操作';
+
+                const waitingMsg = await this.ctx.service.specMsg.getDataByCondition({ tid: this.ctx.tender.id, timing: pushOperate.report.file, status: [0, 1] });
+                if (waitingMsg) throw '上一次归档完成,未执行完毕,请稍后再试';
+
+                const data = JSON.parse(ctx.request.body.data);
+                const stage = await this.ctx.service.stage.getDataById(data.sid);
+
+                await this.ctx.service.specMsg.addReportMsg(null, this.ctx.session.sessionProject.id, this.ctx.tender.data, stage, pushOperate.report.file);
+                ctx.body = { err: 0, msg: '提交成功,稍后将同步至档案系统', data: null };
+            } catch(err) {
+                this.ctx.log(err);
+                this.ctx.ajaxErrorBody(err, '操作失败');
+            }
+        }
     }
     return ReportArchiveController;
 };

+ 25 - 2
app/controller/revise_controller.js

@@ -286,6 +286,7 @@ module.exports = app => {
                 user,
                 auditHistory,
                 shenpiConst,
+                nodeType: stdConst.nodeType,
             };
         }
 
@@ -442,7 +443,7 @@ module.exports = app => {
                 reviseCheck.checkBillsTp([
                     {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);
+                ], this.ctx.tender.info.decimal, x => { return !x.check_calc; });
                 ctx.body = { err: 0, msg: '', data: reviseCheck.checkResult };
             } catch (err) {
                 this.log(err);
@@ -483,6 +484,7 @@ module.exports = app => {
                     auditConst: audit.revise,
                     user,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.revise.history),
+                    nodeType: stdConst.nodeType,
                 };
                 await this.layout('revise/history.ejs', renderData, 'revise/history_modal.ejs');
             } catch (err) {
@@ -916,6 +918,25 @@ module.exports = app => {
             return curStageData;
         }
 
+        async _loadChange(ctx) {
+            const change = await ctx.service.change.getAllDataByCondition({
+                columns: [ 'cid', 'code', 'name', 'w_code' ],
+                where: { tid: ctx.tender.id }
+            });
+            const changeBills = await ctx.service.changeAuditList.getAllDataByCondition({
+                columns: [ 'cid', 'code', 'name', 'unit', 'unit_price' ],
+                where: { tid: ctx.tender.id }
+            });
+            const changeIndex = {};
+            change.forEach(x => { changeIndex[x.cid] = x; x.bills = [] });
+            changeBills.forEach(cb => {
+                const c = changeIndex[cb.cid];
+                if (!c) return;
+                c.bills.push(cb);
+            });
+            return change;
+        }
+
         async _loadDataByFilter(ctx, filter) {
             switch(filter) {
                 case 'bills':
@@ -950,6 +971,8 @@ module.exports = app => {
                     return await this.ctx.service.ledgerTag.getDatas(ctx.tender.id);
                 case 'price':
                     return await this.ctx.service.revisePrice.getAllDataByCondition({ where: { rid: ctx.revise.id } });
+                case 'change':
+                    return await this._loadChange(ctx);
                 default: throw '请求的数据不存在';
             }
         }
@@ -995,7 +1018,7 @@ module.exports = app => {
                 revise: ctx.revise,
                 jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.revise.price),
             };
-            await this.layout('revise/price.ejs', renderData);
+            await this.layout('revise/price.ejs', renderData, 'revise/price_modal.ejs');
         }
 
         async priceUpdate(ctx) {

+ 9 - 0
app/controller/schedule_controller.js

@@ -284,9 +284,11 @@ module.exports = app => {
             } else {
                 preStageData = [];
             }
+            const pcData = await ctx.service.stageBillsPc.getAllDataByCondition({ where: { sid: stageInfo.id } });
             this.ctx.helper.assignRelaData(ledgerData, [
                 { data: curStageData, fields: ['contract_qty', 'contract_expr', 'contract_tp', 'qc_qty', 'qc_tp', 'postil'], prefix: '', relaId: 'lid' },
                 { data: preStageData, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'used'], prefix: 'pre_', relaId: 'lid' },
+                { data: pcData, fields: ['contract_pc_tp', 'qc_pc_tp', 'pc_tp', 'org_price'], prefix: '', relaId: 'lid' },
             ]);
             // 获取进度最新期的数据
             ledgerData = await this._addFinalStageData(ctx, ledgerData);
@@ -313,9 +315,11 @@ module.exports = app => {
                 } else {
                     prefinalStageData = [];
                 }
+                const finalPcData = await ctx.service.stageBillsPc.getAllDataByCondition({ where: { sid: finalStageInfo.id } });
                 this.ctx.helper.assignRelaData(ledgerData, [
                     { data: finalStageData, fields: ['contract_qty', 'contract_expr', 'contract_tp', 'qc_qty', 'qc_tp', 'postil'], prefix: 'final_', relaId: 'lid' },
                     { data: prefinalStageData, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'used'], prefix: 'pre_final_', relaId: 'lid' },
+                    { data: finalPcData, fields: ['contract_pc_tp', 'qc_pc_tp', 'pc_tp', 'org_price'], prefix: 'final_', relaId: 'lid' },
                 ]);
             }
             return ledgerData;
@@ -368,6 +372,11 @@ module.exports = app => {
                 // }), 'id');
                 const stageIdList = _.map(stageList, 'id');
                 curYearStageData = await ctx.service.stageBills.getStagesData(tender.id, stageIdList.join(','));
+                const curYearStagePcData = await ctx.service.stageBillsPc.getStagesData(tender.id, stageIdList.join(','));
+                for (const cys of curYearStageData) {
+                    const cyspc = ctx.helper._.find(curYearStagePcData, { lid: cys.lid });
+                    if (cyspc) ctx.helper._.assign(cys, cyspc);
+                }
             }
             return { slmList, nextSlmList, endSlmList, yearSlmList, curYearStageData, scheduleMonth, stageOrderList, scheduleStage, curScheduleStage };
         }

+ 13 - 1
app/controller/setting_controller.js

@@ -19,6 +19,7 @@ const S2b = require('../lib/s2b');
 const measureType = require('../const/tender').measureType;
 const sendToWormhole = require('stream-wormhole');
 const path = require('path');
+const funSet = require('../const/fun_set');
 
 module.exports = app => {
 
@@ -915,11 +916,15 @@ module.exports = app => {
                 if (ctx.session.sessionUser.is_admin === 0) {
                     throw '没有访问权限';
                 }
+                const fun_set = await ctx.service.project.getFunSet(projectData.fun_set);
+                console.log(fun_set);
                 await this.layout('setting/fun.ejs', {
                     projectData,
                     funRela,
                     imType,
-                })
+                    endMonth: funSet.endMonth,
+                    funSet: fun_set,
+                });
             } catch (error) {
                 ctx.helper.log(error);
                 ctx.redirect('/dashboard');
@@ -956,8 +961,15 @@ module.exports = app => {
                 this.ctx.session.sessionProject.page_show.openMaterialChecklist = data.openMaterialChecklist ? 1 : 0;
                 this.ctx.session.sessionProject.page_show.openMaterialSelf = data.openMaterialSelf ? 1 : 0;
                 this.ctx.session.sessionProject.page_show.openMaterialEditForAudit = data.openMaterialEditForAudit ? 1 : 0;
+                this.ctx.session.sessionProject.page_show.openStageStart = data.openStageStart ? 1 : 0;
                 const result2 = await ctx.service.project.updatePageshow(projectId);
                 if (!result2) throw '保存数据失败';
+                if (data.addFunSet) {
+                    const funSet = projectData.fun_set ? JSON.parse(projectData.fun_set) : {};
+                    ctx.helper._.defaultsDeep(data.addFunSet, funSet);
+                    const result3 = await ctx.service.project.updateFunSet(projectId, data.addFunSet);
+                    if (!result3) throw '保存数据失败';
+                }
 
                 ctx.body = { err: 0, msg: '', data: null };
             } catch (error) {

+ 97 - 11
app/controller/stage_controller.js

@@ -26,6 +26,7 @@ const fs = require('fs');
 const stdConst = require('../const/standard');
 const LzString = require('lz-string');
 const spreadSetting = require('../lib/spread_setting');
+const funSetConst = require('../const/fun_set');
 
 module.exports = app => {
     class StageController extends app.BaseController {
@@ -108,6 +109,46 @@ module.exports = app => {
             if (status === auditConst.status.uncheck || status === auditConst.status.checkNo) {
                 ctx.stage.auditorList = await ctx.service.stageAudit.getAuditors(ctx.stage.id, ctx.stage.times);
             }
+            await this._checkStageStart(ctx);
+        }
+
+        async _checkStageStart(ctx) {
+            // 上报日期获取,及上报权限开关
+            if (ctx.session.sessionProject.page_show.openStageStart) {
+                const stageStart = ctx.session.sessionProject.funSet.stage_start;
+                const thisYear = moment().year();
+                // const thisYear = 2021;
+                const thisMonth = moment().month();
+                // const thisMonth = 11;
+                const thisDate = new Date();
+                let startEndDay = stageStart.end_day + '号';
+                let startDay = moment([thisYear, thisMonth, stageStart.start_day]).isValid() ? [thisYear, thisMonth, stageStart.start_day] : [thisYear, thisMonth + 1, 1];
+                const maxDay = moment(thisDate).format('YYYY-MM-DD') >= moment(startDay).format('YYYY-MM-DD');
+                startEndDay = maxDay && stageStart.end_month === funSetConst.endMonth[1].val ? funSetConst.endMonth[stageStart.end_month - 1].name + startEndDay : startEndDay;
+                if (moment(thisDate).format('YYYY-MM-DD') < moment(startDay).format('YYYY-MM-DD') && stageStart.end_month === funSetConst.endMonth[1].val) {
+                    startDay = thisMonth !== 0 && moment([thisYear, thisMonth - 1, stageStart.start_day]).isValid() ? [thisYear, thisMonth - 1, stageStart.start_day] : (thisMonth !== 0 ? [thisYear, thisMonth - 1, 1] : [thisYear - 1, 11, stageStart.start_day]);
+                }
+                ctx.stage.startEndDay = startEndDay; // 上报截止时间
+                let endDay = [thisYear, thisMonth, stageStart.end_day];
+                if (stageStart.end_month === funSetConst.endMonth[0].val && !moment(endDay).isValid()) {
+                    endDay = thisMonth === 11 ? [thisYear + 1, 0, 1] : [thisYear, thisMonth + 1, 1];
+                    endDay = moment(endDay).subtract(1, 'days');
+                } else if (stageStart.end_month === funSetConst.endMonth[1].val && maxDay) {
+                    if (!moment(endDay).isValid()) {
+                        endDay = [thisYear, thisMonth + 1, 1];
+                        endDay = moment(endDay).subtract(1, 'days');
+                    } else {
+                        endDay = thisMonth === 11 ? [thisYear + 1, 0, stageStart.end_day] : [thisYear, thisMonth + 1, stageStart.end_day];
+                    }
+                }
+                if (!moment(endDay).isValid()) {
+                    endDay = [thisYear, thisMonth + 1, 1];
+                    endDay = moment(endDay).subtract(1, 'days');
+                }
+                // console.log(moment(thisDate).format('YYYY-MM-DD'), moment(startDay).format('YYYY-MM-DD'), moment(endDay).format('YYYY-MM-DD'));
+                const startPermission = moment(moment(thisDate).format('YYYY-MM-DD')).isBetween(moment(startDay).format('YYYY-MM-DD'), moment(endDay).format('YYYY-MM-DD'), null, '[]');
+                ctx.stage.startPermission = startPermission;
+            }
         }
 
         _checkStageCanModify(ctx) {
@@ -118,6 +159,9 @@ module.exports = app => {
             if (ctx.stage.revising) {
                 throw '台账修订中,请勿修改提交期数据';
             }
+            if (ctx.stage.assist && ctx.stage.assist.confirm) {
+                throw '协同数据已确认,如需修改,请撤销上报或重新审批';
+            }
         }
 
         /**
@@ -145,7 +189,6 @@ module.exports = app => {
                     attData[index].in_time = moment(attData[index].in_time * 1000).format('YYYY-MM-DD');
                 }
                 renderData.attData = attData;
-                renderData.coopwd = ((ctx.stage.status === auditConst.status.uncheck || ctx.stage.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ctx.stage.user_id) || ((ctx.stage.status === auditConst.status.checking || ctx.stage.status === auditConst.status.checkNoPre) && ctx.stage.curAuditor && ctx.stage.curAuditor.aid === ctx.session.sessionUser.accountId);
                 // 获取收方单列表
                 const sfData = await ctx.service.stageShoufang.getAllDataByCondition({ where: { sid: ctx.stage.id }, orders: [['id', 'desc']] });
                 for (const sf of sfData) {
@@ -156,6 +199,7 @@ module.exports = app => {
                 // 收方单附件删除权限
                 renderData.sfAttDelPower = ctx.session.sessionUser.accountId === ctx.stage.user_id || ctx.helper._.findIndex(ctx.stage.auditors2, { aid: ctx.session.sessionUser.accountId }) !== -1;
                 renderData.hintOver = projectFunInfo.hintOver && ctx.tender.info.fun_rela.hintOver;
+                renderData.hintMinusCb = projectFunInfo.banMinusChangeBills && ctx.tender.info.ledger_check.banMinusChangeBills;
                 renderData.categoryData = await this.ctx.service.category.getAllCategory(this.ctx.session.sessionProject.id);
                 await this.layout('stage/index.ejs', renderData, 'stage/modal.ejs');
             } catch (err) {
@@ -303,6 +347,7 @@ module.exports = app => {
                             } else {
                                 responseData.data.ledgerData = await this._getStageLedgerData(ctx);
                             }
+                            responseData.data.locked = await ctx.service.stageAuditAss.getLockedId(ctx.stage);
                             break;
                         case 'pos':
                             if (hpack) {
@@ -334,16 +379,8 @@ module.exports = app => {
                             responseData.data.tags = await ctx.service.ledgerTag.getDatas(ctx.tender.id, ctx.stage.id);
                             break;
                         case 'cooperation':
-                            const uid = ctx.stage.curAuditor ?
-                                ctx.stage.curAuditor.aid :
-                                (ctx.stage.status === auditConst.status.uncheck ||
-                                (ctx.stage.status === auditConst.status.checkNo && ctx.stage.user_id === ctx.session.sessionUser.accountId) ?
-                                    ctx.stage.user_id : (ctx.stage.status === auditConst.status.checkNo && ctx.stage.user_id !== ctx.session.sessionUser.accountId) ?
-                                        ctx.session.sessionUser.accountId : null);
-                            responseData.data.cooperation = uid !== null ? await ctx.service.ledgerCooperation.getValidData(
-                                ctx.tender.id, uid) : [];
-                            const stageTimes = (ctx.stage.status === auditConst.status.checkNo && ctx.stage.user_id !== ctx.session.sessionUser.accountId) ? ctx.stage.times - 1 : ctx.stage.times;
-                            responseData.data.cooperationConfirm = uid !== null ? await ctx.service.cooperationConfirm.getValidData(ctx.tender.id, ctx.stage.id, stageTimes, uid) : [];
+                            responseData.data.cooperation = await ctx.service.ledgerCooperation.getValidData(ctx.tender.id, ctx.session.sessionUser.accountId);
+                            responseData.data.cooperationConfirm = await ctx.service.cooperationConfirm.getValidData(ctx.tender.id, ctx.stage.id, ctx.stage.times, ctx.session.sessionUser.accountId);
                             break;
                         case 'spec':
                             const spec = {zlj: JSON.parse(JSON.stringify(stdConst.zlj)), jrg: stdConst.jrg};
@@ -388,6 +425,15 @@ module.exports = app => {
                     { qty: 'contract_qty', tp: 'contract_tp' }, { qty: 'qc_qty', tp: 'qc_tp' },
                 ], this.ctx.tender.info.decimal, x => { return x.is_tp; });
                 checkData.checkBillsQty(['contract_qty', 'qc_qty']);
+                if (projRela.banMinusChangeBills && ctx.tender.info.ledger_check.banMinusChangeBills) {
+                    const change = await this.ctx.service.change.getAllChangeHasMinus(ctx.tender.id);
+                    if (change.length > 0) {
+                        const cid = change.map(x => { return x.cid; });
+                        const changeBills = await this.ctx.service.changeAuditList.getAllDataByCondition({ where: { cid } });
+                        const endStageChange = await this.ctx.service.stageChange.getFinalUsedData(ctx.tender.id, cid);
+                        checkData.checkMinusChangeBills(change, changeBills, endStageChange);
+                    }
+                }
                 ctx.body = { err: 0, msg: '', data: checkData.checkResult };
             } catch (err) {
                 this.log(err);
@@ -395,6 +441,38 @@ module.exports = app => {
             }
         }
 
+        async _checkMinusChangeBills(ctx) {
+            const checkDataModel = require('../lib/ledger').checkData;
+            const checkData = new checkDataModel(ctx, measureType);
+            const change = await this.ctx.service.change.getAllChangeHasMinus(ctx.tender.id);
+            if (change.length > 0) {
+                const cid = change.map(x => { return x.cid; });
+                const changeBills = await this.ctx.service.changeAuditList.getAllDataByCondition({ where: { cid } });
+                const endStageChange = await this.ctx.service.stageChange.getFinalUsedData(ctx.tender.id, cid);
+                checkData.checkMinusChangeBills(change, changeBills, endStageChange);
+            }
+            return checkData.checkResult.error;
+        }
+
+        async stageCheck(ctx) {
+            try {
+                if (!ctx.query.type) throw '参数错误';
+                const type = ctx.query.type.split(',');
+                const result = {};
+                for (const t of type) {
+                    switch (t) {
+                        case 'minus_cb':
+                            result.minus_cb = await this._checkMinusChangeBills(ctx);
+                            break;
+                    }
+                }
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '检查数据错误');
+            }
+        }
+
         /**
          * 获取期数据(截止上期 & 本期) (Ajax)
          * @param ctx
@@ -909,6 +987,9 @@ module.exports = app => {
                 // 用户有无权限上传和删除附件
                 renderData.uploadPermission = !ctx.tender.isTourist || ctx.tender.touristPermission.file;
 
+                const projectFunInfo = await this.ctx.service.project.getFunRela(ctx.session.sessionProject.id);
+                renderData.lockPayExpr = projectFunInfo.lockPayExpr;
+
                 if (!ctx.stage.readOnly || ctx.tender.isTourist) {
                     // 计算 本期金额
                     const payCalculator = new PayCalculator(ctx, ctx.stage, ctx.tender.info);
@@ -1217,6 +1298,11 @@ module.exports = app => {
                     throw '该期数据当前无法上报';
                 }
 
+                await this._checkStageStart(ctx);
+                if (ctx.session.sessionProject.page_show.openStageStart && !ctx.stage.startPermission) {
+                    throw '非指定日期范围内无法上报';
+                }
+
                 await ctx.service.stageAudit.start(ctx.stage.id, ctx.stage.times);
                 ctx.body = { err: 0, msg: '', data: [] };
             } catch (err) {

+ 61 - 18
app/controller/tender_controller.js

@@ -399,6 +399,10 @@ module.exports = app => {
                     tender.ledger_status === auditConst.ledger.status.checkNo || tender.ledger_status === auditConst.ledger.status.uncheck);
                 const stages = await ctx.service.stage.getValidStages(ctx.tender.id);
                 const lastStage = stages.length > 0 ? stages[0] : null; // await ctx.service.stage.getLastestStage(ctx.tender.id);
+                const [change_tp, change_p_tp, change_n_tp] = await ctx.service.change.getChangeTp(tender.id);
+                tender.change_tp = change_tp;
+                tender.change_p_tp = change_p_tp;
+                tender.change_n_tp = change_n_tp;
                 if (lastStage) {
                     await this.ctx.service.stage.checkStageGatherData(lastStage, this.ctx.session.sessionUser.is_admin);
 
@@ -410,17 +414,16 @@ module.exports = app => {
                         tender.total_price = sum.total_price;
                         tender.deal_tp = sum.deal_tp;
                     }
-
-                    tender.gather_tp = ctx.helper.add(lastStage.contract_tp, lastStage.qc_tp);
-                    tender.end_contract_tp = ctx.helper.add(lastStage.contract_tp, lastStage.pre_contract_tp);
-                    tender.end_qc_tp = ctx.helper.add(lastStage.qc_tp, lastStage.pre_qc_tp);
+                    tender.sum = ctx.helper.add(tender.total_price, tender.change_tp);
+                    tender.gather_tp = ctx.helper.sum([lastStage.contract_tp, lastStage.qc_tp, lastStage.pc_tp]);
+                    tender.end_contract_tp = ctx.helper.sum([lastStage.contract_tp, lastStage.pre_contract_tp, lastStage.contract_pc_tp]);
+                    tender.end_qc_tp = ctx.helper.sum([lastStage.qc_tp, lastStage.pre_qc_tp, lastStage.qc_pc_tp]);
+                    tender.end_positive_qc_tp = ctx.helper.sum([lastStage.positive_qc_tp, lastStage.pre_positive_qc_tp, lastStage.positive_qc_pc_tp]);
+                    tender.end_negative_qc_tp = ctx.helper.sum([lastStage.negative_qc_tp, lastStage.pre_negative_qc_tp, lastStage.negative_qc_pc_tp]);
                     tender.end_gather_tp = ctx.helper.add(tender.end_contract_tp, tender.end_qc_tp);
                     tender.pre_gather_tp = ctx.helper.add(lastStage.pre_contract_tp, lastStage.pre_qc_tp);
                     tender.yf_tp = lastStage.yf_tp;
-                    const change_tp = await ctx.service.change.getChangeTp(tender.id);
-                    tender.change_tp = change_tp;
                     tender.qc_ratio = ctx.helper.mul(ctx.helper.div(tender.end_qc_tp, ctx.tender.info.deal_param.contractPrice, 2), 100);
-                    tender.sum = ctx.helper.add(tender.total_price, tender.change_tp);
                     tender.pre_ratio = ctx.helper.mul(ctx.helper.div(tender.pre_gather_tp, tender.sum, 2), 100);
                     tender.cur_ratio = ctx.helper.mul(ctx.helper.div(tender.gather_tp, tender.sum, 2), 100);
                     tender.other_tp = ctx.helper.sub(ctx.helper.sub(tender.sum, tender.pre_gather_tp), tender.gather_tp);
@@ -453,7 +456,7 @@ module.exports = app => {
                         tender.total_price = sum.total_price;
                         tender.deal_tp = sum.deal_tp;
                     }
-
+                    tender.sum = ctx.helper.add(tender.total_price, tender.change_tp);
                     if (tender.ledger_status !== auditConst.ledger.status.uncheck) {
                         const status_name = await this.ctx.service.ledgerAudit.getStatusName(tender.id, tender.ledger_times);
                         tender.status_users = status_name ? status_name.name : '';
@@ -1060,6 +1063,9 @@ module.exports = app => {
                     case 'company':
                         info = await ctx.service.ledgerCooperation.saveCompany(data);
                         break;
+                    case 'audit-ass':
+                        info = await ctx.service.auditAss.updateData(data);
+                        break;
                     default:break;
                 }
                 ctx.body = { err: 0, msg: '', data: info };
@@ -1148,21 +1154,20 @@ module.exports = app => {
         }
 
         /**
-         * 获取部位明细数据(Ajax)
+         * 获取设置协同人相关(Ajax)
          *
          * @param ctx
          * @return {Promise<void>}
          */
-        async loadLedgerData(ctx) {
+        async loadAuditAss(ctx) {
             try {
-                const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
-                const posData = ctx.tender.data.measure_type === measureType.tz.value
-                    ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
-                const convert = new billsPosConvert(ctx);
-                convert.loadData(ledgerData, posData, []);
-                const result = await convert.convert();
-                const ledgerCooperationList = await ctx.service.ledgerCooperation.getAllDataByCondition({ where: { tid: ctx.tender.id, status: 1 } });
-                ctx.body = { err: 0, msg: '', data: { ledgerList: result, ledgerCooperationList } };
+                const ledgerData = await ctx.service.ledger.getAllDataByCondition({
+                    columns: ['id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf', 'code', 'b_code', 'name'],
+                    where: { tender_id: ctx.tender.id }
+                });
+                const result = ledgerData.filter(x => { return !x.b_code; });
+                const auditAssList = await ctx.service.auditAss.getData(ctx.tender.id);
+                ctx.body = { err: 0, msg: '', data: { ledgerList: result, auditAssList } };
             } catch (err) {
                 this.log(err);
                 ctx.body = { err: 1, msg: err.toString(), data: [] };
@@ -1325,6 +1330,44 @@ module.exports = app => {
             }
         }
 
+        async auditAssist(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+
+                if (!data.user_id || !data.type) throw '数据错误';
+                switch(data.type) {
+                    case 'stage':
+                        const stageAssists = await this.ctx.service.stageAuditAss.getUserAssist(ctx.stage, data.user_id);
+                        ctx.body = { err: 0, msg: '', data: stageAssists };
+                        break;
+                    default:
+                        throw '数据错误';
+                }
+            } catch (err) {
+                this.log(err);
+                ctx.ajaxErrorBody(err, '操作失败');
+            }
+        }
+
+        async auditAssistConfirm(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+
+                if (!data.user_id || !data.ass_user_id || !data.type || data.confirm === undefined) throw '数据错误';
+                switch(data.type) {
+                    case 'stage':
+                        const stageAss = await this.ctx.service.stageAuditAss.updateData(data);
+                        ctx.body = { err: 0, msg: '', data: stageAss };
+                        break;
+                    default:
+                        throw '数据错误';
+                }
+            } catch (err) {
+                this.log(err);
+                ctx.ajaxErrorBody(err, '操作失败');
+            }
+        }
+
         /**
          * 地图数据设置(Ajax)
          *

+ 13 - 9
app/controller/wap_controller.js

@@ -124,11 +124,11 @@ module.exports = app => {
                     const isLastage = await ctx.service.stage.isLastStage(audit.tid, audit.id);
                     if (isLastage) await this.ctx.service.stage.checkStageGatherData(audit);
                 }
-                audit.gather_tp = ctx.helper.add(audit.contract_tp, audit.qc_tp);
-                audit.end_contract_tp = ctx.helper.add(audit.contract_tp, audit.pre_contract_tp);
-                audit.end_qc_tp = ctx.helper.add(audit.qc_tp, audit.pre_qc_tp);
-                audit.end_gather_tp = ctx.helper.add(audit.end_contract_tp, audit.end_qc_tp);
+                audit.gather_tp = ctx.helper.sum([audit.contract_tp, audit.qc_tp, audit.pc_tp]);
+                audit.end_contract_tp = ctx.helper.sum([audit.contract_tp, audit.pre_contract_tp, audit.contract_pc_tp]);
+                audit.end_qc_tp = ctx.helper.sum([audit.qc_tp, audit.pre_qc_tp, audit.qc_pc_tp]);
                 audit.pre_gather_tp = ctx.helper.add(audit.pre_contract_tp, audit.pre_qc_tp);
+                audit.end_gather_tp = ctx.helper.add(audit.gather_tp, audit.pre_gather_tp);
             }
             // 获取待审批的变更期
             const auditChanges = await ctx.service.changeAudit.getAuditChangeByWap(ctx.session.sessionUser.accountId);
@@ -223,18 +223,22 @@ module.exports = app => {
                     tender.total_price = sum.total_price;
                     tender.deal_tp = sum.deal_tp;
                 }
+                const [change_tp, change_p_tp, change_n_tp] = await ctx.service.change.getChangeTp(ctx.tender.id);
+                tender.change_tp = change_tp;
+                tender.change_p_tp = change_p_tp;
+                tender.change_n_tp = change_n_tp;
                 const stages = await ctx.service.stage.getValidStages(ctx.tender.id);
                 const lastStage = stages.length > 0 ? stages[0] : null; //await ctx.service.stage.getLastestStage(ctx.tender.id);
                 if (lastStage) {
                     await this.ctx.service.stage.checkStageGatherData(lastStage);
-                    tender.gather_tp = ctx.helper.add(lastStage.contract_tp, lastStage.qc_tp);
-                    tender.end_contract_tp = ctx.helper.add(lastStage.contract_tp, lastStage.pre_contract_tp);
-                    tender.end_qc_tp = ctx.helper.add(lastStage.qc_tp, lastStage.pre_qc_tp);
-                    tender.end_gather_tp = ctx.helper.add(tender.end_contract_tp, tender.end_qc_tp);
+                    tender.gather_tp = ctx.helper.sum([lastStage.contract_tp, lastStage.qc_tp, lastStage.pc_tp]);
+                    tender.end_contract_tp = ctx.helper.sum([lastStage.contract_tp, lastStage.pre_contract_tp, lastStage.contract_pc_tp]);
+                    tender.end_qc_tp = ctx.helper.sum([lastStage.qc_tp, lastStage.pre_qc_tp, lastStage.qc_pc_tp]);
                     tender.pre_gather_tp = ctx.helper.add(lastStage.pre_contract_tp, lastStage.pre_qc_tp);
+                    tender.end_gather_tp = ctx.helper.add(tender.gather_tp, tender.pre_gather_tp);
                     tender.yf_tp = lastStage.yf_tp;
                     tender.qc_ratio = ctx.helper.mul(ctx.helper.div(tender.end_qc_tp, ctx.tender.info.deal_param.contractPrice, 2), 100);
-                    tender.sum = ctx.helper.add(tender.total_price, tender.end_qc_tp);
+                    tender.sum = ctx.helper.add(tender.total_price, tender.change_tp);
                     tender.pre_ratio = ctx.helper.mul(ctx.helper.div(tender.pre_gather_tp, tender.sum, 2), 100);
                     tender.cur_ratio = ctx.helper.mul(ctx.helper.div(tender.gather_tp, tender.sum, 2), 100);
                     tender.other_tp = ctx.helper.sub(ctx.helper.sub(tender.sum, tender.pre_gather_tp), tender.gather_tp);

+ 1 - 1
app/extend/helper.js

@@ -411,7 +411,7 @@ module.exports = {
      */
     numEqual(value1, value2) {
         if (value1 && value2) {
-            return Math.abs(value2 - value1) > zeroRange;
+            return Math.abs(value2 - value1) < zeroRange;
         }
         return (!value1 && !value2);
 

+ 1 - 1
app/lib/budget_final.js

@@ -162,7 +162,7 @@ class BudgetFinal {
                 ? await this.ctx.service.stageBills.getAuditorStageData2(id, stage.id, stage.curTimes, stage.curOrder)
                 : await this.ctx.service.stageBills.getLastestStageData2(id, stage.id);
             const preBills = stage.order > 1 ? await this.ctx.service.stageBillsFinal.getFinalData({id}, stage.order - 1) : [];
-            const bpcData = await ctx.service.stageBillsPc.getAllDataByCondition({ where: { sid: ctx.stage.id } });
+            const bpcData = await this.ctx.service.stageBillsPc.getAllDataByCondition({ where: { sid: stage.id } });
             helper.assignRelaData(bills, [
                 { data: curBills, fields: ['contract_tp', 'qc_tp'], prefix: '', relaId: 'lid' },
                 { data: preBills, fields: ['contract_tp', 'qc_tp'], prefix: 'pre_', relaId: 'lid' },

+ 140 - 14
app/lib/ledger.js

@@ -54,6 +54,30 @@ class baseTree {
     getParent (node) {
         return this.getItems(node[this.setting.pid]);
     };
+    getTopParent(node) {
+        const parents = this.getAllParents(node);
+        return parents[0];
+    };
+    getAllParents(node) {
+        const parents = [];
+        if (!node) return parents;
+
+        if (node[this.setting.fullPath] && node[this.setting.fullPath] !== '') {
+            const parentIds = node[this.setting.fullPath].split('-');
+            for (const id of parentIds) {
+                if (id !== node[this.setting.id]) {
+                    parents.push(this.getItems(id));
+                }
+            }
+        } else {
+            let vP = this.getParent(node);
+            while (vP) {
+                parents.unshift(vP);
+                vP = this.getParent(vP);
+            }
+        }
+        return parents;
+    }
     /**
      * 查询node的已下载子节点
      * @param {Object} node
@@ -66,7 +90,7 @@ class baseTree {
             return x[setting.pid] === pid;
         });
         children.sort(function (a, b) {
-            return a.order - b.order;
+            return a[setting.order] - b[setting.order];
         });
         return children;
     };
@@ -84,6 +108,7 @@ class baseTree {
      */
     sortTreeNode (isResort) {
         const self = this;
+        const setting = this.setting;
         const addSortNodes = function (nodes) {
             if (!nodes) { return }
             for (let i = 0; i < nodes.length; i++) {
@@ -93,7 +118,7 @@ class baseTree {
                     nodes[i].children = self.getChildren(nodes[i]);
                 } else {
                     nodes[i].children.sort(function (a, b) {
-                        return a.order - b.order;
+                        return a[setting.order] - b[setting.order];
                     })
                 }
                 addSortNodes(nodes[i].children);
@@ -104,7 +129,7 @@ class baseTree {
             this.children = this.getChildren();
         } else {
             this.children.sort(function (a, b) {
-                return a.order - b.order;
+                return a[setting.order] - b[setting.order];
             })
         }
         addSortNodes(this.children);
@@ -119,9 +144,10 @@ class baseTree {
         this.nodes = [];
         this.datas = [];
         this.children = [];
+        const setting = this.setting;
         // 加载全部数据
         datas.sort(function (a, b) {
-            return a.level - b.level;
+            return a[setting.level] - b[setting.level];
         });
         for (const data of datas) {
             const keyName = itemsPre + data[this.setting.id];
@@ -143,7 +169,7 @@ class baseTree {
             }
         }
         this.children.sort(function (a, b) {
-            return a.order - b.order;
+            return a[setting.order] - b[setting.order];
         });
         this.sortTreeNode(true);
     }
@@ -448,7 +474,7 @@ class gatherTree extends baseTree {
         return this._newId++;
     }
 
-    loadGatherNode(node, parent, loadFun) {
+    loadGatherNode(node, parent, loadFun, loadPosFun) {
         const siblings = parent ? parent.children : this.children;
         let cur = siblings.find(function (x) {
             return node.b_code
@@ -471,8 +497,12 @@ class gatherTree extends baseTree {
             this.datas.push(cur);
         }
         loadFun(cur, node);
-        for (const c of node.children) {
-            this.loadGatherNode(c, cur, loadFun);
+        if (node.children && node.children.length > 0) {
+            for (const c of node.children) {
+                this.loadGatherNode(c, cur, loadFun, loadPosFun);
+            }
+        } else if (loadPosFun) {
+            loadPosFun(cur, node);
         }
     }
 
@@ -490,11 +520,10 @@ class gatherTree extends baseTree {
         }
     }
 
-    loadGatherTree(sourceTree,  loadFun) {
+    loadGatherTree(sourceTree, loadFun, loadPosFun) {
         for (const c of sourceTree.children) {
-            this.loadGatherNode(c, null, loadFun);
+            this.loadGatherNode(c, null, loadFun, loadPosFun);
         }
-        // todo load Pos Data;
     }
     resortChildrenByCustom(fun) {
         for (const n of this.datas) {
@@ -564,6 +593,14 @@ class pos {
         }
     }
 
+    getLedgerPosKey() {
+        const result = [];
+        for (const prop in this.ledgerPos) {
+            result.push(prop);
+        }
+        return result;
+    }
+
     getLedgerPos(mid) {
         return this.ledgerPos[itemsPre + mid];
     }
@@ -593,6 +630,26 @@ class pos {
     }
 }
 
+class gatherPos extends pos {
+    loadGatherPos(ledgerId, sourcePosRange, loadFun) {
+        let posRange = this.getLedgerPos(itemsPre + ledgerId);
+        if (!posRange) {
+            posRange = [];
+            this.ledgerPos[itemsPre + ledgerId] = posRange;
+        }
+        for (const spr of sourcePosRange) {
+            let gp = posRange.find(x => { return x.name === spr.name; });
+            if (!gp) {
+                gp = { name: spr.name };
+                gp[this.setting.ledgerId] = ledgerId;
+                this.datas.push(gp);
+                posRange.push(gp);
+            }
+            loadFun(gp, spr);
+        }
+    }
+}
+
 class checkData {
     constructor(ctx, measureType) {
         this.ctx = ctx;
@@ -648,7 +705,6 @@ class checkData {
         return sc ? sc.ratio : null;
     }
 
-
     _getValid = function (type, status, limit) {
         if (limit) {
             const statusConst = type === 'gxby' ? this.ctx.session.sessionProject.gxby_status : this.ctx.session.sessionProject.dagl_status;
@@ -847,7 +903,7 @@ class checkData {
     }
     checkBillsTp(field, decimal, filter) {
         for (const b of this.checkBills.nodes) {
-            if ((b.children && b.children.length > 0) || !b.check_calc) continue;
+            if ((b.children && b.children.length > 0)) continue;
             if (filter && filter(b)) continue;
 
             const checkData = {}, calcData = {};
@@ -942,6 +998,41 @@ class checkData {
             }
         }
     }
+
+    checkMinusChangeBills(change, changeBills, finalStageChange) {
+        const error = this.checkResult.error;
+        const helper = this.ctx.helper;
+        const changeIndex = {};
+        change.forEach(c => {
+            changeIndex[c.cid] = c;
+            c.bills = [];
+            c.billsIndex = {};
+            c.stageChange = [];
+        });
+        changeBills.forEach(cb => {
+            const c = changeIndex[cb.cid];
+            if (c) c.bills.push(cb);
+            c.billsIndex[cb.id] = cb;
+            cb.used_qty = 0;
+            cb.qty = parseFloat(cb.samount);
+        });
+        finalStageChange.forEach(sc => {
+            if (!sc.qty) return;
+            const c = changeIndex[sc.cid];
+            if (c) {
+                c.used = true;
+                const cb = c.billsIndex[sc.cbid];
+                if (cb) cb.used_qty = helper.add(cb.used_qty, sc.qty);
+            }
+        });
+        change.forEach(c => {
+            if (!c.used) return;
+            c.bills.forEach(b => {
+                if (b.qty >= 0) return;
+                if (!helper.numEqual(b.used_qty, b.qty)) error.push({ b_code: b.code, name: b.name, errorType: 'minus_cb', memo: c.code });
+            });
+        });
+    }
 }
 
 class reviseTree extends billsTree {
@@ -952,10 +1043,36 @@ class reviseTree extends billsTree {
     loadRevisePrice(price, decimal) {
         this.decimal = decimal;
         this.price = price || [];
+        this.rela_price = [];
+        this.common_price = [];
+        this.price.forEach(x => {
+            if (x.rela_lid) {
+                x.rela_lid = x.rela_lid.split(',');
+                this.rela_price.push(x);
+            } else {
+                this.common_price.push(x);
+            }
+        });
     }
     checkRevisePrice(d) {
         const helper = this.ctx.helper;
-        const p = this.price.find(x => {
+        const setting = this.setting;
+        const pid = this.getAllParents(d).map(x => { return x[setting.id] + ''; });
+        const checkRela = function(rela_lid) {
+            if (!rela_lid || rela_lid.length === 0) return false;
+            for (const lid of rela_lid) {
+                if (pid.indexOf(lid) >= 0) return true;
+            }
+            return false;
+        };
+        let p = this.rela_price.find(x => {
+            return x.b_code === d.b_code &&
+                ((!x.name && !d.name) || x.name === d.name) &&
+                ((!x.unit && !d.unit) || x.unit === d.unit) &&
+                helper.checkZero(x.org_price - d.unit_price) &&
+                checkRela(x.rela_lid);
+        });
+        if (!p) p = this.common_price.find(x => {
             return x.b_code === d.b_code &&
                 ((!x.name && !d.name) || x.name === d.name) &&
                 ((!x.unit && !d.unit) || x.unit === d.unit) &&
@@ -1012,6 +1129,14 @@ class reviseTree extends billsTree {
             }
         });
     }
+    sum() {
+        const result = { total_price: 0 };
+        for (const d of this.datas) {
+            if (d.children && d.children.length > 0) continue;
+            result.total_price = this.ctx.helper.add(result.total_price, d.total_price);
+        }
+        return result;
+    }
 }
 
 module.exports = {
@@ -1020,6 +1145,7 @@ module.exports = {
     filterTree,
     filterGatherTree,
     gatherTree,
+    gatherPos,
     checkData,
     reviseTree,
 };

+ 16 - 13
app/lib/pay_calc.js

@@ -118,21 +118,24 @@ class PayCalculate {
      * @param {Array} pays - (标段)合同支付数据
      */
     async calculateStartRangePrice (pays) {
-        const order = this.stage.curOrder;
+        //const order = this.stage.curOrder;
         for (const p of pays) {
             // 非本期,本次添加的合同支付项,不允许计算,其中默认添加的合同支付项,归属于第一期原报
-            if (p.csorder === this.stage.order || (p.csorder === 0 || this.stage.order === 1)) {
-                if (p.csaorder === order) {
-                    if (!p.sprice && p.sexpr && p.sexpr !== '') {
-                        p.sprice = this.ctx.helper.round(this._calculateExpr(p.sexpr), this.decimal);
-                    } else if (p.sprice && !p.sexpr) {
-                        p.sprice = this.ctx.helper.round(p.sprice, this.decimal);
-                    }
-                    if (!p.rprice && p.rexpr && p.rexpr !== '') {
-                        p.rprice = this.ctx.helper.round(this._calculateExpr(p.rexpr), this.decimal);
-                    } else if (p.rprice && !p.rexpr) {
-                        p.rprice = this.ctx.helper.round(p.rprice, this.decimal);
-                    }
+            // if (p.csorder === this.stage.order || (p.csorder === 0 || this.stage.order === 1)) {
+            //     if (p.csaorder === order) {
+            //     }
+            // }
+            // 上一期已计量的合同支付项,不予计算起扣金额、扣款限额
+            if (!p.pre_used) {
+                if (!p.sprice && p.sexpr && p.sexpr !== '') {
+                    p.sprice = this.ctx.helper.round(this._calculateExpr(p.sexpr), this.decimal);
+                } else if (p.sprice && !p.sexpr) {
+                    p.sprice = this.ctx.helper.round(p.sprice, this.decimal);
+                }
+                if (!p.rprice && p.rexpr && p.rexpr !== '') {
+                    p.rprice = this.ctx.helper.round(this._calculateExpr(p.rexpr), this.decimal);
+                } else if (p.rprice && !p.rexpr) {
+                    p.rprice = this.ctx.helper.round(p.rprice, this.decimal);
                 }
             }
         }

+ 112 - 40
app/lib/revise_price.js

@@ -22,9 +22,32 @@ class revisePriceCalc {
         this.ctx = ctx;
     }
 
-    findPrice(b_code, name, unit, unit_price) {
+    set price(price) {
+        this._price = price;
+        this.common_price_c = [];
+        this.rela_price_c = [];
+        price.forEach(x => {
+            x.rela_lid = x.rela_lid ? x.rela_lid.split(',') : [];
+            if (x.rela_cid) {
+                x.rela_cid = x.rela_cid.split(',');
+                this.rela_price_c.push(x);
+            } else {
+                this.common_price_c.push(x);
+            }
+        });
+    }
+    get price() {
+        return this._price;
+    }
+
+    findChangeBillsPrice(b_code, name, unit, unit_price, cid) {
         const helper = this.ctx.helper;
-        return this.price.find(x => {
+        const p = this.rela_price_c.find(x => {
+            return b_code === x.b_code && name === x.name && unit === x.unit && helper.numEqual(unit_price, x.org_price) && x.rela_cid.indexOf(cid) >= 0;
+        });
+        if (p) return p;
+
+        return this.common_price_c.find(x => {
             return b_code === x.b_code && name === x.name && unit === x.unit && helper.numEqual(unit_price, x.org_price);
         });
     }
@@ -37,8 +60,8 @@ class revisePriceCalc {
      */
     async newStagePriceChange(newStage, preStage, transaction) {
         // 获取未执行的单价变更,无单价变更不执行
-        const price = await this.ctx.service.revisePrice.getAllDataByCondition({ where: { tid: newStage.tid, valid: 1, use_stage: 0 } });
-        if (price.length === 0) return;
+        this.price = await this.ctx.service.revisePrice.getAllDataByCondition({ where: { tid: newStage.tid, valid: 1, use_stage: 0 } });
+        if (this.price.length === 0) return;
         // 无截止上期数据不执行
         const preBillsData = await this.ctx.service.stageBillsFinal.getAllDataByCondition({ where: { sid: preStage.id } });
         if (preBillsData.length === 0) return;
@@ -46,7 +69,7 @@ class revisePriceCalc {
         // 加载树结构
         const bills = await this.ctx.service.ledger.getData(newStage.tid);
         this.ctx.helper.assignRelaData(bills, [
-            { data: preBillsData, fields: ['id', 'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'unit_price'], prefix: 'pre_', relaId: 'lid' },
+            { data: preBillsData, fields: ['id', 'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'unit_price', 'positive_qc_qty', 'negative_qc_qty', 'positive_qc_tp', 'negative_qc_tp'], prefix: 'pre_', relaId: 'lid' },
         ]);
         const billsTree = new Ledger.billsTree(this.ctx, { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1, calcFields: [] });
         billsTree.loadDatas(bills);
@@ -57,26 +80,33 @@ class revisePriceCalc {
         const decimal = this.ctx.tender.info.decimal;
         billsTree.calculateAll(node => {
             if (!node.pre_id) return;
+            if (!node.pre_contract_qty && !node.pre_qc_qty) return;
             if (node.children && node.children.length > 0) return;
             const priceDiff = helper.sub(node.unit_price, node.pre_unit_price);
             if (!priceDiff) return;
             node.contract_pc_tp = helper.sub(helper.mul(node.pre_contract_qty, node.unit_price, decimal.tp), node.pre_contract_tp);
             node.qc_pc_tp = helper.sub(helper.mul(node.pre_qc_qty, node.unit_price, decimal.tp), node.pre_qc_tp);
             node.pc_tp = helper.add(node.contract_pc_tp, node.qc_pc_tp);
+            node.positive_qc_pc_tp = helper.sub(helper.mul(node.pre_positive_qc_qty, node.unit_price, decimal.tp), node.pre_positive_qc_tp);
+            node.negative_qc_pc_tp = helper.sub(helper.mul(node.pre_negative_qc_qty, node.unit_price, decimal.tp), node.pre_negative_qc_tp);
             result.ibData.push({
-                tid: newStage.tid, sid: newStage.id, sorder: newStage.order, lid: node.id, org_price: node.pre_unit_price,
+                tid: newStage.tid, sid: newStage.id, sorder: newStage.order, lid: node.id, org_price: node.pre_unit_price, unit_price: node.unit_price,
                 contract_pc_tp: node.contract_pc_tp, qc_pc_tp: node.qc_pc_tp, pc_tp: node.pc_tp,
+                positive_qc_pc_tp: node.positive_qc_pc_tp, negative_qc_pc_tp: node.negative_qc_pc_tp,
             });
         });
         if (result.ibData.length > 0) await transaction.insert(this.ctx.service.stageBillsPc.tableName, result.ibData);
 
-        let contract_pc_tp = 0, qc_pc_tp = 0, pc_tp = 0;
+        let contract_pc_tp = 0, qc_pc_tp = 0, pc_tp = 0, positive_qc_pc_tp = 0, negative_qc_pc_tp = 0;
         for (const ibc of result.ibData) {
             contract_pc_tp = helper.add(contract_pc_tp, ibc.contract_pc_tp);
             qc_pc_tp = helper.add(qc_pc_tp, ibc.qc_pc_tp);
             pc_tp = helper.add(pc_tp, ibc.pc_tp);
+            positive_qc_pc_tp = helper.add(positive_qc_pc_tp, ibc.positive_qc_pc_tp);
+            negative_qc_pc_tp = helper.add(negative_qc_pc_tp, ibc.negative_qc_pc_tp);
         }
-        await transaction.update(this.ctx.service.stage.tableName, { id: newStage.id, contract_pc_tp, qc_pc_tp, pc_tp, check_calc: true, cache_time_l: new Date() });
+        await transaction.update(this.ctx.service.stage.tableName,
+            { id: newStage.id, contract_pc_tp, qc_pc_tp, pc_tp, positive_qc_pc_tp, negative_qc_pc_tp, check_calc: true, cache_time_l: new Date() });
     }
 
     /**
@@ -87,8 +117,8 @@ class revisePriceCalc {
      */
     async stageCheckAgainPriceChange(stage, auditOrder, transaction) {
         // 获取未执行的单价变更,无单价变更不执行
-        const price = await this.ctx.service.revisePrice.getAllDataByCondition({ where: { tid: stage.tid, valid: 1, use_stage: 0 } });
-        if (price.length === 0) return;
+        this.price = await this.ctx.service.revisePrice.getAllDataByCondition({ where: { tid: stage.tid, valid: 1, use_stage: 0 } });
+        if (this.price.length === 0) return;
 
         const curBillsData = await this.ctx.service.stageBills.getLastestStageData2(stage.tid, stage.id);
         const preBillsData = await this.ctx.service.stageBillsFinal.getAllDataByCondition({ where: { tid: stage.tid, sorder: stage.order - 1 } });
@@ -96,15 +126,16 @@ class revisePriceCalc {
         // 加载树结构
         const bills = await this.ctx.service.ledger.getData(stage.tid);
         this.ctx.helper.assignRelaData(bills, [
-            { data: curBillsData, fields: ['id', 'contract_qty', 'qc_qty', 'postil', 'times', 'order'], prefix: 'cur_', relaId: 'lid' },
-            { data: preBillsData, fields: ['id', 'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'unit_price'], prefix: 'pre_', relaId: 'lid' },
+            { data: curBillsData, fields: ['id', 'contract_qty', 'qc_qty', 'positive_qc_qty', 'negative_qc_qty', 'postil', 'times', 'order'], prefix: 'cur_', relaId: 'lid' },
+            { data: preBillsData, fields: ['id', 'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'unit_price', 'positive_qc_qty', 'negative_qc_qty', 'positive_qc_tp', 'negative_qc_tp'], prefix: 'pre_', relaId: 'lid' },
         ]);
         const billsTree = new Ledger.billsTree(this.ctx, { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1, calcFields: [] });
         billsTree.loadDatas(bills);
 
+        const stageChange = await this.ctx.service.stageChange.getFinalStageData(stage.tid, stage.id);
 
-        // 计算 insertBills billsPriceChange reCalcBillsCur/reCalcBillsPrice
-        const result = { ibData: [], bpcData: [] };
+        // 计算 insertBills billsPriceChange stageChange
+        const result = { ibData: [], bpcData: [], scData: [] };
         const helper = this.ctx.helper;
         const decimal = this.ctx.tender.info.decimal;
         const said = this.ctx.session.sessionUser.accountId;
@@ -112,38 +143,54 @@ class revisePriceCalc {
             if (node.children && node.children.length > 0) return;
             const priceDiff = helper.sub(node.unit_price, node.pre_unit_price);
             if (!priceDiff) return;
-            if (node.cur_id) {
+            if (node.cur_id && (node.cur_contract_qty || node.cur_qc_qty)) {
                 node.cur_contract_tp = helper.mul(node.cur_contract_qty, node.unit_price, decimal.tp);
                 node.cur_qc_tp = helper.mul(node.cur_qc_qty, node.unit_price, decimal.tp);
+                node.cur_positive_qc_tp = helper.mul(node.cur_positive_qc_qty, node.unit_price, decimal.tp);
+                node.cur_negative_qc_tp = helper.mul(node.cur_negative_qc_qty, node.unit_price, decimal.tp);
                 result.ibData.push({
                     tid: stage.tid, sid: stage.id, said,
                     lid: node.id,
                     times: stage.times, order: auditOrder,
-                    contract_qty: node.cur_contract_qty, contract_tp: node.cur_contract_tp, qc_qty: node.cur_qc_qty, qc_tp: node.cur_qc_tp,
+                    contract_qty: node.cur_contract_qty, contract_tp: node.cur_contract_tp,
+                    qc_qty: node.cur_qc_qty, qc_tp: node.cur_qc_tp,
+                    positive_qc_qty: node.cur_positive_qc_qty, positive_qc_tp: node.cur_positive_qc_tp,
+                    negative_qc_qty: node.cur_negative_qc_qty, negative_qc_tp: node.cur_negative_qc_tp,
                     postil: node.postil,
                 });
             }
-            if (node.pre_id) {
+            if (node.pre_id && (node.pre_contract_qty || node.pre_qc_qty)) {
                 node.contract_pc_tp = helper.sub(helper.mul(node.pre_contract_qty, node.unit_price, decimal.tp), node.pre_contract_tp);
                 node.qc_pc_tp = helper.sub(helper.mul(node.pre_qc_qty, node.unit_price, decimal.tp), node.pre_qc_tp);
                 node.pc_tp = helper.add(node.contract_pc_tp, node.qc_pc_tp);
+                node.positive_qc_pc_tp = helper.sub(helper.mul(node.pre_positive_qc_qty, node.unit_price, decimal.tp), node.pre_positive_qc_tp);
+                node.negative_qc_pc_tp = helper.sub(helper.mul(node.pre_negative_qc_qty, node.unit_price, decimal.tp), node.pre_negative_qc_tp);
                 result.bpcData.push({
-                    tid: stage.tid, sid: stage.id, sorder: stage.order, lid: node.lid, org_price: node.pre_unit_price,
+                    tid: stage.tid, sid: stage.id, sorder: stage.order, lid: node.id, org_price: node.pre_unit_price, unit_price: node.unit_price,
                     contract_pc_tp: node.contract_pc_tp, qc_pc_tp: node.qc_pc_tp, pc_tp: node.pc_tp,
+                    positive_qc_pc_tp: node.positive_qc_pc_tp, negative_qc_pc_tp: node.negative_qc_pc_tp,
                 });
             }
+            const scDetail = stageChange.filter(x => { return x.lid === node.id; });
+            for (const scd of scDetail) {
+                result.scData.push({ id: scd.id, unit_price: node.unit_price });
+            }
         });
         if (result.ibData.length > 0) await transaction.insert(this.ctx.service.stageBills.tableName, result.ibData);
         await transaction.delete(this.ctx.service.stageBillsPc.tableName, { sid: stage.id });
         if (result.bpcData.length > 0) await transaction.insert(this.ctx.service.stageBillsPc.tableName, result.bpcData);
+        if (result.scData.length > 0) await transaction.updateRows(this.ctx.service.stageChange.tableName, result.scData);
 
-        let contract_pc_tp = 0, qc_pc_tp = 0, pc_tp = 0;
+        let contract_pc_tp = 0, qc_pc_tp = 0, pc_tp = 0, positive_qc_pc_tp = 0, negative_qc_pc_tp = 0;
         for (const bpc of result.bpcData) {
             contract_pc_tp = helper.add(contract_pc_tp, bpc.contract_pc_tp);
             qc_pc_tp = helper.add(qc_pc_tp, bpc.qc_pc_tp);
             pc_tp = helper.add(pc_tp, bpc.pc_tp);
+            positive_qc_pc_tp = helper.add(positive_qc_pc_tp, bpc.positive_qc_pc_tp);
+            negative_qc_pc_tp = helper.add(negative_qc_pc_tp, bpc.negative_qc_pc_tp);
         }
-        await transaction.update(this.ctx.service.stage.tableName, { id: stage.id, contract_pc_tp, qc_pc_tp, pc_tp, check_calc: true, cache_time_l: new Date() });
+        await transaction.update(this.ctx.service.stage.tableName,
+            { id: stage.id, contract_pc_tp, qc_pc_tp, pc_tp, positive_qc_pc_tp, negative_qc_pc_tp, check_calc: true, cache_time_l: new Date() });
     }
 
     /**
@@ -152,22 +199,29 @@ class revisePriceCalc {
      * @param {Object} transaction - 事务 (无则非事务提交)
      */
     async calcChange(change, transaction) {
+        const decimal = this.ctx.tender.info.decimal;
         const changeBills = await this.ctx.service.changeAuditList.getAllDataByCondition({ where: { cid: change.cid } });
         const updateBills = [];
-        let total_price = 0;
+        let total_price = 0, positive_tp = 0, negative_tp = 0;
         for (const b of changeBills) {
-            const p = this.findPrice(b.code, b.name, b.unit, b.unit_price);
+            const p = this.findChangeBillsPrice(b.code, b.name, b.unit, b.unit_price, change.cid);
+            let bills_tp;
             if (p) {
                 updateBills.push({ id: b.id, unit_price: p.new_price });
-                total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(p.new_price, b.spamount, change.tp_decimal));
+                bills_tp = this.ctx.helper.mul(p.new_price, b.spamount, change.tp_decimal || decimal.tp);
+            } else {
+                bills_tp = this.ctx.helper.mul(b.unit_price, b.spamount, change.tp_decimal || decimal.tp);
+            }
+            total_price = this.ctx.helper.add(total_price, bills_tp);
+            if (b.spamount >= 0) {
+                positive_tp = this.ctx.helper.add(positive_tp, bills_tp);
             } else {
-                total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(b.unit_price, b.spamount, change.tp_decimal));
+                negative_tp = this.ctx.helper.add(negative_tp, bills_tp);
             }
         }
         if (updateBills.length > 0) {
-            const conn = transaction || this.ctx.sub_db;
-            await conn.updateRows(this.ctx.service.changeAuditList.tableName, updateBills);
-            await conn.update(this.ctx.service.change.tableName, { total_price }, { where: { cid: change.cid } });
+            await transaction.updateRows(this.ctx.service.changeAuditList.tableName, updateBills);
+            await transaction.update(this.ctx.service.change.tableName, { total_price, positive_tp, negative_tp }, { where: { cid: change.cid } });
         }
     }
     /**
@@ -191,14 +245,15 @@ class revisePriceCalc {
 
         // 加载树结构
         this.ctx.helper.assignRelaData(bills, [
-            { data: curBillsData, fields: ['id', 'contract_qty', 'qc_qty', 'times', 'order', 'postil'], prefix: 'cur_', relaId: 'lid' },
-            { data: preBillsData, fields: ['id', 'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'unit_price'], prefix: 'pre_', relaId: 'lid' },
+            { data: curBillsData, fields: ['id', 'contract_qty', 'qc_qty', 'positive_qc_qty', 'negative_qc_qty', 'times', 'order', 'postil'], prefix: 'cur_', relaId: 'lid' },
+            { data: preBillsData, fields: ['id', 'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'unit_price', 'positive_qc_qty', 'negative_qc_qty', 'positive_qc_tp', 'negative_qc_tp'], prefix: 'pre_', relaId: 'lid' },
         ]);
         const billsTree = new Ledger.billsTree(this.ctx, { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1, calcFields: [] });
         billsTree.loadDatas(bills);
+        const stageChange = await this.ctx.service.stageChange.getFinalStageData(stage.tid, stage.id);
 
-        // 计算 insertBills/updateBills billsPriceChange
-        const result = { ibData: [], ubData: [], bpcData: [] };
+        // 计算 insertBills/updateBills billsPriceChange StageChange
+        const result = { ibData: [], ubData: [], bpcData: [], scData: [] };
         const helper = this.ctx.helper;
         const decimal = this.ctx.tender.info.decimal;
         const said = this.ctx.session.sessionUser.accountId;
@@ -206,45 +261,62 @@ class revisePriceCalc {
             if (node.children && node.children.length > 0) return;
             const priceDiff = helper.sub(node.unit_price, node.pre_unit_price);
             if (!priceDiff) return;
-            node.contract_pc_tp = helper.sub(helper.mul(node.pre_contract_qty, node.unit_price, decimal.tp), node.pre_contract_tp);
-            node.qc_pc_tp = helper.sub(helper.mul(node.pre_qc_qty, node.unit_price, decimal.tp), node.pre_qc_tp);
-            node.pc_tp = helper.add(node.contract_pc_tp, node.qc_pc_tp);
-            if (node.cur_id) {
+            if (node.cur_id && (node.cur_contract_qty || node.cur_qc_qty)) {
                 node.cur_contract_tp = helper.mul(node.cur_contract_qty, node.unit_price, decimal.tp);
                 node.cur_qc_tp = helper.mul(node.cur_qc_qty, node.unit_price, decimal.tp);
+                node.cur_positive_qc_tp = helper.mul(node.cur_positive_qc_qty, node.unit_price, decimal.tp);
+                node.cur_negative_qc_tp = helper.mul(node.cur_negative_qc_qty, node.unit_price, decimal.tp);
                 if (node.cur_times === stage.times && node.cur_order === 0) {
                     result.ubData.push({
                         id: node.cur_id,
                         contract_tp: node.cur_contract_tp, qc_tp: node.cur_qc_tp,
+                        positive_qc_tp: node.cur_positive_qc_tp, negative_qc_tp: node.cur_negative_qc_tp,
                     });
                 } else {
                     result.ibData.push({
                         tid: stage.tid, sid: stage.id, said,
                         lid: node.id, times: stage.times, order: 0,
-                        contract_qty: node.cur_contract_qty, contract_tp: node.cur_contract_tp, qc_qty: node.cur_qc_qty, qc_tp: node.cur_qc_tp,
+                        contract_qty: node.cur_contract_qty, contract_tp: node.cur_contract_tp,
+                        qc_qty: node.cur_qc_qty, qc_tp: node.cur_qc_tp,
+                        positive_qc_qty: node.cur_positive_qc_qty, positive_qc_tp: node.cur_positive_qc_tp,
+                        negative_qc_qty: node.cur_negative_qc_qty, negative_qc_tp: node.cur_negative_qc_tp,
                         postil: node.cur_postil,
                     });
                 }
             }
-            if (node.pre_id) {
+            if (node.pre_id && (node.pre_contract_qty || node.pre_qc_qty)) {
+                node.contract_pc_tp = helper.sub(helper.mul(node.pre_contract_qty, node.unit_price, decimal.tp), node.pre_contract_tp);
+                node.qc_pc_tp = helper.sub(helper.mul(node.pre_qc_qty, node.unit_price, decimal.tp), node.pre_qc_tp);
+                node.pc_tp = helper.add(node.contract_pc_tp, node.qc_pc_tp);
+                node.positive_qc_pc_tp = helper.sub(helper.mul(node.pre_positive_qc_qty, node.unit_price, decimal.tp), node.pre_positive_qc_tp);
+                node.negative_qc_pc_tp = helper.sub(helper.mul(node.pre_negative_qc_qty, node.unit_price, decimal.tp), node.pre_negative_qc_tp);
                 result.bpcData.push({
-                    tid: stage.tid, sid: stage.id, sorder: stage.order, lid: node.id, org_price: node.pre_unit_price,
+                    tid: stage.tid, sid: stage.id, sorder: stage.order, lid: node.id, org_price: node.pre_unit_price, unit_price: node.unit_price,
                     contract_pc_tp: node.contract_pc_tp, qc_pc_tp: node.qc_pc_tp, pc_tp: node.pc_tp,
+                    positive_qc_pc_tp: node.positive_qc_pc_tp, negative_qc_pc_tp: node.negative_qc_pc_tp,
                 });
             }
+            const scDetail = stageChange.filter(x => { return x.lid === node.id; });
+            for (const scd of scDetail) {
+                result.scData.push({ id: scd.id, unit_price: node.unit_price });
+            }
         });
         if (result.ibData.length > 0) await transaction.insert(this.ctx.service.stageBills.tableName, result.ibData);
         if (result.ubData.length > 0) await transaction.updateRows(this.ctx.service.stageBills.tableName, result.ubData);
         await transaction.delete(this.ctx.service.stageBillsPc.tableName, { sid: stage.id });
         if (result.bpcData.length > 0) await transaction.insert(this.ctx.service.stageBillsPc.tableName, result.bpcData);
+        if (result.scData.length > 0) await transaction.updateRows(this.ctx.service.stageChange.tableName, result.scData);
 
-        let contract_pc_tp = 0, qc_pc_tp = 0, pc_tp = 0;
+        let contract_pc_tp = 0, qc_pc_tp = 0, pc_tp = 0, positive_qc_pc_tp = 0, negative_qc_pc_tp = 0;
         for (const bpc of result.bpcData) {
             contract_pc_tp = helper.add(contract_pc_tp, bpc.contract_pc_tp);
             qc_pc_tp = helper.add(qc_pc_tp, bpc.qc_pc_tp);
             pc_tp = helper.add(pc_tp, bpc.pc_tp);
+            positive_qc_pc_tp = helper.add(positive_qc_pc_tp, bpc.positive_qc_pc_tp);
+            negative_qc_pc_tp = helper.add(negative_qc_pc_tp, bpc.negative_qc_pc_tp);
         }
-        await transaction.update(this.ctx.service.stage.tableName, { id: stage.id, contract_pc_tp, qc_pc_tp, pc_tp, check_calc: true, cache_time_l: new Date() });
+        await transaction.update(this.ctx.service.stage.tableName,
+            { id: stage.id, contract_pc_tp, qc_pc_tp, pc_tp, positive_qc_pc_tp, negative_qc_pc_tp, check_calc: true, cache_time_l: new Date() });
     }
     /**
      * 计算修订台账

+ 157 - 58
app/lib/rm/material.js

@@ -144,36 +144,84 @@ class ReportMemoryMaterial {
         }
     }
 
+    async _loadMaterialMonth(material, gl) {
+        const materialMonth = await this.ctx.service.materialMonth.getAllDataByCondition({
+            where: { mid: material.id },
+            orders: [['mb_id', 'asc'], ['yearmonth', 'asc']],
+        });
+
+        const month = this.ctx.helper._.uniq(materialMonth.map(x => { return x.yearmonth; }));
+        let g;
+        for (const mm of materialMonth) {
+            if (!g || g.id !== mm.mb_id) g = gl.find(x => { return mm.mb_id === x.id; });
+            if (!g.month_msg_tp) g.month_msg_tp = [];
+            if (!g.month) g.month = month.concat([]);
+            const index = month.indexOf(mm.yearmonth);
+            if (index >= 0) g.month_msg_tp[index] = mm.msg_tp;
+        }
+    }
+
     async getMaterialGl(tender_id, material_order, fields) {
         const materials = await this.ctx.service.material.getAllDataByCondition({
             where: {tid: tender_id},
             orders: [['order', 'desc']],
         });
-        if (materials.length > 0) {
-            let result;
-            if (materials[0].order === material_order) {
+        if (materials.length === 0) return [];
+
+        let result, material;
+        if (materials[0].order === material_order) {
+            material = materials[0];
+            if (material.is_stage_self) {
+                const sql = 'SELECT msb.id, msb.tid, msb.mid, msb.ms_id, ms.sid, ms.`order` as s_order, mb.order, mb.t_type, mb.code, mb.name, mb.unit, mb.spec, mb.m_type,' +
+                    '    msb.quantity, mb.expr,' +
+                    '    mb.basic_price, mb.basic_times, ' +
+                    '    msb.msg_tp, msb.msg_times, msb.msg_spread, mb.m_up_risk, mb.m_down_risk, msb.m_spread, msb.m_tp, mb.pre_tp, msb.m_tax_tp, mb.tax_pre_tp, mb.origin, ' +
+                    '    msb.remark, msb.is_summary, mb.m_tax, mb.in_time, mb.origin' +
+                    `  FROM ${this.ctx.service.materialStageBills.tableName} msb` +
+                    `  LEFT JOIN ${this.ctx.service.materialBills.tableName} mb ON msb.mb_id = mb.id` +
+                    '  LEFT JOIN ' + this.ctx.service.materialStage.tableName + ' ms ON msb.ms_id = ms.id ' +
+                    `  WHERE msb.mid = ?` +
+                    '  ORDER By msb.ms_id, mb.order';
+                result = await this.ctx.app.mysql.query(sql, [material.id]);
+            } else {
                 result = await this.ctx.service.materialBills.getAllDataByCondition({
                     where: {tid: tender_id}
                 });
-            } else {
-                const material = this.ctx.helper._.find(materials, {order: material_order});
-                if (!material) return [];
+            }
+        } else {
+            material = this.ctx.helper._.find(materials, {order: material_order});
+            if (!material) return [];
 
-                const sql = 'SELECT mb.id, mb.tid, mb.mid, mb.order, mb.order, mb.t_type, mb.code, mb.name, mb.unit, mb.spec, mb.m_type,' +
+            if (material.is_stage_self) {
+                const sql = 'SELECT msb.id, msb.tid, msb.mid, msb.ms_id, ms.sid, ms.`order` as s_order, mb.order, mb.t_type, mb.code, mb.name, mb.unit, mb.spec, mb.m_type,' +
+                    '    msb.quantity, mbh.expr,' +
+                    '    mb.basic_price, mb.basic_times, ' +
+                    '    msb.msg_tp, msb.msg_times, msb.msg_spread, mbh.m_up_risk, mbh.m_down_risk, msb.m_spread, msb.m_tp, mbh.pre_tp, msb.m_tax_tp, mbh.tax_pre_tp, mbh.origin, ' +
+                    '    msb.remark, msb.is_summary, mbh.m_tax, mb.in_time, mbh.origin' +
+                    `  FROM ${this.ctx.service.materialStageBills.tableName} msb` +
+                    '  LEFT JOIN ' + this.ctx.service.materialBillsHistory.tableName + ' mbh ON msb.mb_id = mbh.mb_id AND msb.mid = mbh.mid' +
+                    '  LEFT JOIN ' + this.ctx.service.materialBills.tableName + ' mb ON msb.mb_id = mb.id ' +
+                    '  LEFT JOIN ' + this.ctx.service.materialStage.tableName + ' ms ON msb.ms_id = ms.id ' +
+                    '  WHERE msb.mid = ?'+
+                    '  ORDER By msb.ms_id, mb.order';
+                result = await this.ctx.app.mysql.query(sql, [material.id]);
+            } else {
+                const sql = 'SELECT mb.id, mb.tid, mb.mid, mb.order, mb.t_type, mb.code, mb.name, mb.unit, mb.spec, mb.m_type,' +
                     '    mbh.quantity, mbh.expr,' +
                     '    mb.basic_price, mb.basic_times, ' +
                     '    mbh.msg_tp, mbh.msg_times, mbh.msg_spread, mbh.m_up_risk, mbh.m_down_risk, mbh.m_spread, mbh.m_tp, mbh.pre_tp, mbh.m_tax_tp, mbh.tax_pre_tp, mbh.origin, ' +
-                    '    mb.remark, mb.is_summary, mbh.m_tax, mb.in_time' +
+                    '    mb.remark, mb.is_summary, mbh.m_tax, mb.in_time, mbh.origin' +
                     '  FROM ' + this.ctx.service.materialBillsHistory.tableName + ' mbh ' +
                     '  LEFT JOIN ' + this.ctx.service.materialBills.tableName + ' mb ON mbh.mb_id = mb.id ' +
-                    '  WHERE mbh.tid = ? And mbh.mid = ?';
+                    '  WHERE mbh.tid = ? And mbh.mid = ?'+
+                    '  ORDER By mb.order';
                 result = await this.ctx.app.mysql.query(sql, [tender_id, material.id]);
             }
-            this._completeMaterialGl(result);
-            return result;
-        } else {
-            return [];
         }
+        this._completeMaterialGl(result);
+
+        if (this._checkFieldsExist(fields, ['month_msg_tp', 'month'])) await this._loadMaterialMonth(material, result);
+        return result;
     }
 
     async getMaterialGlDetail(tender_id, material_order, fields) {
@@ -233,6 +281,62 @@ class ReportMemoryMaterial {
         }
     }
 
+    async _getMaterialStageGatherBills(tender_id, stage_id, stage_order, stageSelf) {
+        const decimal = this.materialGatherBase.decimal;
+        const billsData = this.ctx.helper.clone(this.materialGatherBase.billsData);
+        const curStageBills = await this.ctx.service.stageBills.getStagesData(tender_id, stage_id);
+        this.ctx.helper.assignRelaData(billsData, [
+            { data: curStageBills, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid' },
+        ]);
+        const billsTree = this._getNewBillsTree();
+        billsTree.loadDatas(billsData);
+        billsTree.calculateAll();
+
+        const posData = this.ctx.helper.clone(this.materialGatherBase.posData);
+        const curStage = await this.ctx.service.stagePos.getStagesData(tender_id, stage_id);
+        this.ctx.helper.assignRelaData(posData, [
+            { data: curStage, fields: ['contract_qty', 'qc_qty'], prefix: '', relaId: 'pid' },
+        ]);
+        const pos = this._getNewPos();
+        pos.loadDatas(posData);
+        pos.calculateAll();
+
+        const gclGatherModel = require('../gcl_gather').gclGather;
+        const gatherUtil = new gclGatherModel(this.ctx);
+        gatherUtil.gatherObj(billsTree, pos);
+
+        const materialGl = stageSelf
+            ? this.materialGatherBase.materialGl.filter(x => { return x.sid === parseInt(stage_id); })
+            : this.materialGatherBase.materialGl;
+        const materialNotJoin = this.materialGatherBase.materialNotJoin;
+
+        const helper = this.ctx.helper;
+        for (const g of gatherUtil.gclList) {
+            g.sid = stage_id;
+            g.sorder = stage_order;
+            g.jiacha = 0;
+            for (const x of g.leafXmjs) {
+                x.sid = stage_id;
+                x.sorder = stage_order;
+                x.jiacha = 0;
+                const mnj = materialNotJoin.find(m => {
+                    return m.gcl_id === x.org_gcl_id && m.xmj_id === x.id && (x.mx_id && x.mx_id !== x.id  ? x.mx_id === m.mx_id : true);
+                });
+                x.is_join = !mnj;
+                if (mnj) continue;
+                const list = materialGl.filter(gl => {
+                    return gl.gcl_id === x.org_gcl_id && gl.xmj_id === x.id && (x.mx_id && x.mx_id !== x.id ? x.mx_id === gl.mx_id : true);
+                });
+                for (const l of list) {
+                    x.jiacha = helper.add(x.jiacha, helper.mul(helper.mul(x.gather_qty, l.quantity), l.m_spread));
+                }
+                x.jiacha = helper.round(x.jiacha, decimal.tp);
+                g.jiacha = helper.add(g.jiacha, x.jiacha);
+            }
+        }
+        return [gatherUtil.gclList, gatherUtil.leafXmjs ];
+    }
+
     async getMaterialGatherBills(tender_id, material_order) {
         const materials = await this.ctx.service.material.getAllDataByCondition({
             where: { tid: tender_id },
@@ -241,60 +345,55 @@ class ReportMemoryMaterial {
         if (materials.length === 0) return {};
 
         const material = await this.ctx.service.material.getDataByCondition({ tid: tender_id, order: material_order });
-        const decimal = material.decimal ? JSON.parse(material.decimal) : materialConst.decimal;
+        this.materialGatherBase = {};
+        this.materialGatherBase.decimal = material.decimal ? JSON.parse(material.decimal) : materialConst.decimal;
         try {
-            const billsData = await this.ctx.service.ledger.getData(tender_id);
-            const curStageBills = await this.ctx.service.stageBills.getStagesData(tender_id, material.stage_id);
-            this.ctx.helper.assignRelaData(billsData, [
-                { data: curStageBills, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid' },
-            ]);
-            const billsTree = this._getNewBillsTree();
-            billsTree.loadDatas(billsData);
-            billsTree.calculateAll();
-
-            const posData = await this.ctx.service.pos.getPosData({ tid: tender_id });
-            const curStage = await this.ctx.service.stagePos.getStagesData(tender_id, material.stage_id);
-            this.ctx.helper.assignRelaData(posData, [
-                { data: curStage, fields: ['contract_qty', 'qc_qty'], prefix: '', relaId: 'pid' },
-            ]);
-            const pos = this._getNewPos();
-            pos.loadDatas(posData);
-            pos.calculateAll();
+            // 获取基础数据
+            this.materialGatherBase.billsData = await this.ctx.service.ledger.getData(tender_id);
+            this.materialGatherBase.posData = await this.ctx.service.pos.getPosData({ tid: tender_id });
+            if (material.is_stage_self) {
+                this.materialGatherBase.materialGl = material_order === materials[0].order
+                    ? await this.ctx.service.materialList.getMaterialStageData(tender_id, material.id)
+                    : await this.ctx.service.materialList.getPreMaterialStageData(tender_id, material.id);
+            } else {
+                this.materialGatherBase.materialGl = material_order === materials[0].order
+                    ? await this.ctx.service.materialList.getMaterialData(tender_id, material.id)
+                    : await this.ctx.service.materialList.getPreMaterialData(tender_id, material.id);
+            }
+            this.materialGatherBase.materialNotJoin = await this.ctx.service.materialListNotjoin.getAllDataByCondition({ where: { mid: material.id } });
 
-            const gclGatherModel = require('../gcl_gather').gclGather;
-            const gatherUtil = new gclGatherModel(this.ctx);
-            gatherUtil.gatherObj(billsTree, pos);
-            const materialGl = material_order === materials[0].order
-                ? await this.ctx.service.materialList.getMaterialData(tender_id, material.id)
-                : await this.ctx.service.materialList.getPreMaterialData(tender_id, material.id);
-            const materialNotJoin = await this.ctx.service.materialListNotjoin.getAllDataByCondition({ where: { mid: material.id } });
-
-            const helper = this.ctx.helper;
-            for (const g of gatherUtil.gclList) {
-                g.jiacha = 0;
-                for (const x of g.leafXmjs) {
-                    x.jiacha = 0;
-                    const mnj = materialNotJoin.find(m => {
-                        return m.gcl_id === x.org_gcl_id && m.xmj_id === x.id && (x.mx_id && x.mx_id !== x.id  ? x.mx_id === m.mx_id : true);
-                    });
-                    x.is_join = !mnj;
-                    if (mnj) continue;
-                    const list = materialGl.filter(g => {
-                        return g.gcl_id === x.org_gcl_id && g.xmj_id === x.id && (x.mx_id && x.mx_id !== x.id ? x.mx_id === g.mx_id : true);
-                    });
-                    for (const l of list) {
-                        x.jiacha = helper.add(x.jiacha, helper.mul(helper.mul(x.gather_qty, l.quantity), l.m_spread));
-                    }
-                    x.jiacha = helper.round(x.jiacha, decimal.tp);
-                    g.jiacha = helper.add(g.jiacha, x.jiacha);
+            const mem_material_gather_bills = [], mem_material_gather_xmj = [];
+            if (material.is_stage_self) {
+                const stageIds = material.stage_id.split(',');
+                const stageOrders = material.s_order.split(',');
+                for (const [i, sid] of stageIds.entries()) {
+                    const [gclList, leafXmjs] = await this._getMaterialStageGatherBills(tender_id, sid, stageOrders[i], true);
+                    mem_material_gather_bills.push(...gclList);
+                    mem_material_gather_xmj.push(...leafXmjs);
                 }
+            } else {
+                const [gclList, leafXmjs] = await this._getMaterialStageGatherBills(tender_id, material.stage_id, material.stage_order, false);
+                mem_material_gather_bills.push(...gclList);
+                mem_material_gather_xmj.push(...leafXmjs);
             }
-            return { mem_material_gather_bills: gatherUtil.gclList, mem_material_gather_xmj: gatherUtil.leafXmjs, mem_material_gather_gl: materialGl };
+            return {mem_material_gather_bills, mem_material_gather_xmj, mem_material_gather_gl: this.materialGatherBase.materialGl};
         } catch (err) {
             this.ctx.log(err);
             return {};
         }
     }
+
+    async getMaterialStage(tender_id, material_order, fields) {
+        const material = await this.ctx.service.material.getDataByCondition({tid: tender_id, order: material_order});
+        if (material.is_stage_self) {
+            return await this.ctx.service.materialStage.getAllDataByCondition({ where: { mid: material.id } });
+        } else {
+            return [{
+                id: -1, tid: material.id, mid: material.id, sid: material.stage_id, order: material.stage_order,
+                m_tp: material.m_tp, m_tax_tp: material.m_tax_tp,
+            }];
+        }
+    }
 }
 
 module.exports = ReportMemoryMaterial;

+ 1 - 0
app/lib/rpt_data_analysis.js

@@ -1214,6 +1214,7 @@ const gatherSelectConverse = {
         for (const t of options.table) {
             switch (t) {
                 case 'mem_gather_stage_bills':
+                case 'mem_gather_stage_pos':
                 case 'mem_gather_stage_pay':
                 case 'mem_gather_deal_bills':
                 case 'mem_union_data':

+ 4 - 1
app/lib/spread_setting.js

@@ -117,7 +117,10 @@ const getStageGatherSpreadSetting = async function (ctx, tid) {
 
     // if (tender.data.measure_type === measureType.tz.value && !tender.info.display.ledger.deal)
     //     removeFieldCols(gcl, spreadConst.filterCols.dealCols);
-    if (tender.info.display.stage.priceDiff) removeFieldCols(leafXmj, spreadConst.filterCols.priceDiffCols);
+    if (!tender.info.display.stage.priceDiff) {
+        removeFieldCols(gcl, spreadConst.filterCols.priceDiffCols);
+        hiddenFieldCols(leafXmj, spreadConst.filterCols.priceDiffCols);
+    }
 
     if (tender.data.measure_type === measureType.gcl.value) removeFieldCols(leafXmj, ['quantity']);
     return [gcl, leafXmj];

+ 30 - 13
app/lib/stage_im.js

@@ -70,7 +70,9 @@ class StageIm {
 
     // 加载数据
     async _loadMainData() {
-        const billsData = await this.ctx.service.ledger.getData(this.ctx.tender.id);
+        const billsData = this.ctx.stage.ledgerHis
+            ? await this.ctx.helper.loadLedgerDataFromOss(this.ctx.stage.ledgerHis.bills_file)
+            : await this.ctx.service.ledger.getData(this.ctx.tender.id);
         const curStage = this.ctx.stage.readOnly
             ? await this.ctx.service.stageBills.getAuditorStageData2(this.ctx.tender.id,
                 this.ctx.stage.id, this.ctx.stage.curTimes, this.ctx.stage.curOrder)
@@ -81,12 +83,14 @@ class StageIm {
         this.ctx.helper.assignRelaData(billsData, [
             { data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'qc_minus_qty'], prefix: '', relaId: 'lid' },
             { data: preStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'qc_minus_qty'], prefix: 'pre_', relaId: 'lid' },
-            { data: bpcStage, fields: ['contract_pc_tp', 'qc_pc_tp', 'pc_tp'], prefix: '', relaId: 'lid' },
+            { data: bpcStage, fields: ['contract_pc_tp', 'qc_pc_tp', 'pc_tp', 'org_price'], prefix: '', relaId: 'lid' },
         ]);
         this.billsTree.loadDatas(billsData);
         this.billsTree.calculateAll();
 
-        const posData = await this.ctx.service.pos.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
+        const posData = this.ctx.stage.ledgerHis
+            ? await this.ctx.helper.loadLedgerDataFromOss(this.ctx.stage.ledgerHis.pos_file)
+            : await this.ctx.service.pos.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
         const curPosStage = this.ctx.stage.readOnly
             ? await this.ctx.service.stagePos.getAuditorStageData2(this.ctx.tender.id,
                 this.ctx.stage.id, this.ctx.stage.curTimes, this.ctx.stage.curOrder)
@@ -310,8 +314,8 @@ class StageIm {
                         (!im.code || im.code === d.code) &&
                         (!im.name || im.name === d.name) &&
                         (!im.unit || im.unit === d.unit) &&
-                        (!im.pid || im.pid === d.pid) &&
-                        (!im.pos_name || im.pos_name === d.pos_name);
+                        ((!im.pid && !d.pid) || im.pid === d.pid) &&
+                        ((!im.pos_name && !d.pos_name) || im.pos_name === d.pos_name);
                 });
                 break;
             case imType.zl.value:
@@ -321,8 +325,8 @@ class StageIm {
                         (!im.name || im.name === d.name) &&
                         (!im.unit || im.unit === d.unit) &&
                         self.ctx.helper.checkZero(self.ctx.helper.sub(im[self.up_field], d[self.up_field])) &&
-                        (!im.pid || im.pid === d.pid) &&
-                        (!im.pos_name || im.pos_name === d.pos_name);
+                        ((!im.pid && !d.pid) || im.pid === d.pid) &&
+                        ((!im.pos_name && !d.pos_name) || im.pos_name === d.pos_name);
                 });
                 break;
             case imType.bw.value:
@@ -332,8 +336,8 @@ class StageIm {
                         (!im.name || im.name === d.name) &&
                         (!im.unit || im.unit === d.unit) &&
                         self.ctx.helper.checkZero(self.ctx.helper.sub(im[self.up_field], d[self.up_field])) &&
-                        (!im.pid || im.pid === d.pid) &&
-                        (!im.pos_name || im.pos_name === d.pos_name);
+                        ((!im.pid && !d.pid) || im.pid === d.pid) &&
+                        ((!im.pos_name && !d.pos_name) || im.pos_name === d.pos_name);
                 });
                 break;
             case imType.bb.value:
@@ -341,8 +345,8 @@ class StageIm {
                     return im.lid === d.lid &&
                         (!im.name || im.name === d.name) &&
                         (!im.unit || im.unit === d.unit) &&
-                        (!im.pid || im.pid === d.pid) &&
-                        (!im.pos_name || im.pos_name === d.pos_name);
+                        ((!im.pid && !d.pid) || im.pid === d.pid) &&
+                        ((!im.pos_name && !d.pos_name) || im.pos_name === d.pos_name);
                 });
                 break;
         }
@@ -575,7 +579,7 @@ class StageIm {
                 helper.checkZero(helper.sub(bills.unit_price, x.unit_price));
         });
         if (!gcl) {
-            gcl = {b_code: bills.b_code, name: bills.name, unit: bills.unit, unit_price: bills.unit_price};
+            gcl = { b_code: bills.b_code, name: bills.name, unit: bills.unit, unit_price: bills.unit_price, org_price: bills.org_price };
             im.gclBills.push(gcl);
         }
         if (pos) {
@@ -641,6 +645,7 @@ class StageIm {
         }
     }
     _calculateBwBillsIm(im) {
+        const helper = this.ctx.helper;
         const tp_decimal = this.ctx.tender.info.decimal.tp;
         im.contract_jl = 0;
         im.qc_jl = 0;
@@ -652,8 +657,20 @@ class StageIm {
             im.qc_jl = this.ctx.helper.add(im.qc_jl, this.ctx.helper.mul(b.qc_jl, b.unit_price, tp_decimal));
             im.pre_qc_jl = this.ctx.helper.add(im.pre_qc_jl, this.ctx.helper.mul(b.pre_qc_jl, b.unit_price, tp_decimal));
             im.end_qc_jl = this.ctx.helper.add(im.end_qc_jl, this.ctx.helper.mul(b.end_qc_jl, b.unit_price, tp_decimal));
+
+            const priceDiff = b.org_price ? helper.sub(b.unit_price, b.org_price) : 0;
+            if (priceDiff) {
+                const contract_pc_tp = helper.sub(helper.mul(b.unit_price, b.pre_contract_jl, tp_decimal), helper.mul(b.org_price, b.pre_contract_jl, tp_decimal));
+                const qc_pc_tp = helper.sub(helper.mul(b.unit_price, b.pre_qc_jl, tp_decimal), helper.mul(b.org_price, b.pre_qc_jl, tp_decimal));
+                const pc_tp = helper.add(contract_pc_tp, qc_pc_tp);
+                im.contract_pc_jl = helper.add(contract_pc_tp, im.contract_pc_jl);
+                im.qc_pc_jl = helper.add(qc_pc_tp, im.qc_pc_jl);
+                im.pc_jl = helper.add(pc_tp, im.pc_jl);
+                im.end_contract_jl = helper.add(contract_pc_tp, im.end_contract_jl);
+                im.end_qc_jl = helper.add(qc_pc_tp, im.end_qc_jl);
+            }
         }
-        im.jl = this.ctx.helper.add(im.contract_jl, im.qc_jl);
+        im.jl = this.ctx.helper.sum([im.contract_jl, im.qc_jl, im.pc_jl]);
         im.pre_jl = this.ctx.helper.add(im.pre_contract_jl, im.pre_qc_jl);
         im.end_jl = this.ctx.helper.add(im.end_contract_jl, im.end_qc_jl);
     }

+ 27 - 4
app/lib/sum_load.js

@@ -344,6 +344,11 @@ class gatherStageGclTree extends loadGclBaseTree {
                 org_contract_tp: d.contract_tp || 0,
                 org_qc_qty: d.qc_qty || 0,
                 org_qc_tp: d.qc_tp || 0,
+                org_qc_minus_qty: d.qc_minus_qty || 0,
+                org_positive_qc_qty: d.positive_qc_qty || 0,
+                org_positive_qc_tp: d.positive_qc_tp || 0,
+                org_negative_qc_qty: d.negative_qc_qty || 0,
+                org_negative_qc_tp: d.negative_qc_tp || 0,
                 org_order: d.order,
                 contract_qty: 0,
                 contract_tp: 0,
@@ -380,6 +385,11 @@ class gatherStageGclTree extends loadGclBaseTree {
 
             node.qc_qty = this.ctx.helper.add(node.qc_qty, source.qc_qty);
             node.qc_tp = this.ctx.helper.mul(node.unit_price, node.qc_qty, this.ctx.tender.info.decimal.tp);
+            node.qc_minus_qty = this.ctx.helper.add(node.qc_minus_qty, source.qc_minus_qty);
+            node.positive_qc_qty = this.ctx.helper.add(node.positive_qc_qty, source.positive_qc_qty);
+            node.positive_qc_tp = this.ctx.helper.mul(node.unit_price, node.positive_qc_qty, this.ctx.tender.info.decimal.tp);
+            node.negative_qc_qty = this.ctx.helper.add(node.negative_qc_qty, source.negative_qc_qty);
+            node.negative_qc_tp = this.ctx.helper.mul(node.unit_price, node.negative_qc_qty, this.ctx.tender.info.decimal.tp);
         }
         this._gatherChange(node, source);
         return node;
@@ -390,17 +400,27 @@ class gatherStageGclTree extends loadGclBaseTree {
             if (!this.cover && !bn.is_import && !bn.contract_qty && !bn.qc_qty && !bn.contract_tp) continue;
 
             if (!bn.is_import && bn.org_qc_qty !== 0 && bn.qc_qty !== 0) {
-                result.errors.push({ b_code: bn.b_code, name: bn.name, unit: bn.unit, qc_qty: bn.qc_qty, ledger_id: bn.ledger_id, type: 'qc-conflict'});
+                result.errors.push({ b_code: bn.b_code, name: bn.name, unit: bn.unit, qc_qty: bn.qc_qty, qc_minus_qty: bn.qc_minus_qty, ledger_id: bn.ledger_id, type: 'qc-conflict'});
                 continue;
             }
-            if (bn.is_import || this.cover || bn.contract_qty !== bn.org_contract_qty || bn.contract_tp !== bn.org_contract_tp || bn.qc_qty !== bn.org_qc_qty) {
+            if (bn.is_import || this.cover || bn.contract_qty !== bn.org_contract_qty || bn.contract_tp !== bn.org_contract_tp || bn.qc_qty !== bn.org_qc_qty || bn.qc_minus_qty != bn.org_qc_minus_qty) {
                 let data = { lid: bn.id, contract_qty: bn.contract_qty, contract_tp: bn.contract_tp };
                 if (!bn.is_import && bn.org_qc_qty) {
                     data.qc_qty = bn.org_qc_qty;
                     data.qc_tp = bn.org_qc_tp;
+                    data.qc_minus_qty = bn.org_qc_minus_qty;
+                    data.positive_qc_qty = bn.positive_qc_qty;
+                    data.positive_qc_tp = bn.positive_qc_tp;
+                    data.negative_qc_qty = bn.negative_qc_qty;
+                    data.negative_qc_tp = bn.negative_qc_tp;
                 } else {
                     data.qc_qty = bn.qc_qty;
                     data.qc_tp = bn.qc_tp;
+                    data.qc_minus_qty = bn.qc_minus_qty;
+                    data.positive_qc_qty = bn.positive_qc_qty;
+                    data.positive_qc_tp = bn.positive_qc_tp;
+                    data.negative_qc_qty = bn.negative_qc_qty;
+                    data.negative_qc_tp = bn.negative_qc_tp;
                 }
                 result.update.push(data);
             }
@@ -413,7 +433,7 @@ class gatherStageGclTree extends loadGclBaseTree {
             }
         }
         for (const i of this.items) {
-            result.errors.push({ b_code: i.b_code, name: i.name, unit: i.unit, qty: i.contract_qty, qc_qty: i.qc_qty, type: 'miss' });
+            result.errors.push({ b_code: i.b_code, name: i.name, unit: i.unit, qty: i.contract_qty, qc_qty: i.qc_qty, qc_minus_qty: i.qc_minus_qty, type: 'miss' });
         }
         return result;
     }
@@ -502,6 +522,9 @@ class sumLoad {
             b.contract_qty = csb.contract_qty;
             b.contract_tp = csb.contract_tp;
             b.qc_qty = csb.qc_qty;
+            b.qc_minus_qty = csb.qc_minus_qty;
+            b.positive_qc_qty = csb.positive_qc_qty;
+            b.negative_qc_qty = csb.negative_qc_qty;
         }
         for (const csc of curStageChange) {
             if (!csc.qty) continue;
@@ -530,7 +553,7 @@ class sumLoad {
         this.ctx.helper.assignRelaData(posterity, [
             { data: extraData, fields: ['is_import'], prefix: '', relaId: 'id' },
             { data: importLid, fields: ['is_import'], prefix: '', relaId: 'lid' },
-            { data: stageBills, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid' },
+            { data: stageBills, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'qc_minus_qty', 'positive_qc_qty', 'positive_qc_tp', 'negative_qc_qty', 'negative_qc_tp'], prefix: '', relaId: 'lid' },
         ]);
         const pos = await this.ctx.service.pos.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
         this.loadTree.loadBase(posterity, pos);

+ 3 - 3
app/lib/tender_info.js

@@ -85,8 +85,8 @@ class TenderInfo {
             const chapter = this._findGclChapter(gclChapter, b, 'b_code');
             if (!chapter) continue;
             chapter.total_price = this.ctx.helper.add(chapter.total_price, b.total_price);
-            chapter.contract_tp = this.ctx.helper.add(chapter.contract_tp, b.contract_tp);
-            chapter.qc_tp = this.ctx.helper.add(chapter.qc_tp, b.qc_tp);
+            chapter.contract_tp = this.ctx.helper.sum([chapter.contract_tp, b.contract_tp, b.contract_pc_tp]);
+            chapter.qc_tp = this.ctx.helper.sum([chapter.qc_tp, b.qc_tp, b.qc_pc_tp]);
             chapter.pre_contract_tp = this.ctx.helper.add(chapter.pre_contract_tp, b.pre_contract_tp);
             chapter.pre_qc_tp = this.ctx.helper.add(chapter.pre_qc_tp, b.pre_qc_tp);
         }
@@ -105,4 +105,4 @@ class TenderInfo {
     }
 }
 
-module.exports = TenderInfo;
+module.exports = TenderInfo;

+ 1 - 0
app/middleware/session_auth.js

@@ -37,6 +37,7 @@ module.exports = options => {
             this.session.sessionProject.custom = projectData.custom;
             this.session.sessionProject.dataCollect = projectData.data_collect;
             this.session.sessionProject.customType = projectData.customType;
+            this.session.sessionProject.funSet = projectData.fun_set ? JSON.parse(projectData.fun_set) : null;
             // 判断是否有权限查看决策大屏
             let showDataCollect = 0;
             if (projectData.data_collect) {

+ 25 - 14
app/middleware/stage_check.js

@@ -67,15 +67,32 @@ module.exports = options => {
             // todo 校验权限 (标段参与人、分享、游客)
             const accountId = this.session.sessionUser.accountId,
                 auditorIds = _.map(stage.auditors, 'aid'),
+                userAssistIds = _.map(stage.userAssists, 'ass_user_id'),
+                auditAssistIds = _.map(stage.auditAssists, 'ass_user_id'),
                 shareIds = [];
-            stage.users = stage.status === status.uncheck ? [stage.user_id] : [stage.user_id, ...auditorIds];
+            let auditAssists = yield this.service.stageAuditAss.getData(stage);
+            auditAssists = auditAssists.filter(x => {
+                return x.user_id === stage.user_id || auditorIds.indexOf(x.user_id) >= 0;
+            });
+            stage.userAssists = auditAssists.filter(x => { return x.user_id === stage.user_id; }); // 原报协同人
+            stage.auditAssists = auditAssists.filter(x => { return x.user_id !== stage.user_id; }); // 审批协同人
+            stage.users = stage.status === status.uncheck ? [stage.user_id, ...userAssistIds] : [stage.user_id, ...userAssistIds, ...auditorIds, ...auditAssistIds];
+            stage.relaAssists = auditAssists.filter(x => { return x.user_id === accountId });
+            if (stage.status === status.uncheck || stage.status === status.checkNo) {
+                stage.readOnly = accountId !== stage.user_id && userAssistIds.indexOf(accountId) < 0;
+                if (!stage.readOnly) stage.assist = stage.userAssists.find(x => { return x.ass_user_id === accountId; });
+            } else if (stage.status === status.checked) {
+                stage.readOnly = true;
+            } else {
+                const ass = stage.auditAssists.find(x => { return x.user_id === stage.curAuditor.aid && x.ass_user_id === accountId; });
+                stage.readOnly = stage.curAuditor.aid !== accountId && !ass;
+                if (stage.readOnly) stage.assist = ass;
+            }
+            if (stage.readOnly)  {
+                stage.assist = accountId === stage.user_id || auditorIds.indexOf(accountId) >= 0 ? null : auditAssists.find(x => { return x.ass_user_id === accountId});
+            }
             const permission = this.session.sessionUser.permission;
-            if (accountId === stage.user_id) { // 原报
-                if (stage.curAuditor) {
-                    stage.readOnly = stage.curAuditor.aid !== accountId;
-                } else {
-                    stage.readOnly = stage.status !== status.uncheck && stage.status !== status.checkNo;
-                }
+            if (accountId === stage.user_id || userAssistIds.indexOf(accountId) >= 0) { // 原报
                 stage.curTimes = stage.times;
                 if (stage.status === status.uncheck || stage.status === status.checkNo) {
                     stage.curOrder = 0;
@@ -85,7 +102,7 @@ module.exports = options => {
                     stage.curOrder = stage.curAuditor.aid === accountId ? stage.curAuditor.order : stage.curAuditor.order - 1;
                 }
                 stage.filePermission = true;
-            } else if (auditorIds.indexOf(accountId) !== -1) { // 审批人
+            } else if (auditorIds.indexOf(accountId) !== -1 || auditAssistIds.indexOf(accountId) >= 0) { // 审批人/协审
                 if (stage.status === status.uncheck) {
                     throw '您无权查看该数据';
                 }
@@ -100,7 +117,6 @@ module.exports = options => {
                 } else {
                     stage.curOrder = accountId === stage.curAuditor.aid ? stage.curAuditor.order : stage.curAuditor.order - 1;
                 }
-                stage.readOnly = (stage.status !== status.checking && stage.status !== status.checkNoPre) || accountId !== stage.curAuditor.aid;
                 stage.filePermission = true;
             } else if (shareIds.indexOf(accountId) !== -1 || (permission !== null && permission.tender !== undefined && permission.tender.indexOf('2') !== -1)) { // 分享人
                 if (stage.status === status.uncheck) {
@@ -118,11 +134,6 @@ module.exports = options => {
                 }
                 stage.filePermission = false;
             } else if (this.tender.isTourist || this.session.sessionUser.is_admin) {
-                if (auditorIds.indexOf(accountId) !== -1) {
-                    stage.readOnly = (stage.status !== status.checking && stage.status !== status.checkNoPre) || accountId !== stage.curAuditor.aid;
-                } else {
-                    stage.readOnly = true;
-                }
                 stage.curTimes = stage.times;
                 if (stage.status === status.uncheck || stage.status === status.checkNo) {
                     stage.curOrder = 0;

+ 7 - 1
app/middleware/tender_check.js

@@ -57,6 +57,11 @@ module.exports = options => {
             const auditorsId = this.helper._.map(auditors, 'audit_id');
             const stageAuditors = yield this.service.stageAudit.getAllAuditors(tender.id);
             const stageAuditorsId = this.helper._.map(stageAuditors, 'aid');
+            let auditAssists = yield this.service.auditAss.getData(tender.id);
+            auditAssists = auditAssists.filter(x => {
+                return x.user_id === tender.data.user_id || stageAuditorsId.indexOf(x.user_id) >= 0;
+            });
+            const auditAssistsId = this.helper._.map(auditAssists, 'ass_user_id');
             const changeAuditors = yield this.service.changeAudit.getAllAuditors(tender.id);
             const changeAuditorsId = this.helper._.map(changeAuditors, 'uid');
             const reviseAuditors = yield this.service.reviseAudit.getAllAuditors(tender.id);
@@ -79,7 +84,7 @@ module.exports = options => {
             tender.touristPermission = yield this.service.tenderTourist.getTouristPermission(isTenderTourist);
             if (auditorsId.indexOf(accountId) === -1 && tender.data.user_id !== accountId &&
                 (tenderPermission === null || tenderPermission === undefined || tenderPermission.indexOf('2') === -1) &&
-                stageAuditorsId.indexOf(accountId) === -1 && changeAuditorsId.indexOf(accountId) === -1 &&
+                stageAuditorsId.indexOf(accountId) === -1 && auditAssistsId.indexOf(accountId) === -1 && changeAuditorsId.indexOf(accountId) === -1 &&
                 reviseAuditorsId.indexOf(accountId) === -1 && materialAuditorsId.indexOf(accountId) === -1 &&
                 changeProjectAuditorsId.indexOf(accountId) === -1 && changeProjectXsAuditorsId.indexOf(accountId) === -1 &&
                 changeApplyAuditorsId.indexOf(accountId) === -1 && changePlanAuditorsId.indexOf(accountId) === -1 &&
@@ -112,6 +117,7 @@ module.exports = options => {
             tender.schedule_permission = schedule_permission;
             yield next;
         } catch (err) {
+            console.log(err);
             // 输出错误到日志
             if (err.stack) {
                 this.logger.error(err);

+ 19 - 0
app/public/css/main.css

@@ -192,6 +192,10 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
 .in-4{padding-left:63px!important}
 .in-5{padding-left:84px!important}
 .in-6{padding-left:105px!important}
+.in-7{padding-left:126px!important}
+.in-8{padding-left:147px!important}
+.in-9{padding-left:168px!important}
+.in-10{padding-left:189px!important}
 /*滚动条*/
 /* 滚动条 */
 /*水平滚动条的样式*/
@@ -2042,3 +2046,18 @@ animation:shake 1s .2s ease both;}
 .card-gk-active .card-gk-bottom{
   display: inline-block;
 }
+
+.stamp-img{
+  cursor: pointer;
+}
+
+.private-stamp-img{
+  display: inline-block;
+  margin: auto;
+  vertical-align: middle; 
+}
+.private-stamp-img .check-state{
+  position: absolute;
+  right: 10px;
+  top: 10px;
+}

+ 50 - 9
app/public/js/change_information.js

@@ -87,15 +87,16 @@ $(document).ready(() => {
 
     // 计算最新的变更总额和change的total_price是否一致,不一致则更新
     if (changeStatus !== auditConst.status.checked) {
-        let new_tp = 0;
-        for (const c of changeList) {
-            new_tp = ZhCalc.add(new_tp, ZhCalc.round(ZhCalc.mul(ZhCalc.round(c.spamount, findDecimal(c.unit)), ZhCalc.round(c.unit_price, unitPriceUnit)), totalPriceUnit));
-        }
-        console.log(changeTp, new_tp);
-        if (changeTp !== new_tp) {
-            postData(window.location.pathname + '/save', { type:'update_tp', updateData: new_tp }, function (result) {
-            });
-        }
+        calcChangePrice();
+        // let new_tp = 0;
+        // for (const c of changeList) {
+        //     new_tp = ZhCalc.add(new_tp, ZhCalc.round(ZhCalc.mul(ZhCalc.round(c.spamount, findDecimal(c.unit)), ZhCalc.round(c.unit_price, unitPriceUnit)), totalPriceUnit));
+        // }
+        // console.log(changeTp, new_tp);
+        // if (changeTp !== new_tp) {
+        //     postData(window.location.pathname + '/save', { type:'update_tp', updateData: new_tp }, function (result) {
+        //     });
+        // }
     }
 
     //tab change
@@ -496,6 +497,46 @@ $(document).ready(() => {
         xmjSpread.refresh();
     }
 });
+
+function calcChangePrice() {
+    let positive_tp = 0;
+    let negative_tp = 0;
+    let new_tp = 0;
+    for (const c of changeList) {
+        if (c.spamount) {
+            const price = ZhCalc.round(ZhCalc.mul(ZhCalc.round(c.spamount, findDecimal(c.unit)), ZhCalc.round(c.unit_price, unitPriceUnit)), totalPriceUnit);
+            new_tp = ZhCalc.add(new_tp, price);
+            if (price >= 0) {
+                positive_tp = ZhCalc.add(positive_tp, price);
+            } else {
+                negative_tp = ZhCalc.add(negative_tp, price);
+            }
+        }
+    }
+    const updateTpList = {};
+    let updateFlag = false;
+    if (changeTp !== new_tp) {
+        updateTpList.total_price = new_tp;
+        updateFlag = true;
+    }
+    if (positive_tp !== changePp) {
+        updateTpList.positive_tp = positive_tp;
+        updateFlag = true;
+    }
+    if (negative_tp !== changeNp) {
+        updateTpList.negative_tp = negative_tp;
+        updateFlag = true;
+    }
+    if (updateFlag) {
+        console.log(updateTpList);
+        postData(window.location.pathname + '/save', { type:'update_tp', updateData: updateTpList }, function () {
+            changePp = positive_tp;
+            changeNp = negative_tp;
+            changeTp = new_tp;
+        });
+    }
+}
+
 function findDecimal(unit) {
     let value = precision.other.value;
     const changeUnits = precision;

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

@@ -200,7 +200,7 @@ $(document).ready(() => {
                     }
                     validText = ZhCalc.round(validText, findDecimal(select.unit)) || 0;
                     // 判断是否 正数必须大于等于限制值,负数必须小于等于限制值,否则无法更改
-                    const usedInfo = _.find(changeUsedData, { id: select.id });
+                    const usedInfo = _.find(changeUsedData, { cbid: select.id });
                     if (usedInfo && validText >= 0 && validText < usedInfo.qty) {
                         toastr.error('清单变更数值必须大于等于已调用值 ' + usedInfo.qty);
                         SpreadJsObj.reLoadRowData(info.sheet, info.row);

+ 34 - 12
app/public/js/change_information_set.js

@@ -984,7 +984,7 @@ $(document).ready(() => {
                 const quantity = leaf.quantity !== undefined && leaf.quantity !== null ? leaf.quantity : 0;
                 const gcl_id = leaf.gcl_id ? leaf.gcl_id : '';
                 const mx_id = leaf.mx_id ? leaf.mx_id : '';
-                const bwmx = leaf.bwmx !== undefined ? leaf.bwmx : (gcl.leafXmjs.length > 1 && gcl.name ? gcl.name : undefined);
+                const bwmx = leaf.bwmx !== undefined ? leaf.bwmx : undefined;
                 const pushMsg = leaf.code + '!_!' + (leaf.jldy ? leaf.jldy : '') + '!_!' +
                     (leaf.dwgc ? leaf.dwgc : '') + '!_!' + (leaf.fbgc ? leaf.fbgc : '') + '!_!' + (leaf.fxgc ? leaf.fxgc : '')
                     + '!_!' + (leaf.gcl_id ? leaf.gcl_id : '0') + '!_!' + (leaf.mx_id ? leaf.mx_id : '') + '!_!' +
@@ -1621,11 +1621,13 @@ function tableDataRemake(changeListData) {
                     listinfo = changeListData[clinfo.lid - 1];
                     if (listinfo === undefined) {
                         toastr.warning('台账清单列表已不存在'+ clinfo.code +',已更新变更清单列表');
-                        // changeList.splice(index, 1);
-                        removeList.push(clinfo);
+                        if (changeStatus !== auditConst.status.revise) {
+                            removeList.push(clinfo);
+                        } else {
+                            updateList.push(makeWhiteList(clinfo));
+                        }
                         continue;
                     }
-                    $('#table-list-select tr[data-index="'+ clinfo.lid +'"]').addClass('table-success');
                     let pushbwmx = '0*;*0';
                     if (listinfo.leafXmjs !== undefined) {
                         const leafInfo = listinfo.leafXmjs.find(function (item) {
@@ -1643,11 +1645,14 @@ function tableDataRemake(changeListData) {
                                 (leafInfo.fxgc ? leafInfo.fxgc : '') + '!_!' +
                                 (leafInfo.gcl_id ? leafInfo.gcl_id : '') + '!_!' +
                                 (leafInfo.mx_id ? leafInfo.mx_id : '') + '!_!' +
-                                (leafInfo.bwmx !== undefined ? leafInfo.bwmx : (listinfo.leafXmjs.length > 1 && listinfo.name ? listinfo.name : (leafInfo.jldy !== undefined ? leafInfo.jldy : ''))) + '*;*' + (leafInfo.quantity !== null ? leafInfo.quantity : 0);
+                                (leafInfo.bwmx !== undefined ? leafInfo.bwmx : (leafInfo.jldy !== undefined ? leafInfo.jldy : '')) + '*;*' + (leafInfo.quantity !== null ? leafInfo.quantity : 0);
                         } else {
                             toastr.warning('台账清单列表已不存在'+ clinfo.code +',已更新变更清单列表');
-                            // changeList.splice(index, 1);
-                            removeList.push(clinfo);
+                            if (changeStatus !== auditConst.status.revise) {
+                                removeList.push(clinfo);
+                            } else {
+                                updateList.push(makeWhiteList(clinfo));
+                            }
                             continue;
                         }
                     } else {
@@ -1661,8 +1666,8 @@ function tableDataRemake(changeListData) {
                     } else {
                         $('#table-list-select tr[data-index="'+ clinfo.lid +'"]').attr('data-bwmx', pushbwmx);
                     }
+                    $('#table-list-select tr[data-index="'+ clinfo.lid +'"]').addClass('table-success');
                 } else {
-                    $('#table-list-select tr[data-lid="'+ clinfo.lid +'"]').addClass('table-success');
                     let pushbwmx = '0*;*0';
                     if (listinfo.leafXmjs !== undefined) {
                         const leafInfo = listinfo.leafXmjs.find(function (item) {
@@ -1680,12 +1685,16 @@ function tableDataRemake(changeListData) {
                                 (leafInfo.fxgc ? leafInfo.fxgc : '') + '!_!' +
                                 (leafInfo.gcl_id ? leafInfo.gcl_id : '') + '!_!' +
                                 (leafInfo.mx_id ? leafInfo.mx_id : '') + '!_!' +
-                                (leafInfo.bwmx !== undefined ? leafInfo.bwmx : (listinfo.leafXmjs.length > 1 && listinfo.name ? listinfo.name : (leafInfo.jldy !== undefined ? leafInfo.jldy : ''))) + '*;*' + (leafInfo.quantity !== null ? leafInfo.quantity : 0);
+                                (leafInfo.bwmx !== undefined ? leafInfo.bwmx : (leafInfo.jldy !== undefined ? leafInfo.jldy : '')) + '*;*' + (leafInfo.quantity !== null ? leafInfo.quantity : 0);
                         } else {
                             // console.log(clinfo, listinfo.leafXmjs);
-                            toastr.warning('台账清单列表已不存在'+ clinfo.code +',已更新变更清单列表');
                             // changeList.splice(index, 1);
-                            removeList.push(clinfo);
+                            toastr.warning('台账清单列表已不存在'+ clinfo.code +',已更新变更清单列表');
+                            if (changeStatus !== auditConst.status.revise) {
+                                removeList.push(clinfo);
+                            } else {
+                                updateList.push(makeWhiteList(clinfo));
+                            }
                             continue;
                         }
                     } else {
@@ -1699,6 +1708,7 @@ function tableDataRemake(changeListData) {
                     } else {
                         $('#table-list-select tr[data-lid="'+ clinfo.lid +'"]').attr('data-bwmx', pushbwmx);
                     }
+                    $('#table-list-select tr[data-lid="'+ clinfo.lid +'"]').addClass('table-success');
                 }
 
                 if (info && _.findIndex(changeLedgerList, { id: clinfo.gcl_id }) !== -1) {
@@ -1733,7 +1743,7 @@ function tableDataRemake(changeListData) {
             }
         }
         if(updateList.length > 0) {
-            // console.log(updateList);
+            console.log(updateList);
             postData(window.location.pathname + '/save', { type:'update_list', updateData: updateList }, function (result) {
             }, function () {
             });
@@ -1747,6 +1757,18 @@ function tableDataRemake(changeListData) {
     }
 }
 
+function makeWhiteList(clinfo) {
+    clinfo.lid = 0;
+    clinfo.xmj_code = null;
+    clinfo.xmj_jldy = null;
+    clinfo.xmj_dwgc = null;
+    clinfo.xmj_fbgc = null;
+    clinfo.xmj_fxgc = null;
+    clinfo.gcl_id = '';
+    clinfo.mx_id = null;
+    return clinfo;
+}
+
 // 清单搜索隐藏清单table部分值
 function makeListTable(changeListData, showListData = changeListData) {
     // 先加载台账数据

+ 17 - 6
app/public/js/change_information_show.js

@@ -22,6 +22,8 @@ $(document).ready(() => {
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'ca_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.ca_tp'},
             {title: '审批后变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'samount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.samount'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sa_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.sa_tp'},
+            {title: '变更后|数量', colSpan: '2|1', rowSpan: '1|1', field: 'samount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.changed_amount'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sa_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.changed_tp'},
         ],
         emptyRows: 0,
         headRows: 2,
@@ -90,6 +92,12 @@ $(document).ready(() => {
             sa_tp: function (data) {
                 return ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.samount, findDecimal(data.unit))), totalPriceUnit);
             },
+            changed_amount: function (data) {
+                return ZhCalc.round(data.changed_amount, findDecimal(data.unit));
+            },
+            changed_tp: function (data) {
+                return ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.changed_amount, findDecimal(data.unit))), totalPriceUnit);
+            },
         },
     };
 
@@ -122,10 +130,10 @@ $(document).ready(() => {
                 for(let i = 0; i <= rowCount - 1; i++){
                     const data = {
                         unit_price: changeSpreadSheet.getValue(i, 3),
-                        amount: parseFloat(changeSpreadSheet.getValue(i, 12 + parseInt(j)*2)),
+                        amount: parseFloat(changeSpreadSheet.getValue(i, 14 + parseInt(j)*2)),
                     };
                     const sum = ZhCalc.round(ZhCalc.mul(data.unit_price, data.amount), totalPriceUnit);
-                    changeSpreadSheet.setValue(i, 13 + j*2, sum !== 0 ? sum : null);
+                    changeSpreadSheet.setValue(i, 15 + j*2, sum !== 0 ? sum : null);
                 }
             }
         },
@@ -133,27 +141,30 @@ $(document).ready(() => {
             const rowCount = changeSpreadSheet.getRowCount();
             let oSum = 0,
                 cSum = 0,
-                sSum = 0;
+                sSum = 0,
+                cdSum = 0;
             for(let i = 0; i < rowCount - 1; i++){
                 oSum = ZhCalc.add(oSum, changeSpreadSheet.getValue(i, 7));
                 cSum = ZhCalc.add(cSum, changeSpreadSheet.getValue(i, 9));
                 sSum = ZhCalc.add(sSum, changeSpreadSheet.getValue(i, 11));
+                cdSum = ZhCalc.add(cdSum, changeSpreadSheet.getValue(i, 13));
             }
             changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 7, oSum !== 0 ? oSum : null);
             changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 9, cSum !== 0 ? cSum : null);
             changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 11, sSum !== 0 ? sSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 13, cdSum !== 0 ? cdSum : null);
             // 用户的数据合计
             for (const j in aidList) {
                 let audit_sum = 0;
                 for(let i = 0; i < rowCount - 1; i++){
-                    audit_sum = ZhCalc.add(audit_sum, changeSpreadSheet.getValue(i, 13 + j*2));
+                    audit_sum = ZhCalc.add(audit_sum, changeSpreadSheet.getValue(i, 15 + j*2));
                 }
-                changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 13 + j*2, audit_sum !== 0 ? audit_sum : null);
+                changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 15 + j*2, audit_sum !== 0 ? audit_sum : null);
             }
         },
         showHideAudit: function (show = false) {
             const count = changeSpreadSetting.cols.length;
-            for (let i = 12; i < count; i++) {
+            for (let i = 14; i < count; i++) {
                 changeSpreadSheet.setColumnVisible(i, show, GC.Spread.Sheets.SheetArea.viewport);
             }
             changeSpreadSheet.setColumnVisible(10, !show, GC.Spread.Sheets.SheetArea.viewport);

+ 10 - 9
app/public/js/change_revise.js

@@ -191,7 +191,7 @@ $(document).ready(() => {
         treeSetting.calcFields.push('deal_tp');
     }
     treeSetting.calcFun = function (node) {
-        if (node && node.b_code) {
+        if (node && node.b_code && isTz) {
             const posData = pos.getLedgerPos(node.id) || [];
             if (posData.length > 0) {
                 let sgfh_qty = 0;
@@ -2578,20 +2578,21 @@ $(document).ready(() => {
     });
 
     $('#ledger-check2').click(() => {
-        const result = ledgerCheck2({
+        ledgerCheck2({
             ledgerTree: billsTree,
             ledgerPos: pos,
             checkList: checkList,
             decimal: decimal,
             checkOption: checkOption,
+        }).then(result => {
+            check2Viewing({
+                extra: ZhCalc.div(billsTree.datas.length + pos.datas.length, 10000, 0),
+                randomWait: true,
+                prefix: 'check2-',
+                checks: result,
+                checkList: checkList,
+            });
         });
-        check2Viewing({
-            extra: ZhCalc.div(billsTree.datas.length + pos.datas.length, 10000, 0),
-            randomWait: true,
-            prefix: 'check2-',
-            checks: result,
-            checkList: checkList,
-        })
     });
 });
 

File diff suppressed because it is too large
+ 9164 - 0
app/public/js/export/iconv-lite.js


File diff suppressed because it is too large
+ 673 - 0
app/public/js/export/jschardet.min.js


+ 4 - 4
app/public/js/gcl_gather.js

@@ -28,7 +28,7 @@ const gclGatherModel = (function () {
         keys: ['id', 'tender_id', 'ledger_id'],
         stageId: 'id',
     };
-    gsTreeSetting.updateFields = ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'qc_minus_qty'];
+    gsTreeSetting.updateFields = ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'contract_pc_tp', 'qc_pc_tp', 'pc_tp', 'qc_minus_qty'];
     const gsTree = createNewPathTree('stage', gsTreeSetting);
     // 初始化 部位明细
     const posSetting = {
@@ -367,11 +367,11 @@ const gclGatherModel = (function () {
             gcl.end_contract_qty = ZhCalc.add(gcl.pre_contract_qty, gcl.contract_qty);
             gcl.end_qc_qty = ZhCalc.add(gcl.pre_qc_qty, gcl.qc_qty);
             gcl.end_gather_qty = ZhCalc.add(gcl.pre_gather_qty, gcl.gather_qty);
-            gcl.gather_tp = ZhCalc.add(gcl.contract_tp, gcl.qc_tp);
             gcl.end_qc_minus_qty = ZhCalc.add(gcl.pre_qc_minus_qty, gcl.qc_minus_qty);
 
-            gcl.end_contract_tp = ZhCalc.add(gcl.pre_contract_tp, gcl.contract_tp);
-            gcl.end_qc_tp = ZhCalc.add(gcl.pre_qc_tp, gcl.qc_tp);
+            gcl.gather_tp = ZhCalc.sum([gcl.contract_tp, gcl.qc_tp, gcl.pc_tp]);
+            gcl.end_contract_tp = ZhCalc.sum([gcl.pre_contract_tp, gcl.contract_tp, gcl.contract_pc_tp]);
+            gcl.end_qc_tp = ZhCalc.sum([gcl.pre_qc_tp, gcl.qc_tp, gcl.qc_pc_tp]);
             gcl.end_gather_tp = ZhCalc.add(gcl.pre_gather_tp, gcl.gather_tp);
             gcl.dgn_price = ZhCalc.round(ZhCalc.div(gcl.total_price, gcl.dgn_qty1), 2);
             gcl.end_final_qty = ZhCalc.add(gcl.end_qc_qty, gcl.quantity);

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

@@ -291,7 +291,7 @@ const postDataAsync = function (url, data, showWaiting = true) {
         }, err => {
             reject(err);
         }, showWaiting);
-    });
+    }).catch((e) => { console.log(e) });
 };
 
 /**

+ 27 - 15
app/public/js/ledger.js

@@ -78,11 +78,6 @@ $(document).ready(function() {
         markExpandSubKey: window.location.pathname.split('/')[2],
         calcFields: ['deal_tp', 'sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price'],
     };
-    // if (checkTzMeasureType()) {
-    //     treeSetting.calcFields = ['sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price'];
-    // } else {
-    //     treeSetting.calcFields = ['deal_tp', 'sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price'];
-    // }
     treeSetting.calcFun = function (node) {
         node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
     };
@@ -953,6 +948,7 @@ $(document).ready(function() {
         },
         editStarting(e, info) {
             if (!info.sheet.zh_setting || !info.sheet.zh_tree) return;
+            const tree = info.sheet.zh_tree;
             const col = info.sheet.zh_setting.cols[info.col];
             const node = info.sheet.zh_tree.nodes[info.row];
             if (!node) {
@@ -985,6 +981,11 @@ $(document).ready(function() {
                 case 'dgn_qty2':
                     info.cancel = !_.isEmpty(node.b_code);
                     break;
+                case 'node_type':
+                    const parent = tree.getParent(node);
+                    const topParent = tree.getTopParent(node);
+                    info.cancel = !parent || !topParent || [1, 5].indexOf(topParent.node_type) < 0;
+                    break;
             }
         },
         sortCode: function (sheet) {
@@ -1072,6 +1073,7 @@ $(document).ready(function() {
     //     {title: 'full_path', colSpan: '1', rowSpan: '2', field: 'full_path', hAlign: 2, width: 60, type: 'Number', readOnly: true},
     //     {title: 'node_type', colSpan: '1', rowSpan: '2', field: 'node_type', hAlign: 2, width: 60, type: 'Number', readOnly: true}
     // );
+    sjsSettingObj.setNodeTypeCol(ledgerSpreadSetting.cols, [{field: 'node_type'}]);
     SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
     SpreadJsObj.selChangedRefreshBackColor(ledgerSpread.getActiveSheet());
     // 绑定事件
@@ -1562,7 +1564,7 @@ $(document).ready(function() {
                 importExcel.doImport({
                     template: {
                         hint: '0号台账',
-                        url: '/template/导入分项清单EXCEL格式.xlsx',
+                        url: 'https://jl-assets.oss-cn-shenzhen.aliyuncs.com/template/A/导入分项清单EXCEL格式.xlsx',
                     },
                     filter: true,
                     callback: function (sheet, filter) {
@@ -2229,7 +2231,6 @@ $(document).ready(function() {
 
     postData(window.location.pathname + '/load', {}, function (data) {
         ledgerTree.loadDatas(data.bills);
-        console.log(data.bills.find(x => { return x.b_code === '102-4'}));
         treeCalc.calculateAll(ledgerTree);
         for (const t of data.tags) {
             t.node = ledgerTree.datas.find(x => {return x.id === t.lid});
@@ -3487,6 +3488,16 @@ $(document).ready(function() {
 
         SpreadExcelObj.exportSimpleXlsxSheet(setting, data, "台账分解.xlsx");
     });
+    $('#sync-ledger').click(function () {
+        postData(syncLedgerUrl, {}, function (result) {
+            ledgerTree.loadDatas(result.bills);
+            treeCalc.calculateAll(ledgerTree);
+            SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
+            pos.loadDatas(result.pos);
+            posOperationObj.loadCurPosData();
+            checkShowLast(result.bills.length);
+        });
+    });
 
     const dataChecker = DataChecker({
         checkUrl: window.location.pathname + '/check',
@@ -3511,20 +3522,21 @@ $(document).ready(function() {
     });
 
     $('#ledger-check2').click(() => {
-        const result = ledgerCheck2({
+        ledgerCheck2({
             ledgerTree: ledgerTree,
             ledgerPos: pos,
             checkList: checkList,
             decimal: tenderInfo.decimal,
             checkOption: checkOption,
+        }).then(result => {
+            check2Viewing({
+                extra: ZhCalc.div(ledgerTree.datas.length + pos.datas.length, 10000, 0),
+                randomWait: true,
+                prefix: 'check2-',
+                checks: result,
+                checkList: checkList,
+            });
         });
-        check2Viewing({
-            extra: ZhCalc.div(ledgerTree.datas.length + pos.datas.length, 10000, 0),
-            randomWait: true,
-            prefix: 'check2-',
-            checks: result,
-            checkList: checkList,
-        })
     });
 
     // 切换附件里节点和所有附件

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

@@ -45,6 +45,7 @@ $(document).ready(() => {
     const ledgerTree = createNewPathTree('fx', treeSetting);
     sjsSettingObj.setFxTreeStyle(ledgerSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
+    sjsSettingObj.setNodeTypeCol(ledgerSpreadSetting.cols, [{field: 'node_type'}]);
     SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
 
     // 初始化 部位明细

+ 19 - 7
app/public/js/ledger_check.js

@@ -26,6 +26,7 @@ const ledgerCheckType = {
             { value: 11, text: '遗漏计量(档案管理)', key: 'daglLost', type: 'dagl', },
         ]
     },
+    minus_cb: { value: 12, text: '负变更清单漏计', url: window.location.pathname + '/stageCheck?type=minus_cb' },
 };
 const ledgerCheckUtil = {
     checkSibling: function (ledgerTree, ledgerPos, decimal, option) {
@@ -291,7 +292,7 @@ const ledgerCheckUtil = {
     },
 };
 
-const ledgerCheck2 = function (setting) {
+const ledgerCheck2 = async function (setting) {
     const ledger = setting.ledgerTree, ledgerPos = setting.ledgerPos, decimal = setting.decimal;
     const checkOption = setting.checkOption;
 
@@ -315,17 +316,28 @@ const ledgerCheck2 = function (setting) {
     for (const prop in ledgerCheckType) {
         if (!checkOption[prop] || !checkOption[prop].enable) continue;
 
-        if (ledgerCheckType[prop].items) {
-            const errors = ledgerCheckUtil[ledgerCheckType[prop].fun](ledger, ledgerPos, decimal, checkOption[prop]) || {};
-            for (const i of ledgerCheckType[prop].items) {
+        const checkInfo = ledgerCheckType[prop];
+        if (checkInfo.items) {
+            const errors = ledgerCheckUtil[checkInfo.fun](ledger, ledgerPos, decimal, checkOption[prop]) || {};
+            for (const i of checkInfo.items) {
                 if (checkOption[prop].checkType.indexOf(i.type) < 0) continue;
                 assignWarningData(errors[i.key], i.value, checkData.warning_data);
                 progressData.push({key: prop + i.key, caption: i.text, error: errors[i.key].length});
             }
+        } else if (checkInfo.url) {
+            const errors = await postDataAsync(checkInfo.url, {}, false);
+            if (errors && errors.minus_cb) {
+                errors.minus_cb.forEach(mcb => {
+                    checkData.warning_data.push({
+                        type: checkInfo.value, code: mcb.memo, b_code: mcb.b_code, name: mcb.name,
+                    });
+                });
+            }
+            progressData.push({key: prop, caption: checkInfo.text, error: errors && errors.minus_cb ? errors.minus_cb.length : 0});
         } else {
-            const errors = ledgerCheckUtil[ledgerCheckType[prop].fun](ledger, ledgerPos, decimal, checkOption[prop]) || [];
-            assignWarningData(errors, ledgerCheckType[prop].value, checkData.warning_data);
-            progressData.push({key: prop, caption: ledgerCheckType[prop].text, error: errors.length});
+            const errors = ledgerCheckUtil[checkInfo.fun](ledger, ledgerPos, decimal, checkOption[prop]) || [];
+            assignWarningData(errors, checkInfo.value, checkData.warning_data);
+            progressData.push({key: prop, caption: checkInfo.text, error: errors.length});
         }
     }
     setting.checkList.clearCheckData();

+ 67 - 27
app/public/js/material.js

@@ -88,8 +88,8 @@ function resetTpTable() {
     if (isStageSelf) {
         let html = '';
         for (const ms of materialStageData) {
-            const taxHtml = materialTax ? '                                        <td class="text-center">' + ms.m_tax_tp + '</td>\n' : '';
-            html += '<tr><td>第' + ms.order + '期</td><td class="text-center">' + ms.m_tp + '</td>\n' +
+            const taxHtml = materialTax ? '                                        <td class="text-center">' + (ms.m_tax_tp !== null ? ms.m_tax_tp : '') + '</td>\n' : '';
+            html += '<tr><td>第' + ms.order + '期</td><td class="text-center">' + (ms.m_tp !== null ? ms.m_tp : '') + '</td>\n' +
                 taxHtml +
                 '                                </tr>';
         }
@@ -136,12 +136,12 @@ $(document).ready(() => {
         {title: '工料分类', colSpan: '1', rowSpan: '2', field: 'm_type', hAlign: 1, width: 60, readOnly: 'readOnly.isEdit', cellType: 'customizeCombo', comboItems: materialType.m_type, cellTypeKey: 2},
     ];
     if (materialTax) {
-        materialSpreadSettingCols.push({title: '税率(%)', colSpan: '1', rowSpan: '2', field: 'm_tax', hAlign: 2, width: 50, type: 'Number', readOnly: editTaxPermission ? 'readOnly.isEdit' : 'readOnly.remark'});
+        materialSpreadSettingCols.push({title: '税率(%)', colSpan: '1', rowSpan: '2', field: 'm_tax', hAlign: 2, width: 50, type: 'Number', readOnly: editTaxPermission ? 'readOnly.remark' : 'readOnly.isEdit'});
     }
     materialSpreadSettingCols = _.concat(materialSpreadSettingCols, [
         {title: '上涨 幅度(%)', colSpan: '1', rowSpan: '2', field: 'm_up_risk', hAlign: 2, width: 50, type: 'Number', readOnly: 'readOnly.isEdit'},
         {title: '下跌 幅度(%)', colSpan: '1', rowSpan: '2', field: 'm_down_risk', hAlign: 2, width: 50, type: 'Number', readOnly: 'readOnly.isEdit'},
-        {title: '基准价', colSpan: '1', rowSpan: '2', field: 'basic_price', hAlign: 2, width: 50, type: 'Number', readOnly: 'readOnly.isEdit'},
+        {title: '基准价', colSpan: '1', rowSpan: '2', field: 'basic_price', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.isEdit'},
         {title: '基准时间', colSpan: '1', rowSpan: '2', field: 'basic_times', hAlign: 0, width: 70, formatter: '@', readOnly: 'readOnly.isEdit'},
         {title: '本期信息价|单价', colSpan: '3|1', rowSpan: '1|1', field: 'msg_tp', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.msg_tp'},
         {title: '|时间', colSpan: '|1', rowSpan: '|1', field: 'msg_times', hAlign: 0, width: 80, formatter: '@', readOnly: 'readOnly.remark'},
@@ -175,6 +175,7 @@ $(document).ready(() => {
     spCol.normalImg = '#ellipsis-icon';
     spCol.indent = 5;
     spCol.showImage = function (data) {
+        // console.log(data, data.t_type);
         // return !readOnly && data.t_type === 2 && data.mid === materialID;
         return data.t_type === 2;
     };
@@ -254,7 +255,7 @@ $(document).ready(() => {
         },
     };
 
-    const needUpdateArray = ['quantity', 'msg_tp', 'msg_times', 'msg_spread', 'm_spread', 'm_tp', 'm_tax_tp', 'is_summary', 'remark'];
+    const needUpdateArray = ['quantity', 'expr', 'msg_tp', 'msg_times', 'msg_spread', 'm_spread', 'm_tp', 'm_tax_tp', 'is_summary', 'remark'];
 
     const materialSpreadObj = {
         getMaterialBillsData: function () {
@@ -300,6 +301,11 @@ $(document).ready(() => {
             if (datas.pushStageBillsData && datas.pushStageBillsData.length > 0) {
                 materialStageBillsData = _.concat(materialStageBillsData, datas.pushStageBillsData);
             }
+            if (datas.removeStageBillsData && datas.removeStageBillsData.length > 0) {
+                materialStageBillsData = _.remove(materialStageBillsData, function (item) {
+                    return _.findIndex(datas.removeStageBillsData, { id: item.mb_id }) === -1
+                })
+            }
             if (datas.stageBillsData && datas.stageBillsData.length > 0) {
                 if (datas.stageBillsData.length === materialStageBillsData.length) {
                     // 全体替换
@@ -312,15 +318,32 @@ $(document).ready(() => {
                     }
                 }
             }
+            if (datas.billsData && datas.billsData.length > 0) {
+                if (datas.billsData.length === materialBillsData.length) {
+                    // 全体替换
+                    materialBillsData = datas.billsData;
+                    materialSpreadObj.getMaterialBillsData();
+                } else {
+                    for (const b of datas.billsData) {
+                        const index = _.findIndex(materialBillsData, {id: b.id});
+                        materialBillsData.splice(index, 1, b);
+                        materialSpreadObj.updateOneMaterialBill(materialBillsData[index]);
+                    }
+                }
+            }
         },
-        materialSheetReset: function () {
-            let newMaterialBillsData = materialBillsData;
-            console.log(materialBillsData);
+        materialSheetReset: function (redo = false) {
+            let newMaterialBillsData = _.cloneDeep(materialBillsData);
             if($('#bills0_list').is(':checked')) {
                 newMaterialBillsData = _.filter(materialBillsData, function (item) {
                     return item.quantity !== null && item.quantity !== 0;
                 });
+                if (redo) {
+                    SpreadJsObj.initSpreadSettingEvents(materialSpreadSetting, materialCol);
+                    SpreadJsObj.initSheet(materialSpread.getActiveSheet(), materialSpreadSetting);
+                }
             }
+            console.log(newMaterialBillsData);
             SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, newMaterialBillsData);
             materialSpreadObj.refreshActn();
         },
@@ -334,10 +357,23 @@ $(document).ready(() => {
             };
             const sheet = materialSpread.getActiveSheet();
             const select = SpreadJsObj.getSelectObject(sheet);
+            const selection = sheet.getSelections();
+            const sel = selection ? selection[0] : sheet.getSelections()[0];
+            const row = sel ? sel.row : -1;
+            const first = materialBillsData[row];
+            let last = first;
+            if (sel.rowCount > 1 && first) {
+                for (let r = 1; r < sel.rowCount; r++) {
+                    const rNode = materialBillsData[sel.row + r];
+                    if (!rNode) break;
+                    last = rNode;
+                }
+            }
+            const preNode = materialBillsData[row - 1];
             // 还需判断是否已被调差清单调用
             setObjEnable($('#del'), !readOnly && select && materialBase.isUsed(select) && rowCount === 1);
-            setObjEnable($('#up-move'), !readOnly && select && materialBillsData.indexOf(select) > 0 && !$('#bills0_list').is(':checked'));
-            setObjEnable($('#down-move'), !readOnly && select && materialBillsData.indexOf(select) < materialBillsData.length - 1 && !$('#bills0_list').is(':checked'));
+            setObjEnable($('#up-move'), !readOnly && first && preNode && materialBillsData.indexOf(last) > 0 && sel.row + sel.rowCount <= materialBillsData.length && !$('#bills0_list').is(':checked'));
+            setObjEnable($('#down-move'), !readOnly && first && materialBillsData.indexOf(last) < materialBillsData.length - 1 && sel.row + sel.rowCount <= materialBillsData.length && !$('#bills0_list').is(':checked'));
         },
         add: function () {
             const sheet = materialSpread.getActiveSheet();
@@ -381,30 +417,32 @@ $(document).ready(() => {
         del: function () {
             const sheet = materialSpread.getActiveSheet();
             const select = SpreadJsObj.getSelectObject(sheet);
+            const sel = sheet.getSelections();
             postData(window.location.pathname + '/save', {type: 'del', id: select.id}, function (result) {
                 m_tp = result.m_tp;
                 if (materialTax) {
                     m_tax_tp = result.m_tax_tp;
                 }
 
-                const index = materialBillsData.indexOf(select);
+                const index = _.findIndex(materialBillsData, { id: select.id });
+                materialBillsData.splice(index, 1);
+                if (isStageSelf) {
+                    result.removeStageBillsData = [{ id: select.id }];
+                    materialSpreadObj.updateMaterialData(result);
+                    materialSpreadObj.getMaterialBillsData();
+                }
                 let newMaterialBillsData = materialBillsData;
                 let newIndex = index;
                 if($('#bills0_list').is(':checked')) {
                     newMaterialBillsData = _.filter(materialBillsData, function (item) {
                         return item.quantity !== null && item.quantity !== 0;
                     });
-                    newIndex = newMaterialBillsData.indexOf(select);
+                    newIndex = _.findIndex(newMaterialBillsData, { id: select.id });
                 }
-                materialBillsData.splice(index, 1);
                 sheet.deleteRows(newIndex, 1);
-                if (isStageSelf) {
-                    materialSpreadObj.updateMaterialData(result);
-                    materialSpreadObj.getMaterialBillsData();
-                }
+
                 resetTpTable();
                 // SpreadJsObj.reLoadSheetData(materialSpread.getActiveSheet());
-                const sel = sheet.getSelections();
                 sheet.setSelection(newIndex > 0 ? newIndex - 1 : 0, sel.length > 0 ? sel[0].col : 0, 1, 1);
                 materialSpreadObj.refreshActn();
                 // 月信息价需要同时删除
@@ -532,7 +570,7 @@ $(document).ready(() => {
             materialSpreadObj.refreshActn(sel.rowCount);
             const data = SpreadJsObj.getSelectObject(info.sheet);
             materialSpreadObj.setReadOnly(true);
-            console.log(data);
+            // console.log(data);
         },
         editEnded: function (e, info) {
             if (info.sheet.zh_setting) {
@@ -618,7 +656,7 @@ $(document).ready(() => {
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
                     // 判断如果是更改了编号,名称,单位,月信息价需要跟着改变值
                     if (months.length > 0 && (col.field === 'code' || col.field === 'name' || col.field === 'unit')) {
-                        const index = materialBillsData.indexOf(select);
+                        const index = _.findIndex(materialBillsData, { id: select.id });
                         monthsList[index][col.field] = validText;
                         monthFunGather.monthsListSet();
                     }
@@ -816,7 +854,6 @@ $(document).ready(() => {
             // SpreadJsObj.resetFieldReadOnly(materialSpread.getActiveSheet(), 'msg_spread', 'm_spread', 'm_tp', 'pre_tp', readOnly);
         }
     };
-    materialSpreadObj.refreshActn();
     materialSpread.bind(spreadNS.Events.SelectionChanged, materialSpreadObj.selectionChanged);
     materialSpread.bind(spreadNS.Events.ClipboardPasted, materialSpreadObj.clipboardPasted);
     SpreadJsObj.addDeleteBind(materialSpread, materialSpreadObj.deletePress);
@@ -824,6 +861,7 @@ $(document).ready(() => {
     materialSpread.bind(spreadNS.Events.ButtonClicked, materialSpreadObj.buttonClicked);
     const sheet = materialSpread.getActiveSheet();
     sheet.suspendPaint();
+    // materialSpreadObj.refreshActn();
     // const basic_range = sheet.getRange(-1, 8, -1, 1);
     // basic_range.cellType(new DatePickerCellType());
     // basic_range.formatter("yyyy-MM-dd");
@@ -1151,6 +1189,7 @@ $(document).ready(() => {
     SpreadJsObj.initSheet(materialSpread.getActiveSheet(), materialSpreadSetting);
     materialSpreadObj.getMaterialBillsData();
     SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialBillsData);
+    materialSpreadObj.refreshActn();
 
     materialMonthSpread.bind(spreadNS.Events.ClipboardPasted, materialMonthSpreadObj.clipboardPasted);
     SpreadJsObj.addDeleteBind(materialMonthSpread, materialMonthSpreadObj.deletePress);
@@ -1162,7 +1201,8 @@ $(document).ready(() => {
         showWaitingView();
         setTimeout(function () {
             materialSpreadObj.getMaterialBillsData();
-            SpreadJsObj.reLoadSheetData(materialSpread.getActiveSheet());
+            materialSpreadObj.materialSheetReset(true);
+            // SpreadJsObj.reLoadSheetData(materialSpread.getActiveSheet());
             // 消耗量表格更新
             let html = '';
             calcBase = _.find(calcBaseList, { ms_id: $('#myTab').find('.active').data('msid') }).calcBase;
@@ -1319,7 +1359,7 @@ $(document).ready(() => {
                 toastr.error(msg);
                 return false;
             }
-            postData(window.location.pathname + '/save', { type:'expr', id: $('#materialbillsId').val(), expr: expr }, function (result) {
+            postData(window.location.pathname + '/save', { type:'expr', id: $('#materialbillsId').val(), expr: expr, ms_id: $('#myTab').find('.active').data('msid') || null }, function (result) {
                 m_tp = result.m_tp;
                 if (materialTax) {
                     m_tax_tp = result.m_tax_tp;
@@ -1327,23 +1367,23 @@ $(document).ready(() => {
 
                 const sheet = materialSpread.getActiveSheet();
                 const select = SpreadJsObj.getSelectObject(sheet);
-                const index = materialBillsData.indexOf(select);
-                console.log(select, index, result.info);
+                const index = _.findIndex(materialBillsData, { id: select.id });
                 let newIndex = index;
                 if (isStageSelf) {
                     materialSpreadObj.updateMaterialData(result);
                     result.info = materialSpreadObj.updateOneMaterialBill(result.info);
-                    console.log(result.info);
                 }
                 if($('#bills0_list').is(':checked')) {
                     const newMaterialBillsData = _.filter(materialBillsData, function (item) {
                         return item.quantity !== null && item.quantity !== 0;
                     });
-                    newIndex = newMaterialBillsData.indexOf(select);
+                    newIndex = _.findIndex(newMaterialBillsData, { id: select.id });
+                    // newIndex = newMaterialBillsData.indexOf(select);
                     newMaterialBillsData.splice(newIndex, 1, result.info);
                     sheet.zh_data[newIndex] = result.info;
                 }
                 materialBillsData.splice(index, 1, result.info);
+                sheet.zh_data.splice(newIndex, 1, result.info);
                 SpreadJsObj.reLoadRowData(sheet, newIndex);
                 resetTpTable();
                 $('#bcyy').modal('hide');

+ 310 - 2
app/public/js/material_checklist.js

@@ -138,8 +138,8 @@ $(document).ready(() => {
             // updateBillsData(ms_id);
             const newGclGatherListData = [];
             for (const [index, s] of result.ledgerListData.entries()) {
-                gclGatherModel.loadLedgerData(ledger, s);
-                gclGatherModel.loadPosData(pos, result.posListData[index]);
+                gclGatherModel.loadLedgerData(_.cloneDeep(ledger), s);
+                gclGatherModel.loadPosData(_.cloneDeep(pos), result.posListData[index]);
                 const oneGclGatherData = gclGatherModel.gatherGclData().filter(item => {
                     return item.qc_qty || item.contract_qty
                 });
@@ -1081,4 +1081,312 @@ $(document).ready(() => {
         ledgerSpread.refresh();
         materialSpread.refresh();
     }
+
+    // 导入功能
+    // 上传图片
+    $('#upload-list').click(function () {
+        // if (materialChecklistData.length === 0) {
+        //     toastr.error('请选择调差清单再导入。');
+        //     return
+        // }
+        $(this).siblings('input').trigger('click');
+    });
+    $('#upload-list-file').change(function () {
+        const file = this.files[0];
+        const ext = file.name.toLowerCase().split('.').splice(-1)[0];
+        const imgStr = /(xls|xlsx|json|XLS|XLSX|JSON)$/;
+        if (!imgStr.test(ext)) {
+            toastr.error('请导入正确格式的json或excel文件。');
+            return
+        }
+        const fileReader = new FileReader();
+        fileReader.onload = async function(ev) {
+            try{
+                const data = ev.target.result;
+                let tree = [];
+                resetExport();
+                $('#okedit').modal('show');
+                setProgress($('#export-progress'), 30);
+                if (/(xls|xlsx|XLS|XLSX)$/.test(ext)) {
+                    const workbook = XLSX.read(data, {type: 'binary'}); // 以二进制流方式读取得到整份excel表格对象
+                    const jsonData = transExcel(XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]], { defval: null }));
+                    if (!(jsonData[0] && jsonData[0].b_code !== undefined && jsonData[0].b_code !== null &&
+                        jsonData[0].GLJcode !== undefined && jsonData[0].name !== undefined && jsonData[0].name !== undefined &&
+                        jsonData[0].unit !== undefined && jsonData[0].unit_price !== undefined)) {
+                        throw 'excel必须按指定格式内容上传';
+                    }
+                    tree = _.filter(jsonData, function (item) {
+                        return item.b_code !== null;
+                    });
+                    for (const [i,t] of tree.entries()) {
+                        const jIndex1 = _.findIndex(jsonData, { b_code: t.b_code, name: t.name, unit: t.unit, unit_price: t.unit_price });
+                        if (i + 1 < tree.length) {
+                            const jIndex2 = _.findIndex(jsonData, { b_code: tree[i+1].b_code, name: tree[i+1].name, unit: tree[i+1].unit, unit_price: tree[i+1].unit_price });
+                            t.children = jsonData.slice(jIndex1 + 1, jIndex2);
+                        } else {
+                            t.children = jsonData.slice(jIndex1 + 1);
+                        }
+                    }
+                } else {
+                    const ascii = jschardet.detect(data.substring(0, 10000));
+                    iconv.skipDecodeWarning = true
+                    tree = JSON.parse(iconv.decode(data, ascii.encoding));// 需要转码,否则前端处理中文会出现乱码
+                    if (!(tree[0] && tree[0].b_code !== undefined && tree[0].b_code !== null &&
+                        tree[0].GLJcode !== undefined && tree[0].name !== undefined && tree[0].name !== undefined &&
+                        tree[0].unit !== undefined && tree[0].unit_price !== undefined)) {
+                        throw 'json必须按指定格式内容上传';
+                    }
+                }
+                stopProgress($('#export-progress'));
+                $('#bill-detail').show();
+                setProgress($('#bill-progress'), 30);
+                // 导入先生成materialCheckList,再生成materialBillsData,最后生成materialListData
+                console.log(tree, gclGatherData, materialChecklistData, materialBillsData);
+                const pushChecklist = [];
+                const pushBillsData = [];
+                const needPushTree = [];
+                // 分析materialCheckList和tree,找出相同的清单并插入对应不存在的工料
+                for (const t of tree) {
+                    const order = _.findIndex(gclGatherData, { b_code: t.b_code, name: t.name, unit: t.unit, unit_price: t.unit_price ? parseFloat(t.unit_price) : null });
+                    const mlOrder = _.findIndex(materialChecklistData, { b_code: t.b_code, name: t.name, unit: t.unit, unit_price: t.unit_price ? parseFloat(t.unit_price) : null });
+                    if (mlOrder === -1 && order !== -1 && _.findIndex(pushChecklist, { b_code: t.b_code, name: t.name, unit: t.unit, unit_price: t.unit_price ? parseFloat(t.unit_price) : null}) === -1) {
+                        pushChecklist.push({
+                            b_code: gclGatherData[order].b_code,
+                            name: gclGatherData[order].name,
+                            unit: gclGatherData[order].unit,
+                            unit_price: gclGatherData[order].unit_price,
+                            quantity: gclGatherData[order].quantity ? gclGatherData[order].quantity : null,
+                            total_price: gclGatherData[order].total_price ? gclGatherData[order].total_price : null,
+                            had_bills: 0,
+                        });
+                    } else if (mlOrder === -1 && order === -1) {
+                        continue;
+                    }
+                    needPushTree.push(t);
+                    for (const c of t.children) {
+                        const mbOrder = _.findIndex(materialBillsData, { code: c.GLJcode, name: c.name, unit: c.unit });
+                        if (c.b_code !== null && mbOrder === -1 && _.findIndex(pushBillsData, { code: c.GLJcode, name: c.name, unit: c.unit }) === -1) {
+                            pushBillsData.push({
+                                code: c.GLJcode,
+                                name: c.name,
+                                unit: c.unit,
+                                spec: c.specs,
+                            })
+                        }
+                    }
+                }
+                console.log(pushChecklist, pushBillsData);
+                // stopProgress($('#bill-progress'));
+                // await pushListData(needPushTree);
+                // 先上传需要生成的清单及工料
+                if (pushChecklist.length > 0 || pushBillsData.length > 0) {
+                    postData(window.location.pathname + '/save', { type:'exportCB', addChecklist: pushChecklist, addBillsList: pushBillsData }, async function (result) {
+                        // materialListData = result;
+                        materialChecklistData = result.materialChecklistData;
+                        materialBillsData = result.materialBillsData;
+                        materialStageBillsData = result.materialStageBillsData;
+                        stopProgress($('#bill-progress'));
+                        await pushListData(needPushTree);
+                    }, function () {
+                        stop = true;
+                        clearInterval(interval);
+                        setTimeout(function () { $('#okedit').modal('hide') }, 1000);
+                    });
+                } else {
+                    stopProgress($('#bill-progress'));
+                    await pushListData(needPushTree);
+                }
+            } catch (error) {
+                console.log(error);
+                toastr.error(error);
+                stop = true;
+                clearInterval(interval);
+                setTimeout(function () { $('#okedit').modal('hide') }, 1000);
+                return
+            }
+
+            // 分析jsondata,得出每个清单间工料数量,生成新的树结构数组
+
+            // let persons = []; // 存储获取到的数据
+            // // 表格的表格范围,可用于判断表头是否数量是否正确
+            // let fromTo = '';
+            // // 遍历每张表读取
+            // for (const sheet in workbook.Sheets) {
+            //     if (workbook.Sheets.hasOwnProperty(sheet)) {
+            //         fromTo = workbook.Sheets[sheet]['!ref'];
+            //         console.log(fromTo);
+            //         persons = persons.concat(XLSX.utils.sheet_to_json(workbook.Sheets[sheet]));
+            //         // break; // 如果只取第一张表,就取消注释这行
+            //     }
+            // }
+            // console.log(persons);
+        };
+
+        // 以二进制方式打开文件
+        fileReader.readAsBinaryString(file);
+        $('#upload-list-file').val('');
+    });
+
+    function sleep(millisecond) {
+        return new Promise(resolve => {
+            setTimeout(() => {
+                resolve()
+            }, millisecond)
+        })
+    }
+
+    let value = 0;
+    let interval;
+    let stop = false;
+    function setProgress(_this, time = 50) {
+        interval = setInterval(function () {
+            if (value < 100) {
+                value = parseInt(value) + 1;
+                _this.css("width", value + "%").text(value + "%");
+            } else if (value === 100) {
+                value = parseInt(value) + 1;
+                value = 30;
+            }
+        }, time);
+    }
+
+    function resetExport() {
+        resetProgress($('#export-progress'));
+        $('#bill-detail').hide();
+        resetProgress($('#bill-progress'));
+        $('#list-detail').hide();
+        resetProgress($('#list-progress'));
+    }
+
+    function resetProgress(_this) {
+        _this.removeClass('bg-success');
+        _this.css("width", "0%").text("0%").attr('aria-valuenow', '0');
+    }
+
+    function stopProgress(_this) {
+        if (interval) {
+            _this.addClass('bg-success');
+            _this.css("width", "100%").text("100%");
+            value = 0;
+            stop = true;
+            clearInterval(interval);
+            interval = 0;
+        }
+    }
+
+    async function pushListData(tree = []) {
+        console.log(tree);
+        if (tree.length > 0) {
+            for (const [i,t] of tree.entries()) {
+                $('#list-detail').find('b').text(t.b_code);
+                resetProgress($('#list-progress'));
+                if (!interval) {
+                    $('#list-detail').show();
+                    setProgress($('#list-progress'), 30);
+                }
+                const mbList = [];
+                for (const mb of t.children) {
+                    const mbInfo = _.find(materialBillsData, { code: mb.GLJcode, name: mb.name, unit: mb.unit });
+                    if (mbInfo) {
+                        const num = parseFloat(mb.quantity);
+                        if (num < 0 || !/^\d+(\.\d{1,6})?$/.test(num)) {
+                            // toastr.warning('已保留6位小数');
+                            mb.quantity = ZhCalc.round(num, 6);
+                        }
+                        mbList.push({ id: mbInfo.id, quantity: mb.quantity });
+                    }
+                }
+
+                const gclIndex = _.findIndex(gclGatherData, { b_code: t.b_code, name: t.name, unit: t.unit, unit_price: t.unit_price ? parseFloat(t.unit_price) : null });
+                const gcl = gclGatherData[gclIndex].leafXmjs;
+                const ms_id = isStageSelf ? materialStageData[0].id : null;
+                // const index = materialChecklistData.indexOf(select);
+                const datas = [];
+                for (const xmj of gcl) {
+                    const notx = findNotJoinLeafXmj(xmj);
+                    const data = {
+                        xmj_id: xmj.id,
+                        gcl_id: xmj.gcl_id,
+                        mx_id: xmj.mx_id !== undefined ? xmj.mx_id : '',
+                        gather_qty: xmj.gather_qty,
+                        is_join: notx === undefined ? 1 : 0,
+                    };
+                    if (ms_id) data.ms_id = ms_id;
+                    datas.push(data);
+                }
+                if (isStageSelf) {
+                    // 取所有的gclGatherData才行,然后获取下的值
+                    const gclData = gclGatherData[gclIndex];
+                    for (const [index, ms] of materialStageData.entries()) {
+                        if (ms.id !== ms_id) {
+                            const gclOther = _.find(gclGatherListData[index], { b_code: gclData.b_code, name: gclData.name, unit: gclData.unit, unit_price: gclData.unit_price });
+                            if (gclOther) {
+                                const leafXmjs = gclOther.leafXmjs.filter(item => item.gather_qty !== null && item.gather_qty !== undefined);
+                                for (const xmj of leafXmjs) {
+                                    const notx = findNotJoinLeafXmj(xmj);
+                                    const data = {
+                                        xmj_id: xmj.id,
+                                        gcl_id: xmj.gcl_id,
+                                        mx_id: xmj.mx_id ? xmj.mx_id : '',
+                                        gather_qty: xmj.gather_qty,
+                                        is_join: notx === undefined ? 1 : 0,
+                                        ms_id: ms.id,
+                                    };
+                                    datas.push(data);
+                                }
+                            }
+                        }
+                    }
+                }
+
+                // 上传到数据库
+                const select = _.find(materialChecklistData, { b_code: t.b_code, name: t.name, unit: t.unit, unit_price: t.unit_price ? parseFloat(t.unit_price) : null });
+                console.log(select, datas, mbList);
+                if (select) {
+                    await postData(window.location.pathname + '/save', {type: 'adds', checklist: { id: select.id, had_bills: 1 }, postData: {xmjs: datas, mbIds: mbList, export: true}}, function (result) {
+                        // materialListData = result;
+                        select.had_bills = 1;
+                        gclList = result;
+                        stopProgress($('#list-progress'));
+                        if (i+1 === tree.length) {
+                            toastr.success('导入成功');
+                            setTimeout(function () { $('#okedit').modal('hide') }, 2000);
+                            showSjsData();
+                        }
+
+                    }, function () {
+                        stop = true;
+                        clearInterval(interval);
+                        setTimeout(function () { $('#okedit').modal('hide') }, 1000);
+                    });
+                } else {
+                    stopProgress($('#list-progress'));
+                }
+            }
+        }
+    }
+
+    function transExcel(results) {
+        const mapInfo = {
+            '清单编号': 'b_code',
+            '工料编号': 'GLJcode',
+            '名称': 'name',
+            '单位': 'unit',
+            '规格': 'specs',
+            '单价': 'unit_price',
+            '单位耗量': 'quantity',
+        };
+        return results.map(zhObj => {
+            const enObj = {}
+            const zhKeys = Object.keys(zhObj);
+
+            zhKeys.forEach(zhKey => {
+                const enKey = mapInfo[zhKey];
+
+                enObj[enKey] = zhObj[zhKey];
+            });
+
+            return enObj
+        })
+    }
 });

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

@@ -270,8 +270,8 @@ $(document).ready(() => {
                     updateBillsData(ms_id);
                     const newGclGatherListData = [];
                     for (const [index, s] of result.ledgerListData.entries()) {
-                        gclGatherModel.loadLedgerData(ledger, s);
-                        gclGatherModel.loadPosData(pos, result.posListData[index]);
+                        gclGatherModel.loadLedgerData(_.cloneDeep(ledger), s);
+                        gclGatherModel.loadPosData(_.cloneDeep(pos), result.posListData[index]);
                         const oneGclGatherData = gclGatherModel.gatherGclData().filter(item => {
                             return item.qc_qty || item.contract_qty
                         });
@@ -280,8 +280,8 @@ $(document).ready(() => {
                     gclGatherListData = newGclGatherListData;
                 }
                 // 解析清单汇总数据
-                gclGatherModel.loadLedgerData(ledger, curLedgerData);
-                gclGatherModel.loadPosData(pos, curPosData);
+                gclGatherModel.loadLedgerData(_.cloneDeep(ledger), curLedgerData);
+                gclGatherModel.loadPosData(_.cloneDeep(pos), curPosData);
                 gclGatherData = gclGatherModel.gatherGclData();
                 console.log(gclGatherData);
 
@@ -438,6 +438,25 @@ $(document).ready(() => {
                     return item.qc_qty || item.contract_qty
                 });
             }
+            if ($('#show_material_gcl').is(':checked')) {
+                const hadMaterialGclGatherData = [];
+                const hadGclIdList = [];
+                for (const ml of materialListData) {
+                    if (hadGclIdList.indexOf(ml.gcl_id) === -1) {
+                        hadGclIdList.push(ml.gcl_id);
+                    }
+                }
+                for (const gcl of gclGatherData) {
+                    for (const index in gcl.leafXmjs) {
+                        const gcl_id = gcl.leafXmjs[index].gcl_id;
+                        if (hadGclIdList.indexOf(gcl_id) !== -1) {
+                            hadMaterialGclGatherData.push(gcl);
+                            break;
+                        }
+                    }
+                }
+                gclGatherData = hadMaterialGclGatherData;
+            }
             console.log(gclGatherData);
             calculateJiaCha(gclGatherData);
             SpreadJsObj.initSheet(leafXmjSpread.getActiveSheet(), leafXmjSpreadSetting);
@@ -1934,11 +1953,18 @@ $(document).ready(() => {
             }
             gclGatherData = hadMaterialGclGatherData;
         } else {
-            gclGatherModel.loadLedgerData(ledger, curLedgerData);
-            gclGatherModel.loadPosData(pos, curPosData);
-            gclGatherData = gclGatherModel.gatherGclData().filter(item => {
-                return item.qc_qty || item.contract_qty
-            });
+            if (isStageSelf) {
+                const ms_id = parseInt($('#myTab').find('.active').data('msid'));
+                updateBillsData(ms_id);
+                const i = _.findIndex(materialStageData, { id: ms_id });
+                gclGatherData = gclGatherListData[i];
+            } else {
+                gclGatherModel.loadLedgerData(_.cloneDeep(ledger), curLedgerData);
+                gclGatherModel.loadPosData(_.cloneDeep(pos), curPosData);
+                gclGatherData = gclGatherModel.gatherGclData().filter(item => {
+                    return item.qc_qty || item.contract_qty
+                });
+            }
             if (openMaterialChecklist) {
                 // 取交集
                 gclGatherData = _.filter(gclGatherData, function (item) {

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

@@ -219,7 +219,7 @@ $(document).ready(() => {
         keys: ['id', 'tender_id', 'ledger_id'],
         masterId: 'id',
         minorId: 'lid',
-        calcFields: [],
+        calcFields: ['total_price'],
         markFoldKey: 'bills-fold',
         markFoldSubKey: window.location.pathname.split('/')[2],
     });
@@ -232,6 +232,7 @@ $(document).ready(() => {
 
     postData(window.location.pathname + '/load', {main: true}, function (result) {
         cTree.loadDatas(result.main.ledger);
+        treeCalc.calculateAll(cTree);
         cPos.loadDatas(result.main.pos);
         SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, cTree);
         loadPosData(0);
@@ -246,6 +247,7 @@ $(document).ready(() => {
         gclGatherModel.loadGatherField(checkField, checkField);
         gclGatherModel.loadLedgerData(result.main.ledger);
         gclGatherModel.loadPosData(result.main.pos);
+        gclGatherModel.loadDealBillsData(result.main.deal);
         gclGatherData = gclGatherModel.gatherGclData();
         SpreadJsObj.loadSheetData(gclSheet, SpreadJsObj.DataType.Data, gclGatherData);
         loadLeafXmjData(0);
@@ -413,5 +415,5 @@ $(document).ready(() => {
         posSpread.refresh();
         gclSpread.refresh();
         leafXmjSpread.refresh();
-    })
+    });
 });

+ 7 - 4
app/public/js/measure_material.js

@@ -234,6 +234,9 @@ $(function () {
             $('#show_order').show();
             $('#s_order').val(order_array.join(','));
             if (stageList.length > 1) {
+                if($('#material_unitPrice').is(':hidden')){
+                    $('#unitPrice_dl').prop('checked', true);
+                }
                 $('#material_unitPrice').show();
             } else {
                 $('#material_unitPrice').hide();
@@ -296,8 +299,8 @@ $(function () {
                 for (const sid of stage_id) {
                     const curLedger = _.find(result.curLedgerData, {sid: sid});
                     const curPos = _.find(result.curPosData, {sid: sid});
-                    gclGatherModel.loadLedgerData(ledger, curLedger.ledgerData);
-                    gclGatherModel.loadPosData(pos, curPos.posData);
+                    gclGatherModel.loadLedgerData(_.cloneDeep(ledger), curLedger.ledgerData);
+                    gclGatherModel.loadPosData(_.cloneDeep(pos), curPos.posData);
                     const gclGatherData = gclGatherModel.gatherGclData();
                     // console.log(gclGatherData);
                     const insertGcl = [];
@@ -382,8 +385,8 @@ $(function () {
             } else {
                 const curLedgerData = result.curLedgerData;
                 const curPosData = result.curPosData;
-                gclGatherModel.loadLedgerData(ledger, curLedgerData);
-                gclGatherModel.loadPosData(pos, curPosData);
+                gclGatherModel.loadLedgerData(_.cloneDeep(ledger), curLedgerData);
+                gclGatherModel.loadPosData(_.cloneDeep(pos), curPosData);
                 const gclGatherData = gclGatherModel.gatherGclData();
                 const insertGcl = [];
                 for (const g of gclList) {

+ 115 - 82
app/public/js/path_tree.js

@@ -387,6 +387,31 @@ const createNewPathTree = function (type, setting) {
             }
         }
 
+        loadFilter(select, filterType = 'access') {
+            const defaultValue = filterType === 'access' ? true : false;
+            this.select = select ? select.split(',') : [];
+            for (const d of this.datas) {
+                d.filter = defaultValue;
+            }
+            for (const s of this.select) {
+                const node = this.getItems(s);
+                if (!node) continue;
+                node.filter = !defaultValue;
+                const posterity = this.getPosterity(node);
+                for (const p of posterity) {
+                    p.filter = !defaultValue;
+                }
+                const parents = this.getAllParents(node);
+                for (const p of parents) {
+                    p.filter = !defaultValue;
+                }
+            }
+            for (const node of this.nodes) {
+                const parent = this.getParent(node);
+                node.visible = parent ? (parent.expanded && parent.visible && !node.filter) : !node.filter;
+            }
+        }
+
         getItemsByIndex(index) {
             return this.nodes[index];
         }
@@ -409,10 +434,18 @@ const createNewPathTree = function (type, setting) {
         getParent(node) {
             return this.getItems(node[this.setting.pid]);
         };
+        getTopParent(node) {
+            const parents = this.getAllParents(node);
+            parents.sort((a, b) => { return a.level - b.level; });
+            return parents[0];
+        };
         getAllParents(node) {
             const parents = [];
+            if (!node) return parents;
+
             if (node.full_path && node.full_path !== '') {
                 const parentIds = node.full_path.split('-');
+                parentIds.length = parentIds.length - 1;
                 for (const id of parentIds) {
                     if (id !== node[this.setting.id]) {
                         parents.push(this.getItems(id));
@@ -530,6 +563,16 @@ const createNewPathTree = function (type, setting) {
         };
 
         /**
+         * 查询node是否是父节点的最后一个可见子节点
+         * @param {Object} node
+         * @returns {boolean}
+         */
+        isLastViewSibling(node) {
+            const siblings = (this.getChildren(this.getParent(node))).filter(x => { return !x.filter });
+            return (siblings && siblings.length > 0) ? node.order === siblings[siblings.length - 1].order : false;
+        };
+
+        /**
          * 提取节点key和索引数据
          * @param {Object} node - 节点
          * @returns {key}
@@ -593,7 +636,7 @@ const createNewPathTree = function (type, setting) {
             }
             if (node.children && node.children.length > 0) {
                 for (const child of node.children) {
-                    child.visible = node.expanded && node.visible;
+                    child.visible = node.expanded && node.visible && !child.filter;
                     this._refreshChildrenVisible(child);
                 }
             }
@@ -623,7 +666,7 @@ const createNewPathTree = function (type, setting) {
                     node.expanded = expanded;
                     this._markExpandFold(node);
                 }
-                node.visible = parent ? (parent.expanded && parent.visible) : true;
+                node.visible = parent ? (parent.expanded && parent.visible && !node.filter) : !node.filter;
                 this._recursiveExpand(node.children, node, checkFun);
             }
         }
@@ -900,6 +943,23 @@ const createNewPathTree = function (type, setting) {
                 this._getDefaultNodeData(node);
             }
         }
+
+        checkParent(node, field = 'check') {
+            const parent = this.getParent(node);
+            return parent
+                ? (parent[field] ? parent[field] : this.checkParent(parent, field))
+                : false;
+        }
+        checkChildren(node, field = 'check') {
+            for (const child of node.children) {
+                if (child[field]) {
+                    return true;
+                } else if (this.checkChildren(child, field)) {
+                    return true;
+                }
+            }
+            return false;
+        }
     }
 
     class MeasureTree extends BaseTree {
@@ -1153,9 +1213,35 @@ const createNewPathTree = function (type, setting) {
         loadRevisePrice(price, decimal) {
             this.decimal = decimal;
             this.price = price || [];
+            this.rela_price = [];
+            this.common_price = [];
+            this.price.forEach(x => {
+                if (x.rela_lid) {
+                    x.rela_lid = x.rela_lid.split(',');
+                    this.rela_price.push(x);
+                } else {
+                    this.common_price.push(x);
+                }
+            });
         }
         checkRevisePrice(d) {
-            const p = this.price.find(x => {
+            const setting = this.setting;
+            const pid = this.getAllParents(d).map(x => { return x[setting.id] + ''; });
+            const checkRela = function(rela_lid) {
+                if (!rela_lid || rela_lid.length === 0) return false;
+                for (const lid of rela_lid) {
+                    if (pid.indexOf(lid) >= 0) return true;
+                }
+                return false;
+            };
+            let p = this.rela_price.find(x => {
+                return x.b_code === d.b_code &&
+                    ((!x.name && !d.name) || x.name === d.name) &&
+                    ((!x.unit && !d.unit) || x.unit === d.unit) &&
+                    checkZero(x.org_price - d.unit_price) &&
+                    checkRela(x.rela_lid);
+            });
+            if (!p) p = this.common_price.find(x => {
                 return x.b_code === d.b_code &&
                     ((!x.name && !d.name) || x.name === d.name) &&
                     ((!x.unit && !d.unit) || x.unit === d.unit) &&
@@ -1227,11 +1313,28 @@ const createNewPathTree = function (type, setting) {
             // stage关联索引
             this.stageItems = {};
         }
+
+        _loadLockedNodes(ids){
+            this.Locked = ids || [];
+            for (const f of this.Locked) {
+                f.ass_ledger_id = f.ass_ledger_id ? f.ass_ledger_id.split(',') : [];
+                for (const id of f.ass_ledger_id) {
+                    const node = this.getItems(id);
+                    node.locked = true;
+                    const posterity = this.getPosterity(node);
+                    for (const pn of posterity) {
+                        pn.locked = true;
+                        pn.lock = true;
+                    }
+                }
+
+            }
+        }
         /**
          * 加载数据(初始化), 并给数据添加部分树结构必须数据
          * @param datas
          */
-        loadDatas(datas) {
+        loadDatas(datas, locked) {
             super.loadDatas(datas);
             // 清空旧数据
             this.stageItems = {};
@@ -1240,52 +1343,7 @@ const createNewPathTree = function (type, setting) {
                 const keyName = itemsPre + data[this.setting.stageId];
                 this.stageItems[keyName] = data;
             }
-        }
-        // 解锁相关
-        _loadPwdCache() {
-            const cache = getLocalCache(this.pwdCacheKey);
-            if (!cache) return;
-
-            const cacheArr = cache.split('|');
-            for (const ca of cacheArr) {
-                if (!ca) continue;
-
-                const [ledger_id, pwd] = ca.split(':');
-                if (!ledger_id || !pwd) continue;
-
-                const p = this.pwd.find(x => {return x.ledger_id == ledger_id});
-                if (p) p.check = p.pwd === pwd;
-            }
-        }
-        loadPwd(data, cacheKey, confirmList) {
-            this.loadingPwd = true;
-            try {
-                this.pwdCacheKey = cacheKey;
-                this.confirmList = confirmList;
-                this.pwd = data;
-                this._loadPwdCache();
-                this._loadOnlineConfirm();
-                for (const p of this.pwd) {
-                    p.node = this.getItems(p.ledger_id);
-                    this.lockNode(p, !p.check);
-                }
-            } catch(err) {}
-            this.loadingPwd = false;
-        }
-
-        // 确认相关
-        _loadOnlineConfirm() {
-            const cacheArr = this.confirmList;
-            for (const ca of cacheArr) {
-                if (!ca) continue;
-                if (!ca.ledger_id) continue;
-
-                const p = this.pwd.find(x => {return x.ledger_id == ca.ledger_id});
-                if (p) {
-                    p.confirm = true;
-                    p.confirm_time = ca.create_time;
-                }
-            }
+            this._loadLockedNodes(locked)
         }
 
         getStageItems(id) {
@@ -1445,39 +1503,14 @@ const createNewPathTree = function (type, setting) {
             return result;
         }
 
-        _savePwdCache() {
-            const cache = [];
-            for (const p of this.pwd) {
-                if (p.check && p.pwd) cache.push(p.ledger_id + ':' + p.pwd);
-            }
-            setLocalCache(this.pwdCacheKey, cache.join('|'));
-        }
-
-        _getPwdPosterity(node) {
-            const children = [];
-            for (const c of node.children) {
-                const cPwd = this.pwd.find(x => {return x.node && x.node.id === c.id});
-                if (!cPwd) children.push(c);
-            }
-            let posterity = [...children];
-            for (const c of children) {
-                posterity = posterity.concat(this._getPwdPosterity(c));
-            }
-            return posterity;
-        }
-
-        lockNode(p, isLock) {
-            const refresh = [];
-            p.check = !isLock;
-            p.node.lock = isLock;
-            refresh.push(this.getNodeIndex(p.node));
-            const posterity = this._getPwdPosterity(p.node);
-            for (const pn of posterity) {
-                pn.lock = isLock;
-                refresh.push(this.getNodeIndex(pn));
+        getLockedInfo(node) {
+            const ownerNodes = this.getAllParents(node);
+            const lockedInfo = [];
+            for (const on of ownerNodes) {
+                const filter = this.Locked.filter(x => { return x.ass_ledger_id.indexOf(on.ledger_id + '') >= 0; });
+                lockedInfo.push(...filter);
             }
-            if (!this.loadingPwd) this._savePwdCache();
-            return refresh;
+            return lockedInfo;
         }
     }
 

+ 46 - 17
app/public/js/profile.js

@@ -120,9 +120,12 @@ $(document).ready(function() {
         });
 
         // 移除签章
-        $('#delete-stamp').click(function () {
-            postData('/profile/sign/delete', { type: 'stamp' }, function (result) {
-                $('#stamp-show').html('');
+        $('body').on('click', '.delete-stamp', function () {
+            let imgSrc = $(this).siblings('img').attr('src');
+            imgSrc = _.replace(imgSrc, fujianOssPath, '');
+            const _self = $(this);
+            postData('/profile/sign/delete', { type: 'stamp', src: imgSrc }, function (result) {
+                _self.parents('.stamp-show').remove();
                 toastr.success('移除成功');
             })
         });
@@ -141,6 +144,11 @@ $(document).ready(function() {
         // 上传签名
         $('#sign-upload').change(function () {
             const file = this.files[0];
+            const filesize = file.size;
+            if (filesize > 1 * 1024 * 1024) {
+                toastr.error('上传的文件大小不能超过1MB!');
+                return false;
+            }
             const ext = file.name.toLowerCase().split('.').splice(-1)[0];
             const imgStr = /(jpg|jpeg|png|bmp|BMP|JPG|PNG|JPEG)$/;
             if (!imgStr.test(ext)) {
@@ -160,23 +168,44 @@ $(document).ready(function() {
 
         // 上传签章
         $('#stamp-upload').change(function () {
-            const file = this.files[0];
-            const ext = file.name.toLowerCase().split('.').splice(-1)[0];
-            const imgStr = /(jpg|jpeg|png|bmp|BMP|JPG|PNG|JPEG)$/;
-            if (!imgStr.test(ext)) {
-                toastr.error('请上传正确的图片格式文件');
+            const hadstamp = $('#show-stamp .stamp-show').length;
+            const files = this.files;
+            if (hadstamp + files.length > 5) {
+                toastr.error('最多只能5个签章');
                 return
             }
-            if ($(this).val()) {
-                const formData = new FormData();
-                formData.append('type', 'stamp');
-                formData.append('file', this.files[0]);
-                postDataWithFile('/profile/sign/upload', formData, function (result) {
-                    const html = '<img src="'+ fujianOssPath + result.stamp_path +'" width="90">';
-                    $('#stamp-show').html(html);
-                    $('#stamp-upload').val('');
-                });
+            const formData = new FormData();
+            for (const file of files) {
+                if (file === undefined) {
+                    toast('未选择上传文件!', 'error');
+                    return false;
+                }
+                const ext = file.name.toLowerCase().split('.').splice(-1)[0];
+                const imgStr = /(jpg|jpeg|png|bmp|BMP|JPG|PNG|JPEG)$/;
+                if (!imgStr.test(ext)) {
+                    toastr.error('请上传正确的图片格式文件');
+                    return
+                }
+                const filesize = file.size;
+                if (filesize > 1 * 1024 * 1024) {
+                    toastr.error('上传的文件大小不能超过1MB!');
+                    return false;
+                }
+                formData.append('file[]', file);
             }
+            postDataWithFile('/profile/stamp/upload', formData, function (result) {
+                let html = '';
+                for (const [index, sp] of result.stamp_path.entries()) {
+                    html += '<div class="position-absolute fixed-top stamp-show" style="left:'+ (50 + index*130) + 'px;top: 280px;width: 100px">\n' +
+                        '                                    <div class="position-relative">\n' +
+                        '                                        <a href="javascript:void(0);" title="移除签章" class="position-absolute delete-stamp" style="right: 0;top: 0;color: red;font-size:20px;font-weight: bold;text-decoration:none;">×</a>\n' +
+                        '                                        <img src="'+ fujianOssPath + sp +'" width="90">\n' +
+                        '                                    </div>\n' +
+                        '                                </div>';
+                }
+                $('#show-stamp').html(html);
+                $('#stamp-upload').val('');
+            });
         })
     } catch (error) {
         console.log(error);

+ 10 - 8
app/public/js/revise.js

@@ -88,6 +88,7 @@ $(document).ready(() => {
             },
         },
     ];
+    sjsSettingObj.setNodeTypeCol(billsSpreadSetting.cols, [{field: 'node_type'}]);
     SpreadJsObj.initSheet(billsSheet, billsSpreadSetting);
     const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
     const posSheet = posSpread.getActiveSheet();
@@ -2791,20 +2792,21 @@ $(document).ready(() => {
     });
 
     $('#ledger-check2').click(() => {
-        const result = ledgerCheck2({
+        ledgerCheck2({
             ledgerTree: billsTree,
             ledgerPos: pos,
             checkList: checkList,
             decimal: decimal,
             checkOption: checkOption,
+        }).then(result => {
+            check2Viewing({
+                extra: ZhCalc.div(billsTree.datas.length + pos.datas.length, 10000, 0),
+                randomWait: true,
+                prefix: 'check2-',
+                checks: result,
+                checkList: checkList,
+            });
         });
-        check2Viewing({
-            extra: ZhCalc.div(billsTree.datas.length + pos.datas.length, 10000, 0),
-            randomWait: true,
-            prefix: 'check2-',
-            checks: result,
-            checkList: checkList,
-        })
     });
 
     $('[name=revise-start]').submit(function (e) {

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

@@ -150,7 +150,7 @@ $(document).ready(() => {
         $('#chapter-list').html(html.join(''));
     }
 
-    postData('load', {filter: 'bills;pos;reviseBills;revisePos;dealBills;spec'}, function (data) {
+    postData('load', {filter: 'bills;pos;reviseBills;revisePos;dealBills;spec;price'}, function (data) {
         const setting = {
             tree: {
                 id: 'ledger_id',
@@ -168,7 +168,7 @@ $(document).ready(() => {
         };
         gclCompareModel.init(gclData, chapter, data.spec);
         setting.prefix = 'new_';
-        gclCompareModel.gatherLedgerData(data.reviseBills, data.revisePos, setting);
+        gclCompareModel.gatherReviseLedgerData(data.reviseBills, data.revisePos, setting, data.price, decimal);
         setting.prefix = 'org_';
         gclCompareModel.gatherLedgerData(data.bills, data.pos, setting);
         gclCompareModel.gatherDealBills(data.dealBills);

+ 1 - 0
app/public/js/revise_history.js

@@ -16,6 +16,7 @@ $(document).ready(() => {
     const billsSheet = billsSpread.getActiveSheet();
     sjsSettingObj.setFxTreeStyle(billsSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(billsSpreadSetting);
+    sjsSettingObj.setNodeTypeCol(billsSpreadSetting.cols, [{field: 'node_type'}]);
     SpreadJsObj.initSheet(billsSheet, billsSpreadSetting);
     const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
     const posSheet = posSpread.getActiveSheet();

+ 567 - 9
app/public/js/revise_price.js

@@ -44,6 +44,24 @@ $(document).ready(() => {
         font: '12px 微软雅黑',
         readOnly: true,
     };
+    const ledgerXmjSpreadSetting = {
+        cols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 100, formatter: '@'},
+            {title: '单位工程', colSpan: '1', rowSpan: '1', field: 'dwgc', hAlign: 0, width: 80, formatter: '@'},
+            {title: '分部工程', colSpan: '1', rowSpan: '1', field: 'fbgc', hAlign: 0, width: 80, formatter: '@'},
+            {title: '分项工程', colSpan: '1', rowSpan: '1', field: 'fxgc', hAlign: 0, width: 80, formatter: '@'},
+            {title: '细目', colSpan: '1', rowSpan: '1', field: 'jldy', hAlign: 0, width: 80, formatter: '@'},
+            {title: '计量单元', colSpan: '1', rowSpan: '1', field: 'bwmx', hAlign: 0, width: 80, formatter: '@'},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [32],
+        headColWidth: [30],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+    };
     const priceSpreadSetting = {
         cols: [
             { title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 100, formatter: '@', readOnly: true },
@@ -61,25 +79,85 @@ $(document).ready(() => {
         headerFont: '12px 微软雅黑',
         font: '12px 微软雅黑',
         readOnly,
+        getColor: function (sheet, data, row, col, defaultColor) {
+            if (!data || (data.rela_lid && data.rela_cid)) return defaultColor;
+
+            const samePrice = sheet.zh_data.find(x => {
+                return x.b_code === data.b_code && x.name === data.name && x.unit === data.unit && x.org_price === data.org_price;
+            });
+            return samePrice ? '#f5deb3' : defaultColor;
+        }
+    };
+    const priceBwSpreadSetting = {
+        cols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 150, formatter: '@', cellType: 'tree'},
+            {title: '清单编号', colSpan: '1', rowSpan: '1', field: 'b_code', hAlign: 0, width: 80, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 60, formatter: '@'},
+            {title: '原单价', colSpan: '1', rowSpan: '1', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+        ],
+        headRows: 1,
+        emptyRows: 0,
+        headRowHeight: [25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+    };
+    sjsSettingObj.setFxTreeStyle(priceBwSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    const priceChangeSpreadSetting = {
+        cols: [
+            {title: '变更令', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 80, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+            {title: '批复文号', colSpan: '1', rowSpan: '1', field: 'w_code', hAlign: 1, width: 80, formatter: '@'},
+        ],
+        headRows: 1,
+        emptyRows: 0,
+        headRowHeight: [25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
     };
     autoFlashHeight();
+    const bcontent = $(".bcontent-wrap").length > 0 ? $(".bcontent-wrap").height() : 0;
+    $(".sp-wrap").height(bcontent-30);
     const priceSpread = SpreadJsObj.createNewSpread($('#price-spread')[0]);
     const priceSheet = priceSpread.getActiveSheet();
+    const priceBwSpread = SpreadJsObj.createNewSpread($('#price-bw-spread')[0]);
+    const priceBwSheet = priceBwSpread.getActiveSheet();
+    const priceChangeSpread = SpreadJsObj.createNewSpread($('#price-change-spread')[0]);
+    const priceChangeSheet = priceChangeSpread.getActiveSheet();
 
     SpreadJsObj.initSheet(priceSheet, priceSpreadSetting);
+    SpreadJsObj.initSheet(priceBwSheet, priceBwSpreadSetting);
+    SpreadJsObj.initSheet(priceChangeSheet, priceChangeSpreadSetting);
 
     class RevisePrice {
         constructor () {
             this.data = [];
+            this.tree = createNewPathTree('filter', {
+                id: 'ledger_id',
+                pid: 'ledger_pid',
+                order: 'order',
+                level: 'level',
+                rootId: -1,
+                fullPath: 'full_path',
+                keys: ['id', 'tender_id', 'ledger_id'],
+            });
         }
         resortData() {
             this.data.sort(function (a, b) {
                 return a.order - b.order;
             });
         }
-        loadDatas(datas) {
+        loadDatas(datas, treeData, changeData) {
             this.data = datas;
+            this.tree.loadDatas(treeData);
             this.resortData();
+            this.change = changeData;
+            this.relaChange = [];
+            if (this.data.length > 0) this.refreshRela(this.data[0]);
         }
         loadUpdateData(updateData) {
             if (updateData.add) {
@@ -106,12 +184,66 @@ $(document).ready(() => {
             }
             this.resortData();
         }
+        getSamePrice(price) {
+            return this.data.filter(x => {
+                if (x.id === price.id) return false;
+                return x.b_code === price.b_code && x.name === price.name && x.unit === price.unit && x.org_price === price.org_price;
+            });
+        }
+        refreshTreeRela(price, samePrice) {
+            if (price.rela_lid) {
+                this.tree.loadFilter(price.rela_lid);
+            } else {
+                const invalid = [];
+                for (const sp of samePrice) {
+                    const lid = sp.rela_lid ? sp.rela_lid.split(',') : [];
+                    invalid.push(...lid);
+                }
+                this.tree.loadFilter(invalid.join(','), 'filter');
+            }
+        }
+        refreshChangeRela(price, samePrice) {
+            if (price.rela_cid) {
+                const choose = price.rela_cid.split(',');
+                for (const c of this.change) {
+                    c.rela = choose.indexOf(c.cid + '') >= 0;
+                    c.valid = c.rela;
+                }
+            } else {
+                const invalid = [];
+                for (const sp of samePrice) {
+                    const cid = sp.rela_cid ? sp.rela_cid.split(',') : [];
+                    invalid.push(...cid);
+                }
+                for (const c of this.change) {
+                    c.rela = invalid.indexOf(c.cid + '') >= 0;
+                    if (c.rela) {
+                        c.valid = 0;
+                    } else {
+                        const exist = c.bills.find(x => {
+                            return x.code === price.b_code && x.name === price.name && x.unit === price.unit && x.unit_price === price.org_price;
+                        });
+                        c.valid = exist;
+                    }
+                }
+            }
+            this.relaChange.length = 0;
+
+            for (const c of this.change) {
+                if (c.valid) this.relaChange.push(c);
+            }
+        }
+        refreshRela(price) {
+            const samePrice = this.getSamePrice(price);
+            this.refreshTreeRela(price, samePrice);
+            this.refreshChangeRela(price, samePrice);
+        }
     }
     const revisePrice = new RevisePrice();
     const priceOprObj = {
         addRevisePrice(data) {
             const op = revisePrice.data.find(x => {
-                return x.b_code === data.b_code && x.name === x.name && x.unit === x.unit && checkZero(ZhCalc.sub(x.org_price, data.unit_price));
+                return x.b_code === data.b_code && x.name === x.name && x.unit === x.unit && checkZero(ZhCalc.sub(x.org_price, data.unit_price)) && (!x.rela_lid || !x.rela_cid);
             });
             if (op) {
                 toastr.warning('已存在该单价调整');
@@ -289,7 +421,29 @@ $(document).ready(() => {
                 SpreadJsObj.reLoadRowsData(priceSheet, [sels[0].row, sels[0].row + 1]);
                 priceSheet.setSelection(sels[0].row + 1, sels[0].col, sels[0].rowCount, sels[0].colCount);
             });
-        }
+        },
+        updateRelaLid: function (price, rela_lid) {
+            const data = { update: { id: price.id, rela_lid } };
+            postData(window.location.pathname + '/update', data, function (result) {
+                revisePrice.loadUpdateData(result);
+                revisePrice.refreshTreeRela(price);
+                SpreadJsObj.refreshTreeRowVisible(priceBwSheet);
+            });
+        },
+        updateRelaCid: function (price, rela_cid) {
+            const data = { update: { id: price.id, rela_cid } };
+            postData(window.location.pathname + '/update', data, function (result) {
+                revisePrice.loadUpdateData(result);
+                revisePrice.refreshChangeRela(price);
+                SpreadJsObj.reLoadSheetData(priceChangeSheet);
+            });
+        },
+        selectionChanged: function () {
+            const price = SpreadJsObj.getSelectObject(priceSheet);
+            revisePrice.refreshRela(price);
+            SpreadJsObj.refreshTreeRowVisible(priceBwSheet);
+            SpreadJsObj.reLoadSheetData(priceChangeSheet);
+        },
     };
     if (!readOnly) {
         priceSheet.bind(spreadNS.Events.EditEnded, priceOprObj.editEnded);
@@ -316,7 +470,7 @@ $(document).ready(() => {
                         return !readOnly;
                     }
                 },
-                sprDel: '------------',
+                sprDel: '----',
                 upMove: {
                     name: '上移',
                     icon: 'fa-arrow-up',
@@ -352,10 +506,44 @@ $(document).ready(() => {
                     visible: function (key, opt) {
                         return !readOnly;
                     }
-                }
+                },
+                sprMove: '----',
+                chooseRelaBw: {
+                    name: '选择应用部位',
+                    icon: 'fa-tags',
+                    callback: function (key, opt) {
+                        const price = SpreadJsObj.getSelectObject(priceSheet);
+                        const samePrice = revisePrice.getSamePrice(price);
+                        chooseRelaBw.show(price, samePrice);
+                    },
+                    disabled: function (key, opt) {
+                        const node = SpreadJsObj.getSelectObject(priceSheet);
+                        return !node;
+                    },
+                    visible: function (key, opt) {
+                        return !readOnly;
+                    }
+                },
+                chooseRelaChange: {
+                    name: '选择应用变更令',
+                    icon: 'fa-tags',
+                    callback: function (key, opt) {
+                        const price = SpreadJsObj.getSelectObject(priceSheet);
+                        const samePrice = revisePrice.getSamePrice(price);
+                        chooseRelaChange.show(price, samePrice);
+                    },
+                    disabled: function (key, opt) {
+                        const node = SpreadJsObj.getSelectObject(priceSheet);
+                        return !node;
+                    },
+                    visible: function (key, opt) {
+                        return !readOnly;
+                    }
+                },
             },
         });
     }
+    priceSpread.bind(spreadNS.Events.SelectionChanged, priceOprObj.selectionChanged);
 
     class LedgerGcl {
         constructor(setting) {
@@ -363,23 +551,42 @@ $(document).ready(() => {
             this.spread = SpreadJsObj.createNewSpread($(this.setting.selector)[0]);
             this.sheet = this.spread.getActiveSheet();
             SpreadJsObj.initSheet(this.sheet, this.setting.spreadSetting);
+            this.xmjSpread = SpreadJsObj.createNewSpread($(this.setting.xmjSelector)[0]);
+            this.xmjSheet = this.xmjSpread.getActiveSheet();
+            SpreadJsObj.initSheet(this.xmjSheet, this.setting.xmjSpreadSetting);
             if (!readOnly) {
                 this.spread.bind(spreadNS.Events.CellDoubleClick, function (e, info) {
                     const gcl = SpreadJsObj.getSelectObject(info.sheet);
                     priceOprObj.addRevisePrice(gcl);
                 });
             }
+            const self = this;
+            this.spread.bind(spreadNS.Events.SelectionChanged, function (e, info) {
+                self.loadLeafXmj();
+            });
         }
         loadData(bills, pos) {
             gclGatherModel.loadLedgerData(bills);
             gclGatherModel.loadPosData(pos);
             this.gcl = gclGatherModel.gatherGclData();
             this.sheet && SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Data, this.gcl);
+            this.loadLeafXmj(0);
+        }
+        loadLeafXmj(iGclRow) {
+            const gcl = iGclRow ? this.gcl[iGclRow] : SpreadJsObj.getSelectObject(this.sheet);
+            SpreadJsObj.resetTopAndSelect(this.xmjSheet);
+            if (gcl) {
+                SpreadJsObj.loadSheetData(this.xmjSheet, SpreadJsObj.DataType.Data, gcl.leafXmjs);
+            } else {
+                SpreadJsObj.loadSheetData(this.xmjSheet, SpreadJsObj.DataType.Data, []);
+            }
         }
     }
     const ledgerGcl = new LedgerGcl({
         selector: '#ledger-gcl-spread',
-        spreadSetting: ledgerGclSpreadSetting
+        spreadSetting: ledgerGclSpreadSetting,
+        xmjSelector: '#ledger-xmj-spread',
+        xmjSpreadSetting: ledgerXmjSpreadSetting,
     });
 
     $.subMenu({
@@ -398,6 +605,8 @@ $(document).ready(() => {
             autoFlashHeight();
             priceSpread.refresh();
             ledgerGcl.spread.refresh();
+            priceBwSpread.refresh();
+            priceChangeSpread.refresh();
         }
     });
 
@@ -405,14 +614,352 @@ $(document).ready(() => {
         select: '#revise-right-spr',
         callback: function () {
             priceSpread.refresh();
+            priceBwSpread.refresh();
+            priceChangeSpread.refresh();
+            ledgerGcl.spread.refresh();
+            ledgerGcl.xmjSpread.refresh();
+        }
+    });
+    $.divResizer({
+        select: '#gcl-spr',
+        callback: function () {
             ledgerGcl.spread.refresh();
+            ledgerGcl.xmjSpread.refresh();
         }
     });
+    $.divResizer({
+        select: '#price-resize',
+        callback: function () {
+            priceSpread.refresh();
+            let bcontent = $(".bcontent-wrap").length > 0 ? $(".bcontent-wrap").height() : 0;
+            $(".sp-wrap").height(bcontent-30);
+            priceBwSpread.refresh();
+            priceChangeSpread.refresh();
+        }
+    });
+
+    class ChooseRelaBw {
+        constructor() {
+            const self = this;
+            this.tree = createNewPathTree('ledger', {
+                id: 'ledger_id',
+                pid: 'ledger_pid',
+                order: 'order',
+                level: 'level',
+                rootId: -1,
+                fullPath: 'full_path',
+                keys: ['id', 'tender_id', 'ledger_id'],
+            });
+            $('#choose-rela-bw').on('shown.bs.modal', function() {
+                self.initSpread();
+                SpreadJsObj.reloadColData(self.sheet, 0, 1);
+                SpreadJsObj.reloadRowBackColor(self.sheet, 0, self.tree.nodes.length);
+            });
+            $('#choose-rela-bw-ok').click(function() {
+                const choose_lid = [];
+                self.tree.nodes.forEach(x => {
+                    if (x.check) choose_lid.push(x.ledger_id);
+                });
+                priceOprObj.updateRelaLid(self.price, choose_lid.join(','));
+                $('#choose-rela-bw').modal('hide');
+            });
+        }
+        get locate() {
+            return this._locate;
+        }
+        set locate(value) {
+            if (!this.searchResult || this.searchResult.length === 0) return;
+            this._locate = !value || value >= this.searchResult.length ? 0 : (value < 0 ? this.searchResult.length - 1 : value);
+            SpreadJsObj.locateTreeNode(this.sheet, this.searchResult[this._locate].ledger_id, true);
+        }
+        search(keyword) {
+            this.searchResult = [];
+            for (const node of this.tree.nodes) {
+                const code = node.code || '', name = node.name || '';
+                if (code.indexOf(keyword) >= 0 || name.indexOf(keyword) >= 0) this.searchResult.push(node);
+            }
+            $('#rela-bw-search-result').html(`结果:${this.searchResult.length}`);
+            this.locate = 0;
+        }
+        initSpread() {
+            if (this.spread) return;
+
+            this.spread = SpreadJsObj.createNewSpread($('#rela-bw-spread')[0]);
+            this.sheet = this.spread.getActiveSheet();
+            const spreadSetting = {
+                cols: [
+                    {title: '选择', colSpan: '1', rowSpan: '1', field: 'check', hAlign: 1, width: 50, formatter: '@', cellType: 'checkbox'},
+                    {title: '项目节编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 150, formatter: '@', cellType: 'tree'},
+                    {title: '清单编号', colSpan: '1', rowSpan: '1', field: 'b_code', hAlign: 0, width: 80, formatter: '@'},
+                    {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+                    {title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 60, formatter: '@'},
+                    {title: '原单价', colSpan: '1', rowSpan: '1', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+                ],
+                headRows: 1,
+                emptyRows: 0,
+                headRowHeight: [25],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                readOnly: true,
+                getColor: function (sheet, data, row, col, defaultColor) {
+                    return data && data.invalid ? '#dddddd' : defaultColor;
+                }
+            };
+            sjsSettingObj.setFxTreeStyle(spreadSetting, sjsSettingObj.FxTreeStyle.jz);
+            SpreadJsObj.initSheet(this.sheet, spreadSetting);
+            const self = this;
+            this.spread.bind(spreadNS.Events.ButtonClicked, function (e, info) {
+                function checkInvalid(node) {
+                    if (node.invalid) return 1;
+                    if (self.tree.checkParent(node, 'invalid')) return 2;
+                    if (self.tree.checkChildren(node, 'invalid')) return 3;
+                    return 0;
+                }
+
+                const sheet = info.sheet, cellType = sheet.getCellType(info.row, info.col);
+                if (!sheet.zh_setting) return;
+
+                if (cellType instanceof spreadNS.CellTypes.CheckBox) {
+                    if (sheet.isEditing()) sheet.endEdit(true);
+                }
+
+                const col = sheet.zh_setting.cols[info.col];
+                if (col.field !== 'check') return;
+
+                const tree = sheet.zh_tree;
+                const node = SpreadJsObj.getSelectObject(sheet);
+                if (node.b_code) {
+                    toastr.warning('请选择部位');
+                    return;
+                }
+
+                if (!node.check) {
+                    const invalid = checkInvalid(node);
+                    const invalidHint = ['该部位已被选择,请勿重复选择', '该部位的父项已被选择,请勿选择', '该部位的子项已被选择,请勿在其子项中选择'];
+                    if (invalid) {
+                        toastr.warning(invalidHint[invalid-1]);
+                        return;
+                    }
+                    if (self.tree.checkParent(node)) {
+                        const rect = info.sheet.getCellRect(info.row, info.col);
+                        self.chooseConfirmPopover({
+                            x: rect.x + rect.width / 2 + 25,
+                            y: rect.y + rect.height / 2 + 3,
+                        }, '父项已勾选,继续将取消父项勾选。', function () {
+                            node.check = true;
+                            const parents = tree.getFullPathNodes(tree.getParent(node).full_path);
+                            const rows = [tree.nodes.indexOf(node)];
+                            for (const p of parents) {
+                                if (p.check) {
+                                    p.check = false;
+                                    rows.push(tree.nodes.indexOf(p));
+                                }
+                            }
+                            SpreadJsObj.reLoadRowsData(info.sheet, rows);
+                        });
+                    } else if (self.tree.checkChildren(node)) {
+                        const rect = info.sheet.getCellRect(info.row, info.col);
+                        self.chooseConfirmPopover({
+                            x: rect.x + rect.width / 2 + 25,
+                            y: rect.y + rect.height / 2 + 3,
+                        }, '子项已勾选,继续将取消子项勾选。', function () {
+                            node.check = true;
+                            const posterity = tree.getPosterity(node);
+                            const rows = [tree.nodes.indexOf(node)];
+                            for (const p of posterity) {
+                                if (p.check) {
+                                    rows.push(tree.nodes.indexOf(p));
+                                    p.check = false;
+                                }
+                            }
+                            SpreadJsObj.reLoadRowsData(info.sheet, rows);
+                        });
+                    } else {
+                        node.check = true;
+                        SpreadJsObj.reLoadRowsData(info.sheet, [info.row]);
+                    }
+                } else {
+                    node.check = false;
+                    SpreadJsObj.reLoadRowsData(info.sheet, [info.row]);
+                }
+            });
+            SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Tree, this.tree);
+
+            (function (select, sheet) {
+                $(select).click(function () {
+                    if (!sheet.zh_tree) return;
+                    const tag = $(this).attr('tag');
+                    const tree = sheet.zh_tree;
+                    setTimeout(() => {
+                        showWaitingView();
+                        switch (tag) {
+                            case "1":
+                            case "2":
+                            case "3":
+                            case "4":
+                            case "5":
+                                tree.expandByLevel(parseInt(tag));
+                                SpreadJsObj.refreshTreeRowVisible(sheet);
+                                break;
+                            case "last":
+                                tree.expandByCustom(() => { return true; });
+                                SpreadJsObj.refreshTreeRowVisible(sheet);
+                                break;
+                            case "leafXmj":
+                                tree.expandToLeafXmj();
+                                SpreadJsObj.refreshTreeRowVisible(sheet);
+                                break;
+                        }
+                        closeWaitingView();
+                    }, 100);
+                });
+            })('a[name=showLevel]', this.sheet);
+            $('#rela-bw-search-keyword').change(function () {
+                self.search(this.value);
+            });
+            $('#rela-bw-search-pre').click(function () {
+                self.locate = self.locate - 1;
+            });
+            $('#rela-bw-search-next').click(function () {
+                self.locate = self.locate + 1;
+            });
+        }
+        reBind(obj, eventName, fun) {
+            obj.unbind(eventName);
+            obj.bind(eventName, fun);
+        }
+        chooseConfirmPopover(pos, hint, okCallback) {
+            const confirmObj = $('#choose-confirm'), hintObj = $('#choose-confirm-hint');
+            const okObj = $('#choose-confirm-ok'), cancelObj = $('#choose-confirm-cancel');
+            this.reBind(cancelObj, 'click', function () {
+                confirmObj.hide();
+            });
+            this.reBind(okObj, 'click', function () {
+                okCallback();
+                confirmObj.hide();
+            });
+            hintObj.text(hint);
+            confirmObj.css("top", pos.y).css("left", pos.x).show();
+        }
+        loadTree(data) {
+            this.tree.loadDatas(data);
+        }
+        show(price, samePrice){
+            this.price = price;
+            this.choose = price.rela_lid ? price.rela_lid.split(',') : [];
+            this.invalid = [];
+            for (const sp of samePrice) {
+                const lid = sp.rela_lid ? sp.rela_lid.split(',') : [];
+                this.invalid.push(...lid);
+            }
+            for (const node of this.tree.nodes) {
+                node.check = this.choose.indexOf(node.ledger_id + '') >= 0;
+                node.invalid = this.invalid.indexOf(node.ledger_id + '') >= 0;
+            }
+            $('#choose-rela-bw').modal('show');
+        }
+    }
+    const chooseRelaBw = new ChooseRelaBw();
+    class ChooseRelaChange {
+        constructor (){
+            const self = this;
+            $('#choose-rela-change').on('shown.bs.modal', function() {
+                self.initSpread();
+                SpreadJsObj.reloadColData(self.sheet, 0, 1);
+                SpreadJsObj.reloadRowBackColor(self.sheet, 0, self.change.length);
+            });
+            $('#choose-rela-change-ok').click(function() {
+                const choose_cid = [];
+                self.change.forEach(x => {
+                    if (x.check) choose_cid.push(x.cid);
+                });
+                priceOprObj.updateRelaCid(self.price, choose_cid.join(','));
+                $('#choose-rela-change').modal('hide');
+            });
+        }
+        initSpread() {
+            if (this.spread) return;
+
+            this.spread = SpreadJsObj.createNewSpread($('#rela-change-spread')[0]);
+            this.sheet = this.spread.getActiveSheet();
+            const spreadSetting = {
+                cols: [
+                    {title: '选择', colSpan: '1', rowSpan: '1', field: 'check', hAlign: 1, width: 50, formatter: '@',  cellType: 'checkbox'},
+                    {title: '变更令', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 150, formatter: '@'},
+                    {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+                    {title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 60, formatter: '@'},
+                ],
+                headRows: 1,
+                emptyRows: 0,
+                headRowHeight: [25],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                readOnly: true,
+                getColor: function (sheet, data, row, col, defaultColor) {
+                    return data && data.invalid ? '#dddddd' : defaultColor;
+                }
+            };
+            SpreadJsObj.initSheet(this.sheet, spreadSetting);
+            this.spread.bind(spreadNS.Events.ButtonClicked, function (e, info) {
+                const sheet = info.sheet, cellType = sheet.getCellType(info.row, info.col);
+                if (!sheet.zh_setting) return;
+
+                if (cellType instanceof spreadNS.CellTypes.CheckBox) {
+                    if (sheet.isEditing()) sheet.endEdit(true);
+                }
+
+                const col = sheet.zh_setting.cols[info.col];
+                if (col.field !== 'check') return;
+
+                const node = SpreadJsObj.getSelectObject(sheet);
+
+                if (!node.check) {
+                    if (node.invalid) return;
+                    node.check = true;
+                    SpreadJsObj.reLoadRowsData(info.sheet, [info.row]);
+                } else {
+                    node.check = false;
+                    SpreadJsObj.reLoadRowsData(info.sheet, [info.row]);
+                }
+            });
+            SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Data, this.change);
+        }
+        loadChange(data) {
+            this.change = data;
+        }
+        show(price, samePrice) {
+            this.price = price;
+            this.choose = price.rela_cid ? price.rela_cid.split(',') : [];
+            this.invalid = [];
+            for (const sp of samePrice) {
+                const cid = sp.rela_cid ? sp.rela_cid.split(',') : [];
+                this.invalid.push(...cid);
+            }
+            for (const c of this.change) {
+                c.check = this.choose.indexOf(c.cid + '') >= 0;
+                c.invalid = this.invalid.indexOf(c.cid + '') >= 0;
+                if (!c.check && !c.invalid) {
+                    const exist = c.bills.find(x => {
+                        return x.code === price.b_code && x.name === price.name && x.unit === price.unit && x.unit_price === price.org_price;
+                    });
+                    c.visible = !exist;
+                }
+            }
+            $('#choose-rela-change').modal('show');
+        }
+    }
+    const chooseRelaChange = new ChooseRelaChange();
 
-    postData('load', { filter: 'bills;pos;price' }, result => {
-        revisePrice.loadDatas(result.price);
+    postData('load', { filter: 'bills;pos;price;change' }, result => {
+        revisePrice.loadDatas(result.price, result.bills, result.change);
         SpreadJsObj.loadSheetData(priceSheet, SpreadJsObj.DataType.Data, revisePrice.data);
         ledgerGcl.loadData(result.bills, result.pos);
+        chooseRelaBw.loadTree(result.bills);
+        chooseRelaChange.loadChange(result.change);
+        SpreadJsObj.loadSheetData(priceBwSheet, SpreadJsObj.DataType.Tree, revisePrice.tree);
+        SpreadJsObj.loadSheetData(priceChangeSheet, SpreadJsObj.DataType.Data, revisePrice.relaChange);
         $("[content='#ledgerGcl']").click();
     });
 
@@ -423,15 +970,26 @@ $(document).ready(() => {
         if (!tab.hasClass('active')) {
             $('a', '#side-menu').removeClass('active');
             tab.addClass('active');
-            $('.tab-content .tab-pane').removeClass('active');
+            $('#right-view .tab-pane').removeClass('active');
             tabPanel.addClass('active');
             showSideTools(tab.hasClass('active'));
             ledgerGcl.spread.refresh();
+            ledgerGcl.xmjSpread.refresh();
         } else {// 收起工具栏
             tab.removeClass('active');
             tabPanel.removeClass('active');
             showSideTools(tab.hasClass('active'));
         }
         priceSpread.refresh();
+        priceBwSpread.refresh();
+        priceChangeSpread.refresh();
+    });
+    $('a', '.bcontent-wrap').click(function() {
+        $('[name=priceRela]').removeClass('active');
+        $(this).addClass('active');
+        $('#priceRelaTab').children().removeClass('active');
+        $(this.getAttribute('href')).addClass('active');
+        priceBwSpread.refresh();
+        priceChangeSpread.refresh();
     });
 });

+ 8 - 7
app/public/js/schedule_stage_tp.js

@@ -86,15 +86,15 @@ $(function () {
     if (curScheduleStage && curScheduleStage.order) {
         postData('/tender/' + getTenderId() + '/schedule/stage/' + curScheduleStage.order + '/load', {}, function (data) {
             const calcList = ['year_gather_tp',
-                'contract_tp', 'qc_qty', 'qc_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp',
+                'qc_qty', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp', 'contract_pc_tp', 'qc_pc_tp', 'pc_tp',
                 'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp', 'end_correct_tp',
-                'final_contract_tp', 'final_qc_tp', 'pre_final_contract_tp', 'pre_final_qc_tp', 'pre_final_gather_tp', 'final_gather_tp', 'end_final_gather_tp'];
+                'final_contract_tp', 'final_qc_tp', 'final_pc_tp', 'pre_final_contract_tp', 'pre_final_qc_tp', 'pre_final_gather_tp', 'final_gather_tp', 'end_final_gather_tp'];
             const showList = ['plan_gcl', 'plan_tp', 'next_plan_gcl', 'next_plan_tp', 'end_plan_gcl', 'end_plan_tp', 'year_plan_gcl', 'year_plan_tp',
                 'year_contract_qty', 'year_gather_tp',
                 'contract_qty', 'end_gather_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'ledger_id', 'ledger_pid', 'order', 'level', 'tender_id', 'full_path',
                 'code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'dgn_price', 'quantity', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp',
                 'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp', 'end_correct_tp',
-                'final_contract_tp', 'final_qc_tp', 'pre_final_contract_tp', 'pre_final_qc_tp', 'pre_final_gather_tp', 'final_gather_tp', 'end_final_gather_tp'];
+                'final_contract_tp', 'final_qc_tp', 'final_pc_tp', 'pre_final_contract_tp', 'pre_final_qc_tp', 'pre_final_gather_tp', 'final_gather_tp', 'end_final_gather_tp'];
             const baseLedgerTreeSetting = {
                 id: 'ledger_id',
                 pid: 'ledger_pid',
@@ -104,7 +104,7 @@ $(function () {
                 fullPath: 'full_path',
                 calcFields: calcList,
             };
-            baseLedgerTreeSetting.updateFields = ['year_contract_qty', 'year_gather_tp', 'contract_qty', 'end_gather_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'final_contract_tp', 'final_qc_tp', 'postil', 'used', 'contract_expr'];
+            baseLedgerTreeSetting.updateFields = ['year_contract_qty', 'year_gather_tp', 'contract_qty', 'end_gather_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'final_contract_tp', 'final_qc_tp', 'final_pc_tp', 'postil', 'used', 'contract_expr'];
             baseLedgerTreeSetting.calcFun = function (node) {
                 if (!node.children || node.children.length === 0) {
                     node.pre_gather_qty = ZhCalc.add(node.pre_contract_qty, node.pre_qc_qty);
@@ -114,7 +114,7 @@ $(function () {
                     node.end_gather_qty = ZhCalc.add(node.pre_gather_qty, node.gather_qty);
                 }
                 node.pre_gather_tp = ZhCalc.add(node.pre_contract_tp, node.pre_qc_tp);
-                node.gather_tp = ZhCalc.add(node.contract_tp, node.qc_tp);
+                node.gather_tp = ZhCalc.sum([node.contract_tp, node.qc_tp, node.pc_tp]);
                 node.end_contract_tp = ZhCalc.add(node.pre_contract_tp, node.contract_tp);
                 node.end_qc_tp = ZhCalc.add(node.pre_qc_tp, node.qc_tp);
                 node.end_gather_tp = ZhCalc.add(node.pre_gather_tp, node.gather_tp);
@@ -132,8 +132,9 @@ $(function () {
                 node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
 
                 node.pre_final_gather_tp = ZhCalc.add(node.pre_final_contract_tp, node.pre_final_qc_tp);
-                node.final_gather_tp = ZhCalc.add(node.final_contract_tp, node.final_qc_tp);
+                node.final_gather_tp = ZhCalc.sum([node.final_contract_tp, node.final_qc_tp, node.final_pc_tp]);
                 node.end_final_gather_tp = ZhCalc.add(node.pre_final_gather_tp, node.final_gather_tp);
+                console.log(node)
             };
             const baseLedgerTree = createNewPathTree('base', baseLedgerTreeSetting);
             const newLedgerList = setMonthToLedger(data.ledgerData, slmList, nextSlmList, endSlmList, yearSlmList, curYearStageData);
@@ -294,7 +295,7 @@ function setMonthToLedger(ledgerList, slm, nextSlm, endSlm, yearSlm, yearLedgerD
             });
             if (index !== undefined && index !== -1) {
                 ledgerList[index].year_contract_qty = ZhCalc.add(yl.contract_qty, yl.qc_qty);
-                ledgerList[index].year_gather_tp = ZhCalc.add(yl.contract_tp, yl.qc_tp);
+                ledgerList[index].year_gather_tp = ZhCalc.sum([yl.contract_tp, yl.qc_tp, yl.pc_tp]);
             }
         }
     }

+ 56 - 10
app/public/js/se_bonus.js

@@ -217,20 +217,32 @@ $(document).ready(() => {
     class Bonus {
         constructor () {
             this.data = [];
+            this.displayData = [];
+            this._curOnlyKey = 'se_bonus_' + window.location.pathname.split('/')[2];
+            const cache = getLocalCache(this._curOnlyKey);
+            this._curOnly = cache && cache === 'true' ? true : false;
         }
         resortData() {
             this.data.sort(function (a, b) {
                 return a.sorder !== b.sorder ? a.sorder - b.sorder : a.order - b.order;
             });
         }
+        refreshDisplay() {
+            this.displayData.length = 0;
+            for (const d of this.data) {
+                if (!this._curOnly || d.sid === stageId) this.displayData.push(d);
+            }
+        }
         loadDatas(datas) {
             this.data = datas;
             this.resortData();
+            this.refreshDisplay();
         }
         loadUpdateData(updateData) {
             if (updateData.add) {
                 for (const a of updateData.add) {
                     this.data.push(a);
+                    this.displayData.push(a);
                 }
             }
             if (updateData.update) {
@@ -242,6 +254,7 @@ $(document).ready(() => {
                         _.assign(d, u);
                     } else {
                         this.data.push(d);
+                        this.displayData.push(d);
                     }
                 }
             }
@@ -249,6 +262,9 @@ $(document).ready(() => {
                 _.remove(this.data, function (d) {
                     return updateData.del.indexOf(d.id) >= 0;
                 });
+                _.remove(this.displayData, function (d) {
+                    return updateData.del.indexOf(d.id) >= 0;
+                });
             }
             this.resortData();
         }
@@ -271,30 +287,55 @@ $(document).ready(() => {
             return cur.indexOf(bonusData) === cur.length - 1;
         }
         sum () {
-            const result = {
-                tp: 0,
-            };
-            for (const d of this.data) {
-                result.tp = ZhCalc.add(result.tp, d.tp);
+            const result = [];
+            const sum = { name: '合计', positive_tp: 0, negative_tp: 0 };
+            for (const d of this.displayData) {
+                if (d.b_type) {
+                    let type = result.find(x => { return d.b_type === x.name; });
+                    if (!type) {
+                        type = { name: d.b_type, positive_tp: 0, negative_tp: 0 };
+                        result.push(type);
+                    }
+                    if (d.tp >= 0) {
+                        type.positive_tp = ZhCalc.add(type.positive_tp, d.tp);
+                    } else {
+                        type.negative_tp = ZhCalc.add(type.negative_tp, d.tp);
+                    }
+                }
+                if (d.tp >= 0) {
+                    sum.positive_tp = ZhCalc.add(sum.positive_tp, d.tp);
+                } else {
+                    sum.negative_tp = ZhCalc.add(sum.negative_tp, d.tp);
+                }
             }
+            result.push(sum);
+            result.forEach(x => { x.tp = ZhCalc.add(x.positive_tp, x.negative_tp); });
             return result;
         }
+        set curOnly(b) {
+            this._curOnly = b;
+            setLocalCache(this._curOnlyKey, this._curOnly);
+            this.refreshDisplay();
+        }
+        get curOnly() {
+            return this._curOnly;
+        }
     }
     const bonusObj = new Bonus();
     const refreshSum = function () {
         const sum = bonusObj.sum();
         const html = [];
-        const getTrHtml = function (name, value) {
-            return '<tr><td>' + name + '</td><td class="text-right">' + (!checkZero(value) ? value : '') + ' </td></tr>';
-        };
-        html.push(getTrHtml('金额', sum.tp));
+        for (const s of sum) {
+            html.push(`<tr><td>${s.name}</td><td class="text-right">${s.positive_tp || ''}</td><td class="text-right">${s.negative_tp || ''}</td><td class="text-right">${s.tp || ''}</td></tr>`);
+        }
         $('#sum').html(html.join(' '));
     };
 
     postData(window.location.pathname + '/load', null, function (result) {
         bonusObj.loadDatas(result);
-        SpreadJsObj.loadSheetData(bonusSheet, SpreadJsObj.DataType.Data, bonusObj.data);
+        SpreadJsObj.loadSheetData(bonusSheet, SpreadJsObj.DataType.Data, bonusObj.displayData);
         refreshSum();
+        $('#cur-only')[0].checked = bonusObj.curOnly;
     });
 
     if (!readOnly) {
@@ -590,4 +631,9 @@ $(document).ready(() => {
     });
     $('#upload-file').change(fileObj.uploadFile);
     $('body').on('click', '.delete-att', fileObj.deleteFile);
+    $('#cur-only').change(function (){
+        bonusObj.curOnly = this.checked;
+        SpreadJsObj.reLoadSheetData(bonusSheet, true);
+        refreshSum();
+    })
 });

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

@@ -20,7 +20,7 @@
                     {title: '单位', field: 'unit', width: 50, formatter: '@', hAlign: 1, },
                     {title: '单价', field: 'unit_price', width: 60, hAlign: 2, },
                     {title: '数量', field: 'quantity', width: 60, hAlign: 2, },
-                    {title: '数量', field: 'total_price', width: 60, hAlign: 2, },
+                    {title: '金额', field: 'total_price', width: 60, hAlign: 2, },
                 ],
                 emptyRows: 0,
                 headRows: 1,

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

@@ -68,9 +68,9 @@ const showSelectTab = function(select, spread, afterShow) {
         if (!setting.spreadSetting) {
             setting.spreadSetting = {
                 cols: [
-                    {title: '行号', field: 'serialNo', width: 50, formatter: '@'},
+                    {title: '行号', field: 'serialNo', width: 50, formatter: '@', hAlign: 1},
                     {
-                        title: '错误类型', field: 'errorType', width: 60, formatter: '@',
+                        title: '错误类型', field: 'errorType', width: 85, formatter: '@',
                         getValue: function (x) {
                             switch (x.errorType) {
                                 case 'gather': return '汇总错误';
@@ -83,12 +83,14 @@ const showSelectTab = function(select, spread, afterShow) {
                                 case 's2b_over_dagl': return '违规计量(档案管理)';
                                 case 's2b_lost_gxby': return '遗漏计量(工序报验)';
                                 case 's2b_lost_dagl': return '遗漏计量(档案管理)';
+                                case 'minus_cb': return '负变更清单漏计';
                                 default: return '';
                             }
                         }
                     },
-                    {title: '清单编号', field: 'b_code', width: 135, formatter: '@'},
-                    {title: '清单名称', field: 'name', width: 215, formatter: '@'},
+                    {title: '清单编号', field: 'b_code', width: 85, formatter: '@'},
+                    {title: '清单名称', field: 'name', width: 165, formatter: '@'},
+                    {title: '备注', field: 'memo', width: 100, formatter: '@'},
                 ],
                 emptyRows: 0,
                 headRows: 1,
@@ -146,7 +148,7 @@ const showSelectTab = function(select, spread, afterShow) {
                     d.serialNo = sourceTree.getNodeIndex(sourceTree.getItems(d.ledger_id)) + 1;
                 }
                 data.sort(function (a, b) {
-                    return a.serialNo - b.serialNo;
+                    return a.serialNo ? (b.serialNo ? a.serialNo - b.serialNo : 1) : 1;
                 });
 
                 SpreadJsObj.loadSheetData(sheet, SpreadJsObj.DataType.Data, data);
@@ -221,7 +223,7 @@ const showSelectTab = function(select, spread, afterShow) {
                         }
                     },
                     {title: '行号', field: 'serialNo', hAlign: 1, width: 40, formatter: '@'},
-                    {title: '项目节编号', field: 'code', width: 80, formatter: '@'},
+                    {title: '项目节编号\n(变更令号)', field: 'code', width: 80, formatter: '@'},
                     {title: '清单编号', field: 'b_code', width: 80, formatter: '@'},
                     {title: '名称', field: 'name', width: 150, formatter: '@'},
                 ],

+ 18 - 1
app/public/js/shares/gcl_gather_compare.js

@@ -397,6 +397,23 @@ const gclCompareModel = (function () {
         ledgerSetting = null;
     }
 
+    function gatherReviseLedgerData(bills, pos, setting, price, decimal) {
+        ledgerSetting = setting;
+        try {
+            if (leafXmjs.length > 0) leafXmjs.length = 0;
+            gsTree = createNewPathTree('revise', setting.tree);
+            gsTree.loadRevisePrice(price, decimal);
+            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;
@@ -483,7 +500,7 @@ const gclCompareModel = (function () {
 
     return {
         init,
-        gatherLedgerData, gatherDealBills, checkDiffer,
+        gatherLedgerData, gatherReviseLedgerData, gatherDealBills, checkDiffer,
         chapterData
     };
 })();

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

@@ -100,9 +100,30 @@ const sjsSettingObj = (function () {
             }
         }
     };
+    const setNodeTypeCol = function (cols, rela) {
+        for (const r of rela) {
+            const col = _.find(cols, {field: r.field});
+            if (col) {
+                col.comboItems = nodeType;
+                col.cellType = 'specCombo';
+                col.comboEdit = function (sheet, data) {
+                    if (!data) return false;
+
+                    const tree = sheet.zh_tree;
+                    if (!tree) return false;
+
+                    const parent = tree.getParent(data);
+                    if (!parent) return false;
+
+                    const topParent = tree.getTopParent(data);
+                    return [1, 5].indexOf(topParent.node_type) >= 0;
+                };
+            }
+        }
+    };
     return {
         setFxTreeStyle, FxTreeStyle, setGridSelectStyle,
         setTpThousandthFormat, setThousandthFormat, setTpColsThousandthFormat,
-        setPropValue, set3FCols, setQcCols, setOrgPriceCol,
+        setPropValue, set3FCols, setQcCols, setOrgPriceCol, setNodeTypeCol,
     };
 })();

+ 179 - 517
app/public/js/shenpi.js

@@ -224,7 +224,7 @@ function getShenpiHtml (this_code) {
 $(document).ready(function () {
     let timer = null;
     let oldSearchVal = null;
-    const needYB = ['ledger', 'revise', 'change'];
+    const needYB = ['advance', 'ledger', 'revise', 'change', 'audit-ass'];
     $('body').on('input propertychange', '.gr-search', function(e) {
         oldSearchVal = e.target.value;
         timer && clearTimeout(timer);
@@ -341,12 +341,17 @@ $(document).ready(function () {
     // 选中审批人
     $('body').on('click', 'dl dd', function () {
         const id = parseInt($(this).data('id'));
-        if (id) {
-            const user = _.find(accountList, function (item) {
-                return item.id === id;
-            });
+        if (!id) return;
+
+        let this_code = $(this).parents('.lc-show').siblings('.form-group').find('input:checked').data('code');
+        if (!this_code) this_code = $(this).parents('.dropdown').data('code');
+        const user = _.find(accountList, function (item) {
+            return item.id === id;
+        });
+        if (this_code === 'audit-ass') {
+            auditAss.setAuditAssist(user);
+        } else {
             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;
@@ -383,7 +388,7 @@ $(document).ready(function () {
                     '                                                            </div>\n' +
                     '                                                        </span> ' +
                     '                                            </span></span></span>\n');
-                    // <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>');
+                // <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>');
             });
         }
     });
@@ -605,421 +610,191 @@ $(document).ready(function () {
         })
     });
 
-    const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
-    const treeSetting = {
-        id: 'ledger_id',
-        pid: 'ledger_pid',
-        order: 'order',
-        level: 'level',
-        rootId: -1,
-        fullPath: 'full_path',
-    };
-    const ledgerTree = createNewPathTree('base', treeSetting);
-
-    const ledgerSpreadSetting = {
-        cols: [
-            {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 165, formatter: '@', readOnly: true, cellType: 'tree'},
-            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', readOnly: true},
-            {title: '密码', colSpan: '1', rowSpan: '2', field: 'pwd', hAlign: 0, width: 100, formatter: '@', getValue:'getValue.pwd', readOnly: 'readOnly.pwd'},
-        ],
-        emptyRows: 0,
-        headRows: 1,
-        headRowHeight: [25, 25],
-        defaultRowHeight: 21,
-        headerFont: '12px 微软雅黑',
-        font: '12px 微软雅黑',
-        // readOnly: true,
-        localCache: {
-            key: 'ledger-cooperation',
-            colWidth: true,
-        }
-    };
-
-    const ledgerCol = {
-        getValue: {
-            pwd: function (data) {
-                let txt = '';
-                // console.log(data);
-                // if (data.is_leaf) {
-                if (data.pwd && data.pwd !== '' && data.pwd !== null) {
-                    txt = data.pwd;
-                } else if(data.can_edit) {
-                    txt = '请输入密码';
+    class AuditAss {
+        constructor() {
+            this.spread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
+            this.sheet = this.spread.getActiveSheet();
+            this.tree = createNewPathTree('base', {
+                id: 'ledger_id',
+                pid: 'ledger_pid',
+                order: 'order',
+                level: 'level',
+                rootId: -1,
+            });
+            this.spreadSetting = {
+                cols: [
+                    {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 165, formatter: '@', cellType: 'tree'},
+                    {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@'},
+                    {title: '协同人', colSpan: '1', rowSpan: '2', field: 'ass_name', hAlign: 1, width: 100, formatter: '@'},
+                ],
+                emptyRows: 0,
+                headRows: 1,
+                headRowHeight: [25, 25],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                readOnly: true,
+                localCache: {
+                    key: 'ledger-cooperation',
+                    colWidth: true,
                 }
-                // }
-                return txt;
-            }
-        },
-        readOnly: {
-            pwd: function (data) {
-                return !data.can_edit;
-            },
-        },
-    };
+            };
+            sjsSettingObj.setFxTreeStyle(this.spreadSetting, sjsSettingObj.FxTreeStyle.jz);
+            SpreadJsObj.initSheet(this.sheet, this.spreadSetting);
+
+            const self = this;
+            SpreadJsObj.addDeleteBind(this.spread, function() { return; });
+            SpreadJsObj.selChangedRefreshBackColor(this.sheet);
+            this.spread.bind(spreadNS.Events.SelectionChanged, function() {
+                self.refreshOperate();
+            });
 
-    const ledgerSpreadObj = {
-        setFontColor: function(row = null) {
-            if(row) {
-                const value = ledgerSpread.getActiveSheet().getValue(row, 2);
-                if (value === '请输入密码') {
-                    ledgerSpread.getActiveSheet().getCell(row, 2).foreColor('#007bff');
-                } else {
-                    ledgerSpread.getActiveSheet().getCell(row, 2).foreColor('#000');
-                }
-            } else {
-                const rowCount = ledgerSpread.getActiveSheet().getRowCount();
-                for(let i = 0; i < rowCount; i++){
-                    const value = ledgerSpread.getActiveSheet().getValue(i, 2);
-                    if (value === '请输入密码') {
-                        ledgerSpread.getActiveSheet().getCell(i, 2).foreColor('#007bff');
-                    } else {
-                        ledgerSpread.getActiveSheet().getCell(i, 2).foreColor('#000');
-                    }
-                }
-            }
-        },
-        setAllRightPwd: function(uid) {
-            selects = [];
-            for (const l of ledgerTree.nodes) {
-                const coo = _.find(ledger_cooperation_list, { 'ledger_id': l.ledger_id, 'user_id': parseInt(uid) });
-                if (l.pwd && !coo) {
-                    delete l.pwd;
-                    l.can_edit = true;
-                    selects.push(l);
-                } else if(coo) {
-                    l.pwd = coo.pwd;
-                    l.can_edit = true;
-                    selects.push(l);
-                } else if (l.can_edit === false) {
-                    l.can_edit = true;
-                    selects.push(l);
-                }
-            }
-            if(selects.length > 0) {
-                // updateByCanEdit(ledgerTree.nodes, _.filter(ledger_cooperation_list, { user_id: parseInt(uid) }), false);
-                const refreshNode = ledgerTree.loadPostData({update: selects});
-                ledgerSpreadObj.refreshTree(ledgerSpread.getActiveSheet(), refreshNode);
-                ledgerSpreadObj.setFontColor();
-            }
-        },
-        refreshTree: function (sheet, data) {
-            SpreadJsObj.massOperationSheet(sheet, function () {
-                const tree = sheet.zh_tree;
-                // 处理删除
-                if (data.delete) {
-                    data.delete.sort(function (x, y) {
-                        return y.deleteIndex - x.deleteIndex;
-                    });
-                    for (const d of data.delete) {
-                        sheet.deleteRows(d.deleteIndex, 1);
-                    }
-                }
-                // 处理新增
-                if (data.create) {
-                    const newNodes = data.create;
-                    if (newNodes) {
-                        newNodes.sort(function (a, b) {
-                            return a.index - b.index;
-                        });
+            $('#stage_audits').change(function () {
+                self.uid = $(this).val();
+            });
 
-                        for (const node of newNodes) {
-                            sheet.addRows(node.index, 1);
-                            SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(node), 1);
-                        }
-                    }
-                }
-                // 处理更新
-                if (data.update) {
-                    const rows = [];
-                    for (const u of data.update) {
-                        rows.push(tree.nodes.indexOf(u));
-                    }
-                    SpreadJsObj.reLoadRowsData(sheet, rows);
-                }
-                // 处理展开
-                if (data.expand) {
-                    const expanded = [];
-                    for (const e of data.expand) {
-                        if (expanded.indexOf(e) === -1) {
-                            const posterity = tree.getPosterity(e);
-                            for (const p of posterity) {
-                                sheet.setRowVisible(tree.nodes.indexOf(p), p.visible);
-                                expanded.push(p);
-                            }
+            // 多人协同
+            $('#cooperation').on('shown.bs.modal', function () {
+                // 执行一些动作...
+                // 更新新的多人协同表格信息
+                const newUidList = [];
+                $('.stage_div ul li').each(function (k, v) {
+                    const uid = $(v).find('button').eq(0).data('id');
+                    if(uid) newUidList.push(uid);
+                });
+                const oldUidList = [];
+                $('#stage_audits option').each(function (k, v) {
+                    const uid = parseInt($(v).val());
+                    if(k !== 0) oldUidList.push(uid);
+                });
+                if (!_.isEqual(oldUidList, newUidList)) {
+                    const yb = _.find(accountList, { 'id': cur_uid });
+                    let newhtml = '<option value="' + yb.id + '">' + yb.name + '(原报)</option>';
+                    if(newUidList.length > 0) {
+                        for (const [i,id] of newUidList.entries()) {
+                            const audit = _.find(accountList, { 'id': id });
+                            newhtml += '<option value="' + audit.id + '">' + audit.name + '(' + transFormToChinese(i+1) + '审)</option>';
                         }
                     }
+                    $('#stage_audits').html(newhtml);
+                    self.uid = cur_uid;
                 }
+                self.initLedgerTree(cur_uid);
             });
-        },
-        enterCell: function(e, info) {
-            if (info.sheet.zh_setting && info.col === 2) {
-                if (ledgerSpread.getActiveSheet().getValue(info.row, info.col) !== '') {
-                    const col = info.sheet.zh_setting.cols[info.col];
-                    info.sheet.setActiveCell(info.row, 2);
-                    info.sheet.startEdit(true);
-                }
+            $('#del-audit-ass').click(function () {
+                self.setAuditAssist();
+            });
+        }
+        set uid(id) {
+            this._uid = parseInt(id);
+            this._refreshAss();
+        }
+        get uid() {
+            return this._uid;
+        }
+        _refreshAssTable(){
+            $('#stage_audit').text($("#stage_audits option:selected").text());
+            const html = [];
+            for (const sa of this.showAssList) {
+                const lid = sa.ass_ledger_id ? sa.ass_ledger_id.split(',') : [];
+                html.push(`<tr><td>${sa.name}</td><td>${sa.company}</td><td>${lid.length}</td></tr>`);
             }
-        },
-        editStarting: function (e, info) {
-            if (info.sheet.zh_setting) {
-                const select = SpreadJsObj.getSelectObject(info.sheet);
-                const col = info.sheet.zh_setting.cols[info.col];
-                ledgerSpread.getActiveSheet().getCell(info.row, 2).foreColor('#000');
-                if(col.getValue(select) === '请输入密码') {
-                    ledgerSpread.getActiveSheet().setValue(info.row, 2, '');
-                }
+            $('#coo_table').html(html.join(''));
+        }
+        _refreshAssTree() {
+            const ledgerAss = {};
+            for (const sa of this.showAssList) {
+                const ledger = sa.ass_ledger_id ? sa.ass_ledger_id.split(',') : [];
+                ledger.forEach(l => { ledgerAss[l] = sa; });
             }
-        },
-        editEnded: function (e, info) {
-            if (info.sheet.zh_setting) {
-                const select = SpreadJsObj.getSelectObject(info.sheet);
-                const col = info.sheet.zh_setting.cols[info.col];
-                const validText = trimInvalidChar(info.editingText);
-                const user_id = parseInt($('#stage_audits').val());
-                const orgValue = select[col.field];
-                const reg = /^[0-9a-zA-Z]+$/;
-                if(validText !== '' && !reg.test(validText)) {
-                    toastr.error('不能输入非数字和字母的字符');
-                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                    ledgerSpreadObj.setFontColor(info.row);
-                    return;
-                }
-                if((validText === '' && orgValue === undefined) || validText == orgValue) {
-                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                    ledgerSpreadObj.setFontColor(info.row);
-                    return;
-                }
-                // const num = _.filter(ledger_cooperation_list, { pwd: validText, status: 1, user_id });
-                // console.log(num, ledger_cooperation_list);
-                // if(num.length > 0) {
-                //     toastr.error('同一个审批人密码不能相同');
-                //     SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                //     ledgerSpreadObj.setFontColor(info.row);
-                //     return;
-                // }
-                select.pwd = validText;
-                const data = {
-                    type: 'pwd',
-                    ledger_id: select.ledger_id,
-                    user_id,
-                    pwd: validText,
-                };
-                info.sheet.setSelection(info.row, 0, 1, 1);
-                postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
-                    const lcindex = _.findIndex(ledger_cooperation_list, { ledger_id: select.ledger_id, user_id });
-                    let flag = false;
-                    if(lcindex !== -1) {
-                        validText === '' ? ledger_cooperation_list.splice(lcindex, 1) : ledger_cooperation_list.splice(lcindex, 1, result);
-                        flag = validText === '';
-                    } else {
-                        ledger_cooperation_list.push(result);
-                        flag = false;
-                    }
-                    $('#cooperation-num').text(ledger_cooperation_list.length);
-                    setLeftTable(ledgerTree.nodes, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
-                    selects = [select];
-                    // if(select) {
-                    //     setAllChildrenCanEdit(select, flag);
-                    //     setParentCanEdit(ledgerTree.nodes, select.ledger_pid, flag);
-                    // }
-                    const refreshNode = ledgerTree.loadPostData({update: selects});
-                    ledgerSpreadObj.refreshTree(info.sheet, refreshNode);
-                    ledgerSpreadObj.setFontColor();
-                });
+            for (const n of this.tree.nodes) {
+                const la = ledgerAss[n.ledger_id];
+                n.ass_audit_id = la ? la.ass_user_id : null;
+                n.ass_name = la ? la.name : '';
             }
-        },
-        deletePress: function (sheet) {
-            return;
-        },
-        clipboardPasted(e, info) {
-            // 禁止复制粘贴
-            SpreadJsObj.reLoadSheetHeader(ledgerSpread.getActiveSheet());
-            SpreadJsObj.reLoadSheetData(ledgerSpread.getActiveSheet());
-            return;
+            SpreadJsObj.reloadColData(this.sheet, 2);
+        }
+        _refreshAss() {
+            this.showAssList = _.filter(this.assList, { 'user_id': parseInt(this.uid) });
+            this._refreshAssTable();
+            this._refreshAssTree();
+        }
+        initLedgerTree(uid) {
+            if (this.loaded) return;
+            this.spread.refresh();
+            const self = this;
+
+            postData('/tender/' + cur_tenderid + '/shenpi/ass/load', {}, function (data) {
+                self.loaded = true;
+                self.assList = data.auditAssList;
+                self.tree.loadDatas(data.ledgerList);
+                SpreadJsObj.loadSheetData(self.sheet, SpreadJsObj.DataType.Tree, self.tree);
+                self.uid = uid;
+                self.refreshOperate();
+            }, null, true);
         }
-    };
-
-    sjsSettingObj.setFxTreeStyle(ledgerSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
-    if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
-    SpreadJsObj.initSpreadSettingEvents(ledgerSpreadSetting, ledgerCol);
-    SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
-    SpreadJsObj.selChangedRefreshBackColor(ledgerSpread.getActiveSheet());
-    ledgerSpread.bind(spreadNS.Events.EditStarting, ledgerSpreadObj.editStarting);
-    ledgerSpread.bind(spreadNS.Events.EditEnded, ledgerSpreadObj.editEnded);
-    SpreadJsObj.addDeleteBind(ledgerSpread, ledgerSpreadObj.deletePress);
-    ledgerSpread.bind(spreadNS.Events.ClipboardPasted, ledgerSpreadObj.clipboardPasted);
-    ledgerSpread.bind(spreadNS.Events.EnterCell, ledgerSpreadObj.enterCell);
-    let ledger_data, ledger_cooperation_list = [];
+        loadPostData(data) {
+            if (data.add) {
+                this.assList.push(data.add);
+            }
+            if (data.del) {
+                this.assList.splice(this.assList.findIndex(x => { return x.id === data.del.id }), 1);
+            }
+            if (data.update) {
+                for (const d of data.update) {
+                    const od = this.assList.find(x => { return x.id === d.id });
+                    if (!od) continue;
 
-    // 多人协同
-    $('#cooperation').on('shown.bs.modal', function () {
-        // 执行一些动作...
-        // 更新新的多人协同表格信息
-        const newUidList = [];
-        $('.stage_div ul li').each(function (k, v) {
-            const uid = $(v).find('button').eq(0).data('id');
-            if(uid) newUidList.push(uid);
-        });
-        const oldUidList = [];
-        $('#stage_audits option').each(function (k, v) {
-            const uid = parseInt($(v).val());
-            if(k !== 0) oldUidList.push(uid);
-        });
-        if (!_.isEqual(oldUidList, newUidList)) {
-            const yb = _.find(accountList, { 'id': cur_uid });
-            let newhtml = '<option value="' + yb.id + '">' + yb.name + '(原报)</option>';
-            if(newUidList.length > 0) {
-                for (const [i,id] of newUidList.entries()) {
-                    const audit = _.find(accountList, { 'id': id });
-                    newhtml += '<option value="' + audit.id + '">' + audit.name + '(' + transFormToChinese(i+1) + '审)</option>';
+                    od.ass_ledger_id = d.ass_ledger_id;
                 }
             }
-            $('#stage_audits').html(newhtml);
-            if(ledger_data) {
-                setLeftTable(ledgerTree.nodes, ledger_cooperation_list, cur_uid, yb.name + '(原报)');
-                ledgerSpreadObj.setAllRightPwd(cur_uid);
+            this._refreshAss();
+        }
+        setAuditAssist(assist){
+            const node = SpreadJsObj.getSelectObject(this.sheet);
+            if (assist && node.ass_audit_id === assist.id) return;
+            if (assist && assist.id === this.uid) {
+                toastr.warning('请勿添加本人');
+                return;
             }
-        }
-        if(!ledger_data) {
-            postData('/tender/' + cur_tenderid + '/shenpi/ledger/load', {}, function (data) {
-                ledger_data = true;
-                const ledgerList = setRightData(data.ledgerList, data.ledgerCooperationList);
-                ledgerTree.loadDatas(ledgerList);
-                // treeCalc.calculateAll(ledgerTree);
-                selects = [];
-                // updateByCanEdit(ledgerTree.nodes, _.filter(ledger_cooperation_list, { user_id: cur_uid }), false);
-                ledgerTree.loadPostData({update: selects});
-                console.log(ledgerTree);
-                ledger_cooperation_list = data.ledgerCooperationList;
-                const yb = _.find(accountList, { 'id': cur_uid });
-                setLeftTable(ledgerTree.nodes, ledger_cooperation_list, cur_uid, yb.name + '(原报)');
-                SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, ledgerTree);
-                ledgerSpreadObj.setFontColor();
-            }, null, true);
-        }
-        ledgerSpread.refresh();
-    });
-
-    $('#stage_audits').change(function () {
-        const uid = $(this).val();
-        const title = $("#stage_audits option:selected").text();
-        setLeftTable(ledgerTree.nodes, ledger_cooperation_list, uid, title);
-        ledgerSpreadObj.setAllRightPwd(uid);
-    });
-
-    $('body').on('click', '.del-pwd', function () {
-        const ledger_id = parseInt($(this).data('lid'));
-        const user_id = parseInt($(this).data('uid'));
-        const data = {
-            type: 'pwd',
-            user_id,
-            ledger_id,
-            pwd: '',
-        };
-        postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
-            const lcindex = _.findIndex(ledger_cooperation_list, { ledger_id, user_id });
-            ledger_cooperation_list.splice(lcindex, 1);
-            setLeftTable(ledgerTree.nodes, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
-            const select = _.find(ledgerTree.nodes, { ledger_id });
-            delete select.pwd;
-            // const refreshNode = ledgerTree.loadPostData({update: select});
-            selects = [select];
-            // if(select) {
-            //     setAllChildrenCanEdit(select, true);
-                // setParentCanEdit(ledgerTree.nodes, select.ledger_pid, true);
-            // }
-            const refreshNode = ledgerTree.loadPostData({update: selects});
-            ledgerSpreadObj.refreshTree(ledgerSpread.getActiveSheet(), refreshNode);
-            ledgerSpreadObj.setFontColor();
-            $('#cooperation-num').text(ledger_cooperation_list.length);
-        });
-    });
-
-    $('body').on('click', '.edit-pwd', function () {
-        const pwd = $(this).data('pwd');
-        const html = `<div class="input-group input-group-sm">
-            <input type="text" class="form-control" value="${pwd}" placeholder="输入新密码" aria-describedby="button-${$(this).data('lid')}" style="width:50px">
-            <div class="input-group-append" id="button-${$(this).data('lid')}">
-                <button class="btn btn-outline-primary confirm-btn" data-pwd="${pwd}" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" type="button">确认</button>
-                <button class="btn btn-outline-secondary cancel-btn" data-pwd="${pwd}" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" type="button">取消</button>
-            </div>
-            </div>`;
-        $(this).parents('td').html(html);
-    });
-
-    $('body').on('click', '.cancel-btn', function () {
-        const html = `<p class="mb-0">${$(this).data('pwd')}</p><a href="javascript:void(0);" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" data-pwd="${$(this).data('pwd')}" class="edit-pwd">修改</a> <a href="javascript:void(0)" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" class="del-pwd text-danger">移除</a>`;
-        $(this).parents('td').html(html);
-    });
-
-    $('body').on('click', '.confirm-btn', function () {
-        const validText = $(this).parents('td').find('input').val();
-        const orgValue = $(this).data('pwd');
-        const ledger_id = parseInt($(this).data('lid'));
-        const user_id = parseInt($(this).data('uid'));
-        const reg = /^[0-9a-zA-Z]+$/;
-        if(!reg.test(validText)) {
-            toastr.error('不能输入非数字和字母的字符');
-            return;
-        }
-        if(validText == orgValue) {
-            const html = `<p class="mb-0">${$(this).data('pwd')}</p><a href="javascript:void(0);" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" data-pwd="${$(this).data('pwd')}" class="edit-pwd">修改</a> <a href="javascript:void(0)" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" class="del-pwd text-danger">移除</a>`;
-            $(this).parents('td').html(html);
-            return;
-        }
-        // const num = _.filter(ledger_cooperation_list, { pwd: validText, status: 1, user_id });
-        // if(num.length > 0) {
-        //     toastr.error('同一个审批人密码不能相同');
-        //     return;
-        // }
-        const data = {
-            type: 'pwd',
-            ledger_id,
-            user_id,
-            pwd: validText,
-        };
-        postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
-            const lcindex = _.findIndex(ledger_cooperation_list, { ledger_id, user_id });
-            ledger_cooperation_list.splice(lcindex, 1, result);
-            setLeftTable(ledgerTree.nodes, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
-            const select = _.find(ledgerTree.nodes, { ledger_id });
-            select.pwd = validText;
-            const refreshNode = ledgerTree.loadPostData({update: select});
-            ledgerSpreadObj.refreshTree(ledgerSpread.getActiveSheet(), refreshNode);
-            ledgerSpreadObj.setFontColor();
-        });
-    });
 
-    // 上传图片
-    $('body').on('click', '.upload-img', function () {
-        $(this).siblings('input').trigger('click');
-        // $('.upload-img-file').trigger('click');
-    });
-    $('body').on('change', '.upload-img-file', function () {
-        const file = this.files[0];
-        const ext = file.name.toLowerCase().split('.').splice(-1)[0];
-        const imgStr = /(png|PNG)$/;
-        if (!imgStr.test(ext)) {
-            toastr.error('请上传签名大小为600x300,格式PNG透明背景。');
-            return
-        }
-        if ($(this).val()) {
-            var _self = $(this);
-            const formData = new FormData();
-            formData.append('id', $(this).data('id'));
-            formData.append('file', this.files[0]);
-            postDataWithFile(window.location.pathname + '/save-sign', formData, function (result) {
-                _self.siblings('img').attr('src', '/' + result);
-                _self.siblings('img').show();
-                _self.siblings('a').removeClass('btn btn-outline-primary btn-sm').addClass('d-inline-flex').text('更改');
-                _self.val('');
-                const lcindex = _.findIndex(ledger_cooperation_list, { id: _self.data('id') });
-                ledger_cooperation_list[lcindex].sign_path = result;
-                ledger_cooperation_list.splice(lcindex, 1, ledger_cooperation_list[lcindex]);
+            const self = this;
+            const data = { type: 'audit-ass'};
+            if (assist) {
+                const newAss = this.showAssList.find(x => { return x.ass_user_id === assist.id; });
+                if (!newAss) {
+                    data.add = { user_id: this.uid, ass_user_id: assist.id, name: assist.name, company: assist.company, role: assist.role, ass_ledger_id: node.ledger_id + '' };
+                } else {
+                    data.update = [{ id: newAss.id, ass_ledger_id: newAss.ass_ledger_id + ',' + node.ledger_id}];
+                }
+            }
+            if (node.ass_audit_id) {
+                const orgAss = this.showAssList.find(x => { return x.ass_user_id === node.ass_audit_id; });
+                const rela_lid = orgAss.ass_ledger_id ? orgAss.ass_ledger_id.split(',') : [];
+                rela_lid.splice(rela_lid.indexOf(node.ledger_id + ''), 1);
+                if (rela_lid.length === 0) {
+                    data.del = { id: orgAss.id };
+                } else {
+                    if (!data.update) data.update = [];
+                    data.update.push({ id: orgAss.id, ass_ledger_id: rela_lid.join(',')});
+                }
+            }
+            postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
+                self.loadPostData(result);
             });
         }
-    });
-
+        refreshOperate() {
+            const node = SpreadJsObj.getSelectObject(this.sheet);
+            if (node.ass_audit_id) {
+                $('#del-audit-ass').show();
+                $('#audit-ass_dropdownMenuButton')[0].innerHTML = '替换协同人';
+            } else {
+                $('#del-audit-ass').hide();
+                $('#audit-ass_dropdownMenuButton')[0].innerHTML = '添加协同人';
+            }
+        }
+    }
+    const auditAss = new AuditAss();
 
     $.subMenu({
         menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
@@ -1106,117 +881,4 @@ $(document).ready(function () {
         const X = $('#tender-list').find('.result').eq(now-1).offset().top;
         $('#tender-list').scrollTop(X - $('#tender-list').offset().top + $('#tender-list').scrollTop() -30);
     });
-
-    $('body').on('blur', '#coo_table .edit-company', function () {
-        const id = $(this).data('id');
-        const newVal = $(this).val();
-        const cooInfo = _.find(ledger_cooperation_list, { id: id });
-        if(cooInfo && cooInfo.company !== newVal) {
-            const data = {
-                type: 'company',
-                id,
-                company: newVal,
-            };
-            postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
-                const lcindex = _.findIndex(ledger_cooperation_list, { id: id });
-                cooInfo.company = newVal;
-                ledger_cooperation_list.splice(lcindex, 1, cooInfo);
-            });
-        }
-    })
 });
-
-function setRightData(datas, coolist) {
-    const newdatas = [];
-    const reg = /(^GD([0-9\-]+))|(^([0-9\-]+)$)/;
-    for (const l of datas) {
-        // if (reg.test(l.code) && l.level <= 4) {
-        if (reg.test(l.code)) {
-            // if(l.level === 4 && l.is_leaf === false) {
-            //     l.is_leaf = true;
-            // }
-            const coo = _.find(coolist, { 'ledger_id': l.ledger_id, 'user_id': cur_uid });
-            if(coo) {
-                l.pwd = coo.pwd;
-            }
-            l.can_edit = true;
-            newdatas.push(l);
-        }
-    }
-    // for (const nd of newdatas) {
-    //     const child = _.find(newdatas, { 'ledger_pid': nd.ledger_id });
-    //     if (!child && !nd.is_leaf) {
-    //         nd.is_leaf = true;
-    //     }
-    // }
-    return newdatas;
-}
-function updateByCanEdit(datas, coolist, flag) {
-    for (const coo of coolist) {
-        const ledgerInfo = _.find(datas, { 'ledger_id': coo.ledger_id });
-        if(ledgerInfo) {
-            setAllChildrenCanEdit(ledgerInfo, flag);
-            // setParentCanEdit(datas, ledgerInfo.ledger_pid, flag);
-        }
-    }
-}
-function setParentCanEdit(datas, id, flag) {
-    if(id !== -1) {
-        const pl = _.find(datas, { 'ledger_id': id });
-        // 判断父节点下所有子节点有无pwd,有则为false
-        if (pl) {
-            const existfalse = _.find(pl.children, function (item) {
-                return item.can_edit === false || (item.pwd && item.pwd !== '' && item.pwd !== null);
-            });
-            pl.can_edit = existfalse ? false : flag;
-            selects.push(pl);
-            setParentCanEdit(datas, pl.ledger_pid, flag);
-        }
-    }
-}
-function setAllChildrenCanEdit(ledgerInfo, flag) {
-    if (ledgerInfo.children && ledgerInfo.children.length > 0) {
-        for(const li of ledgerInfo.children) {
-            if (li.pwd === undefined || li.pwd === null || li.pwd === '') {
-                li.can_edit = flag;
-                selects.push(li);
-                setAllChildrenCanEdit(li, flag);
-            }
-        }
-    }
-}
-function setLeftTable(ledgerList, coolist, uid, title) {
-    $('#stage_audit').text(title);
-    const showCooList = _.filter(coolist, { 'user_id': parseInt(uid) });
-    const removeList = [];
-    for (const sc of showCooList) {
-        const info = _.find(ledgerList, { 'ledger_id': sc.ledger_id });
-        if (info) {
-            sc.code = info.code;
-            sc.name = info.name;
-        } else {
-            removeList.push(sc);
-        }
-    }
-    if (removeList.length > 0) {
-        // 移除
-        _.pull(showCooList, ...removeList);
-        console.log(removeList);
-    }
-
-    let html = '';
-    // showCooList 需要根据右边台账顺序进行排序
-    showCooList.sort(function (a, b) {
-        return _.findIndex(ledgerList, { ledger_id: a.ledger_id }) - _.findIndex(ledgerList, { ledger_id: b.ledger_id });
-    });
-    for (const sc of showCooList) {
-        const pichtml = sc.sign_path ? `<img src="/${sc.sign_path}" width="60"><input type="file" data-id="${sc.id}" class="upload-img-file" style="display: none;"><a href="javascript: void(0);" class="d-inline-flex upload-img">更改</a>`
-            : `<img src="" style="display: none" width="60"><input type="file" data-id="${sc.id}" class="upload-img-file" style="display: none;"><a href="javascript: void(0);" class="btn btn-outline-primary btn-sm upload-img">上传签名</a>`;
-        html += `<tr>` +
-            `<td>${sc.code} ${sc.name}</td>` +
-            `<td><p class="mb-0">${sc.pwd}</p><a href="javascript:void(0);" data-lid="${sc.ledger_id}" data-uid="${sc.user_id}" data-pwd="${sc.pwd}" class="edit-pwd">修改</a> <a href="javascript:void(0)" data-lid="${sc.ledger_id}" data-uid="${sc.user_id}" class="del-pwd text-danger">移除</a></td>` +
-            `<td>${pichtml}</td><td><input type="text" class="form-control form-control-sm edit-company" data-id="${sc.id}" value="${sc.company}" placeholder="输入单位名称或备注"></td>` +
-            `</tr>`;
-    }
-    $('#coo_table').html(html);
-}

+ 123 - 16
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -436,14 +436,6 @@ const SpreadJsObj = {
         }
         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 {
@@ -461,9 +453,20 @@ const SpreadJsObj = {
         }
 
         cell.backColor(SpreadJsObj._getBackColor(sheet, data, iRow, col));
+        cell.foreColor(SpreadJsObj._getForeColor(sheet, data, iRow, col));
 
         cell.setBorder(sheet.borderLine, {all: true});
     },
+    _getForeColor: function (sheet, data, row, col) {
+        let foreColor = sheet.getDefaultStyle().foreColor;
+        if (sheet.zh_setting.tree.getForeColor && Object.prototype.toString.apply(sheet.zh_setting.tree.getForeColor) === "[object Function]") {
+            foreColor = sheet.zh_setting.tree.getColor(sheet, data, row, col, foreColor);
+        }
+        if (sheet.zh_setting.getForeColor && Object.prototype.toString.apply(sheet.zh_setting.getForeColor) === "[object Function]") {
+            foreColor = sheet.zh_setting.getForeColor(sheet, data, row, col, foreColor);
+        }
+        return foreColor;
+    },
     _getBackColor: function (sheet, data, row, col) {
         let backColor = sheet.getDefaultStyle().backColor;
         let sels = sheet.getSelections();
@@ -487,10 +490,6 @@ const SpreadJsObj = {
                 cell.font(col.font);
             }
 
-            if (col.foreColor && Object.prototype.toString.apply(col.foreColor) !== "[object Function]") {
-                cell.foreColor(col.foreColor);
-            }
-
             const readOnly1 = (sheet.zh_setting.readOnly && Object.prototype.toString.apply(sheet.zh_setting.readOnly) === "[object Function]")
                 ? sheet.zh_setting.readOnly(data) : (sheet.zh_setting.readOnly || false);
             const readOnly2 = (col.readOnly && Object.prototype.toString.apply(col.readOnly) === "[object Function]")
@@ -559,6 +558,7 @@ const SpreadJsObj = {
             }
 
             cell.backColor(SpreadJsObj._getBackColor(sheet, data, row, col));
+            cell.foreColor(SpreadJsObj._getForeColor(sheet, data, row, col));
 
             cell.setBorder(sheet.borderLine, {all: true});
             data.waitingLoading = false;
@@ -697,6 +697,13 @@ const SpreadJsObj = {
             }
             sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.stageCombo);
         }
+        if (colSetting.cellType === 'specCombo') {
+            if (!sheet.extendCellType.specCombo) {
+                sheet.extendCellType.specCombo = this.CellType.getSpecComboCellType(colSetting.comboItems);
+                SpreadJsObj._addActivePaintEvents(sheet, sheet.extendCellType.specCombo);
+            }
+            sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.specCombo);
+        }
         if (colSetting.cellType === 'datepicker') {
             if (!sheet.extendCellType.datepicker) {
                 sheet.extendCellType.datepicker = this.CellType.getDatePickerCellType();
@@ -816,6 +823,57 @@ const SpreadJsObj = {
             this.endMassOperation(sheet);
         }
     },
+    reloadRowForeColor: function (sheet, row, count) {
+        const sortData = sheet.zh_dataType === 'tree' ? sheet.zh_tree.nodes : sheet.zh_data;
+
+        this.beginMassOperation(sheet);
+        try {
+            for (let i = row; i < row + count; i++) {
+                if (i < 0) { continue; }
+                const data = sortData[i];
+                for (const [iCol, col] of sheet.zh_setting.cols.entries()) {
+                    sheet.getCell(i, iCol).foreColor(SpreadJsObj._getForeColor(sheet, data, i, col));
+                }
+            };
+            this.endMassOperation(sheet);
+        } catch (err) {
+            this.endMassOperation(sheet);
+        }
+    },
+    reloadRowsForeColor: function (sheet, rows) {
+        const sortData = sheet.zh_dataType === 'tree' ? sheet.zh_tree.nodes : sheet.zh_data;
+
+        this.beginMassOperation(sheet);
+        try {
+            for (const row of rows) {
+                if (row < 0) { continue; }
+                const data = sortData[row];
+                for (const [iCol, col] of sheet.zh_setting.cols.entries()) {
+                    sheet.getCell(row, iCol).foreColor(SpreadJsObj._getForeColor(sheet, data, row, col));
+                }
+            };
+            this.endMassOperation(sheet);
+        } catch (err) {
+            this.endMassOperation(sheet);
+        }
+    },
+    reloadRowBackColor: function (sheet, row, count) {
+        const sortData = sheet.zh_dataType === 'tree' ? sheet.zh_tree.nodes : sheet.zh_data;
+
+        this.beginMassOperation(sheet);
+        try {
+            for (let i = row; i < row + count; i++) {
+                if (i < 0) { continue; }
+                const data = sortData[i];
+                for (const [iCol, col] of sheet.zh_setting.cols.entries()) {
+                    sheet.getCell(i, iCol).backColor(SpreadJsObj._getBackColor(sheet, data, i, col));
+                }
+            };
+            this.endMassOperation(sheet);
+        } catch (err) {
+            this.endMassOperation(sheet);
+        }
+    },
     reloadRowsBackColor: function (sheet, rows) {
         const sortData = sheet.zh_dataType === 'tree' ? sheet.zh_tree.nodes : sheet.zh_data;
 
@@ -859,7 +917,6 @@ const SpreadJsObj = {
      * @param {Array} cols
      */
     reLoadColsData: function (sheet, cols) {
-        const self = this;
         const sortData = sheet.zh_dataType === 'tree' ? sheet.zh_tree.nodes : sheet.zh_data;
 
         this.beginMassOperation(sheet);
@@ -1151,7 +1208,6 @@ const SpreadJsObj = {
                 for (let iRow = 0; iRow < sheet.getRowCount(); iRow++) {
                     sheet.getCell(iRow, i).locked(col.readOnly || sheet.zh_setting.readOnly || false);
                 }
-                //sheet.getRange(-1, i, -1, 1, GC.Spread.Sheets.SheetArea.viewport).locked(col.readOnly || sheet.zh_setting.readOnly || false);
             });
         }
         this.endMassOperation(sheet);
@@ -1342,26 +1398,32 @@ const SpreadJsObj = {
                                 const x1 = centerX + indent / 2;
                                 if (dotLine) {
                                     drawDotLine(canvas, centerX, centerY, Math.min(x1, x + w), centerY, lineColor);
+                                    // drawDotLine(canvas, centerX, centerY, Math.min(x1, x + w), centerY, style.foreColor);
                                 } else {
                                     drawLine(canvas, centerX, centerY, Math.min(x1, x + w), centerY, lineColor);
+                                    // drawLine(canvas, centerX, centerY, Math.min(x1, x + w), centerY, style.foreColor);
                                 }
                             }
                             // Draw Vertical Line
                             if (centerX < x + w) {
-                                const y1 = tree.isLastSibling(node) ? centerY : y + h;
+                                const y1 = tree.isLastViewSibling(node) ? centerY : y + h;
                                 const parent = tree.getParent(node);
                                 const y2 = y1 - centerY;
                                 if (node.order === 1 && !parent) {
                                     if (dotLine) {
                                         drawDotLine(canvas, centerX, centerY, centerX, y1, lineColor);
+                                        // drawDotLine(canvas, centerX, centerY, centerX, y1, style.foreColor);
                                     } else {
                                         drawLine(canvas, centerX, centerY, centerX, y1, lineColor);
+                                        // drawLine(canvas, centerX, centerY, centerX, y1, style.foreColor);
                                     }
                                 } else {
                                     if (dotLine) {
                                         drawDotLine(canvas, centerX, y, centerX, y1, lineColor);
+                                        // drawDotLine(canvas, centerX, y, centerX, y1, style.foreColor);
                                     } else {
                                         drawLine(canvas, centerX, y, centerX, y1, lineColor);
+                                        // drawLine(canvas, centerX, y, centerX, y1, style.foreColor);
                                     }
                                 }
                             }
@@ -1374,12 +1436,14 @@ const SpreadJsObj = {
                         if (showTreeLine) {
                             let parent = tree.getParent(node), parentCenterX = centerX - indent - levelIndent;
                             while (parent) {
-                                if (!tree.isLastSibling(parent)) {
+                                if (!tree.isLastViewSibling(parent)) {
                                     if (parentCenterX < x + w) {
                                         if (dotLine) {
                                             drawDotLine(canvas, parentCenterX, y, parentCenterX, y + h, lineColor);
+                                            // drawDotLine(canvas, parentCenterX, y, parentCenterX, y + h, style.foreColor);
                                         } else {
                                             drawLine(canvas, parentCenterX, y, parentCenterX, y + h, lineColor);
+                                            // drawLine(canvas, parentCenterX, y, parentCenterX, y + h, style.foreColor);
                                         }
                                     }
                                 }
@@ -2329,6 +2393,49 @@ const SpreadJsObj = {
             combo.itemHeight(10).editorValueType(spreadNS.CellTypes.EditorValueType.value).items(items);
             return combo;
         },
+        getSpecComboCellType: function (items) {
+            const ComboCellType = function () {};
+            ComboCellType.prototype = new spreadNS.CellTypes.ComboBox();
+            const proto = ComboCellType.prototype;
+            proto.paintValue = function (ctx, value, x, y, w, h, style, options) {
+                const sheet = options.sheet;
+                const setting = options.sheet.zh_setting;
+                const col = setting.cols[options.col];
+                const data = SpreadJsObj.getSelectObject(options.sheet);
+                const comboEdit = (col.comboEdit && Object.prototype.toString.apply(col.comboEdit) === "[object Function]")
+                    ? col.comboEdit(options.sheet, data)
+                    : (col.comboEdit !== undefined ? col.comboEdit : true);
+                if (options.row === sheet.getActiveRowIndex() && options.col === sheet.getActiveColumnIndex()
+                    && !sheet.getCell(options.row, options.col).locked() && comboEdit) {
+                    spreadNS.CellTypes.ComboBox.prototype.paintValue.apply(this, arguments);
+                } else {
+                    spreadNS.CellTypes.Base.prototype.paintValue.apply(this, arguments);
+                }
+            };
+            proto.processMouseEnter = function (hitinfo) {
+                hitinfo.sheet.repaint(hitinfo.cellRect);
+            };
+            proto.getHitInfo = function (x, y, cellStyle, cellRect, options) {
+                const sheet = options.sheet;
+                if (options.row === sheet.getActiveRowIndex() && options.col === sheet.getActiveColumnIndex()
+                    && !sheet.getCell(options.row, options.col).locked()) {
+                    return spreadNS.CellTypes.ComboBox.prototype.getHitInfo.apply(this, [x, y, cellStyle, cellRect, options]);
+                } else {
+                    return  {
+                        x: x,
+                        y: y,
+                        row: options.row,
+                        col: options.col,
+                        cellStyle: cellStyle,
+                        cellRect: cellRect,
+                        sheetArea: options.sheetArea
+                    };
+                }
+            };
+            const combo = new ComboCellType();
+            combo.itemHeight(10).items(items).editorValueType(spreadNS.CellTypes.EditorValueType.value);
+            return combo;
+        },
         getStageComboCellType: function () {
             const ComboCellType = function () {};
             ComboCellType.prototype = new spreadNS.CellTypes.ComboBox();

+ 137 - 151
app/public/js/stage.js

@@ -239,12 +239,9 @@ $(document).ready(() => {
                 return node.is_tp;
             }
         },
-        over: {
-            enable: 1, isTz: checkTzMeasureType(),
-        },
-        limit3f: {
-            enable: 1, checkType: [], status: thirdParty,
-        }
+        over: { enable: 1, isTz: checkTzMeasureType(), },
+        limit3f: { enable: 1, checkType: [], status: thirdParty, },
+        minus_cb: { enable: hintMinusCb },
     };
     if (tender.s2b_gxby_check) checkOption.limit3f.checkType.push('gxby');
     if (tender.s2b_dagl_check) checkOption.limit3f.checkType.push('dagl');
@@ -327,45 +324,31 @@ $(document).ready(() => {
     };
     const stagePos = new StagePosData(stagePosSetting);
 
-    const setCooperationSelectHtml = function () {
-        const selectHtml = [];
-        selectHtml.push('<option>全部</option>');
-        const cooName = _.uniqWith(_.map(stageTree.pwd, 'company'));
-        for (const i of cooName) {
-            selectHtml.push('<option>', i, '</option>');
-        }
-        $('#cooperationSelect').html(selectHtml.join(''));
-    };
-
-    const reloadCooperationHtml = function () {
-        const html = [];
-        const select = $('#cooperationSelect').val();
-        const list = select !== '全部' ? _.filter(stageTree.pwd, { company: select }) : stageTree.pwd;
-        for (const p of list) {
-            if (!p.node) continue;
-            if (p.confirm) {
-                html.push(`<tr class="text-success">`);
-            } else {
-                html.push(`<tr>`);
-            }
-            html.push(`<td>${p.node.code}</td>`, `<td>${p.node.name}</td>`);
-            if(coopwd) {
-                html.push('<td>', p.check ? `已解锁:${p.pwd}` : `<a name="ledger-unlock" lid="${p.ledger_id}" href="javascript: void(0);">解锁</a>`, '</td>');
+    function checkUsed(tree, pos, node) {
+        if (node.children && node.children.length > 0) {
+            const posterity = tree.getPosterity(node);
+            for (const p of posterity) {
+                if (p.children && p.children.length > 0) continue;
+                if (!checkZero(p.contract_qty) || !checkZero(p.contract_tp) ||
+                    !checkZero(p.qc_qty) || !checkZero(p.qc_tp) || !checkZero(p.qc_minus_qty))
+                    return true;
+                const pPos = pos.getLedgerPos(p.id);
+                if (!pPos || pPos.length === 0) continue;
+                for (const pp of pPos) {
+                    if (!checkZero(pp.contract_qty) || !checkZero(pp.qc_qty) || !checkZero(pp.qc_minus_qty)) return true;
+                }
             }
-            html.push('<td>', p.company, '</td>');
-            if (p.check && p.confirm) {
-                html.push('<td>', moment(p.confirm_time).format('YYYY-MM-DD HH:mm') + ' ' + (coopwd ? `<a name="ledger-unconfirm" href="javascript: void(0);" lid="${p.ledger_id}">重新审批</a>` : ''), '</td>');
-            } else if (!p.check && p.confirm) {
-                html.push('<td>', moment(p.confirm_time).format('YYYY-MM-DD HH:mm'), '</td>');
-            } else if (p.check && !p.confirm && coopwd) {
-                html.push('<td>', `<a name="ledger-confirm" href="javascript: void(0);" lid="${p.ledger_id}" class="btn btn-sm btn-success">确认</a>`, '</td>');
-            } else {
-                html.push('<td>', '</td>');
+            return false;
+        } else {
+            if (!checkZero(node.contract_qty) || !checkZero(node.contract_tp) ||
+                !checkZero(node.qc_qty) || !checkZero(node.qc_tp) || !checkZero(node.qc_minus_qty))
+                return true;
+            const pPos = pos.getLedgerPos(node.id);
+            if (!pPos || pPos.length === 0) return false;
+            for (const pp of pPos) {
+                if (!checkZero(pp.contract_qty) || !checkZero(pp.qc_qty) || !checkZero(pp.qc_minus_qty)) return true;
             }
-            html.push('</tr>');
         }
-        $('#cooperationList').html(html.join(''));
-
     };
 
     class Changes {
@@ -738,7 +721,7 @@ $(document).ready(() => {
     ];
     ledgerSpreadSetting.readOnly = function (data) {
         if (!data) return false;
-        return data.lock || false;
+        return data.lock || stageTreeSpreadObj.assistReadOnly;
     };
     SpreadJsObj.initSheet(slSpread.getActiveSheet(), ledgerSpreadSetting);
 
@@ -1504,13 +1487,6 @@ $(document).ready(() => {
             return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
         },
         items: {
-            copyDisplay: {
-                name: '仅复制可见数据',
-                callback: function (key, opt) {
-
-                }
-            },
-            copySpr: '----',
             'locateZjjl': {
                 name: '定位至中间计量',
                 icon: 'fa-sign-in',
@@ -1620,7 +1596,20 @@ $(document).ready(() => {
                     $('#shoufang-pos').text('').hide();
                     $('#addshoufang').modal('show');
                 }
-            }
+            },
+            lockedSpr: '----',
+            lockedHint: {
+                name: '检查审批状态',
+                visible: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    return node.locked;
+                },
+                callback: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    const lockedInfo = stageTree.getLockedInfo(node);
+                    toastr.warning(`审批人 ${lockedInfo.map(x => { return x.name; }).join(',')} 审批通过,如需修改,请联系审批人重新审批`);
+                }
+            },
         }
     });
 
@@ -1792,7 +1781,7 @@ $(document).ready(() => {
             if (info.sheet.zh_setting) {
                 const sortData = info.sheet.zh_data;
                 const range = info.cellRange;
-                const validField = ['contract_qty', 'qc_qty', 'qc_minus_qty', 'postil', 'real_qty', 'ex_memo1', 'ex_memo2', 'ex_memo3'];
+                const validField = ['contract_qty', 'postil', 'real_qty', 'ex_memo1', 'ex_memo2', 'ex_memo3'];
                 if (!checkTzMeasureType()) {
                     validField.push('name', 'sgfh_qty', 'sjcl_qty', 'qtcl_qty', 'position', 'drawing_code');
                 }
@@ -2025,7 +2014,6 @@ $(document).ready(() => {
         },
         selectionChanged: function (e, info) {
             stagePosSpreadObj.loadExprToInput(info.sheet);
-            console.log(SpreadJsObj.getSelectObject(info.sheet));
         },
         addPegs: function (pegs) {
             if (!pegs || pegs.length <= 0) return;
@@ -2058,21 +2046,14 @@ $(document).ready(() => {
     });
 
     // 加载计量单元数据 - 暂时统一加载,如有需要,切换成动态加载并缓存
-    postData(window.location.pathname + '/load', { filter: 'ledger;pos;detail;change;import_change;tag;cooperation' }, function (result) {
+    postData(window.location.pathname + '/load', { filter: 'ledger;pos;detail;change;import_change;tag;' }, function (result) {
         // 加载树结构
-        stageTree.loadDatas(result.ledgerData);
+        stageTree.loadDatas(result.ledgerData, result.locked);
+        if (stage.assist) stageTree.loadFilter(stage.assist.ass_ledger_id);
         // 加载部位明细
         stagePos.loadDatas(result.posData);
         checkShowLast(result.ledgerData.length);
         treeCalc.calculateAll(stageTree);
-        // 加载解锁相关
-        if (result.cooperation) {
-            stageTree.loadPwd(result.cooperation, 'bills-p-' + userID + '-' + window.location.pathname.split('/')[2], result.cooperationConfirm);
-            $('#cooperationCount').html(stageTree.pwd.length || '');
-            if (stageTree.pwd.length > 0) $('#cooperationCount').parent().show();
-            setCooperationSelectHtml();
-            reloadCooperationHtml();
-        }
         for (const t of result.tags) {
             t.node = stageTree.datas.find(x => {return x.id === t.lid});
         }
@@ -2083,7 +2064,7 @@ $(document).ready(() => {
         stagePosSpreadObj.loadCurPosData();
         SpreadJsObj.resetTopAndSelect(spSpread.getActiveSheet());
         // 加载中间计量
-        stageIm.init(stage, imType, tenderInfo.decimal);
+        stageIm.init(stage, imType, tenderInfo.decimal, stage.assist ? stage.assist.ass_ledger_id : '');
         stageIm.loadData(result.ledgerData, result.posData, result.detailData, result.changeData, result.import_change, result.detailAtt);
 
         errorList.loadHisErrorData();
@@ -2898,12 +2879,16 @@ $(document).ready(() => {
 
                 const uuid = $(this).attr('data-imid');
                 const file_id = $(this).attr('data-attid');
-
-                postData(window.location.pathname + '/im-file/del', { uuid, file_id }, function (result) {
-                    stageIm.loadUpdateDetailAtt(result);
-                    SpreadJsObj.reLoadRowData(self.sheet, sels[0].row);
-                    self.makeAttTable(select);
+                const att = select.attachment.find(x => {
+                    return x.file_id === file_id;
                 });
+                deleteAfterHint(function () {
+                    postData(window.location.pathname + '/im-file/del', { uuid, file_id }, function (result) {
+                        stageIm.loadUpdateDetailAtt(result);
+                        SpreadJsObj.reLoadRowData(self.sheet, sels[0].row);
+                        self.makeAttTable(select);
+                    });
+                }, `确认删除附件「${att.filename}」?`);
             });
             // 预览附件
             $('body').on('click', '.preview-att', function () {
@@ -3807,6 +3792,8 @@ $(document).ready(() => {
                                 }
                             } else if (changeBills.gcl_id) {
                                 const node = stageTree.nodes.find(x => {return x.id === changeBills.gcl_id});
+                                if (!node) return;
+
                                 SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), node.ledger_id, true);
                                 SpreadJsObj.resetTopAndSelect(spSpread.getActiveSheet());
                                 stagePosSpreadObj.loadCurPosData();
@@ -4550,14 +4537,15 @@ $(document).ready(() => {
         );
     }
     $('#ledger-check2').click(() => {
-        const result = ledgerCheck2(stageCheckerSetting);
-        check2Viewing({
-            extra: ZhCalc.div(stageTree.datas.length + stagePos.datas.length, 10000, 0),
-            randomWait: true,
-            prefix: 'check2-',
-            checks: result,
-            checkList: checkList,
-        })
+        ledgerCheck2(stageCheckerSetting).then(result => {
+            check2Viewing({
+                extra: ZhCalc.div(stageTree.datas.length + stagePos.datas.length, 10000, 0),
+                randomWait: true,
+                prefix: 'check2-',
+                checks: result,
+                checkList: checkList,
+            });
+        });
     });
 
     const dataChecker = DataChecker({
@@ -4576,7 +4564,7 @@ $(document).ready(() => {
     $('[name=stage-start]').submit(function (e) {
         if (checkAuditorFrom()) {
             // 再检查多人协同确认情况
-            const list = stageTree.pwd.find(x => {return !x.confirm });
+            const list = stage.relaAssists.find(x => {return !x.confirm });
             if(list) {
                 toastr.error('请检查多人协同确认情况再上报');
                 $('#hide-all').hide();
@@ -4590,15 +4578,17 @@ $(document).ready(() => {
     });
     $('#audit-check0').submit(function (e) {
         // 再检查多人协同确认情况
-        const list = stageTree.pwd.find(x => {return !x.confirm });
+        const list = stage.relaAssists.find(x => {return !x.confirm && stageAssistRela.checkAssistPartUsed(x) });
         if(list) {
             toastr.error('请检查多人协同确认情况再审批通过');
             $('#hide-all').hide();
             return false;
         }
         const checkType = parseInt($('[name=checkType]').val());
+        const noUsed = stage.relaAssists.filter(x => {return !stageAssistRela.checkAssistPartUsed(x) });
         const data = {
             opinion: $(`${'#sp-done'}`).find('[name=opinion]').val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' '),
+            coop_noUsed: noUsed && noUsed.length > 0 ? noUsed.map(x => { return x.ass_ledger_id; }) : [],
             checkType,
         };
         $('#sp-done').modal('hide');
@@ -4608,11 +4598,13 @@ $(document).ready(() => {
     });
     $('#audit-check1').submit(function (e) {
         const checkType = parseInt($('[name=checkType]:checked').val());
+        const confirm = stage.relaAssists.filter(x => { return x.confirm });
         const data = {
             opinion: $(`${'#sp-back'}`).find('[name=opinion]').val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' '),
+            coop_checked: confirm && confirm.length > 0 ? confirm.map(x => { return x.id; }) : [],
             checkType,
         };
-        if ($('#warning-text').length) $('#warning-text').remove()
+        if ($('#warning-text').length) $('#warning-text').remove();
         if (!checkType && !$('#warning-text').length) {
             $('#reject-process').prepend('<p id="warning-text" style="color: red; margin: 0;">请选择退回流程</p>');
             return false
@@ -4655,76 +4647,6 @@ $(document).ready(() => {
         }
         stageTreeSpreadObj.measureByBatch(posName, ratio, apply2sibling);
     });
-
-    $('body').on('click', '[name=ledger-unlock]', function() {
-        $('#cooperation').modal('hide');
-        const lid = this.getAttribute('lid');
-        if (!lid) return;
-
-        const p = stageTree.pwd.find(x => {return x.ledger_id == lid});
-        if (!p) return;
-
-        $('#unlock-info').html(`${p.node.code} ${p.node.name} 解锁密码<b class="text-danger">*</b>`);
-        $('#unlock-pwd').val('').removeClass('is-invalid');
-        $('.invalid-feedback', '#unlock').hide();
-        $('.alert-warning', '#unlock').hide();
-        $('#unlock-ok').attr('lid', lid);
-        $('#unlock').modal('show');
-    });
-    $('#unlock-ok').click(function () {
-        const lid = this.getAttribute('lid');
-        if (!lid) return;
-
-        const p = stageTree.pwd.find(x => {return x.ledger_id == lid});
-        if (!p) return;
-
-        if (p.pwd === $('#unlock-pwd').val()) {
-            const refresh = stageTree.lockNode(p, false);
-            // 修改线上
-            SpreadJsObj.reloadRowsReadonly(slSpread.getActiveSheet(), refresh);
-            stagePosSpreadObj.loadCurPosData();
-            $('#unlock').modal('hide');
-            reloadCooperationHtml();
-        } else {
-            $('#unlock-pwd').addClass('is-invalid');
-            $('.invalid-feedback', '#unlock').show();
-            $('.alert-warning', '#unlock').show();
-        }
-    });
-    $('#cooperationSelect').change(function () {
-        reloadCooperationHtml();
-    });
-    // 确认
-    $('body').on('click', '[name=ledger-confirm]', function() {
-        const lid = this.getAttribute('lid');
-        if (!lid) return;
-
-        const p = stageTree.pwd.find(x => {return x.ledger_id == lid});
-        if (!p) return;
-        // 修改线上
-        postData(window.location.pathname + '/save/cooperation', { type: 'save-confirm', postData: { ledger_id: lid } }, function (result) {
-            p.confirm = true;
-            p.confirm_time = new Date();
-            stageTree.confirmList = result.cooperationConfirm;
-            reloadCooperationHtml();
-        });
-    });
-
-    // 确认
-    $('body').on('click', '[name=ledger-unconfirm]', function() {
-        const lid = this.getAttribute('lid');
-        if (!lid) return;
-
-        const p = stageTree.pwd.find(x => {return x.ledger_id == lid});
-        if (!p) return;
-        // 修改线上
-        postData(window.location.pathname + '/save/cooperation', { type: 'del-confirm', postData: { ledger_id: lid } }, function (result) {
-            p.confirm = false;
-            p.confirm_time = null;
-            stageTree.confirmList = result.cooperationConfirm;
-            reloadCooperationHtml();
-        });
-    });
     // 收方单生成
     $('#setshoufang').click(function () {
         const lid = $('#shoufang-lid').val();
@@ -4922,7 +4844,71 @@ $(document).ready(() => {
                 }, null);
             }
         });
-    })
+    });
+    const stageAssistRela = {
+        refreshAssConfirmBtn: function () {
+            if (stage.assist) {
+                const caption = stage.assist.user_id === stage.user_id
+                    ? (stage.assist.confirm ? '撤销上报' : '确认上报')
+                    : (stage.assist.confirm ? '审批通过' : '重新审批');
+                if (!stage.readOnly || stage.assist.locked) $('#ass-confirm-btn').show().html(caption);
+            } else {
+                $('#ass-confirm-btn').hide();
+            }
+        },
+        checkAssistPartUsed(assist) {
+            const ass_lid = assist.ass_ledger_id.split(',');
+            for (const lid of ass_lid) {
+                const node = stageTree.getItems(lid);
+                if (checkUsed(stageTree, stagePos, node)) return true;
+            }
+            return false;
+        },
+        refreshAssistTable: function () {
+            const html = [];
+            for (const ra of stage.relaAssists) {
+                const used = this.checkAssistPartUsed(ra);
+                const hint = used
+                    ? (ra.confirm ? `<td class="text-success">${moment(ra.update_time).format('YYYY-MM-DD HH:mm')}</td>` : '<td class="text-warning">待确认</td>')
+                    : '<td>本期未计量</td>';
+                html.push(`<tr><td>${ra.name}</td><td>${ra.company}</td>${hint}</tr>`);
+            }
+            return $('#assistList').html(html.join(''));
+        },
+        refreshAssists: function () {
+            if (stage.relaAssists.length > 0) {
+                $('#assistCount').html(stage.relaAssists.length || '').parent().show();
+            } else {
+                $('#assistCount').parent().hide();
+            }
+        },
+        refreshReadOnly(){
+            stageTreeSpreadObj.assistReadOnly = stage.readOnly || stage.revising || (!!stage.assist && !!stage.assist.confirm);
+            SpreadJsObj.refreshSheetReadOnly(slSpread.getActiveSheet());
+            stagePosSpreadObj.loadCurPosData();
+        },
+        init: function() {
+            this.refreshAssConfirmBtn();
+            this.refreshReadOnly();
+            this.refreshAssists();
+        },
+    };
+    stageAssistRela.init();
+    $('#ass-confirm-btn').click(() => {
+        const data = { user_id: stage.assist.user_id, ass_user_id: stage.assist.ass_user_id, confirm: !stage.assist.confirm, type: 'stage' };
+        postData(window.location.pathname + '/ass-confirm', data, function(result) {
+            window.location.reload();
+            // stage.assist = result;
+            // stageAssistRela.refreshAssConfirmBtn();
+            // stageAssistRela.refreshReadOnly();
+        });
+    });
+    $('#assists').on('show.bs.modal', function(){
+        postData(window.location.pathname + '/ass', { user_id: userID, type: 'stage' }, function (result) {
+            stage.relaAssists = result;
+            stageAssistRela.refreshAssistTable();
+        });
+    });
 });
 function makeOneShouFang(sf) {
     const lData = _.find(ledgerData, { id: sf.lid });

+ 10 - 0
app/public/js/stage_compare.js

@@ -341,4 +341,14 @@ $(document).ready(function () {
 
         SpreadExcelObj.exportSimpleXlsxSheet(ledgerSpreadSetting, data, $('.sidebar-title').attr('data-original-title') + "-审核比较.xlsx");
     });
+    $('[name=compareType]').click(function () {
+        $('[name=compareType]').removeClass('active');
+        $(this).addClass('active');
+        $('#compareType').children().removeClass('active');
+        $(this.getAttribute('href')).addClass('active');
+        xmjSpread.refresh();
+        posSpread.refresh();
+        gclSpread.refresh();
+        leafXmjSpread.refresh();
+    });
 });

+ 22 - 9
app/public/js/stage_im.js

@@ -13,7 +13,7 @@ const stageIm = (function () {
     const resetFields = ['peg', 'bw', 'xm', 'drawing_code', 'calc_memo', 'position', 'jldy'];
     const splitChar = '-';
     const mergeChar = ';';
-    let stage, imType, decimal, details, changes, importChanges, detailsAtt, ImData, pre, orgImData;
+    let stage, imType, decimal, filter, details, changes, importChanges, detailsAtt, ImData, pre, orgImData;
     let up_field = 'unit_price';
     const gsTreeSetting = {
         id: 'ledger_id',
@@ -55,10 +55,11 @@ const stageIm = (function () {
     };
     const gsPos = new StagePosData(gsPosSetting);
 
-    function init (s, i, d) {
+    function init (s, i, d, f) {
         stage = s;
         imType = i;
         decimal = d;
+        filter = f;
     }
 
     function initCheck () {
@@ -71,6 +72,7 @@ const stageIm = (function () {
     function loadData (ledger, pos, stageDetail, stageChange, stageImportChange, stageDetailAtt) {
         up_field = 'unit_price';
         gsTree.loadDatas(ledger);
+        gsTree.loadFilter(filter);
         treeCalc.calculateAll(gsTree);
 
         gsPos.loadDatas(pos);
@@ -624,13 +626,14 @@ const stageIm = (function () {
                 checkZero(ZhCalc.sub(bills.unit_price, x.unit_price));
         });
         if (!gcl) {
-            gcl = {b_code: bills.b_code, name: bills.name, unit: bills.unit, unit_price: bills.unit_price};
+            gcl = {b_code: bills.b_code, name: bills.name, unit: bills.unit, unit_price: bills.unit_price, org_price: bills.org_price};
             im.gclBills.push(gcl);
         }
         gcl.contract_jl = ZhCalc.add(gcl.contract_jl, bills.contract_jl);
         gcl.qc_jl = ZhCalc.add(gcl.qc_jl, bills.qc_jl);
         gcl.qc_minus_jl = ZhCalc.add(gcl.qc_minus_jl, bills.qc_minus_jl);
         gcl.jl = ZhCalc.add(gcl.jl, bills.jl);
+        gcl.pre_jl = ZhCalc.add(gcl.pre_jl, bills.pre_jl);
     }
     function calculateBwBillsIm(im) {
         im.contract_jl = 0;
@@ -639,8 +642,13 @@ const stageIm = (function () {
         for (const b of im.gclBills) {
             im.contract_jl = ZhCalc.add(im.contract_jl, ZhCalc.mul(b.contract_jl, b.unit_price, decimal.tp));
             im.qc_jl = ZhCalc.add(im.qc_jl, ZhCalc.mul(b.qc_jl, b.unit_price, decimal.tp));
+            const priceDiff = b.org_price ? ZhCalc.sub(b.unit_price, b.org_price) : 0;
+            if (priceDiff) {
+                const pc_jl = ZhCalc.sub(ZhCalc.mul(b.unit_price, b.pre_jl, decimal.tp), ZhCalc.mul(b.org_price, b.pre_jl, decimal.tp));
+                im.pc_jl = ZhCalc.add(im.pc_jl, pc_jl);
+            }
         }
-        im.jl = ZhCalc.add(im.contract_jl, im.qc_jl);
+        im.jl = ZhCalc.sum([im.contract_jl, im.qc_jl, im.pc_jl]);
     }
     function generateBwBillsImData (node) {
         if (checkUsed(node)) {
@@ -694,8 +702,8 @@ const stageIm = (function () {
                             }
                         }
                         addBwBillsGclBills(im, {
-                            b_code: p.b_code, name: p.name, unit: p.unit, unit_price: p.unit_price,
-                            jl: pp.gather_qty, contract_jl: pp.contract_qty, qc_jl: pp.qc_qty, qc_minus_jl: pp.qc_minus_qty,
+                            b_code: p.b_code, name: p.name, unit: p.unit, unit_price: p.unit_price, org_price: p.org_price,
+                            jl: pp.gather_qty, contract_jl: pp.contract_qty, qc_jl: pp.qc_qty, qc_minus_jl: pp.qc_minus_qty, pre_jl: pp.pre_gather_qty,
                         });
 
                         if (pp.drawing_code && im.drawing_code instanceof Array)
@@ -714,16 +722,17 @@ const stageIm = (function () {
                     imDefault.contract_jl = ZhCalc.add(imDefault.contract_jl, p.contract_tp);
                     imDefault.qc_jl = ZhCalc.add(imDefault.qc_jl, p.qc_tp);
                     imDefault.qc_minus_jl = 0;
+                    imDefault.pc_jl = ZhCalc.add(imDefault.pc_jl, p.pc_tp);
                     imDefault.used = true;
 
                     addBwBillsGclBills(imDefault, {
-                        b_code: p.b_code, name: p.name, unit: p.unit, unit_price: p.unit_price,
-                        jl: p.gather_qty, contract_jl: p.contract_qty, qc_jl: p.qc_qty, qc_minus_jl: p.qc_qty,
+                        b_code: p.b_code, name: p.name, unit: p.unit, unit_price: p.unit_price, org_price: p.org_price,
+                        jl: p.gather_qty, contract_jl: p.contract_qty, qc_jl: p.qc_qty, qc_minus_jl: p.qc_qty, pre_jl: p.pre_gather_qty
                     });
                 }
             }
             if (imDefault.used) {
-                imDefault.jl = ZhCalc.add(imDefault.contract_jl, imDefault.qc_jl);
+                imDefault.jl = ZhCalc.sum([imDefault.contract_jl, imDefault.qc_jl, imDefault.pc_jl]);
                 ImData.push(imDefault);
             }
             for (const im of nodeImData) {
@@ -922,6 +931,8 @@ const stageIm = (function () {
     function recursiveBuildImData (nodes) {
         if (!nodes || nodes.length === 0) { return; }
         for (const node of nodes) {
+            if (node.filter !== undefined && node.filter) continue;
+
             if (gsTree.isLeafXmj(node) || ((stage.im_type !== imType.bw.value && stage.im_type !== imType.bb.value) && stage.im_gather && node.check)) {
                 if (stage.im_type === imType.tz.value) {
                     generateTzImData(node);
@@ -1000,6 +1011,8 @@ const stageIm = (function () {
         if (!nodes || nodes.length === 0) { return; }
         for (const node of nodes) {
             if (refreshNodeIds.indexOf(node.id) < 0) continue;
+            if (node.filter) continue;
+
             if (gsTree.isLeafXmj(node) || ((stage.im_type !== imType.bw.value && stage.im_type !== imType.bb.value) && stage.im_gather && node.check)) {
                 if (stage.im_type === imType.tz.value) {
                     generateTzImData(node);

+ 18 - 5
app/public/js/stage_pay.js

@@ -51,7 +51,7 @@ $(document).ready(() => {
     const payCalc = (function (b) {
         class PayCalc {
             constructor (bases) {
-                this.percentReg = /[0-9]+%/g;
+                this.percentReg = /((\d+)|((\d+)(\.\d+)))%/g;
                 this.bases = bases;
                 this.bases.sort(function (a, b) {
                     return a.sort - b.sort;
@@ -215,10 +215,23 @@ $(document).ready(() => {
             return data.uid === 0 ? payCol.isYB() : data.uid === userID;
         },
         isStarted: function (data) {
-            return data.pre_used;
+            return !!data.pre_used;
         },
         isFinish: function (data) {
             return data.pre_finish;
+        },
+        hasBase: function (data) {
+            if (!data.expr) return false;
+
+            for (const b of payCalc.bases) {
+                if (b.reg.test(data.expr)) return true;
+            }
+            return false;
+        },
+        isLock: function (data) {
+            const result = !!lockPayExpr && payBase.isStarted(data) && payBase.hasBase(data);
+            console.log(data.name, result);
+            return result;
         }
     };
     const payCol = {
@@ -249,18 +262,18 @@ $(document).ready(() => {
                 }
             },
             tp: function (data) {
-                return payBase.isWC(data) || payBase.isYF(data);
+                return payBase.isWC(data) || payBase.isYF(data) || payBase.isLock(data);
             },
             sprice: function (data) {
                 if (payBase.isOld(data)) {
-                    return payBase.isStarted(data) || !payBase.isYB(data);
+                    return payBase.isStarted(data) || !payBase.isYB(data) || payBase.isLock(data);
                 } else {
                     return payBase.isWC(data) || payBase.isSF(data) || payBase.isYF(data) || !(payBase.isOwner(data) || payBase.isYB());
                 }
             },
             rprice: function (data) {
                 if (payBase.isOld(data)) {
-                    return !payBase.isYB(data);
+                    return !payBase.isYB(data) || payBase.isLock(data);
                 } else {
                     return payBase.isWC(data) || payBase.isYF(data) || !(payBase.isOwner(data) || payBase.isYB());
                 }

+ 9 - 0
app/public/js/tender.js

@@ -33,6 +33,10 @@ function loadCommonProperty () {
     $('#supervision2-company').val(property.construction_unit.supervision2.company);
     $('#supervision2-corporation').val(property.construction_unit.supervision2.corporation);
     $('#supervision2-date').val(property.construction_unit.supervision2.date);
+    // 检测单位
+    $('#detect-company').val(property.construction_unit.detect.company);
+    $('#detect-corporation').val(property.construction_unit.detect.corporation);
+    $('#detect-date').val(property.construction_unit.detect.date);
 
     // 技术参数
     $('#loadLevel').val(property.tech_param.loadLevel);
@@ -512,6 +516,11 @@ $(document).ready(function() {
                     corporation: $('#supervision2-corporation').val(),
                     date: $('#supervision2-date').val(),
                 },
+                detect: {
+                    company: $('#detect-company').val(),
+                    corporation: $('#detect-corporation').val(),
+                    date: $('#detect-date').val(),
+                },
             },
             tech_param: {
                 loadLevel: _.toNumber($('#loadLevel').val()),

+ 1 - 2
app/public/js/tender_showhide.js

@@ -156,7 +156,7 @@ function doTrStatus(node, status, all = '') {
 
 let tenderTreeShowLevel;
 const getChildrenLevel = function (node) {
-    let iLevel = node.level;
+    let iLevel = node.level || 1;
     if (node.children && node.children.length > 0) {
         for (const c of node.children) {
             iLevel = Math.max(iLevel, getChildrenLevel(c));
@@ -232,7 +232,6 @@ $(document).ready(() => {
             },
         ],
         showLevel: function (tag) {
-
             switch (tag) {
                 case "1":
                 case "2":

+ 12 - 0
app/public/report/js/rpt_custom.js

@@ -310,10 +310,13 @@ const rptCustomObj = (function () {
         if (gsObj.setting.type === 'zone') $('#gather-by-zone').show();
         if (gsObj.setting.type === 'stage') $('#gather-by-stage').show();
         if (gsObj.setting.type === 'stage-zone') $('#gather-by-stage-zone').show();
+        if (gsObj.setting.type === 'checked-zone') $('#gather-by-checked-zone').show();
 
         if (gsSelect) {
             if (gsSelect.zone) {
                 $('#gather-zone').val(gsSelect.zone ? gsSelect.zone : '');
+            } else if (gsSelect.checked_zone) {
+                $('#gather-checked-zone').val(gsSelect.checked_zone ? gsSelect.checked_zone : '');
             } else if (gsSelect.month) {
                 $('#gather-month').val(gsSelect.month ? gsSelect.month: '');
             } else if (gsSelect.stage) {
@@ -613,6 +616,15 @@ const rptCustomObj = (function () {
                 return;
             }
             data[sGatherSelect].stage_zone = stageBegin + ':' + stageEnd;
+        } else if (gsObj.setting.type === 'checked-zone') {
+            data[sGatherSelect].checked_zone = $('#gather-checked-zone').val();
+            if (data[sGatherSelect].checked_zone === '') {
+                hintObj.html('请选择 汇总周期').show();
+                return;
+            } else if(data[sGatherSelect].checked_zone.indexOf(' - ') < 0) {
+                hintObj.html('请选择 完整汇总周期').show();
+                return;
+            }
         }
         hintObj.hide();
         if (resolve) {

+ 71 - 8
app/public/report/js/rpt_signature.js

@@ -137,6 +137,9 @@ let rptSignatureHelper = {
             }
             let roleRel = null;
             if (directAcc) {
+
+              
+
                 rptSignatureHelper.pushDomElementByUser(elementsStrArr, userAcc.name, userAcc.role);
                 // 还有ROLE_REL_LIST
                 let roleRelObj = {};
@@ -150,7 +153,7 @@ let rptSignatureHelper = {
                 roleRelObj.type = '用户';
                 roleRelObj.sign_output = [NORMAL_SIGN_STR]; // 默认是签字(还有:COMPANY_SIGN_STR:单位章, PRIVATE_SIGN_STR:个人章)
                 roleRelObj.company_stamp_path = rptSignatureHelper._getCompanySign(directAcc.company);
-                roleRelObj.private_stamp_path = (userAcc.stamp_path && userAcc.stamp_path !== '') ? userAcc.stamp_path : '';
+                roleRelObj.private_stamp_path = rptSignatureHelper._get_newPrivate_stamp_path(roleRelObj,userAcc);
                 roleRelObj.role = (userAcc.role === '')?DFT_ROLE_NAME:userAcc.role;
                 ROLE_REL_LIST.push(roleRelObj);
                 roleRel = roleRelObj;
@@ -168,7 +171,7 @@ let rptSignatureHelper = {
                 roleRelObj.acc_id = userAcc.id;
                 roleRelObj.sign_output = [NORMAL_SIGN_STR]; // 默认是签字(还有:COMPANY_SIGN_STR:单位章, PRIVATE_SIGN_STR:个人章)
                 roleRelObj.company_stamp_path = rptSignatureHelper._getCompanySign(userAcc.company);
-                roleRelObj.private_stamp_path = (userAcc.stamp_path && userAcc.stamp_path !== '') ? userAcc.stamp_path : null;
+                roleRelObj.private_stamp_path = rptSignatureHelper._get_newPrivate_stamp_path(roleRelObj,userAcc);
                 roleRelObj.type = '角色';
                 roleRelObj.role = (userAcc.role === '')?DFT_ROLE_NAME:userAcc.role;
                 roleRelObj.role_name = roleAcc.name;
@@ -253,7 +256,7 @@ let rptSignatureHelper = {
                             }
                             const userAcc = rptSignatureHelper.getUserAccount(role_rel.acc_id);
                             if (userAcc) {
-                                role_rel.private_stamp_path = (userAcc.stamp_path && userAcc.stamp_path !== '') ? userAcc.stamp_path : '';
+                                role_rel.private_stamp_path = rptSignatureHelper._get_newPrivate_stamp_path(role_rel,userAcc);
                                 role_rel.company_stamp_path = rptSignatureHelper._getCompanySign(userAcc.company);
                             }
                     
@@ -280,7 +283,8 @@ let rptSignatureHelper = {
             }
         }
     },
-    checkAndShowCrossTendersESignature: function () {
+    checkAndShowCrossTendersESignature: function (fujianOssPath) {
+        rptSignatureHelper.fujianOssPath=fujianOssPath;
         let btnDom = $('#btn_cross_tender')[0];
         if (zTreeOprObj.currentNode) {
             if (btnDom) btnDom.style.display = '';
@@ -312,7 +316,7 @@ let rptSignatureHelper = {
         const companySignChkStr = (role_rel.sign_output.indexOf(COMPANY_SIGN_STR) >= 0) ? 'checked' : '';
         const privateSignChkStr = (role_rel.sign_output.indexOf(PRIVATE_SIGN_STR) >= 0) ? 'checked' : '';
         // 1. 签章类型:签字 单位章 个人章....
-        elementsStrArr.push('<div class="col-6">');
+        elementsStrArr.push('<div class="col-8">');
         elementsStrArr.push('   <div class="form-control form-control-sm d-inline pt-2">');
         elementsStrArr.push('       <div class="form-check form-check-inline px-2">');
         elementsStrArr.push(`           <input class="form-check-input" type="checkbox" id="${idSuffixStr}_sign1" value="option1" ${normalSignChkStr} onchange="rptSignatureHelper._changeSignType(this, '${role_rel.signature_name}', '${NORMAL_SIGN_STR}')" >`);
@@ -322,6 +326,9 @@ let rptSignatureHelper = {
         // let chkType = hasIndividualStamp ? `radio` : 'checkbox';
         const chkType = 'checkbox';
         let rdoNameStr = `dtp_${role_rel.signature_name}_${rptSignatureHelper.currentSelectedESignParentDivId}`;
+        // 个人章列表
+        const stampPathList=userAcc.stamp_path?userAcc.stamp_path.split('!;!'):[];
+
         if (hasIndividualStamp) {
             elementsStrArr.push('       <div class="form-check form-check-inline mx-1">');
             elementsStrArr.push('           <div class="form-group">');
@@ -330,8 +337,11 @@ let rptSignatureHelper = {
             elementsStrArr.push(`                   <label class="form-check-label" for="${idSuffixStr}_sign2">单位章</label>`);
             elementsStrArr.push('               </div>');
             elementsStrArr.push('               <div class="form-check form-check-inline">');
-            elementsStrArr.push(`                   <input class="form-check-input" type="${chkType}" id="${idSuffixStr}_sign3" value="individualStamp" name="${rdoNameStr}" onchange="rptSignatureHelper._changeSignType(this, '${role_rel.signature_name}', '${PRIVATE_SIGN_STR}')" ${privateSignChkStr}>`);
+            elementsStrArr.push(`                   <input class="form-check-input" type="${chkType}" id="${idSuffixStr}_sign3" value="individualStamp" name="${rdoNameStr}" onchange="rptSignatureHelper._changeSignType(this, '${role_rel.signature_name}', '${PRIVATE_SIGN_STR}','${userAcc.stamp_path}')" ${privateSignChkStr}>`);
             elementsStrArr.push(`                   <label class="form-check-label" for="${idSuffixStr}_sign3">个人章</label>`);
+            if(stampPathList.length>1){
+            elementsStrArr.push(`                   <a class="pl-2" href="#chose-private-stamp-path" data-toggle="modal" data-target="#chose-private-stamp-path" onclick="rptSignatureHelper.currentSelectedESignAccDom = this.parentNode;rptSignatureHelper.initChosePrivateStampPath('${userAcc.stamp_path}','${role_rel.signature_name}') ">选择个人章</a>`);
+            } 
             elementsStrArr.push('               </div>');
             elementsStrArr.push('           </div>');
             elementsStrArr.push('       </div>');
@@ -344,7 +354,7 @@ let rptSignatureHelper = {
         elementsStrArr.push('   </div>');
         elementsStrArr.push('</div>');
         // 2. 日期
-        elementsStrArr.push('<div class="col-6">');
+        elementsStrArr.push('<div class="col-4">');
         let dftDate = '';
         if (role_rel.sign_date !== '' && role_rel.sign_date.length > 20) {
             dftDate = (new Date(role_rel.sign_date)).Format('yyyy-MM-dd');
@@ -861,13 +871,17 @@ let rptSignatureHelper = {
             }
         }
     },
-    _changeSignType: function(dom, signature_name, chkStr) {
+    _changeSignType: function(dom, signature_name, chkStr, userStamp) {
         for (const roleRel of ROLE_REL_LIST) {
             if (roleRel.signature_name === signature_name) {
                 if (dom.checked) {
                     if (roleRel.sign_output.indexOf(chkStr) < 0) {
                         roleRel.sign_output.push(chkStr);
                     }
+                    if(chkStr==='private_stamp'&&!roleRel.private_stamp_path && userStamp){
+                        roleRel.private_stamp_path = userStamp.split('!;!')[0] ;
+                        console.log(roleRel.private_stamp_path);
+                    }
                 } else {
                     let idx = roleRel.sign_output.indexOf(chkStr);
                     if (idx >= 0) {
@@ -908,6 +922,53 @@ let rptSignatureHelper = {
         targetCell.area.Right = pLeft + STD_STAMP_SIZE_WIDTH;
         targetCell.area.Bottom = pTop + STD_STAMP_SIZE_HEIGHT;
     },
+    initChosePrivateStampPath:function(stampPathList,signature_name){
+        let currentStamp='';
+        const targetNode= ROLE_REL_LIST.find(item=>item.signature_name===signature_name);
+        if(targetNode) currentStamp=targetNode.private_stamp_path
+        let content=`<div class='row justify-content-md-center'>`;
+        stampPathList.split('!;!').forEach(item=>{
+            content+=`<div class="card col-3 p-2 m-3 d-flex ${currentStamp===item?'card-gk-active':''} stamp-img">
+                            <div class="p-0 private-stamp-img">
+                                <div  class="sel-width check-state ${currentStamp===item?'sel-blue':''} "></div>
+                                <img src="${rptSignatureHelper.fujianOssPath}${item}" data-src='${item}' class="img-fluid" alt="...">
+                            </div>
+                        </div>`;
+        })
+        
+        for(let i=0;i<stampPathList.length%3;i++){
+            content+=`<div class="col-3 p-2 m-3"></div>`;
+        }
+
+        content+=` </div><div class='privateStampRoleName' data-name='${signature_name}'></div>`;
+
+        $('#chose-private-stamp-path .modal-body').empty().append(content);
+        $('.stamp-img').on('click',(e)=>{
+            $('.stamp-img').removeClass('card-gk-active');
+            $('.stamp-img').find('.sel-width').removeClass('sel-blue');
+            $(e.currentTarget).addClass('card-gk-active');
+            $(e.currentTarget).find('.sel-width').addClass('sel-blue');
+        })
+    },
+    setPrivateStamp(e){
+        const imgSrc= $('.stamp-img.card-gk-active').find('.img-fluid').data('src');
+        const privateStampRoleName=$('.privateStampRoleName').data('name');
+        const targetNode= ROLE_REL_LIST.find(item=>item.signature_name==privateStampRoleName);
+        targetNode.private_stamp_path=imgSrc;
+        $('#chose-private-stamp-path').modal('hide');
+    },
+    _get_newPrivate_stamp_path(roleRelObj,userAcc){
+        if(!roleRelObj.private_stamp_path){
+            if(!userAcc.stamp_path) return '';
+            if(userAcc.stamp_path.split('!;!').length===1){
+                return userAcc.stamp_path;
+            }else{
+                return userAcc.stamp_path.split('!;!')[0];
+            }
+        }
+        return roleRelObj.private_stamp_path;
+
+    }
 }
 
 function _getSignDateByAllScenarios(userAccId) {
@@ -1176,3 +1237,5 @@ function getHttpBlobText(url) {
         xhr.send();
     });
 }
+
+

+ 10 - 1
app/router.js

@@ -40,6 +40,7 @@ module.exports = app => {
     const budgetCheck = app.middlewares.budgetCheck();
     // 登入登出相关
     app.get('/login', 'loginController.index');
+    app.get('/login/:code', 'loginController.index');
     app.get('/login/port', api2otherCheck, 'loginController.port');
     app.get('/', 'loginController.index');
     app.get('/logout', 'loginController.logout');
@@ -157,7 +158,7 @@ module.exports = app => {
     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.post('/tender/:id/shenpi/ledger/load', sessionAuth, tenderCheck, 'tenderController.loadLedgerData');
+    app.post('/tender/:id/shenpi/ass/load', sessionAuth, tenderCheck, 'tenderController.loadAuditAss');
     app.post('/tender/:id/shenpi/save-sign', sessionAuth, tenderCheck, 'tenderController.saveCooperateSign');
     app.post('/tender/:id/copy-setting', sessionAuth, tenderCheck, 'tenderController.copyTender');
     app.post('/tender/:id/tourist/audit/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.saveTourist');
@@ -287,6 +288,7 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/use-change', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.useChange');
     app.post('/tender/:id/measure/stage/:order/auto-use-change', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.autoUseChange');
     app.post('/tender/:id/measure/stage/:order/check', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.check');
+    app.post('/tender/:id/measure/stage/:order/stageCheck', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.stageCheck');
     app.post('/tender/:id/measure/stage/:order/save/cooperation', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.saveCooperationData');
     app.post('/tender/:id/measure/stage/:order/im-file/upload', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.uploadImFile');
     app.post('/tender/:id/measure/stage/:order/im-file/del', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.deleteImFile');
@@ -429,6 +431,8 @@ module.exports = app => {
     app.post('/tender/report_api/addArchiveEncryption', sessionAuth, 'reportArchiveController.addReportArchiveEncryption');
     app.post('/tender/report_api/updateArchiveEncryption', sessionAuth, 'reportArchiveController.updateReportArchiveEncryption');
     app.post('/tender/report_api/removeArchiveEncryption', sessionAuth, 'reportArchiveController.removeReportArchiveEncryption');
+    app.post('/tender/:id/sendReportFileMsg', sessionAuth, tenderCheck, uncheckTenderCheck, 'reportArchiveController.sendFileMsg');
+    app.get('/tender/:id/measure/stage/:order/sendReportFileMsg', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'reportArchiveController.sendFileMsg');
 
     // 电子签名
     app.get('/tender/:id/signReport', sessionAuth, tenderCheck, uncheckTenderCheck, 'reportArchiveController.signReport');
@@ -570,6 +574,7 @@ module.exports = app => {
     // app.get('/profile/netcasign/upload', sessionAuth, 'profileController.netcasignload');
     app.post('/profile/sign/delete', sessionAuth, 'profileController.signDelete');
     app.post('/profile/sign/upload', sessionAuth, 'profileController.signUpload');
+    app.post('/profile/stamp/upload', sessionAuth, 'profileController.stampUpload');
     app.get('/profile/safe', sessionAuth, 'profileController.safe');
     app.post('/profile/save', sessionAuth, 'profileController.saveBase');
     app.post('/profile/password', sessionAuth, 'profileController.modifyPassword');
@@ -652,6 +657,10 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/sumLoad', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'tenderController.sumLoad');
     app.post('/tender/:id/revise/:rid/info/sumLoad', sessionAuth, tenderCheck, uncheckTenderCheck, reviseCheck, 'tenderController.sumLoad');
 
+    // 多人协同
+    app.post('/tender/:id/measure/stage/:order/ass', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'tenderController.auditAssist');
+    app.post('/tender/:id/measure/stage/:order/ass-confirm', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'tenderController.auditAssistConfirm');
+
     // 扫码登录
     app.get('/wxAuth', 'loginController.wxAuth');
     app.get('/wxproject', 'loginController.wxProject');

+ 99 - 0
app/service/audit_ass.js

@@ -0,0 +1,99 @@
+'use strict';
+
+/**
+ *  奖罚金
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class AuditAss extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'audit_ass';
+        }
+
+        async getData(tid) {
+            return await this.getAllDataByCondition({ where: { tid } });
+        }
+
+        async getUserData(tid, user_id) {
+            return await this.getAllDataByCondition({ where: { tid, user_id } });
+        }
+
+        async _addData(data) {
+            if (!data.user_id || !data.ass_user_id) throw '新增协审错误';
+            const nd = {
+                pid: this.ctx.session.sessionProject.id,
+                tid: this.ctx.tender.id,
+                user_id: data.user_id,
+                ass_user_id: data.ass_user_id,
+                name: data.name,
+                company: data.company,
+                role: data.role,
+                ass_ledger_id: data.ass_ledger_id,
+            };
+            const result = await this.db.insert(this.tableName, nd);
+            nd.id = result.insertId;
+            return nd;
+        }
+
+        async _delData(data) {
+            await this.db.delete(this.tableName, {id: data.id});
+            return data;
+        }
+
+        async _updateData(data) {
+            if (!data) return;
+            const datas = data instanceof Array ? data : [datas];
+
+            const orgDatas = await this.getAllDataByCondition({
+                where: { id: this.ctx.helper._.map(datas, 'id') }
+            });
+            const uDatas = [];
+            for (const d of datas) {
+                const od = this.ctx.helper._.find(orgDatas, {id: d.id});
+                if (!od) continue;
+
+                const nd = {id: od.id};
+                if (d.ass_ledger_id !== undefined) nd.ass_ledger_id = d.ass_ledger_id;
+                uDatas.push(nd);
+            }
+            if (uDatas.length > 0) {
+                await this.db.updateRows(this.tableName, uDatas);
+                return uDatas;
+            } else {
+                return [];
+            }
+        }
+
+        async updateData(data) {
+            const result = { update: [] };
+            try {
+                if (data.add) {
+                    result.add = await this._addData(data.add);
+                }
+                if (data.update) {
+                    result.update = await this._updateData(data.update);
+                }
+                if (data.del) {
+                    result.del = await this._delData(data.del);
+                }
+                return result;
+            } catch (err) {
+                if (err) result.err = err;
+                return result;
+            }
+        }
+    }
+
+    return AuditAss;
+};

+ 1 - 0
app/service/category_value.js

@@ -92,6 +92,7 @@ module.exports = app => {
                         let cate = this._.find(tender.category, function (c) { return c.cid === cid; });
                         if (!cate) {
                             cate = {cid: cid};
+                            if (!tender.category) tender.category = [];
                             tender.category.push(cate);
                         }
                         cate.value = v.id;

+ 7 - 2
app/service/change.js

@@ -500,10 +500,10 @@ module.exports = app => {
          * @return {void}
          */
         async getChangeTp(tenderId) {
-            const sql = 'SELECT SUM(`total_price`) AS tp FROM ?? WHERE tid = ? AND status = ?';
+            const sql = 'SELECT SUM(`total_price`) AS tp, SUM(`positive_tp`) AS p_tp, SUM(`negative_tp`) AS n_tp FROM ?? WHERE tid = ? AND status = ?';
             const sqlParam = [this.tableName, tenderId, audit.flow.status.checked];
             const result = await this.db.queryOne(sql, sqlParam);
-            return result ? result.tp : 0;
+            return result ? [result.tp, result.p_tp, result.n_tp] : [0, 0, 0];
         }
 
         /**
@@ -1690,6 +1690,11 @@ module.exports = app => {
                 throw err;
             }
         }
+
+        async getAllChangeHasMinus(tenderId) {
+            const sql = `SELECT * FROM ${this.tableName} WHERE tid = ? AND negative_tp < 0 ORDER BY code asc`;
+            return this.db.query(sql, [tenderId]);
+        }
     }
 
     return Change;

+ 11 - 1
app/service/change_audit_list.js

@@ -396,12 +396,22 @@ module.exports = app => {
             const sqlParam = [this.tableName, this.ctx.change.cid];
             const changeList = await transaction.query(sql, sqlParam);
             let total_price = 0;
+            let positive_tp = 0;
+            let negative_tp = 0;
             const tp_decimal = this.ctx.change.tp_decimal ? this.ctx.change.tp_decimal : this.ctx.tender.info.decimal.tp;
             for (const cl of changeList) {
-                total_price = this.ctx.helper.accAdd(total_price, this.ctx.helper.mul(cl.unit_price, cl.spamount, tp_decimal));
+                const price = this.ctx.helper.mul(cl.unit_price, cl.spamount, tp_decimal);
+                total_price = this.ctx.helper.accAdd(total_price, price);
+                if (price >= 0) {
+                    positive_tp = this.ctx.helper.accAdd(positive_tp, price);
+                } else {
+                    negative_tp = this.ctx.helper.accAdd(negative_tp, price);
+                }
             }
             const updateData = {
                 total_price,
+                positive_tp,
+                negative_tp,
             };
             if (updateTpDecimal) {
                 updateData.tp_decimal = tp_decimal;

+ 2 - 0
app/service/cooperation_confirm.js

@@ -8,6 +8,8 @@
  * @version
  */
 
+const auditConst = require('../const/audit');
+
 module.exports = app => {
     class CooperationConfirm extends app.BaseService {
         /**

+ 3 - 0
app/service/ledger_audit.js

@@ -15,6 +15,7 @@ const SmsAliConst = require('../const/sms_alitemplate');
 const wxConst = require('../const/wechat_template');
 const shenpiConst = require('../const/shenpi');
 const pushType = require('../const/audit').pushType;
+const pushOperate = require('../const/spec_3f').pushOperate;
 
 module.exports = app => {
     class LedgerAudit extends app.BaseService {
@@ -390,6 +391,8 @@ module.exports = app => {
                             begin_time: Date.parse(begin_audit.begin_time),
                         };
                         await this.ctx.helper.sendWechat(users, smsTypeConst.const.TZ, smsTypeConst.judge.result.toString(), wxConst.template.ledger, wechatData);
+                        // 审批通过 - 检查三方特殊推送
+                        await this.ctx.service.specMsg.addLedgerMsg(transaction, pid, this.ctx.tender, pushOperate.ledger.checked);
                     }
                 } else {
                     // 同步标段信息

+ 16 - 10
app/service/ledger_history.js

@@ -71,25 +71,25 @@ module.exports = app => {
          * @return {Promise<void>} - 新增备份id
          * @private
          */
-        async backupStageLedgerHistory(stage) {
-            const sbCount = await this.ctx.service.ledger.count({ tender_id: this.ctx.stage.tid });
-            const spCount = await this.ctx.service.pos.count({ tid: this.ctx.stage.tid });
-            const ledgerHis = await this.ctx.service.ledgerHistory.getLatestHistory(this.ctx.tender.id);
+        async checkBackupLedgerHistory(tid, sid = 0, rid = '') {
+            const sbCount = await this.ctx.service.ledger.count({ tender_id: tid });
+            const spCount = await this.ctx.service.pos.count({ tid: tid });
+            const ledgerHis = await this.ctx.service.ledgerHistory.getLatestHistory(tid);
             if (sbCount === ledgerHis.bills_count && spCount === ledgerHis.pos_count) return ledgerHis.id;
 
             const now = new Date();
             const timestamp = (now).getTime();
 
-            const billsHis = `${this.ctx.session.sessionProject.id}/${stage.tid}/ledger/bills${timestamp}-s.json`;
-            const bills = await this.ctx.service.ledger.getData(stage.tid);
+            const billsHis = `${this.ctx.session.sessionProject.id}/${tid}/ledger/bills${timestamp}-s.json`;
+            const bills = await this.ctx.service.ledger.getData(tid);
             await this.ctx.hisOss.put(this.ctx.hisOssPath + billsHis, Buffer.from(JSON.stringify(bills), 'utf8'));
 
-            const posHis = `${this.ctx.session.sessionProject.id}/${stage.tid}/ledger/pos${timestamp}-s.json`;
-            const pos = await this.ctx.service.pos.getPosData({ tid: stage.tid });
+            const posHis = `${this.ctx.session.sessionProject.id}/${tid}/ledger/pos${timestamp}-s.json`;
+            const pos = await this.ctx.service.pos.getPosData({ tid: tid });
             await this.ctx.hisOss.put(this.ctx.hisOssPath + posHis, Buffer.from(JSON.stringify(pos), 'utf8'));
 
             const result = await this.db.insert(this.tableName, {
-                pid: this.ctx.session.sessionProject.id, tid: stage.tid, sid: stage.id,
+                pid: this.ctx.session.sessionProject.id, tid, sid, rid,
                 in_time: now,
                 bills_file: billsHis, pos_file: posHis,
                 bills_count: bills.length, pos_count: pos.length,
@@ -109,6 +109,7 @@ module.exports = app => {
             const timestamp = (now).getTime();
 
             const price = await this.ctx.service.revisePrice.getAllDataByCondition({ where: { rid: revise.id } });
+            let sum = { total_price: 0 };
 
             const billsHis = `${this.ctx.session.sessionProject.id}/${revise.tid}/ledger/bills${timestamp}-r.json`;
             const bills = await this.ctx.service.reviseBills.getData(revise.tid);
@@ -116,12 +117,17 @@ module.exports = app => {
             const posHis = `${this.ctx.session.sessionProject.id}/${revise.tid}/ledger/pos${timestamp}-r.json`;
             const pos = await this.ctx.service.revisePos.getData(revise.tid);
             if (price.length === 0) {
+                for (const b of bills) {
+                    if (!b.is_leaf) continue;
+                    sum.total_price = this.ctx.helper.add(sum.total_price, b.total_price);
+                }
                 await this.ctx.hisOss.put(this.ctx.hisOssPath + billsHis, Buffer.from(JSON.stringify(bills), 'utf8'));
                 await this.ctx.hisOss.put(this.ctx.hisOssPath + posHis, Buffer.from(JSON.stringify(pos), 'utf8'));
             } else {
                 const reviseTree = new Ledger.reviseTree(this.ctx, { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1 });
                 reviseTree.loadRevisePrice(price, this.ctx.tender.info.decimal);
                 reviseTree.loadDatas(bills);
+                sum = reviseTree.sum();
                 await this.ctx.hisOss.put(this.ctx.hisOssPath + billsHis, Buffer.from(JSON.stringify(reviseTree.getUpdateReviseData()), 'utf8'));
                 await this.ctx.hisOss.put(this.ctx.hisOssPath + posHis, Buffer.from(JSON.stringify(pos), 'utf8'));
             }
@@ -134,7 +140,7 @@ module.exports = app => {
                 bills_count: bills.length, pos_count: pos.length,
             });
 
-            return result.insertId;
+            return [result.insertId, sum];
         }
     }
 

+ 25 - 9
app/service/ledger_revise.js

@@ -24,6 +24,14 @@ module.exports = app => {
             this.tableName = 'ledger_revise';
         }
 
+        _analysisData(data) {
+            if (!data) return;
+            const datas = data instanceof Array ? data : [data];
+            for (const d of datas) {
+                if (d.sum) d.sum = JSON.parse(d.sum);
+            }
+        }
+
         /**
          * 获取标段下,修订(分页,且按时间倒序)
          * @param {Number}tid - 标段id
@@ -39,7 +47,9 @@ module.exports = app => {
                 '  LIMIT ?, ?';
             const Len = this.app.config.pageSize;
             const sqlParam = [tid, (this.ctx.page - 1) * Len, Len];
-            return await this.db.query(sql, sqlParam);
+            const result = await this.db.query(sql, sqlParam);
+            this._analysisData(result);
+            return result;
         }
 
         /**
@@ -55,7 +65,9 @@ module.exports = app => {
                 '  WHERE lc.tid = ?' +
                 '  ORDER BY lc.in_time DESC';
             const sqlParam = [tid];
-            return await this.db.query(sql, sqlParam);
+            const result = await this.db.query(sql, sqlParam);
+            this._analysisData(result);
+            return result;
         }
 
         async getLastestRevise(tid, valid = true) {
@@ -67,7 +79,9 @@ module.exports = app => {
                 '  ORDER BY lc.in_time DESC' +
                 '  LIMIT 0, 1';
             const sqlParam = [tid];
-            return await this.db.queryOne(sql, sqlParam);
+            const result = await this.db.queryOne(sql, sqlParam);
+            this._analysisData(result);
+            return result;
         }
 
         async getRevise(tid, rid) {
@@ -79,7 +93,9 @@ module.exports = app => {
                 '  ORDER BY lc.in_time DESC' +
                 '  LIMIT 0, 1';
             const sqlParam = [tid, rid];
-            return await this.db.queryOne(sql, sqlParam);
+            const result = await this.db.queryOne(sql, sqlParam);
+            this._analysisData(result);
+            return result;
         }
 
         /**
@@ -160,11 +176,11 @@ module.exports = app => {
                 throw '数据错误';
             }
             const maxOrder = await this.getNewOrder(tid);
-            const latest = await this.ctx.service.ledgerHistory.getLatestHistory(tid);
-            if (!latest) throw '台账历史数据有误';
+            const preHisId = await this.ctx.service.ledgerHistory.checkBackupLedgerHistory(tid);
+            if (!preHisId) throw '台账历史数据有误';
             const data = {
                 id: this.uuid.v4(), tid: tid, uid: uid,
-                corder: maxOrder + 1, in_time: new Date(), status: audit.status.uncheck, pre_his_id: latest.id,
+                corder: maxOrder + 1, in_time: new Date(), status: audit.status.uncheck, pre_his_id: preHisId,
             };
             const transaction = await this.db.beginTransaction();
             try {
@@ -191,12 +207,12 @@ module.exports = app => {
          * @returns {Promise<void>}
          */
         async cancelRevise(revise) {
-            const his_id = await this.ctx.service.ledgerHistory.backupReviseLedgerHistory(revise);
+            const [his_id, sum] = await this.ctx.service.ledgerHistory.backupReviseLedgerHistory(revise);
             const transaction = await this.db.beginTransaction();
             try {
                 const result = await transaction.update(this.tableName, {
                     id: revise.id, valid: false, end_time: new Date(),
-                    his_id,
+                    his_id, sum: JSON.stringify(sum),
                 });
                 await transaction.update(this.ctx.service.ledgerHistory.tableName, { id: his_id, valid: 0 });
                 if (revise.his_id > 0) await transaction.update(this.ctx.service.ledgerHistory.tableName, { id: revise.his_id, valid: 0 });

+ 2 - 2
app/service/material.js

@@ -209,9 +209,9 @@ module.exports = app => {
                     let m_tp = null;
                     let m_tax_tp = null;
                     if (data.is_stage_self) {
-                        [m_tp, m_tax_tp] = await this.ctx.service.materialStageBills.insertBills(transaction, this.ctx.tender.id, newMaterial.id, newMaterial.stage_id, insertMaterialStage, JSON.parse(newMaterial.decimal));
+                        [m_tp, m_tax_tp] = await this.ctx.service.materialStageBills.insertBills(transaction, this.ctx.tender.id, newMaterial.id, newMaterial.stage_id, insertMaterialStage, JSON.parse(newMaterial.decimal), preMaterial.is_stage_self);
                     } else {
-                        [m_tp, m_tax_tp] = await this.ctx.service.materialBills.updateNewMaterial(transaction, this.ctx.tender.id, newMaterial.id, this.ctx, newMaterial.stage_id, JSON.parse(newMaterial.decimal));
+                        [m_tp, m_tax_tp] = await this.ctx.service.materialBills.updateNewMaterial(transaction, this.ctx.tender.id, newMaterial.id, this.ctx, newMaterial.stage_id, JSON.parse(newMaterial.decimal), preMaterial.is_stage_self);
                     }
                     // 修改现行价格指数,并返回调差基数json
                     const ex_calc = await this.ctx.service.materialExponent.updateNewMaterial(transaction, newMaterial.id, this.ctx, newMaterial.stage_id, preMaterial.ex_calc, JSON.parse(newMaterial.decimal));

+ 83 - 39
app/service/material_bills.js

@@ -83,7 +83,7 @@ module.exports = app => {
             if (!this.ctx.tender || !this.ctx.material) {
                 throw '数据错误';
             }
-            const newOrder = order ? parseInt(order) + 1 : await this._getMaxOrder(this.ctx.tender.id);
+            const newOrder = this._.isNumber(order) ? parseInt(order) + 1 : await this._getMaxOrder(this.ctx.tender.id);
             const transaction = await this.db.beginTransaction();
             try {
                 // order以下的工料+1
@@ -292,6 +292,9 @@ module.exports = app => {
                         await transaction.select(this.ctx.service.materialStage.tableName, { where: { mid: this.ctx.material.id, id: ms_id } });
                 }
                 await transaction.update(this.tableName, data);
+                if (this.ctx.material.is_stage_self) {
+                    result.billsData = await transaction.select(this.tableName, { where: { id: data.id } });
+                }
                 result.m_tp = await this.calcMaterialMTp(transaction);
                 await transaction.commit();
                 return result;
@@ -410,6 +413,7 @@ module.exports = app => {
                     await this.ctx.service.materialStage.updateMtp(transaction, ms_id);
                     result.stageBillsData = await transaction.select(this.ctx.service.materialStageBills.tableName, { where: { mid: this.ctx.material.id } });
                     result.stageData = await transaction.select(this.ctx.service.materialStage.tableName, { where: { mid: this.ctx.material.id } });
+                    result.billsData = await transaction.select(this.tableName, { where: { mid: this.ctx.material.id } });
                 }
                 result.m_tp = await this.calcMaterialMTp(transaction);
                 await transaction.commit();
@@ -427,13 +431,13 @@ module.exports = app => {
          * @param mid
          * @returns {Promise<number>}
          */
-        async updateNewMaterial(transaction, tid, mid, ctx, stage_id, decimal) {
+        async updateNewMaterial(transaction, tid, mid, ctx, stage_id, decimal, pre_is_stage_self = 0) {
             const materialBillsData = await this.getAllDataByCondition({ where: { tid } });
             let m_tp = 0;
             let m_tax_tp = 0;
             const materialCalculator = new MaterialCalculator(ctx, stage_id, ctx.tender.info);
             for (const mb of materialBillsData) {
-                const [one_tp, one_tax_tp] = await this.calcQuantityByMB(transaction, mid, mb, materialCalculator, decimal);
+                const [one_tp, one_tax_tp] = await this.calcQuantityByMB(transaction, mid, mb, materialCalculator, decimal, pre_is_stage_self);
                 m_tp = this.ctx.helper.add(m_tp, one_tp);
                 m_tax_tp = this.ctx.helper.add(m_tax_tp, one_tax_tp);
             }
@@ -447,7 +451,7 @@ module.exports = app => {
          * @param mb
          * @returns {Promise<*>}
          */
-        async calcQuantityByMB(transaction, mid, mb, materialCalculator, decimal) {
+        async calcQuantityByMB(transaction, mid, mb, materialCalculator, decimal, pre_is_stage_self) {
             const [newmsg_spread, newm_spread] = await this.getSpread(mb, null, decimal.up);
             if (mb.t_type === materialConst.t_type[0].value) {
                 const sql = 'SELECT SUM(`gather_qty`*`quantity`) as quantity FROM ' + this.ctx.service.materialList.tableName + ' WHERE `mid`=? AND `mb_id`=? AND `is_join`=1';
@@ -475,11 +479,12 @@ module.exports = app => {
                 const m_tax_tp = this.ctx.helper.round(this.ctx.helper.mul(m_tp, (1 + this.ctx.helper.div(mb.m_tax, 100))), decimal.tp);
                 return [m_tp, m_tax_tp];
             } else if (mb.t_type === materialConst.t_type[1].value) {
-                const quantity = await materialCalculator.calculateExpr(mb.expr);
+                const quantity = pre_is_stage_self ? 0 : await materialCalculator.calculateExpr(mb.expr);
                 const newTp = quantity !== 0 && quantity !== null ? this.ctx.helper.round(this.ctx.helper.mul(this.ctx.helper.round(quantity, decimal.qty), newm_spread), decimal.tp) : null;
                 const updateData = {
                     id: mb.id,
                     quantity: quantity !== 0 && quantity !== null ? this.ctx.helper.round(quantity, decimal.qty) : null,
+                    expr: pre_is_stage_self ? null : mb.expr,
                     msg_tp: null,
                     msg_times: null,
                     msg_spread: newmsg_spread,
@@ -525,30 +530,50 @@ module.exports = app => {
                 const returnData = {};
                 returnData.m_tp = this.ctx.material.m_tp;
                 if (this.ctx.material.is_stage_self) {
+                    if (!data.ms_id) throw '参数有误';
                     const mbInfo = await this.getDataById(data.id);
-                    let all_m_tp = 0;
-                    let all_tax_tp = 0;
-                    for (const sid of this.ctx.material.stage_id.split(',')) {
-                        const materialCalculator = new MaterialCalculator(this.ctx, sid, this.ctx.tender.info);
-                        const quantity = await materialCalculator.calculateExpr(data.expr);
-                        const msInfo = await this.ctx.service.materialStage.getDataByCondition({ mid: this.ctx.material.id, sid });
-                        const msbInfo = await this.ctx.service.materialStageBills.getDataByCondition({ mid: this.ctx.material.id, mb_id: data.id, ms_id: msInfo.id });
-                        const newQuantity = quantity !== 0 ? this.ctx.helper.round(quantity, this.ctx.material.decimal.qty) : null;
-                        const m_tp = newQuantity ? this.ctx.helper.round(this.ctx.helper.mul(newQuantity, msbInfo.m_spread), this.ctx.material.decimal.tp) : null;
-                        const updateData = {
-                            id: data.id,
-                            quantity: newQuantity,
-                            m_tp,
-                            m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(m_tp, (1 + this.ctx.helper.div(mbInfo.m_tax, 100))), this.ctx.material.decimal.tp),
-                        };
-                        const [one_bill_m_tp, one_bill_tax_tp] = await this.ctx.service.materialStageBills.update(transaction, updateData, msInfo.id);
-                        await this.ctx.service.materialStage.updateMtp(transaction, msInfo.id);
-                        all_m_tp = this.ctx.helper.round(one_bill_m_tp, this.ctx.material.decimal.tp);
-                        all_tax_tp = this.ctx.helper.round(one_bill_tax_tp, this.ctx.material.decimal.tp);
-                    }
-                    const updateBillsData = {
+                    const msInfo = await this.ctx.service.materialStage.getDataById(data.ms_id);
+                    const msbInfo = await this.ctx.service.materialStageBills.getDataByCondition({ mid: this.ctx.material.id, mb_id: data.id, ms_id: data.ms_id });
+                    // let all_m_tp = 0;
+                    // let all_tax_tp = 0;
+                    const materialCalculator = new MaterialCalculator(this.ctx, msInfo.sid, this.ctx.tender.info);
+                    const quantity = await materialCalculator.calculateExpr(data.expr);
+                    const newQuantity = quantity !== 0 ? this.ctx.helper.round(quantity, this.ctx.material.decimal.qty) : null;
+                    const m_tp = newQuantity ? this.ctx.helper.round(this.ctx.helper.mul(newQuantity, msbInfo.m_spread), this.ctx.material.decimal.tp) : null;
+                    const updateData = {
                         id: data.id,
+                        quantity: newQuantity,
                         expr: data.expr,
+                        m_tp,
+                        m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(m_tp, (1 + this.ctx.helper.div(mbInfo.m_tax, 100))), this.ctx.material.decimal.tp),
+                    };
+                    const [one_bill_m_tp, one_bill_tax_tp] = await this.ctx.service.materialStageBills.update(transaction, updateData, msInfo.id);
+                    await this.ctx.service.materialStage.updateMtp(transaction, msInfo.id);
+                    const all_m_tp = this.ctx.helper.round(one_bill_m_tp, this.ctx.material.decimal.tp);
+                    const all_tax_tp = this.ctx.helper.round(one_bill_tax_tp, this.ctx.material.decimal.tp);
+
+                    // for (const sid of this.ctx.material.stage_id.split(',')) {
+                    //     const materialCalculator = new MaterialCalculator(this.ctx, sid, this.ctx.tender.info);
+                    //     const quantity = await materialCalculator.calculateExpr(data.expr);
+                    //     const msInfo = await this.ctx.service.materialStage.getDataByCondition({ mid: this.ctx.material.id, sid });
+                    //     const msbInfo = await this.ctx.service.materialStageBills.getDataByCondition({ mid: this.ctx.material.id, mb_id: data.id, ms_id: msInfo.id });
+                    //     const newQuantity = quantity !== 0 ? this.ctx.helper.round(quantity, this.ctx.material.decimal.qty) : null;
+                    //     const m_tp = newQuantity ? this.ctx.helper.round(this.ctx.helper.mul(newQuantity, msbInfo.m_spread), this.ctx.material.decimal.tp) : null;
+                    //     const updateData = {
+                    //         id: data.id,
+                    //         quantity: newQuantity,
+                    //         m_tp,
+                    //         m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(m_tp, (1 + this.ctx.helper.div(mbInfo.m_tax, 100))), this.ctx.material.decimal.tp),
+                    //     };
+                    //     const [one_bill_m_tp, one_bill_tax_tp] = await this.ctx.service.materialStageBills.update(transaction, updateData, msInfo.id);
+                    //     await this.ctx.service.materialStage.updateMtp(transaction, msInfo.id);
+                    //     all_m_tp = this.ctx.helper.round(one_bill_m_tp, this.ctx.material.decimal.tp);
+                    //     all_tax_tp = this.ctx.helper.round(one_bill_tax_tp, this.ctx.material.decimal.tp);
+                    // }
+
+                    const updateBillsData = {
+                        id: data.id,
+                        // expr: data.expr,
                         m_tp: all_m_tp ? all_m_tp : null,
                         m_tax_tp: all_tax_tp ? all_tax_tp : null,
                     };
@@ -624,13 +649,24 @@ module.exports = app => {
                         };
                         if (newDecimalQty !== this.ctx.material.decimal.qty) {
                             // 通过管理重新算出quantity并保留小数位
-                            const sql = 'SELECT SUM(`gather_qty`*`quantity`) as quantity FROM ' + this.ctx.service.materialList.tableName + ' WHERE `mid`=? AND `mb_id`=? AND `ms_id`=? AND `is_join`=1';
-                            const sqlParam = [this.ctx.material.id, mb.id, ms.id];
-                            const mb_quantity = await transaction.queryOne(sql, sqlParam);
-                            const newQuantity = this.ctx.helper.round(mb_quantity.quantity, newDecimalQty);
-                            if (newQuantity !== msb.quantity) {
-                                updateStageBillData.quantity = newQuantity;
-                                msb.quantity = newQuantity;
+                            if (mb.t_type === materialConst.t_type[0].value) {
+                                // 通过管理重新算出quantity并保留小数位
+                                const sql = 'SELECT SUM(`gather_qty`*`quantity`) as quantity FROM ' + this.ctx.service.materialList.tableName + ' WHERE `mid`=? AND `mb_id`=? AND `ms_id`=? AND `is_join`=1';
+                                const sqlParam = [this.ctx.material.id, mb.id, ms.id];
+                                const mb_quantity = await transaction.queryOne(sql, sqlParam);
+                                const newQuantity = this.ctx.helper.round(mb_quantity.quantity, newDecimalQty);
+                                if (newQuantity !== msb.quantity) {
+                                    updateStageBillData.quantity = newQuantity;
+                                    msb.quantity = newQuantity;
+                                }
+                            } else if (mb.t_type === materialConst.t_type[1].value) {
+                                const materialCalculator = new MaterialCalculator(this.ctx, ms.sid, this.ctx.tender.info);
+                                const quantity = await materialCalculator.calculateExpr(msb.expr);
+                                const newQuantity = this.ctx.helper.round(quantity, newDecimalQty);
+                                if (newQuantity !== msb.quantity) {
+                                    updateStageBillData.quantity = newQuantity;
+                                    msb.quantity = newQuantity;
+                                }
                             }
                         }
                         if (newDecimalUp !== this.ctx.material.decimal.up) {
@@ -692,12 +728,20 @@ module.exports = app => {
                     }
                     if (newDecimalQty !== this.ctx.material.decimal.qty) {
                         // 通过管理重新算出quantity并保留小数位
-                        const sql = 'SELECT SUM(`gather_qty`*`quantity`) as quantity FROM ' + this.ctx.service.materialList.tableName + ' WHERE `mid`=? AND `mb_id`=? AND `is_join`=1';
-                        const sqlParam = [this.ctx.material.id, mb.id];
-                        const mb_quantity = await transaction.queryOne(sql, sqlParam);
-                        const newQuantity = this.ctx.helper.round(mb_quantity.quantity, newDecimalQty);
-                        mb.quantity = newQuantity;
-                        updateData.quantity = newQuantity;
+                        if (mb.t_type === materialConst.t_type[0].value) {
+                            const sql = 'SELECT SUM(`gather_qty`*`quantity`) as quantity FROM ' + this.ctx.service.materialList.tableName + ' WHERE `mid`=? AND `mb_id`=? AND `is_join`=1';
+                            const sqlParam = [this.ctx.material.id, mb.id];
+                            const mb_quantity = await transaction.queryOne(sql, sqlParam);
+                            const newQuantity = this.ctx.helper.round(mb_quantity.quantity, newDecimalQty);
+                            mb.quantity = newQuantity;
+                            updateData.quantity = newQuantity;
+                        } else if (mb.t_type === materialConst.t_type[1].value) {
+                            const materialCalculator = new MaterialCalculator(this.ctx, this.ctx.material.stage_id, this.ctx.tender.info);
+                            const quantity = await materialCalculator.calculateExpr(mb.expr);
+                            const newQuantity = this.ctx.helper.round(quantity, newDecimalQty);
+                            mb.quantity = newQuantity;
+                            updateData.quantity = newQuantity;
+                        }
                     }
                     const newTp = this.ctx.helper.round(this.ctx.helper.mul(mb.quantity, mb.m_spread), newDecimalTp);
                     updateData.m_tp = newTp;

+ 94 - 1
app/service/material_checklist.js

@@ -5,7 +5,7 @@
  * @date 2020/6/30
  * @version
  */
-
+const materialConst = require('../const/material');
 module.exports = app => {
     class MaterialChecklist extends app.BaseService {
         /**
@@ -61,6 +61,99 @@ module.exports = app => {
             }
             return await transaction.update(this.tableName, { id, had_bills });
         }
+
+        async addExportCB(addChecklist, addBillsList) {
+            if (!this.ctx.tender || !this.ctx.material) {
+                throw '数据错误';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                if (addChecklist.length > 0) {
+                    const insertDatas = [];
+                    for (const p of addChecklist) {
+                        p.mid = this.ctx.material.id;
+                        p.tid = this.ctx.tender.id;
+                        insertDatas.push(p);
+                    }
+                    await transaction.insert(this.tableName, insertDatas);
+                }
+                if (addBillsList.length > 0) {
+                    let order = await this.ctx.service.materialBills._getMaxOrder(this.ctx.tender.id);
+                    const pushBills = [];
+                    for (const b of addBillsList) {
+                        const newBills = {
+                            tid: this.ctx.tender.id,
+                            mid: this.ctx.material.id,
+                            code: b.code,
+                            name: b.name,
+                            unit: b.unit,
+                            order: order + 1,
+                            in_time: new Date(),
+                        };
+                        pushBills.push(newBills);
+                        ++order;
+                    }
+                    // 新增工料
+                    const result = await transaction.insert(this.ctx.service.materialBills.tableName, pushBills);
+                    // 获取刚批量添加的所有list
+                    for (let j = 0; j < pushBills.length; j++) {
+                        pushBills[j].id = result.insertId + j;
+                    }
+                    if (this.ctx.material.is_stage_self) {
+                        await this.ctx.service.materialStageBills.adds(transaction, pushBills);
+                    }
+                    const material_month = this.ctx.material.months ? this.ctx.material.months.split(',') : [];
+                    if (material_month.length > 0) {
+                        const insertArray = [];
+                        for (const pb of pushBills) {
+                            for (const ym of material_month) {
+                                const one_month = {
+                                    tid: this.ctx.tender.id,
+                                    mid: this.ctx.material.id,
+                                    mb_id: pb.id,
+                                    msg_tp: null,
+                                    yearmonth: ym,
+                                };
+                                insertArray.push(one_month);
+                            }
+                        }
+                        if (insertArray.length !== 0) await transaction.insert(this.ctx.service.materialMonth.tableName, insertArray);
+                    }
+                }
+                await transaction.commit();
+                const materialChecklistData = await this.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
+                const searchsql = { tid: this.ctx.tender.id };
+                let midList = [];
+                if (this.ctx.material.highOrder !== this.ctx.material.order) {
+                    midList = await this.ctx.service.material.getPreMidList(this.ctx.tender.id, this.ctx.material.order);
+                    searchsql.mid = midList;
+                }
+                searchsql.t_type = materialConst.t_type[0].value;
+                const materialBillsData = await this.ctx.service.materialBills.getAllDataByCondition({ where: searchsql, orders: [['order', 'asc']] });
+                // 取对应期的截取上期的调差金额和应耗数量
+                if (this.ctx.material.highOrder !== this.ctx.material.order) {
+                    for (const [mindex, mb] of materialBillsData.entries()) {
+                        const result = await this.ctx.service.materialBillsHistory.getByMbId(this.ctx.material.id, this.ctx.material.order, mb.id);
+                        this._.forEach(result, function(value, key) {
+                            if (key === 'mb_id') {
+                                materialBillsData[mindex].id = result ? result[key] : null;
+                            } else {
+                                materialBillsData[mindex][key] = result ? result[key] : null;
+                            }
+                        });
+                    }
+                }
+                const materialStageBillsData = this.ctx.material.is_stage_self ? await this.ctx.service.materialStageBills.getAllDataByCondition({ where: { tid: this.ctx.tender.id, mid: this.ctx.material.id } }) : [];
+                const self = this;
+                return { materialChecklistData: await materialChecklistData.sort(function(a, b) {
+                    return self.ctx.helper.compareCode(a.b_code, b.b_code);
+                }), materialBillsData, materialStageBillsData };
+            } catch (err) {
+                console.log(err);
+                await transaction.rollback();
+                throw err;
+            }
+        }
     }
     return MaterialChecklist;
 };

+ 148 - 20
app/service/material_list.js

@@ -294,13 +294,90 @@ module.exports = app => {
         }
 
         /**
+         * 修改material_bills的quantity值和计算本期金额
+         * @param transaction
+         * @param mb_id
+         * @return {Promise<*>}
+         */
+        async calcAllQuantityByML(transaction, mbIds) {
+            const mbList = await transaction.select(this.ctx.service.materialBills.tableName, { where: { id: mbIds } });
+            // 修改material_bills值
+            for (const mbInfo of mbList) {
+                if (!mbInfo) {
+                    throw '不存在该工料';
+                }
+                if (this.ctx.material.is_stage_self) {
+                    const updateDatas = [];
+                    const updateMsIds = [];
+                    const msbList = await transaction.select(this.ctx.service.materialStageBills.tableName, { where: { mid: this.ctx.material.id, mb_id: mbInfo.id } });
+                    for (const msb of msbList) {
+                        const sql = 'SELECT SUM(`gather_qty`*`quantity`) as quantity FROM ' + this.tableName + ' WHERE `mid`=? AND `mb_id`=? AND `ms_id`=? AND `is_join`=1';
+                        const sqlParam = [this.ctx.material.id, mbInfo.id, msb.ms_id];
+                        const mb_quantity = await transaction.queryOne(sql, sqlParam);
+                        const newQuantity = this.ctx.helper.round(mb_quantity.quantity, this.ctx.material.decimal.qty);
+                        const newTp = this.ctx.helper.round(this.ctx.helper.mul(newQuantity, msb.m_spread), this.ctx.material.decimal.tp);
+                        const updateData = {
+                            id: msb.id,
+                            quantity: newQuantity,
+                            m_tp: newTp,
+                            m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(newTp, (1 + this.ctx.helper.div(mbInfo.m_tax, 100))), this.ctx.material.decimal.tp),
+                        };
+                        updateDatas.push(updateData);
+                        updateMsIds.push(msb.ms_id);
+                    }
+                    if (updateDatas.length > 0) {
+                        await transaction.updateRows(this.ctx.service.materialStageBills.tableName, updateDatas);
+                        for (const msId of updateMsIds) {
+                            await this.ctx.service.materialStage.updateMtp(transaction, msId);
+                        }
+                    }
+                    // 还要更新bills表的m_tp和m_tax_tp值
+                    const sql3 = 'SELECT SUM(`m_tp`) as total_price, SUM(IF(`m_tax_tp` is null, `m_tp`, `m_tax_tp`)) as tax_total_price FROM ' + this.ctx.service.materialStageBills.tableName + ' WHERE `tid` = ? AND `mid` = ? AND `mb_id` = ? AND `is_summary` = 1';
+                    const sqlParam3 = [this.ctx.tender.id, this.ctx.material.id, mbInfo.id];
+                    const tp3 = await transaction.queryOne(sql3, sqlParam3);
+                    const updateBillsData = {
+                        id: mbInfo.id,
+                        m_tp: tp3.total_price,
+                        m_tax_tp: tp3.tax_total_price,
+                    };
+                    await transaction.update(this.ctx.service.materialBills.tableName, updateBillsData);
+                } else {
+                    const sql = 'SELECT SUM(`gather_qty`*`quantity`) as quantity FROM ' + this.tableName + ' WHERE `mid`=? AND `mb_id`=? AND `is_join`=1';
+                    const sqlParam = [this.ctx.material.id, mbInfo.id];
+                    const mb_quantity = await transaction.queryOne(sql, sqlParam);
+                    console.log(mb_quantity);
+                    const newQuantity = this.ctx.helper.round(mb_quantity.quantity, this.ctx.material.decimal.qty);
+                    const newTp = this.ctx.helper.round(this.ctx.helper.mul(newQuantity, mbInfo.m_spread), this.ctx.material.decimal.tp);
+                    const updateData = {
+                        id: mbInfo.id,
+                        quantity: newQuantity,
+                        m_tp: newTp,
+                        m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(newTp, (1 + this.ctx.helper.div(mbInfo.m_tax, 100))), this.ctx.material.decimal.tp),
+                    };
+                    await transaction.update(this.ctx.service.materialBills.tableName, updateData);
+                }
+            }
+            // 计算本期总金额
+            const sql2 = 'SELECT SUM(`m_tp`) as total_price, SUM(IF(`m_tax_tp` is null, `m_tp`, `m_tax_tp`)) as tax_total_price FROM ' + this.ctx.service.materialBills.tableName + ' WHERE `tid` = ? AND `is_summary` = 1';
+            const sqlParam2 = [this.ctx.tender.id];
+            const tp = await transaction.queryOne(sql2, sqlParam2);
+            console.log(tp);
+            const updateData2 = {
+                id: this.ctx.material.id,
+                m_tp: tp.total_price,
+                m_tax_tp: tp.tax_total_price,
+            };
+            return await transaction.update(this.ctx.service.material.tableName, updateData2);
+        }
+
+        /**
          * 获取工料清单关联表
          * @param {int} tid 标段id
          * @param {Object} mid 期id
          * @return {void}
          */
         async getMaterialData(tid, mid) {
-            const sql = 'SELECT ml.`id`, mb.`code`, mb.`name`, mb.`unit`, ml.`order`, ml.`quantity`, ml.`expr`, ml.`mb_id`, ml.`gcl_id`, ml.`xmj_id`, ml.`mx_id`, ml.`ms_id`, ml.`tid`, ml.`mid`, mb.m_spread' +
+            const sql = 'SELECT ml.`id`, mb.`code`, mb.`name`, mb.`unit`, ml.`order`, ml.`quantity`, ml.`expr`, ml.`mb_id`, ml.`gcl_id`, ml.`xmj_id`, ml.`mx_id`, ml.`ms_id`, ml.`tid`, ml.`mid`, mb.m_spread, ml.ms_id' +
                 ' FROM ' + this.tableName + ' as ml' +
                 ' LEFT JOIN ' + this.ctx.service.materialBills.tableName + ' as mb' +
                 ' ON ml.`mb_id` = mb.`id`' +
@@ -310,7 +387,7 @@ module.exports = app => {
         }
 
         async getPreMaterialData(tid, mid) {
-            const sql = 'SELECT ml.`id`, mb.`code`, mb.`name`, mb.`unit`, ml.`order`, ml.`quantity`, ml.`expr`, ml.`mb_id`, ml.`gcl_id`, ml.`xmj_id`, ml.`mx_id`, ml.`ms_id` ml.`tid`, ml.`mid`, mbh.m_spread' +
+            const sql = 'SELECT ml.`id`, mb.`code`, mb.`name`, mb.`unit`, ml.`order`, ml.`quantity`, ml.`expr`, ml.`mb_id`, ml.`gcl_id`, ml.`xmj_id`, ml.`mx_id`, ml.`ms_id`, ml.`tid`, ml.`mid`, mbh.m_spread, ml.ms_id' +
                 ' FROM ' + this.tableName + ' as ml' +
                 ' LEFT JOIN ' + this.ctx.service.materialBills.tableName + ' as mb ON ml.`mb_id` = mb.`id`' +
                 ' LEFT JOIN ' + this.ctx.service.materialBillsHistory.tableName + ' as mbh ON ml.`mb_id` = mbh.`mb_id` and mbh.mid = ?' +
@@ -319,6 +396,28 @@ module.exports = app => {
             return await this.db.query(sql, sqlParam);
         }
 
+        async getMaterialStageData(tid, mid) {
+            const sql = 'SELECT ml.`id`, mb.`code`, mb.`name`, mb.`unit`, ml.`order`, ml.`quantity`, ml.`expr`, ml.`mb_id`, ml.`gcl_id`, ml.`xmj_id`, ml.`mx_id`, ml.`ms_id`, ml.`tid`, ml.`mid`, msb.m_spread, ml.ms_id, ms.sid, ms.order as s_order' +
+                ' FROM ' + this.tableName + ' as ml' +
+                ' LEFT JOIN ' + this.ctx.service.materialBills.tableName + ' as mb ON ml.`mb_id` = mb.`id`' +
+                ' LEFT JOIN ' + this.ctx.service.materialStageBills.tableName + ' as msb ON ml.mb_id = msb.mb_id AND ml.ms_id = msb.ms_id' +
+                ' LEFT JOIN ' + this.ctx.service.materialStage.tableName + ' as ms ON ml.`ms_id` = ms.`id`' +
+                ' WHERE ml.`tid` = ? AND ml.`mid` = ?';
+            const sqlParam = [tid, mid];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        async getPreMaterialStageData(tid, mid) {
+            const sql = 'SELECT ml.`id`, mb.`code`, mb.`name`, mb.`unit`, ml.`order`, ml.`quantity`, ml.`expr`, ml.`mb_id`, ml.`gcl_id`, ml.`xmj_id`, ml.`mx_id`, ml.`ms_id`, ml.`tid`, ml.`mid`, msb.m_spread, ml.ms_id, ms.sid, ms.order as s_order' +
+                ' FROM ' + this.tableName + ' as ml' +
+                ' LEFT JOIN ' + this.ctx.service.materialBills.tableName + ' as mb ON ml.`mb_id` = mb.`id`' +
+                ' LEFT JOIN ' + this.ctx.service.materialStageBills.tableName + ' as msb ON ml.`mb_id` = msb.mb_id AND ml.ms_id = msb.ms_id And ml.mid = msb.mid' +
+                ' LEFT JOIN ' + this.ctx.service.materialStage.tableName + ' as ms ON ml.`ms_id` = ms.`id`' +
+                ' WHERE ml.`tid` = ? AND ml.`mid` = ?';
+            const sqlParam = [tid, mid];
+            return await this.db.query(sql, sqlParam);
+        }
+
         /**
          * 复制上一期并生成新一期清单工料关联,计算新一期小计值
          * @param transaction
@@ -432,10 +531,14 @@ module.exports = app => {
             try {
                 const list = [];
                 const listGcl = [];
+                const uplist = [];
+                const uplistGcl = [];
                 // const delList = [];
                 // const mb_idList = [];
+                const selfList = await transaction.select(this.ctx.service.materialListSelf.tableName, { where: { tid: this.ctx.tender.id, mid: this.ctx.material.id } });
+                const oldGclList = await transaction.select(this.ctx.service.materialListGcl.tableName, { where: { tid: this.ctx.tender.id } });
+                const oldMaterialList = await transaction.select(this.ctx.service.materialList.tableName, { where: { tid: this.ctx.tender.id, mid: this.ctx.material.id } });
                 for (const xmj of datas.xmjs) {
-                    const selfList = await transaction.select(this.ctx.service.materialListSelf.tableName, { where: { tid: this.ctx.tender.id, mid: this.ctx.material.id } });
                     for (const mb of datas.mbIds) {
                         // // 旧数据兼容问题,要去删除相同已存在的工料
                         // const mlInfo = await this.getDataByCondition({
@@ -449,33 +552,48 @@ module.exports = app => {
                         //     delList.push(mlInfo.id);
                         //     mb_idList.push(mb);
                         // }
+                        const mbId = typeof mb === 'object' ? mb.id : mb;
+                        const quantity = typeof mb === 'object' ? mb.quantity : 0;
                         if (xmj.gather_qty && this._.findIndex(selfList, { gcl_id: xmj.gcl_id, xmj_id: xmj.xmj_id, mx_id: xmj.mx_id }) === -1) {
-                            const newLists = {
-                                tid: this.ctx.tender.id,
-                                order: this.ctx.material.order,
-                                mid: this.ctx.material.id,
-                                mb_id: mb,
-                                gcl_id: xmj.gcl_id,
-                                xmj_id: xmj.xmj_id,
-                                mx_id: xmj.mx_id,
-                                ms_id: xmj.ms_id ? xmj.ms_id : null,
-                                gather_qty: xmj.gather_qty,
-                                in_time: new Date(),
-                                is_join: xmj.is_join,
-                            };
-                            list.push(newLists);
+                            const mlInfo = this._.find(oldMaterialList, { gcl_id: xmj.gcl_id, xmj_id: xmj.xmj_id, mx_id: xmj.mx_id, mb_id: mbId, ms_id: xmj.ms_id ? xmj.ms_id : null });
+                            if (mlInfo) {
+                                uplist.push({ id: mlInfo.id, quantity, expr: '' });
+                            } else {
+                                const newLists = {
+                                    tid: this.ctx.tender.id,
+                                    order: this.ctx.material.order,
+                                    mid: this.ctx.material.id,
+                                    mb_id: mbId,
+                                    gcl_id: xmj.gcl_id,
+                                    xmj_id: xmj.xmj_id,
+                                    mx_id: xmj.mx_id,
+                                    ms_id: xmj.ms_id ? xmj.ms_id : null,
+                                    gather_qty: xmj.gather_qty,
+                                    quantity,
+                                    in_time: new Date(),
+                                    is_join: xmj.is_join,
+                                };
+                                list.push(newLists);
+                            }
                         }
-                        if (this._.findIndex(listGcl, { gcl_id: xmj.gcl_id, mb_id: mb }) === -1) {
+                        const gclIndex = this._.findIndex(oldGclList, { gcl_id: xmj.gcl_id, mb_id: mbId });
+                        if (gclIndex === -1 && this._.findIndex(listGcl, { gcl_id: xmj.gcl_id, mb_id: mbId }) === -1) {
                             const newListGcl = {
                                 tid: this.ctx.tender.id,
                                 order: this.ctx.material.order,
                                 mid: this.ctx.material.id,
-                                mb_id: mb,
+                                mb_id: mbId,
                                 gcl_id: xmj.gcl_id,
-                                quantity: 0,
+                                quantity,
                                 expr: '',
                             };
                             listGcl.push(newListGcl);
+                        } else if (gclIndex !== -1 && this._.findIndex(uplistGcl, { id: oldGclList[gclIndex].id }) === -1) {
+                            uplistGcl.push({
+                                id: oldGclList[gclIndex].id,
+                                expr: '',
+                                quantity,
+                            });
                         }
                     }
                 }
@@ -497,6 +615,13 @@ module.exports = app => {
                         throw '新增工料关联数据失败';
                     }
                 }
+                // 覆盖
+                if (uplist.length > 0) {
+                    await transaction.updateRows(this.tableName, uplist);
+                }
+                if (uplistGcl.length > 0) {
+                    await transaction.updateRows(this.ctx.service.materialListGcl.tableName, uplistGcl);
+                }
                 if (checklist) {
                     await this.ctx.service.materialChecklist.updateHadBills(transaction, checklist.id, checklist.had_bills);
                 }
@@ -507,6 +632,9 @@ module.exports = app => {
                 //         await this.calcQuantityByML(transaction, select);
                 //     }
                 // }
+                if ((list.length > 0 || uplist.length > 0) && datas.export) {
+                    await this.calcAllQuantityByML(transaction, this._.map(datas.mbIds, 'id'));
+                }
                 await transaction.commit();
                 const gclList = await this.ctx.service.materialListGcl.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
                 return checklist ? gclList : {

+ 26 - 5
app/service/material_stage_bills.js

@@ -11,7 +11,7 @@ const auditConst = require('../const/audit').material;
 const materialConst = require('../const/material');
 const MaterialCalculator = require('../lib/material_calc');
 
-const needUpdateArray = ['quantity', 'msg_tp', 'msg_times', 'msg_spread', 'm_spread', 'm_tp', 'm_tax_tp', 'is_summary', 'remark'];
+const needUpdateArray = ['quantity', 'expr', 'msg_tp', 'msg_times', 'msg_spread', 'm_spread', 'm_tp', 'm_tax_tp', 'is_summary', 'remark'];
 
 module.exports = app => {
     class MaterialStageBills extends app.BaseService {
@@ -25,7 +25,7 @@ module.exports = app => {
             super(ctx);
             this.tableName = 'material_stage_bills';
         }
-        async insertBills(transaction, tid, mid, stage_id, materialStageData, decimal) {
+        async insertBills(transaction, tid, mid, stage_id, materialStageData, decimal, pre_is_stage_self = 0) {
             let m_tp = 0;
             let m_tax_tp = 0;
             // 复制工料表并生成
@@ -42,7 +42,7 @@ module.exports = app => {
                             ms_id: ms.id,
                             is_summary: b.is_summary,
                         };
-                        const [one_tp, one_tax_tp] = await this.calcQuantityByMB(transaction, mid, b, oneStageBillsData, materialCalculator, decimal);
+                        const [one_tp, one_tax_tp] = await this.calcQuantityByMB(transaction, mid, b, oneStageBillsData, materialCalculator, decimal, pre_is_stage_self);
                         insertStageBillsData.push(oneStageBillsData);
                         m_tp = this.ctx.helper.add(m_tp, one_tp);
                         m_tax_tp = this.ctx.helper.add(m_tax_tp, one_tax_tp);
@@ -59,6 +59,7 @@ module.exports = app => {
                     const oneBillsData = {
                         id: mb.id,
                         quantity: null,
+                        expr: null,
                         msg_tp: null,
                         msg_times: null,
                         msg_spread: newmsg_spread,
@@ -108,6 +109,25 @@ module.exports = app => {
             }
         }
 
+        // 批量添加工料时同步生成
+        async adds(transaction, billsList, remark = null) {
+            const insertList = [];
+            for (const sid of this.ctx.material.stage_id.split(',')) {
+                const msInfo = await transaction.get(this.ctx.service.materialStage.tableName, { tid: this.ctx.tender.id, sid });
+                for (const b of billsList) {
+                    const insertData = {
+                        tid: this.ctx.tender.id,
+                        mid: this.ctx.material.id,
+                        ms_id: msInfo.id,
+                        mb_id: b.id,
+                    };
+                    if (remark) insertData.remark = remark;
+                    insertList.push(insertData);
+                }
+            }
+            if (insertList.length > 0) await transaction.insert(this.tableName, insertList);
+        }
+
         // 单个修改工料时同步修改
         async update(transaction, data, ms_id) {
             const msbInfo = await transaction.get(this.tableName, { tid: this.ctx.tender.id, ms_id, mb_id: data.id });
@@ -132,7 +152,7 @@ module.exports = app => {
          * @param mb
          * @returns {Promise<*>}
          */
-        async calcQuantityByMB(transaction, mid, mb, msb, materialCalculator, decimal) {
+        async calcQuantityByMB(transaction, mid, mb, msb, materialCalculator, decimal, pre_is_stage_self) {
             const [newmsg_spread, newm_spread] = await this.getSpread(mb, null, decimal.up);
             if (mb.t_type === materialConst.t_type[0].value) {
                 const sql = 'SELECT SUM(`gather_qty`*`quantity`) as quantity FROM ' + this.ctx.service.materialList.tableName + ' WHERE `mid`=? AND `ms_id`=? AND `mb_id`=? AND `is_join`=1';
@@ -165,9 +185,10 @@ module.exports = app => {
                 const m_tax_tp = this.ctx.helper.round(this.ctx.helper.mul(m_tp, (1 + this.ctx.helper.div(mb.m_tax, 100))), decimal.tp);
                 return [m_tp, m_tax_tp];
             } else if (mb.t_type === materialConst.t_type[1].value) {
-                const quantity = await materialCalculator.calculateExpr(mb.expr);
+                const quantity = pre_is_stage_self ? 0 : await materialCalculator.calculateExpr(mb.expr);
                 const newTp = quantity !== 0 && quantity !== null ? this.ctx.helper.round(this.ctx.helper.mul(this.ctx.helper.round(quantity, decimal.qty), newm_spread), decimal.tp) : null;
                 msb.quantity = quantity !== 0 && quantity !== null ? this.ctx.helper.round(quantity, decimal.qty) : null;
+                msb.expr = mb.expr;
                 msb.msg_spread = newmsg_spread;
                 msb.m_spread = newm_spread;
                 msb.m_tp = newTp;

+ 26 - 3
app/service/project.js

@@ -11,12 +11,20 @@ const imType = require('../const/tender').imType;
 const defaultFunRela = {
     banOver: true,
     hintOver: true,
+    banMinusChangeBills: false,
     minusNoValue: true,
+    lockPayExpr: false,
     imType: imType.zl.value,
     needGcl: false,
 };
+const funSet = require('../const/fun_set');
+const defaultFunSet = funSet.defaultInfo;
 const sjsRelaConst = require('../const/setting').sjsRela;
 
+
+const infoConst = require('../const/tender_info');
+const parseInfo = infoConst.parseInfo;
+
 module.exports = app => {
 
     class Project extends app.BaseService {
@@ -57,7 +65,9 @@ module.exports = app => {
                         imType: {type: 'enum', values: [imType.tz.value, imType.zl.value, imType.bb.value, imType.bw.value], required: true},
                         banOver: {type: 'bool', required: true,},
                         hintOver: {type: 'bool', required: true,},
-                        minusNoValue: {type: 'bool', required: true,}
+                        banMinusChangeBills: {type: 'bool', required: true,},
+                        minusNoValue: {type: 'bool', required: true,},
+                        lockPayExpr: {type: 'bool', required: true,},
                     };
                     break;
                 default:
@@ -163,8 +173,8 @@ module.exports = app => {
         async updateFunRela(id, data) {
             const result = await this.db.update(this.tableName, {
                 id: id, fun_rela: JSON.stringify({
-                    banOver: data.banOver, hintOver: data.hintOver,
-                    imType: data.imType, needGcl: data.needGcl, minusNoValue: data.minusNoValue,
+                    banOver: data.banOver, hintOver: data.hintOver, banMinusChangeBills: data.banMinusChangeBills,
+                    imType: data.imType, needGcl: data.needGcl, minusNoValue: data.minusNoValue, lockPayExpr: data.lockPayExpr,
                 }),
             });
             return result.affectedRows === 1;
@@ -263,6 +273,19 @@ module.exports = app => {
             const result = await this.cache.get('pmDeal-' + pid);
             return result ? result.split(',') : [];
         }
+
+        async getFunSet(fun_set = null) {
+            const result = fun_set ? JSON.parse(fun_set) : {};
+            this.ctx.helper._.defaults(result, defaultFunSet);
+            return result;
+        }
+
+        async updateFunSet(id, funSet) {
+            const result = await this.db.update(this.tableName, {
+                id, fun_set: JSON.stringify(funSet),
+            });
+            return result.affectedRows === 1;
+        }
     }
 
     return Project;

+ 12 - 5
app/service/report.js

@@ -169,11 +169,6 @@ module.exports = app => {
                             runnableRst.push(service.reportMemory.getStageTempLand(params.tender_id, params.stage_id, memFieldKeys[filter]));
                             runnableKey.push(filter);
                             break;
-                        case 'mem_gather_stage_bills':
-                            runnableRst.push(service.rptGatherMemory.getGatherStageBills(memFieldKeys[filter],
-                                customDefine.gather_select, customSelect ? customSelect.gather_select : null));
-                            runnableKey.push(filter);
-                            break;
                         case 'mem_gather_tender_info':
                             runnableRst.push(service.rptGatherMemory.getGatherTenderInfo(memFieldKeys[filter],
                                 customDefine.gather_select, customSelect ? customSelect.gather_select : null));
@@ -217,6 +212,10 @@ module.exports = app => {
                             runnableRst.push(materialSource.getMaterialPos(params.tender_id, params.material_order, memFieldKeys[filter]));
                             runnableKey.push(filter);
                             break;
+                        case 'mem_material_stage':
+                            runnableRst.push(materialSource.getMaterialStage(params.tender_id, params.material_order, memFieldKeys[filter]));
+                            runnableKey.push(filter);
+                            break;
                         case 'mem_stage_sum_bills':
                             runnableRst.push(service.rptStageSumMemory.getStageSumBills(params.tender_id, memFieldKeys[filter],
                                 customDefine.stage_select, customSelect ? customSelect.stage_select : null));
@@ -370,6 +369,14 @@ module.exports = app => {
                         const jhHelper3 = new rptCustomData.jhHelper(this.ctx);
                         rst[filter] = await jhHelper3.gatherBills(memFieldKeys[filter], customDefine.gather_select, customSelect ? customSelect.gather_select : null);
                         break;
+                    case 'mem_gather_stage_bills':
+                        rst[filter] = await service.rptGatherMemory.getGatherStageBills(memFieldKeys[filter],
+                            customDefine.gather_select, customSelect ? customSelect.gather_select : null);
+                        break;
+                    case 'mem_gather_stage_pos':
+                        rst[filter] = await service.rptGatherMemory.getGatherStagePos(memFieldKeys[filter],
+                            customDefine.gather_select, customSelect ? customSelect.gather_select : null);
+                        break;
                     // case 'mem_material_bills':
                     //     rst[filter] = await service.rptGatherMemory.getMaterialBills(params.tender_id, params.material_order, memFieldKeys[filter]);
                     //     break;

+ 123 - 7
app/service/report_memory.js

@@ -24,11 +24,14 @@ const stageImVersion = '1.3';
 const PermissionCheck = require('../const/account_permission').PermissionCheck;
 
 const Ledger = require('../lib/ledger');
+const moment = require('moment');
+const auditConst = require('../const/audit');
 
 const billsFields = (function () {
     const cur = ['contract_qty', 'contract_tp', 'contract_expr', 'qc_qty', 'qc_tp', 'qc_minus_qty', 'qc_minus_tp', 'gather_qty', 'gather_tp', 'postil'];
     const pre = ['pre_contract_qty', 'pre_contract_tp', 'pre_qc_qty', 'pre_qc_tp', 'pre_qc_minus_qty', 'pre_qc_minus_tp', 'pre_gather_qty', 'pre_gather_tp'];
     const end = ['end_contract_qty', 'end_contract_tp', 'end_qc_qty', 'end_qc_tp', 'end_qc_minus_qty', 'end_qc_minus_tp', 'end_gather_qty', 'end_gather_tp'];
+    const year = ['year_contract_qty', 'year_contract_tp', 'year_qc_qty', 'year_qc_tp', 'year_qc_minus_qty', 'year_qc_minus_tp', 'year_gather_qty', 'year_gather_tp'];
     const final = ['final_tp', 'final_ratio'];
     const final1 = [ 'final_1_qty', 'final_1_tp', 'final_1_ratio'];
     const stageDgn = ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'];
@@ -107,7 +110,12 @@ module.exports = app => {
                 rootId: -1,
                 keys: ['id', 'tender_id', 'ledger_id'],
                 stageId: 'id',
-                calcFields: calcFields || ['deal_tp', 'total_price', 'contract_tp', 'qc_tp', 'contract_pc_tp', 'qc_pc_tp', 'pc_tp', 'gather_tp', 'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'final_1_tp'],
+                calcFields: calcFields || ['deal_tp', 'total_price',
+                    'contract_tp', 'qc_tp', 'contract_pc_tp', 'qc_pc_tp', 'pc_tp', 'gather_tp',
+                    'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'final_1_tp',
+                    'end_contract_pc_tp', 'end_qc_pc_tp', 'end_pc_tp',
+                    'year_contract_tp', 'year_qc_tp', 'year_contract_pc_tp', 'year_qc_pc_tp', 'year_pc_tp', 'year_gather_tp',
+                ],
                 calc: function (node, helper, decimal) {
                     if (node.children && node.children.length === 0) {
                         node.pre_gather_qty = helper.add(node.pre_contract_qty, node.pre_qc_qty);
@@ -378,6 +386,88 @@ module.exports = app => {
             }
         }
 
+        async _getValidStages(tenderId) {
+            const stages = await this.db.select(this.ctx.service.stage.tableName, {
+                where: { tid: tenderId },
+                orders: [['order', 'desc']],
+            });
+            if (stages.length !== 0) {
+                const lastStage = stages[0];
+                if (lastStage.status === auditConst.stage.status.uncheck && lastStage.user_id !== this.ctx.session.sessionUser.accountId) {
+                    stages.splice(0, 1);
+                }
+            }
+            return stages;
+        }
+
+        async _getTimeZoneStages(tender, zone) {
+            const times = zone.split(' - ');
+            if (times.length !== 2) throw '选择的汇总周期无效';
+            const beginTime = moment(times[0], 'YYYY-MM');
+            const endTime = moment(times[1], 'YYYY-MM');
+
+            const stages = await this._getValidStages(tender.id), validStages = [];
+            for (const stage of stages) {
+                const sTime = moment(stage.s_time, 'YYYY-MM');
+                if (sTime.isBetween(beginTime, endTime, null, '[]')) {
+                    validStages.push(stage);
+                }
+            }
+            return validStages;
+        }
+
+        async _gatherStageBills(tender, stages) {
+            let billsIndexData = {};
+            const helper = this.ctx.helper;
+            const sumAssignRelaData = function (index, rela) {
+                const loadFields = function (datas, fields, prefix, relaId) {
+                    for (const d of datas) {
+                        if (!index[d[relaId]]) index[d[relaId]] = {};
+                        const m = index[d[relaId]];
+                        m[relaId] = d[relaId];
+                        if (m) {
+                            for (const f of fields) {
+                                if (d[f] !== undefined) {
+                                    m[prefix + f] = helper.add(m[prefix + f], d[f]);
+                                }
+                            }
+                        }
+                    }
+                };
+                for (const r of rela) {
+                    loadFields(r.data, r.fields, r.prefix, r.relaId);
+                }
+            };
+            for (const stage of stages) {
+                await this.ctx.service.stage.doCheckStage(stage);
+                const curStage = stage.readOnly
+                    ? await this.ctx.service.stageBills.getAuditorStageData2(tender.id, stage.id, stage.curTimes, stage.curOrder)
+                    : await this.ctx.service.stageBills.getLastestStageData2(tender.id, stage.id);
+                const bpcStage = await this.ctx.service.stageBillsPc.getAllDataByCondition({ where: { sid: stage.id } });
+                sumAssignRelaData(billsIndexData, [
+                    { data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'qc_minus_qty'], prefix: '', relaId: 'lid' },
+                    { data: bpcStage, fields: ['contract_pc_tp', 'qc_pc_tp', 'pc_tp'], prefix: '', relaId: 'lid' },
+                ]);
+            }
+            const result = [];
+            for (const prop in billsIndexData) {
+                const yearStageBills = billsIndexData[prop];
+                yearStageBills.gather_qty = helper.add(yearStageBills.contract_qty, yearStageBills.qc_qty);
+                yearStageBills.gather_tp = helper.sum([yearStageBills.contract_tp, yearStageBills.qc_tp, yearStageBills.pc_tp]);
+                result.push(yearStageBills);
+            }
+            return result;
+        };
+
+        async _loadStageBillsZone(tender, zone){
+            const stages = await this._getTimeZoneStages(tender, zone);
+            return await this._gatherStageBills(tender, stages);
+        }
+
+        async _loadStageBillsYear(tender, year) {
+            return await this._loadStageBillsZone(tender, `${year}-01 - ${year}-12`);
+        }
+
         async getStageBillsData(tid, sid, fields) {
             try {
                 await this.ctx.service.tender.checkTender(tid);
@@ -385,7 +475,9 @@ module.exports = app => {
                     await this.ctx.service.stage.checkStage(sid);
                 }
 
-                const billsData = await this.ctx.service.ledger.getData(this.ctx.tender.id);
+                const billsData = this.ctx.stage.ledgerHis
+                    ? await this.ctx.helper.loadLedgerDataFromOss(this.ctx.stage.ledgerHis.bills_file)
+                    : await this.ctx.service.ledger.getData(this.ctx.tender.id);
                 if (this._checkFieldsExist(fields, billsFields.stageDgn)) {
                     const dgnData = await this.ctx.service.stageBillsDgn.getDgnData(this.ctx.tender.id);
                     for (const d of dgnData) {
@@ -407,12 +499,25 @@ module.exports = app => {
                 }
                 const bpcStage = await this.ctx.service.stageBillsPc.getAllDataByCondition({ where: { sid: this.ctx.stage.id } });
                 const endBpcStage = await this.ctx.service.stageBillsPc.getEndStageData(this.ctx.stage);
+                const yearStage = this._checkFieldsExistReg(fields, 'year_') && this.ctx.stage.s_time
+                    ? await this._loadStageBillsYear(this.ctx.tender, this.ctx.stage.s_time.split('-')[0])
+                    : [];
                 this.ctx.helper.assignRelaData(billsData, [
                     { data: curStage, fields: ['contract_qty', 'contract_tp', 'contract_expr', 'qc_qty', 'qc_tp', 'qc_minus_qty', 'postil'], prefix: '', relaId: 'lid' },
                     { data: preStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'qc_minus_qty'], prefix: 'pre_', relaId: 'lid' },
-                    { data: bpcStage, fields: ['contract_pc_tp', 'qc_pc_tp', 'pc_tp'], prefix: '', relaId: 'lid' },
-                    { data: endBpcStage, fields: ['end_contract_pc_tp', 'end_qc_pc_tp', 'end_pc_tp'], prefix: '', relaId: 'lid' },
+                    { data: bpcStage, fields: ['contract_pc_tp', 'qc_pc_tp', 'pc_tp', 'org_price'], prefix: '', relaId: 'lid' },
+                    { data: endBpcStage, fields: ['end_contract_pc_tp', 'end_qc_pc_tp', 'end_pc_tp', 'org_price_his'], prefix: '', relaId: 'lid' },
+                    { data: yearStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'qc_minus_qty', 'contract_pc_tp', 'qc_pc_tp', 'pc_tp', 'gather_qty', 'gather_tp'], prefix: 'year_', relaId: 'lid' },
                 ]);
+                billsData.forEach(x => {
+                    if (x.org_price_his && x.org_price_his.length > 0) {
+                        for (const prop of x.org_price_his) {
+                            x[`org_price_${prop}`] = x.org_price_his[prop];
+                        }
+                    } else {
+                        x.org_price_0 = x.unit_price;
+                    }
+                });
 
                 const billsTree = this._getNewBillsTree();
                 billsTree.loadDatas(billsData);
@@ -526,7 +631,9 @@ module.exports = app => {
 
                 const stage = this.ctx.stage, helper = this.ctx.helper;
                 const validRole = this.stageValidRole;
-                const billsData = await this.ctx.service.ledger.getData(this.ctx.tender.id);
+                const billsData = this.ctx.stage.ledgerHis
+                    ? await this.ctx.helper.loadLedgerDataFromOss(this.ctx.stage.ledgerHis.bills_file)
+                    : await this.ctx.service.ledger.getData(this.ctx.tender.id);
                 const allStageBills = await this.ctx.service.stageBills.getAllDataByCondition({where: {sid: sid}});
 
                 const stageBillsIndex = {}, timesLen = 100;
@@ -561,9 +668,18 @@ module.exports = app => {
 
                 this.ctx.helper.assignRelaData(billsData, [
                     {data: preStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'qc_minus_qty'], prefix: 'pre_', relaId: 'lid'},
-                    { data: bpcStage, fields: ['contract_pc_tp', 'qc_pc_tp', 'pc_tp'], prefix: '', relaId: 'lid' },
-                    { data: endBpcStage, fields: ['end_contract_pc_tp', 'end_qc_pc_tp', 'end_pc_tp'], prefix: '', relaId: 'lid' },
+                    { data: bpcStage, fields: ['contract_pc_tp', 'qc_pc_tp', 'pc_tp', 'org_price'], prefix: '', relaId: 'lid' },
+                    { data: endBpcStage, fields: ['end_contract_pc_tp', 'end_qc_pc_tp', 'end_pc_tp', 'org_price_his'], prefix: '', relaId: 'lid' },
                 ]);
+                billsData.forEach(x => {
+                    if (x.org_price_his && x.org_price_his.length > 0) {
+                        for (const prop of x.org_price_his) {
+                            x[`org_price_${prop}`] = x.org_price_his[prop];
+                        }
+                    } else {
+                        x.org_price_0 = x.unit_price;
+                    }
+                });
                 const billsTree = this._getNewBillsTree();
                 billsTree.loadDatas(billsData);
 

+ 6 - 3
app/service/revise_audit.js

@@ -15,6 +15,7 @@ const wxConst = require('../const/wechat_template');
 const shenpiConst = require('../const/shenpi');
 const pushType = require('../const/audit').pushType;
 const RevisePrice = require('../lib/revise_price');
+const pushOperate = require('../const/spec_3f').pushOperate;
 
 module.exports = app => {
     class ReviseAudit extends app.BaseService {
@@ -232,7 +233,7 @@ module.exports = app => {
             const time = new Date();
 
             // 拷贝备份台账数据
-            const his_id = await this.ctx.service.ledgerHistory.backupReviseLedgerHistory(revise);
+            const [his_id, sum] = await this.ctx.service.ledgerHistory.backupReviseLedgerHistory(revise);
 
             const transaction = await this.db.beginTransaction();
             try {
@@ -246,7 +247,7 @@ module.exports = app => {
                 const reviseData = {
                     id: revise.id,
                     status: auditConst.status.checking,
-                    his_id,
+                    his_id, sum: JSON.stringify(sum),
                 };
                 if (revise.times === 1) {
                     reviseData.begin_time = time;
@@ -453,7 +454,7 @@ module.exports = app => {
                         // 重算台账、计量、工程变更
                         const reviseCalc = new RevisePrice(this.ctx);
                         await reviseCalc.calcRevise(revise, transaction);
-                        const sum = await this.ctx.service.reviseBills.addUp({
+                        const sum = revise.sum || await this.ctx.service.reviseBills.addUp({
                             tender_id: revise.tid, /* , is_leaf: true*/
                         });
                         await transaction.update(this.ctx.service.tender.tableName, {
@@ -493,6 +494,8 @@ module.exports = app => {
                             code: this.ctx.session.sessionProject.code,
                         };
                         await this.ctx.helper.sendWechat(users, smsTypeConst.const.XD, smsTypeConst.judge.result.toString(), wxConst.template.revise, wechatData2);
+                        // 审批通过 - 检查三方特殊推送
+                        await this.ctx.service.specMsg.addReviseMsg(transaction, pid, revise, pushOperate.ledger.checked);
                     }
                 } else {
                     // 同步修订信息

+ 2 - 0
app/service/revise_price.js

@@ -69,6 +69,8 @@ module.exports = app => {
                     nd.valid = !this.ctx.helper.checkZero(this.ctx.helper.sub(nd.new_price, d.org_price));
                 }
                 if (d.memo !== undefined) nd.memo = d.memo;
+                if (d.rela_lid !== undefined) nd.rela_lid = d.rela_lid;
+                if (d.rela_cid !== undefined) nd.rela_cid = d.rela_cid;
                 uDatas.push(nd);
             }
             if (uDatas.length > 0) {

+ 421 - 179
app/service/rpt_gather_memory.js

@@ -67,37 +67,38 @@ const gatherUtils = {
         gatherNode[prefix + "c_dgn_qty1"] = helper.add(gatherNode[prefix + "c_dgn_qty1"], sourceNode.c_dgn_qty1);
         gatherNode[prefix + "c_dgn_qty2"] = helper.add(gatherNode[prefix + "c_dgn_qty2"], sourceNode.c_dgn_qty2);
 
-        gatherNode['s_' + "qty"] = helper.add(gatherNode['s_' + "qty"], sourceNode.quantity);
-        gatherNode['s_' + "tp"] = helper.add(gatherNode['s_' + "tp"], sourceNode.total_price);
-
-        gatherNode['s_' + "dgn_qty1"] = helper.add(gatherNode['s_' + "dgn_qty1"], sourceNode.dgn_qty1);
-        gatherNode['s_' + "dgn_qty2"] = helper.add(gatherNode['s_' + "dgn_qty2"], sourceNode.dgn_qty2);
-
-        gatherNode['s_' + "contract_qty"] = helper.add(gatherNode['s_' + "contract_qty"], sourceNode.contract_qty);
-        gatherNode['s_' + "contract_tp"] = helper.add(gatherNode['s_' + "contract_tp"], sourceNode.contract_tp);
-        gatherNode['s_' + "qc_qty"] = helper.add(gatherNode['s_' + "qc_qty"], sourceNode.qc_qty);
-        gatherNode['s_' + "qc_tp"] = helper.add(gatherNode['s_' + "qc_tp"], sourceNode.qc_tp);
-        gatherNode['s_' + "gather_qty"] = helper.add(gatherNode['s_' + "gather_qty"], sourceNode.gather_qty);
-        gatherNode['s_' + "gather_tp"] = helper.add(gatherNode['s_' + "gather_tp"], sourceNode.gather_tp);
-
-        gatherNode['s_' + "pre_contract_qty"] = helper.add(gatherNode['s_' + "pre_contract_qty"], sourceNode.pre_contract_qty);
-        gatherNode['s_' + "pre_contract_tp"] = helper.add(gatherNode['s_' + "pre_contract_tp"], sourceNode.pre_contract_tp);
-        gatherNode['s_' + "pre_qc_qty"] = helper.add(gatherNode['s_' + "pre_qc_qty"], sourceNode.pre_qc_qty);
-        gatherNode['s_' + "pre_qc_tp"] = helper.add(gatherNode['s_' + "pre_qc_tp"], sourceNode.pre_qc_tp);
-        gatherNode['s_' + "pre_gather_qty"] = helper.add(gatherNode['s_' + "pre_gather_qty"], sourceNode.pre_gather_qty);
-        gatherNode['s_' + "pre_gather_tp"] = helper.add(gatherNode['s_' + "pre_gather_tp"], sourceNode.pre_gather_tp);
-
-        gatherNode['s_' + "end_contract_qty"] = helper.add(gatherNode['s_' + "end_contract_qty"], sourceNode.end_contract_qty);
-        gatherNode['s_' + "end_contract_tp"] = helper.add(gatherNode['s_' + "end_contract_tp"], sourceNode.end_contract_tp);
-        gatherNode['s_' + "end_qc_qty"] = helper.add(gatherNode['s_' + "end_qc_qty"], sourceNode.end_qc_qty);
-        gatherNode['s_' + "end_qc_tp"] = helper.add(gatherNode['s_' + "end_qc_tp"], sourceNode.end_qc_tp);
-        gatherNode['s_' + "end_gather_qty"] = helper.add(gatherNode['s_' + "end_gather_qty"], sourceNode.end_gather_qty);
-        gatherNode['s_' + "end_gather_tp"] = helper.add(gatherNode['s_' + "end_gather_tp"], sourceNode.end_gather_tp);
-
-        gatherNode['s_' + "deal_dgn_qty1"] = helper.add(gatherNode['s_' + "deal_dgn_qty1"], sourceNode.deal_dgn_qty1);
-        gatherNode['s_' + "deal_dgn_qty2"] = helper.add(gatherNode['s_' + "deal_dgn_qty2"], sourceNode.deal_dgn_qty2);
-        gatherNode['s_' + "c_dgn_qty1"] = helper.add(gatherNode['s_' + "c_dgn_qty1"], sourceNode.c_dgn_qty1);
-        gatherNode['s_' + "c_dgn_qty2"] = helper.add(gatherNode['s_' + "c_dgn_qty2"], sourceNode.c_dgn_qty2);
+        gatherNode.s_qty = helper.add(gatherNode.s_qty, sourceNode.quantity);
+        gatherNode.s_tp = helper.add(gatherNode.s_tp, sourceNode.total_price);
+
+        gatherNode.s_dgn_qty1 = helper.add(gatherNode.s_dgn_qty1, sourceNode.dgn_qty1);
+        gatherNode.s_dgn_qty2 = helper.add(gatherNode.s_dgn_qty2, sourceNode.dgn_qty2);
+
+        gatherNode.s_contract_qty = helper.add(gatherNode.s_contract_qty, sourceNode.contract_qty);
+        gatherNode.s_contract_tp = helper.add(gatherNode.s_contract_tp, sourceNode.contract_tp);
+        gatherNode.s_qc_qty = helper.add(gatherNode.s_qc_qty, sourceNode.qc_qty);
+        gatherNode.s_qc_tp = helper.add(gatherNode.s_qc_tp, sourceNode.qc_tp);
+        gatherNode.s_gather_qty = helper.add(gatherNode.s_gather_qty, sourceNode.gather_qty);
+        gatherNode.s_gather_tp = helper.add(gatherNode.s_gather_tp, sourceNode.gather_tp);
+        gatherNode.s_pc_tp = helper.add(gatherNode.s_pc_tp, sourceNode.pc_tp);
+
+        gatherNode.s_pre_contract_qty = helper.add(gatherNode.s_pre_contract_qty, sourceNode.pre_contract_qty);
+        gatherNode.s_pre_contract_tp = helper.add(gatherNode.s_pre_contract_tp, sourceNode.pre_contract_tp);
+        gatherNode.s_pre_qc_qty = helper.add(gatherNode.s_pre_qc_qty, sourceNode.pre_qc_qty);
+        gatherNode.s_pre_qc_tp = helper.add(gatherNode.s_pre_qc_tp, sourceNode.pre_qc_tp);
+        gatherNode.s_pre_gather_qty = helper.add(gatherNode.s_pre_gather_qty, sourceNode.pre_gather_qty);
+        gatherNode.s_pre_gather_tp = helper.add(gatherNode.s_pre_gather_tp, sourceNode.pre_gather_tp);
+
+        gatherNode.s_end_contract_qty = helper.add(gatherNode.s_end_contract_qty, sourceNode.end_contract_qty);
+        gatherNode.s_end_contract_tp = helper.add(gatherNode.s_end_contract_tp, sourceNode.end_contract_tp);
+        gatherNode.s_end_qc_qty = helper.add(gatherNode.s_end_qc_qty, sourceNode.end_qc_qty);
+        gatherNode.s_end_qc_tp = helper.add(gatherNode.s_end_qc_tp, sourceNode.end_qc_tp);
+        gatherNode.s_end_gather_qty = helper.add(gatherNode.s_end_gather_qty, sourceNode.end_gather_qty);
+        gatherNode.s_end_gather_tp = helper.add(gatherNode.s_end_gather_tp, sourceNode.end_gather_tp);
+
+        gatherNode.s_deal_dgn_qty1 = helper.add(gatherNode.s_deal_dgn_qty1, sourceNode.deal_dgn_qty1);
+        gatherNode.s_deal_dgn_qty2 = helper.add(gatherNode.s_deal_dgn_qty2, sourceNode.deal_dgn_qty2);
+        gatherNode.s_c_dgn_qty1 = helper.add(gatherNode.s_c_dgn_qty1, sourceNode.c_dgn_qty1);
+        gatherNode.s_c_dgn_qty2 = helper.add(gatherNode.s_c_dgn_qty2, sourceNode.c_dgn_qty2);
     },
     gatherZone: function (tender, gatherNode, sourceNode, prefix, helper) {
         gatherNode[prefix + 'id'] = tender.id;
@@ -123,15 +124,16 @@ const gatherUtils = {
         gatherNode[prefix + "c_dgn_qty1"] = helper.add(gatherNode[prefix + "c_dgn_qty1"], sourceNode.c_dgn_qty1);
         gatherNode[prefix + "c_dgn_qty2"] = helper.add(gatherNode[prefix + "c_dgn_qty2"], sourceNode.c_dgn_qty2);
 
-        gatherNode['s_' + "qty"] = helper.add(gatherNode['s_' + "qty"], sourceNode.quantity);
-        gatherNode['s_' + "tp"] = helper.add(gatherNode['s_' + "tp"], sourceNode.total_price);
+        gatherNode.s_qty = helper.add(gatherNode.s_qty, sourceNode.quantity);
+        gatherNode.s_tp = helper.add(gatherNode.s_tp, sourceNode.total_price);
 
-        gatherNode['s_' + "contract_qty"] = helper.add(gatherNode['s_' + "contract_qty"], sourceNode.contract_qty);
-        gatherNode['s_' + "contract_tp"] = helper.add(gatherNode['s_' + "contract_tp"], sourceNode.contract_tp);
-        gatherNode['s_' + "qc_qty"] = helper.add(gatherNode['s_' + "qc_qty"], sourceNode.qc_qty);
-        gatherNode['s_' + "qc_tp"] = helper.add(gatherNode['s_' + "qc_tp"], sourceNode.qc_tp);
-        gatherNode['s_' + "gather_qty"] = helper.add(gatherNode['s_' + "gather_qty"], sourceNode.gather_qty);
-        gatherNode['s_' + "gather_tp"] = helper.add(gatherNode['s_' + "gather_tp"], sourceNode.gather_tp);
+        gatherNode.s_contract_qty = helper.add(gatherNode.s_contract_qty, sourceNode.contract_qty);
+        gatherNode.s_contract_tp = helper.add(gatherNode.s_contract_tp, sourceNode.contract_tp);
+        gatherNode.s_qc_qty = helper.add(gatherNode.s_qc_qty, sourceNode.qc_qty);
+        gatherNode.s_qc_tp = helper.add(gatherNode.s_qc_tp, sourceNode.qc_tp);
+        gatherNode.s_gather_qty = helper.add(gatherNode.s_gather_qty, sourceNode.gather_qty);
+        gatherNode.s_gather_tp = helper.add(gatherNode.s_gather_tp, sourceNode.gather_tp);
+        gatherNode.s_pc_tp = helper.add(gatherNode.s_pc_tp, sourceNode.pc_tp);
     },
     gatherLedger: function (tender, gatherNode, sourceNode, prefix, helper) {
         gatherNode[prefix + 'id'] = tender.id;
@@ -144,8 +146,8 @@ const gatherUtils = {
         gatherNode[prefix + "dgn_qty1"] = helper.add(gatherNode[prefix + "dgn_qty1"], sourceNode.dgn_qty1);
         gatherNode[prefix + "dgn_qty2"] = helper.add(gatherNode[prefix + "dgn_qty2"], sourceNode.dgn_qty2);
 
-        gatherNode['s_' + "qty"] = helper.add(gatherNode['s_' + "qty"], sourceNode.quantity);
-        gatherNode['s_' + "tp"] = helper.add(gatherNode['s_' + "tp"], sourceNode.total_price);
+        gatherNode.s_qty = helper.add(gatherNode.s_qty, sourceNode.quantity);
+        gatherNode.s_tp = helper.add(gatherNode.s_tp, sourceNode.total_price);
     },
     gatherSpecial: function (gatherNode, sourceNode, prefix, helper) {
         gatherNode[prefix + "qty"] = helper.add(gatherNode[prefix + "qty"], sourceNode.quantity);
@@ -154,6 +156,68 @@ const gatherUtils = {
         gatherNode[prefix + "dgn_qty1"] = helper.add(gatherNode[prefix + "dgn_qty1"], sourceNode.dgn_qty1);
         gatherNode[prefix + "dgn_qty2"] = helper.add(gatherNode[prefix + "dgn_qty2"], sourceNode.dgn_qty2);
     },
+    gatherStagePos: function (tender, gatherNode, sourceNode, prefix, helper) {
+        gatherNode[prefix + 'id'] = tender.id;
+        gatherNode[prefix + 'name'] = tender.name;
+        gatherNode[prefix + 'category'] = tender.category;
+
+        gatherNode[prefix + "qty"] = helper.add(gatherNode[prefix + "qty"], sourceNode.quantity);
+
+        gatherNode[prefix + "contract_qty"] = helper.add(gatherNode[prefix + "contract_qty"], sourceNode.contract_qty);
+        gatherNode[prefix + "qc_qty"] = helper.add(gatherNode[prefix + "qc_qty"], sourceNode.qc_qty);
+        gatherNode[prefix + "gather_qty"] = helper.add(gatherNode[prefix + "gather_qty"], sourceNode.gather_qty);
+
+        gatherNode[prefix + "pre_contract_qty"] = helper.add(gatherNode[prefix + "pre_contract_qty"], sourceNode.pre_contract_qty);
+        gatherNode[prefix + "pre_qc_qty"] = helper.add(gatherNode[prefix + "pre_qc_qty"], sourceNode.pre_qc_qty);
+        gatherNode[prefix + "pre_gather_qty"] = helper.add(gatherNode[prefix + "pre_gather_qty"], sourceNode.pre_gather_qty);
+
+        gatherNode[prefix + "end_contract_qty"] = helper.add(gatherNode[prefix + "end_contract_qty"], sourceNode.end_contract_qty);
+        gatherNode[prefix + "end_qc_qty"] = helper.add(gatherNode[prefix + "end_qc_qty"], sourceNode.end_qc_qty);
+        gatherNode[prefix + "end_gather_qty"] = helper.add(gatherNode[prefix + "end_gather_qty"], sourceNode.end_gather_qty);
+
+        gatherNode.s_qty = helper.add(gatherNode.s_qty, sourceNode.quantity);
+
+        gatherNode.s_contract_qty = helper.add(gatherNode.s_contract_qty, sourceNode.contract_qty);
+        gatherNode.s_qc_qty = helper.add(gatherNode.s_qc_qty, sourceNode.qc_qty);
+        gatherNode.s_gather_qty = helper.add(gatherNode.s_gather_qty, sourceNode.gather_qty);
+
+        gatherNode.s_pre_contract_qty = helper.add(gatherNode.s_pre_contract_qty, sourceNode.pre_contract_qty);
+        gatherNode.s_pre_qc_qty = helper.add(gatherNode.s_pre_qc_qty, sourceNode.pre_qc_qty);
+        gatherNode.s_pre_gather_qty = helper.add(gatherNode.s_pre_gather_qty, sourceNode.pre_gather_qty);
+
+        gatherNode.s_end_contract_qty = helper.add(gatherNode.s_end_contract_qty, sourceNode.end_contract_qty);
+        gatherNode.s_end_qc_qty = helper.add(gatherNode.s_end_qc_qty, sourceNode.end_qc_qty);
+        gatherNode.s_end_gather_qty = helper.add(gatherNode.s_end_gather_qty, sourceNode.end_gather_qty);
+    },
+    gatherZonePos: function (tender, gatherNode, sourceNode, prefix, helper) {
+        gatherNode[prefix + 'id'] = tender.id;
+        gatherNode[prefix + 'name'] = tender.name;
+        gatherNode[prefix + 'category'] = tender.category;
+
+        gatherNode[prefix + "qty"] = helper.add(gatherNode[prefix + "qty"], sourceNode.quantity);
+
+        gatherNode[prefix + "contract_qty"] = helper.add(gatherNode[prefix + "contract_qty"], sourceNode.contract_qty);
+        gatherNode[prefix + "qc_qty"] = helper.add(gatherNode[prefix + "qc_qty"], sourceNode.qc_qty);
+        gatherNode[prefix + "gather_qty"] = helper.add(gatherNode[prefix + "gather_qty"], sourceNode.gather_qty);
+
+        gatherNode.s_qty = helper.add(gatherNode.s_qty, sourceNode.quantity);
+
+        gatherNode.s_contract_qty = helper.add(gatherNode.s_contract_qty, sourceNode.contract_qty);
+        gatherNode.s_qc_qty = helper.add(gatherNode.s_qc_qty, sourceNode.qc_qty);
+        gatherNode.s_gather_qty = helper.add(gatherNode.s_gather_qty, sourceNode.gather_qty);
+    },
+    gatherPos: function (tender, gatherNode, sourceNode, prefix, helper) {
+        gatherNode[prefix + 'id'] = tender.id;
+        gatherNode[prefix + 'name'] = tender.name;
+        gatherNode[prefix + 'category'] = tender.category;
+
+        gatherNode[prefix + "qty"] = helper.add(gatherNode[prefix + "qty"], sourceNode.quantity);
+
+        gatherNode.s_qty = helper.add(gatherNode.s_qty, sourceNode.quantity);
+    },
+    gatherSpecialPos: function (gatherNode, sourceNode, prefix, helper) {
+        gatherNode[prefix + "qty"] = helper.add(gatherNode[prefix + "qty"], sourceNode.quantity);
+    },
 };
 
 module.exports = app => {
@@ -167,22 +231,17 @@ module.exports = app => {
          */
         constructor(ctx) {
             super(ctx);
-            this.resultTree = new Ledger.gatherTree(ctx, {
-                id: 'id',
-                pid: 'pid',
-                order: 'order',
-                level: 'level',
-                rootId: -1
-            });
+            this.resultTree = null;
+            this.resultPos = null;
             this.resultTenderInfo = [];
             this.resultDealPay = [];
             this.resultDealBills = [];
         }
 
-        async _getValidStages(tenderId) {
+        async _getValidStages(tenderId, sort = 'desc') {
             const stages = await this.db.select(this.ctx.service.stage.tableName, {
                 where: { tid: tenderId },
-                orders: [['order', 'desc']],
+                orders: [['order', sort]],
             });
             if (stages.length !== 0) {
                 const lastStage = stages[0];
@@ -222,36 +281,97 @@ module.exports = app => {
             const beginTime = moment(times[0], 'YYYY-MM');
             const endTime = moment(times[1], 'YYYY-MM');
 
-            const stages = await this._getValidStages(tender.id), validStages = [];
+            const stages = await this._getValidStages(tender.id, 'asc'), validStages = [];
+            let preStage, endStage;
             for (const stage of stages) {
                 const sTime = moment(stage.s_time, 'YYYY-MM');
                 if (sTime.isBetween(beginTime, endTime, null, '[]')) {
                     validStages.push(stage);
+                } else if (sTime.isBefore(beginTime)) {
+                    if (!preStage || moment(preStage.s_time, 'YYYY-MM').isBefore(sTime)) preStage = stage;
+                } else if (sTime.isAfter(endTime)) {
+                    if (!endStage || moment(endStage.s_time, 'YYYY-MM').isAfter(sTime)) endStage = stage;
                 }
             }
-            return validStages;
+            return [validStages, preStage, endStage];
         }
-
         async _getOrderZoneStages (tender, zone) {
             let [iBegin, iEnd] = zone.split(':');
             iBegin = this.ctx.helper._.toInteger(iBegin) || 0;
             iEnd = this.ctx.helper._.toInteger(iEnd) || 0;
-            const stages = await this._getValidStages(tender.id), validStages = [];
+            const stages = await this._getValidStages(tender.id, 'asc'), validStages = [];
+            let preStage, endStage;
             for (const stage of stages) {
-                if (stage.order < iBegin || stage.order > iEnd) continue;
+                if (stage.order < iBegin) {
+                    if (!preStage || preStage.order < stage.order) preStage = stage;
+                } else if (stage.order > iEnd) {
+                    if (!endStage || endStage.order > stage.order) endStage = stage;
+                } else {
+                    validStages.push(stage);
+                }
+            }
+            return [validStages, preStage, endStage];
+        }
+        async _getCheckedZoneStages(tender, zone) {
+            const times = zone.split(' - ');
+            if (times.length !== 2) throw '选择的汇总周期无效';
+            const beginTime = moment(times[0], 'YYYY-MM-DD');
+            const endTime = moment(times[1], 'YYYY-MM-DD');
+
+            const stages = await this._getValidStages(tender.id, 'asc'), validStages = [];
+            let preStage, endStage;
+            for (const stage of stages) {
+                if (stage.status !== auditConst.stage.status.checked) continue;
+
+                const finalAudit = await this.ctx.service.stageAudit.getLastestAuditor(stage.id, stage.times, stage.status);
+                if (!finalAudit) continue;
 
-                validStages.push(stage);
+                const sTime = moment(moment(finalAudit.end_time).format('YYYY-MM-DD'), 'YYYY-MM-DD');
+                stage.checked_day = sTime;
+                stage.checked_time = moment(finalAudit.end_time);
+                if (sTime.isBetween(beginTime, endTime, null, '[]')) {
+                    validStages.push(stage);
+                } else if (sTime.isBefore(beginTime)) {
+                    if (!preStage || preStage.checked_time.isBefore(stage.checked_time)) preStage = stage;
+                } else if (sTime.isAfter(endTime)) {
+                    if (!endStage || endStage.checked_time.isAfter(stage.checked_time)) endStage = stage;
+                }
             }
-            return validStages;
+            return [validStages, preStage, endStage];
         }
 
         /**
          * 台账数据
          */
-        async _gatherStageData(completeData, tender, stage, hasPre) {
+
+        async _gatherStagesData(completeData, tender, stages, preStage) {
+            const resultPos = this.resultPos;
             const helper = this.ctx.helper;
             completeData.id = tender.id;
             completeData.name = tender.name;
+            /**
+             * 汇总并合并 相关数据
+             * @param {Array} index - 主数据
+             * @param {Array[]}rela - 相关数据 {data, fields, prefix, relaId}
+             */
+            const sumAssignRelaData = function (index, rela) {
+                const loadFields = function (datas, fields, prefix, relaId) {
+                    for (const d of datas) {
+                        const key = indexPre + d[relaId];
+                        const m = index[key];
+                        if (m) {
+                            for (const f of fields) {
+                                if (d[f] !== undefined) {
+                                    m[prefix + f] = helper.add(m[prefix + f], d[f]);
+                                }
+                            }
+                        }
+                    }
+                };
+                for (const r of rela) {
+                    loadFields(r.data, r.fields, r.prefix, r.relaId);
+                }
+            };
             const billsTree = new Ledger.billsTree(this.ctx, {
                 id: 'ledger_id',
                 pid: 'ledger_pid',
@@ -260,7 +380,7 @@ module.exports = app => {
                 rootId: -1,
                 keys: ['id', 'tender_id', 'ledger_id'],
                 stageId: 'id',
-                calcFields: ['deal_tp', 'total_price', 'contract_tp', 'qc_tp', 'contract_pc_tp', 'qc_pc_tp', 'pc_tp', 'gather_tp', 'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp'],
+                calcFields: ['deal_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp', 'contract_pc_tp', 'qc_pc_tp', 'pc_tp', 'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp'],
                 calc: function (node) {
                     if (node.children && node.children.length === 0) {
                         node.pre_gather_qty = helper.add(node.pre_contract_qty, node.pre_qc_qty);
@@ -270,67 +390,101 @@ module.exports = app => {
                         node.end_gather_qty = helper.add(node.pre_gather_qty, node.gather_qty);
                     }
                     node.pre_gather_tp = helper.add(node.pre_contract_tp, node.pre_qc_tp);
-                    node.gather_tp = helper.sum([node.contract_tp, node.qc_tp, node.pc_tp]);
-                    node.end_contract_tp = helper.sum([node.pre_contract_tp, node.contract_tp, node.contract_pc_tp]);
-                    node.end_qc_tp = helper.sum([node.pre_qc_tp, node.qc_tp, node.qc_pc_tp]);
+                    node.gather_tp = helper.add(node.contract_tp, node.qc_tp);
+                    node.end_contract_tp = helper.add(node.pre_contract_tp, node.contract_tp);
+                    node.end_qc_tp = helper.add(node.pre_qc_tp, node.qc_tp);
                     node.end_gather_tp = helper.add(node.pre_gather_tp, node.gather_tp);
                 }
             });
             const billsData = await this.ctx.service.ledger.getData(tender.id);
-
             const dgnData = await this.ctx.service.stageBillsDgn.getDgnData(tender.id);
             for (const d of dgnData) {
                 const l = this.ctx.helper._.find(billsData, {id: d.id});
                 this.ctx.helper._.assignIn(l, d);
             }
+            const pos = new Ledger.pos({
+                id: 'id', ledgerId: 'lid',
+                calc: function (node) {
+                    node.pre_gather_qty = helper.add(node.pre_contract_qty, node.pre_qc_qty);
+                    node.gather_tp = helper.sum([node.contract_tp, node.qc_tp, node.pc_tp]);
+                    node.end_contract_tp = helper.sum([node.pre_contract_tp, node.contract_tp, node.contract_pc_tp]);
+                    node.end_qc_tp = helper.sum([node.pre_qc_tp, node.qc_tp, node.qc_pc_tp]);
+                    node.end_gather_qty = helper.add(node.pre_gather_qty, node.gather_qty);
+                },
+            });
+            const posData = await this.ctx.service.pos.getAllDataByCondition({ where: { tid: tender.id} });
+            let billsIndexData = {};
+            for (const bd of billsData) {
+                billsIndexData[indexPre + bd.id] = bd;
+            }
+            let posIndexData = {};
+            for (const p of posData) {
+                posIndexData[indexPre + p.id] = p;
+            }
 
-            if (stage) {
+            if (preStage) {
+                await this.ctx.service.stage.doCheckStage(preStage);
+                const endStage = await this.ctx.service.stageBillsFinal.getFinalData(tender, preStage.order);
+                sumAssignRelaData(billsIndexData, [
+                    { data: endStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: 'pre_', relaId: 'lid' },
+                ]);
+                const endStagePos = await this.ctx.service.stagePosFinal.getFinalData(tender, preStage.order);
+                sumAssignRelaData(posIndexData, [
+                    {data: endStagePos, fields: ['contract_qty', 'qc_qty'], prefix: 'pre_', relaId: 'lid'},
+                ]);
+            }
+
+            for (const stage of stages) {
                 await this.ctx.service.stage.doCheckStage(stage);
                 const curStage = stage.readOnly
                     ? await this.ctx.service.stageBills.getAuditorStageData2(tender.id, stage.id, stage.curTimes, stage.curOrder)
                     : await this.ctx.service.stageBills.getLastestStageData2(tender.id, stage.id);
-                const preStage = hasPre && stage.order > 1 ? await this.ctx.service.stageBillsFinal.getFinalData(tender, stage.order - 1) : [];
                 const bpcStage = await this.ctx.service.stageBillsPc.getAllDataByCondition({ where: { sid: stage.id } });
-                this.ctx.helper.assignRelaData(billsData, [
-                    { data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid' },
-                    { data: preStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: 'pre_', relaId: 'lid' },
+                sumAssignRelaData(billsIndexData, [
+                    {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'},
                     { data: bpcStage, fields: ['contract_pc_tp', 'qc_pc_tp', 'pc_tp'], prefix: '', relaId: 'lid' },
                 ]);
+                const curStagePos = stage.readOnly
+                    ? await this.ctx.service.stagePos.getAuditorStageData2(tender.id, stage.id, stage.curTimes, stage.curOrder)
+                    : await this.ctx.service.stagePos.getLastestStageData2(tender.id, stage.id);
+                sumAssignRelaData(posIndexData, [
+                    {data: curStagePos, fields: ['contract_qty', 'qc_qty'], prefix: '', relaId: 'pid'},
+                ]);
             }
+
             billsTree.loadDatas(billsData);
             billsTree.calculateAll();
+            pos.loadDatas(posData);
+            pos.calculateAll();
             this.resultTree.loadGatherTree(billsTree, function (gatherNode, sourceNode) {
                 gatherUtils.gatherStage(tender, gatherNode, sourceNode, completeData.prefix, helper);
+            }, function (gatherNode, sourceNode) {
+                const posRange = pos.getLedgerPos(sourceNode.id);
+                if (!posRange || posRange.length === 0) return;
+                resultPos.loadGatherPos(gatherNode.id, posRange, (gatherPos, sourcePos) => {
+                    gatherUtils.gatherStagePos(tender, gatherPos, sourcePos, completeData.prefix, helper);
+                });
             });
         }
+        async _gatherZoneData(tender, completeData, zone) {
+            const [stages, preStage, endStage] = await this._getTimeZoneStages(tender, zone);
+            await this._gatherStagesData(completeData, tender, stages, preStage);
+        }
+        async _gatherIndexZoneData(tender, completeData, stageZone) {
+            const [stages, preStage, endStage] = await this._getOrderZoneStages(tender, stageZone);
+            await this._gatherStagesData(completeData, tender, stages, preStage);
+        }
+        async _gatherCheckedZoneData(tender, completeData, zone) {
+            const [stages, preStage, endStage] = await this._getCheckedZoneStages(tender, zone);
+            await this._gatherStagesData(completeData, tender, stages, preStage);
+        }
 
-        async _gatherStagesData(completeData, tender, stages) {
+        async _gatherStageData(completeData, tender, stage, hasPre) {
             const helper = this.ctx.helper;
+            const resultPos = this.resultPos;
             completeData.id = tender.id;
             completeData.name = tender.name;
-            /**
-             * 汇总并合并 相关数据
-             * @param {Array} index - 主数据
-             * @param {Array[]}rela - 相关数据 {data, fields, prefix, relaId}
-             */
-            const sumAssignRelaData = function (index, rela) {
-                const loadFields = function (datas, fields, prefix, relaId) {
-                    for (const d of datas) {
-                        const key = indexPre + d[relaId];
-                        const m = index[key];
-                        if (m) {
-                            for (const f of fields) {
-                                if (d[f] !== undefined) {
-                                    m[prefix + f] = helper.add(m[prefix + f], d[f]);
-                                }
-                            }
-                        }
-                    }
-                };
-                for (const r of rela) {
-                    loadFields(r.data, r.fields, r.prefix, r.relaId);
-                }
-            };
+
             const billsTree = new Ledger.billsTree(this.ctx, {
                 id: 'ledger_id',
                 pid: 'ledger_pid',
@@ -339,80 +493,96 @@ module.exports = app => {
                 rootId: -1,
                 keys: ['id', 'tender_id', 'ledger_id'],
                 stageId: 'id',
-                calcFields: ['deal_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp'],
+                calcFields: ['deal_tp', 'total_price', 'contract_tp', 'qc_tp', 'contract_pc_tp', 'qc_pc_tp', 'pc_tp', 'gather_tp', 'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp'],
                 calc: function (node) {
                     if (node.children && node.children.length === 0) {
+                        node.pre_gather_qty = helper.add(node.pre_contract_qty, node.pre_qc_qty);
                         node.gather_qty = helper.add(node.contract_qty, node.qc_qty);
+                        node.end_contract_qty = helper.add(node.pre_contract_qty, node.contract_qty);
+                        node.end_qc_qty = helper.add(node.pre_qc_qty, node.qc_qty);
+                        node.end_gather_qty = helper.add(node.pre_gather_qty, node.gather_qty);
                     }
-                    node.gather_tp = helper.add(node.contract_tp, node.qc_tp);
+                    node.pre_gather_tp = helper.add(node.pre_contract_tp, node.pre_qc_tp);
+                    node.gather_tp = helper.sum([node.contract_tp, node.qc_tp, node.pc_tp]);
+                    node.end_contract_tp = helper.sum([node.pre_contract_tp, node.contract_tp, node.contract_pc_tp]);
+                    node.end_qc_tp = helper.sum([node.pre_qc_tp, node.qc_tp, node.qc_pc_tp]);
+                    node.end_gather_tp = helper.add(node.pre_gather_tp, node.gather_tp);
                 }
             });
             const billsData = await this.ctx.service.ledger.getData(tender.id);
-
             const dgnData = await this.ctx.service.stageBillsDgn.getDgnData(tender.id);
             for (const d of dgnData) {
                 const l = this.ctx.helper._.find(billsData, {id: d.id});
                 this.ctx.helper._.assignIn(l, d);
             }
-
-            let billsIndexData = {};
-            for (const bd of billsData) {
-                billsIndexData[indexPre + bd.id] = bd;
-            }
-
-            for (const stage of stages) {
+            const pos = new Ledger.pos({
+                id: 'id', ledgerId: 'lid',
+                calc: function (node) {
+                    node.pre_gather_qty = helper.add(node.pre_contract_qty, node.pre_qc_qty);
+                    node.gather_qty = helper.add(node.contract_qty, node.qc_qty);
+                    node.end_contract_qty = helper.add(node.pre_contract_qty, node.contract_qty);
+                    node.end_qc_qty = helper.add(node.pre_qc_qty, node.qc_qty);
+                    node.end_gather_qty = helper.add(node.pre_gather_qty, node.gather_qty);
+                },
+            });
+            const posData = await this.ctx.service.pos.getPosData({ tid: tender.id });
+            if (stage) {
                 await this.ctx.service.stage.doCheckStage(stage);
                 const curStage = stage.readOnly
                     ? await this.ctx.service.stageBills.getAuditorStageData2(tender.id, stage.id, stage.curTimes, stage.curOrder)
                     : await this.ctx.service.stageBills.getLastestStageData2(tender.id, stage.id);
-                const bpcStage = await this.ctx.service.stageBillsPc.getAllDataByCondition({ where: { sid: this.ctx.stage.id } });
-                sumAssignRelaData(billsIndexData, [
-                    {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'},
+                const preStage = hasPre && stage.order > 1 ? await this.ctx.service.stageBillsFinal.getFinalData(tender, stage.order - 1) : [];
+                const bpcStage = await this.ctx.service.stageBillsPc.getAllDataByCondition({ where: { sid: stage.id } });
+                this.ctx.helper.assignRelaData(billsData, [
+                    { data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid' },
+                    { data: preStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: 'pre_', relaId: 'lid' },
                     { data: bpcStage, fields: ['contract_pc_tp', 'qc_pc_tp', 'pc_tp'], prefix: '', relaId: 'lid' },
                 ]);
-            }
 
+                const curStagePos = stage.readOnly
+                    ? await this.ctx.service.stagePos.getAuditorStageData2(tender.id, stage.id, stage.curTimes, stage.curOrder)
+                    : await this.ctx.service.stagePos.getLastestStageData2(tender.id, stage.id);
+                const preStagePos = hasPre && stage.order > 1 ? await this.ctx.service.stagePosFinal.getFinalData(tender, stage.order - 1) : [];
+                this.ctx.helper.assignRelaData(posData, [
+                    { data: curStagePos, fields: ['contract_qty', 'qc_qty'], prefix: '', relaId: 'pid' },
+                    { data: preStagePos, fields: ['contract_qty', 'qc_qty'], prefix: 'pre_', relaId: 'pid' },
+                ]);
+            }
             billsTree.loadDatas(billsData);
             billsTree.calculateAll();
+            pos.loadDatas(posData);
+            pos.calculateAll();
+
             this.resultTree.loadGatherTree(billsTree, function (gatherNode, sourceNode) {
-                gatherUtils.gatherZone(tender, gatherNode, sourceNode, completeData.prefix, helper);
+                gatherUtils.gatherStage(tender, gatherNode, sourceNode, completeData.prefix, helper);
+            }, function (gatherNode, sourceNode) {
+                const posRange = pos.getLedgerPos(sourceNode.id);
+                if (!posRange || posRange.length === 0) return;
+                resultPos.loadGatherPos(gatherNode.id, posRange, (gatherPos, sourcePos) => {
+                    gatherUtils.gatherStagePos(tender, gatherPos, sourcePos, completeData.prefix, helper);
+                });
             });
         }
-
         async _gatherMonthData(tender, completeData, month, hasPre) {
             const stages = await this._getValidStages(tender.id);
             const stage = this.ctx.helper._.find(stages, {s_time: month});
             await this._gatherStageData(completeData, tender, stage, hasPre);
         }
-
         async _gatherIndexData(tender, completeData, index, hasPre) {
             const stages = await this._getValidStages(tender.id);
             const stage = this.ctx.helper._.find(stages, {order: index});
             await this._gatherStageData(completeData, tender, stage, hasPre);
         }
-
-        async _gatherZoneData(tender, completeData, zone) {
-            const stages = await this._getTimeZoneStages(tender, zone);
-            await this._gatherStagesData(completeData, tender, stages);
-        }
-
-        async _gatherIndexZoneData(tender, completeData, stageZone) {
-            const stages = await this._getOrderZoneStages(tender, stageZone);
-            await this._gatherStagesData(completeData, tender, stages);
-        }
-
         async _gatherFinalData(tender, completeData, hasPre) {
             const stages = await this._getValidStages(tender.id);
             await this._gatherStageData(completeData, tender, stages[0], hasPre);
         }
-
         async _gatherCheckedFinalData(tender, completeData, hasPre) {
             const stages = await this._getCheckedStages(tender.id);
             await this._gatherStageData(completeData, tender, stages[0], hasPre);
         }
 
-        async _gatherLedgerData(tender, completeData) {
-            const helper = this.ctx.helper;
+        async _loadGatherLedger(tender) {
             const billsTree = new Ledger.billsTree(this.ctx, {
                 id: 'ledger_id',
                 pid: 'ledger_pid',
@@ -426,36 +596,59 @@ module.exports = app => {
             const billsData = await this.ctx.service.ledger.getData(tender.id);
             billsTree.loadDatas(billsData);
             billsTree.calculateAll();
+            return billsTree;
+        }
+        async _loadGatherPos(tender) {
+            const pos = new Ledger.pos({ id: 'id', ledgerId: 'lid' });
+            const posData = await this.ctx.service.pos.getAllDataByCondition({ where: { tid: tender.id } });
+            pos.loadDatas(posData);
+            return pos;
+        }
+        async _gatherLedgerData(tender, completeData) {
+            const resultPos = this.resultPos;
+            const helper = this.ctx.helper;
+            const billsTree = await this._loadGatherLedger(tender);
+            const pos = await this._loadGatherPos(tender);
             this.resultTree.loadGatherTree(billsTree, function (gatherNode, sourceNode) {
                 gatherUtils.gatherLedger(tender, gatherNode, sourceNode, completeData.prefix, helper);
+            }, function (gatherNode, sourceNode) {
+                const posRange = pos.getLedgerPos(sourceNode.id);
+                if (!posRange || posRange.length === 0) return;
+                resultPos.loadGatherPos(posRange, (gatherPos, sourcePos) => {
+                    gatherUtils.gatherPos(gatherPos, sourcePos, completeData.prefix, helper);
+                });
             });
         }
-
         async _gatherSpecialData(tender, sKey) {
+            const resultPos = this.resultPos;
             const helper = this.ctx.helper;
-            const billsTree = new Ledger.billsTree(this.ctx, {
-                id: 'ledger_id',
-                pid: 'ledger_pid',
-                order: 'order',
-                level: 'level',
-                rootId: -1,
-                keys: ['id', 'tender_id', 'ledger_id'],
-                stageId: 'id',
-                calcFields: ['deal_tp', 'total_price'],
-            });
-            const billsData = await this.ctx.service.ledger.getData(tender.id);
-            billsTree.loadDatas(billsData);
-            billsTree.calculateAll();
+            const billsTree = await this._loadGatherLedger(tender);
+            const pos = await this._loadGatherPos(tender);
             this.resultTree.loadGatherTree(billsTree, function (gatherNode, sourceNode) {
                 gatherUtils.gatherSpecial(gatherNode, sourceNode, 'ts_' + sKey + '_', helper);
+            }, function (gatherNode, sourceNode) {
+                const posRange = pos.getLedgerPos(sourceNode.id);
+                if (!posRange || posRange.length === 0) return;
+                resultPos.loadGatherPos(gatherNode.id, posRange, (gatherPos, sourcePos) => {
+                    gatherUtils.gatherSpecialPos(gatherPos, sourcePos, 'ts_' + sKey + '_', helper);
+                });
             });
         }
 
-        async getGatherStageBills(memFieldKeys, gsDefine, gsCustom) {
-            if (!gsDefine || !gsDefine.enable) return [];
-            if (!gsCustom || !gsCustom.tenders || gsCustom.tenders.length === 0) return [];
+        async _doGatherStageData(memFieldKeys, gsDefine, gsCustom) {
+            if (this.resultTree || this.resultPos) return;
+            if (!gsDefine || !gsDefine.enable) return;
+            if (!gsCustom || !gsCustom.tenders || gsCustom.tenders.length === 0) return;
+
+            this.resultTree = new Ledger.gatherTree(this.ctx, {
+                id: 'id',
+                pid: 'pid',
+                order: 'order',
+                level: 'level',
+                rootId: -1
+            });
+            this.resultPos = new Ledger.gatherPos({ id: 'id', ledgerId: 'lid' });
 
-            this.resultTree.clear();
             const gsSetting = JSON.parse(gsDefine.setting);
             let commonIndex = 0;
             const completeDatas = [];
@@ -489,6 +682,9 @@ module.exports = app => {
                         case 'stage-zone':
                             await this._gatherIndexZoneData(tender, completeData, gsCustom.stage_zone);
                             break;
+                        case 'checked-zone':
+                            await this._gatherCheckedZoneData(tender, completeData, gsCustom.checked_zone);
+                            break;
                     }
                     commonIndex++;
                 } else {
@@ -498,7 +694,16 @@ module.exports = app => {
 
             this.resultTree.resortChildrenDefault();
             gatherUtils.completeGatherData(this.resultTree.nodes, completeDatas);
-            return this.resultTree.getDefaultDatas();
+            gatherUtils.completeGatherData(this.resultPos.datas, completeDatas);
+        }
+
+        async getGatherStageBills(memFieldKeys, gsDefine, gsCustom) {
+            await this._doGatherStageData(memFieldKeys, gsDefine, gsCustom);
+            return this.resultTree ? this.resultTree.getDefaultDatas() : [];
+        }
+        async getGatherStagePos(memFieldKeys, gsDefine, gsCustom) {
+            await this._doGatherStageData(memFieldKeys, gsDefine, gsCustom);
+            return this.resultPos ? this.resultPos.getDatas() : [];
         }
 
         /**
@@ -556,19 +761,49 @@ module.exports = app => {
                 info.end_sf_tp = helper.add(stage.sf_tp, stage.pre_sf_tp);
             }
         }
-
-        async _getStagesTenderInfo(stages, info) {
+        async _getStagesTenderInfo(stages, info, preStage) {
             const helper = this.ctx.helper;
+            if (preStage) {
+                info.pre_contract_tp = helper.add(preStage.contract_tp, preStage.pre_contract_tp);
+                info.pre_qc_tp = helper.add(preStage.qc_tp, preStage.pre_qc_tp);
+                info.pre_gather_tp = helper.add(info.pre_contract_tp, info.pre_qc_tp);
+                info.pre_yf_tp = helper.add(preStage.yf_tp, preStage.pre_yf_tp);
+            }
+
             for (const stage of stages) {
                 await this.ctx.service.stage.doCheckStage(stage);
                 await this.ctx.service.stage.checkStageGatherData(stage, this.ctx.session.sessionUser.is_admin);
 
+
                 info.contract_tp = helper.add(info.contract_tp, stage.contract_tp);
                 info.qc_tp = helper.add(info.qc_tp, stage.qc_tp);
 
                 info.yf_tp = helper.add(info.yf_tp, stage.yf_tp);
             }
             info.gather_tp = helper.add(info.contract_tp, info.qc_tp);
+            info.end_contract_tp = helper.add(info.pre_contract_tp, info.contract_tp);
+            info.end_qc_tp = helper.add(info.pre_qc_tp, info.qc_tp);
+            info.end_gather_tp = helper.add(info.pre_gather_tp, info.gather_tp);
+            info.end_yf_tp = helper.add(info.pre_yf_tp, info.yf_tp);
+        }
+
+        async _gatherZoneTenderInfo(tender, index, zone) {
+            const info = await this._getBaseTenderInfo(tender);
+            const [stages, preStage, endStage] = await this._getTimeZoneStages(tender, zone);
+            await this._getStagesTenderInfo(stages, info, preStage);
+            this.resultTenderInfo.push(info);
+        }
+        async _gatherOrderZoneTenderInfo(tender, index, stageZone) {
+            const info = await this._getBaseTenderInfo(tender);
+            const [stages, preStage, endStage] = await this._getOrderZoneStages(tender, stageZone);
+            await this._getStagesTenderInfo(stages, info, preStage);
+            this.resultTenderInfo.push(info);
+        }
+        async _gatherCheckedZoneTenderInfo(tender, index, zone) {
+            const info = await this._getBaseTenderInfo(tender);
+            const [stages, preStage, endStage] = await this._getCheckedZoneStages(tender, zone);
+            await this._getStagesTenderInfo(stages, info, preStage);
+            this.resultTenderInfo.push(info);
         }
 
         async _gatherMonthTenderInfo(tender, index, month, hasPre) {
@@ -578,7 +813,6 @@ module.exports = app => {
             await this._getStageTenderInfo(stage, info);
             this.resultTenderInfo.push(info);
         }
-
         async _gatherOrderTenderInfo(tender, index, order, hasPre) {
             const info = await this._getBaseTenderInfo(tender);
             const stages = await this._getValidStages(tender.id);
@@ -586,28 +820,12 @@ module.exports = app => {
             await this._getStageTenderInfo(stage, info);
             this.resultTenderInfo.push(info);
         }
-
-        async _gatherZoneTenderInfo(tender, index, zone) {
-            const info = await this._getBaseTenderInfo(tender);
-            const stages = await this._getTimeZoneStages(tender, zone);
-            await this._getStagesTenderInfo(stages, info);
-            this.resultTenderInfo.push(info);
-        }
-
-        async _gatherOrderZoneTenderInfo(tender, index, stageZone) {
-            const info = await this._getBaseTenderInfo(tender);
-            const stages = await this._getOrderZoneStages(tender, stageZone);
-            await this._getStagesTenderInfo(stages, info);
-            this.resultTenderInfo.push(info);
-        }
-
         async _gatherFinalTenderInfo(tender, index, hasPre) {
             const info = await this._getBaseTenderInfo(tender);
             const stages = await this._getValidStages(tender.id);
             await this._getStageTenderInfo(stages[0], info);
             this.resultTenderInfo.push(info);
         }
-
         async _gatherCheckedFinalTenderInfo(tender, index, hasPre) {
             const info = await this._getBaseTenderInfo(tender);
             const stages = await this._getCheckedStages(tender.id);
@@ -619,7 +837,6 @@ module.exports = app => {
             const info = await this._getBaseTenderInfo(tender);
             this.resultTenderInfo.push(info);
         }
-
         async _gatherSpecialTenderInfo(tender, sKey) {
             const info = await this._getBaseTenderInfo(tender);
             info.spec = sKey;
@@ -659,6 +876,9 @@ module.exports = app => {
                         case 'stage-zone':
                             await this._gatherOrderZoneTenderInfo(tender, commonIndex, gsCustom.stage_zone);
                             break;
+                        case 'checked-zone':
+                            await this._gatherCheckedZoneTenderInfo(tender, commonIndex, gsCustom.checked_zone);
+                            break;
                     }
                     commonIndex++;
                 } else {
@@ -728,12 +948,27 @@ module.exports = app => {
                 }
             }
         }
-
-        async _gatherStagesPay(completeData, tender, stages) {
+        async _gatherStagesPay(completeData, tender, stages, preStage) {
             const helper = this.ctx.helper;
             completeData.id = tender.id;
             completeData.name = tender.name;
 
+            if (preStage) await this.ctx.service.stage.doCheckStage(preStage);
+            const preDealPay = preStage ? await this.ctx.service.stagePay.getStagePays(preStage) : [];
+            for (const dp of preDealPay) {
+                dp.end_tp = helper.add(dp.pre_tp, dp.tp);
+                this._gatherPayRecord(dp, function (gatherData, sourceData) {
+                    gatherData[completeData.prefix + 'id'] = tender.id;
+                    gatherData[completeData.prefix + 'name'] = tender.name;
+
+                    gatherData[completeData.prefix + 'pre_tp'] = sourceData.end_tp;
+                    gatherData.s_pre_tp = helper.add(gatherData.s_pre_tp, sourceData.end_tp);
+
+                    gatherData[completeData.prefix + 'end_tp'] = sourceData.end_tp;
+                    gatherData.s_end_tp = helper.add(gatherData.s_end_tp, sourceData.end_tp);
+                });
+            }
+
             for (const stage of stages) {
                 await this.ctx.service.stage.doCheckStage(stage);
 
@@ -747,43 +982,47 @@ module.exports = app => {
 
                         gatherData[completeData.prefix + 'tp'] = helper.add(gatherData[completeData.prefix + 'tp'], sourceData.tp);
                         gatherData['s_' + 'tp'] = helper.add(gatherData['s_' + 'tp'], sourceData.tp);
+
+                        gatherData[completeData.prefix + 'end_tp'] = helper.add(gatherData[completeData.prefix + 'end_tp'], sourceData.tp);
+                        gatherData['s_' + 'end_tp'] = helper.add(gatherData['s_' + 'end_tp'], sourceData.tp);
                     });
                 }
             }
         }
 
+        async _gatherZoneStagePay(sTender, completeData, zone) {
+            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+            const [stages, preStage, endStage] = await this._getTimeZoneStages(tender, zone);
+            await this._gatherStagesPay(completeData, tender, stages, preStage);
+        }
+        async _gatherOrderZoneStagePay(sTender, completeData, stageZone) {
+            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+            const [stages, preStage, endStage] = await this._getOrderZoneStages(tender, stageZone);
+            await this._gatherStagesPay(completeData, tender, stages, preStage);
+        }
+        async _gatherCheckedZoneStagePay(sTender, completeData, zone) {
+            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+            const [stages, preStage, endStage] = await this._getCheckedZoneStages(tender, zone);
+            await this._gatherStagesPay(completeData, tender, stages, preStage);
+        }
+
         async _gatherMonthStagePay(sTender, completeData, month, hasPre) {
             const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
             const stages = await this._getValidStages(tender.id);
             const stage = this.ctx.helper._.find(stages, {s_time: month});
             await this._gatherStagePay(completeData, tender, stage, hasPre);
         }
-
         async _gatherOrderStagePay(sTender, completeData, order, hasPre) {
             const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
             const stages = await this._getValidStages(tender.id);
             const stage = this.ctx.helper._.find(stages, {order: order});
             await this._gatherStagePay(completeData, tender, stage, hasPre);
         }
-
-        async _gatherZoneStagePay(sTender, completeData, zone) {
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
-            const stages = await this._getTimeZoneStages(tender, zone);
-            await this._gatherStagesPay(completeData, tender, stages);
-        }
-
-        async _gatherOrderZoneStagePay(sTender, completeData, stageZone) {
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
-            const stages = await this._getOrderZoneStages(tender, stageZone)
-            await this._gatherStagesPay(completeData, tender, stages);
-        }
-
         async _gatherFinalStagePay(sTender, completeData, hasPre) {
             const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
             const stages = await this._getValidStages(tender.id);
             await this._gatherStagePay(completeData, tender, stages[0], hasPre);
         }
-
         async _gatherCheckedFinalStagePay(sTender, completeData, hasPre) {
             const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
             const stages = await this._getCheckedStages(tender.id);
@@ -824,6 +1063,9 @@ module.exports = app => {
                         case 'stage-zone':
                             await this._gatherOrderZoneStagePay(tender, completeData, gsCustom.stage_zone);
                             break;
+                        case 'checked-zone':
+                            await this._gatherCheckedZoneStagePay(tender, completeData, gsCustom.checked_zone);
+                            break;
                     }
                     commonIndex++;
                 }

+ 74 - 0
app/service/spec_msg.js

@@ -0,0 +1,74 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class SpecPull extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 's2b_spec_msg';
+        }
+
+        async tenderNeedMsg(pid, tid, timing) {
+            const specProj = await this.db.get('zh_s2b_spec_proj', { id: pid });
+            if (!specProj || !specProj.is_push) return false;
+
+            switch (specProj.push_tender_type) {
+                case 1:
+                    const filter = specProj.filter_tender ? specProj.filter_tender.split(',') : [];
+                    if (filter.indexOf(tid + '') >= 0) return false;
+                    break;
+                case 2:
+                    const access = specProj.access_tender ? specProj.access_tender.split(',') : [];
+                    if (access.indexOf(tid + '') < 0) return false;
+                    break;
+            }
+
+            const soleTender = await this.db.get('zh_s2b_spec_tender', { id: tid, is_valid: 1 });
+            const specPush = await this.db.get('zh_s2b_spec_push', { pid, tid: soleTender ? tid : 0, valid: 1, push_timing: timing });
+            return !!specPush;
+        }
+
+        async addLedgerMsg(transaction, pid, tender, timing) {
+            const needMsg = await this.tenderNeedMsg(pid, tender.id, timing);
+            if (!needMsg) return;
+            await transaction.insert(this.tableName, { pid, tid: tender.id, timing });
+        }
+
+        async addReviseMsg(transaction, pid, revise, timing) {
+            const needMsg = await this.tenderNeedMsg(pid, revise.tid, timing);
+            if (!needMsg) return;
+            await transaction.insert(this.tableName, { pid, tid: revise.tid, rid: revise.id, timing });
+        }
+
+        async addStageMsg(transaction, pid, stage, timing) {
+            const needMsg = await this.tenderNeedMsg(pid, stage.tid, timing);
+            if (!needMsg) return;
+            await transaction.insert(this.tableName, { pid, tid: stage.tid, sid: stage.id, timing });
+        }
+
+        async addReportMsg(transaction, pid, tender, stage, timing) {
+            const needMsg = await this.tenderNeedMsg(pid, stage.tid, timing);
+            if (!needMsg) return;
+            if (transaction) {
+                await transaction.insert(this.tableName, { pid, tid: tender.id, sid: stage ? stage.id : 0, timing });
+            } else {
+                await this.db.insert(this.tableName, { pid, tid: tender.id, sid: stage ? stage.id : 0, timing });
+            }
+        }
+    }
+
+    return SpecPull;
+};

+ 0 - 0
app/service/spec_pull.js


Some files were not shown because too many files changed in this diff