Browse Source

Merge branch 'dev' into uat

MaiXinRong 3 years ago
parent
commit
31458e97d5
100 changed files with 7782 additions and 1695 deletions
  1. 16 2
      app/const/cld_office.js
  2. 2 0
      app/const/deal_pay.js
  3. 8 7
      app/const/spread.js
  4. 2 0
      app/const/tender_info.js
  5. 13 4
      app/const/wechat_template.js
  6. 18 50
      app/controller/budget_controller.js
  7. 14 6
      app/controller/change_controller.js
  8. 33 1
      app/controller/dashboard_controller.js
  9. 2 2
      app/controller/ledger_controller.js
  10. 77 6
      app/controller/material_controller.js
  11. 11 3
      app/controller/profile_controller.js
  12. 2 3
      app/controller/report_archive_controller.js
  13. 210 34
      app/controller/report_controller.js
  14. 11 0
      app/controller/setting_controller.js
  15. 96 8
      app/controller/stage_controller.js
  16. 17 3
      app/controller/tender_controller.js
  17. 50 0
      app/controller/wap_controller.js
  18. 4 0
      app/extend/context.js
  19. 34 3
      app/extend/helper.js
  20. 118 33
      app/lib/analysis_excel.js
  21. 16 0
      app/lib/bills_pos_convert.js
  22. 265 0
      app/lib/budget_final.js
  23. 18 1
      app/lib/gcl_gather.js
  24. 1 1
      app/lib/ledger.js
  25. 84 0
      app/lib/rm/budget.js
  26. 300 0
      app/lib/rm/material.js
  27. 0 1
      app/lib/rpt_data_analysis.js
  28. 11 27
      app/lib/stage_im.js
  29. 7 5
      app/lib/wechat.js
  30. 0 1
      app/middleware/auto_finish_logger.js
  31. BIN
      app/public/css/about.png
  32. BIN
      app/public/css/bg_participate_blue.png
  33. BIN
      app/public/css/bg_participate_orange.png
  34. 58 28
      app/public/css/main.css
  35. BIN
      app/public/images/erweima.jpg
  36. BIN
      app/public/images/juecedaping01.png
  37. BIN
      app/public/images/juecedaping02.png
  38. BIN
      app/public/images/nulllogo.png
  39. BIN
      app/public/images/wechat.png
  40. 1 1
      app/public/js/advance.js
  41. 118 106
      app/public/js/budget_compare.js
  42. 10 0
      app/public/js/change.js
  43. 20 2
      app/public/js/change_information.js
  44. 1 1
      app/public/js/change_information_approval.js
  45. 496 35
      app/public/js/change_information_set.js
  46. 1 1
      app/public/js/change_information_show.js
  47. 415 0
      app/public/js/colResizable/colResizable-1.6.js
  48. 3 0
      app/public/js/colResizable/colResizable-1.6.min.js
  49. 27 4
      app/public/js/gcl_gather.js
  50. 1 1
      app/public/js/global.js
  51. 0 2
      app/public/js/ledger.js
  52. 62 14
      app/public/js/material.js
  53. 59 0
      app/public/js/material_audit.js
  54. 13 7
      app/public/js/material_checklist.js
  55. 64 15
      app/public/js/material_exponent.js
  56. 631 102
      app/public/js/material_list.js
  57. 58 15
      app/public/js/measure_material.js
  58. 2 2
      app/public/js/measure_stage.js
  59. 27 10
      app/public/js/setting.js
  60. 1 1
      app/public/js/shares/gcl_gather_compare.js
  61. 55 9
      app/public/js/stage.js
  62. 3 0
      app/public/js/stage_bwtz.js
  63. 4 1
      app/public/js/stage_gather.js
  64. 11 25
      app/public/js/stage_im.js
  65. 4 1
      app/public/js/tender_list_manage.js
  66. 137 43
      app/public/report/js/jpc_output.js
  67. 20 1
      app/public/report/js/jpc_output_value_define.js
  68. 7 3
      app/public/report/js/rpt_archive.js
  69. 5 2
      app/public/report/js/rpt_indexDb.js
  70. 1789 0
      app/public/report/js/rpt_jsexcel.js
  71. 62 24
      app/public/report/js/rpt_jspdf.js
  72. 171 33
      app/public/report/js/rpt_main.js
  73. 54 0
      app/public/report/js/rpt_other_stage.js
  74. 6 3
      app/public/report/js/rpt_preview_common.js
  75. 75 30
      app/public/report/js/rpt_print.js
  76. 449 108
      app/public/report/js/rpt_signature.js
  77. 24 22
      app/reports/rpt_component/helper/jpc_helper_common.js
  78. 3 3
      app/reports/rpt_component/helper/jpc_helper_common_output.js
  79. 9 9
      app/reports/rpt_component/helper/jpc_helper_field.js
  80. 48 26
      app/reports/rpt_component/helper/jpc_helper_flow_tab.js
  81. 489 489
      app/reports/rpt_component/helper/jpc_helper_font_width.js
  82. 13 2
      app/reports/rpt_component/jpc_bill_tab.js
  83. 25 8
      app/reports/rpt_component/jpc_cross_tab.js
  84. 8 8
      app/reports/rpt_component/jpc_ex.js
  85. 141 23
      app/reports/rpt_component/jpc_flow_tab.js
  86. 16 16
      app/reports/rpt_component/jpc_rte.js
  87. 1 0
      app/reports/rpt_component/jpc_value_define.js
  88. 7 7
      app/reports/util/rpt_excel_util.js
  89. 8 1
      app/router.js
  90. 33 6
      app/service/advance_audit.js
  91. 27 0
      app/service/budget.js
  92. 27 0
      app/service/budget_final.js
  93. 57 0
      app/service/budget_final_list.js
  94. 65 65
      app/service/change.js
  95. 66 44
      app/service/change_apply_audit.js
  96. 28 0
      app/service/change_audit.js
  97. 216 84
      app/service/change_audit_list.js
  98. 1 1
      app/service/change_plan.js
  99. 110 50
      app/service/change_plan_audit.js
  100. 0 0
      app/service/change_project_audit.js

+ 16 - 2
app/const/cld_office.js

@@ -33,7 +33,14 @@ const officeType = {
     JIANGSU: 24,
     LIAONING: 25,
     NINGXIA: 26,
-    YJZX: 27,
+    SHANXI: 27,
+    SHAN3XI: 28,
+    SHANGHAI: 29,
+    TIANJIN: 30,
+    XIZANG: 31,
+    XINJIANG: 32,
+    QINGHAI: 33,
+    YJZX: 100,
 };
 
 const office = [];
@@ -46,7 +53,7 @@ office[officeType.SICHUAN] = '四川办';
 office[officeType.CHONGQING] = '重庆办';
 office[officeType.NEIMENG] = '内蒙办';
 office[officeType.ZHEJIANG] = '浙江办';
-office[officeType.SHANDONG] = '上海办';
+office[officeType.SHANDONG] = '山东办';
 office[officeType.ZONGBU] = '总部';
 office[officeType.YUNNAN] = '云南办';
 office[officeType.GUIZHOU] = '贵州办';
@@ -62,6 +69,13 @@ office[officeType.JILIN] = '吉林办';
 office[officeType.JIANGSU] = '江苏办';
 office[officeType.LIAONING] = '辽宁办';
 office[officeType.NINGXIA] = '宁夏办';
+office[officeType.SHANXI] = '山西办';
+office[officeType.SHAN3XI] = '陕西办';
+office[officeType.SHANGHAI] = '上海办';
+office[officeType.TIANJIN] = '天津办';
+office[officeType.XIZANG] = '西藏办';
+office[officeType.XINJIANG] = '新疆办';
+office[officeType.QINGHAI] = '青海办';
 office[officeType.YJZX] = '总部-造价研究中心';
 // const office = [
 //     {id: officeType.ANHUI, name: '安徽办'},

+ 2 - 0
app/const/deal_pay.js

@@ -68,6 +68,8 @@ const chapterDetail = [
     {name: '清单 第500章 隧道', cType: 1, serialNo: 5, filter: '^[^0-9]*5[0-9]{2}(-|$)'},
     {name: '清单 第600章 安全设施及预埋管线', cType: 1, serialNo: 6, filter: '^[^0-9]*6[0-9]{2}(-|$)'},
     {name: '清单 第700章 绿化及环境保护', cType: 1, serialNo: 7, filter: '^[^0-9]*7[0-9]{2}(-|$)'},
+    {name: '清单 第800章 机电工程', cType: 1, serialNo: 6, filter: '^[^0-9]*8[0-9]{2}(-|$)'},
+    {name: '清单 第900章 房建工程', cType: 1, serialNo: 7, filter: '^[^0-9]*9[0-9]{2}(-|$)'},
     {name: '未计入章节清单合计', cType: 21, serialNo: 8},
     {name: '清单小计(A)', cType: 11, serialNo: 9},
     {name: '非清单项费用(B)', cType: 31, serialNo: 10},

+ 8 - 7
app/const/spread.js

@@ -205,7 +205,7 @@ const stageTz = {
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_qc_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '截止本期完成计量|数量', colSpan: '3|1', rowSpan: '1|1', field: 'end_gather_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
-            {title: '|完成率(%)', colSpan: '1', rowSpan: '|1', field: 'end_gather_percent', hAlign: 2, width: 80, readOnly: true, type: 'Number'},
+            {title: '|完成率(%)', colSpan: '1', rowSpan: '|1', field: 'end_gather_1_percent', hAlign: 2, width: 80, readOnly: true, type: 'Number'},
             {title: '合同|项目节数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'deal_dgn_qty1', hAlign: 2, width: 60, type: 'Number'},
             {title: '|项目节数量2',  colSpan: '|1', rowSpan: '|1', field: 'deal_dgn_qty2', hAlign: 2, width: 60, type: 'Number'},
             {title: '变更|项目节数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'c_dgn_qty1', hAlign: 2, width: 60, type: 'Number'},
@@ -283,7 +283,7 @@ const stageCl = {
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_qc_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '截止本期完成计量|数量', colSpan: '3|1', rowSpan: '1|1', field: 'end_gather_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
-            {title: '|完成率(%)', colSpan: '1', rowSpan: '|1', field: 'end_gather_percent', hAlign: 2, width: 80, readOnly: true, type: 'Number'},
+            {title: '|完成率(%)', colSpan: '1', rowSpan: '|1', field: 'end_gather_1_percent', hAlign: 2, width: 80, readOnly: true, type: 'Number'},
             {title: '合同|项目节数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'deal_dgn_qty1', hAlign: 2, width: 60, type: 'Number'},
             {title: '|项目节数量2',  colSpan: '|1', rowSpan: '|1', field: 'deal_dgn_qty2', hAlign: 2, width: 60, type: 'Number'},
             {title: '变更|项目节数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'c_dgn_qty1', hAlign: 2, width: 60, type: 'Number'},
@@ -360,7 +360,7 @@ const stageNoCl = {
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_qc_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '截止本期完成计量|数量', colSpan: '3|1', rowSpan: '1|1', field: 'end_gather_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
-            {title: '|完成率(%)', colSpan: '1', rowSpan: '|1', field: 'end_gather_percent', hAlign: 2, width: 80, readOnly: true, type: 'Number'},
+            {title: '|完成率(%)', colSpan: '1', rowSpan: '|1', field: 'end_gather_1_percent', hAlign: 2, width: 80, readOnly: true, type: 'Number'},
             {title: '合同|项目节数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'deal_dgn_qty1', hAlign: 2, width: 60, type: 'Number'},
             {title: '|项目节数量2',  colSpan: '|1', rowSpan: '|1', field: 'deal_dgn_qty2', hAlign: 2, width: 60, type: 'Number'},
             {title: '变更|项目节数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'c_dgn_qty1', hAlign: 2, width: 60, type: 'Number'},
@@ -436,9 +436,10 @@ const stageGather = {
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_qc_tp', hAlign: 2, width: 60, type: 'Number'},
             {title: '截止本期完成计量|数量', colSpan: '3|1', rowSpan: '1|1', field: 'end_gather_qty', hAlign: 2, width: 60, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 60, type: 'Number'},
-            {title: '|完成率(%)', colSpan: '|1', rowSpan: '|1', field: 'end_gather_percent', hAlign: 2, width: 80, readOnly: true, type: 'Number'},
-            {title: '台账+变更令|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_final_qty', hAlign: 2, width: 60, type: 'Number'},
-            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_final_tp', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|完成率(%)', colSpan: '|1', rowSpan: '|1', field: 'end_gather_percent', hAlign: 2, width: 80, type: 'Number'},
+            {title: '台账+变更令|数量', colSpan: '3|1', rowSpan: '1|1', field: 'final_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'final_tp', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|完成率(%)', colSpan: '|1', rowSpan: '|1', field: 'final_percent', hAlign: 2, width: 80, type: 'Number'},
         ],
         emptyRows: 0,
         headRows: 2,
@@ -459,7 +460,7 @@ const stageGather = {
             {title: '截止本期计量数量|合同', colSpan: '4|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, type: 'Number'},
             {title: '|数量变更', colSpan: '|1', rowSpan: '|1', field: 'end_qc_qty', hAlign: 2, width: 60, type: 'Number'},
             {title: '|完成', colSpan: '|1', rowSpan: '|1', field: 'end_gather_qty', hAlign: 2, width: 60, type: 'Number'},
-            {title: '|完成率(%)', colSpan: '1', rowSpan: '|1', field: 'end_gather_percent', hAlign: 2, width: 80, readOnly: true, type: 'Number'},
+            {title: '|完成率(%)', colSpan: '1', rowSpan: '|1', field: 'end_gather_percent', hAlign: 2, width: 80, type: 'Number'},
             {title: '单位工程', colSpan: '1', rowSpan: '2', field: 'dwgc', hAlign: 0, width: 80, formatter: '@'},
             {title: '分部工程', colSpan: '1', rowSpan: '2', field: 'fbgc', hAlign: 0, width: 80, formatter: '@'},
             {title: '分项工程', colSpan: '1', rowSpan: '2', field: 'fxgc', hAlign: 0, width: 80, formatter: '@'},

+ 2 - 0
app/const/tender_info.js

@@ -139,6 +139,7 @@ const defaultInfo = {
             rate: '',
             contact: '',
             phone: '',
+            num: '',
         },
         worker: {
             name: '',
@@ -147,6 +148,7 @@ const defaultInfo = {
             rate: '',
             contact: '',
             phone: '',
+            num: '',
         },
     },
     shenpi: {

+ 13 - 4
app/const/wechat_template.js

@@ -9,7 +9,7 @@
  */
 const template = {
     stage: 0,
-    change: 1,
+    change: 1, // 变更所有共用模板
     ledger: 2, // 台账和台账修订共用模板
     revise: 3,
     material: 4,
@@ -17,9 +17,9 @@ const template = {
 };
 const templateId = {
     stage: '5vU3WmR90yDajbs4LWIWH4OQhunYlS1HXTiesIGxrsk',
-    change: 'nSKl9u4DMvu7KtmFFS9bfVvznRDRpx6L7LlTnVFn4fs',
-    ledger: 'ia3ObPVe_0A1GLVpU-jcDe1P6zVriJ36eAijeQvbpFM', // 台账和台账修订共用模板
-    revise: 'ia3ObPVe_0A1GLVpU-jcDe1P6zVriJ36eAijeQvbpFM',
+    change: 'nSKl9u4DMvu7KtmFFS9bfVvznRDRpx6L7LlTnVFn4fs', // 变更所有共用模板
+    ledger: '9Ul3KFxvYQGfT6wbGGjqqnGR0zHtx0BHKs9sXj4Ii44', // 台账和台账修订共用模板
+    revise: '9Ul3KFxvYQGfT6wbGGjqqnGR0zHtx0BHKs9sXj4Ii44',
     material: 'Y9ov80rev3LHcoM2hOQE6jrK_5xZsqE-PZ0Z6HS9VGA',
     advance: 'rgZHkyiLzrqaSGnQ2nSCCrOdUz2RJJZ_JA34L_MnQik',
 };
@@ -28,17 +28,26 @@ const status = {
     back: '退回',
     success: '通过',
     report: '已上报',
+    stop: '终止',
 };
 const tips = {
     check: '需要您审批',
     back: '审批退回',
     success: '审批通过',
     report: '已上报',
+    stop: '审批终止',
 };
 
+const changeType = {
+    project: '变更立项',
+    apply: '变更申请',
+    plan: '变更方案',
+}
+
 module.exports = {
     template,
     templateId,
     status,
     tips,
+    changeType,
 };

+ 18 - 50
app/controller/budget_controller.js

@@ -166,10 +166,16 @@ module.exports = app => {
 
         async compareLoad(ctx) {
             try {
-                const gu = await ctx.service.budgetGu.getData(ctx.budget.id);
-                const gai = await ctx.service.budgetGai.getData(ctx.budget.id);
-                const yu = await ctx.service.budgetYu.getData(ctx.budget.id);
-                ctx.body = { err: 0, msg: '', data: { gu, gai, yu } }
+                const data = {};
+                if (ctx.budget.final_id) {
+                    data.final = await ctx.service.budgetFinal.getAllDataByCondition({ where: { final_id: ctx.budget.final_id } });
+                    data.finalInfo = await ctx.service.budgetFinalList.getFinal(ctx.budget.final_id);
+                } else {
+                    data.gu = await ctx.service.budgetGu.getData(ctx.budget.id);
+                    data.gai = await ctx.service.budgetGai.getData(ctx.budget.id);
+                    data.yu = await ctx.service.budgetYu.getData(ctx.budget.id);
+                }
+                ctx.body = { err: 0, msg: '', data };
             } catch (err) {
                 ctx.log(err);
                 ctx.ajaxErrorBody(err, '获取数据错误');
@@ -178,53 +184,15 @@ module.exports = app => {
         async compareFinal(ctx) {
             try {
                 const data = JSON.parse(ctx.request.body.data);
-                const final = [], helper = this.ctx.helper;
-                for (const id of data.id) {
-                    const bills = await ctx.service.ledger.getFinalData(id, ['id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
-                        'code', 'b_code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'total_price']);
-                    // 使用完成数据对比
-                    // const stage = ctx.service.stage.getLastestCompleteStage(id);
-                    // const finalBills = ctx.service.stageBillsFinal.getFinalData(id, stage.id);
-                    // ctx.helper.assignRelaData(bills, [
-                    //     { data: finalBills, fields: ['contract_tp', 'qc_tp', 'used'], prefix: 'end_', relaId: 'lid' },
-                    // ]);
-
-                    const dgnData = await ctx.service.stageBillsDgn.getDgnData(id);
-                    // 使用最新一期对比
-                    const stage = await ctx.service.stage.getLastestStage(id);
-                    if (stage.status === auditConst.stage.status.checked) {
-                        const finalBills = await ctx.service.stageBillsFinal.getFinalData({id}, stage.order);
-                        ctx.helper.assignRelaData(bills, [
-                            { data: finalBills, fields: ['contract_tp', 'qc_tp'], prefix: 'end_', relaId: 'lid' },
-                            { data: dgnData, fields: ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'], prefix: '', relaId: 'id' },
-                        ]);
-                        bills.forEach(b => {
-                            b.end_gather_tp = helper.add(b.end_qc_tp, b.end_contract_tp);
-                            delete b.end_contract_tp;
-                            delete b.end_qc_tp;
-                        });
-                    } else {
-                        await ctx.service.stage.doCheckStage(stage);
-                        const curBills = stage.readOnly
-                            ? await ctx.service.stageBills.getAuditorStageData2(id, stage.id, stage.curTimes, stage.curOrder)
-                            : await ctx.service.stageBills.getLastestStageData2(id, stage.id);
-                        const preBills = stage.order > 1 ? await ctx.service.stageBillsFinal.getFinalData({id}, stage.order - 1) : [];
-                        ctx.helper.assignRelaData(bills, [
-                            { data: curBills, fields: ['contract_tp', 'qc_tp'], prefix: '', relaId: 'lid' },
-                            { data: preBills, fields: ['contract_tp', 'qc_tp'], prefix: 'pre_', relaId: 'lid' },
-                            { data: dgnData, fields: ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'], prefix: '', relaId: 'id' },
-                        ]);
-                        bills.forEach(b => {
-                            b.end_gather_tp = helper.sum([b.qc_tp, b.contract_tp, b.pre_qc_tp, b.pre_contract_tp]);
-                            delete b.contract_tp;
-                            delete b.qc_tp;
-                            delete b.pre_contract_tp;
-                            delete b.pre_qc_tp;
-                        });
-                    }
-                    final.push(bills);
+                if (ctx.budget.final_id && data.final_id !== ctx.budget.final_id) {
+                    const final = await ctx.service.budgetFinal.getAllDataByCondition({ where: { final_id: ctx.budget.final_id } });
+                    const finalInfo = await ctx.service.budgetFinalList.getFinal(ctx.budget.final_id);
+                    ctx.body = { err: 0, msg: `决算数据已在${ctx.moment(finalInfo.update_time).format('YYYY-DD-MM HH:mm:ss')}更新,请先查看后再决定是否生成`, data: { final, finalInfo } };
+                } else {
+                    const finalInfo = await ctx.service.budgetFinalList.addFinal(ctx.budget, data.id);
+                    const final = await ctx.service.budget.doFinal(ctx.budget, finalInfo);
+                    ctx.body = { err: 0, msg: '', data: { final, finalInfo } };
                 }
-                ctx.body = { err: 0, msg: '', data: final }
             } catch (err) {
                 ctx.log(err);
                 ctx.ajaxErrorBody(err, '获取决算数据错误');

+ 14 - 6
app/controller/change_controller.js

@@ -342,8 +342,8 @@ module.exports = app => {
                     whiteList,
                     auditList,
                     changeList,
-                    tpUnit: change.tp_decimal ? change.tp_decimal : ctx.tender.info.decimal.tp,
-                    upUnit: change.up_decimal ? change.up_decimal : ctx.tender.info.decimal.up,
+                    tpUnit: change.tp_decimal !== null ? change.tp_decimal : ctx.tender.info.decimal.tp,
+                    upUnit: change.up_decimal !== null ? change.up_decimal : ctx.tender.info.decimal.up,
                     authMobile: auth_mobile,
                     shenpiConst,
                 };
@@ -792,7 +792,7 @@ module.exports = app => {
                         responseData.data = await ctx.service.changeAuditList.batchAdd(data);
                         break;
                     case 'del':
-                        await ctx.service.changeAuditList.del(data.id);
+                        await ctx.service.changeAuditList.del(data);
                         break;
                     case 'update':
                         await ctx.service.changeAuditList.save(data.updateData);
@@ -803,7 +803,7 @@ module.exports = app => {
                         responseData.data = await ctx.service.changeAuditList.getList(ctx.change.cid);
                         break;
                     case 'ledger_list':
-                        await ctx.service.changeAuditList.saveLedgerListDatas(data.updateData);
+                        await ctx.service.changeAuditList.saveLedgerListDatas(data.updateData, data.postData);
                         // 取所有工料表
                         responseData.data = { changeList: await ctx.service.changeAuditList.getList(ctx.change.cid),
                             usedList: await ctx.service.stageChange.getFinalUsedData(ctx.tender.id, ctx.change.cid) };
@@ -825,9 +825,9 @@ module.exports = app => {
                         break;
                     case 'paste_amount_rows':
                         await ctx.service.changeAuditList.saveDatas(data.updateData);
-                        let changeList = await ctx.service.changeAuditList.getList(ctx.change.cid);
+                        const changeList = await ctx.service.changeAuditList.getList(ctx.change.cid);
                         const auditList2 = await ctx.service.changeAudit.getListGroupByTimes(ctx.change.cid, ctx.change.times);
-                        changeList = JSON.parse(JSON.stringify(changeList.sort())).sort().sort();
+                        // changeList = JSON.parse(JSON.stringify(changeList.sort())).sort().sort();
                         for (const cl of changeList) {
                             const audit_amount = cl.audit_amount !== null && cl.audit_amount !== '' ? cl.audit_amount.split(',') : '';
                             // 清单表页赋值
@@ -843,6 +843,13 @@ module.exports = app => {
                     case 'update_tp':
                         await ctx.service.change.saveInfo({ total_price: data.updateData });
                         break;
+                    case 'order_by':
+                        const result = await ctx.service.change.saveOrderBy(data.updateData, data.newLedgerList);
+                        responseData.data = result;
+                        break;
+                    case 'changeOrder':
+                        await ctx.service.changeAuditList.changeOrder(data.postData);
+                        break;
                     default: throw '参数有误';
                 }
 
@@ -3564,6 +3571,7 @@ module.exports = app => {
                 ctx.redirect(ctx.request.header.referer);
             } catch (err) {
                 this.log(err);
+                console.log(err);
                 ctx.session.postError = err.toString();
                 ctx.redirect(ctx.request.header.referer);
             }

+ 33 - 1
app/controller/dashboard_controller.js

@@ -11,6 +11,7 @@
 const auditConst = require('../const/audit');
 const officeList = require('../const/cld_office').list;
 const maintainConst = require('../const/maintain');
+const typeColMap = require('../const/advance').typeColMap;
 
 module.exports = app => {
 
@@ -45,9 +46,36 @@ module.exports = app => {
             const userPermission = pa !== undefined && pa.permission !== '' ? JSON.parse(pa.permission) : null;
             const userMsgPermission = userPermission !== null && userPermission.project_msg !== undefined && parseInt(userPermission.project_msg) === 1;
             // 获取系统通知
-            const sysMsgList = await ctx.service.message.getMsgList(ctx.session.sessionProject.id, 2, 0, 2);
+            const sysMsgList = await ctx.service.message.getMsgList(ctx.session.sessionProject.id, 1, 0, 2);
             // 获取系统维护信息
             const maintainData = await ctx.service.maintain.getDataById(1);
+            // 获取各个审批的次数及最后的审批时间
+            const shenpi_count = [
+                { count: await ctx.service.advanceAudit.getCountByChecked(ctx.session.sessionUser.accountId), name: '预付款' },
+                { count: await ctx.service.ledgerAudit.getCountByChecked(ctx.session.sessionUser.accountId), name: '台账审批' },
+                { count: await ctx.service.reviseAudit.getCountByChecked(ctx.session.sessionUser.accountId), name: '台账修订' },
+                { count: await ctx.service.stageAudit.getCountByChecked(ctx.session.sessionUser.accountId), name: '计量审批' },
+                { count: await ctx.service.changeAudit.getCountByChecked(ctx.session.sessionUser.accountId), name: '变更审批' },
+            ];
+            if (ctx.session.sessionProject.page_show.openChangeProject) shenpi_count.push({ count: await ctx.service.changeProjectAudit.getCountByChecked(ctx.session.sessionUser.accountId), name: '变更立项' });
+            if (ctx.session.sessionProject.page_show.openChangeApply) shenpi_count.push({ count: await ctx.service.changeApplyAudit.getCountByChecked(ctx.session.sessionUser.accountId), name: '变更申请' });
+            if (ctx.session.sessionProject.page_show.openChangePlan) shenpi_count.push({ count: await ctx.service.changePlanAudit.getCountByChecked(ctx.session.sessionUser.accountId), name: '变更方案' });
+            shenpi_count.push({ count: await ctx.service.materialAudit.getCountByChecked(ctx.session.sessionUser.accountId), name: '材料调差' });
+            // shenpi_count.push({ count: await ctx.service.advanceAudit.getCountByChecked(ctx.session.sessionUser.accountId), name: '预付款' });
+            const total_count = ctx.app._.sumBy(shenpi_count, 'count');
+            const shenpi_lastime = [
+                await ctx.service.advanceAudit.getLastEndTimeByChecked(ctx.session.sessionUser.accountId),
+                await ctx.service.ledgerAudit.getLastEndTimeByChecked(ctx.session.sessionUser.accountId),
+                await ctx.service.reviseAudit.getLastEndTimeByChecked(ctx.session.sessionUser.accountId),
+                await ctx.service.stageAudit.getLastEndTimeByChecked(ctx.session.sessionUser.accountId),
+                await ctx.service.changeAudit.getLastEndTimeByChecked(ctx.session.sessionUser.accountId),
+                ctx.session.sessionProject.page_show.openChangeProject ? await ctx.service.changeProjectAudit.getLastEndTimeByChecked(ctx.session.sessionUser.accountId) : null,
+                ctx.session.sessionProject.page_show.openChangeApply ? await ctx.service.changeApplyAudit.getLastEndTimeByChecked(ctx.session.sessionUser.accountId) : null,
+                ctx.session.sessionProject.page_show.openChangePlan ? await ctx.service.changePlanAudit.getLastEndTimeByChecked(ctx.session.sessionUser.accountId) : null,
+                await ctx.service.materialAudit.getLastEndTimeByChecked(ctx.session.sessionUser.accountId),
+            ];
+            const last_time = ctx.app._.max(shenpi_lastime);
+            // console.log(ctx.app._.max(shenpi_lastime), ctx.helper.calcDayNum(last_time));
             const renderData = {
                 auditTenders,
                 auditStages,
@@ -58,6 +86,9 @@ module.exports = app => {
                 auditChangeProject,
                 auditChangeApply,
                 auditChangePlan,
+                shenpi_count,
+                total_count,
+                last_day: ctx.helper.calcDayNum(last_time),
                 role: pa.role,
                 authMobile: pa.auth_mobile,
                 acLedger: auditConst.ledger,
@@ -81,6 +112,7 @@ module.exports = app => {
                 uid: ctx.session.sessionUser.accountId,
                 maintainData,
                 maintainConst,
+                typeColMap,
             };
             await this.layout('dashboard/index.ejs', renderData, 'dashboard/modal.ejs');
             await ctx.service.projectAccount.defaultUpdate({

+ 2 - 2
app/controller/ledger_controller.js

@@ -115,11 +115,11 @@ module.exports = app => {
             const tender = this.ctx.tender;
             const ledgerColumn = [
                 'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
-                'code', 'b_code', 'name', 'unit', 'unit_price',
+                'code', 'b_code', 'name', 'unit', 'unit_price', 'quantity', 'total_price',
                 'sgfh_qty', 'sgfh_expr', 'sgfh_tp', 'memo', 'drawing_code'];
             if (tender.data.measure_type === measureType.gcl.value) ledgerColumn.push('deal_qty', 'deal_tp');
             if (tender.info.display.ledger.dgnQty) ledgerColumn.push('dgn_qty1', 'dgn_qty2');
-            if (tender.info.display.ledger.clQty) ledgerColumn.push('sjcl_qty', 'qtcl_qty', 'sjcl_expr', 'qtcl_expr', 'sjcl_tp', 'qtcl_tp', 'quantity', 'total_price');
+            if (tender.info.display.ledger.clQty) ledgerColumn.push('sjcl_qty', 'qtcl_qty', 'sjcl_expr', 'qtcl_expr', 'sjcl_tp', 'qtcl_tp');
             const posColumn = ['id', 'tid', 'lid', 'name', 'position', 'porder', 'sgfh_qty', 'sgfh_expr', 'add_stage_order', 'drawing_code'];
             if (tender.info.display.ledger.clQty) posColumn.push('sjcl_qty', 'qtcl_qty', 'sjcl_expr', 'qtcl_expr', 'quantity');
             for (const field of sjsRela.ledgerCol) {

+ 77 - 6
app/controller/material_controller.js

@@ -279,6 +279,10 @@ module.exports = app => {
             if (ctx.material.status === auditConst.status.uncheck || ctx.material.status === auditConst.status.checkNo) {
                 ctx.material.auditorList = await ctx.service.materialAudit.getAuditors(ctx.material.id, ctx.material.times);
             }
+
+            // 是否已验证手机短信
+            const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+            ctx.material.authMobile = pa.auth_mobile;
         }
 
         /**
@@ -475,6 +479,7 @@ module.exports = app => {
                 // 取所有已被调用的工料清单表
                 responseData.data.materialListData = await ctx.service.materialList.getMaterialData(ctx.tender.id, ctx.material.id);
                 responseData.data.materialNotJoinListData = await ctx.service.materialListNotjoin.getAllDataByCondition({ where: { tid: ctx.tender.id, mid: ctx.material.id } });
+                responseData.data.materialSelfListData = await ctx.service.materialListSelf.getAllDataByCondition({ where: { tid: ctx.tender.id, mid: ctx.material.id } });
                 // 获取清单数据
                 responseData.data.ledger = await ctx.service.ledger.getData(ctx.tender.id);
                 responseData.data.pos = await ctx.service.pos.getPosData({ tid: ctx.tender.id });
@@ -487,11 +492,11 @@ module.exports = app => {
                     return ctx.helper.compareCode(a.b_code, b.b_code);
                 });
                 const lastMaterial = await ctx.service.material.getLastestMaterial(ctx.tender.id, true);
-                if (!lastMaterial.is_new) {
+                if (lastMaterial && !lastMaterial.is_new) {
                     const materialListData = await ctx.service.materialList.getMaterialData(ctx.tender.id, lastMaterial.id);
                     const hadBillsList = [];
                     for (const ml of materialListData) {
-                        const hb = _.find(hadBillsList, { gcl_id: ml.gcl_id, mb_id: ml.mb_id });
+                        const hb = _.find(hadBillsList, {gcl_id: ml.gcl_id, mb_id: ml.mb_id});
                         if (!hb) {
                             hadBillsList.push({
                                 gcl_id: ml.gcl_id,
@@ -504,9 +509,18 @@ module.exports = app => {
                     }
                     // 批量插入并修改is_new值
                     await ctx.service.materialListGcl.setData(lastMaterial.id, hadBillsList);
+                    // responseData.data.gclList = hadBillsList;
+                }
+                // } else {
+                // 判断是否是最新期,不是则获取的gclList是由materialList拼接而成,
+                if (lastMaterial.id === ctx.material.id) {
+                    const hadBillsList = await ctx.service.materialListGcl.getAllDataByCondition({ where: { tid: ctx.tender.id } });
                     responseData.data.gclList = hadBillsList;
                 } else {
-                    const hadBillsList = await ctx.service.materialListGcl.getAllDataByCondition({ where: { tid: ctx.tender.id } });
+                    const materialList = await this.ctx.service.materialList.getAllDataByCondition({ where: { mid: ctx.material.id, is_self: 0 } });
+                    const hadBillsList = ctx.app._.unionWith(materialList, function(item1, item2) {
+                        return item1.gcl_id === item2.gcl_id && item1.mb_id === item2.mb_id;
+                    });
                     responseData.data.gclList = hadBillsList;
                 }
                 ctx.body = responseData;
@@ -543,7 +557,7 @@ module.exports = app => {
                 // 获取gclidlist值
                 const lastMaterial = await ctx.service.material.getLastestCompleteMaterial(ctx.tender.id);
                 // responseData.data.materialListData = await ctx.service.materialList.getMaterialData(ctx.tender.id, lastMaterial.id);
-                if (!lastMaterial.is_new) {
+                if (lastMaterial && !lastMaterial.is_new) {
                     const materialListData = await ctx.service.materialList.getMaterialData(ctx.tender.id, lastMaterial.id);
                     const hadBillsList = [];
                     for (const ml of materialListData) {
@@ -564,6 +578,10 @@ module.exports = app => {
                 }
                 const hadBillsList = await ctx.service.materialListGcl.getAllDataByCondition({ where: { tid: ctx.tender.id } });
                 responseData.data.gclList = hadBillsList;
+
+                // 获取上一期的单独设置的清单明细
+                responseData.data.materialListForSelf = lastMaterial ? await ctx.service.materialList.getAllDataByCondition({ where: { tid: ctx.tender.id, mid: lastMaterial.id, is_self: 1 } }) : [];
+                responseData.data.selfList = lastMaterial ? await ctx.service.materialListSelf.getAllDataByCondition({ where: { tid: ctx.tender.id, mid: lastMaterial.id } }) : [];
                 // 获取清单设置已选清单
                 // const materialChecklistData = await ctx.service.materialChecklist.getAllDataByCondition({ where: { tid: ctx.tender.id } });
                 // responseData.data.materialChecklistData = materialChecklistData.sort(function(a, b) {
@@ -714,7 +732,8 @@ module.exports = app => {
                     msg: '',
                     data: {},
                 };
-                if (ctx.session.sessionProject.page_show.openMaterialChecklist && (data.type !== 'join' && data.type !== 'notjoin' && data.type !== 'useOther')) {
+                const notControlList = ['join', 'notjoin', 'self', 'noself', 'useOther', 'add', 'del', 'update', 'paste'];
+                if (ctx.session.sessionProject.page_show.openMaterialChecklist && ctx.app._.indexOf(notControlList, data.type) === -1) {
                     throw '清单设置功能已启动,请前往清单设置页操作清单内容';
                 }
                 switch (data.type) {
@@ -743,6 +762,12 @@ module.exports = app => {
                     case 'notjoin':
                         responseData.data = await ctx.service.materialListNotjoin.add(data.select);
                         break;
+                    case 'self':
+                        responseData.data = await ctx.service.materialListSelf.add(data.select);
+                        break;
+                    case 'noself':
+                        responseData.data = await ctx.service.materialListSelf.del(data.select.id);
+                        break;
                     case 'paste':
                         await ctx.service.materialList.saveDatas(data.updateData);
                         // 取所有工料表
@@ -975,7 +1000,7 @@ module.exports = app => {
                         if (ctx.material.readOnly) {
                             throw '无权操作';
                         }
-                        await ctx.service.material.changeRate(data.rate);
+                        await ctx.service.material.changeExponentRate(data.rate);
                         break;
                     case 'paste':
                         [ex_tp, ex_expr] = await ctx.service.materialExponent.saveDatas(data.updateData);
@@ -1474,6 +1499,52 @@ module.exports = app => {
                 ctx.body = { err: 1, msg: err.toString(), data: null };
             }
         }
+
+        /**
+         * 重新审批
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async checkAuditAgain(ctx) {
+            try {
+                if (ctx.query.confirm !== undefined && ctx.query.confirm !== '确认设置终审审批') {
+                    throw '请输入正确的文本信息';
+                }
+                if (ctx.session.sessionUser.loginStatus === 0) {
+                    const code = ctx.query.code;
+                    const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                    if (!pa.auth_mobile) {
+                        throw '未绑定手机号';
+                    }
+                    const cacheKey = 'smsCode:' + ctx.session.sessionUser.accountId;
+                    const cacheCode = await app.redis.get(cacheKey);
+                    if (cacheCode === null || code === undefined || cacheCode !== (code + pa.auth_mobile)) {
+                        throw '验证码不正确!';
+                    }
+                }
+
+                if ((ctx.material.auditors[ctx.material.auditors.length - 1].aid === ctx.session.sessionUser.accountId || (ctx.query.confirm === '确认设置终审审批' && ctx.session.sessionUser.is_admin)) && ctx.material.status === auditConst.status.checked && ctx.material.order === ctx.material.highOrder) {
+                    await ctx.service.materialAudit.checkAgain(ctx.material.id, ctx.material.times);
+                    // ctx.redirect(ctx.request.header.referer);
+                    ctx.body = {
+                        err: 0,
+                        url: ctx.request.header.referer,
+                        msg: '',
+                    };
+                } else {
+                    throw '您无权进行该操作';
+                }
+            } catch (err) {
+                this.log(err);
+                // ctx.session.postError = err.toString();
+                // ctx.redirect(ctx.request.header.referer);
+                ctx.body = {
+                    err: 1,
+                    // url: ctx.request.header.referer,
+                    msg: err,
+                };
+            }
+        }
     }
 
     return MaterialController;

+ 11 - 3
app/controller/profile_controller.js

@@ -103,6 +103,11 @@ module.exports = app => {
                 // 验证数据
                 const passwordRule = ctx.service.projectAccount.rule('modifyPassword');
                 ctx.helper.validate(passwordRule);
+                // 判断新密码的强度
+                const reg = /^(?![0-9]+$)(?![a-zA-Z]+$).{6,16}$/;
+                if (!reg.test(newPassword)) {
+                    throw '请设置至少包含数字和字母的新密码';
+                }
 
                 const result = await ctx.service.projectAccount.modifyPassword(accountId, password, newPassword);
                 if (!result) {
@@ -111,6 +116,7 @@ module.exports = app => {
                 this.setMessage('修改密码成功', this.messageType.SUCCESS);
                 ctx.redirect('/logout');
             } catch (error) {
+                ctx.session.postError = error.toString();
                 this.setMessage(error.toString(), this.messageType.ERROR);
                 ctx.redirect(ctx.request.header.referer);
             }
@@ -469,14 +475,16 @@ module.exports = app => {
             const accountData = await ctx.service.projectAccount.getDataByCondition({ id: sessionUser.accountId });
 
             // 获取修改密码的字段规则
-            const passwordRule = ctx.service.projectAccount.rule('modifyPassword');
-            const passwordJsValidator = await this.jsValidator.convert(passwordRule).setSelector('#password-form').build();
+            // const passwordRule = ctx.service.projectAccount.rule('modifyPassword');
+            // const passwordJsValidator = await this.jsValidator.convert(passwordRule).setSelector('#password-form').build();
+
+            // console.log(passwordJsValidator);
 
             // 获取登录日志
             const loginLogging = await ctx.service.loginLogging.getLoginLogs(ctx.session.sessionProject.id, ctx.session.sessionUser.accountId);
             const renderData = {
                 accountData,
-                passwordJsValidator,
+                // passwordJsValidator,
                 loginLogging,
                 loginWay,
             };

+ 2 - 3
app/controller/report_archive_controller.js

@@ -581,7 +581,7 @@ module.exports = app => {
                 if (archiveEncryptions.length > 0) {
                     archiveEncryptionList = JSON.parse(archiveEncryptions[0].content);
                 }
-            } else {
+            } else if (stageList.length > 0 && stageList[0].status === auditConst.stage.status.checked) {
                 // console.log('stageList[0].id: ' + stageList[0].id);
                 // const archives = await ctx.service.rptArchive.getPrjStgArchive(tender.data.project_id, stageList[stageList.length - 1].id);
                 let archives = [];
@@ -592,8 +592,7 @@ module.exports = app => {
                         break;
                     }
                 }
-
-
+                ctx.stage = ctx.stage ? ctx.stage : stageList[stageList.length - 1];
                 const archiveEncryptions = await ctx.service.rptArchiveEncryption.getPrjStgArchiveEncryption(tender.data.project_id, ctx.stage.id);
                 // stage_id = stageList[0].id;
                 // stage_order = stageList[0].order;

+ 210 - 34
app/controller/report_controller.js

@@ -27,13 +27,19 @@ const needCustomTables = [
     'mem_material_sum_gl',
 ];
 
+const STD_COMP_STAMP_SIZE_WIDTH = Math.round(5 * 96 / 2.54); // 公章标准尺寸(宽4.2厘米)转成像素
+const STD_COMP_STAMP_SIZE_HEIGHT = STD_COMP_STAMP_SIZE_WIDTH; // 公章标准尺寸(高4.2厘米)转成像素
+const NORMAL_SIGN_STR = 'normal_sign';
+const COMPANY_SIGN_STR = 'company_stamp';
+const PRIVATE_SIGN_STR = 'private_stamp';
+
 module.exports = app => {
     class ReportController extends app.BaseController {
 
         /**
          * 获取审批界面所需的 原报、审批人数据等
-         * @param ctx
-         * @return {Promise<void>}
+         * @param {Object} ctx - egg全局context
+         * @return {void}
          * @private
          */
         async _getStageAuditViewData(ctx) {
@@ -85,11 +91,8 @@ module.exports = app => {
                 const custTreeNodes = await ctx.service.rptTreeNodeCust.getCustFoldersByUserId(this.ctx.session.sessionUser.accountId);
                 const custCfg = await ctx.service.rptCustomizeCfg.getCustomizeCfgByUserId('Administrator');
                 const stageList = await ctx.service.stage.getValidStagesShort(tender.id);
+                const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: tender.data.project_id } }); // 找公司章用的
                 const isAdmin = ctx.session.sessionUser.is_admin;
-                // console.log(stage);
-                // console.log(stageList);
-                // console.log('ctx.stage.id: ' + ctx.stage.id);
-                // console.log('ctx.stage.stage_id: ' + ctx.stage.stage_id);
                 // console.log('this.ctx.session.sessionUser.accountId: ' + this.ctx.session.sessionUser.accountId);
                 if (stage && stage.status === auditConst.stage.status.uncheck) {
                     // 得判断账号是否在审核人列表中(不含原报)
@@ -97,8 +100,6 @@ module.exports = app => {
                         stage.auditorList = await ctx.service.stageAudit.getAuditors(ctx.stage.id, ctx.stage.times);
                     }
                     let isAudit = false;
-                    // console.log('stage.auditorList');
-                    // console.log(stage.auditorList);
                     for (const audit of stage.auditorList) {
                         if (audit.aid === this.ctx.session.sessionUser.accountId) {
                             isAudit = true;
@@ -116,8 +117,6 @@ module.exports = app => {
                                 stageList[idx].auditorList = await ctx.service.stageAudit.getAuditors(stageList[idx].id, stageList[idx].times);
                             }
                             let isAudit = false;
-                            // console.log('stageList[idx].auditorList');
-                            // console.log(stageList[idx].auditorList);
                             for (const audit of stageList[idx].auditorList) {
                                 if (audit.aid === this.ctx.session.sessionUser.accountId) {
                                     isAudit = true;
@@ -195,7 +194,7 @@ module.exports = app => {
                     customSelects.stageFlow.forEach(x => {
                         if (!x) return;
                         x.visible = (!ctx.session.sessionUser.is_admin && ctx.session.sessionProject.page_show.individualSign === 1)
-                            ? x.aid === ctx.session.sessionUser.accountId : true
+                            ? x.aid === ctx.session.sessionUser.accountId : true;
                     });
                 } else {
                     customSelects.audit_select = [];
@@ -281,6 +280,7 @@ module.exports = app => {
                 const renderData = {
                     accountGroup: newAccountGroup,
                     accountList,
+                    unitList: JSON.stringify(unitList),
                     tender: tender.data,
                     tenderInfo: tender.info,
                     rpt_tpl_data: JSON.stringify(treeNodes),
@@ -319,6 +319,7 @@ module.exports = app => {
                     lastAuditor,
                     rpt_id: ctx.query.rpt_id,
                     isAdmin,
+                    OSS_PATH: ctx.app.config.fujianOssPath,
                 };
                 await this.layout('report/index.ejs', renderData, 'report/rpt_all_popup.ejs');
                 // await this.layout('report/index.ejs', renderData);
@@ -396,7 +397,7 @@ module.exports = app => {
             if (!params.stage_select && copyCustomSelect) delete copyCustomSelect.stage_select;
             if (!params.material_sum_select && copyCustomSelect) delete copyCustomSelect.material_sum_select;
             if (params.change_select) {
-                copyCustomSelect = { tid: params.tender_id, rid: params.rpt_tpl_id, sid: -1, change_select: params.change_select};
+                copyCustomSelect = { tid: params.tender_id, rid: params.rpt_tpl_id, sid: -1, change_select: params.change_select };
             }
 
             const pageRst = await getAllPagesCommon(ctx, rptTpl, params, JV.PAGING_OPTION_NORMAL, JV.OUTPUT_TYPE_NORMAL, this.app.baseDir, copyCustomSelect);
@@ -418,6 +419,7 @@ module.exports = app => {
             const stgAuditForOrg = await ctx.service.stageAudit.getStageAudit(params.stage_id, 1);
             // console.log('after role stage!');
             // console.log(roleRel);
+            mergeStampSignature(ctx, params.stage_status, pageRst, roleRel[0]);
             mergeTextSignature(params.isTextSignature, params.stage_status, pageRst, roleRel[0]);
             await encodeSignatureDataUri(roleRel, this.app.baseDir);
             if (params.getPicFlag) {
@@ -427,7 +429,7 @@ module.exports = app => {
             stageFlow.forEach(x => {
                 if (!x) return;
                 x.visible = (!ctx.session.sessionUser.is_admin && ctx.session.sessionProject.page_show.individualSign === 1)
-                    ? x.aid === ctx.session.sessionUser.accountId : true
+                    ? x.aid === ctx.session.sessionUser.accountId : true;
             });
 
             // console.log('encodeSignatureDataUri!');
@@ -532,6 +534,7 @@ module.exports = app => {
             });
             // console.log('roleRel: ');
             // console.log(roleRel);
+            mergeStampSignature(ctx, params.stage_status, pageRstArr, roleRel, params.rpt_ids);
             mergeTextSignature(params.isTextSignature, params.stage_status, pageRstArr, roleRel, params.rpt_ids);
 
             await encodeSignatureDataUri(roleRel, this.app.baseDir);
@@ -600,6 +603,7 @@ module.exports = app => {
                 }
             }
 
+            mergeStampSignature(ctx, params.stage_status, pageRstArr, roleRelArr, params.rpt_ids);
             mergeTextSignature(params.isTextSignature, params.stage_status, pageRstArr, roleRelArr, params.rpt_ids);
 
             await this.ctx.helper.recursiveMkdirSync(baseDir + '/app/public/download');
@@ -787,6 +791,7 @@ module.exports = app => {
                     waterMarkStr = await getWatermarkPicData(pageRstArr[0], this.app.baseDir);
                 }
             }
+            mergeStampSignature(ctx, params.stage_status, pageRstArr, roleRelArr, params.rpt_ids);
             mergeTextSignature(params.isTextSignature, params.stage_status, pageRstArr, roleRelArr, params.rpt_ids);
             await this.ctx.helper.recursiveMkdirSync(this.app.baseDir + '/app/public/download');
             const runnableRst = [];
@@ -960,6 +965,7 @@ async function getAllPagesCommon(ctx, rptTpl, params, option, outputType, baseDi
         }
         // fsUtil.writeObjToFile(pageRst, 'D:/GitHome/temp/计量testBuiltPageResult.jsp');
         // console.log(pageRst);
+        pageRst.id = params.rpt_tpl_id;
         return pageRst;
     } catch (ex) {
         console.log('报表数据异常, tender id: ' + params.tender_id);
@@ -1085,6 +1091,7 @@ async function getMultiRptsCommon(ctx, params, outputType, baseDir) {
             } else {
                 pageRst = printCom.outputAsPreviewPage(rptTpl, defProperties);
             }
+            pageRst.id = rptTpl.id;
             rptPageRstArray.push(pageRst);
             // 注意:这里需要把备份数据assign回去!!!
             // if (tplIdx < rptTpls.length - 1) {
@@ -1471,32 +1478,39 @@ function mergeTextSignature(isTxtSignature, status, pageData, singleRoleRel, rpt
                 const deleteSCellsIdx = [];
                 for (let scIdx = 0; scIdx < page.signature_cells.length; scIdx++) {
                     const sCell = page.signature_cells[scIdx];
-                    sCell.Value = ''; // 这里要先清除原有信息
                     if (sCell.signature_name !== JV.SIGNATURE_NAME_DUMMY) {
-                        // console.log(sCell);
+                        sCell.Value = ''; // 这里要先清除原有信息
                         sCell.path = '';
                         sCell.pic = '';
-                    }
-                    if (status === 3) {
-                        for (const role_rel of roleRelContent) {
-                            if (sCell.signature_name === role_rel.signature_name) {
-                                sCell.Value = role_rel.user_name; // 只有审核通过了才需要文本签名内容,但不管如何,都不影响迁移
-                                break;
+                        let needTransferText = true;
+                        if (status === 3) {
+                            for (const role_rel of roleRelContent) {
+                                if (sCell.signature_name === role_rel.signature_name) {
+                                    sCell.Value = role_rel.user_name; // 只有审核通过了才需要文本签名内容
+                                    if (role_rel.sign_output && role_rel.sign_output.indexOf(NORMAL_SIGN_STR) < 0) {
+                                        // 用户选择不签名,则不迁移
+                                        needTransferText = false;
+                                    }
+                                    break;
+                                }
                             }
                         }
+                        if (needTransferText) {
+                            const newCell = {
+                                font: 'Footer',
+                                control: sCell.control,
+                                style: sCell.style,
+                                Value: sCell.Value,
+                                area: { Left: sCell.area.Left, Right: sCell.area.Right, Top: sCell.area.Top, Bottom: sCell.area.Bottom },
+                            };
+                            page.cells.push(newCell); // 迁移
+                            deleteSCellsIdx.push(scIdx);
+                        }
                     }
-                    // 无论怎么处理,都得迁移
-                    const newCell = {
-                        font: 'Footer',
-                        control: sCell.control,
-                        style: sCell.style,
-                        Value: sCell.Value,
-                        area: { Left: sCell.area.Left, Right: sCell.area.Right, Top: sCell.area.Top, Bottom: sCell.area.Bottom },
-                    };
-                    page.cells.push(newCell); // 迁移
-                    deleteSCellsIdx.push(scIdx);
                 }
                 // 删除 page.signature_cells 签名(草图不能删);
+                // console.log('isDeleteSignCell: ' + isDeleteSignCell);
+                // console.log(deleteSCellsIdx);
                 if (isDeleteSignCell) {
                     for (let dIdx = deleteSCellsIdx.length - 1; dIdx >= 0; dIdx--) {
                         page.signature_cells.splice(deleteSCellsIdx[dIdx], 1);
@@ -1510,12 +1524,12 @@ function mergeTextSignature(isTxtSignature, status, pageData, singleRoleRel, rpt
             pageData.forEach((dtlPage, index) => {
                 let dftRolRel = [];
                 const rpt_id = rpt_ids[index];
-                for (let roleRel of singleRoleRel) {
+                for (const roleRel of singleRoleRel) {
                     if (roleRel.rpt_id === rpt_id) {
                         dftRolRel = roleRel;
                     }
                 }
-                // console.log(rptTpls[rtIdx].id)
+                // console.log(dftRolRel);
                 _mergeSingle(dtlPage, dftRolRel, true);
             });
         } else {
@@ -1524,6 +1538,152 @@ function mergeTextSignature(isTxtSignature, status, pageData, singleRoleRel, rpt
     }
 }
 
+// 因机制问题,此方法必须在mergeTextSignature之前调用
+async function mergeStampSignature(ctx, status, pageData, singleRoleRel, rpt_ids) {
+    const dupPicPaths = [];
+    const _getMaxRect = function(page) {
+        const rect = [100000000, 100000000, 0, 0];
+        for (const cell of page.cells) {
+            if (rect[0] > cell.area.Left) {
+                rect[0] = cell.area.Left;
+            }
+            if (rect[1] > cell.area.Top) {
+                rect[1] = cell.area.Top;
+            }
+            if (rect[2] < cell.area.Right) {
+                rect[2] = cell.area.Right;
+            }
+            if (rect[3] < cell.area.Bottom) {
+                rect[3] = cell.area.Bottom;
+            }
+        }
+        return rect;
+    };
+    const createStampArea = function(orgCell, controls, maxRect) {
+        const ctrl = controls[orgCell.control];
+        const rst = { Left: orgCell.area.Left, Right: orgCell.area.Right, Top: orgCell.area.Top, Bottom: orgCell.area.Bottom };
+        let pLeft = orgCell.area.Left,
+            pTop = orgCell.area.Top;
+        switch (ctrl[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_HORIZON]]) {
+            case JV.OUTPUT_ALIGN.H[JV.H_ALIGN_IDX_LEFT]:
+                pLeft = orgCell.area.Left;
+                break;
+            case JV.OUTPUT_ALIGN.H[JV.H_ALIGN_IDX_CENTER]:
+                pLeft = (orgCell.area.Left + orgCell.area.Right - STD_COMP_STAMP_SIZE_WIDTH) / 2;
+                break;
+            case JV.OUTPUT_ALIGN.H[JV.H_ALIGN_IDX_RIGHT]:
+                pLeft = orgCell.area.Right - STD_COMP_STAMP_SIZE_WIDTH;
+                break;
+            default:break;
+        }
+        switch (ctrl[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]]) {
+            case JV.OUTPUT_ALIGN.H[JV.V_ALIGN_IDX_TOP]:
+                pTop = orgCell.area.Top;
+                break;
+            case JV.OUTPUT_ALIGN.H[JV.V_ALIGN_IDX_CENTER]:
+                pTop = (orgCell.area.Top + orgCell.area.Bottom - STD_COMP_STAMP_SIZE_HEIGHT) / 2;
+                break;
+            case JV.OUTPUT_ALIGN.H[JV.V_ALIGN_IDX_BOTTOM]:
+                pTop = orgCell.area.Bottom - STD_COMP_STAMP_SIZE_HEIGHT;
+                break;
+            default:break;
+        }
+        rst.Left = pLeft;
+        rst.Top = pTop;
+        rst.Right = pLeft + STD_COMP_STAMP_SIZE_WIDTH;
+        rst.Bottom = pTop + STD_COMP_STAMP_SIZE_WIDTH;
+        // 最后一步,如超过报表范围,则要调整坐标
+        // if (rst.Left < maxRect[0]) {
+        //     const width = maxRect[0] - rst.Left;
+        //     rst.Left += width;
+        //     rst.Right += width;
+        // }
+        // if (rst.Top < maxRect[1]) {
+        //     const height = maxRect[1] - rst.Top;
+        //     rst.Top += height;
+        //     rst.Bottom += height;
+        // }
+        // if (rst.Right > maxRect[2]) {
+        //     const width = maxRect[2] - rst.Right; // 负
+        //     rst.Left += width;
+        //     rst.Right += width;
+        // }
+        // if (rst.Bottom > maxRect[3]) {
+        //     const height = maxRect[3] - rst.Bottom;
+        //     rst.Top += height;
+        //     rst.Bottom += height;
+        // }
+        return rst;
+
+    };
+    const _mergeSingleStamp = async function(_page, _roleRelList) {
+        let roleRelContent = [];
+        if (_roleRelList && _roleRelList.rel_content !== null && _roleRelList.rel_content !== undefined && _roleRelList.rel_content !== '') {
+            roleRelContent = JSON.parse(_roleRelList.rel_content);
+        }
+        for (const page of _page.items) {
+            const maxRect = _getMaxRect(page);
+            if (page.signature_cells) {
+                const newStampCells = [];
+                for (let scIdx = 0; scIdx < page.signature_cells.length; scIdx++) {
+                    const sCell = page.signature_cells[scIdx];
+                    for (const role_rel of roleRelContent) {
+                        if (sCell.signature_name === role_rel.signature_name) {
+                            if (Array.isArray(role_rel.sign_output) && role_rel.sign_output.length > 0) {
+                                for (const signType of role_rel.sign_output) {
+                                    switch (signType) {
+                                        case COMPANY_SIGN_STR:
+                                        case PRIVATE_SIGN_STR:
+                                            // 创建一个新的cell
+                                            let stampPath = (signType === COMPANY_SIGN_STR) ? role_rel.company_stamp_path : role_rel.private_stamp_path;
+                                            stampPath = ctx.app.config.fujianOssPath + stampPath;
+                                            if (dupPicPaths.indexOf(stampPath) < 0) {
+                                                dupPicPaths.push(stampPath);
+                                                // await _chkRawPicSizeOSS(ctx, stampPath);
+                                            }
+                                            const newStampCell = {
+                                                signature_name: JV.SIGNATURE_NAME_DUMMY,
+                                                control: sCell.control,
+                                                style: sCell.style,
+                                                path: stampPath,
+                                                isStamp: true,
+                                                maxRect,
+                                                orgArea: sCell.area,
+                                                area: createStampArea(sCell, _page[JV.NODE_CONTROL_COLLECTION], maxRect),
+                                            };
+                                            newStampCells.push(newStampCell);
+                                            break;
+                                        default: break;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                if (newStampCells.length > 0) {
+                    page.signature_cells = page.signature_cells.concat(newStampCells);
+                }
+            }
+        }
+    };
+    if (status === 3) {
+        if (pageData instanceof Array) {
+            pageData.forEach((dtlPage, index) => {
+                let dftRolRel = [];
+                const rpt_id = rpt_ids[index];
+                for (const roleRel of singleRoleRel) {
+                    if (roleRel.rpt_id === rpt_id) {
+                        dftRolRel = roleRel;
+                    }
+                }
+                _mergeSingleStamp(dtlPage, dftRolRel);
+            });
+        } else {
+            _mergeSingleStamp(pageData, singleRoleRel);
+        }
+    }
+}
+
 function mergeSignAudit(pageData, currRoleRelList, currAuditList) {
     for (const page of pageData.items) {
         if (page.signature_audit_cells) {
@@ -1673,6 +1833,22 @@ async function _chkRawPicSize(pageData, baseDir) {
     return rst;
 }
 
+async function _chkRawPicSizeOSS(ctx, picPath) {
+    // 计量的草图、签章都放在OSS服务器上,需要另外的方式来获取,考虑到效率问题,机制上得有所变化
+    const rst = [0, 0];
+    try {
+        const pf = await ctx.app.signPdfOss.get(picPath);
+        console.log('get pic successfully!');
+        console.log(pf);
+    } catch (ex) {
+        console.log(ex);
+    } finally {
+        console.log('finally!');
+    }
+    return rst;
+}
+
+
 function _resetPageDataByBreaks(pageDataArr, breakAmt, rpt_names, newRptNames) {
     const rst = [];
     for (let pi = 0; pi < pageDataArr.length; pi++) {
@@ -1706,4 +1882,4 @@ function _resetPageDataByBreaks(pageDataArr, breakAmt, rpt_names, newRptNames) {
         }
     }
     return rst;
-};
+}

+ 11 - 0
app/controller/setting_controller.js

@@ -476,6 +476,11 @@ module.exports = app => {
                 // 获取验证规则
                 const rule = ctx.service.projectAccount.rule('add');
                 ctx.validate(rule);
+                // 判断新密码的强度
+                const reg = /^(?![0-9]+$)(?![a-zA-Z]+$).{6,16}$/;
+                if (!reg.test(ctx.request.body.password)) {
+                    throw '请设置至少包含数字和字母的密码';
+                }
                 ctx.request.body.project_id = projectData.id;
                 const result = await ctx.service.projectAccount.save(ctx.request.body);
                 if (!result) {
@@ -486,6 +491,7 @@ module.exports = app => {
                 ctx.redirect('/' + ctx.controllerName + '/user');
             } catch (error) {
                 console.log(error);
+                ctx.session.postError = error.toString();
                 this.setMessage(error.toString(), this.messageType.ERROR);
                 ctx.redirect(ctx.request.header.referer);
             }
@@ -541,6 +547,11 @@ module.exports = app => {
                 if (isNaN(accountId) || accountId <= 0 || password.length < 6) {
                     throw '参数错误';
                 }
+                // 判断新密码的强度
+                const reg = /^(?![0-9]+$)(?![a-zA-Z]+$).{6,16}$/;
+                if (!reg.test(password)) {
+                    throw '请设置至少包含数字和字母的密码';
+                }
                 const result = await ctx.service.projectAccount.resetPassword(accountId, password, account);
                 if (!result) {
                     throw '重置密码失败!';

+ 96 - 8
app/controller/stage_controller.js

@@ -25,6 +25,7 @@ const sendToWormhole = require('stream-wormhole');
 const billsPosConvert = require('../lib/bills_pos_convert');
 const fs = require('fs');
 const stdConst = require('../const/standard');
+const LzString = require('lz-string');
 
 module.exports = app => {
     class StageController extends app.BaseController {
@@ -346,6 +347,12 @@ module.exports = app => {
                         case 'change':
                             responseData.data.changeData = await this._getStageChangeData(ctx);
                             break;
+                        case 'changeBills':
+                            responseData.data.changeBills = await this.ctx.service.changeAuditList.checkedChangeBills(ctx.stage.tid);
+                            break;
+                        case 'minus_change':
+                            responseData.data.minus_change = await this.ctx.service.stageChangeFinal.getPreMinusChange(ctx.stage.tid, ctx.stage.order);
+                            break;
                         case 'import_change':
                             responseData.data.import_change = await this.ctx.service.stageImportChange.getStageImportData(this.ctx.stage);
                             break;
@@ -497,9 +504,7 @@ module.exports = app => {
                 }
                 const bills = data.bills ? data.bills : await ctx.service.ledger.getDataById(data.pos.lid);
                 const pos = data.pos;
-                const changes = ctx.stage.readOnly
-                    ? await ctx.service.change.getAuditValidChanges(ctx.tender.id, bills, null, ctx.stage.curTimes, ctx.stage.curOrder)
-                    : await ctx.service.change.getValidChanges(ctx.tender.id, bills);
+                const changes = await ctx.service.change.getValidChanges(ctx.tender, ctx.stage, bills);
                 const useChanges = ctx.stage.readOnly
                     ? await ctx.service.stageChange.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.curTimes, ctx.stage.curOrder, bills.id, pos ? pos.id : -1)
                     : await ctx.service.stageChange.getLastestStageData(ctx.tender.id, ctx.stage.id, bills.id, pos ? pos.id : -1);
@@ -522,18 +527,22 @@ module.exports = app => {
                 if (!data.target || (!data.target.bills && !data.target.pos) || !data.change) {
                     throw '调用变更令数据错误';
                 }
-                let result;
+                let result, minus;
                 if (data.target.pos) {
                     result = await ctx.service.stageChange.posChange(data.target.pos, data.change);
                     result.change = { target: { lid: data.target.pos.lid, pid: data.target.pos.id } };
                     result.change.data = await ctx.service.stageChange.getLastestStageData(ctx.tender.id,
                         ctx.stage.id, data.target.pos.lid, data.target.pos.id);
+                    minus = await this.ctx.service.stageChange.getBillsMinusQty(ctx.stage, data.target.pos.lid);
                 } else {
                     result = await ctx.service.stageChange.billsChange(data.target.bills, data.change);
                     result.change = { target: { lid: data.target.bills.id, pid: '-1' } };
                     result.change.data = await ctx.service.stageChange.getLastestStageData(ctx.tender.id,
                         ctx.stage.id, data.target.bills.id, '-1');
+                    minus = await this.ctx.service.stageChange.getBillsMinusQty(ctx.stage, data.target.bills.id);
                 }
+                const bills = result.bills.curStageData.find(x => { return x.lid === minus.lid });
+                if (bills) bills.minus_qc_qty = minus.qty;
                 await ctx.service.stage.updateCheckCalcFlag(ctx.stage, true);
                 await ctx.service.stage.updateCacheTime(ctx.stage.id);
                 ctx.body = { err: 0, msg: '', data: result };
@@ -991,10 +1000,11 @@ module.exports = app => {
                     switch (cd.cType) {
                         case 1:
                             const tp = await ctx.service.stageBills.getSumTotalPriceGcl(ctx.stage, cd.filter);
-                            assignStageData(cd, tp);
+                            const preTp = await ctx.service.stageBillsFinal.getSumTotalPriceGcl(ctx.stage.tid, ctx.stage.order - 1, cd.filter);
+                            assignStageData(cd, tp, preTp);
                             break;
                         case 11:
-                            assignStageData(cd, await ctx.service.stageBills.getSumTotalPriceGcl(ctx.stage));
+                            assignStageData(cd, await ctx.service.stageBills.getSumTotalPriceGcl(ctx.stage), await ctx.service.stageBillsFinal.getSumTotalPriceGcl(ctx.stage.tid, ctx.stage.order - 1));
                             break;
                         case 21:
                             const sum = _.find(calcDetail, { cType: 11 });
@@ -1004,7 +1014,7 @@ module.exports = app => {
                             }
                             break;
                         case 31:
-                            assignStageData(cd, await ctx.service.stageBills.getSumTotalPriceNotGcl(ctx.stage));
+                            assignStageData(cd, await ctx.service.stageBills.getSumTotalPriceNotGcl(ctx.stage), await ctx.service.stageBillsFinal.getSumTotalPriceNotGcl(ctx.stage.tid, ctx.stage.order - 1));
                             break;
                         case 41:
                             const sum1 = _.find(calcDetail, { cType: 11 });
@@ -1407,6 +1417,16 @@ module.exports = app => {
                 const ledgerData = await this._getStageLedgerData(ctx);
                 const posData = await this._getStagePosData(ctx);
                 const changeData = await this._getStageChangeData(ctx);
+                const preMinusChange = await this.ctx.service.stageChangeFinal.getPreMinusChange(ctx.stage.tid, ctx.stage.order);
+                const minusChange = changeData.filter(x => { return x.minus });
+                this.ctx.helper.assignSumRelaData(ledgerData, 'id', [
+                    {data: preMinusChange, fields: [{source: 'qty', target: 'pre_minus_qc_qty'}], relaField: 'lid'},
+                    {data: minusChange, fields: [{source: 'qty', target: 'minus_qc_qty'}], relaField: 'lid'},
+                ]);
+                this.ctx.helper.assignSumRelaData(posData, 'id', [
+                    {data: preMinusChange, fields: [{source: 'qty', target: 'pre_minus_qc_qty'}], relaField: 'pid'},
+                    {data: minusChange, fields: [{source: 'qty', target: 'minus_qc_qty'}], relaField: 'pid'},
+                ]);
                 const convert = new billsPosConvert(ctx);
                 convert.loadData(ledgerData, posData, changeData);
                 const result = convert.convert(data.filter);
@@ -1483,7 +1503,6 @@ module.exports = app => {
                     const filepath = `app/public/upload/${this.ctx.tender.id}/stage/fujian_${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);
-                    // console.log(await fs.existsSync(path.resolve(this.app.baseDir, 'app', filepath)));
                     // const fileInfo = path.parse(stream.filename);
                     // const fileName = 'stage' + create_time + '_' + index + fileInfo.ext;
                     // 保存文件
@@ -2040,6 +2059,75 @@ module.exports = app => {
                 ctx.body = { err: 1, msg: err.toString(), data: null };
             }
         }
+
+        async stashList(ctx) {
+            try {
+                if (!ctx.session.sessionUser.is_admin) throw '您无权查看';
+                const data = await ctx.service.stageStash.getValidStash(ctx.tender.id);
+                data.forEach(x => {
+                    x.recover = ctx.stage.status === auditConst.status.uncheck;
+                });
+                ctx.body = { err: 0, msg: '', data };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '加载暂存计量数据失败');
+            }
+        }
+
+        async addStash(ctx) {
+            try {
+                if (!ctx.session.sessionUser.is_admin) throw '您无权操作';
+                const result = await ctx.service.stageStash.addStash(ctx.stage, '');
+                const stash = await ctx.service.stageStash.getDataById(result);
+                stash.recover = ctx.stage.status === auditConst.status.uncheck;
+                ctx.body = { err: 0, msg: '', data: stash };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存当前计量数据失败');
+            }
+        }
+
+        async recoverStash(ctx) {
+            try {
+                if (!ctx.session.sessionUser.is_admin) throw '您无权操作';
+                if (!ctx.stage.status === auditConst.status.uncheck) throw '仅新增期且未上报时,可从暂存计量中导入数据';
+
+                const data = JSON.parse(ctx.request.body.data);
+                await ctx.service.stageStash.recover(ctx.stage, data.id);
+                await ctx.service.stage.updateCheckCalcFlag(ctx.stage, true);
+                await ctx.service.stage.updateCacheTime(ctx.stage.id);
+                ctx.body = { err: 0, msg: '', data: null };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存当前计量数据失败');
+            }
+        }
+
+        async delStash(ctx) {
+            try {
+                if (!ctx.session.sessionUser.is_admin) throw '您无权操作';
+
+                const data = JSON.parse(ctx.request.body.data);
+                await ctx.service.stageStash.delStash(data.id);
+                ctx.body = { err: 0, msg: '', data: null };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '删除数据失败');
+            }
+        }
+
+        async importStageSheet(ctx) {
+            try {
+                const compressData = ctx.request.body.data;
+                const data = JSON.parse(LzString.decompressFromUTF16(compressData));
+
+                await ctx.service.stage.updateCheckCalcFlag(ctx.stage, true);
+                await ctx.service.stage.updateCacheTime(ctx.stage.id);
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '导入计量台账数据错误');
+            }
+        }
     }
 
     return StageController;

+ 17 - 3
app/controller/tender_controller.js

@@ -24,6 +24,7 @@ const scheduleConst = require('../const/schedule');
 const changeConst = require('../const/change');
 const tenderInfoModel = require('../lib/tender_info');
 const mapConst = require('../const/map');
+const advanceConst = require('../const/advance');
 
 module.exports = app => {
 
@@ -416,16 +417,16 @@ module.exports = app => {
                     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.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);
                     tender.other_ratio = Math.max(0, 100 - tender.pre_ratio - tender.cur_ratio);
                     tender.end_yf_tp = ctx.helper.add(lastStage.yf_tp, lastStage.pre_yf_tp);
                     tender.end_sf_tp = ctx.helper.add(lastStage.sf_tp, lastStage.pre_sf_tp);
-                    const change_tp = await ctx.service.change.getChangeTp(tender.id);
-                    tender.change_tp = change_tp;
                     tender.undone_tp = ctx.helper.sub(ctx.helper.sub(ctx.helper.add(tender.total_price, change_tp), tender.end_contract_tp), tender.end_qc_tp);
                     if (lastStage.status === auditConst.stage.status.uncheck) {
                         const status_name = await this.ctx.service.projectAccount.getAccountInfoById(lastStage.user_id);
@@ -1407,6 +1408,18 @@ module.exports = app => {
                                 orders: [['in_time', 'desc']],
                             });
                             break;
+                        case 'advance':
+                            const advance = await ctx.service.advance.getAllDataByCondition({
+                                columns: [ 'id', 'order', 'status', 'selected', 'type' ],
+                                where,
+                                orders: [['type', 'asc'], ['create_time', 'desc']],
+                            });
+                            advance.forEach(x => {
+                                x.statusStr = auditConst.advance.statusString[x.status];
+                                x.typeStr = advanceConst.typeColMap[x.type].text;
+                            });
+                            responseData.data[f] = advance;
+                            break;
                         default:
                             throw '未知数据类型';
                     }
@@ -1426,6 +1439,7 @@ module.exports = app => {
                 if (data.change_apply) await this.ctx.service.changeApply.defaultUpdateRows(data.change_apply);
                 if (data.change_project) await this.ctx.service.changeProject.defaultUpdateRows(data.change_project);
                 if (data.change_plan) await this.ctx.service.changePlan.defaultUpdateRows(data.change_plan);
+                if (data.advance) await this.ctx.service.advance.defaultUpdateRows(data.advance);
                 ctx.body = responseData;
             } catch (err) {
                 ctx.log(err);

+ 50 - 0
app/controller/wap_controller.js

@@ -129,6 +129,11 @@ module.exports = app => {
             }
             // 获取待审批的变更期
             const auditChanges = await ctx.service.changeAudit.getAuditChangeByWap(ctx.session.sessionUser.accountId);
+            // 获取待审批的变更方案
+            let auditChangePlans = [];
+            if (ctx.session.sessionProject.page_show.openChangePlan) {
+                auditChangePlans = await ctx.service.changePlanAudit.getAuditChangePlanByWap(ctx.session.sessionUser.accountId);
+            }
             // 获取待审批的台账修订
             const auditRevise = await ctx.service.reviseAudit.getAuditReviseByWap(ctx.session.sessionUser.accountId);
             for (const revise of auditRevise) {
@@ -140,6 +145,7 @@ module.exports = app => {
             const renderData = {
                 auditStages,
                 auditChanges,
+                auditChangePlans,
                 auditRevise,
                 auditAdvance,
                 changeConst,
@@ -262,6 +268,15 @@ module.exports = app => {
                     c.curAuditor = await ctx.service.changeAudit.getLastUser(c.cid, c.times);
                 }
 
+                // 变更令列表
+                let changePlans = [];
+                if (ctx.session.sessionProject.page_show.openChangePlan) {
+                    changePlans = await ctx.service.changePlan.getListByStatus(tender.id, 0, 0);
+                    for (const c of changePlans) {
+                        c.curAuditor = await ctx.service.changePlanAudit.getAuditorByStatus(c.id, c.status, c.times);
+                    }
+                }
+
                 // 台账修订列表
                 const revises = await ctx.service.ledgerRevise.getReviseList(ctx.tender.id);
                 for (const lr of revises) {
@@ -281,10 +296,12 @@ module.exports = app => {
                     tender,
                     stages,
                     changes,
+                    changePlans,
                     revises,
                     advanceList,
                     auditConst: auditConst.stage,
                     auditChangeConst: auditConst.flow,
+                    auditChangePlanConst: auditConst.changePlan,
                     auditReviseConst: auditConst.revise,
                     changeConst,
                     advanceConst,
@@ -377,6 +394,39 @@ module.exports = app => {
         }
 
         /**
+         * 变更方案审批详细页
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async changePlan(ctx) {
+            try {
+                const tender = ctx.tender.data;
+                if (!ctx.session.sessionProject.page_show.openChangePlan) {
+                    throw '该功能已关闭';
+                }
+                const change = await ctx.service.changePlan.getDataById(ctx.params.cpid);
+                const renderData = {
+                    tender,
+                    change,
+                    auditConst: auditConst.changePlan,
+                    changeConst,
+                    tpUnit: ctx.tender.info.decimal.tp,
+                };
+                const times = change.status !== auditConst.changePlan.status.checkNo ? change.times : change.times - 1;
+                renderData.change.user = await ctx.service.projectAccount.getAccountInfoById(change.uid);
+                renderData.change.auditors = await ctx.service.changePlanAudit.getAuditors(change.id, times);
+                // 获取审批流程中左边列表
+                renderData.change.auditors2 = await ctx.service.changePlanAudit.getAuditGroupByList(change.id, times);
+                await ctx.render('wap/shenpi_change_plan.ejs', renderData);
+            } catch (err) {
+                this.log(err);
+                ctx.redirect('/wap/list');
+            }
+        }
+
+
+        /**
          * 修订审批详细页
          *
          * @param {Object} ctx - egg全局变量

+ 4 - 0
app/extend/context.js

@@ -76,4 +76,8 @@ module.exports = {
         return this.app.hisOss;
     },
 
+    saveTempFile(filename, text) {
+        const filepath = path.join(this.app.config.logger.dir, this.app.config.version, filename);
+        this.helper.saveBufferFile(text, filepath);
+    }
 };

+ 34 - 3
app/extend/helper.js

@@ -847,6 +847,34 @@ module.exports = {
             loadFields(r.data, r.fields, r.prefix, r.relaId);
         }
     },
+    /**
+     * 合并(并汇总) 相关数据
+     * @param {Array} main - 主数据
+     * @param {Array[]}rela - 相关数据 {data, fields, relaField}
+     *
+     * { data, fields}
+     */
+    assignSumRelaData(main, keyField, rela) {
+        const index = {},
+            indexPre = 'id_';
+        const addFun = this.add;
+        const loadFields = function(datas, fields, relaId) {
+            for (const d of datas) {
+                const key = indexPre + d[relaId];
+                const m = index[key];
+                if (!m) continue;
+                for (const f of fields) {
+                    if (d[f.source]) m[f.target] = addFun(m[f.target], d[f.source]);
+                }
+            }
+        };
+        for (const m of main) {
+            index[indexPre + m[keyField]] = m;
+        }
+        for (const r of rela) {
+            loadFields(r.data, r.fields, r.relaField);
+        }
+    },
 
     whereSql(where, as) {
         if (!where) {
@@ -969,6 +997,12 @@ module.exports = {
         return moment(time).format('YYYY-MM-DD HH:mm:ss');
     },
 
+    calcDayNum(startTime, endTime = '') {
+        const dateStart = new Date(startTime);
+        const dateEnd = endTime ? new Date(endTime) : new Date();
+        return parseInt((dateEnd.getTime() - dateStart.getTime()) / (1000 * 60 * 60 * 24));
+    },
+
     // 预付款详情页时间线所需格式
     formatDate(date) {
         if (!date) return '';
@@ -1454,9 +1488,6 @@ module.exports = {
     },
 
     getStartEndMonth(month) {
-        // month = new Date('2021-10-03');
-        // const startMonth = new Date(month.getFullYear(), month.getMonth(), 1);
-        // const endMonth = new Date(month.getFullYear(), month.getMonth() + 1, 1);
         const startMonth = moment(month).startOf('month').format('YYYY-MM-DD HH:mm:ss');
         const endMonth = moment(month).endOf('month').format('YYYY-MM-DD HH:mm:ss');
         return [startMonth, endMonth];

+ 118 - 33
app/lib/analysis_excel.js

@@ -12,6 +12,18 @@ const colDefineType = {
     match: 1,
     pos: 2,
 };
+
+const mainReg = /^(GD*)?G?(\d\d)*\d$/i;
+const subReg = /^(GD*)?G?[A-Z]{2}(\d\d)+$/i;
+const gdXmjPartReg = /^(GD*)?G?/;
+const specCode106 = {
+    code: ['102', '103', '104'],
+    reg: /^(GD*)?G?106/i
+};
+const specCode109 = {
+    code: ['102', '103', '104', '105', '106', '107', '108'],
+    reg: /^(GD*)?G?109/i
+};
 const aeUtils = {
     toNumber: function (value) {
         let num = _.toNumber(value);
@@ -42,21 +54,19 @@ const aeUtils = {
             }
         }
         return colsDef;
+    },
+    isMatch11(tempData) {
+        return _.find(tempData, x => {
+            return x.code.indexOf('-') > 0;
+        })
+    },
+    isMatch18(tempData) {
+        return _.every(tempData, x => {
+            return !x.code || !!x.code.match(mainReg);
+        });
     }
 };
 
-const mainReg = /^(GD*)?G?(\d\d)*\d$/i;
-const subReg = /^(GD*)?G?[A-Z]{2}(\d\d)+$/i;
-const gdXmjPartReg = /^(GD*)?G?/;
-const specCode106 = {
-    code: ['102', '103', '104'],
-    reg: /^(GD*)?G?106/i
-};
-const specCode109 = {
-    code: ['102', '103', '104', '105', '106', '107', '108'],
-    reg: /^(GD*)?G?109/i
-};
-
 class ImportBaseTree {
     /**
      * 构造函数
@@ -362,11 +372,9 @@ class ImportStd18Tree extends ImportBaseTree {
      * @private
      */
     _checkParent(parent, code) {
-        if (code === 'LJ0703') console.log(parent.code, code);
         const codeNumberPart = code.replace(gdXmjPartReg, '');
         if (!parent.code) return false;
         const numberPart = parent.code.replace(gdXmjPartReg, '');
-        if (code === 'LJ0703') console.log(numberPart, codeNumberPart);
         if (!numberPart || !codeNumberPart || numberPart.length >= codeNumberPart.length) return false;
         return code.indexOf(numberPart) === 0 ||
             code.indexOf('G' + numberPart) === 0 ||
@@ -515,24 +523,12 @@ class AnalysisExcelTree {
         this.needCols = needCols || ['code', 'b_code', 'pos'];
     }
 
-    _isMatch11(tempData) {
-        return _.find(tempData, x => {
-            return x.code.indexOf('-') > 0;
-        })
-    }
-
-    _isMatch18(tempData) {
-        return _.every(tempData, x => {
-            return !x.code || !!x.code.match(mainReg);
-        });
-    }
-
     _getNewCacheTree(tempData) {
         // 模板符合11编办规则,使用11编办树
-        if (this._isMatch18(tempData)) {
+        if (aeUtils.isMatch18(tempData)) {
             return new ImportStd18Tree(tempData, this.ctx, this.setting);
         // 反之使用11编办(未校验模板是否符合,替换注释部分即可实现)
-        // } else if (this._isMatch11(tempData)){
+        // } else if (aeUtils.isMatch11(tempData)){
         } else {
             return new ImportBaseTree(tempData, this.ctx, this.setting);
         }
@@ -552,12 +548,12 @@ class AnalysisExcelTree {
             node.unit = this.ctx.helper.replaceReturn(row[this.colsDef.unit]);
             node.dgn_qty1 = aeUtils.toNumber(row[this.colsDef.dgn_qty1]);
             node.dgn_qty2 = aeUtils.toNumber(row[this.colsDef.dgn_qty2]);
-            node.total_price = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.total_price]), this.decimal.tp);
+            // 2022-6-9 曾沛文要求不导入金额
+            //node.total_price = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.total_price]), this.decimal.tp);
             node.drawing_code = this.ctx.helper.replaceReturn(row[this.colsDef.drawing_code]);
             node.memo = this.ctx.helper.replaceReturn(row[this.colsDef.memo]);
             return this.cacheTree.addXmjNode(node);
         } catch (error) {
-            console.log(error);
             if (error.stack) {
                 this.ctx.logger.error(error);
             } else {
@@ -676,8 +672,6 @@ class AnalysisExcelTree {
                 this.checkColHeader(row);
             }
         }
-        this.cacheTree.resortFirstPartChildren();
-        if (!this.filter.filterCalc) this.cacheTree.calculateLeafWithPos();
         return this.cacheTree;
     }
 }
@@ -883,4 +877,95 @@ class AnalysisGclExcelTree {
     }
 }
 
-module.exports = { AnalysisExcelTree, AnalysisGclExcelTree };
+class ImportStageBaseTree {
+
+}
+
+class AnalysisStageTree {
+    /**
+     * 构造函数
+     */
+    constructor(ctx, setting) {
+        this.ctx = ctx;
+        this.setting = setting;
+        this.mid = ctx.tender.id;
+        this.decimal = ctx.tender.info.decimal;
+        this.precision = ctx.tender.info.precision;
+        this.colsDef = null;
+        this.colHeaderMatch = {
+            code: {value: ['项目节编号', '预算项目节'], type: colDefineType.match},
+            b_code: {value: ['清单子目号', '清单编号', '子目号'], type: colDefineType.match},
+            pos: {value: ['计量单元'], type: colDefineType.match},
+            name: {value: ['名称'], type: colDefineType.match},
+            unit: {value: ['单位'], type: colDefineType.match},
+            contract_qty: {value: ['本期合同计量|数量'], type: colDefineType.match},
+            contract_tp: {value: ['本期合同计量|金额'], type: colDefineType.match},
+        };
+        this.needCols = ['code', 'b_code', 'pos', 'name', 'unit', 'contract_qty', 'contract_tp'];
+    }
+
+    /**
+     * 读取表头并检查
+     * @param {Number} row - Excel数据行
+     */
+    checkColHeader(row) {
+        const colsDef = aeUtils.checkColHeader(row, this.colHeaderMatch);
+        let check = true;
+        for (const col of this.needCols) {
+            if (!colsDef[col]) check = false;
+        }
+        if (check) this.colsDef = colsDef;
+    }
+
+    mergeHeaderRow(iRow, row, subRow, merge) {
+        const result = [];
+        for (let iCol = 0, iLen = row.length; iCol < iLen; iCol++) {
+            const colMerge = merge.find(x => { return x.s.c === iCol && x.s.r === iRow});
+            if (colMerge && colMerge.s.c !== colMerge.e.c) {
+                let iSubCol = iCol;
+                while (iSubCol <= colMerge.e.c) {
+                    result.push(row[iCol] + '|' + subRow[iSubCol]);
+                    iSubCol++;
+                }
+                iCol = colMerge.e.c;
+            } else {
+                result.push(row[iCol])
+            }
+        }
+        return result;
+    }
+
+    loadRowData(row, iRow) {
+
+    }
+
+    /**
+     * 将excel清单 平面数据 解析为 树结构数据
+     * @param {object} sheet - excel清单数据
+     * @param {Array} tempData - 新建项目使用的清单模板
+     * @returns {ImportBaseTree}
+     */
+    analysisData(ledger, pos, sheet) {
+        this.filter = filter ? filter : {};
+        this.colsDef = null;
+        this.cacheTree = this._getNewLedger();
+        this.errorData = [];
+        this.loadEnd = false;
+        this.loadBegin = sheet.rows.length;
+
+        for (const iRow in sheet.rows) {
+            if (this.colsDef && !this.loadEnd) {
+                if (iRow < this.loadBegin) continue;
+                this.loadRowData(sheet.rows[iRow], iRow);
+            } else {
+                const mergeRow = this.mergeHeaderRow(iRow, sheet.rows[iRow], sheet.rows[iRow + 1], sheet.merge);
+                this.checkColHeader(mergeRow);
+                if (this.colsDef) this.loadBegin = iRow + 2;
+            }
+        }
+
+        return this.cacheTree;
+    }
+}
+
+module.exports = { AnalysisExcelTree, AnalysisGclExcelTree, AnalysisStageTree };

+ 16 - 0
app/lib/bills_pos_convert.js

@@ -98,6 +98,9 @@ class BillsPosConvert {
         node.pre_gather_qty = this.ctx.helper.add(node.pre_gather_qty, data.pre_gather_qty);
 
         node.real_qty = this.ctx.helper.add(node.real_qty, data.real_qty);
+
+        node.minus_qc_qty = this.ctx.helper.add(node.minus_qc_qty, data.minus_qc_qty);
+        node.pre_minus_qc_qty = this.ctx.helper.add(node.pre_minus_qc_qty, data.pre_minus_qc_qty);
     }
     _loadBillsCalcFields(node, data) {
         node.quantity = this.ctx.helper.add(node.quantity, data.quantity);
@@ -114,6 +117,9 @@ class BillsPosConvert {
         node.pre_qc_qty = this.ctx.helper.add(node.pre_qc_qty, data.pre_qc_qty);
         node.pre_qc_tp = this.ctx.helper.add(node.pre_qc_tp, data.pre_qc_tp);
         node.pre_gather_qty = this.ctx.helper.add(node.pre_gather_qty, data.pre_gather_qty);
+
+        node.minus_qc_qty = this.ctx.helper.add(node.minus_qc_qty, data.minus_qc_qty);
+        node.pre_minus_qc_qty = this.ctx.helper.add(node.pre_minus_qc_qty, data.pre_minus_qc_qty);
     }
     _convertGcl(node, xmj) {
         if (!xmj) return;
@@ -235,6 +241,13 @@ class BillsPosConvert {
         child.estimate_tp = this.ctx.helper.mul(child.estimate_qty, child.unit_price, tpDecimal);
 
         child.bgl_code = this.ctx.helper._.uniq(this.ctx.helper._.map(child.changes, 'c_code')).join(';');
+
+        // 1#台账相关
+        child.end_minus_qc_qty = this.ctx.helper.add(child.pre_minus_qc_qty, child.minus_qc_qty); // 1#台账 台账+负变更
+        child.end_1_qty = this.ctx.helper.add(child.end_minus_qc_qty, child.quantity); // 1#台账 台账+负变更
+        child.end_1_tp = this.ctx.helper.mul(child.unit_price, child.end_1_qty, tpDecimal);
+        child.end_final_1_tp = this.ctx.helper.add(child.end_qc_tp, child.end_1_tp);
+        child.end_gather_1_percent = this.ctx.helper.mul(this.ctx.helper.div(child.end_gather_tp, child.end_final_1_tp, 4), 100);
     }
     _calculateNode(node, children) {
         for (const child of children) {
@@ -250,11 +263,14 @@ class BillsPosConvert {
             node.end_gather_tp = this.ctx.helper.add(node.end_gather_tp, child.end_gather_tp);
             node.real_tp = this.ctx.helper.add(node.real_tp, child.real_tp);
             node.estimate_tp = this.ctx.helper.add(node.estimate_tp, child.estimate_tp);
+            node.end_1_tp = this.ctx.helper.add(node.end_1_tp, child.end_1_tp);
         }
         if (node.dgn_qty1)
             node.dgn_price = this.ctx.helper.div(node.total_price, node.dgn_qty1, this.ctx.tender.info.decimal.up);
         node.final_tp = this.ctx.helper.add(node.total_price, node.end_qc_tp);
         node.end_gather_percent = this.ctx.helper.mul(this.ctx.helper.div(node.end_gather_tp, node.final_tp, 4), 100);
+        node.end_final_1_tp = this.ctx.helper.add(node.end_qc_tp, node.end_1_tp);
+        node.end_gather_1_percent = this.ctx.helper.mul(this.ctx.helper.div(node.end_gather_tp, node.end_final_1_tp, 4), 100);
     }
     _recursiveCalcUnitNodes(nodes) {
         if (!nodes || !nodes instanceof Array || nodes.length === 0) return;

+ 265 - 0
app/lib/budget_final.js

@@ -0,0 +1,265 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const itemsPre = 'id_';
+const BillsTree = require('./ledger').billsTree;
+const auditConst = require('../const/audit');
+
+class FinalTree extends BillsTree {
+    constructor(ctx, setting) {
+        super(ctx, setting);
+        this._newId = 1;
+    }
+    get newId() {
+        return this._newId++;
+    }
+
+    loadNode(node, parent, loadFun) {
+        if (node.b_code) return;
+        const siblings = parent ? parent.children : this.children;
+        let cur = siblings.find(function (x) {
+            return x.code === node.code && x.name === node.name;
+        });
+        if (!cur) {
+            cur = { children: [], code: node.code || '', name: node.name || '', unit: node.unit || '' };
+            const id = this.newId;
+            cur[this.setting.id] = id;
+            cur[this.setting.pid] = parent ? parent[this.setting.id] : this.setting.rootId;
+            cur[this.setting.fullPath] = parent ? parent[this.setting.fullPath] + '-' + id : '' + id;
+            cur[this.setting.level] = parent ? parent[this.setting.level] + 1 : 1;
+            cur[this.setting.order] = siblings.length + 1;
+            siblings.push(cur);
+            this.datas.push(cur);
+        }
+        loadFun(cur, node);
+        for (const c of node.children) {
+            this.loadNode(c, cur, loadFun);
+        }
+    }
+    loadTree(tree, loadFun) {
+        for (const node of tree.children) {
+            this.loadNode(node, null, loadFun);
+        }
+    }
+
+    generateSortNodes() {
+        const self = this;
+        const addSortNode = function (node) {
+            self.nodes.push(node);
+            for (const c of node.children) {
+                addSortNode(c);
+            }
+        };
+        this.nodes = [];
+        for (const n of this.children) {
+            addSortNode(n);
+        }
+    }
+    afterLoad(fun) {
+        for (const d of this.datas) {
+            fun && fun(d);
+            d.is_leaf = d.children.length === 0;
+            d.expanded = true;
+            d.visible = true;
+            this.items[itemsPre + d[this.setting.id]] = d;
+        }
+        this.generateSortNodes();
+    }
+    resortChildrenByCustom(fun) {
+        for (const n of this.nodes) {
+            if (n.children && n.children.length > 1) {
+                n.children.sort(fun);
+                n.children.forEach((x, y) => { x.order = y + 1; });
+            }
+        }
+        this.generateSortNodes();
+    }
+}
+
+class BudgetFinal {
+    constructor (ctx) {
+        this.ctx = ctx;
+        this.budgetSetting = { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] };
+        this.tenderSetting = { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price', 'end_gather_tp'] };
+        this.finalTree = new FinalTree(this.ctx, { id: 'id', pid: 'pid', order: 'order', level: 'level', fullPath: 'full_path', rootId: -1 });
+    }
+
+    async _loadGu(budget) {
+        const helper = this.ctx.helper;
+        const gu = await this.ctx.service.budgetGu.getData(budget.id);
+        const guTree = new BillsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] });
+        guTree.loadDatas(gu);
+        guTree.calculateAll();
+        this.finalTree.loadTree(guTree, function (cur, source) {
+            cur.base = true;
+            cur.gu_dgn_qty1 = helper.add(cur.gu_dgn_qty1, source.dgn_qty1);
+            cur.gu_dgn_qty2 = helper.add(cur.gu_dgn_qty2, source.dgn_qty2);
+            cur.gu_tp = helper.add(cur.gu_tp, source.total_price);
+        });
+    }
+
+    async _loadGai(budget) {
+        const helper = this.ctx.helper;
+        const gai = await this.ctx.service.budgetGai.getData(budget.id);
+        const gaiTree = new BillsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] });
+        gaiTree.loadDatas(gai);
+        gaiTree.calculateAll();
+        this.finalTree.loadTree(gaiTree, function (cur, source) {
+            cur.base = true;
+            cur.gai_dgn_qty1 = helper.add(cur.gai_dgn_qty1, source.dgn_qty1);
+            cur.gai_dgn_qty2 = helper.add(cur.gai_dgn_qty2, source.dgn_qty2);
+            cur.gai_tp = helper.add(cur.gai_tp, source.total_price);
+        });
+    }
+
+    async _loadYu(budget) {
+        const helper = this.ctx.helper;
+        const yu = await this.ctx.service.budgetYu.getData(budget.id);
+        const yuTree = new BillsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] });
+        yuTree.loadDatas(yu);
+        yuTree.calculateAll();
+        this.finalTree.loadTree(yuTree, function (cur, source) {
+            cur.base = true;
+            cur.yu_dgn_qty1 = helper.add(cur.yu_dgn_qty1, source.dgn_qty1);
+            cur.yu_dgn_qty2 = helper.add(cur.yu_dgn_qty2, source.dgn_qty2);
+            cur.yu_tp = helper.add(cur.yu_tp, source.total_price);
+        });
+    }
+
+    async _loadTender(id) {
+        const helper = this.ctx.helper;
+        const bills = await this.ctx.service.ledger.getFinalData(id, ['id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
+            'code', 'b_code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'total_price']);
+
+        const dgnData = await this.ctx.service.stageBillsDgn.getDgnData(id);
+        // 使用最新一期对比
+        const stage = await this.ctx.service.stage.getLastestStage(id);
+        if (!stage) {
+            helper.assignRelaData(bills, [
+                { data: dgnData, fields: ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'], prefix: '', relaId: 'id' },
+            ]);
+            this.final.tender_info.push({ id, stageCount: 0 });
+        } else if (stage.status === auditConst.stage.status.checked) {
+            const finalBills = await this.ctx.service.stageBillsFinal.getFinalData({id}, stage.order);
+            helper.assignRelaData(bills, [
+                { data: finalBills, fields: ['contract_tp', 'qc_tp'], prefix: 'end_', relaId: 'lid' },
+                { data: dgnData, fields: ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'], prefix: '', relaId: 'id' },
+            ]);
+            bills.forEach(b => {
+                b.end_gather_tp = helper.add(b.end_qc_tp, b.end_contract_tp);
+            });
+            this.final.tender_info.push({ id, stageOrder: stage.order });
+        } else {
+            await this.ctx.service.stage.doCheckStage(stage);
+            const curBills = stage.readOnly
+                ? 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) : [];
+            helper.assignRelaData(bills, [
+                { data: curBills, fields: ['contract_tp', 'qc_tp'], prefix: '', relaId: 'lid' },
+                { data: preBills, fields: ['contract_tp', 'qc_tp'], prefix: 'pre_', relaId: 'lid' },
+                { data: dgnData, fields: ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'], prefix: '', relaId: 'id' },
+            ]);
+            bills.forEach(b => {
+                b.end_gather_tp = helper.sum([b.qc_tp, b.contract_tp, b.pre_qc_tp, b.pre_contract_tp]);
+            });
+            this.final.tender_info.push({ id, stageOrder: stage.order, stageStatus: stage.status, stageFlow: stage.curTimes + '-' + stage.curOrder });
+        }
+        const tree = new BillsTree(this.ctx, this.tenderSetting);
+        tree.loadDatas(bills);
+        tree.calculateAll();
+        this.finalTree.loadTree(tree, function (cur, source) {
+            cur.total_price = helper.add(cur.total_price, source.total_price);
+            cur.dgn_qty1 = helper.add(cur.dgn_qty1, source.dgn_qty1);
+            cur.dgn_qty2 = helper.add(cur.dgn_qty2, source.dgn_qty2);
+            cur.final_dgn_qty1 = helper.sum([cur.final_dgn_qty1, source.deal_dgn_qty1, source.c_dgn_qty1]);
+            cur.final_dgn_qty2 = helper.sum([cur.final_dgn_qty2, source.deal_dgn_qty2, source.c_dgn_qty2]);
+            cur.final_tp = helper.add(cur.final_tp, source.end_gather_tp);
+        });
+    }
+
+    async _afterLoad() {
+        const helper = this.ctx.helper;
+        this.finalTree.afterLoad(node => {
+            node.dgn_price = helper.div(node.total_price, node.dgn_qty1, 2);
+            node.dgn_qty = node.dgn_qty1
+                ? (node.dgn_qty2 ? node.dgn_qty1 + '/' + node.dgn_qty2 : node.dgn_qty1)
+                : (node.dgn_qty2 ? '/' + node.dgn_qty2 : '');
+
+            node.gu_dgn_price = helper.div(node.gu_tp, node.gu_dgn_qty1, 2);
+            node.gu_dgn_qty = node.gu_dgn_qty1
+                ? (node.gu_dgn_qty2 ? node.gu_dgn_qty1 + '/' + node.gu_dgn_qty2 : node.gu_dgn_qty1 + '')
+                : (node.gu_dgn_qty2 ? '/' + node.gu_dgn_qty2 : '');
+            node.gai_dgn_price = helper.div(node.gai_tp, node.gai_dgn_qty1, 2);
+            node.gai_dgn_qty = node.gai_dgn_qty1
+                ? (node.gai_dgn_qty2 ? node.gai_dgn_qty1 + '/' + node.gai_dgn_qty2 : node.gai_dgn_qty1 + '')
+                : (node.gai_dgn_qty2 ? '/' + node.gai_dgn_qty2 : '');
+            node.yu_dgn_price = helper.div(node.yu_tp, node.yu_dgn_qty1, 2);
+            node.yu_dgn_qty = node.yu_dgn_qty1
+                ? (node.yu_dgn_qty2 ? node.yu_dgn_qty1 + '/' + node.yu_dgn_qty2 : node.yu_dgn_qty1 + '')
+                : (node.yu_dgn_qty2 ? '/' + node.yu_dgn_qty2 : '');
+
+            node.final_dgn_price = helper.div(node.final_tp, node.final_dgn_qty1, 2);
+            node.final_dgn_qty = node.final_dgn_qty1
+                ? (node.final_dgn_qty2 ? node.final_dgn_qty1 + '/' + node.final_dgn_qty2 : node.final_dgn_qty1)
+                : (node.final_dgn_qty2 ? '/' + node.final_dgn_qty2 : '');
+            node.grow_dgn_qty1 = helper.mul(helper.div(helper.sub(node.final_dgn_qty1, node.gai_dgn_qty1), node.gai_dgn_qty1, 4), 100);
+            node.grow_dgn_qty2 = helper.mul(helper.div(helper.sub(node.final_dgn_qty2, node.gai_dgn_qty2), node.gai_dgn_qty2, 4), 100);
+            node.grow_dgn_qty = node.grow_dgn_qty1
+                ? (node.grow_dgn_qty2 ? node.grow_dgn_qty1 + '/' + node.grow_dgn_qty2 : node.grow_dgn_qty1)
+                : (node.grow_dgn_qty2 ? '/' + node.grow_dgn_qty2 : '');
+            node.grow_tp = helper.mul(helper.div(helper.sub(node.final_tp, node.gai_tp), node.gai_tp, 4), 100);
+        });
+        this.finalTree.resortChildrenByCustom(function (x, y) {
+            const iCode = helper.compareCode(x.code, y.code);
+            if (iCode) return iCode;
+            if (!x.name) return -1;
+            if (!y.name) return 1;
+            return x.name.localeCompare(y.name);
+        });
+    }
+
+    getFinalData() {
+        const data = [], ctx = this.ctx, bid = this.budget.id, final_id = this.final.id;
+        this.finalTree.datas.forEach(x => {
+            data.push({
+                id: ctx.app.uuid.v4(), bid, final_id,
+                tree_id: x.id, tree_pid: x.pid, order: x.order, level: x.level, full_path: x.full_path, is_leaf: x.is_leaf,
+                code: x.code, name: x.name, unit: x.unit,
+                gu_dgn_qty1: x.gu_dgn_qty1 || 0, gu_dgn_qty2: x.gu_dgn_qty2 || 0, gu_dgn_qty: x.gu_dgn_qty, gu_dgn_price: x.gu_dgn_price || 0, gu_tp: x.gu_tp || 0,
+                gai_dgn_qty1: x.gai_dgn_qty1 || 0, gai_dgn_qty2: x.gai_dgn_qty2 || 0, gai_dgn_qty: x.gai_dgn_qty, gai_dgn_price: x.gai_dgn_price || 0, gai_tp: x.gai_tp || 0,
+                yu_dgn_qty1: x.yu_dgn_qty1 || 0, yu_dgn_qty2: x.yu_dgn_qty2 || 0, yu_dgn_qty: x.yu_dgn_qty, yu_dgn_price: x.yu_dgn_price || 0, yu_tp: x.yu_tp || 0,
+
+                dgn_qty1: x.dgn_qty1 || 0, dgn_qty2: x.dgn_qty2 || 0, total_price: x.total_price || 0,
+                final_dgn_qty1: x.final_dgn_qty1 || 0, final_dgn_qty2: x.final_dgn_qty2 || 0, final_tp: x.final_tp || 0,
+                dgn_price: x.dgn_price || 0, dgn_qty: x.dgn_qty,
+                final_dgn_price: x.final_dgn_price || 0, final_dgn_qty: x.final_dgn_qty,
+                grow_dgn_qty1: x.grow_dgn_qty1 || 0, grow_dgn_qty2: x.grow_dgn_qty2 || 0, grow_dgn_qty: x.grow_dgn_qty, grow_tp: x.grow_tp || 0,
+            })
+        });
+        return data;
+    }
+
+    async doFinal(budget, final) {
+        this.budget = budget;
+        this.final = final;
+
+        await this._loadGai(budget);
+        await this._loadGu(budget);
+        await this._loadYu(budget);
+        for (const t of final.tender) {
+            await this._loadTender(t);
+        }
+        this._afterLoad();
+        return this.getFinalData();
+    }
+}
+
+module.exports = BudgetFinal;

+ 18 - 1
app/lib/gcl_gather.js

@@ -85,7 +85,7 @@ const gclGatherModel = class {
      * @constructor
      */
     CheckPeg(text) {
-        const pegReg = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+        const pegReg = /[a-zA-Z]*[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
         return pegReg.test(text);
     }
 
@@ -334,6 +334,23 @@ const gclGatherModel = class {
         this.convertResultData();
         return [this.gclList, this.leafXmjs];
     }
+    gatherObj(bills, pos, deal) {
+        const helper = this.ctx.helper;
+        this.billsTree = bills;
+        this.pos = pos;
+
+        this.gclList = [];
+        this.leafXmjs = [];
+
+        this.recursiveGatherGclData(this.billsTree.children, null);
+        this.gatherDealBillsData(deal);
+        this.gclList.sort(function (a, b) {
+            return helper.compareCode(a.b_code, b.b_code);
+        });
+
+        this.convertResultData();
+        return [this.gclList, this.leafXmjs];
+    }
 };
 
 module.exports = {

+ 1 - 1
app/lib/ledger.js

@@ -270,7 +270,7 @@ class baseTree {
         if (fun) {
             fun(node);
         } else if (this.setting.calc) {
-            this.setting.calc(node, this.ctx.helper);
+            this.setting.calc(node, this.ctx.helper, this.ctx.tender.info.decimal);
         }
     }
     calculateAll(fun) {

+ 84 - 0
app/lib/rm/budget.js

@@ -0,0 +1,84 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const ledger = require('../ledger');
+
+class reportMemoryBudget {
+    constructor(ctx) {
+        this.ctx = ctx;
+        this.budget = null;
+        this.getBudget = false;
+    }
+
+    async budgetGai(bid) {
+        const gai = await this.ctx.service.budgetGai.getAllDataByCondition({ where: { bid } });
+        const tree = new ledger.billsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] });
+        tree.loadDatas(gai);
+        tree.calculateAll();
+        return tree.getDefaultDatas();
+    }
+    async budgetYu(bid) {
+        const yu = await this.ctx.service.budgetYu.getAllDataByCondition({ where: { bid } });
+        const tree = new ledger.billsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] });
+        tree.loadDatas(yu);
+        tree.calculateAll();
+        return tree.getDefaultDatas();
+    }
+    async budgetGu(bid) {
+        const gu = await this.ctx.service.budgetGu.getAllDataByCondition({ where: { bid } });
+        const tree = new ledger.billsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] });
+        tree.loadDatas(gu);
+        tree.calculateAll();
+        return tree.getDefaultDatas();
+    }
+    async budgetFinal(bid) {
+        const budget = this.ctx.budget && this.ctx.budget.id === bid
+            ? this.ctx.budget
+            : await this.ctx.service.budget.getDataById(bid);
+        if (!budget.final_id) return [];
+        const final = await this.ctx.service.budgetFinal.getAllDataByCondition({ where: { final_id: budget.final_id } });
+        const tree = new ledger.billsTree(this.ctx, { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: [] });
+        tree.loadDatas(final);
+        return tree.getDefaultDatas();
+    }
+
+    async _getTenderBudget(tid) {
+        if (this.getBudget) return;
+
+        const budgets = await this.ctx.service.budget.getBudget(true);
+        this.budget = budgets.find(x => {
+            const relaTender = x.rela_tender.split(',');
+            return relaTender.indexOf(tid + '') >= 0;
+        });
+        this.getBudget = true;
+    }
+
+    async tenderGai(tid) {
+        await this._getTenderBudget(tid);
+        return this.budget ? await this.budgetGai(this.budget.id) : [];
+    }
+
+    async tenderYu(tid) {
+        await this._getTenderBudget(tid);
+        return this.budget ? await this.budgetYu(this.budget.id) : [];
+    }
+
+    async tenderGu(tid) {
+        await this._getTenderBudget(tid);
+        return this.budget ? await this.budgetGu(this.budget.id) : [];
+    }
+
+    async tenderFinal(tid) {
+        await this._getTenderBudget(tid);
+        return this.budget ? await this.budgetFinal(this.budget.id) : [];
+    }
+}
+
+module.exports = reportMemoryBudget;

+ 300 - 0
app/lib/rm/material.js

@@ -0,0 +1,300 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const materialConst = require('../../const/material');
+
+const Ledger = require('../../lib/ledger');
+
+const billsFields = (function () {
+    const cur = ['contract_qty', 'contract_tp', 'contract_expr', 'qc_qty', 'qc_tp', 'gather_qty', 'gather_tp', 'postil'];
+    const pre = ['pre_contract_qty', 'pre_contract_tp', 'pre_qc_qty', 'pre_qc_tp', 'pre_gather_qty', 'pre_gather_tp'];
+    const end = ['end_contract_qty', 'end_contract_tp', 'end_qc_qty', 'end_qc_tp', 'end_gather_qty', 'end_gather_tp'];
+    const final = ['final_tp', 'final_ratio'];
+    const stageDgn = ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'];
+
+    const stage = cur.concat(pre, end, final);
+    const stageEnd = pre.concat(end, final);
+    const bgl = ['qc_bgl_code'];
+    const leafXmj = ['leaf_xmj_id'];
+
+    return {cur, pre, end, final, stageDgn, stage, stageEnd, bgl, leafXmj};
+})();
+const posFields = (function () {
+    const cur = ['contract_qty', 'qc_qty', 'gather_qty', 'postil'];
+    const pre = ['pre_contract_qty', 'pre_qc_qty', 'pre_gather_qty'];
+    const end = ['end_contract_qty', 'end_qc_qty', 'end_gather_qty'];
+    const final = ['final_ratio'];
+
+    const stage = cur.concat(pre, end, final);
+    const stageEnd = pre.concat(end, final);
+    const bgl = ['qc_bgl_code'];
+
+    return {cur, pre, end, final, stage, stageEnd, bgl};
+})();
+
+class ReportMemoryMaterial {
+    constructor(ctx) {
+        this.ctx = ctx;
+    }
+
+    _getNewPos(updateFields) {
+        const helper = this.ctx.helper;
+        return new Ledger.pos({
+            id: 'id', ledgerId: 'lid',
+            updateFields: ['contract_qty', 'qc_qty', 'postil'],
+            calc: function (p) {
+                p.pre_gather_qty = helper.add(p.pre_contract_qty, p.pre_qc_qty);
+                p.gather_qty = helper.add(p.contract_qty, p.qc_qty);
+                p.end_contract_qty = helper.add(p.pre_contract_qty, p.contract_qty);
+                p.end_qc_qty = helper.add(p.pre_qc_qty, p.qc_qty);
+                p.end_gather_qty = helper.add(p.pre_gather_qty, p.gather_qty);
+            }
+        });
+    }
+
+    _getNewBillsTree(calcFields) {
+        return 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: calcFields || ['deal_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp', 'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp'],
+            calc: function (node, helper) {
+                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.pre_gather_tp = helper.add(node.pre_contract_tp, node.pre_qc_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);
+
+                node.final_tp = helper.add(node.total_price, node.end_qc_tp);
+                node.final_ratio = helper.mul(helper.div(node.end_gather_tp, node.final_tp, 4), 100);
+            }
+        });
+    }
+
+    _checkFieldsExist(source, check) {
+        for (const s of source) {
+            if (check.indexOf(s) >= 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    async getSelectMaterialAuditors(tid, material_order, fields) {
+        await this.ctx.service.material.checkMaterial(tid, material_order);
+        const auditors = await this.ctx.service.materialAudit.getFinalAuditGroup(this.ctx.material.id, this.ctx.material.curTimes);
+        const user = await this.ctx.service.projectAccount.getDataById(this.ctx.material.user_id);
+        const result = [{
+            aid: user.id,
+            name: user.name,
+            company: user.company,
+            role: user.role,
+            mobile: user.mobile,
+            telephone: user.telephone,
+            sign_path: user.sign_path,
+            opinion: user.opinion,
+            end_time: auditors && auditors.length > 0 ? auditors[0].begin_time : null,
+            sort: 0,
+        }, ...auditors];
+        return result;
+    }
+
+    async getMaterial(tender_id, material_order, fields) {
+        const result = await this.ctx.service.material.getValidMaterials(tender_id);
+        if (this._checkFieldsExist(fields, ['checked_time'])) {
+            for (const r of result) {
+                const auditors = await this.ctx.service.materialAudit.getFinalAuditGroup(r.id, r.curTimes || r.times);
+                r.checked_time = !r.curTimes ? auditors[auditors.length - 1].end_time : null;
+            }
+        }
+        return result;
+    }
+
+    _completeMaterialGl(materialGl) {
+        const tTypeStr = [], mTypeStr = [];
+        for (const t of materialConst.t_type) {
+            tTypeStr[t.value] = t.text;
+        }
+        for (const m of materialConst.m_type) {
+            mTypeStr[m.value] = m.text;
+        }
+        for (const gl of materialGl) {
+            gl.tp = this.ctx.helper.mul(gl.quantity, gl.m_spread, 2);
+            gl.t_type_str = tTypeStr[gl.t_type];
+            gl.m_type_str = mTypeStr[gl.m_type];
+            gl.end_tp = this.ctx.helper.add(gl.tp, gl.pre_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) {
+                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 [];
+
+                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,' +
+                    '    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' +
+                    '  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 = ?';
+                result = await this.ctx.app.mysql.query(sql, [tender_id, material.id]);
+            }
+            this._completeMaterialGl(result);
+            return result;
+        } else {
+            return [];
+        }
+    }
+
+    async getMaterialGlDetail(tender_id, material_order, fields) {
+        const material = await this.ctx.service.material.getDataByCondition({tid: tender_id, order: material_order});
+        return material ? await this.ctx.service.materialList.getMaterialData(tender_id, material.id) : [];
+    }
+
+    async getMaterialBills(tender_id, material_order, fields) {
+        const material = await this.ctx.service.material.getDataByCondition({tid: tender_id, order: material_order});
+        try {
+            const billsData = await this.ctx.service.ledger.getData(tender_id);
+            if (this._checkFieldsExist(fields, billsFields.stage)) {
+                const curStage = await this.ctx.service.stageBills.getStagesData(tender_id, material.stage_id);
+                this.ctx.helper.assignRelaData(billsData, [
+                    {data: curStage, fields: ['contract_qty', 'contract_tp', 'contract_expr', 'qc_qty', 'qc_tp', 'postil'], prefix: '', relaId: 'lid'}
+                ]);
+            }
+
+            const billsTree = this._getNewBillsTree();
+            billsTree.loadDatas(billsData);
+            billsTree.calculateAll();
+
+            return billsTree.getDatas([
+                'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
+                'code', 'b_code', 'name', 'unit', 'unit_price',
+                'deal_qty', 'deal_tp',
+                'sgfh_qty', 'sgfh_tp', 'sjcl_qty', 'sjcl_tp', 'qtcl_qty', 'qtcl_tp', 'quantity', 'total_price',
+                'dgn_qty1', 'dgn_qty2',
+                'drawing_code', 'memo', 'node_type', 'is_tp',
+                'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'gather_qty', 'gather_tp', 'postil',
+                'sgfh_expr', 'sjcl_expr', 'qtcl_expr', 'contract_expr',
+            ]);
+        } catch(err) {
+            this.ctx.helper.log(err);
+            return [];
+        }
+    }
+
+    async getMaterialPos(tender_id, material_order, fields) {
+        const material = await this.ctx.service.material.getDataByCondition({tid: tender_id, order: material_order});
+        try {
+            const posData = await this.ctx.service.pos.getAllDataByCondition({ where: {tid: tender_id }});
+            if (this._checkFieldsExist(fields, posFields.stage)) {
+                const curPosStage = await this.ctx.service.stagePos.getStagesData(tender_id, material.stage_id);
+                this.ctx.helper.assignRelaData(posData, [
+                    {data: curPosStage, fields: ['contract_qty', 'qc_qty', 'contract_expr', 'postil'], prefix: '', relaId: 'pid'}
+                ]);
+            }
+            const pos = this._getNewPos();
+            pos.loadDatas(posData);
+            pos.calculateAll();
+
+            return pos.getDatas();
+        } catch (err) {
+            this.ctx.helper.log(err);
+            return [];
+        }
+    }
+
+    async getMaterialGatherBills(tender_id, material_order) {
+        const materials = await this.ctx.service.material.getAllDataByCondition({
+            where: { tid: tender_id },
+            orders: [['order', 'desc']],
+        });
+        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;
+        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();
+
+            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);
+                    });
+                    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);
+                }
+            }
+            return { mem_material_gather_bills: gatherUtil.gclList, mem_material_gather_xmj: gatherUtil.leafXmjs, mem_material_gather_gl: materialGl };
+        } catch (err) {
+            this.ctx.log(err);
+            return {};
+        }
+    }
+}
+
+module.exports = ReportMemoryMaterial;

+ 0 - 1
app/lib/rpt_data_analysis.js

@@ -1834,7 +1834,6 @@ const gatherMaterialGl = {
                 gd.quantity = ctx.helper.add(gd.quantity, d.quantity);
             }
         }
-        console.log(result);
         data.mem_material_gl_detail = result;
     }
 };

+ 11 - 27
app/lib/stage_im.js

@@ -145,21 +145,21 @@ class StageIm {
         return cur;
     }
     _checkPeg(text) {
-        const pegReg = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+        const pegReg = /[a-zA-Z]*[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
         return pegReg.test(text);
     }
     _getPegStr(text) {
-        const pegReg1 = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?[~~—][a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+        const pegReg1 = /[a-zA-Z]*[kK][0-9]+[++][0-9]{3}([.][0-9]+)?[~~—][a-zA-Z]*[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
         const result1 = text.match(pegReg1);
         if (result1) {
             return result1[0];
         }
-        const pegReg2 = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?-[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+        const pegReg2 = /[a-zA-Z]*[kK][0-9]+[++][0-9]{3}([.][0-9]+)?-[a-zA-Z]*[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
         const result2 = text.match(pegReg2);
         if (result2) {
             return result2[0];
         }
-        const pegReg3 = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+        const pegReg3 = /[a-zA-Z]*[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
         const result3 = text.match(pegReg3);
         return result3 ? result3[0] : '';
 
@@ -488,9 +488,6 @@ class StageIm {
             if (p.children && p.children.length > 0) {
                 continue;
             }
-            if ((!p.qc_tp || p.qc_tp === 0)) {
-                continue;
-            }
             const posRange = this.pos.getLedgerPos(p.id);
             if (!posRange) {
                 for (const c of this.changes) {
@@ -501,9 +498,6 @@ class StageIm {
                 this.import_changes.forEach(x => { if (x.lid === p.id) im.changes.push(x) });
             } else {
                 for (const pp of posRange) {
-                    if ((!pp.qc_qty || pp.qc_qty === 0)) {
-                        continue;
-                    }
                     for (const c of this.changes) {
                         if (c.lid === p.id && c.pid === pp.id && c.qty && c.qty !== 0) {
                             im.changes.push(c);
@@ -803,9 +797,6 @@ class StageIm {
         if (!im.changes) {
             im.changes = [];
         }
-        if ((!node.qc_qty || node.qc_qty === 0)) {
-            return;
-        }
         const posRange = this.pos.getLedgerPos(node.id);
         if (!posRange) {
             for (const c of this.changes) {
@@ -816,9 +807,6 @@ class StageIm {
             this.import_changes.forEach(x => { if (x.lid === node.id) im.changes.push(x) });
         } else {
             for (const p of posRange) {
-                if ((!p.qc_qty || p.qc_qty === 0)) {
-                    continue;
-                }
                 for (const c of this.changes) {
                     if (c.lid === node.id && c.pid === p.id && c.qty && c.qty !== 0) {
                         im.changes.push(c);
@@ -950,11 +938,9 @@ class StageIm {
                     im.end_qc_tp = this.ctx.helper.mul(im.end_qc_jl, im.unit_price, tp_decimal);
                     im.calc_memo = '本期计量:' + (this.ctx.helper.checkZero(im.jl) ? 0 : im.jl) + ' ' + im.unit;
                     this.ImData.push(im);
-                    if (pp.qc_qty && pp.qc_qty !== 0) {
-                        for (const c of this.changes) {
-                            if (c.lid === p.id && c.pid === pp.id && c.qty && c.qty !== 0) {
-                                im.changes.push(c);
-                            }
+                    for (const c of this.changes) {
+                        if (c.lid === p.id && c.pid === pp.id && c.qty && c.qty !== 0) {
+                            im.changes.push(c);
                         }
                     }
                 }
@@ -982,14 +968,12 @@ class StageIm {
                 };
                 im.calc_memo = '本期计量:' + (this.ctx.helper.checkZero(im.jl) ? 0 : im.jl) + ' ' + im.unit;
                 this.ImData.push(im);
-                if (p.qc_qty && p.qc_qty !== 0) {
-                    for (const c of this.changes) {
-                        if (c.lid === p.id && c.pid == -1 && c.qty && c.qty !== 0) {
-                            im.changes.push(c);
-                        }
+                for (const c of this.changes) {
+                    if (c.lid === p.id && c.pid == -1 && c.qty && c.qty !== 0) {
+                        im.changes.push(c);
                     }
-                    this.import_changes.forEach(x => { if (x.lid === p.id) im.changes.push(x) });
                 }
+                this.import_changes.forEach(x => { if (x.lid === p.id) im.changes.push(x) });
             }
         }
     }

+ 7 - 5
app/lib/wechat.js

@@ -9,6 +9,7 @@
  */
 
 // const xmlReader = require('xmlreader');
+const _ = require('lodash');
 const moment = require('moment');
 const wxConst = require('../const/wechat_template.js');
 class WX {
@@ -69,12 +70,13 @@ class WX {
                         break;
                     case wxConst.template.change:
                         templateId = wxConst.templateId.change;
-                        url = this.ctx.protocol + '://' + this.ctx.host + '/wx/url2wap?project=' + data.code + '&url=' + sck + data.wap_url;
-                        remark = data.status === wxConst.status.check ? '微信可快速审批,如需进行详细审批' :
-                            (data.status === wxConst.status.success ? '审批已通过,查看审批结果' : '审批被退回,查看退回结果');
+                        url = data.wap_url ? this.ctx.protocol + '://' + this.ctx.host + '/wx/url2wap?project=' + data.code + '&url=' + sck + data.wap_url : '';
+                        remark = data.status === wxConst.status.check ? (data.type && _.indexOf(['apply', 'project'], data.type) !== -1 ? '微信暂无法在线审批' : '微信可快速审批,如需进行详细审批') :
+                            (data.status === wxConst.status.success ? '审批已通过,查看审批结果' :
+                                (data.status === wxConst.status.back ? '审批被退回,查看退回结果' : '审批已终止,查看终止结果'));
                         msgData = {
                             first: {
-                                value: '您好,工程变更申请' + data.tips,
+                                value: '您好,' + (data.type ? wxConst.changeType[data.type] : '工程变更申请') + data.tips,
                             },
                             keyword1: {
                                 value: data.projectName,
@@ -138,7 +140,7 @@ class WX {
                         break;
                     case wxConst.template.material:
                         templateId = wxConst.templateId.material;
-                        url = this.ctx.protocol + '://' + this.ctx.host + '/wx/url2wap?project=' + data.code + '&url=' + sck + data.wap_url;
+                        // url = this.ctx.protocol + '://' + this.ctx.host + '/wx/url2wap?project=' + data.code + '&url=' + sck + data.wap_url;
                         remark = data.status === wxConst.status.check ? '微信暂无法在线审批' :
                             (data.status === wxConst.status.success ? '审批已通过,查看审批结果' : '审批被退回,查看退回结果');
                         msgData = {

+ 0 - 1
app/middleware/auto_finish_logger.js

@@ -33,7 +33,6 @@ module.exports = options => {
         } else {
             logData = {
                 requestTime: ctx.logTime, responseTime, runTime,
-                data: ctx.body,
             };
         }
         const bLogger = runTime > 500 ? ctx.getLogger('warning') : ctx.getLogger('finish');

BIN
app/public/css/about.png


BIN
app/public/css/bg_participate_blue.png


BIN
app/public/css/bg_participate_orange.png


+ 58 - 28
app/public/css/main.css

@@ -87,7 +87,7 @@ font-size: .875rem;
 }
 .custom-control-warning-input:checked ~ .custom-control-warning-label::before{
   border-color:#da9500 ;
-  background-color:#da9500 
+  background-color:#da9500
 }
 .custom-control-warning-label{
   color:#da9500;
@@ -195,23 +195,23 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
 /*滚动条*/
 /* 滚动条 */
 /*水平滚动条的样式*/
-/*::-webkit-scrollbar-thumb:horizontal { 
+/*::-webkit-scrollbar-thumb:horizontal {
 	width: 5px;
 	background-color: #e9ecef;
 	-webkit-border-radius: 0;
 }*/
 /*滚动条的背景颜色,滚动条的圆角宽度*/
 /*::-webkit-scrollbar-track-piece {
-	background-color: #efefef; 
-	-webkit-border-radius: 0; 
+	background-color: #efefef;
+	-webkit-border-radius: 0;
 }*/
 /*滚动条的宽度,滚动条的高度*/
 /*::-webkit-scrollbar {
-	width: 14px; 
-	height: 14px; 
+	width: 14px;
+	height: 14px;
 }*/
 /*垂直滚动条的样式*/
-/*::-webkit-scrollbar-thumb:vertical { 
+/*::-webkit-scrollbar-thumb:vertical {
 	height: 50px;
 	background-color: #e9ecef;
 	-webkit-border-radius: 0;
@@ -220,7 +220,7 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
 	border: 1px solid #ced4da;
 }*/
 /*滚动条的hover样式*/
-/*::-webkit-scrollbar-thumb:hover { 
+/*::-webkit-scrollbar-thumb:hover {
 	height: 50px;
 	background-color: #ced4da;
 	-webkit-border-radius: 0;
@@ -870,7 +870,7 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
   font-size: 14px
 }
 .bd-toc {
-  
+
     position: sticky;
     top:3rem;
     height: calc(100vh - 10rem);
@@ -1028,7 +1028,7 @@ body{
   line-height: 30px;
 }
 .panel-title > .title-main .btn.pull-right {
-    margin: 5px 0 0 0 
+    margin: 5px 0 0 0
 }
 .panel-content{
   padding-top:35px;
@@ -1246,7 +1246,7 @@ a.maintain-icon .fa{
     }
 }
 
-a.maintain-icon:hover .fa{ 
+a.maintain-icon:hover .fa{
     animation-iteration-count:0
 }
 /*审批列表*/
@@ -1369,10 +1369,10 @@ overflow-y: auto;
   position: relative;
 }
 .circle{
-  width: 62px; 
+  width: 62px;
   height: 62px;
   border-radius: 50%;
-  background: none; 
+  background: none;
   border: 4px solid #D7B014;
 }
 .circle-num{
@@ -1822,36 +1822,60 @@ overflow-y: auto;
   width: 70px;
   text-align: center;
 }
-.bg-new-red{
+.bg-new-advance{
   background: rgba(241, 82, 91, 0.08) !important;
 }
-.bg-new-orange{
+.bg-new-ledger{
   background: rgba(250, 140, 22, 0.08) !important;
 }
-.bg-new-yellow{
+.bg-new-revise{
   background: rgba(251, 182, 45, 0.08) !important;
 }
-.bg-new-green{
+.bg-new-stage{
   background: rgba(82, 196, 26, 0.08) !important;
 }
-.bg-new-blue{
+.bg-new-changeProject{
   background: rgba(51, 119, 255, 0.08) !important;
 }
-.text-new-red{
+.bg-new-changePlan{
+    background: rgba(114, 46, 209, 0.08) !important;
+}
+.bg-new-change{
+    background: rgba(22, 208, 208, 0.08) !important;
+}
+.bg-new-changeApply{
+    background: rgba(41, 58, 210, 0.08) !important;
+}
+.bg-new-material{
+    background: rgba(187, 41, 210, 0.08) !important;
+}
+.text-new-advance{
   color: rgba(241, 82, 91, 1) !important;
 }
-.text-new-orange{
+.text-new-ledger{
   color: rgba(250, 140, 22, 1) !important;
 }
-.text-new-yellow{
+.text-new-revise{
   color: rgba(251, 182, 45, 1) !important;
 }
-.text-new-green{
+.text-new-stage{
   color: rgba(82, 196, 26, 1) !important;
 }
-.text-new-blue{
+.text-new-changeProject{
   color: rgba(51, 119, 255, 1) !important;
 }
+.text-new-changePlan{
+    color: rgba(114, 46, 209, 1) !important;
+}
+.text-new-change{
+    color: rgba(22, 208, 208, 1) !important;
+}
+.text-new-changeApply{
+    color: rgba(41, 58, 210, 1) !important;
+}
+.text-new-material{
+    color: rgba(187, 41, 210, 1); !important;
+}
 .text-width{
   width: 66px;
   text-align: center;
@@ -1889,8 +1913,8 @@ overflow-y: auto;
   font-size: 36px;
 }
 .list-text-vertical{
-  overflow:hidden; 
-  text-overflow:ellipsis; 
+  overflow:hidden;
+  text-overflow:ellipsis;
   white-space:nowrap;
 }
 .about-text i{
@@ -1914,12 +1938,12 @@ overflow-y: auto;
 }
 /*@media (min-width: 768px){
   .weixin-erweima img{
-    width:90%; 
+    width:90%;
     height:auto;
   }
 }*/
 .weixin-erweima img{
-  width:75%; 
+  width:75%;
   height:auto;
 }
 .weixin-erweima span{
@@ -1952,4 +1976,10 @@ animation:shake 1s .2s ease both;}
 0%,100%{transform:translateX(0);}
 10%,30%,50%,70%, 90%{transform:translateX(-10px);}
 20%,40%,60%,80%{transform:translateX(10px);}
-}
+}
+.margin-inputbox{
+  margin-top: -4px;
+}
+.margin-inputbox .height-inputbox{
+  height: 30px !important;
+}

BIN
app/public/images/erweima.jpg


BIN
app/public/images/juecedaping01.png


BIN
app/public/images/juecedaping02.png


BIN
app/public/images/nulllogo.png


BIN
app/public/images/wechat.png


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

@@ -79,7 +79,7 @@ $(document).ready(function () {
 
     $('a[data-target="#sp-list" ]').on('click', function () {
         const id = $(this).data('vid')
-        postData(`${window.location.pathname.replace('/material', '')}/${id}/auditors`, {}, (res) => {
+        postData(`${window.location.pathname}/${id}/auditors`, {}, (res) => {
             const { auditHistory, auditors, user } = res
             let auditorsHTML = ''
             let historyHTML = ''

+ 118 - 106
app/public/js/budget_compare.js

@@ -39,13 +39,102 @@ $(document).ready(() => {
     sjsSettingObj.setFxTreeStyle(spreadSetting, sjsSettingObj.FxTreeStyle.jz);
     SpreadJsObj.initSheet(compareSheet, spreadSetting);
 
-    const compareTree = createNewPathTree('final', {
-        id: 'id',
-        pid: 'pid',
-        order: 'order',
-        level: 'level',
-        rootId: -1,
-    });
+    let sfSelect;
+    const compareObj = {
+        curFinalId() {
+            return this.finalInfo ? this.finalInfo.id : undefined;
+        },
+        initFinalCol() {
+            if (spreadSetting.cols.length < 13) {
+                spreadSetting.cols.push(...[
+                    {title: '台账|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'dgn_qty', hAlign: 2, width: 80},
+                    {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'dgn_price', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '决算|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'final_dgn_qty', hAlign: 2, width: 80},
+                    {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'final_dgn_price', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'final_tp', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '增幅%|数量1/数量2', colSpan: '2|1', rowSpan: '1|1', field: 'grow_dgn_qty', hAlign: 2, width: 80},
+                    {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'grow_tp', hAlign: 2, width: 80, type: 'Number'},
+                ]);
+                SpreadJsObj.reLoadSheetHeader(compareSheet);
+            };
+        },
+        loadBudgetData(result) {
+            const compareTree = createNewPathTree('final', {
+                id: 'id',
+                pid: 'pid',
+                order: 'order',
+                level: 'level',
+                rootId: -1,
+            });
+            const setting = { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] };
+            const guTree = createNewPathTree('ledger', setting);
+            guTree.loadDatas(result.gu);
+            treeCalc.calculateAll(guTree);
+            compareTree.loadTree(guTree, function (cur, source) {
+                cur.base = true;
+                cur.gu_dgn_qty1 = ZhCalc.add(cur.gu_dgn_qty1, source.dgn_qty1);
+                cur.gu_dgn_qty2 = ZhCalc.add(cur.gu_dgn_qty2, source.dgn_qty2);
+                cur.gu_tp = ZhCalc.add(cur.gu_tp, source.total_price);
+            });
+            const gaiTree = createNewPathTree('ledger', setting);
+            gaiTree.loadDatas(result.gai);
+            treeCalc.calculateAll(gaiTree);
+            compareTree.loadTree(gaiTree, function (cur, source) {
+                cur.base = true;
+                cur.gai_dgn_qty1 = ZhCalc.add(cur.gai_dgn_qty1, source.dgn_qty1);
+                cur.gai_dgn_qty2 = ZhCalc.add(cur.gai_dgn_qty2, source.dgn_qty2);
+                cur.gai_tp = ZhCalc.add(cur.gai_tp, source.total_price);
+            });
+            const yuTree = createNewPathTree('ledger', setting);
+            yuTree.loadDatas(result.yu);
+            treeCalc.calculateAll(yuTree);
+            compareTree.loadTree(yuTree, function (cur, source) {
+                cur.base = true;
+                cur.yu_dgn_qty1 = ZhCalc.add(cur.yu_dgn_qty1, source.dgn_qty1);
+                cur.yu_dgn_qty2 = ZhCalc.add(cur.yu_dgn_qty2, source.dgn_qty2);
+                cur.yu_tp = ZhCalc.add(cur.yu_tp, source.total_price);
+            });
+            compareTree.afterLoad(node => {
+                node.gu_dgn_price = ZhCalc.div(node.gu_tp, node.gu_dgn_qty1, 2);
+                node.gu_dgn_qty = node.gu_dgn_qty1
+                    ? (node.gu_dgn_qty2 ? node.gu_dgn_qty1 + '/' + node.gu_dgn_qty2 : node.gu_dgn_qty1)
+                    : (node.gu_dgn_qty2 ? '/' + node.gu_dgn_qty2 : '');
+                node.gai_dgn_price = ZhCalc.div(node.gai_tp, node.gai_dgn_qty1, 2);
+                node.gai_dgn_qty = node.gai_dgn_qty1
+                    ? (node.gai_dgn_qty2 ? node.gai_dgn_qty1 + '/' + node.gai_dgn_qty2 : node.gai_dgn_qty1)
+                    : (node.gai_dgn_qty2 ? '/' + node.gai_dgn_qty2 : '');
+                node.yu_dgn_price = ZhCalc.div(node.yu_tp, node.yu_dgn_qty1, 2);
+                node.yu_dgn_qty = node.yu_dgn_qty1
+                    ? (node.yu_dgn_qty2 ? node.yu_dgn_qty1 + '/' + node.yu_dgn_qty2 : node.yu_dgn_qty1)
+                    : (node.yu_dgn_qty2 ? '/' + node.yu_dgn_qty2 : '');
+            });
+            compareTree.resortChildrenByCustom(function (x, y) {
+                const iCode = compareCode(x.code, y.code);
+                if (iCode) return iCode;
+                if (!x.name) return -1;
+                if (!y.name) return 1;
+                return x.name.localeCompare(y.name);
+            });
+            SpreadJsObj.loadSheetData(compareSheet, SpreadJsObj.DataType.Tree, compareTree);
+        },
+        loadFinalData(result, msg) {
+            if (msg) toastr.warning(msg);
+            this.finalInfo = result.finalInfo;
+            $('#final-info').html(`${moment(result.finalInfo.update_time).format('YYYY-MM-DD HH:mm:ss')} ${result.finalInfo.u_name}(${result.finalInfo.u_role})`);
+            this.initFinalCol();
+            const finalTree = createNewPathTree('ledger', {
+                id: 'tree_id',
+                pid: 'tree_pid',
+                order: 'order',
+                level: 'level',
+                rootId: -1,
+            });
+            finalTree.loadDatas(result.final);
+            SpreadJsObj.loadSheetData(compareSheet, SpreadJsObj.DataType.Tree, finalTree);
+            if (sfSelect) sfSelect.reloadSelect(this.finalInfo.tender);
+        }
+    };
 
     function compareCode(str1, str2, symbol = '-') {
         if (!str1) {
@@ -80,58 +169,13 @@ $(document).ready(() => {
         return aCodes.length - bCodes.length;
     }
 
-    postData(window.location.pathname + '/load', {}, function (result) {
-        const setting = { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] };
-        const guTree = createNewPathTree('ledger', setting);
-        guTree.loadDatas(result.gu);
-        treeCalc.calculateAll(guTree);
-        compareTree.loadTree(guTree, function (cur, source) {
-            cur.base = true;
-            cur.gu_dgn_qty1 = ZhCalc.add(cur.gu_dgn_qty1, source.dgn_qty1);
-            cur.gu_dgn_qty2 = ZhCalc.add(cur.gu_dgn_qty2, source.dgn_qty2);
-            cur.gu_tp = ZhCalc.add(cur.gu_tp, source.total_price);
-        });
-        const gaiTree = createNewPathTree('ledger', setting);
-        gaiTree.loadDatas(result.gai);
-        treeCalc.calculateAll(gaiTree);
-        compareTree.loadTree(gaiTree, function (cur, source) {
-            cur.base = true;
-            cur.gai_dgn_qty1 = ZhCalc.add(cur.gai_dgn_qty1, source.dgn_qty1);
-            cur.gai_dgn_qty2 = ZhCalc.add(cur.gai_dgn_qty2, source.dgn_qty2);
-            cur.gai_tp = ZhCalc.add(cur.gai_tp, source.total_price);
-        });
-        const yuTree = createNewPathTree('ledger', setting);
-        yuTree.loadDatas(result.yu);
-        treeCalc.calculateAll(yuTree);
-        compareTree.loadTree(yuTree, function (cur, source) {
-            cur.base = true;
-            cur.yu_dgn_qty1 = ZhCalc.add(cur.yu_dgn_qty1, source.dgn_qty1);
-            cur.yu_dgn_qty2 = ZhCalc.add(cur.yu_dgn_qty2, source.dgn_qty2);
-            cur.yu_tp = ZhCalc.add(cur.yu_tp, source.total_price);
-        });
-        compareTree.afterLoad(node => {
-            if (node.code === '1')console.log(node);
-            node.gu_dgn_price = ZhCalc.div(node.gu_tp, node.gu_dgn_qty1, 2);
-            node.gu_dgn_qty = node.gu_dgn_qty1
-                ? (node.gu_dgn_qty2 ? node.gu_dgn_qty1 + '/' + node.gu_dgn_qty2 : node.gu_dgn_qty1)
-                : (node.gu_dgn_qty2 ? '/' + node.gu_dgn_qty2 : '');
-            node.gai_dgn_price = ZhCalc.div(node.gai_tp, node.gai_dgn_qty1, 2);
-            node.gai_dgn_qty = node.gai_dgn_qty1
-                ? (node.gai_dgn_qty2 ? node.gai_dgn_qty1 + '/' + node.gai_dgn_qty2 : node.gai_dgn_qty1)
-                : (node.gai_dgn_qty2 ? '/' + node.gai_dgn_qty2 : '');
-            node.yu_dgn_price = ZhCalc.div(node.yu_tp, node.yu_dgn_qty1, 2);
-            node.yu_dgn_qty = node.yu_dgn_qty1
-                ? (node.yu_dgn_qty2 ? node.yu_dgn_qty1 + '/' + node.yu_dgn_qty2 : node.yu_dgn_qty1)
-                : (node.yu_dgn_qty2 ? '/' + node.yu_dgn_qty2 : '');
-        });
-        compareTree.resortChildrenByCustom(function (x, y) {
-            const iCode = compareCode(x.code, y.code);
-            if (iCode) return iCode;
-            if (!x.name) return -1;
-            if (!y.name) return 1;
-            return x.name.localeCompare(y.name);
-        });
-        SpreadJsObj.loadSheetData(compareSheet, SpreadJsObj.DataType.Tree, compareTree);
+    postData(window.location.pathname + '/load', {}, function (result, msg) {
+        if (result.final) {
+            compareObj.loadFinalData(result, msg);
+        } else {
+            compareObj.loadBudgetData(result);
+        }
+
     });
 
     $.subMenu({
@@ -186,6 +230,11 @@ $(document).ready(() => {
                 node.lastStageOrder =`第${source.lastStageOrder}期`;
                 node.lastStageStatus = source.lastStageStatus;
             });
+            if (compareObj.finalInfo) {
+                this.selectTree.datas.forEach(x => {
+                    x.selected = compareObj.finalInfo.tender.indexOf(x.tid + '') >= 0;
+                })
+            }
             const sfSpreadSetting = {
                 cols: [
                     {title: '选择', field: 'selected', hAlign: 1, width: 40, formatter: '@', cellType: 'checkbox'},
@@ -229,56 +278,20 @@ $(document).ready(() => {
             $('#select-final-ok').click(() => {
                 const rela = self.getSelects();
                 if (rela.length === 0) return;
-                postData(window.location.pathname + '/final', {id: rela}, function(result) {
-                    if (spreadSetting.cols.length < 13) {
-                        spreadSetting.cols.push(...[
-                            {title: '台账|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'dgn_qty', hAlign: 2, width: 80},
-                            {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'dgn_price', hAlign: 2, width: 80, type: 'Number'},
-                            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 80, type: 'Number'},
-                            {title: '决算|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'final_dgn_qty', hAlign: 2, width: 80},
-                            {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'final_dgn_price', hAlign: 2, width: 80, type: 'Number'},
-                            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'final_tp', hAlign: 2, width: 80, type: 'Number'},
-                            {title: '增幅%|数量1/数量2', colSpan: '2|1', rowSpan: '1|1', field: 'grow_dgn_qty', hAlign: 2, width: 80},
-                            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'grow_tp', hAlign: 2, width: 80, type: 'Number'},
-                        ]);
-                    }
-                    const setting = { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price', 'end_gather_tp'] };
-                    compareTree.clearFinal();
-                    for (const r of result) {
-                        const tree = createNewPathTree('ledger', setting);
-                        tree.loadDatas(r);
-                        treeCalc.calculateAll(tree);
-                        compareTree.loadTree(tree, function (cur, source) {
-                            cur.total_price = ZhCalc.add(cur.total_price, source.total_price);
-                            cur.dgn_qty1 = ZhCalc.add(cur.dgn_qty1, source.dgn_qty1);
-                            cur.dgn_qty2 = ZhCalc.add(cur.dgn_qty2, source.dgn_qty2);
-                            cur.final_dgn_qty1 = ZhCalc.sum([cur.final_dgn_qty1, source.deal_dgn_qty1, source.c_dgn_qty1]);
-                            cur.final_dgn_qty2 = ZhCalc.sum([cur.final_dgn_qty2, source.deal_dgn_qty2, source.c_dgn_qty2]);
-                            cur.final_tp = ZhCalc.add(cur.final_tp, source.end_gather_tp);
-                        });
-                    }
-                    compareTree.afterLoad(node => {
-                        node.dgn_price = ZhCalc.div(node.total_price, node.dgn_qty1, 2);
-                        node.dgn_qty = node.dgn_qty1
-                            ? (node.dgn_qty2 ? node.dgn_qty1 + '/' + node.dgn_qty2 : node.dgn_qty1)
-                            : (node.dgn_qty2 ? '/' + node.dgn_qty2 : '');
-                        node.final_dgn_price = ZhCalc.div(node.final_tp, node.final_dgn_qty1, 2);
-                        node.final_dgn_qty = node.final_dgn_qty1
-                            ? (node.final_dgn_qty2 ? node.final_dgn_qty1 + '/' + node.final_dgn_qty2 : node.final_dgn_qty1)
-                            : (node.final_dgn_qty2 ? '/' + node.final_dgn_qty2 : '');
-                        node.grow_dgn_qty1 = ZhCalc.mul(ZhCalc.div(ZhCalc.sub(node.final_dgn_qty1, node.gai_dgn_qty1), node.gai_dgn_qty1, 4), 100);
-                        node.grow_dgn_qty2 = ZhCalc.mul(ZhCalc.div(ZhCalc.sub(node.final_dgn_qty2, node.gai_dgn_qty2), node.gai_dgn_qty2, 4), 100);
-                        node.grow_dgn_qty = node.grow_dgn_qty1
-                            ? (node.grow_dgn_qty2 ? node.grow_dgn_qty1 + '/' + node.grow_dgn_qty2 : node.grow_dgn_qty1)
-                            : (node.grow_dgn_qty2 ? '/' + node.grow_dgn_qty2 : '');
-                        node.grow_tp = ZhCalc.mul(ZhCalc.div(ZhCalc.sub(node.final_tp, node.gai_tp), node.gai_tp, 4), 100);
-                    });
-                    SpreadJsObj.reLoadSheetHeader(compareSheet);
-                    SpreadJsObj.reLoadSheetData(compareSheet);
+                postData(window.location.pathname + '/final', {final_id: compareObj.curFinalId(), id: rela}, function(result, msg) {
+                    compareObj.loadFinalData(result, msg);
                     $('#select-final').modal('hide');
                 });
             });
         }
+        reloadSelect(select) {
+            if (compareObj.finalInfo) {
+                this.selectTree.datas.forEach(x => {
+                    x.selected = select.indexOf(x.tid + '') >= 0;
+                })
+            }
+            SpreadJsObj.reloadColData(this.sheet, 0);
+        }
         selectNode(node, select) {
             const posterity = this.selectTree.getPosterity(node);
             posterity.unshift(node);
@@ -295,7 +308,6 @@ $(document).ready(() => {
         }
     }
 
-    let sfSelect;
     $('#select-final').on('shown.bs.modal', () => {
         if (!sfSelect) sfSelect = new sfObject();
     });

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

@@ -136,6 +136,16 @@ class codeRuleSet {
 }
 
 $(document).ready(() => {
+
+    $("#change-table").colResizable({
+        liveDrag:true,
+        gripInnerHtml:"<div class='grip'></div>",
+        draggingClass:"dragging",
+        resizeMode:'fit',
+        postbackSafe:true,
+        partialRefresh:true
+    });
+    
     // 首次进入设置
     let showNoNeed = false;
     if (parseInt(cRuleFirst)) {

+ 20 - 2
app/public/js/change_information.js

@@ -21,7 +21,7 @@ $(document).ready(() => {
     if (cca !== null && cca !== undefined) {
         $('#customCheck1').prop('checked', cca !== 'false');
     }
-    changeSpreadSheet.setColumnVisible(3,$('#customCheck1').is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
+    changeSpreadSheet.setColumnVisible(2,$('#customCheck1').is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
     // 变更详情展示和隐藏
     $('.change-detail-checkbox').on('click', function (e) {
         if($(e.target).is('label')){
@@ -29,7 +29,7 @@ $(document).ready(() => {
         }
         // // 设置用户项目本地记录展示和隐藏情况
         setLocalCache('change-checkbox-account-'+ accountId, $(this).is(':checked'));
-        changeSpreadSheet.setColumnVisible(3,$(this).is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
+        changeSpreadSheet.setColumnVisible(2,$(this).is(':checked'), GC.Spread.Sheets.SheetArea.viewport);
     });
 
     // 计算最新的变更总额和change的total_price是否一致,不一致则更新
@@ -295,6 +295,24 @@ $(document).ready(() => {
         }
     };
     SpreadJsObj.initSheet(xmjSpread.getActiveSheet(), xmjSpreadSetting);
+
+    $.divResizer({
+        select: '#right-spr',
+        callback: function () {
+            changeSpread.refresh();
+            xmjSpread.refresh();
+            const width = (($('#right-view').width()/$('#right-view').parent('div').width())*100).toFixed();
+            setLocalCache('change_information_width', width);
+        }
+    });
+
+    // 根据浏览器记录展开收起
+    if (getLocalCache('change_information_width')) {
+        $('#left-view').css('width', (100 - parseFloat(getLocalCache('change_information_width'))) + '%');
+        $('#right-view').css('width', getLocalCache('change_information_width') + '%');
+        changeSpread.refresh();
+        xmjSpread.refresh();
+    }
 });
 function findDecimal(unit) {
     let value = precision.other.value;

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

@@ -21,10 +21,10 @@ $(document).ready(() => {
         cols: [
             {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
             {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 120, formatter: '@', readOnly: true},
-            {title: '变更部位', colSpan: '1', rowSpan: '2', field: 'bwmx', hAlign: 0, width: 120, formatter: '@', readOnly: true},
             {title: '变更详情', colSpan: '1', rowSpan: '2', field: 'detail', hAlign: 0, width: 120, formatter: '@', readOnly: true},
             {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: true},
             {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.unit_price', readOnly: true},
+            {title: '变更部位', colSpan: '1', rowSpan: '2', field: 'bwmx', hAlign: 0, width: 120, formatter: '@', readOnly: true},
             {title: '原设计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'oamount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.oamount', readOnly: true},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'oa_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.oa_tp', readOnly: true},
             {title: '申请变更增(+)减(-)|数量', colSpan: '2|1', rowSpan: '1|1', field: 'camount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.camount', readOnly: true},

+ 496 - 35
app/public/js/change_information_set.js

@@ -96,10 +96,10 @@ $(document).ready(() => {
         cols: [
             {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 80, formatter: '@', readOnly: 'readOnly.isEdit2'},
             {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 120, formatter: '@', readOnly: 'readOnly.isEdit2'},
-            {title: '变更部位', colSpan: '1', rowSpan: '2', field: 'bwmx', hAlign: 0, width: 120, formatter: '@', readOnly: 'readOnly.isEdit'},
             {title: '变更详情', colSpan: '1', rowSpan: '2', field: 'detail', hAlign: 0, width: 120, formatter: '@', readOnly: false},
             {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: 'readOnly.isEdit2', cellType: 'unit', comboItems: changeUnits, comboEdit: true},
             {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.isEdit2', getValue: 'getValue.unit_price'},
+            {title: '变更部位', colSpan: '1', rowSpan: '2', field: 'bwmx', hAlign: 0, width: 120, formatter: '@', readOnly: 'readOnly.isEdit2'},
             {title: '原设计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'oamount', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.isEdit', getValue: 'getValue.oamount'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'oa_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, getValue: 'getValue.oa_tp'},
             {title: '申请变更增(+)减(-)|数量', colSpan: '2|1', rowSpan: '1|1', field: 'camount', hAlign: 2, width: 60, type: 'Number', readOnly: false, getValue: 'getValue.camount'},
@@ -187,41 +187,185 @@ $(document).ready(() => {
             changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 9, cSum !== 0 ? cSum : null);
         },
         add: function () {
-            postData(window.location.pathname + '/save', {type: 'add'}, function (result) {
+            let select = null;
+            if (changeOrder) {
+                select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+            }
+            postData(window.location.pathname + '/save', {type: 'add', postData: select ? select.order : null}, function (result) {
                 if (result) {
-                    changeList.push(result);
-                    changeSpreadSheet.addRows(changeList.length - 1, 1);
-                    SpreadJsObj.reLoadRowData(changeSpreadSheet, changeList.length - 1);
+                    if (changeOrder === 1 && select) {
+                        // 批量更新changeList的order值
+                        _.forEach(changeList, function (item) {
+                            item.order = item.order > select.order ? item.order + 1 : item.order;
+                        });
+                        changeList.splice(select.order, 0, result);
+                        changeSpreadSheet.addRows(select.order, 1);
+                        SpreadJsObj.reLoadRowData(changeSpreadSheet, select.order);
+                        changeSpreadSheet.setSelection(select.order, 0, 1, 1);
+                        console.log(changeList);
+                    } else {
+                        changeList.push(result);
+                        changeSpreadSheet.addRows(changeList.length - 1, 1);
+                        SpreadJsObj.reLoadRowData(changeSpreadSheet, changeList.length - 1);
+                        changeSpreadSheet.setSelection(changeList.length - 1, 0, 1, 1);
+                    }
                     changeSpreadSheet.setStyle(changeSpreadSheet.getRowCount() - 1, -1, style1);
-                    changeSpreadSheet.setSelection(changeList.length - 1, 0, 1, 1);
                     changeSpreadObj.resetXmjSpread();
+                    changeSpreadObj.refreshActn();
                 }
             });
         },
         batchAdd: function(num) {
-            postData(window.location.pathname + '/save', {type: 'batchadd', num}, function (result) {
+            let select = null;
+            if (changeOrder) {
+                select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+            }
+            postData(window.location.pathname + '/save', {type: 'batchadd', num, postData: select ? select.order : null}, function (result) {
                 if (result) {
-                    changeList = _.concat(changeList, result);
+                    changeList = result;
                     SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
                     changeSpreadObj.makeSjsFooter();
                     changeSpreadObj.resetXmjSpread();
+                    changeSpreadObj.refreshActn();
                 }
             });
         },
         del: function () {
-            const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
-            const index = changeList.indexOf(select);
-            if (index > -1 && !_.find(changeUsedData, { cbid: select.id })) {
-                postData(window.location.pathname + '/save', {type: 'del', id: select.id}, function (result) {
-                    changeList.splice(index, 1);
-                    changeSpreadSheet.deleteRows(index, 1);
+            const selection = changeSpreadSheet.getSelections();
+            const sel = selection ? selection[0] : changeSpreadSheet.getSelections()[0];
+            const row = sel && sel.row !== undefined ? sel.row : -1;
+            if (readOnly || row === -1 || sel.row + sel.rowCount > changeList.length) {
+                return false;
+            }
+            const delList = [];
+            let lastSelect = null;
+            let hadTaiZhang = false;
+            for (let r = 0; r < sel.rowCount; r++) {
+                const select = changeList[row + r];
+                if(!select || _.find(changeUsedData, { cbid: select.id })) {
+                    return false;
+                }
+                if (r === sel.rowCount - 1 && changeOrder) {
+                    lastSelect = select;
+                }
+                if (select.lid != 0) {
+                    hadTaiZhang = true;
+                }
+                delList.push(select.id);
+            }
+            console.log(lastSelect, delList);
+            if (delList.length) {
+                postData(window.location.pathname + '/save', {type: 'del', ids: delList, postData: lastSelect ? lastSelect.order : null}, function (result) {
+                    changeList.splice(row, delList.length);
+                    changeSpreadSheet.deleteRows(row, delList.length);
                     const sel = changeSpreadSheet.getSelections();
                     changeSpreadSheet.setSelection(0, 0, 1, 1);
                     changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));
-                    if (select.lid != 0) {
+                    if (hadTaiZhang) {
                         tableDataRemake(changeListData);
                     }
                     changeSpreadObj.countSum();
+                    changeSpreadObj.refreshActn();
+                });
+            }
+            // const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+            // const index = changeList.indexOf(select);
+            // if (index > -1 && !_.find(changeUsedData, { cbid: select.id })) {
+                // postData(window.location.pathname + '/save', {type: 'del', id: select.id}, function (result) {
+                //     changeList.splice(index, 1);
+                //     changeSpreadSheet.deleteRows(index, 1);
+                //     const sel = changeSpreadSheet.getSelections();
+                //     changeSpreadSheet.setSelection(0, 0, 1, 1);
+                //     changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));
+                //     if (select.lid != 0) {
+                //         tableDataRemake(changeListData);
+                //     }
+                //     changeSpreadObj.countSum();
+                //     changeSpreadObj.refreshActn();
+                // });
+            // }
+        },
+        upMove: function () {
+            const data = {
+                type: 'changeOrder',
+                postData: [],
+            };
+            const selection = changeSpreadSheet.getSelections();
+            const row = selection[0].row, count = selection[0].rowCount;
+            const first = changeSpreadSheet.zh_data[row];
+            if (!first) {
+                changeSpreadObj.refreshActn();
+                return false;
+            }
+            const pre = changeSpreadSheet.zh_data[row - 1], preUpdate = {id: pre.id};
+            for (let iRow = 0; iRow < count; iRow++) {
+                const posData = changeSpreadSheet.zh_data[iRow + row];
+                if (posData) {
+                    data.postData.push({id: posData.id, order: changeSpreadSheet.zh_data[iRow + row - 1].order});
+                    preUpdate.order = posData.order;
+                }
+            }
+            data.postData.push(preUpdate);
+            console.log(data);
+            if (data.postData.length > 0) {
+                postData(window.location.pathname + '/save', data, function () {
+                    _.forEach(data.postData, function (item) {
+                        const cl = _.find(changeList, { id: item.id });
+                        cl.order = item.order;
+                    });
+                    changeList.sort(function (a, b) {
+                        return a.order - b.order
+                    });
+                    SpreadJsObj.reLoadSheetData(changeSpreadSheet);
+                    changeSpreadObj.makeSjsFooter();
+                    const sel = selection[0];
+                    if (sel) {
+                        changeSpreadSheet.setSelection(changeSpreadSheet.zh_data.indexOf(first), sel.col, sel.rowCount, sel.colCount);
+                    }
+                    changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));
+                    changeSpreadObj.refreshActn();
+                });
+            }
+        },
+        downMove: function () {
+            const data = {
+                type: 'changeOrder',
+                postData: [],
+            };
+            const selection = changeSpreadSheet.getSelections();
+            const row = selection[0].row, count = selection[0].rowCount;
+            const first = changeSpreadSheet.zh_data[row];
+            if (!first) {
+                changeSpreadObj.refreshActn();
+                return false;
+            }
+            const next = changeSpreadSheet.zh_data[row + count], nextUpdate = {id: next.id};
+            for (let iRow = count - 1; iRow >= 0; iRow--) {
+                const posData = changeSpreadSheet.zh_data[iRow + row];
+                if (posData) {
+                    data.postData.push({id: posData.id, order: changeSpreadSheet.zh_data[iRow + row + 1].order});
+                    nextUpdate.order = posData.order;
+                }
+            }
+            data.postData.push(nextUpdate);
+            console.log(data);
+            if (data.postData.length > 0) {
+                postData(window.location.pathname + '/save', data, function () {
+                    _.forEach(data.postData, function (item) {
+                        const cl = _.find(changeList, { id: item.id });
+                        cl.order = item.order;
+                    });
+                    changeList.sort(function (a, b) {
+                        return a.order - b.order
+                    });
+                    SpreadJsObj.reLoadSheetData(changeSpreadSheet);
+                    changeSpreadObj.makeSjsFooter();
+                    const sel = selection[0];
+                    if (sel) {
+                        changeSpreadSheet.setSelection(changeSpreadSheet.zh_data.indexOf(first), sel.col, sel.rowCount, sel.colCount);
+                    }
+                    changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));
+                    changeSpreadObj.refreshActn();
                 });
             }
         },
@@ -244,6 +388,36 @@ $(document).ready(() => {
                 changeSpreadObj.del();
             }
             changeSpreadObj.resetXmjSpread(data);
+            changeSpreadObj.refreshActn();
+        },
+        refreshActn: function (rowCount = 1) {
+            const setObjEnable = function (obj, enable) {
+                if (enable) {
+                    obj.removeClass('disabled');
+                } else {
+                    obj.addClass('disabled');
+                }
+            };
+            // const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+            // 还需判断是否已被调差清单调用
+            const selection = changeSpreadSheet.getSelections();
+            const sel = selection ? selection[0] : changeSpreadSheet.getSelections()[0];
+            const row = sel ? sel.row : -1;
+            const first = changeList[row];
+            let last = first;
+            if (sel.rowCount > 1 && first) {
+                for (let r = 1; r < sel.rowCount; r++) {
+                    const rNode = changeList[sel.row + r];
+                    if (!rNode) break;
+                    last = rNode;
+                }
+            }
+            const preNode = changeList[row - 1];
+
+            setObjEnable($('#up-move'), !readOnly && first && preNode && changeList.indexOf(last) > 0 && sel.row + sel.rowCount <= changeList.length);
+            setObjEnable($('#down-move'), !readOnly && first && changeList.indexOf(last) < changeList.length - 1 && sel.row + sel.rowCount <= changeList.length);
+            // setObjEnable($('#open-list-modal'), !readOnly && select && changeList.indexOf(select) !== -1);
+            // setObjEnable($('#add-white-btn'), !readOnly && select && changeList.indexOf(select) !== -1);
         },
         deletePress: function (sheet) {
             return;
@@ -529,10 +703,13 @@ $(document).ready(() => {
         // filter.sortColumn(0, true);
         changeSpreadObj.makeSjsFooter();
         changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));
+        changeSpreadObj.refreshActn();
     });
 
     if (!readOnly) {
         $('#add-white-btn').click(changeSpreadObj.add);
+        $('#up-move').click(changeSpreadObj.upMove);
+        $('#down-move').click(changeSpreadObj.downMove);
         changeSpread.bind(spreadNS.Events.EditEnded, changeSpreadObj.editEnded);
         changeSpread.bind(spreadNS.Events.SelectionChanged, changeSpreadObj.selectionChanged);
         changeSpread.bind(spreadNS.Events.ClipboardPasted, changeSpreadObj.clipboardPasted);
@@ -578,6 +755,9 @@ $(document).ready(() => {
                 'createList': {
                     name: '添加台账清单',
                     icon: 'fa-sign-in',
+                    visible: function () {
+                        return changeOrder === 0;
+                    },
                     callback: function (key, opt) {
                         $('#addlist').modal('show');
                     },
@@ -585,6 +765,9 @@ $(document).ready(() => {
                 'createAdd': {
                     name: '添加空白清单',
                     icon: 'fa-sign-in',
+                    visible: function () {
+                        return changeOrder === 0;
+                    },
                     callback: function (key, opt) {
                         changeSpreadObj.add(changeSpreadSheet);
                     },
@@ -594,6 +777,9 @@ $(document).ready(() => {
                     type: 'batchInsert',
                     value: '2',
                     icon: 'fa-sign-in',
+                    visible: function () {
+                        return changeOrder === 0;
+                    },
                     batchInsert: function (obj, root) {
                         if (_.toNumber(obj.value) > _.toNumber(obj.max)) {
                             obj.value = obj.max;
@@ -608,6 +794,81 @@ $(document).ready(() => {
                         }
                     },
                 },
+                'createList1': {
+                    name: '插入台账清单',
+                    icon: 'fa-sign-in',
+                    visible: function () {
+                        return changeOrder === 1;
+                    },
+                    // disabled: function (key, opt) {
+                    //     const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+                    //     const sel = changeSpreadSheet.getSelections()[0];
+                    //     changeSpreadObj.resetXmjSpread(select);
+                    //     // console.log(select, sel);
+                    //     if (!readOnly && select && sel.row !== changeSpreadSheet.getRowCount() - 1) {
+                    //         return false;
+                    //     } else {
+                    //         return true;
+                    //     }
+                    // },
+                    callback: function (key, opt) {
+                        $('#addlist').modal('show');
+                    },
+                },
+                'createAdd1': {
+                    name: '插入空白清单',
+                    icon: 'fa-sign-in',
+                    visible: function () {
+                        return changeOrder === 1;
+                    },
+                    // disabled: function (key, opt) {
+                    //     const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+                    //     const sel = changeSpreadSheet.getSelections()[0];
+                    //     changeSpreadObj.resetXmjSpread(select);
+                    //     // console.log(select, sel);
+                    //     if (!readOnly && select && sel.row !== changeSpreadSheet.getRowCount() - 1) {
+                    //         return false;
+                    //     } else {
+                    //         return true;
+                    //     }
+                    // },
+                    callback: function (key, opt) {
+                        changeSpreadObj.add(changeSpreadSheet);
+                    },
+                },
+                'batchInsert1': {
+                    name: '批量插入空白清单',
+                    type: 'batchInsert',
+                    value: '2',
+                    icon: 'fa-sign-in',
+                    visible: function () {
+                        return changeOrder === 1;
+                    },
+                    // disabled: function (key, opt) {
+                    //     const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+                    //     const sel = changeSpreadSheet.getSelections()[0];
+                    //     changeSpreadObj.resetXmjSpread(select);
+                    //     // console.log(select, sel);
+                    //     if (!readOnly && select && sel.row !== changeSpreadSheet.getRowCount() - 1) {
+                    //         return false;
+                    //     } else {
+                    //         return true;
+                    //     }
+                    // },
+                    batchInsert: function (obj, root) {
+                        if (_.toNumber(obj.value) > _.toNumber(obj.max)) {
+                            obj.value = obj.max;
+                            toastr.warning('批量插入不可多于' + obj.max);
+                        } else if(_.toNumber(obj.value) < _.toNumber(obj.min)) {
+                            obj.value = obj.min;
+                            toastr.warning('批量插入不可少于' + obj.min);
+                        } else {
+                            // treeOperationObj.addNode(ledgerSpread.getActiveSheet(), parseInt(obj.value));
+                            changeSpreadObj.batchAdd(obj.value);
+                            root.$menu.trigger('contextmenu:hide');
+                        }
+                    },
+                },
                 'delete': {
                     name: '删除',
                     icon: 'fa-remove',
@@ -615,15 +876,29 @@ $(document).ready(() => {
                         changeSpreadObj.del(changeSpreadSheet);
                     },
                     disabled: function (key, opt) {
-                        const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
-                        const sel = changeSpreadSheet.getSelections()[0];
-                        changeSpreadObj.resetXmjSpread(select);
-                        // console.log(select, sel);
-                        if (!readOnly && select && sel.row !== changeSpreadSheet.getRowCount() - 1 && !_.find(changeUsedData, { cbid: select.id })) {
-                            return false;
-                        } else {
+                        // const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+                        // const sel = changeSpreadSheet.getSelections()[0];
+                        // changeSpreadObj.resetXmjSpread(select);
+                        // // console.log(select, sel);
+                        // if (!readOnly && select && sel.row !== changeSpreadSheet.getRowCount() - 1 && !_.find(changeUsedData, { cbid: select.id })) {
+                        //     return false;
+                        // } else {
+                        //     return true;
+                        // }
+                        const selection = changeSpreadSheet.getSelections();
+                        const sel = selection ? selection[0] : changeSpreadSheet.getSelections()[0];
+                        const row = sel && sel.row !== undefined ? sel.row : -1;
+                        if (readOnly || row === -1 || sel.row + sel.rowCount > changeList.length) {
                             return true;
                         }
+                        let isUsed = false;
+                        for (let r = 0; r < sel.rowCount; r++) {
+                            const select = changeList[row + r];
+                            if(!select || _.find(changeUsedData, { cbid: select.id })) {
+                                isUsed = true;
+                            }
+                        }
+                        return isUsed;
                     }
                 },
             }
@@ -637,8 +912,9 @@ $(document).ready(() => {
         $(this).addClass('table-warning');
         const isCheck = $(this).hasClass('table-success') ? true : false;
         const data_bwmx = $(this).attr('data-bwmx').split('$#$');
+        const data_charu = $(this).attr('data-charu') ? $(this).attr('data-charu').split('$#$') : [];
         const isDeal = $(this).data('gcl') !== undefined ? true : false;
-        let codeHtml = '<tr quantity="'+ $(this).children('td').eq(5).text() +'" gcl_id="" mx_id=""><td class="text-center">1</td><td colspan="7" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox"></td></tr>';
+        let codeHtml = '<tr quantity="'+ $(this).children('td').eq(5).text() +'" gcl_id="" mx_id=""><td class="text-center">1</td><td colspan="7" class="colspan_1">&nbsp;</td><td class="colspan_2 text-center"><input type="checkbox"></td></tr>';
         if (isDeal) {
             const lid = $(this).data('lid');
             let gcl = _.find(gclGatherData, function (item) {
@@ -653,15 +929,15 @@ $(document).ready(() => {
                 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 isChecked = data_bwmx.indexOf(
-                    leaf.code + '!_!' + (leaf.jldy ? leaf.jldy : '') + '!_!' +
+                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 : '') + '!_!' +
-                    (bwmx !== undefined ? bwmx : leaf.jldy ? leaf.jldy : '') + '*;*' + quantity) !== -1 && isCheck ?
-                    'checked' : '';
+                    (bwmx !== undefined ? bwmx : leaf.jldy ? leaf.jldy : '') + '*;*' + quantity;
+                const isChecked = data_bwmx.indexOf(pushMsg) !== -1 && isCheck ? 'checked ' : '';
                 const existGcl = _.find(changeList, {gcl_id: leaf.gcl_id, bwmx: (bwmx ? bwmx : leaf.jldy ? leaf.jldy : ''), oamount: leaf.quantity});
                 const isUsed = existGcl ? _.find(changeUsedData, { cbid: existGcl.id }) : null;
-                const isDisabled = isUsed ? 'disabled ' : '';
+                const isOldChaRu = changeOrder && isChecked && data_charu.indexOf(pushMsg) === -1;
+                const isDisabled = isUsed || isOldChaRu ? 'disabled ' : '';
                 codeHtml += '<tr quantity="' + quantity + '" gcl_id="' + gcl_id + '" mx_id="' + mx_id + '">' +
                     '<td class="text-center">' + (index+1) + (leaf.cid ? '<i class="text-danger" style="font-weight: 900">*</i>' : '') + '</td>' +
                     '<td>' + leaf.code + '</td>' +
@@ -676,7 +952,10 @@ $(document).ready(() => {
                 searchCodeList.push(leaf.code + '|!|' + (leaf.jldy ? leaf.jldy: '') + '|!|' + (bwmx !== undefined ? bwmx : ''));
             }
         } else if (!isDeal && isCheck) {
-            codeHtml = '<tr quantity="'+ $(this).children('td').eq(5).text() +'" gcl_id="" mx_id=""><td class="text-center">1</td><td colspan="7" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox" checked></td></tr>';
+            const pushMsg = '0*;*' + $(this).children('td').eq(5).text();
+            const isChecked = data_bwmx.indexOf(pushMsg) !== -1 && isCheck ? 'checked ' : '';
+            const isDisabeld = changeOrder && isChecked && data_charu.indexOf(pushMsg) === -1 ? 'disabled ' : '';
+            codeHtml = '<tr quantity="'+ $(this).children('td').eq(5).text() +'" gcl_id="" mx_id=""><td class="text-center">1</td><td colspan="7" class="colspan_1">&nbsp;</td><td class="colspan_2 text-center"><input type="checkbox" ' + isChecked + isDisabeld +'></td></tr>';
         }
         $('#code-list').attr('data-index', parseInt($(this).children('td').eq(0).text()));
         $('#code-input').val('');
@@ -688,6 +967,7 @@ $(document).ready(() => {
     // 右边项目节选择
     $('body').on('click', '#code-list input', function () {
         let index = $('#code-list').attr('data-index');
+        // 判断是否是自定义排序,是则另外保存一份到tr中,和data-bwmx不相通,最后提交再清除所有的data-charu清单
         if ($(this).is(':checked')) {
             // 去除其它可能已选的checked
             // $('#code-list input').prop('checked', false);
@@ -696,6 +976,7 @@ $(document).ready(() => {
             $('#table-list-select tr[data-index="' + index + '"]').addClass('table-success');
             // 去除部分data-detail值
             let data_bwmx = [];
+            let data_charu = [];
             $('#code-list input:checked').each(function () {
                 const tr = $(this).parents('tr');
                 const length = tr.children('td').length;
@@ -711,14 +992,22 @@ $(document).ready(() => {
                 const quantity = tr.attr('quantity');
                 const de_qu = bwmx + '*;*' + quantity;
                 data_bwmx.push(de_qu);
+                if (changeOrder && $(this).prop('disabled') !== true) {
+                    data_charu.push(de_qu);
+                }
             });
             data_bwmx = data_bwmx.join('$#$');
             $('#table-list-select tr[data-index="' + index + '"]').attr('data-bwmx', data_bwmx);
+            if (changeOrder) {
+                data_charu = data_charu.join('$#$');
+                $('#table-list-select tr[data-index="' + index + '"]').attr('data-charu', data_charu);
+            }
         } else {
             // 判断还有无选中项目节编号
             if ($('#code-list input').is(':checked')) {
                 // 去除部分data-detail值
                 let data_bwmx = [];
+                let data_charu = [];
                 $('#code-list input:checked').each(function () {
                     const tr = $(this).parents('tr');
                     const length = tr.children('td').length;
@@ -734,12 +1023,22 @@ $(document).ready(() => {
                     const quantity = tr.attr('quantity');
                     const de_qu = bwmx + '*;*' + quantity;
                     data_bwmx.push(de_qu);
+                    if (changeOrder && $(this).prop('disabled') !== true) {
+                        data_charu.push(de_qu);
+                    }
                 });
                 data_bwmx = data_bwmx.join('$#$');
                 $('#table-list-select tr[data-index="' + index + '"]').attr('data-bwmx', data_bwmx);
+                if (changeOrder) {
+                    data_charu = data_charu.join('$#$');
+                    $('#table-list-select tr[data-index="' + index + '"]').attr('data-charu', data_charu);
+                }
             } else {
                 $('#table-list-select tr[data-index="' + index + '"]').removeClass('table-success');
                 $('#table-list-select tr[data-index="' + index + '"]').attr('data-bwmx', '');
+                if (changeOrder) {
+                    $('#table-list-select tr[data-index="' + index + '"]').attr('data-charu', '');
+                }
             }
         }
         checkSelectAll();
@@ -747,17 +1046,30 @@ $(document).ready(() => {
 
     // 添加空白清单or签约清单
     $('.add-list-btn').on('click', function () {
+        let select = null;
+        if (changeOrder) {
+            select = SpreadJsObj.getSelectObject(changeSpreadSheet);
+        }
         const newLedgerList = remakeChangeSpread();
+        console.log(newLedgerList, select);
         // 更新至服务器
-        postData(window.location.pathname + '/save', { type:'ledger_list', updateData: newLedgerList }, function (result) {
+        postData(window.location.pathname + '/save', { type:'ledger_list', updateData: newLedgerList, postData: select ? select.order : null }, function (result) {
             changeList = result.changeList;
             changeUsedData = result.usedList;
             SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
             changeSpreadObj.makeSjsFooter();
             const select = SpreadJsObj.getSelectObject(changeSpreadSheet);
             changeSpreadObj.resetXmjSpread(select);
+            changeSpreadObj.refreshActn();
+            $('#table-list-select tr').attr('data-charu', '');
+            if(changeOrder) {
+                $('#code-list input:checked').each(function () {
+                    $(this).attr('disabled', true);
+                })
+            }
             $('#addlist').modal('hide');
         }, function () {
+            $('#table-list-select tr').attr('data-charu', '');
             $('#addlist').modal('hide');
         });
 
@@ -823,12 +1135,41 @@ $(document).ready(() => {
     });
     $('#list-input').on('blur', function () {
         const select = parseInt($('#select-list').val());
-        const value = $(this).val();
+        const value = _.trim($(this).val());
+        const valueList = _.slice(_.without(_.uniq(_.replace(value, /\t/g, ' ').split(' ')), ''), 0, 10);
+        console.log(valueList);
+        // 判断是否存在多个分词,以换行或空格分隔,多个则显示左侧菜单
+        if (value !== '' && valueList.length > 1) {
+            if (_.without(_.uniq(value.split(' ')), '').length > 10) {
+                toastr.warning('最多筛选以空格分割的前10个不重复关键词');
+            }
+            $('#table-list').addClass('col-9').removeClass('col-12');
+            $('#table-list').siblings('.col-3').show();
+            $('#list-search-keyword').html('<tr data-keyword="" class="text-white bg-primary"><td class="border-primary">全部</td></tr>');
+            for (const v of valueList) {
+                $('#list-search-keyword').append(`<tr data-keyword="${v}" class=""><td>${v}</td></tr>`);
+            }
+            $('#list-input').val(valueList.join(' '));
+        } else {
+            $('#table-list').addClass('col-12').removeClass('col-9');
+            $('#table-list').siblings('.col-3').hide();
+            $('#list-search-keyword').html('<tr data-keyword="" class="text-white bg-primary border-primary"><td class="border-primary">全部</td></tr>');
+        }
         let showListData = changeListData;
         if (select === 1 && value !== '') {
             $(this).siblings('a').show();
             showListData = _.filter(changeListData, function (c) {
-                return ((c.code && c.code.indexOf(value) !== -1) || (c.name && c.name.indexOf(value) !== -1)) && c.cid;
+                let flag = false;
+                if (c.cid) {
+                    for(const v of valueList) {
+                        if ((c.code && c.code.indexOf(v) !== -1) || (c.name && c.name.indexOf(v) !== -1)) {
+                            flag = true;
+                            break;
+                        }
+                    }
+                }
+                return flag;
+                // return ((c.code && c.code.indexOf(value) !== -1) || (c.name && c.name.indexOf(value) !== -1)) && c.cid;
             });
         } else if (select === 1 && value === '') {
             $(this).siblings('a').hide();
@@ -838,7 +1179,15 @@ $(document).ready(() => {
         } else if (value !== '') {
             $(this).siblings('a').show();
             showListData = _.filter(changeListData, function (c) {
-                return (c.code && c.code.indexOf(value) !== -1) || (c.name && c.name.indexOf(value) !== -1);
+                let flag = false;
+                for(const v of valueList) {
+                    if ((c.code && c.code.indexOf(v) !== -1) || (c.name && c.name.indexOf(v) !== -1)) {
+                        flag = true;
+                        break;
+                    }
+                }
+                return flag;
+                // return (c.code && c.code.indexOf(value) !== -1) || (c.name && c.name.indexOf(value) !== -1);
             });
         } else {
             $(this).siblings('a').hide();
@@ -850,6 +1199,77 @@ $(document).ready(() => {
         $('#code-list').html('');
         $('#code-select-all').prop('checked', false);
     });
+    // 双击编辑搜索值
+    $('body').on('dblclick', '#list-search-keyword tr', function () {
+        if ($(this).attr('data-keyword') !== '') {
+            $(this).children('td').html(`<input class="form-control form-control-sm" value="">`);
+            $(this).find('input').focus();
+            $(this).find('input').val($(this).attr('data-keyword'));
+        }
+    });
+    // 光标离开
+    $('body').on('blur', '#list-search-keyword tr td input', function () {
+        let val = $(this).val();
+        const oldVal = $(this).parents('tr').attr('data-keyword');
+        if (_.trim(val) === '') {
+            toastr.warning('不能为空');
+            val = oldVal;
+        } else if (_.trim(val) === oldVal) {
+            val = oldVal;
+        } else {
+            const value = _.trim($('#list-input').val());
+            const valueList = _.slice(_.without(_.uniq(_.replace(value, /\t/g, ' ').split(' ')), ''), 0, 10);
+            console.log(_.indexOf(valueList, oldVal), valueList, val, oldVal);
+            if (_.indexOf(valueList, val) !== -1) {
+                toastr.warning('已存在相同的检索词');
+                val = oldVal;
+            }
+            valueList.splice(_.indexOf(valueList, oldVal), 1, val);
+            $('#list-input').val(valueList.join(' '));
+            const select = parseInt($('#select-list').val());
+            const showListData = _.filter(changeListData, function (c) {
+                return ((c.code && c.code.indexOf(val) !== -1) || (c.name && c.name.indexOf(val) !== -1)) && (select === 1 ? c.cid : 1);
+            });
+            makeListTable(changeListData, showListData);
+            $('#table-list-select tr').removeClass('table-warning');
+            $('#code-input').val('');
+            $('#code-input').siblings('a').hide();
+            $('#code-list').html('');
+            $('#code-select-all').prop('checked', false);
+        }
+        $(this).parents('tr').attr('data-keyword', val);
+        $(this).parents('td').html(`${val}`);
+    });
+    $('body').on('keypress', '#list-search-keyword tr td input', function () {
+        if(window.event.keyCode === 13) {
+            $(this).blur();
+        }
+    });
+    $("#addlist").draggable({ handle: '.modal-header, .modal-footer'});
+    // 检索关键字切换
+    $('body').on('click', '#list-search-keyword tr', function () {
+        if (!$(this).hasClass('bg-primary')) {
+            const keyword = $(this).attr('data-keyword');
+            $(this).siblings().removeClass('text-white bg-primary');
+            $(this).siblings().children('td').removeClass('border-primary');
+            $(this).addClass('text-white bg-primary');
+            $(this).children('td').addClass('border-primary');
+            if (keyword === '') {
+                $('#list-input').blur();
+            } else {
+                const select = parseInt($('#select-list').val());
+                const showListData = _.filter(changeListData, function (c) {
+                    return ((c.code && c.code.indexOf(keyword) !== -1) || (c.name && c.name.indexOf(keyword) !== -1)) && (select === 1 ? c.cid : 1);
+                });
+                makeListTable(changeListData, showListData);
+                $('#table-list-select tr').removeClass('table-warning');
+                $('#code-input').val('');
+                $('#code-input').siblings('a').hide();
+                $('#code-list').html('');
+                $('#code-select-all').prop('checked', false);
+            }
+        }
+    });
     // 回车提交
     $('#code-input').on('keypress', function () {
         if(window.event.keyCode === 13) {
@@ -881,6 +1301,10 @@ $(document).ready(() => {
             makeListTable(changeListData, showListData);
             $('#table-list-select tr').removeClass('table-warning');
             $('#code-list').html('');
+            $('#table-list').addClass('col-12').removeClass('col-9');
+            $('#table-list').siblings('.col-3').hide();
+            $('#list-search-keyword').html('');
+            $('#list-search-keyword').siblings('a').addClass('active');
         } else {
             makeCodeTable();
         }
@@ -908,6 +1332,7 @@ $(document).ready(() => {
             if ($('#code-list input').is(':checked')) {
                 // 去除部分data-detail值
                 let data_bwmx = [];
+                let data_charu = [];
                 $('#code-list input:checked').each(function () {
                     const tr = $(this).parents('tr');
                     const length = tr.children('td').length;
@@ -923,13 +1348,23 @@ $(document).ready(() => {
                     const quantity = tr.attr('quantity');
                     const de_qu = bwmx + '*;*' + quantity;
                     data_bwmx.push(de_qu);
+                    if (changeOrder && $(this).prop('disabled') !== true) {
+                        data_charu.push(de_qu);
+                    }
                 });
                 data_bwmx = data_bwmx.join('$#$');
                 $('#table-list-select tr[data-index="' + index + '"]').attr('data-bwmx', data_bwmx);
+                if (changeOrder) {
+                    data_charu = data_charu.join('$#$');
+                    $('#table-list-select tr[data-index="' + index + '"]').attr('data-charu', data_charu);
+                }
                 $('#table-list-select tr[data-index="' + index + '"]').addClass('table-success');
             } else {
                 $('#table-list-select tr[data-index="' + index + '"]').removeClass('table-success');
                 $('#table-list-select tr[data-index="' + index + '"]').attr('data-bwmx', '');
+                if (changeOrder) {
+                    $('#table-list-select tr[data-index="' + index + '"]').attr('data-charu', '');
+                }
             }
         }
     });
@@ -1002,6 +1437,31 @@ $(document).ready(() => {
         }
         toastr.success('已还原到上次保存状态');
     });
+
+    $('.dropdown-menu input[name="paixu"]').on('click', function () {
+        const newChangeOrder = parseInt($(this).val());
+        if (newChangeOrder !== changeOrder) {
+            const newLedgerList = changeOrder ? remakeChangeSpread(0) : [];
+            // 更新至服务器
+            postData(window.location.pathname + '/save', { type:'order_by', updateData: newChangeOrder, newLedgerList }, function (result) {
+                if (newChangeOrder === 0) {
+                    $('#bpaixu').text('清单排序:清单编号');
+                    $('#upAndMoveBtn').attr('style', 'display: none !important');
+                    $('.order_text').text('添加');
+                    changeOrder = newChangeOrder;
+                } else if (newChangeOrder === 1) {
+                    $('#bpaixu').text('清单排序:添加顺序');
+                    $('#upAndMoveBtn').show();
+                    $('.order_text').text('插入');
+                    changeOrder = newChangeOrder;
+                }
+                changeList = result;
+                SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+                changeSpreadObj.makeSjsFooter();
+                changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));
+            });
+        }
+    });
 });
 function checkSelectAll() {
     let check = $('#code-list tr').length > 0 ? true : false;
@@ -1072,6 +1532,7 @@ function tableDataRemake(changeListData) {
     $('#table-list-select tr').removeClass('table-warning');
     $('#table-list-select tr').removeClass('table-success');
     $('#table-list-select tr').attr('data-bwmx', '');
+    $('#table-list-select tr').attr('data-charu', '');
     $('#code-list').html('');
     $('#code-list').attr('data-index', '');
     $('#code-input').val('');
@@ -1274,7 +1735,7 @@ function makeCodeTable(search = '') {
     }
 }
 
-function remakeChangeSpread() {
+function remakeChangeSpread(cOrder = changeOrder) {
     const newTableList = [];
     // 获取选中的签约清单判断并插入到原有清单中
     $('#table-list-select .table-success').each(function(){
@@ -1293,7 +1754,7 @@ function remakeChangeSpread() {
         let lid = $(this).data('lid');
         let lindex = $(this).data('index');
         // 原清单和数量改变
-        let data_bwmx = $(this).attr('data-bwmx').split('$#$');
+        let data_bwmx = cOrder ? ($(this).attr('data-charu') ? $(this).attr('data-charu').split('$#$') : []) : $(this).attr('data-bwmx').split('$#$');
 
         for (const b of data_bwmx) {
             const oamount = b.split('*;*')[1] != '' ? b.split('*;*')[1] : 0;

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

@@ -12,10 +12,10 @@ $(document).ready(() => {
         cols: [
             {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 80, formatter: '@'},
             {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 120, formatter: '@'},
-            {title: '变更部位', colSpan: '1', rowSpan: '2', field: 'bwmx', hAlign: 0, width: 120, formatter: '@'},
             {title: '变更详情', colSpan: '1', rowSpan: '2', field: 'detail', hAlign: 0, width: 120, formatter: '@'},
             {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@'},
             {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.unit_price'},
+            {title: '变更部位', colSpan: '1', rowSpan: '2', field: 'bwmx', hAlign: 0, width: 120, formatter: '@'},
             {title: '原设计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'oamount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.oamount'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'oa_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.oa_tp'},
             {title: '申请变更增(+)减(-)|数量', colSpan: '2|1', rowSpan: '1|1', field: 'camount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.camount'},

File diff suppressed because it is too large
+ 415 - 0
app/public/js/colResizable/colResizable-1.6.js


File diff suppressed because it is too large
+ 3 - 0
app/public/js/colResizable/colResizable-1.6.min.js


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

@@ -35,7 +35,7 @@ const gclGatherModel = (function () {
         updateFields: ['contract_qty', 'qc_qty'],
     };
     const gsPos = new StagePosData(posSetting);
-    let deal = [];
+    let deal = [], change;
 
     const gclList = [], leafXmjs = [];
     const mergeChar = ';';
@@ -70,6 +70,10 @@ const gclGatherModel = (function () {
         deal = dealBills;
     }
 
+    function loadChangeBillsData(data) {
+        change = data;
+    }
+
     function gatherfields(obj, src, fields) {
         if (obj && src) {
             for (const f of fields) {
@@ -124,7 +128,7 @@ const gclGatherModel = (function () {
      * @constructor
      */
     function CheckPeg(text) {
-        const pegReg = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+        const pegReg = /[a-zA-Z]*[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
         return pegReg.test(text);
     }
 
@@ -322,6 +326,19 @@ const gclGatherModel = (function () {
         }
     }
 
+    function gatherChangeBillsData() {
+        if (change && change.length > 0) {
+            for (const node of change) {
+                node.b_code = node.code;
+                node.quantity = parseFloat(node.samount);
+                node.total_price = ZhCalc.mul(node.quantity, node.unit_price, node.tp_decimal);
+                const gcl = getGclNode(node);
+                gcl.change_bills_qty = ZhCalc.add(gcl.change_bills_qty, node.quantity);
+                gcl.change_bills_tp = ZhCalc.add(gcl.change_bills_tp, node.total_price);
+            }
+        }
+    }
+
     function calculateGatherData() {
         for (const gcl of gclList) {
             gcl.pre_gather_qty = ZhCalc.add(gcl.pre_contract_qty, gcl.pre_qc_qty);
@@ -330,7 +347,6 @@ 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.end_final_qty = ZhCalc.add(gcl.end_qc_qty, gcl.quantity);
             gcl.gather_tp = ZhCalc.add(gcl.contract_tp, gcl.qc_tp);
             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);
@@ -338,9 +354,14 @@ const gclGatherModel = (function () {
             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);
             gcl.end_final_tp = ZhCalc.add(gcl.end_qc_tp, gcl.total_price);
+            gcl.final_qty = ZhCalc.add(gcl.quantity, gcl.change_bills_qty);
+            gcl.final_tp = ZhCalc.add(gcl.total_price, gcl.change_bills_tp);
             gcl.end_gather_percent = gcl.end_final_qty && gcl.end_gather_qty
                 ? ZhCalc.mul(ZhCalc.div(gcl.end_gather_qty, gcl.end_final_qty), 100, 2)
                 : ZhCalc.mul(ZhCalc.div(gcl.end_gather_tp, gcl.end_final_tp), 100, 2);
+            gcl.final_percent = gcl.final_qty && gcl.end_gather_qty
+                ? ZhCalc.mul(ZhCalc.div(gcl.end_gather_qty, gcl.final_qty), 100, 2)
+                : ZhCalc.mul(ZhCalc.div(gcl.end_gather_tp, gcl.final_tp), 100, 2);
             for (const xmj of gcl.leafXmjs) {
                 xmj.pre_gather_qty = ZhCalc.add(xmj.pre_contract_qty, xmj.pre_qc_qty);
                 xmj.gather_qty = ZhCalc.add(xmj.contract_qty, xmj.qc_qty);
@@ -395,8 +416,9 @@ const gclGatherModel = (function () {
             gclList.length = 0; //splice(0, gclList.length);
         }
         recursiveGatherGclData(gsTree.children, null);
-        calculateGatherData();
         gatherDealBillsData();
+        gatherChangeBillsData();
+        calculateGatherData();
         gclList.sort(function (a, b) {
             return compareCode(a.b_code, b.b_code) || ZhCalc.sub(a.unit_price, b.unit_price);
         });
@@ -530,6 +552,7 @@ const gclGatherModel = (function () {
         loadLedgerData,
         loadPosData,
         loadDealBillsData,
+        loadChangeBillsData,
         gatherGclData,
         checkDiffer,
         gatherChapterData,

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

@@ -257,7 +257,7 @@ const postData = function (url, data, successCallback, errorCallBack, showWaitin
                     });
                 }
                 if (successCallback) {
-                    successCallback(result.data);
+                    successCallback(result.data, result.msg);
                 }
             } else if (result.err === 2) {
                 toastr.error('error: ' + result.msg);

+ 0 - 2
app/public/js/ledger.js

@@ -3521,7 +3521,6 @@ $(document).ready(function() {
         const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
         if (!node) return;
         const files = $('#upload-file')[0].files;
-        // console.log(node);
         const formData = new FormData();
         formData.append('lid', node.id);
         for (const file of files) {
@@ -3792,7 +3791,6 @@ $(document).ready(function() {
         //   toastr.error('批量下载失败')
         // });
           // const url = `/tender/${tender.id}/ledger/compresse/file?fileIds=${JSON.stringify(fileIds)}`;
-          // console.log(url);
       }
   });
 

+ 62 - 14
app/public/js/material.js

@@ -78,7 +78,7 @@ function resetTpTable() {
         $('#tax_rate_set').find('td').eq(1).text(ZhCalc.round(m_tax_tp, materialDecimal.tp));
         $('#tax_rate_set').find('td').eq(2).text(ZhCalc.round(ZhCalc.add(m_tax_pre_tp, m_tax_tp), materialDecimal.tp));
     } else {
-        const rate = $('#changeRate').val();
+        const rate = $('#rateInput').val();
         const bqhs = ZhCalc.round(ZhCalc.mul(m_tp, 1+rate/100), materialDecimal.tp);
         const jzbqhs = ZhCalc.round(ZhCalc.add(pre_tp_hs, bqhs), materialDecimal.tp);
         $('#rate_set').find('td').eq(1).text(bqhs !== 0 ? bqhs : '');
@@ -106,7 +106,7 @@ $(document).ready(() => {
     const materialSpreadSetting = {
         emptyRows: 0,
         headRows: 2,
-        headRowHeight: [25, 25],
+        headRowHeight: [25, 32],
         defaultRowHeight: 21,
         headerFont: '12px 微软雅黑',
         font: '12px 微软雅黑',
@@ -1063,19 +1063,67 @@ $(document).ready(() => {
                 },
             }
         });
-        $('#changeRate').change(function () {
-            const rate = parseInt($(this).val());
-            postData(window.location.pathname + '/save', { type:'rate', rate: rate }, function (result) {
-                const bqhs = ZhCalc.round(ZhCalc.mul(m_tp, 1+rate/100), materialDecimal.tp);
-                const exbqhs = ZhCalc.round(ZhCalc.mul(ex_tp, 1+rate/100), materialDecimal.tp);
-                const jzbqhs = ZhCalc.round(ZhCalc.add(pre_tp_hs, bqhs), materialDecimal.tp);
-                const exjzbqhs = ZhCalc.round(ZhCalc.add(ex_pre_tp_hs, exbqhs), materialDecimal.tp);
-                $('#rate_set').find('td').eq(1).text(bqhs !== 0 ? bqhs : '');
-                $('#rate_set').find('td').eq(2).text(jzbqhs !== 0 ? jzbqhs : '');
-                $('#rate_set').find('td').eq(3).text(exbqhs !== 0 ? exbqhs : '');
-                $('#rate_set').find('td').eq(4).text(exjzbqhs !== 0 ? exjzbqhs : '');
-            });
+        $('.changeRate').click(function () {
+            $('#rateInput').val(parseInt($(this).data('value')));
+            $('#rateInput').siblings('.dropdown-menu').hide();
+        });
+        $('#rateInput').click(function () {
+            $(this).siblings('.dropdown-menu').show();
+        })
+        // 回车提交
+        $('#rateInput').on('keypress', function () {
+            if(window.event.keyCode === 13) {
+                $(this).blur();
+            }
+        });
+        $('#rateInput').blur(function () {
+            const _self = $(this);
+            setTimeout(function () {
+                let rate = parseFloat(_self.val());
+                if (_.isNaN(rate)) {
+                    toastr.error('请输入0-100之前的整数值');
+                    $('#rateInput').val(materialRate);
+                    return;
+                }
+                rate = _.round(rate);
+                if(rate < 0 || rate > 100) {
+                    toastr.error('请输入0-100之前的整数值');
+                    $('#rateInput').val(materialRate);
+                    return;
+                }
+                $('#rateInput').siblings('.dropdown-menu').hide();
+                console.log(rate, materialRate);
+                if (rate !== materialRate) {
+                    postData(window.location.pathname + '/save', { type:'rate', rate: rate }, function (result) {
+                        const bqhs = ZhCalc.round(ZhCalc.mul(m_tp, 1+rate/100), materialDecimal.tp);
+                        // const exbqhs = ZhCalc.round(ZhCalc.mul(ex_tp, 1+rate/100), materialDecimal.tp);
+                        const jzbqhs = ZhCalc.round(ZhCalc.add(pre_tp_hs, bqhs), materialDecimal.tp);
+                        // const exjzbqhs = ZhCalc.round(ZhCalc.add(ex_pre_tp_hs, exbqhs), materialDecimal.tp);
+                        $('#rate_set').find('td').eq(1).text(bqhs !== 0 ? bqhs : '');
+                        $('#rate_set').find('td').eq(2).text(jzbqhs !== 0 ? jzbqhs : '');
+                        // $('#rate_set').find('td').eq(3).text(exbqhs !== 0 ? exbqhs : '');
+                        // $('#rate_set').find('td').eq(4).text(exjzbqhs !== 0 ? exjzbqhs : '');
+                        materialRate = rate;
+                        $('#rateInput').val(rate);
+                    });
+                } else {
+                    $('#rateInput').val(rate);
+                }
+            }, 500);
         });
+        // $('#changeRate').change(function () {
+        //     const rate = parseInt($(this).val());
+        //     postData(window.location.pathname + '/save', { type:'rate', rate: rate }, function (result) {
+        //         const bqhs = ZhCalc.round(ZhCalc.mul(m_tp, 1+rate/100), materialDecimal.tp);
+        //         const exbqhs = ZhCalc.round(ZhCalc.mul(ex_tp, 1+rate/100), materialDecimal.tp);
+        //         const jzbqhs = ZhCalc.round(ZhCalc.add(pre_tp_hs, bqhs), materialDecimal.tp);
+        //         const exjzbqhs = ZhCalc.round(ZhCalc.add(ex_pre_tp_hs, exbqhs), materialDecimal.tp);
+        //         $('#rate_set').find('td').eq(1).text(bqhs !== 0 ? bqhs : '');
+        //         $('#rate_set').find('td').eq(2).text(jzbqhs !== 0 ? jzbqhs : '');
+        //         $('#rate_set').find('td').eq(3).text(exbqhs !== 0 ? exbqhs : '');
+        //         $('#rate_set').find('td').eq(4).text(exjzbqhs !== 0 ? exjzbqhs : '');
+        //     });
+        // });
 
         $('#expr_select button').on('click', function () {
             const code = $(this).text();

+ 59 - 0
app/public/js/material_audit.js

@@ -180,6 +180,39 @@ $(document).ready(function () {
     $('#sp-list').on('hidden.bs.modal', function (e) {
         $(document.body).addClass('modal-open');
     });
+
+    // 重新审批获取手机验证码
+    // 获取验证码
+    let isPosting = false;
+    $("#get-code").click(function() {
+        if (isPosting) {
+            return false;
+        }
+        const btn = $(this);
+
+        $.ajax({
+            url: '/profile/code?_csrf_j=' + csrf,
+            type: 'post',
+            data: { mobile: authMobile, type: 'shenpi' },
+            dataTye: 'json',
+            error: function() {
+                isPosting = false;
+            },
+            beforeSend: function() {
+                isPosting = true;
+            },
+            success: function(response) {
+                isPosting = false;
+                if (response.err === 0) {
+                    codeSuccess(btn);
+                    $("input[name='code']").removeAttr('readonly');
+                    $("#re-shenpi-btn").removeAttr('disabled');
+                } else {
+                    toastr.error(response.msg);
+                }
+            }
+        });
+    });
 });
 // 检查上报情况
 function checkAuditorFrom () {
@@ -210,3 +243,29 @@ function auditCheck(i) {
     }
     return true;
 }
+
+/**
+ * 获取成功后的操作
+ *
+ * @param {Object} btn - 点击的按钮
+ * @return {void}
+ */
+function codeSuccess(btn) {
+    let counter = 60;
+    btn.addClass('disabled').text('重新获取 ' + counter + 'S');
+    btn.parent().siblings('input').removeAttr('readonly').attr('placeholder', '输入短信中的6位验证码');
+    const bindBtn = $("#bind-btn");
+    bindBtn.removeClass('btn-secondary disabled').addClass('btn-primary');
+
+    const countDown = setInterval(function() {
+        const countString = counter - 1 <= 0 ? '' : ' ' + (counter - 1) + 'S';
+        // 倒数结束后
+        if (countString === '') {
+            clearInterval(countDown);
+            btn.removeClass('disabled');
+        }
+        const text = '重新获取' + countString;
+        btn.text(text);
+        counter -= 1;
+    }, 1000);
+}

+ 13 - 7
app/public/js/material_checklist.js

@@ -140,7 +140,7 @@ $(document).ready(() => {
         gclGatherData = gclGatherModel.gatherGclData();
         console.log(gclGatherData);
         const hadBillsidList = _.uniq(_.map(gclList, 'gcl_id'));
-        console.log(hadBillsidList);
+        console.log(hadBillsidList, materialChecklistData);
         // 对比清单设置和调差清单,还要和台账对比,显示已选清单列表 不同则更新到清单设置页中
         const pushData = [];
         const updateData = [];
@@ -160,20 +160,24 @@ $(document).ready(() => {
         const removeData = [];
         for (const mc of materialChecklistData) {
             const gcl = _.find(gclGatherData, { b_code: mc.b_code, name: mc.name, unit: mc.unit, unit_price: mc.unit_price });
+            console.log(gcl);
             // 判断是否已不存在工料清单,台账修改过后删除之
             if (!gcl) {
                 removeData.push(mc.id);
             } else {
+                const gcl_ids = gcl.leafXmjs ? _.uniq(_.map(gcl.leafXmjs, 'gcl_id')) : [];
+                const jiaoji = _.intersection(gcl_ids, hadBillsidList);
+                // const leafXmjs = gcl.leafXmjs ? gcl.leafXmjs.filter(item => item.gather_qty) : [];
                 // 更新had_bills值
                 const updateObj = { id: mc.id };
                 if (mc.had_bills === 1) {
-                    if (_.indexOf(hadBillsidList, gcl.leafXmjs ? gcl.leafXmjs[0].gcl_id : null) === -1) {
+                    if (jiaoji.length === 0) {
                         updateObj.mid = materialID;
                         updateObj.had_bills = 0;
                         // updateData.push({ id: mc.id, mid: materialID, had_bills: 0 });
                     }
                 } else if (mc.had_bills === 0) {
-                    if (_.indexOf(hadBillsidList, gcl.leafXmjs ? gcl.leafXmjs[0].gcl_id: null) !== -1) {
+                    if (jiaoji.length !== 0) {
                         updateObj.had_bills = 1;
                     }
                 }
@@ -246,7 +250,8 @@ $(document).ready(() => {
     const materialCol = {
         readOnly: {
             isEdit: function (data) {
-                return !(!readOnly && materialBase.isEdit(data));
+                // return !(!readOnly && materialBase.isEdit(data));
+                return readOnly;
             },
         },
     };
@@ -259,10 +264,11 @@ $(document).ready(() => {
         //     return item.qc_qty || item.contract_qty
         // }) : null;
         if (gcl && gcl.leafXmjs) {
-            const xmj2 = gcl.leafXmjs[iLXmjRow];
+            const gcl_ids = gcl.leafXmjs ? _.uniq(_.map(gcl.leafXmjs, 'gcl_id')) : [];
+            // const xmj2 = gcl.leafXmjs[iLXmjRow];
             materialList = [];
             const newMaterialList = _.uniqBy(_.filter(gclList, function (m) {
-                return xmj2 && m.gcl_id === xmj2.gcl_id;
+                return _.indexOf(gcl_ids, m.gcl_id) !== -1;
             }), 'mb_id');
             for(const m of newMaterialList) {
                 const bills = _.find(materialBillsData, { id: m.mb_id });
@@ -282,7 +288,7 @@ $(document).ready(() => {
             //     return xmj && m.gcl_id === xmj.gcl_id && m.xmj_id === xmj.id && ((xmj.mx_id !==undefined && m.mx_id === xmj.mx_id) || xmj.mx_id === undefined);
             // });
             // 对清单调差工料table的单位数量进行改变
-            materialSpreadSetting.cols[materialSpreadSetting.cols.length - 2].title = '|' + gcl.unit + '数量 �';
+            materialSpreadSetting.cols[materialSpreadSetting.cols.length - 2].title = '|' + '每' + gcl.unit + '数量 �';
             SpreadJsObj.initSheet(materialSpread.getActiveSheet(), materialSpreadSetting);
             SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialList);
         } else {

+ 64 - 15
app/public/js/material_exponent.js

@@ -13,7 +13,7 @@ function getPasteHint (str, row = '') {
     return returnObj;
 }
 function resetExTpTable() {
-    const rate = $('#changeRate').val();
+    const rate = $('#rateInput').val();
     const bqhs = ZhCalc.round(ZhCalc.mul(ex_tp, 1+rate/100), materialDecimal.tp);
     const jzbqhs = ZhCalc.round(ZhCalc.add(ex_pre_tp_hs, bqhs), materialDecimal.tp);
     $('#tp_set').find('td').eq(3).text(ZhCalc.round(ex_tp, materialDecimal.tp));
@@ -523,22 +523,71 @@ $(document).ready(() => {
             });
         });
 
-
-        $('#changeRate').change(function () {
-            const rate = parseInt($(this).val());
-            postData(window.location.pathname + '/save', { type:'rate', rate: rate }, function (result) {
-                const exbqhs = ZhCalc.round(ZhCalc.mul(ex_tp, 1+rate/100), materialDecimal.tp);
-                const exjzbqhs = ZhCalc.round(ZhCalc.add(ex_pre_tp_hs, exbqhs), materialDecimal.tp);
-                if (!materialTax) {
-                    const bqhs = ZhCalc.round(ZhCalc.mul(m_tp, 1+rate/100), materialDecimal.tp);
-                    const jzbqhs = ZhCalc.round(ZhCalc.add(pre_tp_hs, bqhs), materialDecimal.tp);
-                    $('#rate_set').find('td').eq(1).text(bqhs !== 0 ? bqhs : '');
-                    $('#rate_set').find('td').eq(2).text(jzbqhs !== 0 ? jzbqhs : '');
+        $('.changeRate').click(function () {
+            $('#rateInput').val(parseInt($(this).data('value')));
+            $('#rateInput').siblings('.dropdown-menu').hide();
+        });
+        $('#rateInput').click(function () {
+            $(this).siblings('.dropdown-menu').show();
+        })
+        // 回车提交
+        $('#rateInput').on('keypress', function () {
+            if(window.event.keyCode === 13) {
+                $(this).blur();
+            }
+        });
+        $('#rateInput').blur(function () {
+            const _self = $(this);
+            setTimeout(function () {
+                let rate = parseFloat(_self.val());
+                if (_.isNaN(rate)) {
+                    toastr.error('请输入0-100之前的整数值');
+                    $('#rateInput').val(materialRate);
+                    return;
                 }
-                $('#rate_set').find('td').eq(3).text(exbqhs !== 0 ? exbqhs : '');
-                $('#rate_set').find('td').eq(4).text(exjzbqhs !== 0 ? exjzbqhs : '');
-            });
+                rate = _.round(rate);
+                if(rate < 0 || rate > 100) {
+                    toastr.error('请输入0-100之前的整数值');
+                    $('#rateInput').val(materialRate);
+                    return;
+                }
+                $('#rateInput').siblings('.dropdown-menu').hide();
+                console.log(rate, materialRate);
+                if (rate !== materialRate) {
+                    postData(window.location.pathname + '/save', { type:'rate', rate: rate }, function (result) {
+                        const exbqhs = ZhCalc.round(ZhCalc.mul(ex_tp, 1+rate/100), materialDecimal.tp);
+                        const exjzbqhs = ZhCalc.round(ZhCalc.add(ex_pre_tp_hs, exbqhs), materialDecimal.tp);
+                        // if (!materialTax) {
+                        //     const bqhs = ZhCalc.round(ZhCalc.mul(m_tp, 1+rate/100), materialDecimal.tp);
+                        //     const jzbqhs = ZhCalc.round(ZhCalc.add(pre_tp_hs, bqhs), materialDecimal.tp);
+                        //     $('#rate_set').find('td').eq(1).text(bqhs !== 0 ? bqhs : '');
+                        //     $('#rate_set').find('td').eq(2).text(jzbqhs !== 0 ? jzbqhs : '');
+                        // }
+                        $('#rate_set').find('td').eq(3).text(exbqhs !== 0 ? exbqhs : '');
+                        $('#rate_set').find('td').eq(4).text(exjzbqhs !== 0 ? exjzbqhs : '');
+                        materialRate = rate;
+                        $('#rateInput').val(rate);
+                    });
+                } else {
+                    $('#rateInput').val(rate);
+                }
+            }, 500);
         });
+        // $('#changeRate').change(function () {
+        //     const rate = parseInt($(this).val());
+        //     postData(window.location.pathname + '/save', { type:'rate', rate: rate }, function (result) {
+        //         const exbqhs = ZhCalc.round(ZhCalc.mul(ex_tp, 1+rate/100), materialDecimal.tp);
+        //         const exjzbqhs = ZhCalc.round(ZhCalc.add(ex_pre_tp_hs, exbqhs), materialDecimal.tp);
+        //         if (!materialTax) {
+        //             const bqhs = ZhCalc.round(ZhCalc.mul(m_tp, 1+rate/100), materialDecimal.tp);
+        //             const jzbqhs = ZhCalc.round(ZhCalc.add(pre_tp_hs, bqhs), materialDecimal.tp);
+        //             $('#rate_set').find('td').eq(1).text(bqhs !== 0 ? bqhs : '');
+        //             $('#rate_set').find('td').eq(2).text(jzbqhs !== 0 ? jzbqhs : '');
+        //         }
+        //         $('#rate_set').find('td').eq(3).text(exbqhs !== 0 ? exbqhs : '');
+        //         $('#rate_set').find('td').eq(4).text(exjzbqhs !== 0 ? exjzbqhs : '');
+        //     });
+        // });
     }
 
     $.divResizer({

File diff suppressed because it is too large
+ 631 - 102
app/public/js/material_list.js


+ 58 - 15
app/public/js/measure_material.js

@@ -275,6 +275,8 @@ $(function () {
             const pos = result.pos;
             const curPosData = result.curPosData;
             const gclList = result.gclList;
+            const selfList = result.selfList;
+            const materialListForSelf = result.materialListForSelf;
             gclGatherModel.loadLedgerData(ledger, curLedgerData);
             gclGatherModel.loadPosData(pos, curPosData);
             const gclGatherData = gclGatherModel.gatherGclData();
@@ -284,6 +286,7 @@ $(function () {
             // 获取需要新增的工料关联清单
             const insertGcl = [];
             const removeGclList = [];
+            const insertSelfList = [];// 需要单独添加的明细清单工料含量列表
             for (const g of gclList) {
                 const gcl = _.find(gclGatherData, function (item) {
                     return item.leafXmjs && item.leafXmjs.length > 0 && _.findIndex(item.leafXmjs, { gcl_id : g.gcl_id }) !== -1;
@@ -307,39 +310,79 @@ $(function () {
             }
             const insertList = [];
             const insertGclList = [];
+            const hadQtySelfList = [];
             for (const one of insertGcl) {
                 if (one.leafXmjs && one.leafXmjs.length > 0) {
                     for (const xmj of one.leafXmjs) {
-                        const newgcl = _.find(gclList, { gcl_id: xmj.gcl_id });
-                        for (const bill of one.bills) {
-                            insertList.push({
-                                gcl_id: xmj.gcl_id,
-                                mx_id: xmj.mx_id ? xmj.mx_id : null,
-                                xmj_id: xmj.id ? xmj.id : null,
-                                gather_qty: xmj.gather_qty,
-                                quantity: bill.quantity,
-                                expr: bill.expr,
-                                mb_id: bill.mb_id,
-                                order: bill.order,
-                            });
-                            if (!newgcl) {
-                                insertGclList.push({
+                        const is_self = _.findIndex(selfList, { gcl_id: xmj.gcl_id, xmj_id: xmj.id, mx_id: xmj.mx_id ? xmj.mx_id : null }) !== -1;// 区分单独计量的明细工料含量
+                        if (is_self) {
+                            const billsList = _.filter(materialListForSelf, { gcl_id: xmj.gcl_id, xmj_id: xmj.id, mx_id: xmj.mx_id ? xmj.mx_id : null });
+                            console.log(billsList);
+                            for (const bill of billsList) {
+                                hadQtySelfList.push(bill);
+                                insertSelfList.push({
                                     gcl_id: xmj.gcl_id,
+                                    mx_id: xmj.mx_id ? xmj.mx_id : '',
+                                    xmj_id: xmj.id ? xmj.id : null,
+                                    gather_qty: xmj.gather_qty,
                                     quantity: bill.quantity,
                                     expr: bill.expr,
                                     mb_id: bill.mb_id,
                                     order: bill.order,
                                 });
                             }
+                        } else {
+                            const newgcl = _.find(gclList, { gcl_id: xmj.gcl_id });
+                            for (const bill of one.bills) {
+                                insertList.push({
+                                    gcl_id: xmj.gcl_id,
+                                    mx_id: xmj.mx_id ? xmj.mx_id : '',
+                                    xmj_id: xmj.id ? xmj.id : null,
+                                    gather_qty: xmj.gather_qty,
+                                    quantity: bill.quantity,
+                                    expr: bill.expr,
+                                    mb_id: bill.mb_id,
+                                    order: bill.order,
+                                });
+                                if (!newgcl) {
+                                    insertGclList.push({
+                                        gcl_id: xmj.gcl_id,
+                                        quantity: bill.quantity,
+                                        expr: bill.expr,
+                                        old_quantity: bill.quantity,
+                                        old_expr: bill.expr,
+                                        mb_id: bill.mb_id,
+                                        order: bill.order,
+                                    });
+                                }
+                            }
                         }
                     }
                 }
             }
+            // 通过比较materialListForSelf和hadQtySelfList不重合部分,得出本期为null的gather_qty列表,插入到insertSelfList中
+            const pushSelfList = _.xorWith(materialListForSelf, hadQtySelfList, _.isEqual);
+            if (pushSelfList.length > 0) {
+                for (const ps of pushSelfList) {
+                    insertSelfList.push({
+                        gcl_id: ps.gcl_id,
+                        mx_id: ps.mx_id,
+                        xmj_id: ps.xmj_id,
+                        gather_qty: null,
+                        quantity: ps.quantity,
+                        expr: ps.expr,
+                        mb_id: ps.mb_id,
+                        order: ps.order,
+                    });
+                }
+            }
             // 可能需要新增list_gcl表
             newMaterialData.material_list = insertList;
+            newMaterialData.material_self_list = insertSelfList;
             newMaterialData.insertGclList = insertGclList;
             newMaterialData.removeGclList = removeGclList;
             console.log(newMaterialData);
+            console.log(insertSelfList);
             postData(preUrl + '/measure/material/add', newMaterialData, function (result) {
                 window.location.href = preUrl + '/measure/material/' + result.order;
             }, function () {
@@ -351,7 +394,7 @@ $(function () {
                 // _self.parents('div[id="add-qi"]').modal('show');
                 _self.attr('disabled', false).text('确认添加');
             });
-            // return;
+            return;
         });
         // $(this).parents('form').submit();
     });

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

@@ -185,8 +185,8 @@ function checkValidForm() {
     const date = $('#add-qi input[name="date"]').val();
     const period = $('#add-qi input[name="period"]').val();
     const startDate = period.split('~')[0];
-    const endDate = period.split('~')[1];
-    if (startDate.indexOf(date) === -1 && endDate.indexOf(date) === -1) {
+    const endDate = period.split('~')[1] ? period.split('~')[1] : null;
+    if ((startDate.indexOf(date) === -1 && !endDate) || (startDate.indexOf(date) === -1 && endDate && endDate.indexOf(date) === -1)) {
         toastr.error('所选日期与当前月份不匹配,请重新选择');
         $('#add-qi input[name="period"]').parents('.form-group').find('.text-danger').remove();
         $('#add-qi input[name="period"]').parents('.form-group').append('<small class="text-danger">所选日期与当前月份不匹配,请重新选择</small>');

+ 27 - 10
app/public/js/setting.js

@@ -79,6 +79,11 @@ $(document).ready(() => {
             if (!/^[0-9a-zA-Z*~!@&%$^\\(\\)#_\[\]\-\+={}|?'":,<>.`]+$/.test(resetPassword)) {
                 throw '密码只支持英文数字及符号';
             }
+            // 判断新密码的强度
+            const reg = /^(?![0-9]+$)(?![a-zA-Z]+$).{6,16}$/;
+            if (!reg.test(resetPassword)) {
+                throw '请设置至少包含数字和字母的密码';
+            }
             const btn = $(this);
             $.ajax({
                 url: '/setting/user/reset/password',
@@ -425,6 +430,11 @@ function checkPasswordForm() {
         if (!/^[0-9a-zA-Z*~!@&%$^\\(\\)#_\[\]\-\+={}|?'":,<>.`]+$/.test(resetPassword)) {
             throw '密码只支持英文数字及符号';
         }
+        // 判断新密码的强度
+        const reg = /^(?![0-9]+$)(?![a-zA-Z]+$).{6,16}$/;
+        if (!reg.test(resetPassword)) {
+            throw '请设置至少包含数字和字母的密码';
+        }
     } catch (err) {
         toastr.error(err);
         return false;
@@ -449,6 +459,11 @@ function checkUserForm(status) {
             if (!/^[0-9a-zA-Z*~!@&%$^\\(\\)#_\[\]\-\+={}|?'":,<>.`]+$/.test($('#add-user input[name="password"]').val())) {
                 throw '密码只支持英文数字及符号';
             }
+            // 判断新密码的强度
+            const reg = /^(?![0-9]+$)(?![a-zA-Z]+$).{6,16}$/;
+            if (!reg.test($('#add-user input[name="password"]').val())) {
+                throw '请设置至少包含数字和字母的密码';
+            }
             if ($('#add-user input[name="name"]').val() == '') {
                 throw '姓名不能为空';
             }
@@ -460,11 +475,11 @@ function checkUserForm(status) {
             }
             $('#add-user input[name="account"]').val(trimInvalidChar($('#add-user input[name="account"]').val()));
             $('#add-user input[name="name"]').val(trimInvalidChar($('#add-user input[name="name"]').val()));
-            $('#add-user input[name="company"]').val(trimInvalidChar($('#add-user input[name="company"]').val()));
+            // $('#add-user input[name="company"]').val(trimInvalidChar($('#add-user input[name="company"]').val()));
             $('#add-user input[name="role"]').val(trimInvalidChar($('#add-user input[name="role"]').val()));
             $('#add-user input[name="telephone"]').val(trimInvalidChar($('#add-user input[name="telephone"]').val()));
         } else {
-            if ($('#edit-user select[name="account_group"]').val() == 0) {
+            if ($('#edit-user input[name="account_group"]').val() == 0) {
                 throw '请选择账号组';
             }
             if ($('#edit-user input[name="account"]').val() == '' || $('#add-user input[name="account"]').hasClass('is-invalid')) {
@@ -481,7 +496,7 @@ function checkUserForm(status) {
             }
             $('#edit-user input[name="account"]').val(trimInvalidChar($('#edit-user input[name="account"]').val()));
             $('#edit-user input[name="name"]').val(trimInvalidChar($('#edit-user input[name="name"]').val()));
-            $('#edit-user input[name="company"]').val(trimInvalidChar($('#edit-user input[name="company"]').val()));
+            // $('#edit-user input[name="company"]').val(trimInvalidChar($('#edit-user input[name="company"]').val()));
             $('#edit-user input[name="role"]').val(trimInvalidChar($('#edit-user input[name="role"]').val()));
             $('#edit-user input[name="telephone"]').val(trimInvalidChar($('#edit-user input[name="telephone"]').val()));
         }
@@ -513,23 +528,25 @@ function checkUnitForm() {
 }
 
 /**
- * 随机密码
+ * 随机密码(必须包含数字和字母)
  */
 function randPassword() {
-    let result = '';
+    const result = [];
     // 随机6-10位
-    const length = Math.ceil(Math.random() * 2 + 8);
+    const length = Math.ceil(Math.random() * 2 + 6);
     let numberSeed = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
     let stringSeed = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
         'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
         'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
-
+    const numRan = numberSeed[Math.floor((Math.random() * numberSeed.length))];
+    const strRan = stringSeed[Math.floor((Math.random() * stringSeed.length))];
     const randSeed = stringSeed.concat(numberSeed);
     const seedLength = randSeed.length - 1;
     for (let i = 0; i < length; i++) {
         const index = Math.ceil(Math.random() * seedLength);
-        result += randSeed[index];
+        result.push(randSeed[index]);
     }
-
-    return result;
+    result.splice(Math.floor((Math.random() * result.length)), 0, numRan);
+    result.splice(Math.floor((Math.random() * result.length)), 0, strRan);
+    return result.join('');
 }

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

@@ -67,7 +67,7 @@ const gclCompareModel = (function () {
      * @constructor
      */
     function CheckPeg(text) {
-        const pegReg = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+        const pegReg = /[a-zA-Z]*[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
         return pegReg.test(text);
     }
 

+ 55 - 9
app/public/js/stage.js

@@ -36,7 +36,7 @@ function customColDisplay () {
         { title: '本期完成计量', fields: ['gather_qty', 'gather_tp'], visible: true },
         { title: '截止本期计量合同', fields: ['end_contract_qty', 'end_contract_tp'], visible: true },
         { title: '截止本期数量变更', fields: ['end_qc_qty', 'end_qc_tp', 'end_qc_bgl'], visible: true },
-        { title: '截止本期完成计量', fields: ['end_gather_qty', 'end_gather_tp', 'end_gather_percent'], visible: true },
+        { title: '截止本期完成计量', fields: ['end_gather_qty', 'end_gather_tp', 'end_gather_percent', 'end_gather_1_percent'], visible: true },
         { title: '本期批注', fields: ['postil'], visible: true },
         { title: '图册号', fields: ['drawing_code'], visible: true },
         { title: '备注', fields: ['memo'], visible: true },
@@ -267,9 +267,9 @@ $(document).ready(() => {
         markExpandSubKey: window.location.pathname.split('/')[2],
     };
     // 台账树结构计算相关设置
-    stageTreeSetting.updateFields = ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'postil', 'used', 'contract_expr'];
+    stageTreeSetting.updateFields = ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'postil', 'used', 'contract_expr', 'minus_qc_qty'];
     stageTreeSetting.calcFields = ['deal_tp', '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'];
+        'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp', 'end_correct_tp', 'end_1_tp'];
     stageTreeSetting.calcFun = function (node) {
         if (!node.children || node.children.length === 0) {
             node.pre_gather_qty = ZhCalc.add(node.pre_contract_qty, node.pre_qc_qty);
@@ -277,6 +277,9 @@ $(document).ready(() => {
             node.end_contract_qty = ZhCalc.add(node.pre_contract_qty, node.contract_qty);
             node.end_qc_qty = ZhCalc.add(node.pre_qc_qty, node.qc_qty);
             node.end_gather_qty = ZhCalc.add(node.pre_gather_qty, node.gather_qty);
+            node.end_minus_qc_qty = ZhCalc.add(node.pre_minus_qc_qty, node.minus_qc_qty);
+            node.end_1_qty = ZhCalc.add(node.end_minus_qc_qty, node.quantity); // 1#台账 台账+负变更
+            node.end_1_tp = ZhCalc.mul(node.unit_price, node.end_1_qty, tenderInfo.decimal.tp);
         }
         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);
@@ -284,6 +287,7 @@ $(document).ready(() => {
         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);
         node.end_final_tp = ZhCalc.add(node.end_qc_tp, node.total_price);
+        node.end_final_1_tp = ZhCalc.add(node.end_qc_tp, node.end_1_tp);
         if (!node.children || node.children.length === 0) {
             if (node.end_contract_qty) {
                 node.end_correct_tp = ZhCalc.add(node.end_qc_tp, ZhCalc.mul(node.end_contract_qty, node.unit_price, tenderInfo.decimal.tp));
@@ -293,6 +297,8 @@ $(document).ready(() => {
         }
         node.end_gather_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_tp), 100, 2);
         node.end_correct_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_tp), 100, 2);
+        node.end_gather_1_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_1_tp), 100, 2);
+        node.end_correct_1_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_1_tp), 100, 2);
         node.final_dgn_price = ZhCalc.round(ZhCalc.div(node.end_gather_tp, ZhCalc.add(node.deal_dgn_qty1, node.c_dgn_qty1)), tenderInfo.decimal.up);
     };
     const stageTree = createNewPathTree('stage', stageTreeSetting);
@@ -502,7 +508,7 @@ $(document).ready(() => {
                             return;
                         }
 
-                        data.change.push({ cid: c.cid, cbid: c.cbid, qty: c.uamount });
+                        data.change.push({ cid: c.cid, cbid: c.cbid, qty: c.uamount, minus: c.bamount < 0 });
                     }
                 }
                 // 提交数据到后端
@@ -525,14 +531,14 @@ $(document).ready(() => {
         _calculateAmount() {
             for (const c of this.changes) {
                 c.bamount = _.toNumber(c.b_amount);
-                c.vamount = ZhCalc.sub(c.bamount, c.used_amount);
+                c.vamount = ZhCalc.sub(ZhCalc.sub(c.bamount, c.used_amount), c.stage_used_amount);
                 const uc = _.find(this.useChanges, {cid: c.cid, cbid: c.cbid});
                 if (uc) {
                     c.org_uamount = uc.qty;
                     c.uamount = uc.qty;
-                    c.vamount = ZhCalc.add(c.vamount, c.uamount);
+                    c.vamount = ZhCalc.add(c.vamount, uc.qty);
                 }
-                c.pre_amount = ZhCalc.sub(c.used_amount, c.uamount);
+                c.pre_amount = ZhCalc.sub(ZhCalc.add(c.used_amount, c.stage_used_amount), c.uamount);
             }
         }
         _loadChangeDetail(change) {
@@ -651,7 +657,9 @@ $(document).ready(() => {
         }
     };
     const ratioCol = ledgerSpreadSetting.cols.find(x => {return x.field === 'end_gather_percent' || x.field === 'end_correct_percent'});
-    ratioCol.field = tenderInfo.display.stage.correct ? 'end_correct_percent' : 'end_gather_percent';
+    if (ratioCol) ratioCol.field = tenderInfo.display.stage.correct ? 'end_correct_percent' : 'end_gather_percent';
+    const ratioCol1 = ledgerSpreadSetting.cols.find(x => {return x.field === 'end_gather_1_percent' || x.field === 'end_correct_1_percent'});
+    if (ratioCol) ratioCol1.field = tenderInfo.display.stage.correct ? 'end_correct_1_percent' : 'end_gather_1_percent';
     ledgerSpreadSetting.imageClick = function (data, hitinfo) {
         const col = hitinfo.sheet.zh_setting.cols[hitinfo.col];
         switch (col.field) {
@@ -2033,9 +2041,26 @@ $(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;cooperation;minus_change' }, function (result) {
         // 加载树结构
         stageTree.loadDatas(result.ledgerData);
+        let mcIndex = {};
+        if (result.minus_change && result.minus_change.length > 0) {
+            for (const mc of result.minus_change) {
+                if (!mcIndex[mc.lid]) mcIndex[mc.lid] = stageTree.nodes.find(x => { return x.id === mc.lid; });
+                if (!mcIndex[mc.lid]) continue;
+                mcIndex[mc.lid].pre_minus_qc_qty = ZhCalc.add(mcIndex[mc.lid].pre_minus_qc_qty, mc.qty);
+            }
+        }
+        if (result.changeData && result.changeData.length > 0) {
+            for (const mc of result.changeData) {
+                if (!mc.minus || !mc.qty) continue;
+                if (!mcIndex[mc.lid]) mcIndex[mc.lid] = stageTree.nodes.find(x => { return x.id === mc.lid; });
+                if (!mcIndex[mc.lid]) continue;
+                mcIndex[mc.lid].minus_qc_qty = ZhCalc.add(mcIndex[mc.lid].minus_qc_qty, mc.qty);
+            }
+        }
+        mcIndex = null;
         checkShowLast(result.ledgerData.length);
         treeCalc.calculateAll(stageTree);
         // 加载解锁相关
@@ -3875,6 +3900,7 @@ $(document).ready(() => {
                             {title: '单价', field: 'unit_price', hAlign: 2, width: 50},
                             {title: '数量', field: 'quantity', hAlign: 2, width: 50},
                             {title: '完成率(%)', field: 'end_gather_percent', hAlign: 2, width: 70},
+                            {title: '本期负变更', field: 'minus_qc_qty', hAlign: 2, width: 50},
                         ],
                         emptyRows: 0,
                         headRows: 1,
@@ -3913,6 +3939,11 @@ $(document).ready(() => {
                                 }
                             }
                         }, {
+                            key: 'minus_qc_qty', title: '负变更', valid: true,
+                            check: function (node) {
+                                return node.end_minus_qc_qty;
+                            }
+                        }, {
                             key: 'estimate', title: '预计变更', valid: true,
                             check: function (node) {
                                 const posRange = stagePos.ledgerPos[itemsPre + node.id] || [];
@@ -4714,6 +4745,21 @@ $(document).ready(() => {
         sessionStorage.rpt_name = rpt_name;
         window.open('/individualReport/A4');
     });
+
+    $('#importExcel').click(() => {
+        importExcel.doImport({
+            template: {
+                hint: '计量台账',
+                url: 'https://jl-assets.oss-cn-shenzhen.aliyuncs.com/template/导入计量台账Excel格式.xlsx',
+            },
+            callback: function (sheet) {
+                postDataCompress(window.location.pathname + '/importStageSheet', {sheet}, function (result) {
+                    // todo 刷新界面
+                    console.log(result);
+                }, null);
+            }
+        });
+    })
 });
 function makeOneShouFang(sf) {
     const lData = _.find(ledgerData, { id: sf.lid });

+ 3 - 0
app/public/js/stage_bwtz.js

@@ -90,6 +90,9 @@ $(document).ready(() => {
     xmjSpread.bind(spreadNS.Events.SelectionChanged, function (e, info) {
         unitTreeObj.loadCurUnitData();
     });
+    unitSpread.bind(spreadNS.Events.SelectionChanged, function (e, info) {
+        console.log(SpreadJsObj.getSelectObject(info.sheet));
+    });
     const loadData = function (dataType) {
         postData(window.location.pathname + '/load', {filter: dataType}, function (result) {
             const setting = {

+ 4 - 1
app/public/js/stage_gather.js

@@ -54,6 +54,8 @@ $(document).ready(function () {
     // 初始化工程量清单
     const gclSpread = SpreadJsObj.createNewSpread($('#gcl-spread')[0]);
     const gclSheet = gclSpread.getActiveSheet();
+    gclSheet.frozenColumnCount(8);
+    gclSheet.options.frozenlineColor = '#93b5e4';
     gclSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
         if (!data) return defaultColor;
         return data.overRange && hintOver ? '#f8d7da' : data.differ ? '#FFE699' : defaultColor;
@@ -122,11 +124,12 @@ $(document).ready(function () {
         SpreadJsObj.reLoadSheetData(gclSpread.getActiveSheet());
     });
 
-    postData(preUrl + '/load', { filter: 'ledger;pos;dealBills;spec' }, function (result) {
+    postData(preUrl + '/load', { filter: 'ledger;pos;dealBills;spec;changeBills' }, function (result) {
         // 解析清单汇总数据
         gclGatherModel.loadLedgerData(result.ledgerData);
         gclGatherModel.loadPosData(result.posData);
         gclGatherModel.loadDealBillsData(result.dealBills);
+        gclGatherModel.loadChangeBillsData(result.changeBills);
         gclGatherData = gclGatherModel.gatherGclData();
         gclGatherModel.checkDiffer(gclGatherData);
         checkOverRange(gclGatherData);

+ 11 - 25
app/public/js/stage_im.js

@@ -159,22 +159,22 @@ const stageIm = (function () {
     }
 
     function CheckPeg(text) {
-        const pegReg = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+        const pegReg = /[a-zA-Z]*[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
         return pegReg.test(text);
     }
 
     function getPegStr(text) {
-        const pegReg1 = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?[~~—][a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+        const pegReg1 = /[a-zA-Z]*[kK][0-9]+[++][0-9]{3}([.][0-9]+)?[~~—][a-zA-Z]*[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
         const result1 = text.match(pegReg1);
         if (result1) {
             return result1[0];
         } else {
-            const pegReg2 = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?-[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+            const pegReg2 = /[a-zA-Z]*[kK][0-9]+[++][0-9]{3}([.][0-9]+)?-[a-zA-Z]*[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
             const result2 = text.match(pegReg2);
             if (result2) {
                 return result2[0];
             } else {
-                const pegReg3 = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+                const pegReg3 = /[a-zA-Z]*[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
                 const result3 = text.match(pegReg3);
                 return result3 ? result3[0] : '';
             }
@@ -507,9 +507,6 @@ const stageIm = (function () {
             if (p.children && p.children.length > 0) {
                 continue;
             }
-            if ((!p.qc_tp || p.qc_tp === 0)) {
-                continue;
-            }
             const posRange = gsPos.getLedgerPos(p.id);
             if (!posRange) {
                 for (const c of changes) {
@@ -520,9 +517,6 @@ const stageIm = (function () {
                 importChanges.forEach(x => { if (x.lid === p.id) im.changes.push(x); });
             } else {
                 for (const pp of posRange) {
-                    if ((!pp.qc_qty || pp.qc_qty === 0)) {
-                        continue;
-                    }
                     for (const c of changes) {
                         if (c.lid === p.id && c.pid === pp.id && c.qty && c.qty !== 0) {
                             im.changes.push(c);
@@ -778,9 +772,6 @@ const stageIm = (function () {
         if (!im.changes) {
             im.changes = [];
         }
-        if ((!node.qc_qty || node.qc_qty === 0)) {
-            return;
-        }
         const posRange = gsPos.getLedgerPos(node.id);
         if (!posRange) {
             for (const c of changes) {
@@ -791,7 +782,6 @@ const stageIm = (function () {
             importChanges.forEach(x => { if (x.lid === node.id) im.changes.push(x); });
         } else {
             for (const p of posRange) {
-                if ((!p.qc_qty || p.qc_qty === 0)) continue;
                 for (const c of changes) {
                     if (c.lid === node.id && c.pid === p.id && c.qty && c.qty !== 0) {
                         im.changes.push(c);
@@ -879,11 +869,9 @@ const stageIm = (function () {
                     };
                     im.calc_memo = '本期计量:' + (checkZero(im.jl) ? 0 : im.jl) + ' ' + im.unit;
                     ImData.push(im);
-                    if (pp.qc_qty && pp.qc_qty !== 0) {
-                        for (const c of changes) {
-                            if (c.lid === p.id && c.pid === pp.id && c.qty && c.qty !== 0) {
-                                im.changes.push(c);
-                            }
+                    for (const c of changes) {
+                        if (c.lid === p.id && c.pid === pp.id && c.qty && c.qty !== 0) {
+                            im.changes.push(c);
                         }
                     }
                 }
@@ -907,14 +895,12 @@ const stageIm = (function () {
                 };
                 im.calc_memo = '本期计量:' + (checkZero(im.jl) ? 0 : im.jl) + ' ' + im.unit;
                 ImData.push(im);
-                if (p.qc_qty && p.qc_qty !== 0) {
-                    for (const c of changes) {
-                        if (c.lid === p.id && c.pid == -1 && c.qty && c.qty !== 0) {
-                            im.changes.push(c);
-                        }
+                for (const c of changes) {
+                    if (c.lid === p.id && c.pid == -1 && c.qty && c.qty !== 0) {
+                        im.changes.push(c);
                     }
-                    importChanges.forEach(x => { if (x.lid === p.id) im.changes.push(x); });
                 }
+                importChanges.forEach(x => { if (x.lid === p.id) im.changes.push(x); });
             }
         }
     }

+ 4 - 1
app/public/js/tender_list_manage.js

@@ -363,7 +363,10 @@ function bindTenderUrl() {
     });
     // 删除
     $('body').on('click', '.c-body a[name=del]', function () {
-        $('#del-bd-ok').attr('tid', $(this).parent().attr('tid'));
+        const tid = parseInt($(this).parent().attr('tid'));
+        const tender = _.find(tenders, {id: tid});
+        $('#del-bd-ok').attr('tid', tid);
+        $('#del-tender-name').text(tender.name);
         $('#del-bd').modal('show');
     });
 }

+ 137 - 43
app/public/report/js/jpc_output.js

@@ -117,6 +117,13 @@ let JpcCanvasOutput = {
             }
             return rst;
         }
+        function _chkIfCloseOutput(control, actLines, area, fontHeight) {
+            let rst = false;
+            if (control.CloseOutput === 'T' && actLines > 1) {
+                rst = true;
+            }
+            return rst;
+        }
         function private_drawText(val, area, font, control) {
             let dftFontHeight = 12;
             let output = [];
@@ -265,10 +272,11 @@ let JpcCanvasOutput = {
             //根据control的 自动折行 及 缩放优先 这俩属性 来分解cell value
             if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_WRAP]] === 'T' && control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_SHRINK_FIRST]] !== 'T') {
                 let vals = [];
-                let validAreaTxtWidth = cell[JV.PROP_AREA][JV.PROP_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT] - cell[JV.PROP_AREA][JV.PROP_LEFT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT];
+                let validAreaTxtWidth = cell[JV.PROP_AREA][JV.PROP_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT] - cell[JV.PROP_AREA][JV.PROP_LEFT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT] - 1;
                 for (let val of orgValues) {
                     let actW = ctx2D.measureText(val).width;
-                    if (actW > validAreaTxtWidth) {
+                    if (actW > (validAreaTxtWidth - 4)) {
+                        //减4个像素是考虑到导出excel的情况
                         vals = vals.concat(private_splitString(val, validAreaTxtWidth, ctx2D));
                     } else {
                         vals.push(val);
@@ -308,42 +316,59 @@ let JpcCanvasOutput = {
                     }
                 }
 
-                if (font) {
-                    let dftFontHeight = parseFloat(font[JV.FONT_PROPS[1]]);
-                    let dftOthers = "";
-                    let dftFontBold = font[JV.FONT_PROPS[3]];
-                    if (dftFontBold && dftFontBold === 'T') {
-                        dftOthers = "bold " + dftOthers ;
-                    }
-                    let dftFontItalic = font[JV.FONT_PROPS[4]];
-                    if (dftFontItalic && dftFontItalic === 'T') {
-                        dftOthers = dftOthers + "italic ";
-                    }
-                    dftFontHeight = me.scaleFactor * dftFontHeight;
-                    ctx.font = dftOthers + dftFontHeight + "px " + font[JV.PROP_NAME];
+                let dftFontHeight = parseFloat(font[JV.FONT_PROPS[1]]);
+                let dftOthers = "";
+                let dftFontBold = font[JV.FONT_PROPS[3]];
+                if (dftFontBold && dftFontBold === 'T') {
+                    dftOthers = "bold " + dftOthers ;
                 }
+                let dftFontItalic = font[JV.FONT_PROPS[4]];
+                if (dftFontItalic && dftFontItalic === 'T') {
+                    dftOthers = dftOthers + "italic ";
+                }
+                dftFontHeight = me.scaleFactor * dftFontHeight;
+                ctx.font = dftOthers + dftFontHeight + "px " + font[JV.PROP_NAME];
+
                 _splitValues(cell, control, values, ctx);
 
                 let height = cell[JV.PROP_AREA][JV.PROP_BOTTOM] - cell[JV.PROP_AREA][JV.PROP_TOP];
                 let area = [cell[JV.PROP_AREA][JV.PROP_LEFT] + me.offsetX, cell[JV.PROP_AREA][JV.PROP_TOP] + me.offsetY, cell[JV.PROP_AREA][JV.PROP_RIGHT] + me.offsetX, cell[JV.PROP_AREA][JV.PROP_BOTTOM] + me.offsetY];
                 let ah = height;
                 let restTopH = 0, restBottomH = 0;
-                if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_CLOSE_OUTPUT]] === 'T') {
-                    ah = (parseFloat(font[JV.FONT_PROPS[1]]) + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM]) * values.length;
-                    let restH = height - ah;
-                    if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'center') {
-                        restTopH = restH / 2;
-                        restBottomH = restH / 2;
+                // if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_CLOSE_OUTPUT]] === 'T') {
+                //     ah = (parseFloat(font[JV.FONT_PROPS[1]]) + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM]) * values.length;
+                //     let restH = height - ah;
+                //     if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'center') {
+                //         restTopH = restH / 2;
+                //         restBottomH = restH / 2;
+                //     } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'bottom') {
+                //         restBottomH = restH;
+                //     } else {
+                //         // restTopH = restH;
+                //         restTopH = JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
+                //     }
+                // }
+                //这里增加 ‘紧密输出’ 处理:在cell的输出空间划分出上下空间,中间行的输出间隔只有4个像素(要考虑与导出excel一致性,不能少了)
+                const isCloseOutput = _chkIfCloseOutput(control, values.length, area, dftFontHeight);
+                let closeTopOffset = 0;
+                if (isCloseOutput) {
+                    closeTopOffset = (height - (dftFontHeight + 4) * values.length) / 2; // 默认居中对齐
+                    if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'top') {
+                        closeTopOffset = JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
                     } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'bottom') {
-                        restBottomH = restH;
-                    } else {
-                        // restTopH = restH;
-                        restTopH = JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
+                        closeTopOffset = height - (dftFontHeight + 4) * values.length - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM];
                     }
                 }
                 for (let i = 0; i < values.length; i++) {
-                    area[JV.IDX_TOP] = cell[JV.PROP_AREA][JV.PROP_TOP] + i * (ah / values.length) + me.offsetY + restTopH;
-                    area[JV.IDX_BOTTOM] = cell[JV.PROP_AREA][JV.PROP_TOP] + (i + 1) * (ah / values.length) + me.offsetY + restBottomH;
+                    // area[JV.IDX_TOP] = cell[JV.PROP_AREA][JV.PROP_TOP] + i * (ah / values.length) + me.offsetY + restTopH;
+                    // area[JV.IDX_BOTTOM] = cell[JV.PROP_AREA][JV.PROP_TOP] + (i + 1) * (ah / values.length) + me.offsetY + restBottomH;
+                    if (isCloseOutput) {
+                        area[JV.IDX_TOP] = cell[JV.PROP_AREA][JV.PROP_TOP] + closeTopOffset + i *(dftFontHeight + 4) + me.offsetY;
+                        area[JV.IDX_BOTTOM] = cell[JV.PROP_AREA][JV.PROP_TOP] + closeTopOffset + (i + 1) *(dftFontHeight + 4) + me.offsetY;
+                    } else {
+                        area[JV.IDX_TOP] = cell[JV.PROP_AREA][JV.PROP_TOP] + i * (ah / values.length) + me.offsetY + restTopH;
+                        area[JV.IDX_BOTTOM] = cell[JV.PROP_AREA][JV.PROP_TOP] + (i + 1) * (ah / values.length) + me.offsetY + restBottomH;
+                    }
                     if (values[i] === null || values[i] === undefined || values[i] === 'null') {
                         values[i] = "";
                     }
@@ -411,26 +436,38 @@ let JpcCanvasOutput = {
             } else {
                 control = cell[JV.PROP_CONTROL];
             }
-            if (cell.path) {
+            if (cell.pic) {
                 const img = new Image();
-                img.src = cell.path;
+                img.src = cell.pic;
+                img.crossOrigin = 'anonymous';
                 img.onload = function() {
-                    private_drawImage(cell, control, img);
+                    if (cell.signature_name.indexOf(JV.SIGNATURE_NAME_DUMMY) >= 0) {
+                        private_drawImage(cell, control, img, (!!cell.isStamp), [1, 1, 1, 1]);
+                    } else {
+                        private_drawImage(cell, control, img);
+                    }
+                    // private_drawImage(cell, control, img);
                 };
-                img.onerror = function() {
-                    console.log('cell.path error: ' + cell.path);
-                }
-            } else if (cell.pic) {
+            } else if (cell.path) {
                 const img = new Image();
-                img.src = cell.pic;
+                // img.src = cell.path;
+                if (cell.path.indexOf(OSS_PATH) < 0 && cell.path[0] !== '/') {
+                    img.src = OSS_PATH + cell.path;
+                } else {
+                    img.src = cell.path;
+                }
+                img.crossOrigin = 'anonymous';
                 img.onload = function() {
-                    if (cell.signature_name === JV.SIGNATURE_NAME_DUMMY) {
-                        private_drawImage(cell, control, img, [1, 1, 1, 1]);
+                    if (cell.signature_name.indexOf(JV.SIGNATURE_NAME_DUMMY) >= 0) {
+                        private_drawImage(cell, control, img, (!!cell.isStamp), [1, 1, 1, 1]);
                     } else {
                         private_drawImage(cell, control, img);
                     }
-                    // private_drawImage(cell, control, img);
+                    // private_drawImage(cell, control, img, (!!cell.isStamp));
                 };
+                img.onerror = function() {
+                    console.log('cell.path error: ' + cell.path);
+                }
             }
         }
         function private_getProperSignatureArea(cell, control) {
@@ -491,8 +528,14 @@ let JpcCanvasOutput = {
             rst[3] = rst[3] + JpcCanvasOutput.offsetY;
             return rst;
         }
-        function private_drawImage(cell, control, imageData, offsetArea) {
+        function private_drawImage(cell, control, imageData, orgSize = false, offsetArea) {
             let area = private_getProperSignatureArea(cell, control);
+            if (orgSize) {
+                area[0] = cell.area.Left + JpcCanvasOutput.offsetX; // Left
+                area[1] = cell.area.Top + JpcCanvasOutput.offsetY; // Top
+                area[2] = cell.area.Right + JpcCanvasOutput.offsetX; // Right
+                area[3] = cell.area.Bottom + JpcCanvasOutput.offsetY; // Bottom
+            }
             if (offsetArea) {
                 area[0] = area[0] + offsetArea[0]; // Left
                 area[1] = area[1] + offsetArea[1]; // Top
@@ -535,7 +578,7 @@ let JpcCanvasOutput = {
             if (page.signature_cells && page.signature_cells.length > 0) {
                 for (let k = 0; k < page.signature_cells.length; k++) {
                     let cell = page.signature_cells[k];
-                    if (PAGE_SHOW['isTextSignature'] === 0 || cell.signature_name === JV.SIGNATURE_NAME_DUMMY) {
+                    if (PAGE_SHOW['isTextSignature'] === 0 || cell.signature_name.indexOf(JV.SIGNATURE_NAME_DUMMY) >= 0) {
                         // 如果是非文本签名或草图,才显示图片(文本签名已经在后台单独处理,在cells数组内增加一个合适的cell)
                         private_drawSignatureCell(cell, fonts, styles, controls, newPageMergeBand);
                     }
@@ -571,6 +614,7 @@ let JpcCanvasOutput = {
     drawPageBorder: function(rptTpl, canvas, resolution) {
         let me = this;
         let size = me.getReportSizeInPixel(rptTpl, resolution);
+        const shadowPix = 5;
 
         let ctx = canvas.getContext("2d");
         ctx.save();
@@ -586,8 +630,42 @@ let JpcCanvasOutput = {
         ctx.restore();
 
         ctx.fillStyle="black";
-        ctx.fillRect((size[0] + me.offsetX) * me.scaleFactor, (10 + me.offsetY) * me.scaleFactor, 10 * me.scaleFactor, size[1] * me.scaleFactor);
-        ctx.fillRect((10 + me.offsetX) * me.scaleFactor, (size[1] + me.offsetY) * me.scaleFactor, size[0] * me.scaleFactor, 10 * me.scaleFactor);
+        ctx.fillRect((size[0] + me.offsetX) * me.scaleFactor, (shadowPix + me.offsetY) * me.scaleFactor, shadowPix * me.scaleFactor, size[1] * me.scaleFactor);
+        ctx.fillRect((shadowPix + me.offsetX) * me.scaleFactor, (size[1] + me.offsetY) * me.scaleFactor, size[0] * me.scaleFactor, shadowPix * me.scaleFactor);
+    },
+    highlightConflictArea: function (pageObj, pageIdx) {
+        const _chkAndSetConflict = function(orgCells) {
+            let conflictIds = [];
+            for (let j = 0; j < orgCells.length; j++) {
+                if (conflictIds.indexOf(j) < 0) {
+                    let cell1 = orgCells[j];
+                    for (let k = j + 1; k < orgCells.length; k++) {
+                        let cell2 = orgCells[k];
+                        //判断area是否有交叉
+                        if (areaConflict(cell1[JV.PROP_AREA], cell2[JV.PROP_AREA])) {
+                            conflictIds.push(j);
+                            conflictIds.push(k);
+                            cell1[JV.PROP_STYLE] = "ConflictCell";
+                            cell2[JV.PROP_STYLE] = "ConflictCell";
+                        }
+                    }
+                }
+            }
+        };
+        if (pageObj && pageObj.items.length > 0 && pageObj.items.length >= pageIdx) {
+            let private_create_conflict_line = function () {
+                return {"LineWeight": "1", "DashStyle": "SOLID", "Color": "RED"};
+            };
+            let page = pageObj.items[pageIdx - 1];
+            if (!pageObj[JV.NODE_STYLE_COLLECTION].hasOwnProperty('ConflictCell')) {
+                const styleConflict = {"Left": private_create_conflict_line(), "Right": private_create_conflict_line(), "Top": private_create_conflict_line(), "Bottom": private_create_conflict_line()};
+                pageObj[JV.NODE_STYLE_COLLECTION]["ConflictCell"] = styleConflict;
+            }
+            _chkAndSetConflict(page.cells);
+            _chkAndSetConflict(page.signature_date_cells);
+            _chkAndSetConflict(page.signature_audit_cells);
+            _chkAndSetConflict(page.signature_cells);
+        }
     },
     getReportSizeInPixel: function(rptTpl, resolution) {
         let rst = [8.27, 11.69];
@@ -598,4 +676,20 @@ let JpcCanvasOutput = {
         rst[1] = Math.round(resolution[0] * rst[1]);
         return rst;
     }
-};
+};
+
+function areaConflict(area1,area2) {
+    let maxX,maxY,minX,minY,
+        w1 = area1[JV.PROP_RIGHT] - area1[JV.PROP_LEFT],
+        w2 = area2[JV.PROP_RIGHT] - area2[JV.PROP_LEFT],
+        h1 = area1[JV.PROP_BOTTOM] - area1[JV.PROP_TOP],
+        h2 = area2[JV.PROP_BOTTOM] - area2[JV.PROP_TOP]
+    ;
+    //1. 求2个矩形的最小外包矩形
+    minX = (area1[JV.PROP_LEFT] <= area2[JV.PROP_LEFT]) ? area1[JV.PROP_LEFT] : area2[JV.PROP_LEFT];
+    minY = (area1[JV.PROP_TOP] <= area2[JV.PROP_TOP]) ? area1[JV.PROP_TOP] : area2[JV.PROP_TOP];
+    maxX = (area1[JV.PROP_RIGHT] >= area2[JV.PROP_RIGHT]) ? area1[JV.PROP_RIGHT] : area2[JV.PROP_RIGHT];
+    maxY = (area1[JV.PROP_BOTTOM] >= area2[JV.PROP_BOTTOM]) ? area1[JV.PROP_BOTTOM] : area2[JV.PROP_BOTTOM];
+    //2. 判断外包矩形与高与宽是否小于俩矩形的高与宽之和(这里的边界条件是小于,等于的话不算;而且是高与宽都得符合条件!)
+    return (maxX - minX < w1 + w2 && maxY - minY < h1 + h2);
+}

+ 20 - 1
app/public/report/js/jpc_output_value_define.js

@@ -35,6 +35,17 @@ let JV = {
 
     SIGNATURE_NAME_DUMMY: 'dummy_pic',
 
+    OUTPUT_ALIGN: {
+        H: ["left", "center", "right"],
+        V: ["top", "center", "bottom"]
+    },
+    H_ALIGN_IDX_LEFT : 0,
+    H_ALIGN_IDX_CENTER : 1,
+    H_ALIGN_IDX_RIGHT : 2,
+    V_ALIGN_IDX_TOP : 0,
+    V_ALIGN_IDX_CENTER : 1,
+    V_ALIGN_IDX_BOTTOM : 2,
+
     CONTROL_PROPS: ["Shrink", "ShowZero", "Horizon", "Vertical", "Wrap", "VerticalForExcel", "ShrinkFirst", "CloseOutput"],
     CONTROL_PROP_IDX_SHRINK: 0,
     CONTROL_PROP_IDX_SHOW_ZERO: 1,
@@ -58,11 +69,19 @@ let JV = {
     FONT_PROP_IDX_STRIKEOUT: 6,
     FONT_PROP_IDX_ANGLE: 7,
 
+    PROP_PAGE_SEQ: 'page_seq',
+    PROP_CELLS: 'cells',
+    PROP_WATERMARK_CELLS: 'watermark_cells',
+    PROP_SIGNATURE_CELLS: 'signature_cells',
+    PROP_SIGNATURE_DATE_CELLS: 'signature_date_cells',
+    PROP_SIGNATURE_AUDIT_CELLS: 'signature_audit_cells',
+    PAGE_SPECIAL_MERGE_POS: "page_merge_pos",
+
     PAGES_SIZE_STR: ['A3', 'A4', 'A5', 'B4', 'B5', 'LETTER', 'LEGAL', 'EXECUTIVE', '16K'],
     PAGES_SIZE_IDX: [8, 9, 11, 12, 13, 1, 5, 7, 93],
     PAGES_SIZE: [[11.693, 16.535], [8.268, 11.693], [5.827, 8.268], [9.84, 13.898], [6.93, 9.84], [8.5, 11.0], [8.5, 14.0], [7.25, 10.5], [7.25, 10.5]],
 
-    OUTPUT_OFFSET: [2,2,1,3],
+    OUTPUT_OFFSET: [1,1,1,1],
     OFFSET_IDX_LEFT: 0,
     OFFSET_IDX_RIGHT: 1,
     OFFSET_IDX_TOP: 2,

+ 7 - 3
app/public/report/js/rpt_archive.js

@@ -461,7 +461,8 @@ let rptArchiveObj = {
             let page = pageData.items[i];
             for (let sCell of page.signature_cells) {
                 // sCell.signature_name 草图不用加密
-                if (sCell.signature_name !== 'dummy_pic') {
+                if (sCell.signature_name.indexOf('dummy_pic') < 0) {
+                // if (sCell.signature_name !== 'dummy_pic') {
                     let control = null;
                     if (typeof sCell[JV.PROP_CONTROL] === "string") {
                         control = controls[sCell[JV.PROP_CONTROL]];
@@ -506,7 +507,8 @@ let rptArchiveObj = {
             for (let sCellIdx = 0; sCellIdx < currentRptPageRst.items[pageIdx].signature_cells.length; sCellIdx++) {
                 let sCell = currentRptPageRst.items[pageIdx].signature_cells[sCellIdx];
                 // if (sCell.signature_name === 'dummy_pic' && sCell.path.indexOf('/sign') < 0) {
-                if (sCell.signature_name === 'dummy_pic') {
+                if (sCell.signature_name.indexOf('dummy_pic') >= 0) {
+                // if (sCell.signature_name === 'dummy_pic') {
                     //1. 草图就不考虑重复问题,重复就重复吧;
                     //2. 如果是dummy_pic,也不用判断是否为签名了,因为有其他的逻辑直接选择审核人,不经过签名过程,就当草图一样放过去了
                     let picIdx = picKeys.indexOf(sCell.path);
@@ -577,7 +579,8 @@ let rptArchiveObj = {
                         signCells.push(dtlSignCells);
                         for (let sCellIdx = pageItem.signature_cells.length - 1; sCellIdx >= 0; sCellIdx--) {
                             let sCell = pageItem.signature_cells[sCellIdx];
-                            if (sCell.signature_name !== 'dummy_pic') {
+                            if (sCell.signature_name.indexOf('dummy_pic') < 0) {
+                            // if (sCell.signature_name !== 'dummy_pic') {
                                 dtlSignCells.push(sCell);
                                 pageItem.signature_cells.splice(sCellIdx, 1);
                             }
@@ -793,6 +796,7 @@ function getBlob(url) {
     return new Promise(resolve => {
         const xhr = new XMLHttpRequest();
 
+        // let fullUrl = url + '?x-oss-process=image/info';
         xhr.open('GET', url, true);
         xhr.responseType = 'blob';
         xhr.onload = () => {

+ 5 - 2
app/public/report/js/rpt_indexDb.js

@@ -242,9 +242,9 @@ const indexDbOprObj = {
             }
         }
     },
-    _getRptDataForDb: function(params, rpt_names, stage_id, cb) {
+    _getRptDataForDb: async function(params, rpt_names, stage_id, cb) {
         CommonAjax.postXsrfEx("/tender/report_api/getMultiReports", params, 60000, true, getCookie('csrfToken_j'),
-            function(result){
+            async function(result){
                 const signatureRelArr = [];
                 STAGE_AUDIT = result.stageAudit;
                 if (params.needWaterMark) sessionStorage.waterMarkStr = result.waterMarkStr;
@@ -259,6 +259,9 @@ const indexDbOprObj = {
                             break;
                         }
                     }
+                    for (const pageData of result.data) {
+                        await rptSignatureHelper.resetDummySignature(pageData, null); //
+                    }
                     if (params.stage_status === 3) {
                         rptSignatureHelper.mergeSignDate(result.data[idx], singleSignatureRelArr, false);
                         rptSignatureHelper.mergeSignature(result.data[idx], singleSignatureRelArr, true); //这里要把签名数据设置进来

File diff suppressed because it is too large
+ 1789 - 0
app/public/report/js/rpt_jsexcel.js


+ 62 - 24
app/public/report/js/rpt_jspdf.js

@@ -2,8 +2,10 @@
  * Created by Tony on 2019/9/10.
  */
 
+// const PDF_SCALE = 0.75;
 const PDF_SCALE = 0.75;
 // const DPI = getScreenDPI();
+const NORMAL_LINE_WEIGHT = 0.5;
 
 let JpcJsPDFHelper = {
     doc: null,
@@ -139,7 +141,7 @@ let JpcJsPDFHelper = {
             } else {
                 control = cell[JV.PROP_CONTROL];
             }
-            private_drawImage(doc, ctx, cell, control, cell.pic, [1, 1, 1, 1]);
+            private_drawImage(doc, ctx, cell, control, cell.pic, (!!cell.isStamp), [1, 1, 1, 1]);
         }
 
         function private_drawSignature(doc, ctx, cell, styles, controls, mergedBand, onlyShowBorder, signatureRelArr) {
@@ -153,7 +155,7 @@ let JpcJsPDFHelper = {
                 private_drawLine(cell, doc, ctx, style, JV.PROP_LEFT, [JV.PROP_LEFT, JV.PROP_BOTTOM],[JV.PROP_LEFT, JV.PROP_TOP], mergedBand, styles, isNeedMergeBand);
             }
             ctx.closePath();
-            if (!onlyShowBorder || cell.signature_name === JV.SIGNATURE_NAME_DUMMY) private_drawSignatureCellText(doc, ctx, cell, controls, signatureRelArr);
+            if (!onlyShowBorder || cell.signature_name.indexOf(JV.SIGNATURE_NAME_DUMMY) >= 0) private_drawSignatureCellText(doc, ctx, cell, controls, signatureRelArr);
         }
 
         function private_drawSignatureCellText(doc, ctx, cell, controls, signatureRelArr) {
@@ -164,8 +166,9 @@ let JpcJsPDFHelper = {
                 control = cell[JV.PROP_CONTROL];
             }
             if (cell.pic) {
-                if (cell.signature_name === JV.SIGNATURE_NAME_DUMMY) {
-                    private_drawImage(doc, ctx, cell, control, cell.pic, [1, 1, 1, 1]);
+                if (cell.signature_name.indexOf(JV.SIGNATURE_NAME_DUMMY) >= 0) {
+                // if (cell.signature_name.indexOf(JV.SIGNATURE_NAME_DUMMY) >= 0) {
+                    private_drawImage(doc, ctx, cell, control, cell.pic, (!!cell.isStamp), [1, 1, 1, 1]);
                 } else {
                     private_drawImage(doc, ctx, cell, control, cell.pic);
                 }
@@ -173,7 +176,9 @@ let JpcJsPDFHelper = {
                 // 导出PDF时,根本不需要判断cell.path是不是null or undefined
                 for (const signRel of signatureRelArr) {
                     if (cell.signature_name === signRel.signature_name && signRel.sign_pic !== null && signRel.sign_pic !== undefined) {
-                        private_drawImage(doc, ctx, cell, control, signRel.sign_pic);
+                        if (!(signRel.signature_name.indexOf(JV.SIGNATURE_NAME_DUMMY) < 0 && signRel.sign_output.indexOf('normal_sign') < 0)) {
+                            private_drawImage(doc, ctx, cell, control, signRel.sign_pic);
+                        }
                         break;
                     }
                 }
@@ -227,8 +232,14 @@ let JpcJsPDFHelper = {
             return rst;
         }
 
-        function private_drawImage(doc, ctx, cell, control, imageData, offsetArea) {
+        function private_drawImage(doc, ctx, cell, control, imageData, orgSize = false, offsetArea) {
             let area = private_getProperSignatureArea(cell, control);
+            if (orgSize) {
+                area[0] = cell[JV.PROP_AREA][JV.PROP_LEFT] + offsetX; // Left
+                area[1] = cell[JV.PROP_AREA][JV.PROP_TOP] + offsetX; // Top
+                area[2] = cell[JV.PROP_AREA][JV.PROP_RIGHT] + offsetX; // Right
+                area[3] = cell[JV.PROP_AREA][JV.PROP_BOTTOM] + offsetX; // Bottom
+            }
             if (offsetArea) {
                 area[0] = area[0] + offsetArea[0]; // Left
                 area[1] = area[1] + offsetArea[1]; // Top
@@ -262,10 +273,10 @@ let JpcJsPDFHelper = {
             }
             if (destStyle[styleBorderDest] && parseFloat(destStyle[styleBorderDest][JV.PROP_LINE_WEIGHT]) !== 0) {
                 doc.setDrawColor(destStyle[styleBorderDest][JV.PROP_COLOR]);
-                if (parseInt(destStyle[styleBorderDest][JV.PROP_LINE_WEIGHT]) === 2) {
-                    doc.setLineWidth(1 * destStyle[styleBorderDest][JV.PROP_LINE_WEIGHT]);
+                if (parseInt(destStyle[styleBorderDest][JV.PROP_LINE_WEIGHT]) >= 2) {
+                    doc.setLineWidth(0.8 * destStyle[styleBorderDest][JV.PROP_LINE_WEIGHT]);
                 } else {
-                    doc.setLineWidth(0.1);
+                    doc.setLineWidth(NORMAL_LINE_WEIGHT);
                 }
                 doc.line((cell[JV.PROP_AREA][startP[0]] + offsetX) * PDF_SCALE, (cell[JV.PROP_AREA][startP[1]] + offsetY) * PDF_SCALE,
                     (cell[JV.PROP_AREA][destP[0]] + offsetX) * PDF_SCALE, (cell[JV.PROP_AREA][destP[1]] + offsetY) * PDF_SCALE);
@@ -292,10 +303,11 @@ let JpcJsPDFHelper = {
             //根据control的 自动折行 及 缩放优先 这俩属性 来分解cell value
             if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_WRAP]] === 'T' && control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_SHRINK_FIRST]] !== 'T') {
                 let vals = [];
-                let validAreaTxtWidth = cell[JV.PROP_AREA][JV.PROP_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT] - cell[JV.PROP_AREA][JV.PROP_LEFT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT];
+                let validAreaTxtWidth = cell[JV.PROP_AREA][JV.PROP_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT] - cell[JV.PROP_AREA][JV.PROP_LEFT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT] - 1;
                 for (let val of orgValues) {
                     let actW = doc.getTextWidth(val);
-                    if (actW > validAreaTxtWidth) {
+                    if (actW > (validAreaTxtWidth - 4)) {
+                        //减4个像素是考虑到导出excel的情况
                         vals = vals.concat(private_splitString(val, validAreaTxtWidth, doc));
                     } else {
                         vals.push(val);
@@ -309,6 +321,14 @@ let JpcJsPDFHelper = {
             }
         }
 
+        function _chkIfCloseOutput(control, actLines, area, fontHeight) {
+            let rst = false;
+            if (control.CloseOutput === 'T' && actLines > 1) {
+                rst = true;
+            }
+            return rst;
+        }
+
         function private_drawCellText(doc, ctx, cell, fonts, controls) {
             if (cell[JV.PROP_VALUE] !== undefined && cell[JV.PROP_VALUE] !== null) {
                 // let values = ("" + cell[JV.PROP_VALUE]).split('|');
@@ -356,23 +376,41 @@ let JpcJsPDFHelper = {
                 let area = [cell[JV.PROP_AREA][JV.PROP_LEFT] + offsetX, cell[JV.PROP_AREA][JV.PROP_TOP] + offsetY, cell[JV.PROP_AREA][JV.PROP_RIGHT] + offsetX, cell[JV.PROP_AREA][JV.PROP_BOTTOM] + offsetY];
                 let ah = height;
                 let restTopH = 0, restBottomH = 0;
-                if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_CLOSE_OUTPUT]] === 'T') {
-                    ah = (parseFloat(font[JV.FONT_PROPS[1]]) + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM]) * values.length;
-                    let restH = height - ah;
-                    if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'center') {
-                        restTopH = restH / 2;
-                        restBottomH = restH / 2;
+                let dftFontHeight = parseFloat(font[JV.FONT_PROPS[1]]);
+                const isCloseOutput = _chkIfCloseOutput(control, values.length, area, dftFontHeight);
+                let closeTopOffset = 0;
+                if (isCloseOutput) {
+                    closeTopOffset = (height - (dftFontHeight + 4) * values.length) / 2; // 默认居中对齐
+                    if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'top') {
+                        closeTopOffset = JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
                     } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'bottom') {
-                        restBottomH = restH;
-                    } else {
-                        // restTopH = restH;
-                        restTopH = JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
+                        closeTopOffset = height - (dftFontHeight + 4) * values.length - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM];
                     }
                 }
+                // if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_CLOSE_OUTPUT]] === 'T') {
+                //     ah = (parseFloat(font[JV.FONT_PROPS[1]]) + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM]) * values.length;
+                //     let restH = height - ah;
+                //     if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'center') {
+                //         restTopH = restH / 2;
+                //         restBottomH = restH / 2;
+                //     } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'bottom') {
+                //         restBottomH = restH;
+                //     } else {
+                //         // restTopH = restH;
+                //         restTopH = JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
+                //     }
+                // }
                 let spaceIdxArr = [];
                 for (let i = 0; i < values.length; i++) {
-                    area[JV.IDX_TOP] = cell[JV.PROP_AREA][JV.PROP_TOP] + i * (ah / values.length) + offsetY + restTopH;
-                    area[JV.IDX_BOTTOM] = cell[JV.PROP_AREA][JV.PROP_TOP] + (i + 1) * (ah / values.length) + offsetY + restBottomH;
+                    // area[JV.IDX_TOP] = cell[JV.PROP_AREA][JV.PROP_TOP] + i * (ah / values.length) + offsetY + restTopH;
+                    // area[JV.IDX_BOTTOM] = cell[JV.PROP_AREA][JV.PROP_TOP] + (i + 1) * (ah / values.length) + offsetY + restBottomH;
+                    if (isCloseOutput) {
+                        area[JV.IDX_TOP] = cell[JV.PROP_AREA][JV.PROP_TOP] + closeTopOffset + i * (dftFontHeight + 4) + offsetY;
+                        area[JV.IDX_BOTTOM] = cell[JV.PROP_AREA][JV.PROP_TOP] + closeTopOffset + (i + 1) * (dftFontHeight + 4) + offsetY;
+                    } else {
+                        area[JV.IDX_TOP] = cell[JV.PROP_AREA][JV.PROP_TOP] + i * (ah / values.length) + offsetY + restTopH;
+                        area[JV.IDX_BOTTOM] = cell[JV.PROP_AREA][JV.PROP_TOP] + (i + 1) * (ah / values.length) + offsetY + restBottomH;
+                    }
                     if (values[i] === null || values[i] === undefined || values[i] === 'null') {
                         values[i] = "";
                     }
@@ -462,7 +500,7 @@ let JpcJsPDFHelper = {
                 }
 
                 doc.setDrawColor("BLACK");
-                doc.setLineWidth(0.1);
+                doc.setLineWidth(NORMAL_LINE_WEIGHT);
                 doc.line(startX * PDF_SCALE, startY * PDF_SCALE, endX * PDF_SCALE, endY * PDF_SCALE);
             }
 

+ 171 - 33
app/public/report/js/rpt_main.js

@@ -260,7 +260,7 @@ let zTreeOprObj = {
             zTreeOprObj.treeObj.expandNode(treeNode, true, true, false);
         }
     },
-    onClick: function(event,treeId,treeNode) {
+    onClick: async function(event,treeId,treeNode) {
         let me = zTreeOprObj;
         if (treeNode && treeNode.nodeType === TPL_TYPE_TEMPLATE && treeNode.refId > 0) {
             window.history.pushState({},0, window.location.pathname + `?rpt_id=${treeNode.refId}`);
@@ -314,7 +314,7 @@ let zTreeOprObj = {
             }
 
             rptArchiveObj.toggleBtn(true);
-            me.requestNormalReport(params);
+            await me.requestNormalReport(params);
             me.countChkedRptTpl();
             rptCustomObj.showMaterialSelect();
         }
@@ -336,7 +336,7 @@ let zTreeOprObj = {
         dom.innerHTML = tmpStr;
         me.changeCfg();
     },
-    changeCfg: function() {
+    changeCfg: async function() {
         let me = zTreeOprObj;
         if (me.currentNode) {
             CUST_CFG_ORG = JSON.parse(JSON.stringify(CUST_CFG));
@@ -389,7 +389,7 @@ let zTreeOprObj = {
                 return;
             }
 
-            me.requestNormalReport(params);
+            await me.requestNormalReport(params);
             me.countChkedRptTpl();
             rptCustomObj.showMaterialSelect();
 
@@ -417,7 +417,7 @@ let zTreeOprObj = {
     _setupArchive: function() {
         //
     },
-    requestNormalReport: function (params) {
+    requestNormalReport: async function (params) {
         let me = zTreeOprObj;
         if (COMMON_WATER_MARK_PIC_DATA === null || COMMON_WATER_MARK_PIC_DATA === '') {
             params.needWaterMark = true;
@@ -426,7 +426,7 @@ let zTreeOprObj = {
         }
         $.bootstrapLoading.start();
         CommonAjax.postXsrfEx("/tender/report_api/getReport", params, 300000, true, getCookie('csrfToken_j'),
-            function(result){
+            async function(result){
                 $.bootstrapLoading.end();
                 let pageRst = result.data;
                 if (params.needWaterMark) COMMON_WATER_MARK_PIC_DATA = result.waterMarkStr;
@@ -440,7 +440,8 @@ let zTreeOprObj = {
                     rptSignatureHelper.originalRoleRelList = me._parseRoleRelList(result.signatureRelInfo[0].rel_content);
                     if (current_stage_status === 3) {
                         rptSignatureHelper.mergeSignDate(pageRst, ROLE_REL_LIST, true);
-                        rptSignatureHelper.mergeSignature(pageRst, ROLE_REL_LIST);
+                        rptSignatureHelper.mergeSignature(pageRst, ROLE_REL_LIST, true);
+                        await rptSignatureHelper.resetDummySignature(pageRst, ROLE_REL_LIST); // 这里重新整理签章坐标信息(因签章大小在后台暂时获取不到,挪到前端处理)
                         rptSignatureHelper.mergeSignAudit(pageRst, ROLE_REL_LIST, STAGE_AUDIT);
                     }
                 } else {
@@ -521,6 +522,7 @@ let zTreeOprObj = {
             me.currentPage = pageNum;
             JpcCanvasOutput.cleanCanvas(canvas);
             JpcCanvasOutput.drawPageBorder(me.currentRptPageRst, canvas, getScreenDPI());
+            // JpcCanvasOutput.highlightConflictArea(me.currentRptPageRst, pageNum);
             JpcCanvasOutput.drawToCanvas(me.currentRptPageRst, canvas, me.currentPage);
         }
         me.displayPageValue();
@@ -719,22 +721,176 @@ let rptControlObj = {
             );
         }
     },
+
+    downloadExcelReport: async function(pageDataArr, pageSize, rpt_names, signatureRelArr) {
+        const private_download = async function(currentIndex) {
+            if (currentIndex < pageDataArr.length) {
+                //这里的数据应该在调用前己处理
+                const rptName = rpt_names[currentIndex];
+                const singlePage = true;
+                let role_rel_list = signatureRelArr[currentIndex];
+                // await excelExportUtil.exportExcel(pageDataArr[currentIndex], pageSize, rptName, singlePage, null, null, ROLE_REL_LIST, null);
+                await excelExportUtil.exportExcel(pageDataArr[currentIndex], pageSize, rptName, singlePage, null, null, role_rel_list, null);
+            }
+        };
+        for (let idx = 0; idx < pageDataArr.length; idx++) {
+            await private_download(idx);
+        }
+    },
+
+    downloadExcelReportInOneBook: async function(pageDataArr, pageSize, signatureRelArr, rpt_names) {
+        let rptRoleRelArr = [];
+        let ttlRoleAmt = 0;
+        const reAssignSignatureName = function(pageData, roleRel, rpt_name_key) {
+            const keyMap = {};
+            for (const pageItem of pageData.items) {
+                for (const signCell of pageItem[JV.PROP_SIGNATURE_CELLS]) {
+                    if (!keyMap.hasOwnProperty(signCell.signature_name)) {
+                        keyMap[signCell.signature_name] = rpt_name_key + '_' + signCell.signature_name;
+                    }
+                }
+                //水印也要reAssign
+                for (const watermarkCell of pageItem[JV.PROP_WATERMARK_CELLS]) {
+                    if (!keyMap.hasOwnProperty(watermarkCell.signature_name)) {
+                        keyMap[watermarkCell.signature_name] = rpt_name_key + '_' + watermarkCell.signature_name;
+                    }
+                }
+            }
+            for (const pageItem of pageData.items) {
+                for (const signCell of pageItem[JV.PROP_SIGNATURE_CELLS]) {
+                    if (keyMap.hasOwnProperty(signCell.signature_name)) {
+                        signCell.signature_name = keyMap[signCell.signature_name];
+                    }
+                }
+                for (const watermarkCell of pageItem[JV.PROP_WATERMARK_CELLS]) {
+                    if (keyMap.hasOwnProperty(watermarkCell.signature_name)) {
+                        watermarkCell.signature_name = keyMap[watermarkCell.signature_name];
+                    }
+                }
+            }
+            for (const roleSign of roleRel) {
+                if (keyMap.hasOwnProperty(roleSign.signature_name)) {
+                    roleSign.signature_name = keyMap[roleSign.signature_name];
+                }
+            }
+        };
+        for (let idx = 0; idx < pageDataArr.length; idx++) {
+            let roleRel = signatureRelArr[idx];
+            ttlRoleAmt += roleRel.length;
+            pageDataArr[idx][JV.NODE_PAGE_INFO][JV.NODE_MAIN_INFO_RPT_NAME] = rpt_names[idx];
+            
+            // 这里要做些电子签名的signature_name转换,以防重名
+            if (roleRel.length > 0) {
+                reAssignSignatureName(pageDataArr[idx], roleRel, rpt_names[idx]);
+                rptRoleRelArr = rptRoleRelArr.concat(roleRel);
+            }
+
+            let hasWaterMark = false;
+            if (pageDataArr[idx].items[0][JV.PROP_WATERMARK_CELLS] && pageDataArr[idx].items[0][JV.PROP_WATERMARK_CELLS].length > 0) {
+                hasWaterMark = true;
+            }
+            if (ttlRoleAmt < 1 && hasWaterMark) {
+                reAssignSignatureName(pageDataArr[idx], [], rpt_names[idx]);
+                rptRoleRelArr = rptRoleRelArr.concat([pageDataArr[idx].items[0][JV.PROP_WATERMARK_CELLS][0].signature_name]);
+            }
+        }
+        excelExportUtil.exportExcelInOneBook(pageDataArr, pageSize, TENDER_NAME, rptRoleRelArr, null);
+    },
+    
+    getExcel_New: async function (isOneBook) {
+        let me = rptControlObj;
+        if (zTreeOprObj.checkedRptTplNodes && zTreeOprObj.checkedRptTplNodes.length > 0 && PAGE_SHOW['closeExportExcel'] !== 1) {
+            let refRptTplIds = [];
+            let rpt_names = [];
+            rptControlObj.getTplIdsCommon(refRptTplIds, rpt_names);
+            const signatureRelArr = [];
+            if (refRptTplIds.length === 0) {
+                if (zTreeOprObj.currentNode) {
+                    //在复杂情况下会影响到原有数据(文本签名 + 签章),为减少麻烦,还是当勾选处理
+                    refRptTplIds.push(zTreeOprObj.currentNode.refId);
+                    rpt_names.push(zTreeOprObj.currentNode.name);
+                }
+            }
+            if (refRptTplIds.length > 0) {
+                let params = rptControlObj.creatCommonExportParam(refRptTplIds);
+                // params.getPicFlag = true; //专门针对草图项,只有此项为true,才需要把草图信息带过来,预览及打印动态加载草图 // 纠结:但这样还是解决不了效率问题,得另外想交互方式
+                await rptCustomObj.getCustomSelect(params);
+                delete params.orientation; // 打印时有勾选的话,不需要提供方向
+                $.bootstrapLoading.start();
+                if (COMMON_WATER_MARK_PIC_DATA === null || COMMON_WATER_MARK_PIC_DATA === '') {
+                    params.needWaterMark = true;
+                } else {
+                    params.needWaterMark = false;
+                }
+                CommonAjax.postXsrfEx("/tender/report_api/getMultiReports", params, WAIT_TIME_EXPORT, true, getCookie('csrfToken_j'),
+                    function(result){
+                        $.bootstrapLoading.end();
+                        if (params.needWaterMark) COMMON_WATER_MARK_PIC_DATA = result.waterMarkStr;
+                        STAGE_AUDIT = result.stageAudit;
+                        let pageSize = rptControlObj.getCurrentPageSize();
+                        let pageDataArr = result.data;
+                        let signatureRelInfo = result.signatureRelInfo;
+                        for (let pageObj of pageDataArr) {
+                            let tmpRel = [];
+                            for (const signatureRel of signatureRelInfo) {
+                                if (signatureRel.rpt_id === pageObj.id) {
+                                    tmpRel = JSON.parse(signatureRel.rel_content);
+                                    break;
+                                }
+                            }
+                            signatureRelArr.push(tmpRel);
+                        }
+                        if (current_stage_status === 3) {
+                            // 统一安排merge(除草图外)
+                            for (let idx = 0; idx < pageDataArr.length; idx++) {
+                                const pageObj = pageDataArr[idx];
+                                let singleSignatureRelArr = signatureRelArr[idx];
+                                rptSignatureHelper.mergeSignDate(pageObj, singleSignatureRelArr, false);
+                                // rptSignatureHelper.mergeSignature(pageObj, signatureRelArr); // 这里merge的意义不大
+                                rptSignatureHelper.mergeSignAudit(pageObj, singleSignatureRelArr, STAGE_AUDIT);
+                            }
+                        }
+
+                        if (isOneBook) {
+                            me.downloadExcelReportInOneBook(pageDataArr, pageSize, signatureRelArr, rpt_names);
+                        } else {
+                            me.downloadExcelReport(pageDataArr, pageSize, rpt_names, signatureRelArr);
+                        }
+                    },
+                    function(failRst){
+                        $.bootstrapLoading.end();
+                        console.log(failRst);
+                    },
+                    function(exceptionRst){
+                        $.bootstrapLoading.end();
+                        console.log(exceptionRst);
+                    }
+                );
+            } else {
+                // 这个分支本来是为了减少请求,用户已经点过的表,又没有勾选,那么就直接导出EXCEL
+                // 但:发现在复杂情况下会影响到原有数据(文本签名 + 签章),为减少麻烦,在前面处理,保证不会走到这分支!
+            }
+        }
+    },
+
     checkAndGetExcel: function () {
         if (zTreeOprObj.treeObj) {
             let chkNodes = zTreeOprObj.treeObj.getCheckedNodes(true);
             if (chkNodes.length > 0) {
                 $("#show_excel_output_cfg").trigger("click");
             } else {
-                rptControlObj.getAllIndividualExcelBook();
+                rptControlObj.getExcel_New(false);
             }
         }
     },
     getExcel: function () {
         let me = rptControlObj;
         if ($("#excelExportType_AllInOneBook")[0].checked) {
-            me.getAllInOneBook();
+            // me.getAllInOneBook();
+            rptControlObj.getExcel_New(true);
         } else if ($("#excelExportType_IndividualBook")[0].checked) {
-            me.getAllIndividualExcelBook();
+            // me.getAllIndividualExcelBook();
+            rptControlObj.getExcel_New(false);
         }
     },
     getPdfFontCallbackLight: function(fontProperty) {
@@ -800,7 +956,7 @@ let rptControlObj = {
                     params.needWaterMark = false;
                 }
                 CommonAjax.postXsrfEx("/tender/report_api/getMultiReports", params, WAIT_TIME_EXPORT, true, getCookie('csrfToken_j'),
-                    function(result){
+                    async function(result){
                         // closeWaitingView();
                         $.bootstrapLoading.end();
                         if (params.needWaterMark) COMMON_WATER_MARK_PIC_DATA = result.waterMarkStr;
@@ -809,28 +965,10 @@ let rptControlObj = {
                         for (const signatureRel of result.signatureRelInfo) {
                             signatureRelArr.push(JSON.parse(signatureRel.rel_content));
                         }
-                        downloadPDFReport(result.data, pageSize, rpt_names, signatureRelArr, result.signatureRelInfo, refRptTplIds, STAGE_AUDIT);
-                        /*
-                        for (let idx = 0; idx < result.data.length; idx++) {
-                            let pageData = result.data[idx];
-                            // if (current_stage_status === 3) {
-                            //     rptSignatureHelper.mergeSignDate(pageData);
-                            //     rptSignatureHelper.mergeSignature(pageData);
-                            //     rptSignatureHelper.mergeSignAudit(pageRst, ROLE_REL_LIST, STAGE_AUDIT);
-                            // }
-                            // 备注:因多表的原因,无需merge电子签名,在下面处理
-                            let singleSignatureRelArr = [];
-                            for (let rIdx = 0; rIdx < result.signatureRelInfo.length; rIdx++) {
-                                if (result.signatureRelInfo[rIdx].rpt_id === refRptTplIds[idx]) {
-                                    // singleSignatureRelArr.push(signatureRelArr[rIdx]);
-                                    singleSignatureRelArr = signatureRelArr[rIdx]; // 有些报表可能没有签名
-                                    break;
-                                }
-                            }
-                            // JpcJsPDFHelper.outputAsPdf(pageData, pageSize, rpt_names[idx], signatureRelArr);
-                            JpcJsPDFHelper.outputAsPdf(pageData, pageSize, rpt_names[idx], singleSignatureRelArr, STAGE_AUDIT); // 精确控制签名
+                        for (const pageData of result.data) {
+                            await rptSignatureHelper.resetDummySignature(pageData, null); //
                         }
-                        //*/
+                        downloadPDFReport(result.data, pageSize, rpt_names, signatureRelArr, result.signatureRelInfo, refRptTplIds, STAGE_AUDIT);
                     },
                     function(failRst){
                         // closeWaitingView();
@@ -1059,7 +1197,7 @@ function downloadPDFReport(pageDataArr, pageSize, rpt_names, signatureRelArr, si
     for (let pageData of pageDataArr) {
         for (let page of pageData.items) {
             for (let dCell of page.signature_cells) {
-                if (dCell.signature_name === 'dummy_pic') {
+                if (dCell.signature_name.indexOf('dummy_pic') >= 0) {
                     let picIdx = picPaths.indexOf(dCell.path);
                     if (picIdx < 0) {
                         picPaths.push(dCell.path);

+ 54 - 0
app/public/report/js/rpt_other_stage.js

@@ -0,0 +1,54 @@
+const rptOtherStage = (function (){
+    const info = {
+        advance: { title: '请选择预付款', colHeader: ['选择', '类型', '期', '审批状态'] },
+    };
+    const data = {};
+    let curType = '';
+    const initList = function () {
+        const header = info[curType].colHeader;
+        const type = info[curType].type;
+        $('#sos-header').html(`<tr class="text-center"><th>${header[0]}</th><th>${header[1]}</th><th>${header[2]}</th><th>${header[3]}</th></tr>`);
+        const html = [], arr = data[curType];
+        for (const a of arr) {
+            const checked = a.selected ? 'checked' : '';
+            html.push('<tr class="text-center">', `<td><input type="checkbox" name="sos-check" ${checked} value="${a.id}"></td>`, `<td>${a.typeStr}</td>`, `<td>第${a.order}期</td>`, `<td>${a.statusStr}</td>`,  '</tr>');
+        }
+        $('#sos-list').html(html.join(''));
+    };
+    const showOtherStage = async function (type) {
+        curType = type;
+        document.getElementById('sos-title').innerText = info[type].title;
+        if (!data[type]) {
+            const result = await postDataAsync(`/tender/${window.location.pathname.split('/')[2]}/load`, { filter: curType });
+            data[curType] = result[curType];
+        }
+        initList();
+        $('#select-other-stage').modal('show');
+        $('#sos-all').change(function () {
+            const check = this.checked;
+            $('[name=sos-check]').each((i, c) => { c.checked = check });
+        })
+    };
+    const updateOtherStage = async function () {
+        const updateData = {};
+        updateData[curType] = [];
+        $('[name=sos-check]').each((i, c) => {
+            const ci = data[curType].find(x => { return x.id == c.value });
+            const checked = c.checked ? 1 : 0;
+            if (ci && ci.selected !== checked) {
+                updateData[curType].push({id: c.value, selected: c.checked})
+            }
+        });
+        if (updateData[curType].length > 0) {
+            await postData(`/tender/${window.location.pathname.split('/')[2]}/saveRela`, updateData);
+            $('[name=sos-check]').each((i, c) => {
+                const ci = data[curType].find(x => {
+                    return x.id == c.value
+                });
+                if (ci) ci.selected = c.checked ? 1 : 0;
+            });
+        }
+        $('#select-other-stage').modal('hide');
+    };
+    return { showOtherStage, updateOtherStage }
+})();

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

@@ -7,6 +7,7 @@ let STAGE_AUDIT = []; //注意这个,与rpt_main.js不要混了
 let STAGE_LIST = [];
 let STAGE_AUDIT_ORG = [];
 let current_stage_id = -1;
+let OSS_PATH = '';
 // 设置Date对象Format函数
 // -- 打印预览需要重新设置一遍 ------------------------------------------------
 Date.prototype.Format = function(fmt) {
@@ -26,17 +27,18 @@ Date.prototype.Format = function(fmt) {
     return fmt;
 };
 
-function printPageLoading() {
+async function printPageLoading() {
     let params = JSON.parse(sessionStorage.report_params);
     let _current_stage_status = parseInt(sessionStorage.current_stage_status);
     let closeWaterMark = parseInt(sessionStorage.closeWaterMark);
     let refRptTplIds = JSON.parse(sessionStorage.refRptTplIds);
     STAGE_LIST = JSON.parse(sessionStorage.STAGE_LIST);
     STAGE_AUDIT_ORG = JSON.parse(sessionStorage.STAGE_AUDIT_ORG);
+    OSS_PATH = sessionStorage.OSS_PATH;
     current_stage_id = parseInt(sessionStorage.current_stage_id);
     let scaleFactor = 1;
     CommonAjax.postXsrfEx("/tender/report_api/getMultiReports", params, 60000, true, getCookie('csrfToken_j'),
-        function(result){
+        async function(result){
             const signatureRelArr = [];
             STAGE_AUDIT = result.stageAudit;
             // sessionStorage.waterMarkStr = result.waterMarkStr;
@@ -55,7 +57,8 @@ function printPageLoading() {
                 }
                 if (_current_stage_status === 3) {
                     rptSignatureHelper.mergeSignDate(result.data[idx], singleSignatureRelArr, false);
-                    rptSignatureHelper.mergeSignature(result.data[idx], singleSignatureRelArr);
+                    rptSignatureHelper.mergeSignature(result.data[idx], singleSignatureRelArr, true);
+                    await rptSignatureHelper.resetDummySignature(result.data[idx], null); // 这里重新整理签章坐标信息(因签章大小在后台暂时获取不到,挪到前端处理)
                     rptSignatureHelper.mergeSignAudit(result.data[idx], singleSignatureRelArr, result.stageAudit);
                 }
             }

+ 75 - 30
app/public/report/js/rpt_print.js

@@ -27,6 +27,7 @@ let rptPrintHelper = {
             sessionStorage.refRptTplIds = JSON.stringify(refRptTplIds);
             sessionStorage.STAGE_LIST = JSON.stringify(STAGE_LIST);
             sessionStorage.STAGE_AUDIT_ORG = JSON.stringify(STAGE_AUDIT_ORG);
+            sessionStorage.OSS_PATH = OSS_PATH;
             sessionStorage.current_stage_id = getStageId();
             // sessionStorage.STAGE_AUDIT = JSON.stringify(STAGE_AUDIT);
             window.open('/printReport/' + sessionStorage.pageSize);
@@ -161,13 +162,13 @@ let rptPrintHelper = {
             }
             // 计量有电子签名,要单独处理
             for (let cell of page.signature_cells) {
-                svgPageArr.push(buildSignatureCellSvg(cell, styles, controls, page[JV.PROP_PAGE_MERGE_BORDER], pagesData[JV.BAND_PROP_MERGE_BAND],
+                svgPageArr.push(buildSignatureCellSvg(cell, styles, controls, fonts, page[JV.PROP_PAGE_MERGE_BORDER], pagesData[JV.BAND_PROP_MERGE_BAND],
                     offsetX - actAreaOffsetX, offsetY - actAreaOffsetY, adjustY, canvas, isHtoV, pixelSize, actAreaArr[idx]));
             }
             if (closeWaterMark === 0) {
                 for (let cell of page.watermark_cells) {
                     cell.pic = sessionStorage.waterMarkStr;
-                    svgPageArr.push(buildSignatureCellSvg(cell, styles, controls, page[JV.PROP_PAGE_MERGE_BORDER], pagesData[JV.BAND_PROP_MERGE_BAND],
+                    svgPageArr.push(buildSignatureCellSvg(cell, styles, controls, fonts, page[JV.PROP_PAGE_MERGE_BORDER], pagesData[JV.BAND_PROP_MERGE_BAND],
                         offsetX - actAreaOffsetX, offsetY - actAreaOffsetY, adjustY, canvas, isHtoV, pixelSize, actAreaArr[idx]));
                 }
             }
@@ -211,7 +212,7 @@ function getActualBorderStyle(cell, styles, mergeBorderStyle, pageBorderArea, bo
     return rst;
 }
 
-function buildSignatureCellSvg(cell, styles, controls, pageMergeBorder, rptMergeBorder, offsetX, offsetY, adjustY, canvas, isHtoV, pixelSize, actArea) {
+function buildSignatureCellSvg(cell, styles, controls, fonts, pageMergeBorder, rptMergeBorder, offsetX, offsetY, adjustY, canvas, isHtoV, pixelSize, actArea) {
     let rst = [];
     let style = styles[cell[JV.PROP_STYLE]];
     let mergeBandStyle = null;
@@ -268,7 +269,11 @@ function buildSignatureCellSvg(cell, styles, controls, pageMergeBorder, rptMerge
         control = controls[cell[JV.PROP_CONTROL]];
     }
     if (cell.pic || cell.path) {
-        buildImage(rst, cell, control, offsetX, offsetY, adjustY, isHtoV, HtoVStr);
+        if (cell.signature_name.indexOf(JV.SIGNATURE_NAME_DUMMY) >= 0) {
+            buildImage(rst, cell, control, offsetX, offsetY, HtoVStr, (!!cell.isStamp), [1,1,1,1]);
+        } else {
+            buildImage(rst, cell, control, offsetX, offsetY, HtoVStr);
+        }
     }
 
     return rst.join("");
@@ -335,20 +340,35 @@ function buildCellSvg(cell, fonts, styles, controls, pageMergeBorder, rptMergeBo
     return rst.join("");
 }
 
-function buildImage(destRst, cell, control, offsetX, offsetY, adjustY, isHtoV, HtoVStr) {
+function buildImage(destRst, cell, control, offsetX, offsetY, HtoVStr, orgSize = false, offsetArea) {
     let href = '';
-    if (cell.path) {
-        href = 'xlink:href="' + cell.path +'"';
-    } else {
+    if (cell.pic) {
         href = 'href="' + cell.pic +'"';
+    } else {
+        href = 'xlink:href="' + cell.path +'"';
     }
     const area = getProperSignatureArea(cell, control, offsetX, offsetY);
-    if (cell.signature_name === JV.SIGNATURE_NAME_DUMMY) {
-        area[0] = area[0] + 1; // Left
-        area[1] = area[1] + 1; // Top
-        area[2] = area[2] - 1; // Right
-        area[3] = area[3] - 1; // Bottom
+    /*
+    if (cell.signature_name.indexOf(JV.SIGNATURE_NAME_DUMMY) >= 0) {
+        area[0] = cell[JV.PROP_AREA][JV.PROP_LEFT] + 1 + offsetX; // Left
+        area[1] = cell[JV.PROP_AREA][JV.PROP_TOP] + 1 + offsetY; // Top
+        area[2] = cell[JV.PROP_AREA][JV.PROP_RIGHT] - 1 + offsetX; // Right
+        area[3] = cell[JV.PROP_AREA][JV.PROP_BOTTOM] - 1 + offsetY; // Bottom
+    }
+    /*/
+    if (orgSize) {
+        area[0] = cell[JV.PROP_AREA][JV.PROP_LEFT] + offsetX; // Left
+        area[1] = cell[JV.PROP_AREA][JV.PROP_TOP] + offsetY; // Top
+        area[2] = cell[JV.PROP_AREA][JV.PROP_RIGHT] + offsetX; // Right
+        area[3] = cell[JV.PROP_AREA][JV.PROP_BOTTOM] + offsetY; // Bottom
+    }
+    if (offsetArea) {
+        area[0] = area[0] + offsetArea[0]; // Left
+        area[1] = area[1] + offsetArea[1]; // Top
+        area[2] = area[2] - offsetArea[2]; // Right
+        area[3] = area[3] - offsetArea[3]; // Bottom
     }
+    //*/
     destRst.push('<image x="' + area[0] + '" y="' + area[1] + '" width="' + (area[2] - area[0]) + '" height="' + (area[3] - area[1]) + '" ');
     destRst.push(href + HtoVStr + ' />');
 }
@@ -402,10 +422,11 @@ function _splitValues(cell, control, orgValues, ctx2D) {
     //根据control的 自动折行 及 缩放优先 这俩属性 来分解cell value
     if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_WRAP]] === 'T' && control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_SHRINK_FIRST]] !== 'T') {
         let vals = [];
-        let validAreaTxtWidth = cell[JV.PROP_AREA][JV.PROP_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT] - cell[JV.PROP_AREA][JV.PROP_LEFT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT];
+        let validAreaTxtWidth = cell[JV.PROP_AREA][JV.PROP_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT] - cell[JV.PROP_AREA][JV.PROP_LEFT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT] - 1;
         for (let val of orgValues) {
             let actW = ctx2D.measureText(val).width;
-            if (actW > validAreaTxtWidth) {
+            if (actW > (validAreaTxtWidth - 4)) {
+                //减4个像素是考虑到导出excel的情况
                 vals = vals.concat(private_splitString(val, validAreaTxtWidth, ctx2D));
             } else {
                 vals.push(val);
@@ -489,7 +510,8 @@ function buildText(destRst, cell, font, control, offsetX, offsetY, adjustY, canv
             let innerDftFontHeight = (dftFontHeight * 3 / 4); //SVG的字体与canvas的字体大小的切换, 不用考虑取整
             if (control) {
                 if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === "top") {
-                    y = innerArea[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.IDX_TOP];
+                    // y = innerArea[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.IDX_TOP];
+                    y = innerArea[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.IDX_TOP] + innerDftFontHeight + JV.OUTPUT_OFFSET[JV.IDX_TOP] + 2; // 向上对齐
                 } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === "bottom") {
                     y = innerArea[JV.IDX_BOTTOM] - JV.OUTPUT_OFFSET[JV.IDX_BOTTOM];
                 } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === "center") {
@@ -547,28 +569,51 @@ function buildText(destRst, cell, font, control, offsetX, offsetY, adjustY, canv
     };
     let ah = height;
     let restTopH = 0, restBottomH = 0;
-    if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_CLOSE_OUTPUT]] === 'T') {
-        ah = (parseFloat(font[JV.FONT_PROPS[1]]) + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM]) * values.length;
-        let restH = height - ah;
-        if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'center') {
-            restTopH = restH / 2;
-            restBottomH = restH / 2;
+    // if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_CLOSE_OUTPUT]] === 'T') {
+    //     ah = (parseFloat(font[JV.FONT_PROPS[1]]) + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM]) * values.length;
+    //     let restH = height - ah;
+    //     if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'center') {
+    //         restTopH = restH / 2;
+    //         restBottomH = restH / 2;
+    //     } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'bottom') {
+    //         restBottomH = restH;
+    //     } else {
+    //         // restTopH = restH;
+    //         restTopH = JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + 8; // 备注:打印比别的方式多加些空隙
+    //     }
+    // }
+    const isCloseOutput = _chkIfCloseOutput(control, values.length, area, orgFontHeight);
+    let closeTopOffset = 0;
+    if (isCloseOutput) {
+        closeTopOffset = (height - (orgFontHeight + 4) * values.length) / 2; // 默认居中对齐
+        if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'top') {
+            closeTopOffset = JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
         } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'bottom') {
-            restBottomH = restH;
-        } else {
-            // restTopH = restH;
-            restTopH = JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + 8; // 备注:打印比别的方式多加些空隙
+            closeTopOffset = height - (orgFontHeight + 4) * values.length - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM];
         }
     }
     for (let vidx = 0; vidx < values.length; vidx++) {
-        // area[JV.IDX_TOP] = top + vidx * (height / values.length);
-        // area[JV.IDX_BOTTOM] = top + (vidx + 1) * (height / values.length);
-        area[JV.IDX_TOP] = top + vidx * (ah / values.length) + restTopH;
-        area[JV.IDX_BOTTOM] = top + (vidx + 1) * (ah / values.length) + restTopH;
+        // area[JV.IDX_TOP] = top + vidx * (ah / values.length) + restTopH;
+        // area[JV.IDX_BOTTOM] = top + (vidx + 1) * (ah / values.length) + restTopH;
+        if (isCloseOutput) {
+            area[JV.IDX_TOP] = top + closeTopOffset + vidx * (orgFontHeight + 4);
+            area[JV.IDX_BOTTOM] = top + closeTopOffset + (vidx + 1) * (orgFontHeight + 4);
+        } else {
+            area[JV.IDX_TOP] = top + vidx * (ah / values.length) + restTopH;
+            area[JV.IDX_BOTTOM] = top + (vidx + 1) * (ah / values.length) + restBottomH;
+        }
         inner_draw_text(values[vidx]);
     }
 }
 
+function _chkIfCloseOutput(control, actLines, area, fontHeight) {
+    let rst = false;
+    if (control.CloseOutput === 'T' && actLines > 1) {
+        rst = true;
+    }
+    return rst;
+}
+
 function private_splitString(val, areaWidth, ctx) {
     let rst = [];
     if (val) {

+ 449 - 108
app/public/report/js/rpt_signature.js

@@ -5,6 +5,11 @@
 'use strict'
 
 const DFT_ROLE_NAME = '';
+const NORMAL_SIGN_STR = 'normal_sign';
+const COMPANY_SIGN_STR = 'company_stamp';
+const PRIVATE_SIGN_STR = 'private_stamp';
+const STD_STAMP_SIZE_WIDTH = 5 * 96 / 2.54;   // 公章大小:宽度(5 CM)
+const STD_STAMP_SIZE_HEIGHT = 5 * 96 / 2.54;  // 公章大小:高度(5 CM)
 
 let rptSignatureHelper = {
     currentSelectedESignAccDom: null,
@@ -128,6 +133,7 @@ let rptSignatureHelper = {
             if (dftDate !== '' && dftDate.length > 20) {
                 dftDate = (new Date(dftDate)).Format('yyyy-MM-dd');
             }
+            let roleRel = null;
             if (directAcc) {
                 rptSignatureHelper.pushDomElementByUser(elementsStrArr, userAcc.name, userAcc.role);
                 // 还有ROLE_REL_LIST
@@ -140,8 +146,12 @@ let rptSignatureHelper = {
                 roleRelObj.user_name = userAcc.name;
                 roleRelObj.acc_id = userAcc.id;
                 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.role = (userAcc.role === '')?DFT_ROLE_NAME:userAcc.role;
                 ROLE_REL_LIST.push(roleRelObj);
+                roleRel = roleRelObj;
             } else if (roleAcc) {
                 // 创建相关dom元素
                 rptSignatureHelper.pushDomElementByRole(elementsStrArr, roleAcc.name, userAcc.name);
@@ -154,13 +164,17 @@ let rptSignatureHelper = {
                 roleRelObj.sign_date_format = 'yyyy年M月d日';
                 roleRelObj.user_name = userAcc.name;
                 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.type = '角色';
                 roleRelObj.role = (userAcc.role === '')?DFT_ROLE_NAME:userAcc.role;
                 roleRelObj.role_name = roleAcc.name;
                 ROLE_REL_LIST.push(roleRelObj);
+                roleRel = roleRelObj;
             }
             // elementsStrArr.push('');
-            rptSignatureHelper.pushDatePickerDom(elementsStrArr, userAcc.id);
+            rptSignatureHelper.pushDatePickerDom(elementsStrArr, userAcc, roleRel, 0);
             $(rptSignatureHelper.currentSelectedESignAccDom).append(elementsStrArr.join(' '));
             //.appendChild(pNode);
             //*/
@@ -205,7 +219,7 @@ let rptSignatureHelper = {
             for (const page of pageRst.items) {
                 if (page.signature_cells) {
                     for (const sCell of page.signature_cells) {
-                        if (sCell.signature_name !== null && sCell.signature_name !== undefined && sCell.signature_name !== 'dummy_pic') {
+                        if (sCell.signature_name !== null && sCell.signature_name !== undefined && sCell.signature_name.indexOf(JV.SIGNATURE_NAME_DUMMY) < 0) {
                             if (singatureNameArr.indexOf(sCell.signature_name) < 0) {
                                 signature_cells.push(sCell);
                                 singatureNameArr.push(sCell.signature_name);
@@ -235,18 +249,13 @@ let rptSignatureHelper = {
                                 //角色
                                 rptSignatureHelper.pushDomElementByRole(elementsStrArr, role_rel.role_name, role_rel.user_name);
                             }
-                            const idSuffixStr = 'dtp_' + role_rel.signature_name + '_' + signatureDivId;
-                            elementsStrArr.push('<div class="">');
-                            if (role_rel.sign_date !== '') {
-                                const dt = new Date(role_rel.sign_date);
-                                const dtVal = dt.Format('yyyy-MM-dd');
-                                //elementsStrArr.push('<input class="datepicker-here form-control form-control-sm mt-0" placeholder="选择签名日期" data-language="zh" type="text" value="' + (new Date(role_rel.sign_date)).Format('yyyy-M-d') + '">');
-                                // elementsStrArr.push('<input id="' + idSuffixStr + '" class="datepicker-here form-control form-control-sm mt-0" placeholder="选择签名日期" data-language="zh" type="text" readonly="true" value="' + dtVal + '"');
-                                elementsStrArr.push('<input id="' + idSuffixStr + '" class="form-control form-control-sm mt-0" placeholder="选择签名日期" type="date" value="' + dtVal + '"');
-                            } else {
-                                // elementsStrArr.push('<input id="' + idSuffixStr + '" class="datepicker-here form-control form-control-sm mt-0" placeholder="选择签名日期" data-language="zh" type="text" readonly="true"');
-                                elementsStrArr.push('<input id="' + idSuffixStr + '" class="form-control form-control-sm mt-0" placeholder="选择签名日期" type="date"');
+                            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.company_stamp_path = rptSignatureHelper._getCompanySign(userAcc.company);
                             }
+                    
+                            rptSignatureHelper.pushDatePickerDom(elementsStrArr, userAcc, role_rel, idx);
                             hasPic = true;
                             break;
                         }
@@ -281,74 +290,77 @@ let rptSignatureHelper = {
         elementsStrArr.push('<p class=" d-flex justify-content-between m-0"><span>' + userName +
             '-<small class="text-muted">' + ((userRole === '')?DFT_ROLE_NAME:userRole) +
             '</small></span><a onclick="rptSignatureHelper.removeSignature(this)" class="text-danger"><i class="fa fa-remove" title="移除签名"></i></a></p>');
-        // rptSignatureHelper.pushDatePickerDom(elementsStrArr);
     },
     pushDomElementByRole: function (elementsStrArr, roleName, userName) {
         elementsStrArr.push('<p class=" d-flex justify-content-between m-0"><span><i class="fa fa-user" title="角色"> ' + roleName +
             '</i>-<small class="text-muted">' + userName +
             '</small></span><a onclick="rptSignatureHelper.removeSignature(this)" class="text-danger"><i class="fa fa-remove" title="移除签名"></i></a></p>');
-        // rptSignatureHelper.pushDatePickerDom(elementsStrArr);
     },
-    pushDatePickerDom: function (elementsStrArr, userAccId) {
-        let idSuffixStr = 'dtp_' + rptSignatureHelper.currentSelectedESignAccName + '_' + rptSignatureHelper.currentSelectedESignParentDivId;
-        elementsStrArr.push('<div class="">');
-        // 日期控件存在页面高度不过高无法选中bug,先不用
-        // elementsStrArr.push('<input id="' + idSuffixStr + '" class="datepicker-here form-control form-control-sm mt-0" placeholder="选择签名日期" data-language="zh" data-position="right bottom" type="text" readonly="true"');
-        //*
-        let dftDate = _getSignDateByAllScenarios(userAccId);
-        if (dftDate !== '' && dftDate.length > 20) {
-            dftDate = (new Date(dftDate)).Format('yyyy-MM-dd');
-        }
-        /*/
-        let dftDate = '';
-        let hasAudit = false;
-        if (STAGE_AUDIT && STAGE_AUDIT.length > 0) {
-            for (const stga of STAGE_AUDIT) {
-                if (stga.aid === userAccId) {
-                    hasAudit = true;
-                    if (stga.status === 3 && stga.end_time && stga.end_time !== '' && stga.end_time.length > 20) {
-                        //只有在审批人通过后才获取审批时间
-                        // let dt = new Date(stga.end_time);
-                        dftDate = (new Date(stga.end_time)).Format('yyyy-MM-dd');
-                    } else {
-                        dftDate = '';
-                    }
-                    // break; // 实际情况:有可能会有多次审核,要取最后一次
-                }
-            }
+    pushDatePickerDom: function (elementsStrArr, userAcc, role_rel, seq = 0) {
+        const dateIdSuffixStr = 'dtp_' + role_rel.signature_name + '_' + rptSignatureHelper.currentSelectedESignParentDivId;
+        let milliSec = (new Date()).getMilliseconds();
+        const idSuffixStr = `dtp_${seq}_${milliSec}_${rptSignatureHelper.currentSelectedESignParentDivId}`;
+        elementsStrArr.push('<div class="mt-3">');
+        elementsStrArr.push('<div class="row">');
+        //-------------------------------------------------
+        if (!Array.isArray(role_rel.sign_output)) {
+            role_rel.sign_output = [NORMAL_SIGN_STR];
         }
-        let isOrgRpt = false;
-        for (const stg of STAGE_LIST) {
-            if (stg.id === current_stage_id) {
-                if (stg.user_id === userAccId) {
-                    isOrgRpt = true;
-                }
-                break;
-            }
-        }
-        if (isOrgRpt && !hasAudit && STAGE_AUDIT_ORG && STAGE_AUDIT_ORG.length > 0) {
-            if (STAGE_AUDIT_ORG[0].begin_time && STAGE_AUDIT_ORG[0].begin_time !== '' && STAGE_AUDIT_ORG[0].begin_time.length > 20) {
-                dftDate = (new Date(STAGE_AUDIT_ORG[0].begin_time)).Format('yyyy-MM-dd');
-            }
+        const normalSignChkStr = (role_rel.sign_output.indexOf(NORMAL_SIGN_STR) >= 0) ? 'checked' : '';
+        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="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}')" >`);
+        elementsStrArr.push(`           <label class="form-check-label" for="${idSuffixStr}_sign1">签字</label>`);
+        elementsStrArr.push('       </div>');
+        const hasIndividualStamp = (userAcc && userAcc.stamp_path && userAcc.stamp_path !== ''); //用户账号的stamp_path是属于用户自己的私章,不是公司章
+        // let chkType = hasIndividualStamp ? `radio` : 'checkbox';
+        const chkType = 'checkbox';
+        let rdoNameStr = `dtp_${role_rel.signature_name}_${rptSignatureHelper.currentSelectedESignParentDivId}`;
+        if (hasIndividualStamp) {
+            elementsStrArr.push('       <div class="form-check form-check-inline mx-1">');
+            elementsStrArr.push('           <div class="form-group">');
+            elementsStrArr.push('               <div class="form-check form-check-inline px-1">');
+            elementsStrArr.push(`                   <input class="form-check-input" type="${chkType}" id="${idSuffixStr}_sign2" value="companyStamp" name="${rdoNameStr}" onchange="rptSignatureHelper._changeSignType(this, '${role_rel.signature_name}', '${COMPANY_SIGN_STR}')" ${companySignChkStr}>`);
+            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(`                   <label class="form-check-label" for="${idSuffixStr}_sign3">个人章</label>`);
+            elementsStrArr.push('               </div>');
+            elementsStrArr.push('           </div>');
+            elementsStrArr.push('       </div>');
+        } else {
+            elementsStrArr.push('       <div class="form-check form-check-inline px-2">');
+            elementsStrArr.push(`           <input class="form-check-input" type="${chkType}" id="${idSuffixStr}_sign10" value="option2" onchange="rptSignatureHelper._changeSignType(this, '${role_rel.signature_name}', '${COMPANY_SIGN_STR}')" ${companySignChkStr}>`);
+            elementsStrArr.push(`           <label class="form-check-label" for="${idSuffixStr}_sign10">单位章</label>`);
+            elementsStrArr.push('       </div>');
         }
-        if (!isOrgRpt && !hasAudit) {
-            //非审批流程人员以及非原报,则显示期截至时间
-            for (const stg of STAGE_LIST) {
-                if (stg.id === current_stage_id && stg.period) {
-                    const period = stg.period.split(' ~ ');
-                    if (period.length === 2) {
-                        dftDate = period[1];
-                    }
-                }
-            }
+        elementsStrArr.push('   </div>');
+        elementsStrArr.push('</div>');
+        // 2. 日期
+        elementsStrArr.push('<div class="col-6">');
+        let dftDate = '';
+        if (userAcc) dftDate = _getSignDateByAllScenarios(userAcc.id);
+        if (dftDate !== '' && dftDate.length > 20) {
+            dftDate = (new Date(dftDate)).Format('yyyy-MM-dd');
         }
-        //*/
+        elementsStrArr.push('<div class="input-group input-group-sm margin-inputbox">');
+        elementsStrArr.push('   <div class="input-group-prepend">');
+        elementsStrArr.push('       <span class="input-group-text height-inputbox" id="inputGroup-sizing-sm"><i class="fa fa-calendar" title="添加签名日期"></i></span>');
+        elementsStrArr.push('   </div>');
         if (dftDate !== '') {
-            elementsStrArr.push('<input id="' + idSuffixStr + '" class="form-control form-control-sm mt-0" placeholder="选择签名日期" type="date" value="' + dftDate + '"');
+            elementsStrArr.push(`<input id="${dateIdSuffixStr}" type="text" class="form-control datepicker-here height-inputbox" aria-label="Small" aria-describedby="inputGroup-sizing-sm" data-language="zh" value="${dftDate}"></input>`);
         } else {
-            elementsStrArr.push('<input id="' + idSuffixStr + '" class="form-control form-control-sm mt-0" placeholder="选择签名日期" type="date"');
+            elementsStrArr.push(`<input id="${dateIdSuffixStr}" type="text" class="form-control datepicker-here height-inputbox" aria-label="Small" aria-describedby="inputGroup-sizing-sm" data-language="zh"></input>`);
         }
         elementsStrArr.push('</div>');
+        //-------------------------------------------------
+        elementsStrArr.push('</div>');
+        elementsStrArr.push('</div>');
     },
     removeSignature: function (dom) {
         let accTxtName = $(dom.parentNode.parentNode.parentNode.parentNode.parentNode).find('label')[0].innerText;
@@ -449,16 +461,20 @@ let rptSignatureHelper = {
         params.rel_content = ROLE_REL_LIST;
         params.selectedTenders = selectedTenders;
         rptSignatureHelper.originalRoleRelList = JSON.parse(JSON.stringify(ROLE_REL_LIST));
+        $.bootstrapLoading.start();
         CommonAjax.postXsrfEx("/tender/report_api/updateMultiRoleRelationship", params, 10000, true, getCookie('csrfToken_j'),
             function(result){
-                console.log(result);
+                // console.log(result);
+                $.bootstrapLoading.end();
                 if (result.data && result.data.insertId > 0) {
                     CURRENT_ROLE_REL_ID = result.data.insertId;
                 }
             }, function(err){
                 // hintBox.unWaitBox();
+                $.bootstrapLoading.end();
             }, function(ex){
                 // hintBox.unWaitBox();
+                $.bootstrapLoading.end();
             }
         );
     },
@@ -473,27 +489,36 @@ let rptSignatureHelper = {
         return rst;
     },
 
-    setupAfterSelectSignature: function (signatureDivId) {
+    setupAfterSelectSignature: async function (signatureDivId) {
         //0. 签名日期
         let me = rptSignatureHelper;
         rptSignatureHelper.resetSignDate(signatureDivId);
         rptSignatureHelper.resetSignAudit();
         if (current_stage_status === 3) {
             //1. 重刷page
+            let hasStamp = false;
             for (const page of zTreeOprObj.currentRptPageRst.items) {
                 if (page.signature_cells) {
-                    for (const sCell of page.signature_cells) {
-                        if (sCell.hasOwnProperty('pre_path')) {
+                    for (let cIdx = page.signature_cells.length - 1; cIdx >= 0; cIdx--) {
+                        const sCell = page.signature_cells[cIdx];
+                        if (sCell.isStamp) {
+                            page.signature_cells.splice(cIdx);
+                            hasStamp = true;
+                        } else if (sCell.hasOwnProperty('pre_path')) {
                             sCell.path = sCell.pre_path;
                             delete sCell.pre_path;
                         }
+                        for (let role of ROLE_REL_LIST) {
+
+                        }
                     }
                 }
             }
+            resetStampSignature(zTreeOprObj.currentRptPageRst, ROLE_REL_LIST);
             if (PAGE_SHOW.isTextSignature) {
                 resetTextSignature(zTreeOprObj.currentRptPageRst);
             }
-            zTreeOprObj.showPage(zTreeOprObj.currentPage, zTreeOprObj.canvas);
+            // zTreeOprObj.showPage(zTreeOprObj.currentPage, zTreeOprObj.canvas);
         }
         //2. 更新数据
         const params = {};
@@ -505,9 +530,11 @@ let rptSignatureHelper = {
         // rptSignatureHelper.originalRoleRelList = [];
         // rptSignatureHelper.originalRoleRelList = rptSignatureHelper.originalRoleRelList.concat(ROLE_REL_LIST);
         rptSignatureHelper.originalRoleRelList = JSON.parse(JSON.stringify(ROLE_REL_LIST));
+        $.bootstrapLoading.start();
         CommonAjax.postXsrfEx("/tender/report_api/updateRoleRelationship", params, 10000, true, getCookie('csrfToken_j'),
-            function(result){
+            async function(result){
                 // console.log(result);
+                $.bootstrapLoading.end();
                 if (result.data && result.data.insertId > 0) {
                     CURRENT_ROLE_REL_ID = result.data.insertId;
                 }
@@ -526,8 +553,10 @@ let rptSignatureHelper = {
                     rptSignatureHelper.originalRoleRelList = me._parseRoleRelList(result.signatureRelInfo[0].rel_content);
                     if (current_stage_status === 3) {
                         rptSignatureHelper.mergeSignDate(zTreeOprObj.currentRptPageRst, ROLE_REL_LIST, true);
-                        rptSignatureHelper.mergeSignature(zTreeOprObj.currentRptPageRst, ROLE_REL_LIST);
+                        rptSignatureHelper.mergeSignature(zTreeOprObj.currentRptPageRst, ROLE_REL_LIST, true);
+                        await rptSignatureHelper.resetDummySignature(zTreeOprObj.currentRptPageRst, ROLE_REL_LIST); // 这里重新整理签章坐标信息(因签章大小在后台暂时获取不到,挪到前端处理)
                         rptSignatureHelper.mergeSignAudit(zTreeOprObj.currentRptPageRst, ROLE_REL_LIST, STAGE_AUDIT);
+                        zTreeOprObj.showPage(zTreeOprObj.currentPage, zTreeOprObj.canvas);
                     }
                 } else {
                     // CURRENT_ROLE_REL_ID = -1;
@@ -536,8 +565,10 @@ let rptSignatureHelper = {
 
             }, function(err){
                 // hintBox.unWaitBox();
+                $.bootstrapLoading.end();
             }, function(ex){
                 // hintBox.unWaitBox();
+                $.bootstrapLoading.end();
             }
         );
     },
@@ -606,7 +637,7 @@ let rptSignatureHelper = {
             domArr.push('</a>');
             //3. 显示名称
             let acc = rptSignatureHelper.getUserAccount(role.bind_acc_id);
-            domArr.push('<i class="fa fa-user"></i> ' + role.name + '<p>' + acc.name + '-<small class="text-muted">' + ((acc.role === '')?DFT_ROLE_NAME:acc.role) + '</small></p>');
+            if (acc) domArr.push('<i class="fa fa-user"></i> ' + role.name + '<p>' + acc.name + '-<small class="text-muted">' + ((acc.role === '')?DFT_ROLE_NAME:acc.role) + '</small></p>');
             ulDom.append(domArr.join(' '));
         }
     },
@@ -620,18 +651,99 @@ let rptSignatureHelper = {
         }
         return rst;
     },
+    _getCompanySign: function(companyName) {
+        let rst = '';
+        for (const cUnit of CONSTRUCT_UNIT_LIST) {
+            if (cUnit.name === companyName) {
+                rst = cUnit.sign_path;
+                break;
+            }
+        }
+        return rst;
+    },
     mergeSignature: function (pageData, currRoleRelList, setPic = false) {
         for (const page of pageData.items) {
             if (page.signature_cells) {
+                const adHocScells = [];
                 for (const sCell of page.signature_cells) {
+                    if (sCell.signature_name.indexOf(JV.SIGNATURE_NAME_DUMMY) < 0) {
+                        sCell.path = null;
+                        sCell.pre_path = null;
+                        sCell.pic = null;
+                    }
                     for (const role_rel of currRoleRelList) {
                         if (role_rel.signature_name === sCell.signature_name) {
-                            sCell.path = role_rel.sign_path;
-                            sCell.pre_path = role_rel.sign_path;
-                            if (setPic) {
-                                sCell.pic = role_rel.sign_pic; // 有些场景(如:批量归档)需要直接设置签名数据
+                            // 处理签章
+                            if (!Array.isArray(role_rel.sign_output)) {
+                                role_rel.sign_output = [];
+                                role_rel.sign_output.push(NORMAL_SIGN_STR);
+                            }
+                            for (const signType of role_rel.sign_output) {
+                                switch (signType) {
+                                    case NORMAL_SIGN_STR:
+                                        sCell.path = role_rel.sign_path;
+                                        sCell.pre_path = role_rel.sign_path;
+                                        if (setPic) {
+                                            sCell.pic = role_rel.sign_pic; // 有些场景(如:批量归档)需要直接设置签名数据
+                                        }
+                                        break;
+                                    case COMPANY_SIGN_STR:
+                                    case PRIVATE_SIGN_STR:
+                                        /*
+                                        const stampPath = (signType === COMPANY_SIGN_STR) ? role_rel.company_stamp_path : role_rel.private_stamp_path;
+                                        // 生成一个新的dummy cell(类似草图)
+                                        const newStampCell = _createDummyCell();
+                                        newStampCell.signature_name = JV.SIGNATURE_NAME_DUMMY;
+                                        newStampCell.path = OSS_PATH + stampPath;
+                                        newStampCell.control = sCell.control;
+                                        newStampCell.style = sCell.style;
+                                        newStampCell.isStamp = true; // 这个标记纯属为了刷新用,其他sign cell是没有的
+                                        // 设置坐标
+                                        rptSignatureHelper._resetStampPos(sCell, newStampCell, pageData[JV.NODE_CONTROL_COLLECTION]);
+                                        // 加到cells去
+                                        adHocScells.push(newStampCell);
+                                        //*/
+                                    break;
+                                    default:
+                                    break;
+                                }
+                            }
+                            break;
+                        }
+                    }
+                }
+                page.signature_cells = page.signature_cells.concat(adHocScells);
+            }
+        }
+    },
+    resetDummySignature: async function(pageData, roleRel, ifPushRoleRel = false) {
+        // 备注:计算草图等其他图形需要额外做些处理
+        let dummySignIdx = 0;
+        const stampPicKeys = [], stampPicFeatures = [];
+        for (let pageIdx = 0; pageIdx < pageData.items.length; pageIdx++) {
+            const page = pageData.items[pageIdx];
+            if (page[JV.PROP_SIGNATURE_CELLS] && page[JV.PROP_SIGNATURE_CELLS].length > 0) {
+                for (const signatureCell of page[JV.PROP_SIGNATURE_CELLS]) {
+                    if (signatureCell.signature_name.indexOf(JV.SIGNATURE_NAME_DUMMY) >= 0) {
+                        // 表示这是一个其他类型的非原生电子签名图,只是借用signature的处理机制,每个图都是唯一的,所以需要重新给个唯一的新signature_name
+                        // signatureCell.signature_name = signatureCell.signature_name + '_' + pageIdx + '_' + dummySignIdx;
+                        signatureCell.signature_name = signatureCell.signature_name + '_' + page.page_seq + '_' + dummySignIdx; // page_seq在分页后都不会变动
+                        dummySignIdx++;
+                        const roleRelItem = { type: '用户', sign_path: signatureCell.path, signature_name: signatureCell.signature_name };
+                        if (signatureCell.isStamp) {
+                            let stmpIdx = stampPicKeys.indexOf(signatureCell.path);
+                            if (stampPicKeys.indexOf(signatureCell.path) < 0) {
+                                stampPicKeys.push(signatureCell.path);
+                                stmpIdx = stampPicKeys.length - 1;
+                                const picRes = await getHttpBlobText(signatureCell.path + '?x-oss-process=image/info');
+                                stampPicFeatures.push(picRes);
                             }
+                            // { "FileSize": {"value": "2514"}, "Format": {"value": "png"}, "ImageHeight": {"value": "94"}, "ImageWidth": {"value": "94"} }
+                            roleRelItem.stampFeature = stampPicFeatures[stmpIdx];
+                            // 重新处理cell坐标
+                            _resetStampArea(pageData[JV.NODE_CONTROL_COLLECTION][signatureCell[JV.PROP_CONTROL]], signatureCell, roleRelItem);
                         }
+                        if (ifPushRoleRel) roleRel.push(roleRelItem);
                     }
                 }
             }
@@ -740,7 +852,54 @@ let rptSignatureHelper = {
                 }
             }
         }
-    }
+    },
+    _changeSignType: function(dom, signature_name, chkStr) {
+        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);
+                    }
+                } else {
+                    let idx = roleRel.sign_output.indexOf(chkStr);
+                    if (idx >= 0) {
+                        roleRel.sign_output.splice(idx, 1);
+                    }
+                }
+                break;
+            }
+        }
+    },
+    _resetStampPos: function(orgCell, targetCell, controls) {
+        const ctrl = controls[orgCell.control];
+        let pLeft = orgCell.area.Left, pTop = orgCell.area.Top;
+        switch(ctrl[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_HORIZON]]) {
+            case JV.OUTPUT_ALIGN.H[JV.H_ALIGN_IDX_LEFT]: 
+                pLeft = orgCell.area.Left;
+            break;
+            case JV.OUTPUT_ALIGN.H[JV.H_ALIGN_IDX_CENTER]: 
+                pLeft = (orgCell.area.Left + orgCell.area.Right - STD_STAMP_SIZE_WIDTH) / 2 ;
+            break;
+            case JV.OUTPUT_ALIGN.H[JV.H_ALIGN_IDX_RIGHT]: 
+                pLeft = orgCell.area.Right - STD_STAMP_SIZE_WIDTH;
+            break;
+        }
+        switch(ctrl[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]]) {
+            case JV.OUTPUT_ALIGN.H[JV.V_ALIGN_IDX_TOP]: 
+                pTop = orgCell.area.Top;
+            break;
+            case JV.OUTPUT_ALIGN.H[JV.V_ALIGN_IDX_CENTER]: 
+                pTop = (orgCell.area.Top + orgCell.area.Bottom - STD_STAMP_SIZE_HEIGHT) / 2 ;
+            break;
+            case JV.OUTPUT_ALIGN.H[JV.V_ALIGN_IDX_BOTTOM]: 
+                pTop = orgCell.area.Bottom - STD_STAMP_SIZE_HEIGHT;
+            break;
+        }
+        targetCell.area.Left = pLeft;
+        targetCell.area.Top = pTop;
+        targetCell.area.Right = pLeft + STD_STAMP_SIZE_WIDTH;
+        targetCell.area.Bottom = pTop + STD_STAMP_SIZE_HEIGHT;
+    },
 }
 
 function _getSignDateByAllScenarios(userAccId) {
@@ -789,41 +948,223 @@ function _getSignDateDftName() {
     return '    年  月  日';
 }
 
-function resetTextSignature(pageData) {
+function _createDummyCell() {
+    const rst = {
+        font: 'Footer',
+        control: 'Footer',
+        style: 'Default_None',
+        Value: '',
+        area: { Left: 0, Right: 0, Top: 0, Bottom: 0 },
+    };
+    return rst;
+}
+
+function resetStampSignature(pageData, roleRelList) {
+    const dupPicPaths = [];
+    const _getMaxRect = function(page) {
+        const rect = [100000000, 100000000, 0, 0];
+        for (const cell of page.cells) {
+            if (rect[0] > cell.area.Left) {
+                rect[0] = cell.area.Left;
+            }
+            if (rect[1] > cell.area.Top) {
+                rect[1] = cell.area.Top;
+            }
+            if (rect[2] < cell.area.Right) {
+                rect[2] = cell.area.Right;
+            }
+            if (rect[3] < cell.area.Bottom) {
+                rect[3] = cell.area.Bottom;
+            }
+        }
+        return rect;
+    };
+
     for (const page of pageData.items) {
-        for (let sCell of page.signature_cells) {
-            let fitCell = null;
-            for (let idx = page.cells.length - 1; idx >= 0; idx--) {
-                const cell = page.cells[idx];
-                if (sCell.area.Left === cell.area.Left && sCell.area.Right === cell.area.Right && sCell.area.Top === cell.area.Top && sCell.area.Bottom === cell.area.Bottom) {
-                    fitCell = cell;
-                    break;
+        const maxRect = _getMaxRect(page);
+        if (page.signature_cells) {
+            const newStampCells = [];
+            for (let scIdx = 0; scIdx < page.signature_cells.length; scIdx++) {
+                const sCell = page.signature_cells[scIdx];
+                for (const role_rel of roleRelList) {
+                    if (sCell.signature_name === role_rel.signature_name) {
+                        if (Array.isArray(role_rel.sign_output) && role_rel.sign_output.length > 0) {
+                            for (const signType of role_rel.sign_output) {
+                                switch (signType) {
+                                    case COMPANY_SIGN_STR:
+                                    case PRIVATE_SIGN_STR:
+                                        // 创建一个新的cell
+                                        let stampPath = (signType === COMPANY_SIGN_STR) ? role_rel.company_stamp_path : role_rel.private_stamp_path;
+                                        stampPath = OSS_PATH + stampPath;
+                                        if (dupPicPaths.indexOf(stampPath) < 0) {
+                                            dupPicPaths.push(stampPath);
+                                        }
+                                        const newStampCell = {
+                                            signature_name: JV.SIGNATURE_NAME_DUMMY,
+                                            control: sCell.control,
+                                            style: sCell.style,
+                                            path: stampPath,
+                                            isStamp: true,
+                                            maxRect,
+                                            orgArea: sCell.area,
+                                            area: {Left: sCell.area.Left, Right: sCell.area.Right, Top: sCell.area.Top, Bottom: sCell.area.Bottom},
+                                        };
+                                        newStampCells.push(newStampCell);
+                                        break;
+                                    default: break;
+                                }
+                            }
+                        }
+                    }
                 }
             }
-            if (fitCell) {
-                fitCell.Value = '';
-                for (let role of ROLE_REL_LIST) {
-                    if (sCell.signature_name === role.signature_name) {
-                        fitCell.Value = role.user_name;
+            if (newStampCells.length > 0) {
+                page.signature_cells = page.signature_cells.concat(newStampCells);
+            }
+        }
+    }
+}
+
+function resetTextSignature(pageData) {
+    for (const page of pageData.items) {
+        for (let sCell of page.signature_cells) {
+            if (!sCell.isStamp) {
+                let fitCell = null;
+                for (let idx = page.cells.length - 1; idx >= 0; idx--) {
+                    const cell = page.cells[idx];
+                    if (sCell.area.Left === cell.area.Left && sCell.area.Right === cell.area.Right && sCell.area.Top === cell.area.Top && sCell.area.Bottom === cell.area.Bottom) {
+                        fitCell = cell;
                         break;
                     }
                 }
-            } else {
-                // 要创建新的文本签名cell
-                for (let role of ROLE_REL_LIST) {
-                    if (sCell.signature_name === role.signature_name) {
-                        const newCell = {
-                            font: 'Footer',
-                            control: sCell.control,
-                            style: sCell.style,
-                            Value: role.user_name,
-                            area: { Left: sCell.area.Left, Right: sCell.area.Right, Top: sCell.area.Top, Bottom: sCell.area.Bottom },
-                        };
-                        page.cells.push(newCell);
-                        break;
+                if (fitCell) {
+                    fitCell.Value = '';
+                    for (let role of ROLE_REL_LIST) {
+                        if (sCell.signature_name === role.signature_name) {
+                            if (role.sign_output && role.sign_output.indexOf(NORMAL_SIGN_STR) >= 0) {
+                                fitCell.Value = role.user_name;
+                            } else {
+                                fitCell.Value = '';
+                            }
+                            break;
+                        }
+                    }
+                } else {
+                    // 要创建新的文本签名cell
+                    for (let role of ROLE_REL_LIST) {
+                        if (sCell.signature_name === role.signature_name && role.sign_output && role.sign_output.indexOf(NORMAL_SIGN_STR) >= 0) {
+                            const newCell = {
+                                font: 'Footer',
+                                control: sCell.control,
+                                style: sCell.style,
+                                Value: role.user_name,
+                                area: { Left: sCell.area.Left, Right: sCell.area.Right, Top: sCell.area.Top, Bottom: sCell.area.Bottom },
+                            };
+                            page.cells.push(newCell);
+                            break;
+                        }
                     }
                 }
             }
         }
     }
-}
+}
+
+function _resetStampArea(ctrl, stampCell, roleRelItem) {
+    let pLeft = stampCell.orgArea.Left,
+        pTop = stampCell.orgArea.Top;
+    if (roleRelItem.stampFeature !== 'not found!') {
+        let std_stamp_size_width = STD_STAMP_SIZE_WIDTH, std_stamp_size_height = STD_STAMP_SIZE_HEIGHT;
+        let widthRate = 1, heightRate = 1;
+        if (roleRelItem.stampFeature) {
+            std_stamp_size_width = parseFloat(roleRelItem.stampFeature.ImageWidth.value);
+            std_stamp_size_height = parseFloat(roleRelItem.stampFeature.ImageHeight.value);
+            if (roleRelItem.stampFeature.ImageWidth.value !== roleRelItem.stampFeature.ImageHeight.value) {
+                //设置比例
+                if (std_stamp_size_width > std_stamp_size_height) {
+                    heightRate = std_stamp_size_height / std_stamp_size_width;
+                } else {
+                    widthRate = std_stamp_size_width / std_stamp_size_height;
+                }
+            }
+            if (std_stamp_size_width > STD_STAMP_SIZE_WIDTH || std_stamp_size_height > STD_STAMP_SIZE_HEIGHT) {
+                if (widthRate === 1) {
+                    std_stamp_size_width = Math.min(std_stamp_size_width, STD_STAMP_SIZE_WIDTH);
+                    std_stamp_size_height = std_stamp_size_width * heightRate;
+                } else {
+                    std_stamp_size_height = Math.min(std_stamp_size_height, STD_STAMP_SIZE_HEIGHT);
+                    std_stamp_size_width = std_stamp_size_height * widthRate;
+                }
+            }
+        }
+        switch (ctrl[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_HORIZON]]) {
+            case JV.OUTPUT_ALIGN.H[JV.H_ALIGN_IDX_LEFT]:
+                pLeft = stampCell.orgArea.Left;
+                break;
+            case JV.OUTPUT_ALIGN.H[JV.H_ALIGN_IDX_CENTER]:
+                pLeft = (stampCell.orgArea.Left + stampCell.orgArea.Right - std_stamp_size_width) / 2;
+                break;
+            case JV.OUTPUT_ALIGN.H[JV.H_ALIGN_IDX_RIGHT]:
+                pLeft = stampCell.orgArea.Right - std_stamp_size_width;
+                break;
+            default:break;
+        }
+        switch (ctrl[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]]) {
+            case JV.OUTPUT_ALIGN.H[JV.V_ALIGN_IDX_TOP]:
+                pTop = stampCell.orgArea.Top;
+                break;
+            case JV.OUTPUT_ALIGN.H[JV.V_ALIGN_IDX_CENTER]:
+                pTop = (stampCell.orgArea.Top + stampCell.orgArea.Bottom - std_stamp_size_height) / 2;
+                break;
+            case JV.OUTPUT_ALIGN.H[JV.V_ALIGN_IDX_BOTTOM]:
+                pTop = stampCell.orgArea.Bottom - std_stamp_size_height;
+                break;
+            default:break;
+        }
+        stampCell.area.Left = pLeft;
+        stampCell.area.Top = pTop;
+        stampCell.area.Right = pLeft + std_stamp_size_width;
+        stampCell.area.Bottom = pTop + std_stamp_size_height;
+        // 最后一步,如超过报表范围,则要调整坐标
+        const maxRect = stampCell.maxRect;
+        if (stampCell.area.Left < maxRect[0]) {
+            const width = maxRect[0] - stampCell.area.Left;
+            stampCell.area.Left += width;
+            stampCell.area.Right += width;
+        }
+        if (stampCell.area.Top < maxRect[1]) {
+            const height = maxRect[1] - stampCell.area.Top;
+            stampCell.area.Top += height;
+            stampCell.area.Bottom += height;
+        }
+        if (stampCell.area.Right > maxRect[2]) {
+            const width = maxRect[2] - stampCell.area.Right; // 负
+            stampCell.area.Left += width;
+            stampCell.area.Right += width;
+        }
+        if (stampCell.area.Bottom > maxRect[3]) {
+            const height = maxRect[3] - stampCell.area.Bottom;
+            stampCell.area.Top += height;
+            stampCell.area.Bottom += height;
+        }
+    }
+}
+
+function getHttpBlobText(url) {
+    return new Promise(resolve => {
+        const xhr = new XMLHttpRequest();
+
+        // let fullUrl = url + '?x-oss-process=image/info';
+        xhr.open('GET', url, true);
+        xhr.responseType = 'json';
+        xhr.onload = () => {
+            if (xhr.status === 200) {
+                resolve(xhr.response);
+            } else {
+                resolve('not found!');
+            }
+        };
+
+        xhr.send();
+    });
+}

+ 24 - 22
app/reports/rpt_component/helper/jpc_helper_common.js

@@ -4,7 +4,7 @@ const JV = require('../jpc_value_define');
 
 const JpcCommonHelper = {
     commonConstant: {},
-    getResultByID: function(KeyID, collectionList) {
+    getResultByID(KeyID, collectionList) {
         let rst = null;
         if (KeyID) {
             for (let i = 0; i < collectionList.length; i++) {
@@ -22,38 +22,38 @@ const JpcCommonHelper = {
         }
         return rst;
     },
-    getFont: function(fontName, dftFonts, rptTpl) {
+    getFont(fontName, dftFonts, rptTpl) {
         const me = this;
         const list = [];
         if (rptTpl) list.push(rptTpl[JV.NODE_FONT_COLLECTION]);
         list.push(dftFonts);
         return me.getResultByID(fontName, list);
     },
-    getStyle: function(styleName, dftStyles, rptTpl) {
+    getStyle(styleName, dftStyles, rptTpl) {
         const me = this;
         const list = [];
         if (rptTpl) list.push(rptTpl[JV.NODE_STYLE_COLLECTION]);
         list.push(dftStyles);
         return me.getResultByID(styleName, list);
     },
-    getControl: function(controlName, dftControls, rptTpl) {
+    getControl(controlName, dftControls, rptTpl) {
         const me = this;
         const list = [];
         if (rptTpl) list.push(rptTpl[JV.NODE_CONTROL_COLLECTION]);
         list.push(dftControls);
         return me.getResultByID(controlName, list);
     },
-    getLayoutAlignment: function(alignStr) {
+    getLayoutAlignment(alignStr) {
         let rst = JV.LAYOUT.indexOf(alignStr);
         if (rst < 0) rst = JV.LAYOUT_FULFILL;
         return rst;
     },
-    getPosCalculationType: function (typeStr) {
+    getPosCalculationType(typeStr) {
         let rst = JV.CAL_TYPE.indexOf(typeStr);
         if (rst < 0) rst = JV.CAL_TYPE_ABSTRACT;
         return rst;
     },
-    getBoolean: function(bStr) {
+    getBoolean(bStr) {
         let rst = false;
         if (bStr !== null && bStr !== undefined) {
             const valType = typeof bStr;
@@ -68,11 +68,11 @@ const JpcCommonHelper = {
         }
         return rst;
     },
-    getScreenDPI: function() {
+    getScreenDPI() {
         const me = this;
         let arrDPI = [];
         if (!me.commonConstant.resolution) {
-            arrDPI = [96,96];
+            arrDPI = [96, 96];
             // arrDPI = [100,100];
             me.commonConstant.resolution = arrDPI;
         } else {
@@ -80,11 +80,11 @@ const JpcCommonHelper = {
         }
         return arrDPI;
     },
-    getUnitFactor: function(rptTpl) {
+    getUnitFactor(rptTpl) {
         const me = this;
         return me.translateUnit(rptTpl[JV.NODE_MAIN_INFO][JV.PROP_UNITS]);
     },
-    translateUnit: function(unitStr) {
+    translateUnit(unitStr) {
         const me = this;
         let rst = 1.0;
         if (unitStr) {
@@ -99,7 +99,7 @@ const JpcCommonHelper = {
         }
         return rst;
     },
-    getPageSize: function(rptTpl) {
+    getPageSize(rptTpl) {
         let size = null;
         const sizeStr = rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE];
         const sizeIdx = JV.PAGES_SIZE_STR.indexOf(sizeStr);
@@ -112,14 +112,14 @@ const JpcCommonHelper = {
         }
         const page_orientation = rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_ORIENTATION];
         if (page_orientation === JV.ORIENTATION_LANDSCAPE || page_orientation === JV.ORIENTATION_LANDSCAPE_CHN) {
-            //swap x,y
+            // swap x,y
             const tmp = size[0];
             size[0] = size[1];
             size[1] = tmp;
         }
         return size;
     },
-    getReportArea: function(rptTpl, unitFactor) {
+    getReportArea(rptTpl, unitFactor) {
         const me = this;
         const resolution = me.getScreenDPI();
         const rst = [];
@@ -132,7 +132,7 @@ const JpcCommonHelper = {
         rst.push(size[1] - unitFactor * rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS][JV.PROP_BOTTOM]);
         return rst;
     },
-    getSegIdxByPageIdx: function(page, page_seg_map) {
+    getSegIdxByPageIdx(page, page_seg_map) {
         let rst = -1;
         for (let pIdx = 0; pIdx < page_seg_map.length; pIdx++) {
             if (page_seg_map[pIdx][0] === page) {
@@ -142,7 +142,7 @@ const JpcCommonHelper = {
         }
         return rst;
     },
-    getSegPageIdxByPage: function(page, page_seg_map) {
+    getSegPageIdxByPage(page, page_seg_map) {
         let rst = 0;
         for (let pIdx = 0; pIdx < page_seg_map.length; pIdx++) {
             if (page_seg_map[pIdx][0] === page) {
@@ -159,7 +159,7 @@ const JpcCommonHelper = {
         }
         return rst;
     },
-    getStringLinesInArea: function(area, strVal, chnW, otherW) {
+    getStringLinesInArea(area, strVal, chnW, otherW) {
         // 备注: 因后台的pdf kit判断字符串长度与前端的不一样,需要做些调整,不一次性地判断字符串长度。
         //      分2种字符:中文与非中文,按照各种字符的数量分别乘以相关一个字符的宽度再累计。
         //      另判断行数还不能直接用总长度除以宽度来计算,因每一行都会有不同的余量,所以得一行行走过来判断。
@@ -169,9 +169,10 @@ const JpcCommonHelper = {
             let txtWidth = 0;
             let currentW = 0;
             for (let sIdx = 0; sIdx < strVal.length; sIdx++) {
-                currentW = (strVal.charCodeAt(sIdx) > 127)?chnW:otherW;
+                currentW = (strVal.charCodeAt(sIdx) > 127) ? chnW : otherW;
                 txtWidth += currentW;
-                if (txtWidth > areaWidth) {
+                if (txtWidth > (areaWidth - 4)) {
+                    // 减4个像素是考虑到导出excel的情况
                     rst++;
                     txtWidth = currentW;
                 }
@@ -184,7 +185,7 @@ const JpcCommonHelper = {
         return rst;
         // 备注: 其实是想用canvas的,但node canvas装起来麻烦,暂时用PDF Kit来顶用一下,以后有更好的再换
     },
-    splitString: function(area, strVal, chnW, otherW) {
+    splitString(area, strVal, chnW, otherW) {
         const rst = [];
         if (strVal) {
             const areaWidth = area[JV.PROP_RIGHT] - area[JV.PROP_LEFT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT] - 1;
@@ -194,7 +195,8 @@ const JpcCommonHelper = {
             for (let sIdx = 0; sIdx < strVal.length; sIdx++) {
                 currentW = (strVal.charCodeAt(sIdx) > 127) ? chnW : otherW;
                 txtWidth += currentW;
-                if (txtWidth > areaWidth) {
+                if (txtWidth > (areaWidth - 4)) {
+                    // 减4个像素是考虑到导出excel的情况
                     if (preSIdx < sIdx) {
                         rst.push(strVal.substr(preSIdx, sIdx - preSIdx));
                         preSIdx = sIdx;
@@ -214,4 +216,4 @@ const JpcCommonHelper = {
     },
 };
 
-module.exports = JpcCommonHelper;
+module.exports = JpcCommonHelper;

+ 3 - 3
app/reports/rpt_component/helper/jpc_helper_common_output.js

@@ -10,7 +10,7 @@ const REG5 = new RegExp('\t', 'g');
 const OFFSET_FLOAT = 0.0000000001;
 
 const JpcCommonOutputHelper = {
-    createCommonOutputWithoutDecorate: function(node, value, forceCombine) {
+    createCommonOutputWithoutDecorate(node, value, forceCombine) {
         const me = this;
         const rst = {};
         // 1. font/style/control
@@ -36,7 +36,7 @@ const JpcCommonOutputHelper = {
         }
         return rst;
     },
-    createCommonOutput: function(node, value, controls) {
+    createCommonOutput(node, value, controls) {
         const me = this;
         const rst = {};
         // 1. font/style/control
@@ -56,7 +56,7 @@ const JpcCommonOutputHelper = {
         }
         return rst;
     },
-    formatCell: function(formatStr, rstCell) {
+    formatCell(formatStr, rstCell) {
         if (formatStr) {
             const rstCellFloatVal = parseFloat(rstCell[JV.PROP_VALUE]);
             if (!(isNaN(rstCellFloatVal))) {

+ 9 - 9
app/reports/rpt_component/helper/jpc_helper_field.js

@@ -8,7 +8,7 @@ const REG3 = new RegExp('\n', 'g');
 const REG4 = new RegExp('\r', 'g');
 
 const JpcFieldHelper = {
-    getValue: function(dataField, valueIdx) {
+    getValue(dataField, valueIdx) {
         let rst = '';
         if (dataField && (dataField.length > valueIdx) && (valueIdx >= 0)) {
             rst = dataField[valueIdx];
@@ -18,12 +18,12 @@ const JpcFieldHelper = {
         // }
         return rst;
     },
-    setValue: function(dataField, valueIdx, newValue) {
+    setValue(dataField, valueIdx, newValue) {
         if (dataField && (dataField.length > valueIdx) && (valueIdx >= 0)) {
             dataField[valueIdx] = newValue;
         }
     },
-    resetSumFormat: function(ref_tab_fields, sum_tab_field) {
+    resetSumFormat(ref_tab_fields, sum_tab_field) {
         let rst = false;
         if (ref_tab_fields && ref_tab_fields.length > 0) {
             for (const tab_field of ref_tab_fields) {
@@ -38,7 +38,7 @@ const JpcFieldHelper = {
         }
         return rst;
     },
-    resetFormat: function(tab_field, map_field, customizeCfg) {
+    resetFormat(tab_field, map_field, customizeCfg) {
         let rst = false;
         if (map_field && map_field[JV.PROP_PRECISION] && map_field[JV.PROP_PRECISION].type === 'fixed') {
             const formatStrs = ['#'];
@@ -63,14 +63,14 @@ const JpcFieldHelper = {
         }
         return rst;
     },
-    resetFlexibleFormat: function(tab_field, ref_field_data, flexiblePrecisionRefObj, valueIdx, customizeCfg) {
+    resetFlexibleFormat(tab_field, ref_field_data, flexiblePrecisionRefObj, valueIdx, customizeCfg) {
         let precisionAmt = 2;
         if (ref_field_data && flexiblePrecisionRefObj && valueIdx >= 0) {
             precisionAmt = flexiblePrecisionRefObj['refUnit_' + ref_field_data[valueIdx]];
             if (precisionAmt === null || precisionAmt === undefined) precisionAmt = flexiblePrecisionRefObj['refUnit_其他未列单位'];
         }
         const formatStrs = ['#.'];
-        const ffStr = (customizeCfg && customizeCfg.fillZero)?'0':'#';
+        const ffStr = (customizeCfg && customizeCfg.fillZero) ? '0' : '#';
         for (let idx = 0; idx < precisionAmt; idx++) {
             formatStrs.push(ffStr);
         }
@@ -80,7 +80,7 @@ const JpcFieldHelper = {
             tab_field[JV.PROP_FORMAT] = formatStrs.join('');
         }
     },
-    decorateValue: function(cell, controls) {
+    decorateValue(cell, controls) {
         if (controls) {
             const showZero = controls[cell[JV.PROP_CONTROL]][JV.PROP_SHOW_ZERO];
             if (showZero && showZero === 'F') {
@@ -100,7 +100,7 @@ const JpcFieldHelper = {
             }
         }
     },
-    findAndPutDataFieldIdx: function (rptTpl, tab_fields, rstFields, rstFieldsIdx, isEx) {
+    findAndPutDataFieldIdx(rptTpl, tab_fields, rstFields, rstFieldsIdx, isEx) {
         // 通过FieldID找到相关映射指标的位置IDX并记录下来,方便后续引用
         if (tab_fields) {
             const DTL_STR = isEx ? JV.NODE_DETAIL_FIELDS_EX : JV.NODE_DETAIL_FIELDS;
@@ -134,7 +134,7 @@ const JpcFieldHelper = {
             }
         }
     },
-    findAutoHeightFieldIdx: function(rptTpl, tab_fields, rstFieldsIdx, isEx) {
+    findAutoHeightFieldIdx(rptTpl, tab_fields, rstFieldsIdx, isEx) {
         if (tab_fields) {
             const DTL_STR = isEx ? JV.NODE_DETAIL_FIELDS_EX : JV.NODE_DETAIL_FIELDS;
             const detail_fields = rptTpl[JV.NODE_FIELD_MAP][DTL_STR];

+ 48 - 26
app/reports/rpt_component/helper/jpc_helper_flow_tab.js

@@ -1,16 +1,35 @@
-let JV = require('../jpc_value_define');
-let JpcCommonHelper = require('./jpc_helper_common');
+'use strict';
 
-let JpcFlowTabHelper = {
-    getMaxRowsPerPage: function(bands, rptTpl, isEx) {
+const JV = require('../jpc_value_define');
+const JpcCommonHelper = require('./jpc_helper_common');
+
+const JpcFlowTabHelper = {
+    getRowHeight(bands, rptTpl, isEx) {
+        let rst = 0;
+        const FLOW_INFO_STR = (!isEx) ? JV.NODE_FLOW_INFO : JV.NODE_FLOW_INFO_EX;
+        const tab = rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT];
+        const band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let maxFieldMeasure = 1.0;
+            if (JV.CAL_TYPE_ABSTRACT === JpcCommonHelper.getPosCalculationType(tab[JV.PROP_CALCULATION])) {
+                const unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+                maxFieldMeasure = 1.0 * rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT][JV.PROP_CMN_HEIGHT] * unitFactor;
+            } else {
+                maxFieldMeasure = (band.Bottom - band.Top) * rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT][JV.PROP_CMN_HEIGHT] / JV.HUNDRED_PERCENT;
+            }
+            rst = maxFieldMeasure;
+        }
+        return rst;
+    },
+    getMaxRowsPerPage(bands, rptTpl, isEx) {
         let rst = 1;
-        let FLOW_INFO_STR = (!isEx)?JV.NODE_FLOW_INFO:JV.NODE_FLOW_INFO_EX;
-        let tab = rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT];
-        let band = bands[tab[JV.PROP_BAND_NAME]];
+        const FLOW_INFO_STR = (!isEx) ? JV.NODE_FLOW_INFO : JV.NODE_FLOW_INFO_EX;
+        const tab = rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT];
+        const band = bands[tab[JV.PROP_BAND_NAME]];
         if (band) {
             let maxFieldMeasure = 1.0;
             if (JV.CAL_TYPE_ABSTRACT === JpcCommonHelper.getPosCalculationType(tab[JV.PROP_CALCULATION])) {
-                let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+                const unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
                 maxFieldMeasure = 1.0 * rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT][JV.PROP_CMN_HEIGHT] * unitFactor;
             } else {
                 maxFieldMeasure = (band.Bottom - band.Top) * rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT][JV.PROP_CMN_HEIGHT] / JV.HUNDRED_PERCENT;
@@ -19,15 +38,15 @@ let JpcFlowTabHelper = {
         }
         return rst;
     },
-    getActualContentAreaHeight: function(bands, rptTpl, segments, rowAmt, page, isEx) {
+    getActualContentAreaHeight(bands, rptTpl, segments, rowAmt, page, isEx) {
         let rst = 1;
-        let FLOW_INFO_STR = (!isEx)?JV.NODE_FLOW_INFO:JV.NODE_FLOW_INFO_EX;
-        let tab = rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT];
-        let band = bands[tab[JV.PROP_BAND_NAME]];
+        const FLOW_INFO_STR = (!isEx) ? JV.NODE_FLOW_INFO : JV.NODE_FLOW_INFO_EX;
+        const tab = rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT];
+        const band = bands[tab[JV.PROP_BAND_NAME]];
         if (band) {
             let maxFieldMeasure = 1.0;
             if (JV.CAL_TYPE_ABSTRACT === JpcCommonHelper.getPosCalculationType(tab[JV.PROP_CALCULATION])) {
-                let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+                const unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
                 maxFieldMeasure = 1.0 * rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT][JV.PROP_CMN_HEIGHT] * unitFactor;
             } else {
                 maxFieldMeasure = (band.Bottom - band.Top) * rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT][JV.PROP_CMN_HEIGHT] / JV.HUNDRED_PERCENT;
@@ -38,27 +57,30 @@ let JpcFlowTabHelper = {
         }
         return rst;
     },
-    chkSegEnd: function (bands, rptTpl, segmentLength, preRec, pageRecAmt, isEx) {
-        let me = this, rst = false;
-        let remainAmt = preRec + pageRecAmt - segmentLength;
+    chkSegEnd(bands, rptTpl, segmentLength, preRec, pageRecAmt, isEx) {
+        let me = this,
+            rst = false;
+        const remainAmt = preRec + pageRecAmt - segmentLength;
         rst = me.hasEnoughSpace(rptTpl, bands, remainAmt, isEx);
         return rst;
     },
-    hasEnoughSpace: function (rptTpl, bands, remainAmt, isEx) {
+    hasEnoughSpace(rptTpl, bands, remainAmt, isEx) {
         if (remainAmt < 0) return false;
-        let rst = true, measurement = 1.0, douDiffForCompare = 0.00001;
-        let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
-        let FLOW_INFO_STR = (!isEx)?JV.NODE_FLOW_INFO:JV.NODE_FLOW_INFO_EX;
-        let tab = rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT];
-        let band = bands[tab[JV.PROP_BAND_NAME]];
+        let rst = true,
+            measurement = 1.0,
+            douDiffForCompare = 0.00001;
+        const unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+        const FLOW_INFO_STR = (!isEx) ? JV.NODE_FLOW_INFO : JV.NODE_FLOW_INFO_EX;
+        const tab = rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT];
+        const band = bands[tab[JV.PROP_BAND_NAME]];
         if (band !== null && band !== undefined) {
             measurement = 1.0 * tab[JV.PROP_CMN_HEIGHT] * unitFactor;
-            let spareHeight = measurement * remainAmt;
-            let douH = band.Bottom - band.Top;
+            const spareHeight = measurement * remainAmt;
+            const douH = band.Bottom - band.Top;
             rst = ((spareHeight >= douH) || ((spareHeight - douH) <= douDiffForCompare));
         }
         return rst;
-    }
+    },
 };
 
-module.exports = JpcFlowTabHelper;
+module.exports = JpcFlowTabHelper;

+ 489 - 489
app/reports/rpt_component/helper/jpc_helper_font_width.js

@@ -5,503 +5,503 @@
  */
 
 const fontWidthMap = {
-    '宋体': {
-        '宽': {
-            '_6': 6,
-            '_7': 7,
-            '_8': 8,
-            '_9': 9,
-            '_10': 10,
-            '_11': 11,
-            '_12': 12,
-            '_13': 13,
-            '_14': 14,
-            '_15': 15,
-            '_16': 16,
-            '_17': 17,
-            '_18': 18,
-            '_19': 19,
-            '_20': 20,
-            '_21': 21,
-            '_22': 22,
-            '_23': 23,
-            '_24': 24,
-            '_25': 25,
-            '_26': 26,
-            '_27': 27,
-            '_28': 28,
-            '_29': 29,
-            '_30': 30,
-            '_31': 31,
-            '_32': 32,
-            '_33': 33,
-            '_34': 34,
-            '_35': 35,
-            '_36': 36,
-            '_37': 37,
-            '_38': 38,
-            '_39': 39,
-            '_40': 40,
-            '_41': 41,
-            '_42': 42,
-            '_43': 43,
-            '_44': 44,
-            '_45': 45,
-            '_46': 46,
-            '_47': 47,
-            '_48': 48,
-            '_49': 49,
-            '_50': 50,
-            '_51': 51,
-            '_52': 52,
-            '_53': 53,
-            '_54': 54,
-            '_55': 55,
-            '_56': 56,
-            '_57': 57,
-            '_58': 58,
-            '_59': 59,
-            '_60': 60,
-            '_61': 61,
-            '_62': 62,
-            '_63': 63,
-            '_64': 64
+    宋体: {
+        宽: {
+            _6: 6,
+            _7: 7,
+            _8: 8,
+            _9: 9,
+            _10: 10,
+            _11: 11,
+            _12: 12,
+            _13: 13,
+            _14: 14,
+            _15: 15,
+            _16: 16,
+            _17: 17,
+            _18: 18,
+            _19: 19,
+            _20: 20,
+            _21: 21,
+            _22: 22,
+            _23: 23,
+            _24: 24,
+            _25: 25,
+            _26: 26,
+            _27: 27,
+            _28: 28,
+            _29: 29,
+            _30: 30,
+            _31: 31,
+            _32: 32,
+            _33: 33,
+            _34: 34,
+            _35: 35,
+            _36: 36,
+            _37: 37,
+            _38: 38,
+            _39: 39,
+            _40: 40,
+            _41: 41,
+            _42: 42,
+            _43: 43,
+            _44: 44,
+            _45: 45,
+            _46: 46,
+            _47: 47,
+            _48: 48,
+            _49: 49,
+            _50: 50,
+            _51: 51,
+            _52: 52,
+            _53: 53,
+            _54: 54,
+            _55: 55,
+            _56: 56,
+            _57: 57,
+            _58: 58,
+            _59: 59,
+            _60: 60,
+            _61: 61,
+            _62: 62,
+            _63: 63,
+            _64: 64,
+        },
+        窄: {
+            _6: 3,
+            _7: 3.5,
+            _8: 4,
+            _9: 4.5,
+            _10: 5,
+            _11: 5.5,
+            _12: 6,
+            _13: 6.5,
+            _14: 7,
+            _15: 7.5,
+            _16: 8,
+            _17: 8.5,
+            _18: 9,
+            _19: 9.5,
+            _20: 10,
+            _21: 10.5,
+            _22: 11,
+            _23: 11.5,
+            _24: 12,
+            _25: 12.5,
+            _26: 13,
+            _27: 13.5,
+            _28: 14,
+            _29: 14.5,
+            _30: 15,
+            _31: 15.5,
+            _32: 16,
+            _33: 16.5,
+            _34: 17,
+            _35: 17.5,
+            _36: 18,
+            _37: 18.5,
+            _38: 19,
+            _39: 19.5,
+            _40: 20,
+            _41: 20.5,
+            _42: 21,
+            _43: 21.5,
+            _44: 22,
+            _45: 22.5,
+            _46: 23,
+            _47: 23.5,
+            _48: 24,
+            _49: 24.5,
+            _50: 25,
+            _51: 25.5,
+            _52: 26,
+            _53: 26.5,
+            _54: 27,
+            _55: 27.5,
+            _56: 28,
+            _57: 28.5,
+            _58: 29,
+            _59: 29.5,
+            _60: 30,
+            _61: 30.5,
+            _62: 31,
+            _63: 31.5,
+            _64: 32,
         },
-        '窄': {
-            '_6': 3,
-            '_7': 3.5,
-            '_8': 4,
-            '_9': 4.5,
-            '_10': 5,
-            '_11': 5.5,
-            '_12': 6,
-            '_13': 6.5,
-            '_14': 7,
-            '_15': 7.5,
-            '_16': 8,
-            '_17': 8.5,
-            '_18': 9,
-            '_19': 9.5,
-            '_20': 10,
-            '_21': 10.5,
-            '_22': 11,
-            '_23': 11.5,
-            '_24': 12,
-            '_25': 12.5,
-            '_26': 13,
-            '_27': 13.5,
-            '_28': 14,
-            '_29': 14.5,
-            '_30': 15,
-            '_31': 15.5,
-            '_32': 16,
-            '_33': 16.5,
-            '_34': 17,
-            '_35': 17.5,
-            '_36': 18,
-            '_37': 18.5,
-            '_38': 19,
-            '_39': 19.5,
-            '_40': 20,
-            '_41': 20.5,
-            '_42': 21,
-            '_43': 21.5,
-            '_44': 22,
-            '_45': 22.5,
-            '_46': 23,
-            '_47': 23.5,
-            '_48': 24,
-            '_49': 24.5,
-            '_50': 25,
-            '_51': 25.5,
-            '_52': 26,
-            '_53': 26.5,
-            '_54': 27,
-            '_55': 27.5,
-            '_56': 28,
-            '_57': 28.5,
-            '_58': 29,
-            '_59': 29.5,
-            '_60': 30,
-            '_61': 30.5,
-            '_62': 31,
-            '_63': 31.5,
-            '_64': 32
-        }
     },
-    '黑体': {
-        '宽': {
-            '_6': 6,
-            '_7': 7,
-            '_8': 8,
-            '_9': 9,
-            '_10': 10,
-            '_11': 11,
-            '_12': 12,
-            '_13': 13,
-            '_14': 14,
-            '_15': 15,
-            '_16': 16,
-            '_17': 17,
-            '_18': 18,
-            '_19': 19,
-            '_20': 20,
-            '_21': 21,
-            '_22': 22,
-            '_23': 23,
-            '_24': 24,
-            '_25': 25,
-            '_26': 26,
-            '_27': 27,
-            '_28': 28,
-            '_29': 29,
-            '_30': 30,
-            '_31': 31,
-            '_32': 32,
-            '_33': 33,
-            '_34': 34,
-            '_35': 35,
-            '_36': 36,
-            '_37': 37,
-            '_38': 38,
-            '_39': 39,
-            '_40': 40,
-            '_41': 41,
-            '_42': 42,
-            '_43': 43,
-            '_44': 44,
-            '_45': 45,
-            '_46': 46,
-            '_47': 47,
-            '_48': 48,
-            '_49': 49,
-            '_50': 50,
-            '_51': 51,
-            '_52': 52,
-            '_53': 53,
-            '_54': 54,
-            '_55': 55,
-            '_56': 56,
-            '_57': 57,
-            '_58': 58,
-            '_59': 59,
-            '_60': 60,
-            '_61': 61,
-            '_62': 62,
-            '_63': 63,
-            '_64': 64
+    黑体: {
+        宽: {
+            _6: 6,
+            _7: 7,
+            _8: 8,
+            _9: 9,
+            _10: 10,
+            _11: 11,
+            _12: 12,
+            _13: 13,
+            _14: 14,
+            _15: 15,
+            _16: 16,
+            _17: 17,
+            _18: 18,
+            _19: 19,
+            _20: 20,
+            _21: 21,
+            _22: 22,
+            _23: 23,
+            _24: 24,
+            _25: 25,
+            _26: 26,
+            _27: 27,
+            _28: 28,
+            _29: 29,
+            _30: 30,
+            _31: 31,
+            _32: 32,
+            _33: 33,
+            _34: 34,
+            _35: 35,
+            _36: 36,
+            _37: 37,
+            _38: 38,
+            _39: 39,
+            _40: 40,
+            _41: 41,
+            _42: 42,
+            _43: 43,
+            _44: 44,
+            _45: 45,
+            _46: 46,
+            _47: 47,
+            _48: 48,
+            _49: 49,
+            _50: 50,
+            _51: 51,
+            _52: 52,
+            _53: 53,
+            _54: 54,
+            _55: 55,
+            _56: 56,
+            _57: 57,
+            _58: 58,
+            _59: 59,
+            _60: 60,
+            _61: 61,
+            _62: 62,
+            _63: 63,
+            _64: 64,
+        },
+        窄: {
+            _6: 3,
+            _7: 3.5,
+            _8: 4,
+            _9: 4.5,
+            _10: 5,
+            _11: 5.5,
+            _12: 6,
+            _13: 6.5,
+            _14: 7,
+            _15: 7.5,
+            _16: 8,
+            _17: 8.5,
+            _18: 9,
+            _19: 9.5,
+            _20: 10,
+            _21: 10.5,
+            _22: 11,
+            _23: 11.5,
+            _24: 12,
+            _25: 12.5,
+            _26: 13,
+            _27: 13.5,
+            _28: 14,
+            _29: 14.5,
+            _30: 15,
+            _31: 15.5,
+            _32: 16,
+            _33: 16.5,
+            _34: 17,
+            _35: 17.5,
+            _36: 18,
+            _37: 18.5,
+            _38: 19,
+            _39: 19.5,
+            _40: 20,
+            _41: 20.5,
+            _42: 21,
+            _43: 21.5,
+            _44: 22,
+            _45: 22.5,
+            _46: 23,
+            _47: 23.5,
+            _48: 24,
+            _49: 24.5,
+            _50: 25,
+            _51: 25.5,
+            _52: 26,
+            _53: 26.5,
+            _54: 27,
+            _55: 27.5,
+            _56: 28,
+            _57: 28.5,
+            _58: 29,
+            _59: 29.5,
+            _60: 30,
+            _61: 30.5,
+            _62: 31,
+            _63: 31.5,
+            _64: 32,
         },
-        '窄': {
-            '_6': 3,
-            '_7': 3.5,
-            '_8': 4,
-            '_9': 4.5,
-            '_10': 5,
-            '_11': 5.5,
-            '_12': 6,
-            '_13': 6.5,
-            '_14': 7,
-            '_15': 7.5,
-            '_16': 8,
-            '_17': 8.5,
-            '_18': 9,
-            '_19': 9.5,
-            '_20': 10,
-            '_21': 10.5,
-            '_22': 11,
-            '_23': 11.5,
-            '_24': 12,
-            '_25': 12.5,
-            '_26': 13,
-            '_27': 13.5,
-            '_28': 14,
-            '_29': 14.5,
-            '_30': 15,
-            '_31': 15.5,
-            '_32': 16,
-            '_33': 16.5,
-            '_34': 17,
-            '_35': 17.5,
-            '_36': 18,
-            '_37': 18.5,
-            '_38': 19,
-            '_39': 19.5,
-            '_40': 20,
-            '_41': 20.5,
-            '_42': 21,
-            '_43': 21.5,
-            '_44': 22,
-            '_45': 22.5,
-            '_46': 23,
-            '_47': 23.5,
-            '_48': 24,
-            '_49': 24.5,
-            '_50': 25,
-            '_51': 25.5,
-            '_52': 26,
-            '_53': 26.5,
-            '_54': 27,
-            '_55': 27.5,
-            '_56': 28,
-            '_57': 28.5,
-            '_58': 29,
-            '_59': 29.5,
-            '_60': 30,
-            '_61': 30.5,
-            '_62': 31,
-            '_63': 31.5,
-            '_64': 32
-        }
     },
-    '楷体': {
-        '宽': {
-            '_6': 6,
-            '_7': 7,
-            '_8': 8,
-            '_9': 9,
-            '_10': 10,
-            '_11': 11,
-            '_12': 12,
-            '_13': 13,
-            '_14': 14,
-            '_15': 15,
-            '_16': 16,
-            '_17': 17,
-            '_18': 18,
-            '_19': 19,
-            '_20': 20,
-            '_21': 21,
-            '_22': 22,
-            '_23': 23,
-            '_24': 24,
-            '_25': 25,
-            '_26': 26,
-            '_27': 27,
-            '_28': 28,
-            '_29': 29,
-            '_30': 30,
-            '_31': 31,
-            '_32': 32,
-            '_33': 33,
-            '_34': 34,
-            '_35': 35,
-            '_36': 36,
-            '_37': 37,
-            '_38': 38,
-            '_39': 39,
-            '_40': 40,
-            '_41': 41,
-            '_42': 42,
-            '_43': 43,
-            '_44': 44,
-            '_45': 45,
-            '_46': 46,
-            '_47': 47,
-            '_48': 48,
-            '_49': 49,
-            '_50': 50,
-            '_51': 51,
-            '_52': 52,
-            '_53': 53,
-            '_54': 54,
-            '_55': 55,
-            '_56': 56,
-            '_57': 57,
-            '_58': 58,
-            '_59': 59,
-            '_60': 60,
-            '_61': 61,
-            '_62': 62,
-            '_63': 63,
-            '_64': 64
+    楷体: {
+        宽: {
+            _6: 6,
+            _7: 7,
+            _8: 8,
+            _9: 9,
+            _10: 10,
+            _11: 11,
+            _12: 12,
+            _13: 13,
+            _14: 14,
+            _15: 15,
+            _16: 16,
+            _17: 17,
+            _18: 18,
+            _19: 19,
+            _20: 20,
+            _21: 21,
+            _22: 22,
+            _23: 23,
+            _24: 24,
+            _25: 25,
+            _26: 26,
+            _27: 27,
+            _28: 28,
+            _29: 29,
+            _30: 30,
+            _31: 31,
+            _32: 32,
+            _33: 33,
+            _34: 34,
+            _35: 35,
+            _36: 36,
+            _37: 37,
+            _38: 38,
+            _39: 39,
+            _40: 40,
+            _41: 41,
+            _42: 42,
+            _43: 43,
+            _44: 44,
+            _45: 45,
+            _46: 46,
+            _47: 47,
+            _48: 48,
+            _49: 49,
+            _50: 50,
+            _51: 51,
+            _52: 52,
+            _53: 53,
+            _54: 54,
+            _55: 55,
+            _56: 56,
+            _57: 57,
+            _58: 58,
+            _59: 59,
+            _60: 60,
+            _61: 61,
+            _62: 62,
+            _63: 63,
+            _64: 64,
+        },
+        窄: {
+            _6: 3,
+            _7: 3.5,
+            _8: 4,
+            _9: 4.5,
+            _10: 5,
+            _11: 5.5,
+            _12: 6,
+            _13: 6.5,
+            _14: 7,
+            _15: 7.5,
+            _16: 8,
+            _17: 8.5,
+            _18: 9,
+            _19: 9.5,
+            _20: 10,
+            _21: 10.5,
+            _22: 11,
+            _23: 11.5,
+            _24: 12,
+            _25: 12.5,
+            _26: 13,
+            _27: 13.5,
+            _28: 14,
+            _29: 14.5,
+            _30: 15,
+            _31: 15.5,
+            _32: 16,
+            _33: 16.5,
+            _34: 17,
+            _35: 17.5,
+            _36: 18,
+            _37: 18.5,
+            _38: 19,
+            _39: 19.5,
+            _40: 20,
+            _41: 20.5,
+            _42: 21,
+            _43: 21.5,
+            _44: 22,
+            _45: 22.5,
+            _46: 23,
+            _47: 23.5,
+            _48: 24,
+            _49: 24.5,
+            _50: 25,
+            _51: 25.5,
+            _52: 26,
+            _53: 26.5,
+            _54: 27,
+            _55: 27.5,
+            _56: 28,
+            _57: 28.5,
+            _58: 29,
+            _59: 29.5,
+            _60: 30,
+            _61: 30.5,
+            _62: 31,
+            _63: 31.5,
+            _64: 32,
         },
-        '窄': {
-            '_6': 3,
-            '_7': 3.5,
-            '_8': 4,
-            '_9': 4.5,
-            '_10': 5,
-            '_11': 5.5,
-            '_12': 6,
-            '_13': 6.5,
-            '_14': 7,
-            '_15': 7.5,
-            '_16': 8,
-            '_17': 8.5,
-            '_18': 9,
-            '_19': 9.5,
-            '_20': 10,
-            '_21': 10.5,
-            '_22': 11,
-            '_23': 11.5,
-            '_24': 12,
-            '_25': 12.5,
-            '_26': 13,
-            '_27': 13.5,
-            '_28': 14,
-            '_29': 14.5,
-            '_30': 15,
-            '_31': 15.5,
-            '_32': 16,
-            '_33': 16.5,
-            '_34': 17,
-            '_35': 17.5,
-            '_36': 18,
-            '_37': 18.5,
-            '_38': 19,
-            '_39': 19.5,
-            '_40': 20,
-            '_41': 20.5,
-            '_42': 21,
-            '_43': 21.5,
-            '_44': 22,
-            '_45': 22.5,
-            '_46': 23,
-            '_47': 23.5,
-            '_48': 24,
-            '_49': 24.5,
-            '_50': 25,
-            '_51': 25.5,
-            '_52': 26,
-            '_53': 26.5,
-            '_54': 27,
-            '_55': 27.5,
-            '_56': 28,
-            '_57': 28.5,
-            '_58': 29,
-            '_59': 29.5,
-            '_60': 30,
-            '_61': 30.5,
-            '_62': 31,
-            '_63': 31.5,
-            '_64': 32
-        }
     },
-    'Arial': {
-        '宽': {
-            '_6': 6,
-            '_7': 7,
-            '_8': 8,
-            '_9': 9,
-            '_10': 10,
-            '_11': 11,
-            '_12': 12,
-            '_13': 13,
-            '_14': 14,
-            '_15': 15,
-            '_16': 16,
-            '_17': 17,
-            '_18': 18,
-            '_19': 19,
-            '_20': 20,
-            '_21': 21,
-            '_22': 22,
-            '_23': 23,
-            '_24': 24,
-            '_25': 25,
-            '_26': 26,
-            '_27': 27,
-            '_28': 28,
-            '_29': 29,
-            '_30': 30,
-            '_31': 31,
-            '_32': 32,
-            '_33': 33,
-            '_34': 34,
-            '_35': 35,
-            '_36': 36,
-            '_37': 37,
-            '_38': 38,
-            '_39': 39,
-            '_40': 40,
-            '_41': 41,
-            '_42': 42,
-            '_43': 43,
-            '_44': 44,
-            '_45': 45,
-            '_46': 46,
-            '_47': 47,
-            '_48': 48,
-            '_49': 49,
-            '_50': 50,
-            '_51': 51,
-            '_52': 52,
-            '_53': 53,
-            '_54': 54,
-            '_55': 55,
-            '_56': 56,
-            '_57': 57,
-            '_58': 58,
-            '_59': 59,
-            '_60': 60,
-            '_61': 61,
-            '_62': 62,
-            '_63': 63,
-            '_64': 64
+    Arial: {
+        宽: {
+            _6: 6,
+            _7: 7,
+            _8: 8,
+            _9: 9,
+            _10: 10,
+            _11: 11,
+            _12: 12,
+            _13: 13,
+            _14: 14,
+            _15: 15,
+            _16: 16,
+            _17: 17,
+            _18: 18,
+            _19: 19,
+            _20: 20,
+            _21: 21,
+            _22: 22,
+            _23: 23,
+            _24: 24,
+            _25: 25,
+            _26: 26,
+            _27: 27,
+            _28: 28,
+            _29: 29,
+            _30: 30,
+            _31: 31,
+            _32: 32,
+            _33: 33,
+            _34: 34,
+            _35: 35,
+            _36: 36,
+            _37: 37,
+            _38: 38,
+            _39: 39,
+            _40: 40,
+            _41: 41,
+            _42: 42,
+            _43: 43,
+            _44: 44,
+            _45: 45,
+            _46: 46,
+            _47: 47,
+            _48: 48,
+            _49: 49,
+            _50: 50,
+            _51: 51,
+            _52: 52,
+            _53: 53,
+            _54: 54,
+            _55: 55,
+            _56: 56,
+            _57: 57,
+            _58: 58,
+            _59: 59,
+            _60: 60,
+            _61: 61,
+            _62: 62,
+            _63: 63,
+            _64: 64,
+        },
+        窄: {
+            _6: 3.3399999141693115,
+            _7: 3.8899998664855957,
+            _8: 4.449999809265137,
+            _9: 5.009999752044678,
+            _10: 5.559999942779541,
+            _11: 6.119999885559082,
+            _12: 6.670000076293945,
+            _13: 7.230000019073486,
+            _14: 7.789999961853027,
+            _15: 8.34000015258789,
+            _16: 8.899999618530273,
+            _17: 9.449999809265136,
+            _18: 10.010000228881836,
+            _19: 10.569999694824218,
+            _20: 11.119999885559082,
+            _21: 11.679999351501464,
+            _22: 12.239999771118164,
+            _23: 12.789999961853027,
+            _24: 13.34999942779541,
+            _25: 13.899999618530273,
+            _26: 14.460000038146972,
+            _27: 15.019999504089355,
+            _28: 15.569999694824218,
+            _29: 16.1299991607666,
+            _30: 16.68000030517578,
+            _31: 17.239999771118164,
+            _32: 17.799999237060547,
+            _33: 18.350000381469726,
+            _34: 18.90999984741211,
+            _35: 19.469999313354492,
+            _36: 20.020000457763672,
+            _37: 20.579999923706054,
+            _38: 21.1299991607666,
+            _39: 21.689998626708984,
+            _40: 22.25,
+            _41: 22.799999237060547,
+            _42: 23.35999870300293,
+            _43: 23.90999984741211,
+            _44: 24.469999313354492,
+            _45: 25.029998779296875,
+            _46: 25.579999923706054,
+            _47: 26.139999389648437,
+            _48: 26.69999885559082,
+            _49: 27.25,
+            _50: 27.809999465942383,
+            _51: 28.35999870300293,
+            _52: 28.920000076293945,
+            _53: 29.479999542236328,
+            _54: 30.029998779296875,
+            _55: 30.59000015258789,
+            _56: 31.139999389648437,
+            _57: 31.69999885559082,
+            _58: 32.2599983215332,
+            _59: 32.80999755859375,
+            _60: 33.369998931884766,
+            _61: 33.93000030517578,
+            _62: 34.47999954223633,
+            _63: 35.040000915527344,
+            _64: 35.59000015258789,
         },
-        '窄': {
-            '_6': 3.3399999141693115,
-            '_7': 3.8899998664855957,
-            '_8': 4.449999809265137,
-            '_9': 5.009999752044678,
-            '_10': 5.559999942779541,
-            '_11': 6.119999885559082,
-            '_12': 6.670000076293945,
-            '_13': 7.230000019073486,
-            '_14': 7.789999961853027,
-            '_15': 8.34000015258789,
-            '_16': 8.899999618530273,
-            '_17': 9.449999809265136,
-            '_18': 10.010000228881836,
-            '_19': 10.569999694824218,
-            '_20': 11.119999885559082,
-            '_21': 11.679999351501464,
-            '_22': 12.239999771118164,
-            '_23': 12.789999961853027,
-            '_24': 13.34999942779541,
-            '_25': 13.899999618530273,
-            '_26': 14.460000038146972,
-            '_27': 15.019999504089355,
-            '_28': 15.569999694824218,
-            '_29': 16.1299991607666,
-            '_30': 16.68000030517578,
-            '_31': 17.239999771118164,
-            '_32': 17.799999237060547,
-            '_33': 18.350000381469726,
-            '_34': 18.90999984741211,
-            '_35': 19.469999313354492,
-            '_36': 20.020000457763672,
-            '_37': 20.579999923706054,
-            '_38': 21.1299991607666,
-            '_39': 21.689998626708984,
-            '_40': 22.25,
-            '_41': 22.799999237060547,
-            '_42': 23.35999870300293,
-            '_43': 23.90999984741211,
-            '_44': 24.469999313354492,
-            '_45': 25.029998779296875,
-            '_46': 25.579999923706054,
-            '_47': 26.139999389648437,
-            '_48': 26.69999885559082,
-            '_49': 27.25,
-            '_50': 27.809999465942383,
-            '_51': 28.35999870300293,
-            '_52': 28.920000076293945,
-            '_53': 29.479999542236328,
-            '_54': 30.029998779296875,
-            '_55': 30.59000015258789,
-            '_56': 31.139999389648437,
-            '_57': 31.69999885559082,
-            '_58': 32.2599983215332,
-            '_59': 32.80999755859375,
-            '_60': 33.369998931884766,
-            '_61': 33.93000030517578,
-            '_62': 34.47999954223633,
-            '_63': 35.040000915527344,
-            '_64': 35.59000015258789
-        }
     },
-    getFontWidth: function(fontName, fontHeight, typeStr) {
+    getFontWidth(fontName, fontHeight, typeStr) {
         let rst = 12;
         if (fontWidthMap[fontName]) {
             rst = fontWidthMap[fontName][typeStr]['_' + fontHeight];

+ 13 - 2
app/reports/rpt_component/jpc_bill_tab.js

@@ -20,6 +20,7 @@ JpcBillTabSrv.prototype.createNew = function() {
         me.signatureRst = [];
         me.signatureDateRst = [];
         me.signatureAuditRst = [];
+        me.totalPages = 0;
     };
     JpcBillTabResult.sorting = function(rptTpl) {
         const me = this;
@@ -27,6 +28,7 @@ JpcBillTabSrv.prototype.createNew = function() {
     };
     JpcBillTabResult.paging = function(rptTpl, dataObj) {
         let rst = 0;
+        const me = this;
         function getDataLength(fields_str) {
             const dataFields = dataObj[fields_str];
             if (dataFields && dataFields.length > 0) {
@@ -44,6 +46,7 @@ JpcBillTabSrv.prototype.createNew = function() {
                 rst = 1;
             }
         }
+        me.totalPages = rst;
         return rst;
     };
     JpcBillTabResult.outputAsPreviewPage = function(rptTpl, bands, controls, $CURRENT_RPT) {
@@ -51,14 +54,14 @@ JpcBillTabSrv.prototype.createNew = function() {
         let rst = [];
         // const vIdx = [];
         // 只预览第一页的数据
-        const pageStatus = [true, true, false, true, false, false, false, false];
+        const pageStatus = [true, true, true, true, true, true, true, true];
         JpcBandHelper.setBandArea(bands, rptTpl, pageStatus, true, false);
         const unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
         rst = rst.concat(me.outputPreviewContent(rptTpl, bands, unitFactor, controls, pageStatus));
         rst = rst.concat(JpcDiscreteHelper.outputPreviewDiscreteInfo(rptTpl[JV.NODE_BILL_INFO][JV.NODE_DISCRETE_INFO], bands, unitFactor, pageStatus));
         return rst;
     };
-    JpcBillTabResult.outputAsSimpleJSONPage = function(rptTpl, dataObj, page, bands, controls, $CURRENT_RPT, customizeCfg) {
+    JpcBillTabResult.outputAsSimpleJSONPage = function(rptTpl, dataObj, page, bands, controls, fonts, $CURRENT_RPT, customizeCfg) {
         const me = this;
         let rst = [];
         me.signatureRst = [];
@@ -67,6 +70,14 @@ JpcBillTabSrv.prototype.createNew = function() {
         const tabRstLst = [];
         // 1. calculate the band position
         const pageStatus = [true, false, false, false, false, false, false, false];
+        if (page === 1) {
+            pageStatus[JV.STATUS_REPORT_START] = true;
+            pageStatus[JV.STATUS_SEGMENT_START] = true;
+        }
+        if (page === me.totalPages) {
+            pageStatus[JV.STATUS_REPORT_END] = true;
+            pageStatus[JV.STATUS_SEGMENT_END] = true;
+        }
         JpcBandHelper.setBandArea(bands, rptTpl, pageStatus);
         // 2. start to output detail-part
         const unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);

+ 25 - 8
app/reports/rpt_component/jpc_cross_tab.js

@@ -263,7 +263,7 @@ JpcCrossTabSrv.prototype.createNew = function() {
                             if (isNaN(vTtl)) {
                                 vTtl = 0;
                             }
-                            //rowGrandTotal[di] = rowGrandTotal[di] + 1.0 * vTtl;
+                            // rowGrandTotal[di] = rowGrandTotal[di] + 1.0 * vTtl;
                             rowGrandTotal[di] = bc.add(rowGrandTotal[di], vTtl);
                         }
                     }
@@ -392,8 +392,8 @@ JpcCrossTabSrv.prototype.createNew = function() {
                             if ((segIdx === segCnt - 1) && pageStatus[JV.STATUS_CROSS_ROW_END] && pageStatus[JV.STATUS_CROSS_COL_END]) {
                                 pageStatus[JV.STATUS_REPORT_END] = true;
                                 private_resetBandArea();
-                                let hasAdHocColEnd = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_COL_SUM, rptTpl, bands, me.sortedColSequence, segIdx, (colSplitCnt - 1) * orgMaxColRec, maxColRec);
-                                let hasAdHocRowEnd = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_ROW_SUM, rptTpl, bands, me.sortedRowSequence, segIdx, (rowSplitCnt - 1) * orgMaxRowRec, maxRowRec);
+                                const hasAdHocColEnd = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_COL_SUM, rptTpl, bands, me.sortedColSequence, segIdx, (colSplitCnt - 1) * orgMaxColRec, maxColRec);
+                                const hasAdHocRowEnd = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_ROW_SUM, rptTpl, bands, me.sortedRowSequence, segIdx, (rowSplitCnt - 1) * orgMaxRowRec, maxRowRec);
                                 if (hasAdHocColEnd || hasAdHocRowEnd) {
                                     needOneMoreRptEnd = true;
                                     pageStatus[JV.STATUS_REPORT_END] = false;
@@ -445,8 +445,8 @@ JpcCrossTabSrv.prototype.createNew = function() {
                             if ((segIdx === segCnt - 1) && pageStatus[JV.STATUS_CROSS_ROW_END] && pageStatus[JV.STATUS_CROSS_COL_END]) {
                                 pageStatus[JV.STATUS_REPORT_END] = true;
                                 private_resetBandArea();
-                                let hasAdHocColEnd = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_COL_SUM, rptTpl, bands, me.sortedColSequence, segIdx, (colSplitCnt - 1) * orgMaxColRec, maxColRec);
-                                let hasAdHocRowEnd = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_ROW_SUM, rptTpl, bands, me.sortedRowSequence, segIdx, (rowSplitCnt - 1) * orgMaxRowRec, maxRowRec);
+                                const hasAdHocColEnd = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_COL_SUM, rptTpl, bands, me.sortedColSequence, segIdx, (colSplitCnt - 1) * orgMaxColRec, maxColRec);
+                                const hasAdHocRowEnd = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_ROW_SUM, rptTpl, bands, me.sortedRowSequence, segIdx, (rowSplitCnt - 1) * orgMaxRowRec, maxRowRec);
                                 if (hasAdHocColEnd || hasAdHocRowEnd) {
                                     needOneMoreRptEnd = true;
                                     pageStatus[JV.STATUS_REPORT_END] = false;
@@ -572,7 +572,7 @@ JpcCrossTabSrv.prototype.createNew = function() {
         }
         return rst;
     };
-    JpcCrossTabResult.outputAsSimpleJSONPage = function(rptTpl, dataObj, page, bands, controls, $CURRENT_RPT, customizeCfg) {
+    JpcCrossTabResult.outputAsSimpleJSONPage = function(rptTpl, dataObj, page, bands, controls, fonts, $CURRENT_RPT, customizeCfg) {
         const me = this;
         let rst = [];
         const tabRstLst = [];
@@ -859,7 +859,9 @@ JpcCrossTabSrv.prototype.createNew = function() {
                             const cellItem = JpcCommonOutputHelper.createCommonOutput(tab_field, val, controls);
                             cellItem[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, 1, 0, me.dispSumValueLst_Row[page - 1].length, i, 1, 0, true, false);
                             // 方向不同,需要调整下
-                            rst.push(cellItem);
+                            if (!(tab_field[JV.PROP_HIDDEN])) {
+                                rst.push(cellItem);
+                            }
                         }
                     } else {
                         let sumL = 1;
@@ -906,7 +908,9 @@ JpcCrossTabSrv.prototype.createNew = function() {
                             const val = me.dispSumValueLst_Col[page - 1][i][j];
                             const cellItem = JpcCommonOutputHelper.createCommonOutput(tab_field, val, controls);
                             cellItem[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, me.dispSumValueLst_Col[page - 1].length, i, 1, 0, 1, 0, true, false);
-                            rst.push(cellItem);
+                            if (!(tab_field[JV.PROP_HIDDEN])) {
+                                rst.push(cellItem);
+                            }
                         }
                     } else {
                         let sumL = 1;
@@ -973,6 +977,19 @@ JpcCrossTabSrv.prototype.createNew = function() {
                         firstTextOutput = false;
                     }
                 }
+                if (me.row_extension_fields_idx.length === 0) {
+                    const cols = valuesIdx.length;
+                    for (let colIdx = 0; colIdx < cols; colIdx++) {
+                        if (tab[JV.PROP_TEXT]) {
+                            rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXT], band, unitFactor, 1, 0, cols, colIdx, 1, 0));
+                        }
+                        if (tab[JV.PROP_TEXTS]) {
+                            for (let j = 0; j < tab[JV.PROP_TEXTS].length; j++) {
+                                rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXTS][j], band, unitFactor, 1, 0, cols, colIdx, 1, 0));
+                            }
+                        }
+                    }
+                }
             }
         }
         return rst;

+ 8 - 8
app/reports/rpt_component/jpc_ex.js

@@ -248,12 +248,12 @@ JpcExSrv.prototype.createNew = function() {
                 const expression = execFmlMe.formulas[execFmlIdx][JV.PROP_EXPRESSION];
                 if (expression) {
                     const $ME = execFmlMe.formulas[execFmlIdx];
-                    // console.log("current expression idx: " + execFmlIdx);
+                    // console.log('current expression idx: ' + execFmlIdx);
                     // console.log(expression);
                     try {
                         eval(expression);
                     } catch (ex) {
-                        console.log("current expression idx: " + execFmlIdx);
+                        console.log('current expression idx: ' + execFmlIdx);
                         console.log(expression);
                         console.log(ex);
                     }
@@ -341,7 +341,7 @@ JpcExSrv.prototype.createNew = function() {
                 for (let page = startPage; page <= endPage; page++) {
                     me.runTimePageData.currentPage = page;
                     me.executeFormulas($CTX_HELPER, JV.RUN_TYPE_BEFORE_OUTPUT, rptTpl, dataObj, me, $CUSTOM_DEFINE, defProperties);
-                    rst.items.push(me.outputAsSimpleJSONPage(rptTpl, dataObj, bands, page, rst[JV.NODE_CONTROL_COLLECTION], customizeCfg));
+                    rst.items.push(me.outputAsSimpleJSONPage(rptTpl, dataObj, bands, page, rst[JV.NODE_CONTROL_COLLECTION], rst[JV.NODE_FONT_COLLECTION], customizeCfg));
                 }
                 if (bands[JV.BAND_PROP_MERGE_BAND]) {
                     const mergedBand = {};
@@ -361,7 +361,7 @@ JpcExSrv.prototype.createNew = function() {
         }
         return rst;
     };
-    JpcResult.outputAsSimpleJSONPage = function(rptTpl, dataObj, bands, page, controls, customizeCfg) {
+    JpcResult.outputAsSimpleJSONPage = function(rptTpl, dataObj, bands, page, controls, fonts, customizeCfg) {
         const me = this;
         let rst = null;
         function getPageMergeBorder() {
@@ -388,7 +388,7 @@ JpcExSrv.prototype.createNew = function() {
                     if (me.flowTab.paging_option === JV.PAGING_OPTION_INFINITY) {
                         adHocMergePos = {};
                     }
-                    rst[JV.PROP_CELLS] = me.flowTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, adHocMergePos, me, customizeCfg);
+                    rst[JV.PROP_CELLS] = me.flowTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, fonts, adHocMergePos, me, customizeCfg);
                     rst[JV.PROP_SIGNATURE_CELLS] = me.flowTab.signatureRst;
                     rst[JV.PROP_SIGNATURE_DATE_CELLS] = me.flowTab.signatureDateRst;
                     rst[JV.PROP_SIGNATURE_AUDIT_CELLS] = me.flowTab.signatureAuditRst;
@@ -399,19 +399,19 @@ JpcExSrv.prototype.createNew = function() {
 
                 } else {
                     if (!me.isFollowMode) {
-                        rst[JV.PROP_CELLS] = me.flowTabEx.outputAsSimpleJSONPage(rptTpl, dataObj, page - (me.totalPages - me.exTotalPages), bands, controls, adHocMergePos, me, customizeCfg);
+                        rst[JV.PROP_CELLS] = me.flowTabEx.outputAsSimpleJSONPage(rptTpl, dataObj, page - (me.totalPages - me.exTotalPages), bands, controls, fonts, adHocMergePos, me, customizeCfg);
                         rst[JV.PROP_SIGNATURE_CELLS] = me.flowTabEx.signatureRst;
                         rst[JV.PROP_SIGNATURE_DATE_CELLS] = me.flowTabEx.signatureDateRst;
                         rst[JV.PROP_SIGNATURE_AUDIT_CELLS] = me.flowTabEx.signatureAuditRst;
                     }
                 }
             } else if (me.crossTab) {
-                rst[JV.PROP_CELLS] = me.crossTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, me, customizeCfg);
+                rst[JV.PROP_CELLS] = me.crossTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, fonts, me, customizeCfg);
                 rst[JV.PROP_SIGNATURE_CELLS] = me.crossTab.signatureRst;
                 rst[JV.PROP_SIGNATURE_DATE_CELLS] = me.crossTab.signatureDateRst;
                 rst[JV.PROP_SIGNATURE_AUDIT_CELLS] = me.crossTab.signatureAuditRst;
             } else if (me.billTab) {
-                rst[JV.PROP_CELLS] = me.billTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, me, customizeCfg);
+                rst[JV.PROP_CELLS] = me.billTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, fonts, me, customizeCfg);
                 rst[JV.PROP_SIGNATURE_CELLS] = me.billTab.signatureRst;
                 rst[JV.PROP_SIGNATURE_DATE_CELLS] = me.billTab.signatureDateRst;
                 rst[JV.PROP_SIGNATURE_AUDIT_CELLS] = me.billTab.signatureAuditRst;

+ 141 - 23
app/reports/rpt_component/jpc_flow_tab.js

@@ -16,6 +16,7 @@ const strUtil = require('../public/stringUtil');
 const fontWidthMap = require('./helper/jpc_helper_font_width');
 const bc = require('../../lib/base_calc.js');
 const SEG_PAGE_SPLIT_STR = '`';
+const PAGESIZE_THRESHOLD = 2000; // 报表页码的上限值
 
 const JpcFlowTabSrv = function() {};
 JpcFlowTabSrv.prototype.createNew = function() {
@@ -260,7 +261,7 @@ JpcFlowTabSrv.prototype.createNew = function() {
                         let sv = JpcFieldHelper.getValue(data_fields[di], me.segments[i][j]);
                         if (sv) {
                             if (typeof sv === 'string') sv = parseFloat(sv);
-                            rowGrandTotal[di] = bc.add(rowGrandTotal[di] ? rowGrandTotal[di] : 0, sv ? sv: 0);
+                            rowGrandTotal[di] = bc.add(rowGrandTotal[di] ? rowGrandTotal[di] : 0, sv ? sv : 0);
                         }
                     }
                 }
@@ -302,7 +303,7 @@ JpcFlowTabSrv.prototype.createNew = function() {
                         if (typeof sv === 'string') sv = parseFloat(sv);
                         sumV = bc.add(sumV, sv ? sv : 0);
                     }
-                    //sumV += parseFloat(parseFloat(JpcFieldHelper.getValue(data_field, segDataIdx[si])).toFixed(precisionAmt));
+                    // sumV += parseFloat(parseFloat(JpcFieldHelper.getValue(data_field, segDataIdx[si])).toFixed(precisionAmt));
                 }
                 // me.group_sum_values[segIdx][j].push(sumV);
                 me.group_sum_values[segIdx][me.group_sum_fields[j][JV.PROP_SUM_KEY]].push(sumV);
@@ -400,6 +401,7 @@ JpcFlowTabSrv.prototype.createNew = function() {
             };
             const private_get_max_lines_of_the_record = function(theRecIdx) {
                 let rst = 1;
+                let maxAmt = 1;
                 for (let loop = 0; loop < me.auto_height_fields_idx.length; loop++) {
                     /*
                     let data_field = null;
@@ -423,20 +425,24 @@ JpcFlowTabSrv.prototype.createNew = function() {
                         let value = JpcFieldHelper.getValue(data_field, theRecIdx);
                         const area = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, 1, 1, 0, 1, 0, 1, 0, false, false);
                         if (value !== null && value !== undefined) {
-                            // value = value.replace('\t', '');
-                            value = value.replace(reg1, '|').replace(reg2, '|').replace(reg3, '|').replace(reg4, '|').replace(reg5, '');
+                            if (typeof value === 'string') {
+                                value = value.replace('\t', '');
+                                value = value.replace(reg1, '|').replace(reg2, '|').replace(reg3, '|').replace(reg4, '|').replace(reg5, '');
+                            } else {
+                                value = '' + value;
+                            }
                         } else {
                             value = '';
                         }
                         JpcFieldHelper.setValue(data_field, theRecIdx, value);
                         const values = value.split('|');
-                        if (values.length > rst) rst = values.length;
+                        // if (values.length > rst) rst = values.length; // 考虑到紧凑输出,不能那么快设置rst
                         const font = private_get_font(tab_field[JV.PROP_FONT]);
                         let chkFontName = '宋体';
                         let chkFontHeight = 12;
                         if (font) {
                             chkFontName = font[JV.FONT_PROPS[0]];
-                            chkFontHeight = font[JV.FONT_PROPS[JV.FONT_PROP_IDX_HEIGHT]];
+                            chkFontHeight = parseFloat(font[JV.FONT_PROPS[JV.FONT_PROP_IDX_HEIGHT]]);
                         }
                         let hasSplitStr = false;
                         let splitStrArr = [];
@@ -451,7 +457,20 @@ JpcFlowTabSrv.prototype.createNew = function() {
                                 splitStrArr.push(i);
                             }
                         }
-                        if (accAmt > rst) rst = accAmt;
+                        // if (accAmt > rst) rst = accAmt;
+                        if (rst > 3) {
+                            // 新优化,计算实际能显示完的真正行数
+                            const rowHeight = JpcFlowTabHelper.getRowHeight(bands, rptTpl, me.isEx);
+                            const tmpFV = ((chkFontHeight + JV.CLOSE_OUTPUT_ROW_BUFFER) * accAmt) / rowHeight;
+                            let tmpAmt = Math.floor(tmpFV);
+                            if (Math.ceil(tmpFV) > tmpAmt) {
+                                tmpAmt++;
+                            }
+                            // rst = tmpAmt;
+                            if (maxAmt < tmpAmt) maxAmt = tmpAmt;
+                        } else {
+                            if (maxAmt < accAmt) maxAmt = accAmt;
+                        }
                         // if (hasSplitStr && outputType !== JV.OUTPUT_TYPE_EXCEL) {
                         if (hasSplitStr) {
                             let newValArr = [];
@@ -470,6 +489,7 @@ JpcFlowTabSrv.prototype.createNew = function() {
                         //      根据最新需求,暂时不考虑excel类型输出,换回原来逻辑
                     }
                 }
+                if (rst < maxAmt) rst = maxAmt; // 最后才设置最大结果
                 return rst;
             };
             for (const tabField of tab_fields) {
@@ -822,7 +842,7 @@ JpcFlowTabSrv.prototype.createNew = function() {
                     accAutoHeightAmt += adHocAutoHeightAmt;
                     // 控制阈值,超过阈值则强制退出,防止死循环
                     threshold++;
-                    if (threshold > 1000) {
+                    if (threshold > PAGESIZE_THRESHOLD) {
                         console.log('Hey! There may be a dead loop here!!!');
                         break;
                     }
@@ -861,7 +881,7 @@ JpcFlowTabSrv.prototype.createNew = function() {
         rst = rst.concat(JpcDiscreteHelper.outputPreviewDiscreteInfo(rptTpl[JV.NODE_FLOW_INFO][JV.NODE_DISCRETE_INFO], bands, unitFactor, pageStatus));
         return rst;
     };
-    JpcFlowTabResult.outputAsSimpleJSONPage = function(rptTpl, dataObj, page, bands, controls, adHocMergePos, $CURRENT_RPT, customizeCfg) {
+    JpcFlowTabResult.outputAsSimpleJSONPage = function(rptTpl, dataObj, page, bands, controls, fonts, adHocMergePos, $CURRENT_RPT, customizeCfg) {
         const me = this;
         let rst = [];
         const tabRstLst = [];
@@ -893,7 +913,7 @@ JpcFlowTabSrv.prototype.createNew = function() {
             const offsetY = actH - (flowContentBand.Bottom - flowContentBand.Top);
             JpcBandHelper.resetBandPos(rptTpl[JV.NODE_BAND_COLLECTION], bands, flowContentBand, 0, offsetY);
             // 2.1 Content-Tab
-            tabRstLst.push(me.outputContent(rptTpl, dataObj, page, bands, unitFactor, controls, 0, $CURRENT_RPT, customizeCfg));
+            tabRstLst.push(me.outputContent(rptTpl, dataObj, page, bands, unitFactor, controls, fonts, 0, $CURRENT_RPT, customizeCfg));
             // 2.2 Column tab
             tabRstLst.push(me.outputColumn(rptTpl, dataObj, page, segIdx, 0, bands, unitFactor, 0, $CURRENT_RPT, customizeCfg));
             // 2.3 Sum Seg
@@ -928,7 +948,7 @@ JpcFlowTabSrv.prototype.createNew = function() {
             for (let pi = 0; pi < me.multiCols; pi++) {
                 const actualPage = (page - 1) * me.multiCols + pi + 1;
                 // 2.1 Content-Tab
-                tabRstLst.push(me.outputContent(rptTpl, dataObj, actualPage, bands, unitFactor, controls, pi, $CURRENT_RPT, customizeCfg));
+                tabRstLst.push(me.outputContent(rptTpl, dataObj, actualPage, bands, unitFactor, controls, fonts, pi, $CURRENT_RPT, customizeCfg));
                 // 2.2 Column tab
                 tabRstLst.push(me.outputColumn(rptTpl, dataObj, actualPage, segIdx, segPageIdx, bands, unitFactor, pi, $CURRENT_RPT, customizeCfg));
                 // 2.3 Sum Seg
@@ -948,15 +968,57 @@ JpcFlowTabSrv.prototype.createNew = function() {
         }
         return rst;
     };
-    JpcFlowTabResult.combinePageCells = function(rstPageCells, verticalCombinePos, horizonCombinePos) {
+    JpcFlowTabResult.combinePageCells = function(rstPageCells, verticalCombinePos, horizonCombinePos, controls, fonts) {
         // let me = this;
         // 备注:纵向合并要考虑以下因素:
         //     如果有多个column纵向合并,需要总体考虑分割,
         //     假如:第一列的前3个数据(1、2、3)是相同的,第二列中第2、3、4行数据相同,那么第二列只能合并2、3行的数据,不能合并到第四行
         //     同理如此类推第三列...n列
+        // 另:要考虑紧密输出情况,需要重新拆分数据
+        const _splitValues = function(oCell) {
+            // JpcFieldHelper.setValue(data_field, theRecIdx, value);
+            const values = oCell.Value.split('|');
+            let font = oCell[JV.PROP_FONT];
+            if (typeof oCell[JV.PROP_FONT] === 'string') {
+                font = fonts[oCell[JV.PROP_FONT]];
+            }
+            // // if (values.length > rst) rst = values.length; // 考虑到紧凑输出,不能那么快设置rst
+            // const font = private_get_font(tab_field[JV.PROP_FONT]);
+            let chkFontName = '宋体';
+            let chkFontHeight = 12;
+            if (font) {
+                chkFontName = font[JV.FONT_PROPS[0]];
+                chkFontHeight = parseFloat(font[JV.FONT_PROPS[JV.FONT_PROP_IDX_HEIGHT]]);
+            }
+            let hasSplitStr = false;
+            const splitStrArr = [];
+            let accAmt = 0;
+            const chnW = fontWidthMap.getFontWidth(chkFontName, chkFontHeight, '宽');
+            const otherW = fontWidthMap.getFontWidth(chkFontName, chkFontHeight, '窄');
+            for (let i = 0; i < values.length; i++) {
+                const amt = JpcCommonHelper.getStringLinesInArea(oCell.area, values[i], chnW, otherW);
+                accAmt += amt;
+                if (amt > 1) {
+                    hasSplitStr = true;
+                    splitStrArr.push(i);
+                }
+            }
+            if (hasSplitStr) {
+                let newValArr = [];
+                for (let i = 0; i < values.length; i++) {
+                    if (splitStrArr.indexOf(i) < 0) {
+                        newValArr.push(values[i]);
+                    } else {
+                        newValArr = newValArr.concat(JpcCommonHelper.splitString(oCell.area, values[i], chnW, otherW));
+                    }
+                }
+                oCell.Value = newValArr.join('|');
+            }
+        };
         if (verticalCombinePos.length > 0 || horizonCombinePos.length > 1) {
             const cacheObj = { vCache: {}, hCache: {}, hCacheStr: [] };
             const removeCellIds = [];
+            const reservedCells = [];
             for (const vPosArr of verticalCombinePos) {
                 const pStr = '_' + vPosArr[0] + '_' + vPosArr[1];
                 cacheObj.vCache[pStr] = [];
@@ -1020,6 +1082,9 @@ JpcFlowTabSrv.prototype.createNew = function() {
                                 if (preCell.Value === rstPageCells[cacheObj.vCache[pStr][cIdx]].Value) {
                                     const bkBottom = preCell[JV.PROP_AREA][JV.PROP_BOTTOM];
                                     preCell[JV.PROP_AREA][JV.PROP_BOTTOM] = rstPageCells[cacheObj.vCache[pStr][cIdx]][JV.PROP_AREA][JV.PROP_BOTTOM];
+                                    if (reservedCells.indexOf(preCell) < 0) {
+                                        reservedCells.push(preCell);
+                                    }
                                     if (private_chk_in_pre_merge(vidx, preCell)) {
                                         removeCellIds.push(cacheObj.vCache[pStr][cIdx]);
                                         if (cIdx === cacheObj.vCache[pStr].length - 1) {
@@ -1071,6 +1136,13 @@ JpcFlowTabSrv.prototype.createNew = function() {
                     rstPageCells.splice(removeCellIds[idx], 1);
                 }
             }
+            if (reservedCells.length > 0) {
+                for (const rCell of reservedCells) {
+                    // 要拆分下数据
+                    _splitValues(rCell);
+                    setupControl(rCell, controls, true);
+                }
+            }
         }
     };
     JpcFlowTabResult.outputPreviewContent = function(rptTpl, bands, unitFactor, controls, pageStatus, maxRec) {
@@ -1093,7 +1165,7 @@ JpcFlowTabSrv.prototype.createNew = function() {
         }
         return rst;
     };
-    JpcFlowTabResult.outputContent = function(rptTpl, dataObj, page, bands, unitFactor, controls, multiColIdx, $CURRENT_RPT, customizeCfg) {
+    JpcFlowTabResult.outputContent = function(rptTpl, dataObj, page, bands, unitFactor, controls, fonts, multiColIdx, $CURRENT_RPT, customizeCfg) {
         const me = this;
         let rst = [];
         const prepareObj = {};
@@ -1244,8 +1316,8 @@ JpcFlowTabSrv.prototype.createNew = function() {
                             if (contentValuesIdx[rowIdx][2] >= 0) {
                                 const psv = JpcFieldHelper.getValue(page_sum_data_fields[di], contentValuesIdx[rowIdx][2]);
                                 if (psv) {
-                                    rowGrandTotal[di] = bc.add(rowGrandTotal[di] ? rowGrandTotal[di] : 0 , parseFloat(psv));
-                                    //rowGrandTotal[di] = rowGrandTotal[di] + parseFloat(parseFloat(psv).toFixed(precisionAmt));
+                                    rowGrandTotal[di] = bc.add(rowGrandTotal[di] ? rowGrandTotal[di] : 0, parseFloat(psv));
+                                    // rowGrandTotal[di] = rowGrandTotal[di] + parseFloat(parseFloat(psv).toFixed(precisionAmt));
                                 }
                             }
                         }
@@ -1283,7 +1355,7 @@ JpcFlowTabSrv.prototype.createNew = function() {
             rst.splice(eliminateCells[idIdx], 1);
         }
         me.checkCombineEvent(JV.RUN_TYPE_BEFORE_COMBINE, verticalCombinePos, horizonCombinePos, rst, $CURRENT_RPT, dataObj);
-        me.combinePageCells(rst, verticalCombinePos, horizonCombinePos);
+        me.combinePageCells(rst, verticalCombinePos, horizonCombinePos, controls, fonts);
         me.checkCombineEvent(JV.RUN_TYPE_AFTER_COMBINE, verticalCombinePos, horizonCombinePos, rst, $CURRENT_RPT, dataObj);
         return rst;
     };
@@ -1422,11 +1494,53 @@ JpcFlowTabSrv.prototype.createNew = function() {
             if (showText) {
                 textArr = textArr.concat(showText.split('|'));
             }
+            /*
             if (contentValInfo[3] < textArr.length && contentValInfo[3] >= 0) {
                 showText = textArr[contentValInfo[3]];
             } else {
                 showText = '';
             }
+            /*/
+            // 因新逻辑要优化自动行高,不再是一个折行占用1row,而是:
+            // a. 根据实际情况,设置了一个实际的行数值(比如共有10行,根据行高情况,可能只设置了6行的高度,在setupAutoHeightData方法里处理)
+            // b. 这里最大的难题在于跨页分配(预估会有来回调整,目前采用按比例分配原则)
+            // c. 还有个问题就是多个自动行高指标的问题
+            const ACT_ROWS = contentValInfo[4]; // 经实际计算,能容纳所有数据的实际行数
+            const CURRENT_ROW = contentValInfo[3]; // 从0开始
+            const QUOTA_Rate = textArr.length / ACT_ROWS; // 每行显示的加权系数(超过2的话,表示给的行高太多了,需要另外一些特殊处理,后期有需要再加)
+            if (textArr.length <= 3 || ACT_ROWS > textArr.length) {
+                // 表示这个一定是附属的autoheight指标,或者数据就只有3行(假如数据3行精简成2行,但刚好处于分页状态,那么无论是前一页或后一页,显示上都很纠结(多出来的一行放哪好?),所以新逻辑的起点要3行以上,少了就不折腾了,老老实实地一个萝卜一个坑)
+                if (CURRENT_ROW < textArr.length && CURRENT_ROW >= 0) {
+                    showText = textArr[CURRENT_ROW];
+                } else {
+                    showText = '';
+                }
+            } else {
+                // 这里不管是不是附属autoheight指标,有足够的条件单独处理
+                if (QUOTA_Rate >= 2) {
+                    // 压缩超过一半空间的话,说明给的行高度太高了,需要另外一些特殊处理,后期有需要再加
+                    console.log('压缩率异常!!!!!');
+                } else {
+                    // 正常的压缩比例
+                    let startIdx = 0,
+                        endIdx = 0;
+                    if (CURRENT_ROW > 0) {
+                        endIdx = Math.round((CURRENT_ROW + 1) * QUOTA_Rate) - 1;
+                        let preEndIdx = 0;
+                        if (CURRENT_ROW > 1) {
+                            // 重定位 preEndIdx
+                            preEndIdx = Math.round(CURRENT_ROW * QUOTA_Rate) - 1;
+                        }
+                        startIdx = preEndIdx + 1;
+                    }
+                    // 按比例分配
+                    showText = textArr[startIdx];
+                    for (let idx = startIdx + 1; idx <= endIdx; idx++) {
+                        showText += `|${textArr[idx]}`;
+                    }
+                }
+            }
+            // */
             rst = JpcCommonOutputHelper.createCommonOutput(tab_field, showText, controls);
             rst[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, rows, rowIdx, cols, colIdx, me.multiCols, multiColIdx, true, false);
             rst[JV.PROP_IS_AUTO_HEIGHT] = true;
@@ -1455,7 +1569,7 @@ JpcFlowTabSrv.prototype.createNew = function() {
             for (const txt of grp_line[JV.PROP_TEXTS]) {
                 me.checkGrpTxtOutEvent(JV.RUN_TYPE_BEFORE_GROUP_TEXT_OUT, txt, grpCntIdx, $CURRENT_RPT, $CURRENT_DATA);
                 rst.push(JpcTextHelper.outputText(txt, band, unitFactor, rows, rowIdx, cols, colIdx, me.multiCols, multiColIdx));
-                // me.combinePageCells(rst, verticalCombinePos, horizonCombinePos);
+                // me.combinePageCells(rst, verticalCombinePos, horizonCombinePos, controls);
                 // 可能会有一个After,但意义不大,用不着
             }
         }
@@ -1514,7 +1628,7 @@ function push_cell(pageCellObj, cell, cellIdx) {
         pageCellObj[key] = [];
     }
     const cellArr = pageCellObj[key];
-    cellArr.push({ cellIdx: cellIdx, cell: cell });
+    cellArr.push({ cellIdx, cell });
 }
 
 function prepareAutoHeightCells(prepareObj, cellItem, cellIdx, cellsArr) {
@@ -1527,7 +1641,7 @@ function prepareAutoHeightCells(prepareObj, cellItem, cellIdx, cellsArr) {
     }
 }
 
-function setupControl(mergeCell, controls) {
+function setupControl(mergeCell, controls, isCombine = false) {
     let orgCtrl = null;
     if (typeof mergeCell[JV.PROP_CONTROL] === 'string') {
         orgCtrl = controls[mergeCell[JV.PROP_CONTROL]];
@@ -1543,17 +1657,21 @@ function setupControl(mergeCell, controls) {
         };
     } else {
         mergeCell[JV.PROP_CONTROL][JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_SHRINK]] = 'T';
-        mergeCell[JV.PROP_CONTROL][JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] = 'top';
+        // mergeCell[JV.PROP_CONTROL][JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] = 'top';
         mergeCell[JV.PROP_CONTROL][JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_WRAP]] = (mergeCell[JV.PROP_IS_AUTO_HEIGHT]) ? 'T' : 'F';
-        mergeCell[JV.PROP_CONTROL][JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL_EXCEL]] = 'center';
+        // mergeCell[JV.PROP_CONTROL][JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL_EXCEL]] = 'center';
+        mergeCell[JV.PROP_CONTROL][JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL_EXCEL]] = mergeCell[JV.PROP_CONTROL][JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]];
         orgCtrl = mergeCell[JV.PROP_CONTROL];
     }
     if (mergeCell[JV.PROP_IS_AUTO_HEIGHT]) {
         mergeCell[JV.PROP_CONTROL][JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL_EXCEL]] = 'justify';
         // mergeCell[JV.PROP_CONTROL]['Vertical'] = 'center';
+    } else if (isCombine && orgCtrl.CloseOutput === 'T') {
+        mergeCell[JV.PROP_CONTROL][JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_WRAP]] = 'T';
+        mergeCell[JV.PROP_CONTROL][JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL_EXCEL]] = 'justify';
     } else {
         mergeCell[JV.PROP_CONTROL][JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL_EXCEL]] = 'center';
-        // mergeCell[JV.PROP_CONTROL]['Vertical'] = 'top';
+        // mergeCell[JV.PR OP_CONTROL]['Vertical'] = 'top';
     }
     return orgCtrl;
 }
@@ -1584,7 +1702,7 @@ function combineAutoHeightCells(prepareObj, page, controls) {
                         rst.push(sameColCells[i].cellIdx); // 记下Cell的位置,在函数外消除
                         // 如果到了最后一条数据,得判断firstMergeCell是否满格(即数据是满的,有可能有些格数据也有折行但不是自动行高判断指标)
                         // 不满格的cell的Vertical强制设置为 'center'
-                        if (i === sameColCells.length - 1 && validValueAmt !== fullValidValueAmt && (fullValidValueAmt / validValueAmt > 2.5) ) {
+                        if (i === sameColCells.length - 1 && validValueAmt !== fullValidValueAmt && (fullValidValueAmt / validValueAmt > 2.5)) {
                             firstMergeCell[JV.PROP_CONTROL][JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL_EXCEL]] = 'center';
                         }
                     } else {

+ 16 - 16
app/reports/rpt_component/jpc_rte.js

@@ -7,7 +7,7 @@ const strUtil = require('../public/stringUtil');
 const JV = require('./jpc_value_define');
 const JE = {
     $STR_UTIL: strUtil,
-    F: function(fID, $CURRENT_RPT) {
+    F(fID, $CURRENT_RPT) {
         let rst = null;
         if ($CURRENT_RPT) {
             if ($CURRENT_RPT.fields[JV.NODE_DETAIL_FIELDS][JV.PROP_ID + '_' + fID]) {
@@ -30,7 +30,7 @@ const JE = {
         }
         return rst;
     },
-    P: function(pID, $CURRENT_RPT) {
+    P(pID, $CURRENT_RPT) {
         let rst = null;
         if ($CURRENT_RPT && ($CURRENT_RPT.params[JV.PROP_ID + '_' + pID])) {
             rst = $CURRENT_RPT.params[JV.PROP_ID + '_' + pID];
@@ -43,21 +43,21 @@ const JE = {
         }
         return rst;
     },
-    getCurrentPage: function($CURRENT_RPT) {
+    getCurrentPage($CURRENT_RPT) {
         let rst = 0;
         if ($CURRENT_RPT) {
             rst = $CURRENT_RPT.runTimePageData.currentPage;
         }
         return rst;
     },
-    getTotalPage: function($CURRENT_RPT) {
+    getTotalPage($CURRENT_RPT) {
         let rst = 0;
         if ($CURRENT_RPT) {
             rst = $CURRENT_RPT.totalPages;
         }
         return rst;
     },
-    getFieldDataLen: function(field, dataObj) {
+    getFieldDataLen(field, dataObj) {
         let rst = 0;
         if (field.DataNodeName === 'NA') {
             if (!field[JV.PROP_AD_HOC_DATA]) {
@@ -75,14 +75,14 @@ const JE = {
         }
         return rst;
     },
-    setFieldValue: function(field, dataObj, valIdx, newValue) {
+    setFieldValue(field, dataObj, valIdx, newValue) {
         if (field.DataNodeName === 'NA') {
             if (!field[JV.PROP_AD_HOC_DATA]) {
                 field[JV.PROP_AD_HOC_DATA] = [];
             }
             field[JV.PROP_AD_HOC_DATA][valIdx] = newValue;
         } else if (!field.DataNodeName) {
-            //that means this is a self-defined discrete field!
+            // that means this is a self-defined discrete field!
             field.DataNodeName = JV.DATA_DISCRETE_DATA;
             field.DataSeq = dataObj[JV.DATA_DISCRETE_DATA].length;
             dataObj[JV.DATA_DISCRETE_DATA].push([]);
@@ -91,7 +91,7 @@ const JE = {
             dataObj[field.DataNodeName][field.DataSeq][valIdx] = newValue;
         }
     },
-    getFieldValueArray: function(field, dataObj) {
+    getFieldValueArray(field, dataObj) {
         let rst = null;
         if (field.DataNodeName === 'NA') {
             if (!field[JV.PROP_AD_HOC_DATA]) {
@@ -110,7 +110,7 @@ const JE = {
         if (rst === null || rst === undefined) rst = [];
         return rst;
     },
-    setFieldValueArray: function(field, dataObj, newArr) {
+    setFieldValueArray(field, dataObj, newArr) {
         if (newArr instanceof Array) {
             if (field.DataNodeName === 'NA') {
                 field[JV.PROP_AD_HOC_DATA] = newArr;
@@ -125,7 +125,7 @@ const JE = {
             }
         }
     },
-    getFieldValue: function(field, dataObj, valIdx, newVal) {
+    getFieldValue(field, dataObj, valIdx, newVal) {
         let rst = null;
         if (field.DataNodeName === 'NA') {
             if (!field[JV.PROP_AD_HOC_DATA]) {
@@ -140,7 +140,7 @@ const JE = {
             }
         } else {
             if (!field.DataNodeName) {
-                //that means this is a self-defined discrete field!
+                // that means this is a self-defined discrete field!
                 field.DataNodeName = JV.DATA_DISCRETE_DATA;
                 field.DataSeq = dataObj[JV.DATA_DISCRETE_DATA];
                 dataObj[JV.DATA_DISCRETE_DATA].push([]);
@@ -156,7 +156,7 @@ const JE = {
         if (rst === null || rst === undefined) rst = newVal;
         return rst;
     },
-    removeFieldValue: function(field, dataObj, valIdx) {
+    removeFieldValue(field, dataObj, valIdx) {
         if (field.DataNodeName === 'NA') {
             if (field[JV.PROP_AD_HOC_DATA].length > valIdx && valIdx >= 0) {
                 field[JV.PROP_AD_HOC_DATA].splice(valIdx, 1);
@@ -167,7 +167,7 @@ const JE = {
             }
         }
     },
-    insertFieldValue: function(field, dataObj, valIdx, newValue) {
+    insertFieldValue(field, dataObj, valIdx, newValue) {
         if (field.DataNodeName === 'NA') {
             if (field[JV.PROP_AD_HOC_DATA].length > valIdx && valIdx >= 0) {
                 field[JV.PROP_AD_HOC_DATA].splice(valIdx, 0, newValue);
@@ -182,21 +182,21 @@ const JE = {
             }
         }
     },
-    isSignature: function(fID, $CURRENT_RPT) {
+    isSignature(fID, $CURRENT_RPT) {
         let rst = false;
         if ($CURRENT_RPT.fields[JV.NODE_SIGNATURE_FIELDS]) {
             rst = $CURRENT_RPT.fields[JV.NODE_SIGNATURE_FIELDS][JV.PROP_ID + '_' + fID] !== undefined;
         }
         return rst;
     },
-    isDynamicParam: function(pID, $CURRENT_RPT) {
+    isDynamicParam(pID, $CURRENT_RPT) {
         let rst = false;
         if ($CURRENT_RPT.params[JV.NODE_DYNAMIC_DATE_PARAMS]) {
             rst = $CURRENT_RPT.params[JV.NODE_DYNAMIC_DATE_PARAMS][JV.PROP_ID + '_' + pID] !== undefined;
         }
         return rst;
     },
-    isSignatureAudit: function(pID, $CURRENT_RPT) {
+    isSignatureAudit(pID, $CURRENT_RPT) {
         let rst = false;
         if ($CURRENT_RPT.params[JV.NODE_SIGNATURE_AUDIT_PARAMS]) {
             rst = $CURRENT_RPT.params[JV.NODE_SIGNATURE_AUDIT_PARAMS][JV.PROP_ID + '_' + pID] !== undefined;

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

@@ -243,6 +243,7 @@ module.exports = {
     CONTROL_PROP_IDX_VERTICAL_EXCEL: 5,
     CONTROL_PROP_IDX_SHRINK_FIRST: 6,
     CONTROL_PROP_IDX_CLOSE_OUTPUT: 7,
+    CLOSE_OUTPUT_ROW_BUFFER: 4,
     BORDER_STYLE_PROPS: ['LineWeight', 'DashStyle', 'Color'],
     PROP_LINE_WEIGHT: 'LineWeight',
     PROP_DASH_STYLE: 'DashStyle',

+ 7 - 7
app/reports/util/rpt_excel_util.js

@@ -1441,7 +1441,7 @@ function _checkAndSetSignatureCache(pageData, signKeyArr, signPathArr, roleRel,
                         // console.log(chkRoles);
                     }
                     if (signature.pic) {
-                        const signPath = { signature_name: signature.signature_name,  path: null, pic: null };
+                        const signPath = { signature_name: signature.signature_name, path: null, pic: null };
                         signPathArr.push(signPath);
                         signPath.pic = signature.pic; // 历史报表
                         signKeyArr.push(signature.signature_name);
@@ -1459,7 +1459,7 @@ function _checkAndSetSignatureCache(pageData, signKeyArr, signPathArr, roleRel,
                                     rst = true;
                                     signSheetIdxArr[pageIdx] = true;
                                 } else if (role.sign_path) {
-                                    let sPObj = _getDupPicPath(role.sign_path);
+                                    const sPObj = _getDupPicPath(role.sign_path);
                                     if (sPObj !== null && isSinglePage) { // 只有在isSinglePage为true时,才需要优化签名
                                     // if (sPObj !== null) {
                                     //     console.log('signature');
@@ -1488,7 +1488,7 @@ function _checkAndSetSignatureCache(pageData, signKeyArr, signPathArr, roleRel,
                     // console.log(`duplicate signature: ${signature.signature_name}`);
                     // signSheetIdxArr[pageIdx] = true;
                 }
-                //*/
+                //* /
             }
         }
         if (page[JV.PROP_WATERMARK_CELLS] && page[JV.PROP_WATERMARK_CELLS].length > 0) {
@@ -1516,7 +1516,7 @@ function _checkAndSetSignatureCache(pageData, signKeyArr, signPathArr, roleRel,
                 rst = true;
                 signSheetIdxArr[pageIdx] = true;
             }
-            //*/
+            //* /
         }
     }
     return rst;
@@ -1530,7 +1530,7 @@ function base64ToBuffer(dataurl) {
 }
 
 module.exports = {
-    exportExcel: function(pageData, paperSize, fName, options, custSheetNames, custSheetMergeBands, baseDir, roleRel, callback) {
+    exportExcel(pageData, paperSize, fName, options, custSheetNames, custSheetMergeBands, baseDir, roleRel, callback) {
         const rptOptions = ({ singlePage: false, fileName: 'report' });
         if (options === 'true' || options === true) {
             rptOptions.singlePage = true;
@@ -1639,7 +1639,7 @@ module.exports = {
                 file = 'image' + picIdx + '.png';
                 zip_media.file(file, data, { compression: 'DEFLATE' });
             }
-            //*/
+            //* /
             // 5.2
             const zip_drawings = zip_xl.folder('drawings');
             data = writeDrawings(pageData, signKeyArr, signPathArr, isSinglePage, signSheetIdxArr);
@@ -1744,7 +1744,7 @@ module.exports = {
         }
     },
 
-    exportExcelInOneBook: function(pageDataArray, paperSize, fName, baseDir, roleRelArr, callback) {
+    exportExcelInOneBook(pageDataArray, paperSize, fName, baseDir, roleRelArr, callback) {
         const me = this;
         const newPageData = {};
         // 1. 重新编排一下数据,把一份报表的pageData合并到一起作为一个Sheet输出(需要重新调整数据纵向坐标),多份报表数据就形成多个Sheet

+ 8 - 1
app/router.js

@@ -171,7 +171,6 @@ module.exports = app => {
     app.post('/tender/:id/advance/:type/create', sessionAuth, tenderCheck, 'advanceController.create');
     app.post('/tender/:id/advance/:type/delete', sessionAuth, tenderCheck, 'advanceController.delete');
     app.get('/tender/:id/advance/:order/detail', sessionAuth, tenderCheck, advanceCheck, 'advanceController.detail');
-    app.post('/tender/:id/advance/:type/create', sessionAuth, tenderCheck, 'advanceController.create');
     app.post('/tender/:id/advance/:order/audit/add', sessionAuth, tenderCheck, advanceCheck, 'advanceController.addAudit');
     app.post('/tender/:id/advance/:order/audit/delete', sessionAuth, tenderCheck, advanceCheck, 'advanceController.deleteAudit');
     app.post('/tender/:id/advance/:order/audit/start', sessionAuth, tenderCheck, advanceCheck, 'advanceController.start');
@@ -287,6 +286,12 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/im-file/del', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.deleteImFile');
     app.get('/tender/:id/measure/stage/:order/im-file/download', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.downloadImFile');
 
+    // 暂存计量
+    app.post('/tender/:id/measure/stage/:order/stash/list', sessionAuth, tenderCheck, stageCheck, 'stageController.stashList');
+    app.post('/tender/:id/measure/stage/:order/stash/add', sessionAuth, tenderCheck, stageCheck, 'stageController.addStash');
+    app.post('/tender/:id/measure/stage/:order/stash/del', sessionAuth, tenderCheck, stageCheck, 'stageController.delStash');
+    app.post('/tender/:id/measure/stage/:order/stash/recover', sessionAuth, tenderCheck, stageCheck, 'stageController.recoverStash');
+
     // 计量附件
     app.post('/tender/:id/measure/stage/:order/upload/file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.uploadFile');
     app.get('/tender/:id/measure/stage/:order/download/file/:fid', sessionAuth, 'stageController.downloadFile');
@@ -520,6 +525,7 @@ module.exports = app => {
     app.post('/tender/:id/measure/material/:order/audit/delete', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.deleteAudit');
     app.post('/tender/:id/measure/material/:order/audit/start', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.startAudit');
     app.post('/tender/:id/measure/material/:order/audit/check', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.checkAudit');
+    app.get('/tender/:id/measure/material/:order/audit/check/again', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.checkAuditAgain');
     // 调差工料
     app.get('/tender/:id/measure/material/:order', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.info');
     app.post('/tender/:id/measure/material/:order/save', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.saveBillsData');
@@ -596,6 +602,7 @@ module.exports = app => {
     app.get('/wap/tender/:id', sessionAuth, tenderCheck, uncheckTenderCheck, 'wapController.tender');
     app.get('/wap/tender/:id/stage/:order', sessionAuth, tenderCheck, uncheckTenderCheck, 'wapController.stage');
     app.get('/wap/tender/:id/change/:cid/info', sessionAuth, tenderCheck, uncheckTenderCheck, 'wapController.change');
+    app.get('/wap/tender/:id/change/plan/:cpid/info', sessionAuth, tenderCheck, uncheckTenderCheck, 'wapController.changePlan');
     app.post('/wap/tender/:id/change/approval', sessionAuth, tenderCheck, uncheckTenderCheck, 'wapController.changeApproval');
     app.get('/wap/tender/:id/revise/:rid/info', sessionAuth, tenderCheck, uncheckTenderCheck, 'wapController.revise');
     app.get('/wap/tender/:id/advance', sessionAuth, tenderCheck, uncheckTenderCheck, 'wapController.advance');

+ 33 - 6
app/service/advance_audit.js

@@ -269,7 +269,7 @@ module.exports = app => {
             try {
                 await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
                 // 获取推送必要信息
-                const noticeContent = await this.getNoticeContent(pid, audit.tid, advanceId, audit.audit_id);
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, advanceId, audit.audit_id, checkData.opinion);
                 // 添加推送
                 const records = [{ pid, type: pushType.advance, uid: this.ctx.advance.uid, status: auditConst.status.checked, content: noticeContent }];
                 auditors.forEach(audit => {
@@ -350,7 +350,7 @@ module.exports = app => {
                 await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
 
                 // 添加到消息推送表
-                const noticeContent = await this.getNoticeContent(pid, audit.tid, advanceId, audit.audit_id);
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, advanceId, audit.audit_id, checkData.opinion);
                 const records = [{ pid, type: pushType.advance, uid: this.ctx.advance.uid, status: auditConst.status.checkNo, content: noticeContent }];
                 auditors.forEach(audit => {
                     records.push({ pid, type: pushType.advance, uid: audit.audit_id, status: auditConst.status.checkNo, content: noticeContent });
@@ -400,7 +400,7 @@ module.exports = app => {
                 return item.audit_id === audit.audit_id;
             });
             const preAuditor = auditors2[auditorIndex - 1];
-            const noticeContent = await this.getNoticeContent(pid, audit.tid, advanceId, audit.audit_id);
+            const noticeContent = await this.getNoticeContent(pid, audit.tid, advanceId, audit.audit_id, checkData.opinion);
             const transaction = await this.db.beginTransaction();
             try {
                 // 添加到消息推送表
@@ -491,10 +491,10 @@ module.exports = app => {
          * @param {Number} vid 预付款id
          * @param {Number} uid 审批人id
          */
-        async getNoticeContent(pid, tid, vid, uid) {
+        async getNoticeContent(pid, tid, vid, uid, opinion = '') {
             const noticeSql =
                 'SELECT * FROM (SELECT ' +
-                '  t.`id` As `tid`, ad.`vid`, t.`name`, m.`order`, pa.`name` As `su_name`, pa.role As `su_role`' +
+                '  t.`id` As `tid`, ad.`vid`, ad.`type` As `ad_type`, t.`name`, m.`order`, pa.`name` As `su_name`, pa.role As `su_role`' +
                 '  FROM (SELECT * FROM ?? WHERE `id` = ? ) As t' +
                 '  LEFT JOIN ?? As m On t.`id` = m.`tid` AND m.`id` = ?' +
                 '  LEFT JOIN ?? As ad ON m.`id` = ad.`vid`' +
@@ -511,6 +511,9 @@ module.exports = app => {
                 pid,
             ];
             const content = await this.db.query(noticeSql, noticeSqlParam);
+            if (content.length) {
+                content[0].opinion = opinion;
+            }
             return content.length ? JSON.stringify(content[0]) : '';
         }
 
@@ -570,7 +573,7 @@ module.exports = app => {
          */
         async getAuditAdvance(auditorId) {
             const sql = 'SELECT ma.`audit_id`, ma.`times`, ma.`order`, ma.`create_time`, ma.`end_time`, ma.`tid`, ma.`vid`,' +
-                        '    m.`order` As `morder`, m.`status` As `mstatus`,' +
+                        '    m.`order` As `morder`, m.`status` As `mstatus`, m.`type` As `mtype`,' +
                         '    t.`name`, t.`project_id`, t.`type`, t.`user_id` ' +
                         '  FROM ?? AS ma, ?? AS m, ?? As t ' +
                         '  WHERE ((ma.`audit_id` = ? and ma.`status` = ?) OR (m.`uid` = ? and ma.`status` = ? and m.`status` = ? and ma.`times` = (m.`times`-1)))' +
@@ -579,6 +582,30 @@ module.exports = app => {
             return await this.db.query(sql, sqlParam);
         }
 
+        /**
+         * 获取审核人审核的次数
+         *
+         * @param auditorId
+         * @return {Promise<*>}
+         */
+        async getCountByChecked(auditorId) {
+            return await this.db.count(this.tableName, { audit_id: auditorId, status: [auditConst.status.checked, auditConst.status.checkNo, auditConst.status.checkNoPre] });
+        }
+
+        /**
+         * 获取最近一次审批结束时间
+         *
+         * @param auditorId
+         * @return {Promise<*>}
+         */
+        async getLastEndTimeByChecked(auditorId) {
+            const sql = 'SELECT `end_time` FROM ?? WHERE `audit_id` = ? ' +
+                'AND `status` in (' + this.ctx.helper.getInArrStrSqlFilter([auditConst.status.checked, auditConst.status.checkNo]) + ') ORDER BY `end_time` DESC';
+            const sqlParam = [this.tableName, auditorId];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result ? result.end_time : null;
+        }
+
         async updateNewAuditList(advance, newIdList) {
             const transaction = await this.db.beginTransaction();
             try {

+ 27 - 0
app/service/budget.js

@@ -12,6 +12,7 @@ const defaultDecimal = {
     tp: 0,
     up: 2,
 };
+const FinalObj = require('../lib/budget_final');
 
 module.exports = app => {
 
@@ -244,6 +245,32 @@ module.exports = app => {
                 throw err;
             }
         }
+
+        async doFinal(budget, final) {
+            const finalObj = new FinalObj(this.ctx);
+            let finalData;
+            try {
+                finalData = await finalObj.doFinal(budget, final);
+            } catch (err) {
+                this.db.update(this.ctx.service.budgetFinalList.tableName, { id: final.id, status: 4});
+                this.ctx.log(err);
+                throw '生成决算数据错误';
+            }
+
+            const conn = await this.db.beginTransaction();
+            try {
+                await conn.update(this.tableName, {id: budget.id, final_id: final.id, final_time: new Date() });
+                await conn.insert(this.ctx.service.budgetFinal.tableName, finalData);
+                await conn.update(this.ctx.service.budgetFinalList.tableName, { id: final.id, tender_info: JSON.stringify(final.tender_info), status: 3});
+                await conn.commit();
+                return finalData;
+            } catch (err) {
+                this.ctx.log(err);
+                await conn.rollback();
+                this.db.update(this.ctx.service.budgetFinalList.tableName, { id: final.id, status: 4});
+                throw '保存决算数据错误';
+            }
+        }
     }
 
     return Budget;

+ 27 - 0
app/service/budget_final.js

@@ -0,0 +1,27 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+
+    class BudgetFinal extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'budget_final';
+        }
+    }
+
+    return BudgetFinal;
+};

+ 57 - 0
app/service/budget_final_list.js

@@ -0,0 +1,57 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const status = {
+    cancel: 0,
+    wait: 1,
+    ext: 2,
+    done: 3,
+};
+
+module.exports = app => {
+
+    class BudgetFinalList extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'budget_final_list';
+        }
+
+        _analysisFinal(data) {
+            data.tender = data.tender ? data.tender.split(',') : [];
+            data.tender_info = data.tender_info ? JSON.parse(data.tender_info) : [];
+        }
+
+        async getFinal(id) {
+            const data = await this.getDataById(id);
+            this._analysisFinal(data);
+            return data;
+        }
+
+        async addFinal(budget, tender) {
+            const user = await this.ctx.service.projectAccount.getDataById(this.ctx.session.sessionUser.accountId);
+            const final = {
+                bid: budget.id, uid: user.id,
+                u_name: user.name, u_role: user.role, u_company: user.company, u_mobile: user.mobile,
+                u_login: this.ctx.session.sessionUser.loginType + ';' + this.ctx.session.sessionUser.loginStatus,
+                tender: tender ? tender.join(',') : '', status: status.ext,
+            };
+            const result = await this.db.insert(this.tableName, final);
+            return await this.getFinal(result.insertId);
+        }
+    }
+
+    return BudgetFinalList;
+};

+ 65 - 65
app/service/change.js

@@ -696,6 +696,48 @@ module.exports = app => {
         }
 
         /**
+         * 保存变更信息
+         * @param {int} order_by - 表单提交的数据
+         * @return {void}
+         */
+        async saveOrderBy(order_by, newLedgerList = []) {
+            const transaction = await this.db.beginTransaction();
+            let result = [];
+            try {
+                const postData = { order_by };
+                let changeList = await this.ctx.service.changeAuditList.getList(this.ctx.change.cid);
+                if (order_by) {
+                    let i = 1;
+                    const updateArray = [];
+                    for (const cl of changeList) {
+                        updateArray.push({
+                            id: cl.id,
+                            order: i,
+                        });
+                        cl.order = i;
+                        i++;
+                    }
+                    if (updateArray.length > 0) await transaction.updateRows(this.ctx.service.changeAuditList.tableName, updateArray);
+                } else {
+                    await this.ctx.service.changeAuditList.saveLedgerListDatas(newLedgerList, null, order_by);
+                    changeList = await this.ctx.service.changeAuditList.getList(this.ctx.change.cid, order_by);
+                }
+                const options = {
+                    where: {
+                        cid: this.ctx.change.cid,
+                    },
+                };
+                await transaction.update(this.tableName, postData, options);
+                await transaction.commit();
+                result = changeList;
+            } catch (error) {
+                await transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
+
+        /**
          * 审批通过
          * @param {Number} pid 项目id
          * @param {int} postData - 表单提交的数据
@@ -712,7 +754,7 @@ module.exports = app => {
                 // console.log('auditors', auditors);
                 // console.log('postData', postData);
                 // 添加到消息推送表
-                const noticeContent = await this.getNoticeContent(pid, changeData.tid, changeData.cid, this.ctx.session.sessionUser.accountId);
+                const noticeContent = await this.getNoticeContent(pid, changeData.tid, changeData.cid, this.ctx.session.sessionUser.accountId, postData.sdesc);
                 const records = [];
                 auditors.forEach(auditor => {
                     records.push({
@@ -896,7 +938,7 @@ module.exports = app => {
                 // 获取所有审核人列表
                 const auditors = await this.ctx.service.changeAudit.getAllAuditors(changeData.tid);
                 // 添加到消息推送表
-                const noticeContent = await this.getNoticeContent(pid, changeData.tid, changeData.cid, this.ctx.session.sessionUser.accountId);
+                const noticeContent = await this.getNoticeContent(pid, changeData.tid, changeData.cid, this.ctx.session.sessionUser.accountId, postData.sdesc);
                 const records = [];
                 auditors.forEach(auditor => {
                     records.push({
@@ -1010,7 +1052,7 @@ module.exports = app => {
                 // 获取所有审核人列表
                 const auditors = await this.ctx.service.changeAudit.getAllAuditors(changeData.tid);
                 // 添加到消息推送表
-                const noticeContent = await this.getNoticeContent(pid, changeData.tid, changeData.cid, this.ctx.session.sessionUser.accountId);
+                const noticeContent = await this.getNoticeContent(pid, changeData.tid, changeData.cid, this.ctx.session.sessionUser.accountId, postData.sdesc);
                 const records = [];
                 auditors.forEach(auditor => {
                     records.push({
@@ -1148,14 +1190,13 @@ module.exports = app => {
          * @param pos - 查询的部位
          * @return {Promise<*>} - 可用的变更令列表
          */
-        async getValidChanges(tid, bills, pos) {
+        async getValidChanges(tender, stage, bills, pos) {
             const self = this;
             const getFilterPart = function(field, value) {
                 return value
                     ? field + ' = ' + self.db.escape(value)
                     : self.db.format("(?? = null or ?? = '')", [field, field]);
             };
-            const timesLen = 100;
             const filter = getFilterPart('cb.code', bills.b_code) +
                 ' And ' + getFilterPart('cb.name', bills.name) +
                 ' And ' + getFilterPart('cb.unit', bills.unit) +
@@ -1169,69 +1210,25 @@ module.exports = app => {
                 '  FROM ' + this.tableName + ' As c ' +
                 '  Left Join ' + this.ctx.service.changeAuditList.tableName + ' As cb On c.cid = cb.cid ' +
                 '  Left Join (' +
-                '    SELECT SUM(sc.qty) As used_amount, sc.cbid' +
-                '      FROM ' + this.ctx.service.stageChange.tableName + ' As sc' +
-                '      INNER JOIN (SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `flow`, cbid, sid ' +
-                '        FROM ' + this.ctx.service.stageChange.tableName +
-                '        WHERE tid = ?' +
-                '        GROUP BY cbid, sid' +
-                '      ) As MF' +
-                '      ON (sc.stimes * ' + timesLen + ' + sc.sorder) = MF.flow And sc.cbid = MF.cbid And sc.sid = MF.sid' +
-                '    GROUP BY sc.cbid' +
+                '    SELECT SUM(qty) As used_amount, cbid' +
+                '      FROM ' + this.ctx.service.stageChangeFinal.tableName +
+                '        WHERE tid = ? and sorder < ?' +
+                '        GROUP BY cbid' +
                 '  ) As scb ON cb.id = scb.cbid' +
                 '  WHERE c.tid = ? And c.status = ? And c.valid And ' + filter +
                 '  ORDER BY c.in_time';
-            const sqlParam = [tid, tid, audit.flow.status.checked];
-            const changes = await this.db.query(sql, sqlParam);
-            for (const c of changes) {
-                const aSql = 'SELECT ca.*, pa.name As u_name, pa.role As u_role ' +
-                    '  FROM ?? As ca ' +
-                    '  Left Join ?? As pa ' +
-                    '  On ca.uid = pa.id ' +
-                    '  Where ca.cid = ?';
-                const aSqlParam = [this.ctx.service.changeAtt.tableName, this.ctx.service.projectAccount.tableName, c.cid];
-                c.attachments = await this.db.query(aSql, aSqlParam);
-            }
-            return changes;
-        }
-
-        /**
-         * 查询审批人可用的变更令
-         * @param bills - 查询的清单
-         * @param pos - 查询的部位
-         * @return {Promise<*>} - 可用的变更令列表
-         */
-        async getAuditValidChanges(tid, bills, pos, times, order) {
-            const timesLen = 100;
-            const filter =
-                'cb.`code` = ' + this.db.escape(bills.b_code) +
-                ' And cb.`name` = ' + this.db.escape(bills.name) +
-                ' And cb.`unit` = ' + this.db.escape(bills.unit) +
-                ' And cb.`unit_price` = ' + this.db.escape(bills.unit_price) +
-                (pos ? ' And cb.`bwmx` = ' + this.db.escape(pos.name) : '');
-            const sql =
-                'SELECT c.cid, c.code, c.name, c.w_code, c.p_code, c.peg, c.org_name, c.org_code, c.new_name, c.new_code,' +
-                '    c.content, c.basis, c.memo, c.type, c.class, c.quality, c.company, c.charge, ' +
-                '    cb.id As cbid, cb.code As b_code, cb.name As b_name, cb.unit As b_unit, cb.samount As b_amount, cb.detail As b_detail, cb.bwmx As b_bwmx, cb.gcl_id, ' +
-                '    scb.used_amount' +
-                '  FROM ' + this.tableName + ' As c ' +
-                '  Left Join ' + this.ctx.service.changeAuditList.tableName + ' As cb On c.cid = cb.cid ' +
-                '  Left Join (' +
-                '    SELECT SUM(sc.qty) As used_amount, sc.cbid' +
-                '      FROM ' + this.ctx.service.stageChange.tableName + ' As sc' +
-                '      INNER JOIN (SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `flow`, cbid, sid ' +
-                '        FROM ' + this.ctx.service.stageChange.tableName +
-                '        WHERE tid = ? And (`stimes` < ? OR (`stimes` = ? AND `sorder` <= ?)) ' +
-                '        GROUP BY cbid, sid' +
-                '      ) As MF' +
-                '      ON (sc.stimes * ' + timesLen + ' + sc.sorder) = MF.flow And sc.cbid = MF.cbid And sc.sid = MF.sid' +
-                '    GROUP BY sc.cbid' +
-                '  ) As scb ON cb.id = scb.cbid' +
-                '  WHERE c.tid = ? And c.status = ? And c.valid And ' + filter +
-                '  ORDER BY c.in_time';
-            const sqlParam = [tid, times, times, order, tid, audit.flow.status.checked];
+            const sqlParam = [tender.id, stage.order, tender.id, audit.flow.status.checked];
             const changes = await this.db.query(sql, sqlParam);
             for (const c of changes) {
+                let sSql = 'SELECT * FROM ' + this.ctx.service.stageChange.tableName + ' WHERE sid = ? and cbid = ?';
+                const sSqlParam = [stage.id, c.cbid];
+                if (stage.readOnly) {
+                    sSql = sSql + ' and (stimes < ? or (stimes = ? and sorder <= ?))';
+                    sSqlParam.push(stage.curTimes, stage.curTimes, stage.curOrder);
+                }
+                const stageUsedData = await this.db.query(sSql, sSqlParam);
+                const filter = this.ctx.helper.filterLastestData(stageUsedData, ['pid', 'lid'], 'stimes', 'sorder');
+                c.stage_used_amount = this.ctx.helper.sum(filter.map(x => { return x.qty }));
                 const aSql = 'SELECT ca.*, pa.name As u_name, pa.role As u_role ' +
                     '  FROM ?? As ca ' +
                     '  Left Join ?? As pa ' +
@@ -1358,7 +1355,7 @@ module.exports = app => {
                 // 获取所有审核人列表
                 const auditors = await this.ctx.service.changeAudit.getAllAuditors(changeData.tid);
                 // 添加到消息推送表
-                const noticeContent = await this.getNoticeContent(pid, changeData.tid, changeData.cid, this.ctx.session.sessionUser.accountId);
+                const noticeContent = await this.getNoticeContent(pid, changeData.tid, changeData.cid, this.ctx.session.sessionUser.accountId, '发起重新审批');
                 const records = [];
                 auditors.forEach(auditor => {
                     records.push({
@@ -1601,7 +1598,7 @@ module.exports = app => {
          * @param {Number} cid 变更id
          * @param {Number} uid 审核人id
          */
-        async getNoticeContent(pid, tid, cid, uid) {
+        async getNoticeContent(pid, tid, cid, uid, opinion = '') {
             const noticeSql =
                 'SELECT * FROM (SELECT ' +
                 '  t.`id` As `tid`, t.`name`, c.`cid`, c.`code` As `c_code`, pa.`name` As `su_name`, pa.role As `su_role`' +
@@ -1611,6 +1608,9 @@ module.exports = app => {
                 '  WHERE  t.`project_id` = ? ) as new_t GROUP BY new_t.`tid`';
             const noticeSqlParam = [this.ctx.service.tender.tableName, tid, this.ctx.service.change.tableName, cid, this.ctx.service.projectAccount.tableName, uid, pid];
             const content = await this.db.query(noticeSql, noticeSqlParam);
+            if (content.length) {
+                content[0].opinion = opinion;
+            }
             return content.length ? JSON.stringify(content[0]) : '';
         }
 

+ 66 - 44
app/service/change_apply_audit.js

@@ -271,16 +271,14 @@ module.exports = app => {
                     id: caId, status: auditConst.status.checking,
                 });
                 // 微信模板通知
-                // const materialInfo = await this.ctx.service.material.getDataById(materialId);
-                // const wechatData = {
-                //     qi: materialInfo.order,
-                //     status: wxConst.status.check,
-                //     tips: wxConst.tips.check,
-                //     begin_time: Date.parse(new Date()),
-                //     m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, 2), this.ctx.helper.round(materialInfo.ex_tp, 2)),
-                //     hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.rate/100), 2)),
-                // };
-                // await this.ctx.helper.sendWechat(audit.aid, smsTypeConst.const.TC, smsTypeConst.judge.approval.toString(), wxConst.template.material, wechatData);
+                const wechatData = {
+                    type: 'apply',
+                    status: wxConst.status.check,
+                    tips: wxConst.tips.check,
+                    code: this.ctx.session.sessionProject.code,
+                    c_name: this.ctx.change.name,
+                };
+                await this.ctx.helper.sendWechat(audit.aid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
 
                 // todo 更新标段tender状态 ?
                 await transaction.commit();
@@ -309,13 +307,38 @@ module.exports = app => {
         }
 
         /**
+         * 获取审核人审核的次数
+         *
+         * @param auditorId
+         * @return {Promise<*>}
+         */
+        async getCountByChecked(auditorId) {
+            return await this.db.count(this.tableName, { aid: auditorId, status: [auditConst.status.checked, auditConst.status.checkNo] });
+        }
+
+        /**
+         * 获取最近一次审批结束时间
+         *
+         * @param auditorId
+         * @return {Promise<*>}
+         */
+        async getLastEndTimeByChecked(auditorId) {
+            const sql = 'SELECT `end_time` FROM ?? WHERE `aid` = ? ' +
+                'AND `status` in (' + this.ctx.helper.getInArrStrSqlFilter([auditConst.status.checked, auditConst.status.checkNo]) + ') ORDER BY `end_time` DESC';
+            const sqlParam = [this.tableName, auditorId];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result ? result.end_time : null;
+        }
+
+
+        /**
          * 用于添加推送所需的content内容
          * @param {Number} pid 项目id
          * @param {Number} tid 台账id
          * @param {Number} caId 立项书id
          * @param {Number} uid 审批人id
          */
-        async getNoticeContent(pid, tid, caId, uid) {
+        async getNoticeContent(pid, tid, caId, uid, opinion = '') {
             const noticeSql = 'SELECT * FROM (SELECT ' +
                 '  t.`id` As `tid`, ma.`caid`, m.`code` as `c_code`, t.`name`, pa.`name` As `su_name`, pa.role As `su_role`' +
                 '  FROM (SELECT * FROM ?? WHERE `id` = ? ) As t' +
@@ -325,6 +348,9 @@ module.exports = app => {
                 '  WHERE  t.`project_id` = ? ) as new_t GROUP BY new_t.`tid`';
             const noticeSqlParam = [this.ctx.service.tender.tableName, tid, this.ctx.service.changeApply.tableName, caId, this.tableName, this.ctx.service.projectAccount.tableName, uid, pid];
             const content = await this.db.query(noticeSql, noticeSqlParam);
+            if (content.length) {
+                content[0].opinion = opinion;
+            }
             return content.length ? JSON.stringify(content[0]) : '';
         }
 
@@ -374,7 +400,7 @@ module.exports = app => {
                 await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
 
                 // 获取推送必要信息
-                const noticeContent = await this.getNoticeContent(pid, audit.tid, caId, audit.aid);
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, caId, audit.aid, checkData.opinion);
                 // 添加推送
                 const records = [{ pid, type: pushType.changeApply, uid: this.ctx.change.uid, status: auditConst.status.checked, content: noticeContent }];
                 auditors.forEach(audit => {
@@ -391,17 +417,15 @@ module.exports = app => {
                     await transaction.update(this.ctx.service.changeApply.tableName, {
                         id: caId, status: auditConst.status.checking,
                     });
-
                     // 微信模板通知
-                    // const wechatData = {
-                    //     qi: materialInfo.order,
-                    //     status: wxConst.status.check,
-                    //     tips: wxConst.tips.check,
-                    //     begin_time: Date.parse(begin_audit.begin_time),
-                    //     m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, 2), this.ctx.helper.round(materialInfo.ex_tp, 2)),
-                    //     hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.rate/100), 2)),
-                    // };
-                    // await this.ctx.helper.sendWechat(nextAudit.aid, smsTypeConst.const.TC, smsTypeConst.judge.approval.toString(), wxConst.template.material, wechatData);
+                    const wechatData = {
+                        type: 'apply',
+                        status: wxConst.status.check,
+                        tips: wxConst.tips.check,
+                        code: this.ctx.session.sessionProject.code,
+                        c_name: this.ctx.change.name,
+                    };
+                    await this.ctx.helper.sendWechat(nextAudit.aid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
                 } else {
                     // 本期结束
                     // 生成截止本期数据 final数据
@@ -414,16 +438,15 @@ module.exports = app => {
                     });
 
                     // 微信模板通知
-                    // const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), materialInfo.user_id));
-                    // const wechatData = {
-                    //     qi: materialInfo.order,
-                    //     status: wxConst.status.success,
-                    //     tips: wxConst.tips.success,
-                    //     begin_time: Date.parse(begin_audit.begin_time),
-                    //     m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, 2), this.ctx.helper.round(materialInfo.ex_tp, 2)),
-                    //     hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.rate/100), 2)),
-                    // };
-                    // await this.ctx.helper.sendWechat(users, smsTypeConst.const.TC, smsTypeConst.judge.result.toString(), wxConst.template.material, wechatData);
+                    const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), this.ctx.change.uid));
+                    const wechatData = {
+                        type: 'apply',
+                        status: wxConst.status.success,
+                        tips: wxConst.tips.success,
+                        code: this.ctx.session.sessionProject.code,
+                        c_name: this.ctx.change.name,
+                    };
+                    await this.ctx.helper.sendWechat(users, smsTypeConst.const.BG, smsTypeConst.judge.result.toString(), wxConst.template.change, wechatData);
                 }
                 await transaction.commit();
             } catch (err) {
@@ -453,7 +476,7 @@ module.exports = app => {
             try {
                 await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
                 // 添加到消息推送表
-                const noticeContent = await this.getNoticeContent(pid, audit.tid, caId, audit.aid);
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, caId, audit.aid, checkData.opinion);
                 const records = [{ pid, type: pushType.changeApply, uid: this.ctx.change.uid, status: auditConst.status.checkNo, content: noticeContent }];
                 auditors.forEach(audit => {
                     records.push({ pid, type: pushType.changeApply, uid: audit.aid, status: auditConst.status.checkNo, content: noticeContent });
@@ -471,17 +494,16 @@ module.exports = app => {
                 //     mid: materialId,
                 //     order: 1,
                 // });
-                // const materialInfo = await this.ctx.service.material.getDataById(materialId);
-                // const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), materialInfo.user_id));
-                // const wechatData = {
-                //     qi: materialInfo.order,
-                //     status: wxConst.status.back,
-                //     tips: wxConst.tips.back,
-                //     begin_time: Date.parse(begin_audit.begin_time),
-                //     m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, 2), this.ctx.helper.round(materialInfo.ex_tp, 2)),
-                //     hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.rate/100), 2)),
-                // };
-                // await this.ctx.helper.sendWechat(users, smsTypeConst.const.TC, smsTypeConst.judge.result.toString(), wxConst.template.material, wechatData);
+                // 微信模板通知
+                const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), this.ctx.change.uid));
+                const wechatData = {
+                    type: 'apply',
+                    status: wxConst.status.back,
+                    tips: wxConst.tips.back,
+                    code: this.ctx.session.sessionProject.code,
+                    c_name: this.ctx.change.name,
+                };
+                await this.ctx.helper.sendWechat(users, smsTypeConst.const.BG, smsTypeConst.judge.result.toString(), wxConst.template.change, wechatData);
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();

+ 28 - 0
app/service/change_audit.js

@@ -312,6 +312,34 @@ module.exports = app => {
         }
 
         /**
+         * 获取审核人审核的次数
+         *
+         * @param auditorId
+         * @return {Promise<*>}
+         */
+        async getCountByChecked(auditorId) {
+            const sql = 'Select count(*) as count FROM ?? WHERE uid = ? AND usite != 0 AND status in (' + this.ctx.helper.getInArrStrSqlFilter([auditConst.status.checked, auditConst.status.back, auditConst.status.backnew]) +')';
+            const sqlParam = [this.tableName, auditorId];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result.count ? result.count : 0;
+            // return await this.db.count(this.tableName, { aid: auditorId, status: [auditConst.status.checked, auditConst.status.checkNo] });
+        }
+
+        /**
+         * 获取最近一次审批结束时间
+         *
+         * @param auditorId
+         * @return {Promise<*>}
+         */
+        async getLastEndTimeByChecked(auditorId) {
+            const sql = 'SELECT `sin_time` FROM ?? WHERE `uid` = ? AND usite != 0 ' +
+                'AND `status` in (' + this.ctx.helper.getInArrStrSqlFilter([auditConst.status.checked, auditConst.status.back, auditConst.status.backnew]) + ') ORDER BY `sin_time` DESC';
+            const sqlParam = [this.tableName, auditorId];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result ? result.sin_time : null;
+        }
+
+        /**
          * 获取 某时间后 审批进度更新的 变更令
          * @param {Number} pid - 查询标段
          * @param {Number} uid - 查询人

+ 216 - 84
app/service/change_audit_list.js

@@ -27,63 +27,63 @@ module.exports = app => {
          * 取出变更令清单列表,并按台账清单在前,空白清单在后排序
          * @return {void}
          */
-        async getList(cid) {
+        async getList(cid, order_by = this.ctx.change.order_by) {
+            if (order_by) {
+                return await this.getAllDataByCondition({ where: { cid }, orders: [['order', 'asc']] });
+            }
             const sql = 'SELECT * FROM ?? WHERE `cid` = ? ORDER BY `lid` = "0", `id` asc';
             const sqlParam = [this.tableName, cid];
             return await this.db.query(sql, sqlParam);
         }
 
         /**
-         * 添加空白变更清单
-         * @return {void}
+         * 移除清单时,同步其后清单order
+         * @param transaction - 事务
+         * @param {Number} cid - 变更cid
+         * @param {Number} order - order之后的
+         * @return {Promise<*>}
+         * @private
          */
-        async add(data) {
-            if (!this.ctx.tender || !this.ctx.change) {
-                throw '数据错误';
-            }
-            const insertData = {
-                tid: this.ctx.tender.id,
-                cid: this.ctx.change.cid,
-                lid: '0',
-                code: '',
-                name: '',
-                bwmx: '',
-                unit: '',
-                unit_price: null,
-                oamount: 0,
-                camount: 0,
-                samount: '',
-                detail: '',
-                spamount: 0,
-                xmj_code: null,
-                xmj_jldy: null,
-                xmj_dwgc: null,
-                xmj_fbgc: null,
-                xmj_fxgc: null,
-                gcl_id: '',
-            };
-            // 新增工料
-            const result = await this.db.insert(this.tableName, insertData);
-            if (result.affectedRows === 0) {
-                throw '新增空白清单数据失败';
-            }
-            return await this.getDataById(result.insertId);
+        async _syncOrder(transaction, cid, order, selfOperate = '-', num = 1) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('cid', {
+                value: this.db.escape(cid),
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere('order', {
+                value: order,
+                operate: '>=',
+            });
+            this.sqlBuilder.setUpdateData('order', {
+                value: num,
+                selfOperate,
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+            const data = await transaction.query(sql, sqlParam);
+            return data;
         }
 
         /**
-         * 批量添加空白变更清单
+         * 添加空白变更清单
          * @return {void}
          */
-        async batchAdd(data) {
+        async add(data) {
             if (!this.ctx.tender || !this.ctx.change) {
                 throw '数据错误';
             }
-            const num = data.num ? parseInt(data.num) : 0;
-            if (num < 1 || num > 100) {
-                throw '批量添加的空白清单数目不能小于1或大于100';
-            }
-            const insertArray = [];
-            for (let i = 0; i < num; i++) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                let order = null;
+                if (this.ctx.change.order_by) {
+                    if (data) {
+                        order = parseInt(data) + 1;
+                        // order以下的清单+1
+                        await this._syncOrder(transaction, this.ctx.change.cid, order, '+');
+                    } else {
+                        order = await this.count({ cid: this.ctx.change.cid });
+                        order = order ? order + 1 : 1;
+                    }
+                }
                 const insertData = {
                     tid: this.ctx.tender.id,
                     cid: this.ctx.change.cid,
@@ -104,19 +104,88 @@ module.exports = app => {
                     xmj_fbgc: null,
                     xmj_fxgc: null,
                     gcl_id: '',
+                    order,
                 };
-                insertArray.push(insertData);
+                // 新增工料
+                const result = await transaction.insert(this.tableName, insertData);
+                if (result.affectedRows === 0) {
+                    throw '新增空白清单数据失败';
+                }
+                await transaction.commit();
+                return await this.getDataById(result.insertId);
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
             }
-            // 新增工料
-            const result = await this.db.insert(this.tableName, insertArray);
-            if (result.affectedRows !== num) {
-                throw '批量添加空白清单数据失败';
+        }
+
+        /**
+         * 批量添加空白变更清单
+         * @return {void}
+         */
+        async batchAdd(data) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
             }
-            // 获取刚批量添加的所有list
-            for (let j = 0; j < num; j++) {
-                insertArray[j].id = result.insertId + j;
+            const transaction = await this.db.beginTransaction();
+            try {
+                const num = data.num ? parseInt(data.num) : 0;
+                if (num < 1 || num > 100) {
+                    throw '批量添加的空白清单数目不能小于1或大于100';
+                }
+                let order = null;
+                if (this.ctx.change.order_by) {
+                    if (data) {
+                        order = parseInt(data.postData) + 1;
+                        // order以下的清单+1
+                        await this._syncOrder(transaction, this.ctx.change.cid, order, '+', num);
+                    } else {
+                        order = await this.count({ cid: this.ctx.change.cid });
+                        order = order ? order + 1 : 1;
+                    }
+                }
+                const insertArray = [];
+                for (let i = 0; i < num; i++) {
+                    const insertData = {
+                        tid: this.ctx.tender.id,
+                        cid: this.ctx.change.cid,
+                        lid: '0',
+                        code: '',
+                        name: '',
+                        bwmx: '',
+                        unit: '',
+                        unit_price: null,
+                        oamount: 0,
+                        camount: 0,
+                        samount: '',
+                        detail: '',
+                        spamount: 0,
+                        xmj_code: null,
+                        xmj_jldy: null,
+                        xmj_dwgc: null,
+                        xmj_fbgc: null,
+                        xmj_fxgc: null,
+                        gcl_id: '',
+                        order: order ? order + i : null,
+                    };
+                    insertArray.push(insertData);
+                }
+                // 新增工料
+                const result = await transaction.insert(this.tableName, insertArray);
+                if (result.affectedRows !== num) {
+                    throw '批量添加空白清单数据失败';
+                }
+                await transaction.commit();
+                // // 获取刚批量添加的所有list
+                // for (let j = 0; j < num; j++) {
+                //     insertArray[j].id = result.insertId + j;
+                // }
+                // return insertArray;
+                return await this.getList(this.ctx.change.cid);
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
             }
-            return insertArray;
         }
 
         /**
@@ -124,14 +193,18 @@ module.exports = app => {
          * @param {int} id 清单id
          * @return {void}
          */
-        async del(id) {
+        async del(data) {
             if (!this.ctx.tender || !this.ctx.change) {
                 throw '数据错误';
             }
             const transaction = await this.db.beginTransaction();
             try {
                 // 判断是否可删
-                await transaction.delete(this.tableName, { id });
+                await transaction.delete(this.tableName, { id: data.ids });
+                // // order以下的清单-1
+                if (this.ctx.change.order_by) {
+                    await this._syncOrder(transaction, this.ctx.change.cid, data.postData, '-', data.ids.length);
+                }
                 // 重新算变更令总额
                 await this.calcCamountSum(transaction);
                 await transaction.commit();
@@ -202,7 +275,7 @@ module.exports = app => {
          * @param {Object} datas 内容
          * @return {void}
          */
-        async saveLedgerListDatas(datas) {
+        async saveLedgerListDatas(datas, data = null, order_by = this.ctx.change.order_by) {
             if (!this.ctx.tender || !this.ctx.change) {
                 throw '数据错误';
             }
@@ -210,47 +283,64 @@ module.exports = app => {
             // 判断t_type是否为费用
             const transaction = await this.db.beginTransaction();
             try {
-                const sql1 = 'SELECT a.* FROM ?? as b LEFT JOIN ?? as a ON b.cbid = a.id WHERE b.cid = ? GROUP BY b.cbid';
-                const sqlParam1 = [this.ctx.service.stageChange.tableName, this.tableName, this.ctx.change.cid];
-                const usedList = await transaction.query(sql1, sqlParam1);
-                // 先删除原本的台账清单数据
-                const sql = 'DELETE FROM ?? WHERE cid = ? and lid != "0"';
-                const sqlParam = [this.tableName, this.ctx.change.cid];
-                await transaction.query(sql, sqlParam);
+                let usedList = [];
+                let order = null;
+                if (order_by) {
+                    if (data) {
+                        order = parseInt(data) + 1;
+                        // order以下的清单+1
+                        await this._syncOrder(transaction, this.ctx.change.cid, order, '+', datas.length);
+                    } else {
+                        order = await this.count({ cid: this.ctx.change.cid });
+                        order = order ? order + 1 : 1;
+                    }
+                } else {
+                    const sql1 = 'SELECT a.* FROM ?? as b LEFT JOIN ?? as a ON b.cbid = a.id WHERE b.cid = ? GROUP BY b.cbid';
+                    const sqlParam1 = [this.ctx.service.stageChange.tableName, this.tableName, this.ctx.change.cid];
+                    usedList = await transaction.query(sql1, sqlParam1);
+                    // 先删除原本的台账清单数据
+                    const sql = 'DELETE FROM ?? WHERE cid = ? and lid != "0"';
+                    const sqlParam = [this.tableName, this.ctx.change.cid];
+                    await transaction.query(sql, sqlParam);
+                }
                 const insertDatas = [];
                 for (const data of datas) {
                     data.tid = this.ctx.tender.id;
                     data.cid = this.ctx.change.cid;
                     data.spamount = data.camount;
                     data.samount = '';
+                    data.order = order ? order : null;
+                    order = order ? order + 1 : null;
                     insertDatas.push(data);
                 }
                 if (insertDatas.length > 0) await transaction.insert(this.tableName, insertDatas);
                 await this.calcCamountSum(transaction);
-                // 更新stage_change和stage_change_final的cbid
-                if (usedList.length > 0) {
-                    const updateList = [];
-                    const sql2 = 'SELECT * FROM ?? WHERE `cid` = ? AND `lid` != "0"';
-                    const sqlParam2 = [this.tableName, this.ctx.change.cid];
-                    const newList = await transaction.query(sql2, sqlParam2);
-                    // const newList = await transaction.select(this.tableName, { where: { cid: this.ctx.change.cid } });
-                    for (const used of usedList) {
-                        const newone = this._.find(newList, { code: used.code, lid: used.lid, gcl_id: used.gcl_id, bwmx: used.bwmx });
-                        if (newone) {
-                            updateList.push({
-                                row: {
-                                    cbid: newone.id,
-                                },
-                                where: {
-                                    cid: this.ctx.change.cid,
-                                    cbid: used.id,
-                                },
-                            });
+                if (!order_by) {
+                    // 更新stage_change和stage_change_final的cbid
+                    if (usedList.length > 0) {
+                        const updateList = [];
+                        const sql2 = 'SELECT * FROM ?? WHERE `cid` = ? AND `lid` != "0"';
+                        const sqlParam2 = [this.tableName, this.ctx.change.cid];
+                        const newList = await transaction.query(sql2, sqlParam2);
+                        // const newList = await transaction.select(this.tableName, { where: { cid: this.ctx.change.cid } });
+                        for (const used of usedList) {
+                            const newone = this._.find(newList, { code: used.code, lid: used.lid, gcl_id: used.gcl_id, bwmx: used.bwmx });
+                            if (newone) {
+                                updateList.push({
+                                    row: {
+                                        cbid: newone.id,
+                                    },
+                                    where: {
+                                        cid: this.ctx.change.cid,
+                                        cbid: used.id,
+                                    },
+                                });
+                            }
+                        }
+                        if (updateList.length > 0) {
+                            await transaction.updateRows(this.ctx.service.stageChange.tableName, updateList);
+                            await transaction.updateRows(this.ctx.service.stageChangeFinal.tableName, updateList);
                         }
-                    }
-                    if (updateList.length > 0) {
-                        await transaction.updateRows(this.ctx.service.stageChange.tableName, updateList);
-                        await transaction.updateRows(this.ctx.service.stageChangeFinal.tableName, updateList);
                     }
                 }
                 await transaction.commit();
@@ -890,6 +980,48 @@ module.exports = app => {
                 await transaction.query(pSql, []);
             }
         }
+
+        async checkedChangeBills(tid) {
+            const DefaultDecimal = this.ctx.tender.info.decimal.tp;
+            const sql = 'SELECT cal.*, c.tp_decimal FROM ' + this.ctx.service.changeAuditList.tableName + ' cal LEFT JOIN ' + this.ctx.service.change.tableName + ' c on cal.cid = c.cid where c.tid = ? and c.valid and c.status = ?';
+            const changeBills = await this.db.query(sql, [tid, audit.flow.status.checked]);
+            changeBills.forEach(x => {
+                x.tp_decimal = x.tp_decimal !== 0 ? x.tp_decimal : DefaultDecimal
+            });
+            return changeBills;
+        }
+
+        /**
+         * 交换两个清单的顺序
+         * @param {Number} id1 - 工料1的id
+         * @param {Number} id2 - 工料2的id
+         * @returns {Promise<void>}
+         */
+        async changeOrder(datas) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+            // const bill1 = await this.getDataByCondition({ tid: this.ctx.tender.id, id: id1 });
+            // const bill2 = await this.getDataByCondition({ tid: this.ctx.tender.id, id: id2 });
+            // if (!bill1 || !bill2) {
+            //     throw '数据错误';
+            // }
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // const order = bill1.order;
+                // bill1.order = bill2.order;
+                // bill2.order = order;
+                // await transaction.update(this.tableName, { id: bill1.id, order: bill1.order });
+                // await transaction.update(this.tableName, { id: bill2.id, order: bill2.order });
+                await transaction.updateRows(this.tableName, datas);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
     }
 
     return ChangeAuditList;

+ 1 - 1
app/service/change_plan.js

@@ -134,7 +134,7 @@ module.exports = app => {
             let sql = '';
             let sqlParam = '';
             if (this.ctx.tender.isTourist && status === 0) {
-                sql = 'SELECT a.*, p.name as account_name FROM ?? As a LEFT JOIN ?? AS p On a.notice_uid = p.id WHERE a.tid = ?';
+                sql = 'SELECT a.*, p.name as account_name FROM ?? As a LEFT JOIN ?? AS p On a.uid = p.id WHERE a.tid = ?';
                 sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId];
             } else {
                 switch (status) {

+ 110 - 50
app/service/change_plan_audit.js

@@ -95,7 +95,7 @@ module.exports = app => {
             switch (status) {
                 case auditConst.status.checking :
                 case auditConst.status.checked :
-                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`cpid`, la.`aid`, la.`order` ' +
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`cpid`, la.`aid`, la.`order`, la.`status` ' +
                         '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
                         '  WHERE la.`cpid` = ? and la.`status` = ? ' +
                         '  ORDER BY la.`times` desc, la.`order` desc';
@@ -103,7 +103,7 @@ module.exports = app => {
                     auditor = await this.db.queryOne(sql, sqlParam);
                     break;
                 case auditConst.status.checkNo :
-                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`cpid`, la.`aid`, la.`order` ' +
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`cpid`, la.`aid`, la.`order`, la.`status` ' +
                         '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
                         '  WHERE la.`cpid` = ? and la.`status` = ? and la.`times` = ?' +
                         '  ORDER BY la.`times` desc, la.`order` desc';
@@ -271,16 +271,18 @@ module.exports = app => {
                     id: cpId, status: auditConst.status.checking,
                 });
                 // 微信模板通知
-                // const materialInfo = await this.ctx.service.material.getDataById(materialId);
-                // const wechatData = {
-                //     qi: materialInfo.order,
-                //     status: wxConst.status.check,
-                //     tips: wxConst.tips.check,
-                //     begin_time: Date.parse(new Date()),
-                //     m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, 2), this.ctx.helper.round(materialInfo.ex_tp, 2)),
-                //     hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.rate/100), 2)),
-                // };
-                // await this.ctx.helper.sendWechat(audit.aid, smsTypeConst.const.TC, smsTypeConst.judge.approval.toString(), wxConst.template.material, wechatData);
+                const shenpiUrl = await this.ctx.helper.urlToShort(
+                    this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/change/plan/' + cpId + '/info#shenpi'
+                );
+                const wechatData = {
+                    type: 'plan',
+                    wap_url: shenpiUrl,
+                    status: wxConst.status.check,
+                    tips: wxConst.tips.check,
+                    code: this.ctx.session.sessionProject.code,
+                    c_name: this.ctx.change.name,
+                };
+                await this.ctx.helper.sendWechat(audit.aid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
 
                 // todo 更新标段tender状态 ?
                 await transaction.commit();
@@ -309,13 +311,37 @@ module.exports = app => {
         }
 
         /**
+         * 获取审核人审核的次数
+         *
+         * @param auditorId
+         * @return {Promise<*>}
+         */
+        async getCountByChecked(auditorId) {
+            return await this.db.count(this.tableName, { aid: auditorId, status: [auditConst.status.checked, auditConst.status.checkNo] });
+        }
+
+        /**
+         * 获取最近一次审批结束时间
+         *
+         * @param auditorId
+         * @return {Promise<*>}
+         */
+        async getLastEndTimeByChecked(auditorId) {
+            const sql = 'SELECT `end_time` FROM ?? WHERE `aid` = ? ' +
+                'AND `status` in (' + this.ctx.helper.getInArrStrSqlFilter([auditConst.status.checked, auditConst.status.checkNo]) + ') ORDER BY `end_time` DESC';
+            const sqlParam = [this.tableName, auditorId];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result ? result.end_time : null;
+        }
+
+        /**
          * 用于添加推送所需的content内容
          * @param {Number} pid 项目id
          * @param {Number} tid 台账id
          * @param {Number} cpId 方案id
          * @param {Number} uid 审批人id
          */
-        async getNoticeContent(pid, tid, cpId, uid) {
+        async getNoticeContent(pid, tid, cpId, uid, opinion = '') {
             const noticeSql = 'SELECT * FROM (SELECT ' +
                 '  t.`id` As `tid`, ma.`cpid`, m.`code` as `c_code`, t.`name`, pa.`name` As `su_name`, pa.role As `su_role`' +
                 '  FROM (SELECT * FROM ?? WHERE `id` = ? ) As t' +
@@ -325,6 +351,9 @@ module.exports = app => {
                 '  WHERE  t.`project_id` = ? ) as new_t GROUP BY new_t.`tid`';
             const noticeSqlParam = [this.ctx.service.tender.tableName, tid, this.ctx.service.changePlan.tableName, cpId, this.tableName, this.ctx.service.projectAccount.tableName, uid, pid];
             const content = await this.db.query(noticeSql, noticeSqlParam);
+            if (content.length) {
+                content[0].opinion = opinion;
+            }
             return content.length ? JSON.stringify(content[0]) : '';
         }
 
@@ -374,7 +403,7 @@ module.exports = app => {
                 await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
 
                 // 获取推送必要信息
-                const noticeContent = await this.getNoticeContent(pid, audit.tid, cpId, audit.aid);
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, cpId, audit.aid, checkData.opinion);
                 // 添加推送
                 const records = [{ pid, type: pushType.changePlan, uid: this.ctx.change.uid, status: auditConst.status.checked, content: noticeContent }];
                 auditors.forEach(audit => {
@@ -396,15 +425,18 @@ module.exports = app => {
                     });
 
                     // 微信模板通知
-                    // const wechatData = {
-                    //     qi: materialInfo.order,
-                    //     status: wxConst.status.check,
-                    //     tips: wxConst.tips.check,
-                    //     begin_time: Date.parse(begin_audit.begin_time),
-                    //     m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, 2), this.ctx.helper.round(materialInfo.ex_tp, 2)),
-                    //     hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.rate/100), 2)),
-                    // };
-                    // await this.ctx.helper.sendWechat(nextAudit.aid, smsTypeConst.const.TC, smsTypeConst.judge.approval.toString(), wxConst.template.material, wechatData);
+                    const shenpiUrl = await this.ctx.helper.urlToShort(
+                        this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/change/plan/' + cpId + '/info#shenpi'
+                    );
+                    const wechatData = {
+                        type: 'plan',
+                        wap_url: shenpiUrl,
+                        status: wxConst.status.check,
+                        tips: wxConst.tips.check,
+                        code: this.ctx.session.sessionProject.code,
+                        c_name: this.ctx.change.name,
+                    };
+                    await this.ctx.helper.sendWechat(nextAudit.aid, smsTypeConst.const.BG, smsTypeConst.judge.approval.toString(), wxConst.template.change, wechatData);
                 } else {
                     // 本期结束
                     // 生成截止本期数据 final数据
@@ -415,16 +447,19 @@ module.exports = app => {
                     });
 
                     // 微信模板通知
-                    // const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), materialInfo.user_id));
-                    // const wechatData = {
-                    //     qi: materialInfo.order,
-                    //     status: wxConst.status.success,
-                    //     tips: wxConst.tips.success,
-                    //     begin_time: Date.parse(begin_audit.begin_time),
-                    //     m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, 2), this.ctx.helper.round(materialInfo.ex_tp, 2)),
-                    //     hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.rate/100), 2)),
-                    // };
-                    // await this.ctx.helper.sendWechat(users, smsTypeConst.const.TC, smsTypeConst.judge.result.toString(), wxConst.template.material, wechatData);
+                    const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), this.ctx.change.uid));
+                    const shenpiUrl = await this.ctx.helper.urlToShort(
+                        this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/change/plan/' + cpId + '/info#shenpi'
+                    );
+                    const wechatData = {
+                        type: 'plan',
+                        wap_url: shenpiUrl,
+                        status: wxConst.status.success,
+                        tips: wxConst.tips.success,
+                        code: this.ctx.session.sessionProject.code,
+                        c_name: this.ctx.change.name,
+                    };
+                    await this.ctx.helper.sendWechat(users, smsTypeConst.const.BG, smsTypeConst.judge.result.toString(), wxConst.template.change, wechatData);
                 }
                 await transaction.commit();
             } catch (err) {
@@ -454,7 +489,7 @@ module.exports = app => {
             try {
                 await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
                 // 添加到消息推送表
-                const noticeContent = await this.getNoticeContent(pid, audit.tid, cpId, audit.aid);
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, cpId, audit.aid, checkData.opinion);
                 const records = [{ pid, type: pushType.changePlan, uid: this.ctx.change.uid, status: auditConst.status.checkNo, content: noticeContent }];
                 auditors.forEach(audit => {
                     records.push({ pid, type: pushType.changePlan, uid: audit.aid, status: auditConst.status.checkNo, content: noticeContent });
@@ -470,21 +505,19 @@ module.exports = app => {
                 // 清单审批值删除并重算变更金额
                 await this.ctx.service.changePlanList.delAuditAmount(transaction, cpId);
                 // 微信模板通知
-                // const begin_audit = await this.getDataByCondition({
-                //     mid: materialId,
-                //     order: 1,
-                // });
-                // const materialInfo = await this.ctx.service.material.getDataById(materialId);
-                // const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), materialInfo.user_id));
-                // const wechatData = {
-                //     qi: materialInfo.order,
-                //     status: wxConst.status.back,
-                //     tips: wxConst.tips.back,
-                //     begin_time: Date.parse(begin_audit.begin_time),
-                //     m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, 2), this.ctx.helper.round(materialInfo.ex_tp, 2)),
-                //     hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.rate/100), 2)),
-                // };
-                // await this.ctx.helper.sendWechat(users, smsTypeConst.const.TC, smsTypeConst.judge.result.toString(), wxConst.template.material, wechatData);
+                const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), this.ctx.change.uid));
+                const shenpiUrl = await this.ctx.helper.urlToShort(
+                    this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/change/plan/' + cpId + '/info#shenpi'
+                );
+                const wechatData = {
+                    type: 'plan',
+                    wap_url: shenpiUrl,
+                    status: wxConst.status.back,
+                    tips: wxConst.tips.back,
+                    code: this.ctx.session.sessionProject.code,
+                    c_name: this.ctx.change.name,
+                };
+                await this.ctx.helper.sendWechat(users, smsTypeConst.const.BG, smsTypeConst.judge.result.toString(), wxConst.template.change, wechatData);
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
@@ -526,7 +559,34 @@ module.exports = app => {
             const sqlParam = [tenderId];
             return this.db.query(sql, sqlParam);
         }
-    }
 
+        /**
+         * 取待审批期列表(wap用)
+         *
+         * @param auditorId
+         * @return {Promise<*>}
+         */
+        async getAuditChangePlanByWap(auditorId) {
+            const sql =
+                'SELECT sa.`aid`, sa.`times`, sa.`begin_time`, sa.`end_time`, sa.`tid`, sa.`cpid`,' +
+                '    s.*,' +
+                '    t.`name` as `t_name`, t.`project_id`, t.`type`, t.`user_id`,' +
+                '    ti.`deal_info`, ti.`decimal` ' +
+                '  FROM ?? AS sa' +
+                '    Left Join ?? AS s On sa.`cpid` = s.`id`' +
+                '    Left Join ?? As t On sa.`tid` = t.`id`' +
+                '    Left Join ?? AS ti ON ti.`tid` = t.`id`' +
+                '  WHERE sa.`aid` = ? and sa.`status` = ?';
+            const sqlParam = [
+                this.tableName,
+                this.ctx.service.changePlan.tableName,
+                this.ctx.service.tender.tableName,
+                this.ctx.service.tenderInfo.tableName,
+                auditorId,
+                auditConst.status.checking,
+            ];
+            return await this.db.query(sql, sqlParam);
+        }
+    }
     return ChangePlanAudit;
 };

+ 0 - 0
app/service/change_project_audit.js


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