فهرست منبع

Merge branch 'master' of http://121.41.40.202:3000/zhuwei/bladex

huangtf 1 سال پیش
والد
کامیت
2bd3f5a4f9
98فایلهای تغییر یافته به همراه3645 افزوده شده و 365 حذف شده
  1. 8 1
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/ImageClassificationFileDTO.java
  2. 20 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/TrialSummaryMonthlyEditDTO.java
  3. 32 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/TrialSummaryMonthlyPageDTO.java
  4. 32 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/entity/ArchiveFile.java
  5. 9 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/entity/ImageClassificationFile.java
  6. 15 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/entity/TrialSummaryMonthlyRemarks.java
  7. 5 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/feign/ArchiveFileClient.java
  8. 28 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/feign/ImageClassificationFileClient.java
  9. 29 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/vo/TreeVoTwo.java
  10. 8 4
      blade-service-api/blade-e-visa-api/src/main/java/org/springblade/evisa/feign/EVisaClient.java
  11. 6 4
      blade-service-api/blade-e-visa-api/src/main/java/org/springblade/evisa/feign/EVisaClientFallBack.java
  12. 26 0
      blade-service-api/blade-e-visa-api/src/main/java/org/springblade/evisa/vo/SigInfoVO.java
  13. 4 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/ElementData.java
  14. 8 3
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/FormData.java
  15. 2 1
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/TreeNode.java
  16. 20 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/TrialSummaryClassificationConfigurationMatchDTO.java
  17. 20 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/TrialSummaryClassificationConfigurationRelevancyDTO.java
  18. 7 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/ArchiveTreeContract.java
  19. 3 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/ContractInfo.java
  20. 48 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/enums/AssociationTypeEnum.java
  21. 27 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/enums/TreeStructureEnum.java
  22. 26 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/ExecutionTime.java
  23. 77 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/FormulaVo.java
  24. 60 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/InterimMeter.java
  25. 39 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/InterimMeterPaySummary.java
  26. 5 3
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/ItemBlock.java
  27. 67 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/MeterApply.java
  28. 2 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/MeterPeriodInfo.java
  29. 10 1
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/MeterTree.java
  30. 2 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/MeterType.java
  31. 8 2
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/Payment.java
  32. 2 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/ReportResult.java
  33. 21 2
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/SubInterimMeterPaySummary.java
  34. 34 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/TrialTreeVO.java
  35. 1 1
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/ChangeTokenForm.java
  36. 16 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/vo/ChangeTokenSelectVO.java
  37. 14 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ArchiveExpertConclusionController.java
  38. 6 5
      blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ArchiveOfflineVersionInfoController.java
  39. 9 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ArchiveExpertConclusionMapper.java
  40. 15 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ArchiveExpertConclusionMapper.xml
  41. 3 3
      blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ArchivesAutoMapper.java
  42. 9 8
      blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ArchivesAutoMapper.xml
  43. 2 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/IArchiveExpertConclusionService.java
  44. 77 17
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchiveExpertConclusionServiceImpl.java
  45. 56 103
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchivesAutoServiceImpl.java
  46. 78 2
      blade-service/blade-archive/src/main/java/org/springblade/archive/utils/ItextPdfUtils.java
  47. 153 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/utils/MyPdfPageHelper2.java
  48. 40 4
      blade-service/blade-business/src/main/java/org/springblade/business/controller/ImageClassificationFileController.java
  49. 18 6
      blade-service/blade-business/src/main/java/org/springblade/business/controller/InformationWriteQueryController.java
  50. 2 1
      blade-service/blade-business/src/main/java/org/springblade/business/controller/TaskController.java
  51. 498 0
      blade-service/blade-business/src/main/java/org/springblade/business/controller/TrialSummaryController.java
  52. 1 1
      blade-service/blade-business/src/main/java/org/springblade/business/controller/WeatherController.java
  53. 10 0
      blade-service/blade-business/src/main/java/org/springblade/business/feignClient/ArchiveFileClientImpl.java
  54. 22 0
      blade-service/blade-business/src/main/java/org/springblade/business/feignClient/ImageClassificationFileClientImpl.java
  55. 2 0
      blade-service/blade-business/src/main/java/org/springblade/business/mapper/ArchiveFileMapper.xml
  56. 6 0
      blade-service/blade-business/src/main/java/org/springblade/business/mapper/ImageClassificationFileMapper.java
  57. 45 0
      blade-service/blade-business/src/main/java/org/springblade/business/mapper/ImageClassificationFileMapper.xml
  58. 16 0
      blade-service/blade-business/src/main/java/org/springblade/business/mapper/WeatherInfoMapper.java
  59. 28 0
      blade-service/blade-business/src/main/java/org/springblade/business/mapper/WeatherInfoMapper.xml
  60. 138 6
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/WeatherInfoServiceImpl.java
  61. 7 4
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/feign/EVisaClientImpl.java
  62. 7 0
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/service/EVisaService.java
  63. 172 2
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/service/impl/EVisaServiceImpl.java
  64. 17 3
      blade-service/blade-manager/src/main/java/com/mixsmart/utils/FormulaUtils.java
  65. 16 2
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ArchiveTreeContractController.java
  66. 5 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java
  67. 14 80
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/FormulaController.java
  68. 23 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/TrialSummaryClassificationConfigurationController.java
  69. 3 3
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/WbsParamController.java
  70. 5 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/WbsTreeController.java
  71. 1 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/NodeTable.java
  72. 2 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/impl/ExecutorInit.java
  73. 197 31
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/impl/ExecutorSpecial.java
  74. 2 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/impl/FormulaTurnPoint.java
  75. 11 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/impl/TableElementConverter.java
  76. 15 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ArchiveTreeContractMapper.java
  77. 47 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ArchiveTreeContractMapper.xml
  78. 1 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ContractInfoMapper.xml
  79. 4 3
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/IFormulaDao.java
  80. 2 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/IFormulaService.java
  81. 9 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/ITrialSummaryClassificationConfigurationService.java
  82. 712 6
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ArchiveTreeContractSyncImpl.java
  83. 19 14
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ContractInfoServiceImpl.java
  84. 1 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ExcelTabServiceImpl.java
  85. 11 7
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/FormulaDaoImpl.java
  86. 78 9
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/FormulaServiceImpl.java
  87. 106 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/TrialSummaryClassificationConfigurationServiceImpl.java
  88. 24 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsParamServiceImpl.java
  89. 14 3
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreePrivateServiceImpl.java
  90. 83 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/utils/YearTreeUtils.java
  91. 3 3
      blade-service/blade-meter/src/main/java/org/springblade/meter/controller/ChangeTokenFormController.java
  92. 14 1
      blade-service/blade-meter/src/main/java/org/springblade/meter/controller/MiddleMeterApplyController.java
  93. 18 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/controller/TaskController.java
  94. 3 3
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/ChangeTokenFormMapper.xml
  95. 1 1
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/IChangeTokenFormService.java
  96. 1 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/IMiddleMeterApplyService.java
  97. 5 5
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/ChangeTokenFormServiceImpl.java
  98. 17 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/MiddleMeterApplyServiceImpl.java

+ 8 - 1
blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/ImageClassificationFileDTO.java

@@ -16,9 +16,9 @@
  */
 package org.springblade.business.dto;
 
-import org.springblade.business.entity.ImageClassificationFile;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
+import org.springblade.business.entity.ImageClassificationFile;
 
 /**
  * 数据传输对象实体类
@@ -31,4 +31,11 @@ import lombok.EqualsAndHashCode;
 public class ImageClassificationFileDTO extends ImageClassificationFile {
     private static final long serialVersionUID = 1L;
 
+    /**
+     * 存储目录格式
+     * 1-目录存储,2-时间存储
+     * (通过classifyId 关联 m_image_classification_config 这张表主键可得到)
+     */
+    private Integer storageDirectoryFormat;
+
 }

+ 20 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/TrialSummaryMonthlyEditDTO.java

@@ -0,0 +1,20 @@
+package org.springblade.business.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class TrialSummaryMonthlyEditDTO implements Serializable {
+
+    @ApiModelProperty(value = "合同段id")
+    private Long contractId;
+
+    @ApiModelProperty(value = "关联id")
+    private Long recordId;
+
+    @ApiModelProperty(value = "备注文本信息")
+    private String remarks;
+
+}

+ 32 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/TrialSummaryMonthlyPageDTO.java

@@ -0,0 +1,32 @@
+package org.springblade.business.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class TrialSummaryMonthlyPageDTO implements Serializable {
+
+    @ApiModelProperty(value = "合同段id")
+    private Long contractId;
+
+    @ApiModelProperty(value = "检测类别")
+    private Integer type;
+
+    @ApiModelProperty(value = "开始时间,格式yyyy-MM-dd")
+    private String startTime;
+
+    @ApiModelProperty(value = "结束时间,格式yyyy-MM-dd")
+    private String endTime;
+
+    @ApiModelProperty(value = "选择的试验划分树节点的pKeyId英文逗号字符串拼接")
+    private String ids;
+
+    @ApiModelProperty(value = "每页条数")
+    private Integer size;
+
+    @ApiModelProperty(value = "当前页码")
+    private Integer current;
+
+}

+ 32 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/entity/ArchiveFile.java

@@ -283,4 +283,36 @@ public class ArchiveFile extends BaseEntity {
      */
     @ApiModelProperty("类型,1施工 2监理")
     private Integer classify;
+
+    /**
+     * m_wbs_tree_contract表中的主键,p_key_id
+     */
+    @ApiModelProperty("m_wbs_tree_contract表中的主键,p_key_id")
+    private Long mWbsTreeContractPKeyId;
+
+    /**
+     * u_image_classification_file_id表中的主键id
+     */
+    @ApiModelProperty("u_image_classification_file_id表中的主键id")
+    private Long uImageClassificationFileId;
+
+    /**
+     * 归档文件储存类型,可看代码枚举
+     * @StorageTypeEnum
+     */
+    @ApiModelProperty("归档文件储存类型")
+    private Integer archiveFileStorageType;
+
+    /**
+     * 节点树结构,1-普通树,2-时间树
+     * 正常都是1, 2是为了区分声像文件的两个树形结构下的文件
+     */
+    @ApiModelProperty("节点树结构,1-普通树,2-时间树")
+    private Integer nodeTreeStructure;
+
+    /**
+     * 文件时间名称,可用于确定时间树节点的id
+     */
+    @ApiModelProperty("文件时间名称,可用于确定时间树节点的id")
+    private String dateName;
 }

+ 9 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/entity/ImageClassificationFile.java

@@ -127,4 +127,13 @@ public class ImageClassificationFile extends BaseEntity {
     @ApiModelProperty("合并后的PDF")
     private String margePdfUrl;
 
+    @ApiModelProperty("图片或视频的水平分辨率")
+    private Integer fileWidth;
+
+    @ApiModelProperty("图片或视频的垂直分辨率")
+    private Integer fileHeight;
+
+    @ApiModelProperty("文件页数")
+    private Integer filePage;
+
 }

+ 15 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/entity/TrialSummaryMonthlyRemarks.java

@@ -0,0 +1,15 @@
+package org.springblade.business.entity;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class TrialSummaryMonthlyRemarks implements Serializable {
+
+    private Long id;
+    private Long contractId;
+    private Long recordId;
+    private String remarks;
+
+}

+ 5 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/feign/ArchiveFileClient.java

@@ -150,4 +150,9 @@ public interface ArchiveFileClient {
     @PostMapping(API_PREFIX + "/updateById2")
     void updateById2(@RequestBody ArchiveFile archiveFile);
 
+    /**
+     * 根据合同Id和储存类型查询集合
+     */
+    @PostMapping(API_PREFIX + "/getListByContractIdAndArchiveFileStorageType")
+    List<ArchiveFile> getListByContractIdAndArchiveFileStorageType(@RequestParam Long contractId,@RequestParam Integer getListByContractIdAndArchiveFileStorageType);
 }

+ 28 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/feign/ImageClassificationFileClient.java

@@ -0,0 +1,28 @@
+package org.springblade.business.feign;
+
+import org.springblade.business.dto.ImageClassificationFileDTO;
+import org.springblade.common.constant.BusinessConstant;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+import java.util.List;
+
+/**
+ * @author xingqianji
+ */
+@FeignClient(value = BusinessConstant.APPLICATION_WEATHER_NAME)
+public interface ImageClassificationFileClient {
+
+    /**
+     * 接口前缀
+     */
+    String API_PREFIX = "/api/business/imageClassificationFile";
+
+    /**
+     * 根据合同段id查询声像文件
+     */
+    @PostMapping(API_PREFIX + "/getImageClassificationFileListByContractId")
+    List<ImageClassificationFileDTO> getImageClassificationFileListByContractId(@RequestBody Long contractId);
+
+}

+ 29 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/vo/TreeVoTwo.java

@@ -0,0 +1,29 @@
+package org.springblade.business.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class TreeVoTwo {
+
+    private String name;
+
+    private String hierarchy;
+
+    private Long parentId;
+
+    private List<TreeVoTwo> children = new ArrayList<>();
+
+    public TreeVoTwo(String name, List<TreeVoTwo> children, String hierarchy, Long parentId) {
+        this.name = name;
+        this.children = children;
+        this.parentId = parentId;
+        this.hierarchy = hierarchy;
+    }
+
+    public TreeVoTwo() {
+    }
+
+}

+ 8 - 4
blade-service-api/blade-e-visa-api/src/main/java/org/springblade/evisa/feign/EVisaClient.java

@@ -1,10 +1,8 @@
 package org.springblade.evisa.feign;
 
 import org.springblade.common.constant.EVisaConstant;
-import org.springblade.evisa.vo.CertBeanVO;
-import org.springblade.evisa.vo.EVisaMakeSealVO;
-import org.springblade.evisa.vo.EVisaTaskApprovalVO;
-import org.springblade.evisa.vo.TaskArchiveDTO;
+import org.springblade.core.tool.api.R;
+import org.springblade.evisa.vo.*;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
@@ -56,4 +54,10 @@ public interface EVisaClient {
     @PostMapping(API_PREFIX + "/onlineCheckSeal")
     CertBeanVO onlineCheckSeal(@RequestParam String pdfUrl);
 
+    /**
+     * 单张PDF批量电签,返回电签后的PDF
+     */
+    @PostMapping(API_PREFIX + "/batchEVisa")
+    R<String> batchEVisa(@RequestBody SigInfoVO vo);
+
 }

+ 6 - 4
blade-service-api/blade-e-visa-api/src/main/java/org/springblade/evisa/feign/EVisaClientFallBack.java

@@ -3,11 +3,10 @@ package org.springblade.evisa.feign;
 import feign.hystrix.FallbackFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springblade.evisa.vo.CertBeanVO;
-import org.springblade.evisa.vo.EVisaMakeSealVO;
-import org.springblade.evisa.vo.EVisaTaskApprovalVO;
-import org.springblade.evisa.vo.TaskArchiveDTO;
+import org.springblade.core.tool.api.R;
+import org.springblade.evisa.vo.*;
 import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.RequestBody;
 
 @Component
 public class EVisaClientFallBack implements FallbackFactory<EVisaClient> {
@@ -47,6 +46,9 @@ public class EVisaClientFallBack implements FallbackFactory<EVisaClient> {
             public CertBeanVO onlineCheckSeal(String pdfUrl) {
                 return null;
             }
+
+            @Override
+            public R<String> batchEVisa(SigInfoVO vo){return R.fail("feign调用失败");}
         };
     }
 }

+ 26 - 0
blade-service-api/blade-e-visa-api/src/main/java/org/springblade/evisa/vo/SigInfoVO.java

@@ -0,0 +1,26 @@
+package org.springblade.evisa.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @Param   批量电签传参
+ * @Author wangwl
+ * @Date 2024/1/18 17:21
+ **/
+@Data
+public class SigInfoVO {
+
+    private String pdfUrl;
+    private List<SigInfo> list;
+
+    @Data
+    public static class SigInfo{
+        private String userName;
+        private String userSig;
+        private String sigPic;
+        private String sealCode;
+        private String sealPassword;
+    }
+}

+ 4 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/ElementData.java

@@ -119,6 +119,10 @@ public class ElementData implements Serializable {
         return Func.isEmpty(this.value);
     }
 
+    public Boolean isNotEmpty() {
+        return Func.isNotEmpty(this.value);
+    }
+
 
     public ElementData clone(Object value) {
         return new ElementData(index, groupId, value);

+ 8 - 3
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/FormData.java

@@ -9,6 +9,7 @@ import org.springblade.core.tool.utils.StringPool;
 import org.springblade.manager.entity.Formula;
 
 import java.util.*;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -256,19 +257,23 @@ public class FormData {
         }
     }
 
+
+    private Function<List<ElementData>,String> valueFc=l->l.stream().map(ElementData::stringValue).collect(Collectors.joining(StringPool.COMMA));
+    /*用于比较元素内容是否更新*/
     public boolean hasChange(){
         if(this.values!=null&&this.initial!=null) {
             if (this.values.size() != this.initial.size()) {
                 return true;
             } else if (this.values.size() > 0) {
-                String sa = this.values.stream().map(ElementData::stringValue).collect(Collectors.joining());
-                String sc = this.initial.stream().map(ElementData::stringValue).collect(Collectors.joining());
-                return sa.equals(sc);
+                String sa =valueFc.apply(this.values);
+                String sc =valueFc.apply(this.initial);
+                return !sa.equals(sc);
             }
         }
         return false;
     }
 
+
     /*是否是测量业务数据*/
     public boolean isContent(){
         return this.structure==1;

+ 2 - 1
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/TreeNode.java

@@ -7,7 +7,7 @@ import java.util.List;
 /**
  * @author yangyj
  * @Date 2023/10/9 14:21
- * @description 工具类,用来刷treeCode
+ * @description 工具类,树结构遍历读改<T>
  */
 @Data
 public class TreeNode<T> {
@@ -17,6 +17,7 @@ public class TreeNode<T> {
        private TreeNode<T> parent;
        private String name;
        private int sort;
+       private boolean checked=false;
        private List<TreeNode<T>> children=new ArrayList<>();
        public boolean hasChildren(){
           return !children.isEmpty();

+ 20 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/TrialSummaryClassificationConfigurationMatchDTO.java

@@ -0,0 +1,20 @@
+package org.springblade.manager.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class TrialSummaryClassificationConfigurationMatchDTO implements Serializable {
+
+    @ApiModelProperty(value = "项目id")
+    private String projectId;
+
+    @ApiModelProperty(value = "分类id(page接口的id)")
+    private String classId;
+
+    @ApiModelProperty(value = "选择节点的pKeyId英文逗号拼接成ids")
+    private String ids;
+
+}

+ 20 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/TrialSummaryClassificationConfigurationRelevancyDTO.java

@@ -0,0 +1,20 @@
+package org.springblade.manager.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class TrialSummaryClassificationConfigurationRelevancyDTO implements Serializable {
+
+    @ApiModelProperty(value = "项目id")
+    private String projectId;
+
+    @ApiModelProperty(value = "分类id(page接口的id)")
+    private String classId;
+
+    @ApiModelProperty(value = "选择节点的pKeyId英文逗号拼接成ids")
+    private String ids;
+
+}

+ 7 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/ArchiveTreeContract.java

@@ -233,6 +233,13 @@ public class ArchiveTreeContract extends BaseEntity {
     @ApiModelProperty(value = "专家名称,逗号分隔")
     private String expertName;
 
+    /**
+     * 树结构
+     * 正常都是1,数据库默认保存也是1
+     * 2是因为,声像文件那里,有根据时间树形结构来展示的树
+     */
+    @ApiModelProperty(value = "树结构,1-正常树,2-时间树")
+    private Integer treeStructure;
 
     public ArchiveTreeContract() {
     }

+ 3 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/ContractInfo.java

@@ -163,6 +163,9 @@ public class ContractInfo extends BaseEntity {
      */
     private Integer isArchivesAuto;
 
+    @ApiModelProperty(value = "计量是否允许超计 '0'否 '1'是")
+    private Integer isOverMeter;
+
 
     public String archivesUnit() {
 

+ 48 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/enums/AssociationTypeEnum.java

@@ -0,0 +1,48 @@
+package org.springblade.manager.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springblade.core.tool.utils.ObjectUtil;
+
+/**
+ * 关联类型枚举
+ * @author xingqianji
+ */
+@Getter
+@AllArgsConstructor
+public enum AssociationTypeEnum {
+
+    UNKNOWN(-1, ""),
+    QUALITY_INSPECTION_DATA(1, "质检资料"),
+    TRIAL_DATA(2, "试验资料"),
+    SOUND_IMAGE_DATA(3, "影像资料"),
+    LEDGER_DATA(4, "台账资料"),
+    FIRST_ARTICLE_DATA(5, "首件资料"),
+    LOG_FILE(6, "日志文件");
+
+    /**
+     * 编码
+     */
+    private Integer code;
+
+    /**
+     * 描述
+     */
+    private String desc;
+
+    /**
+     * 根据编码获取枚举描述
+     */
+    public static String getByCode(int code) {
+        // 判断code
+        if(ObjectUtil.isNotEmpty(code)){
+            for (AssociationTypeEnum storageTypeEnum : AssociationTypeEnum.values()) {
+                if (storageTypeEnum.getCode() == code) {
+                    return storageTypeEnum.getDesc();
+                }
+            }
+        }
+        return AssociationTypeEnum.UNKNOWN.getDesc();
+    }
+
+}

+ 27 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/enums/TreeStructureEnum.java

@@ -0,0 +1,27 @@
+package org.springblade.manager.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 树形结构枚举
+ * @author xingqianji
+ */
+@Getter
+@AllArgsConstructor
+public enum TreeStructureEnum {
+
+    ORDINARY_TREE(1, "普通树形"),
+    DATE_TREE(2, "时间树形");
+
+    /**
+     * 编码
+     */
+    private Integer code;
+
+    /**
+     * 描述
+     */
+    private String desc;
+
+}

+ 26 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/ExecutionTime.java

@@ -0,0 +1,26 @@
+package org.springblade.manager.vo;
+
+/**
+ * @author yangyj
+ * @Date 2024/1/23 16:14
+ * @description 执行耗时统计
+ */
+public class ExecutionTime {
+    public static final String template="耗时:$1\n 总耗时:$2";
+    private final Long start;
+    private Long last;
+
+    public ExecutionTime() {
+        this.start=System.currentTimeMillis();
+        this.last=start;
+    }
+
+    public void info(String str){
+        long cur = System.currentTimeMillis();
+        long total=cur-start;
+        long part= cur-last;
+        last=cur;
+        String s = str+template.replace("$1",String.valueOf(part)).replace("$2",String.valueOf(total));
+        System.out.println(s);
+    }
+}

+ 77 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/FormulaVo.java

@@ -0,0 +1,77 @@
+package org.springblade.manager.vo;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Data;
+import org.springblade.core.tool.utils.Func;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author yangyj
+ * @Date 2024/1/23 14:05
+ * @description 公式信息
+ */
+@Data
+public class FormulaVo {
+    private Long id;
+    private String rely;
+    private String formula;
+    private String formulaParse;
+    private String number;
+    private Integer orderNum;
+    private String remark;
+    @JsonIgnore
+    private String map;
+    private Integer scale;
+    private Integer batch;
+    /**
+     * 输出模式,0自动 1填充
+     */
+    private Integer outm;
+    /**
+     * 关联的节点参数id
+     */
+    private Long paramId;
+    /**
+     * 偏差范围
+     */
+    private String dev;
+    /**
+     * 版本号
+     */
+    private Integer ver;
+
+
+    public List<String> getRelyList() {
+        if (Func.isNotBlank(rely)) {
+            return Arrays.asList(rely.split(","));
+        } else {
+            return null;
+        }
+    }
+
+
+    public JSONObject getJson() {
+        JSONObject jsonObject =JSON.parseObject(map);
+        String devStr = jsonObject.getString("deviationRangeJson");
+        if(devStr!=null&&devStr.length()>2){
+            jsonObject.fluentPut("deviationRangeJson",JSON.parseObject(devStr));
+        }
+        return jsonObject;
+    }
+
+    public void setFormulaParse(Map<String,String> dict) {
+        if(Func.isNotEmpty(rely)){
+            String f = getFormula();
+            for(String key:getRelyList()){
+                if(dict.containsKey(key)) {
+                    f = f.replace(key, dict.get(key));
+                }
+            }
+            this.formulaParse = f;
+        }
+    }
+}

+ 60 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/InterimMeter.java

@@ -0,0 +1,60 @@
+package org.springblade.manager.vo;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * @author yangyj
+ * @Date 2024/1/24 15:13
+ * @description 中间计量表
+ */
+@Data
+public class InterimMeter {
+    public static final String ID="20270000000";
+    public static final String TBN="IMeter";
+    public static final String TBN_CH="中间计量表";
+
+    @JSONField(name = "key_0",label="计量单编号")
+    private String meterNumber;
+
+    @JSONField(name = "key_1",label="工程划分")
+    private String engineerDivide;
+
+    @JSONField(name = "key_2",label="部位名称")
+    private String partName;
+
+    @JSONField(name = "key_3",label="交工证书编号")
+    private String certificateNumber;
+
+    @JSONField(name = "key_4",label="计量金额")
+    private BigDecimal meterMoney;
+
+    @JSONField(name = "key_5",label="变更令编号")
+    private String changeTokenNumber;
+
+    @JSONField(name = "key_6",label="草图文件地址")
+    private String pictureUrl;
+
+    @JSONField(name = "key_7",label="草图文件名称")
+    private String pictureName;
+
+    @JSONField(name = "key_8",label="计算式")
+    private String calculateFormula;
+
+    @JSONField(name = "key_9",label="合同图号")
+    private String contractPicture;
+
+    @JSONField(name = "key_10",label="支付号")
+    private List<String> formNumberList;
+    @JSONField(name = "key_11",label="项目名称")
+    private List<String> itemNameList;
+    @JSONField(name = "key_12",label="单位")
+    private List<String> unitList;
+    @JSONField(name = "key_13",label="申报数量")
+    private List<String> completedList;
+
+}

+ 39 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/InterimMeterPaySummary.java

@@ -0,0 +1,39 @@
+package org.springblade.manager.vo;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+/**
+ * @author yangyj
+ * @Date 2024/1/24 14:37
+ * @description 中间计量支付汇总表
+ */
+@Data
+public class InterimMeterPaySummary {
+    public static final String ID="20260000000";
+    public static final String TBN="IMeterPaySum";
+    public static final String TBN_CH="中间计量支付汇总表";
+    @JSONField(name = "key_0",label="项目编号")
+    private String formNumber;
+    @JSONField(name = "key_1",label="项目名称",ordinal = 1)
+    private String itemName;
+    @JSONField(name = "key_2",label="凭证号",ordinal = 2)
+    private String meterNumber;
+    @JSONField(name = "key_3",label="单位",ordinal = 3)
+    private String unit;
+    @JSONField(name = "key_4",label="数量",ordinal = 4)
+    private String completed;
+    @JSONField(name = "key_5",label="单价",ordinal = 5)
+    private String price;
+    @JSONField(name = "key_6",label="金额",ordinal = 6)
+    private String money;
+    @JSONField(name = "key_7",label="备注",ordinal = 7)
+    private String remark;
+
+    public InterimMeterPaySummary(String itemName) {
+        this.itemName = itemName;
+    }
+
+    public InterimMeterPaySummary() {
+    }
+}

+ 5 - 3
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/ItemBlock.java

@@ -4,6 +4,7 @@ import com.alibaba.fastjson.annotation.JSONField;
 import lombok.Data;
 import org.springblade.core.tool.utils.Func;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -25,10 +26,10 @@ public class ItemBlock {
     @JSONField(name = "p")
     private Long pkeyId;
     /**
-     * 实测值
+     * 实测值:按设计值分组
      */
     @JSONField(name = "d")
-    private List<List<Double>> data;
+    private List<List<Object>> data;
     /**
      * 设计值
      */
@@ -52,7 +53,8 @@ public class ItemBlock {
 
     public void setD(String d) {
         if (Func.isNotEmpty(d)) {
-            this.data = Arrays.stream(d.split("/")).map(e -> Arrays.stream(e.split(",")).map(Double::parseDouble).collect(Collectors.toList())).collect(Collectors.toList());
+            this.data = Arrays.stream(d.split("/")).map(e-> Arrays.stream(e.split(",")) .map(s -> (Object) s).collect(Collectors.toList())
+            ).collect(Collectors.toList());
         }
     }
 

+ 67 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/MeterApply.java

@@ -0,0 +1,67 @@
+package org.springblade.manager.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.math.BigDecimal;
+
+/**
+ * @author yangyj
+ * @Date 2024/1/24 15:54
+ * @description 中间计量申请映射
+ */
+@Data
+public class MeterApply {
+
+        private Long id;
+        /**
+         * 合同计量单元id
+         */
+        @ApiModelProperty(value = "合同计量单元id")
+        private Long contractUnitId;
+        /**
+         * 计量单编号
+         */
+        @ApiModelProperty(value = "计量单编号")
+        private String meterNumber;
+        /**
+         * 工程划分
+         */
+        @ApiModelProperty(value = "工程划分")
+        private String engineerDivide;
+        /**
+         * 部位名称
+         */
+        @ApiModelProperty(value = "部位名称")
+        private String partName;
+        /**
+         * 交工证书编号
+         */
+        @ApiModelProperty(value = "交工证书编号")
+        private String certificateNumber;
+        /**
+         * 计量金额
+         */
+        @ApiModelProperty(value = "计量金额")
+        private BigDecimal meterMoney;
+        /**
+         * 变更令编号
+         */
+        @ApiModelProperty(value = "变更令编号")
+        private String changeTokenNumber;
+        /**
+         * 草图文件
+         */
+        @ApiModelProperty(value = "草图文件地址")
+        private String pictureUrl;
+
+        @ApiModelProperty(value = "草图文件名称")
+        private String pictureName;
+        /**
+         * 计算式
+         */
+        @ApiModelProperty(value = "计算式")
+        private String calculateFormula;
+
+        @ApiModelProperty(value = "合同图号")
+        private String contractPicture;
+}

+ 2 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/MeterPeriodInfo.java

@@ -38,4 +38,6 @@ public class MeterPeriodInfo {
     /**排序号*/
     private Integer sort;
 
+    private Long id;
+
 }

+ 10 - 1
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/MeterTree.java

@@ -18,10 +18,19 @@ public class MeterTree {
 
     @ApiModelProperty(value = "节点编码")
     private String nodeCode;
-
+    @ApiModelProperty(value = "祖级id")
+    private String ancestor;
     @ApiModelProperty(value = "父级id")
     private Long parentId;
     @ApiModelProperty(value = "排序")
     private Integer sort;
 
+    public String chapterPrefix(){
+        if(this.ancestor.length()>=41){
+            return this.ancestor.substring(0,42);
+        }
+        return this.ancestor;
+    }
+    public SubInterimMeterPaySummary  peer;
+
 }

+ 2 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/MeterType.java

@@ -27,4 +27,6 @@ public enum MeterType {
            return values[0];
         }
     }
+
+
 }

+ 8 - 2
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/Payment.java

@@ -15,15 +15,21 @@ public class Payment {
     private Long  formId;
     /**计量单元id*/
     private Long  meterId;
+    /**中间计量申请Id*/
+    private Long  middleMeterId;
     /**清单编号*/
     private String number;
+    /**计量单编号*/
+    private String meterNumber;
     /**清单名称*/
     private String name;
+    /**单价*/
+    private String price;
     /**单位*/
     private String unit;
-    /*合同数量*/
+    /**合同数量*/
     private Integer contractTotal;
-    /*变更数量*/
+    /**变更数量*/
     private Integer changeTotal;
     /**完成数量*/
     private Integer completed;

+ 2 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/ReportResult.java

@@ -16,6 +16,8 @@ public class ReportResult {
     private String url;
     /**表编号*/
     private String initTableName;
+    /**清表模版*/
+    private Long excelId;
     /**表名*/
     private String name;
     /**每一页的数据,格式{y_x:val,y1_x1:val1...}{...}...*/

+ 21 - 2
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/SubInterimMeterPaySummary.java

@@ -2,6 +2,11 @@ package org.springblade.manager.vo;
 
 import com.alibaba.fastjson.annotation.JSONField;
 import lombok.Data;
+import org.springblade.common.utils.BaseUtils;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * @author yangyj
@@ -30,10 +35,10 @@ public class SubInterimMeterPaySummary {
     private String revisedTotal;
     /**本次完成数量*/
     @JSONField(name = "key_5",label="本次完成数量",ordinal = 5)
-    private String currentPeriodComplete;
+    private String currentPeriodCompleted;
     /**累计完成数量*/
     @JSONField(name = "key_6",label="累计完成数量",ordinal = 6)
-    private String complete;
+    private String completed;
     /**合同金额*/
     @JSONField(name = "key_7",label="合同金额",ordinal = 7)
     private String contractAmount;
@@ -49,4 +54,18 @@ public class SubInterimMeterPaySummary {
     /**比例*/
     @JSONField(name = "key_11",label="变更令号",ordinal = 11)
     private String changeTokenId;
+
+    public void currentPeriodEndPayAdd(String n){
+          List<String> list = new ArrayList<>();
+          list.add(this.currentPeriodEndPay);
+          list.add(n);
+         this.setCurrentPeriodEndPay(list.stream().filter(BaseUtils::isNumber).map(BigDecimal::new).reduce(BigDecimal.ZERO,BigDecimal::add).toString());
+    }
+
+    public SubInterimMeterPaySummary(String itemName) {
+        this.itemName = itemName;
+    }
+
+    public SubInterimMeterPaySummary() {
+    }
 }

+ 34 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/TrialTreeVO.java

@@ -0,0 +1,34 @@
+package org.springblade.manager.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class TrialTreeVO implements Serializable {
+
+    @ApiModelProperty(value = "主键id")
+    private Long pKeyId;
+
+    @ApiModelProperty(value = "id")
+    private Long id;
+
+    @ApiModelProperty(value = "父级id")
+    private Long parentId;
+
+    @ApiModelProperty(value = "节点名称")
+    private String nodeName;
+
+    @ApiModelProperty(value = "子节点数组")
+    private List<TrialTreeVO> childNode;
+
+    @ApiModelProperty(value = "是否存在子节点 true=存在子级 false=不存在子级")
+    private boolean hasChild;
+
+    @ApiModelProperty(value = "回显状态 0=未勾选 1=勾选")
+    private Integer status;
+
+}

+ 1 - 1
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/ChangeTokenForm.java

@@ -80,7 +80,7 @@ public class ChangeTokenForm extends BaseEntity {
      * 延长工期
      */
     @ApiModelProperty(value = "延长工期")
-    private Integer lengthenDays;
+    private String lengthenDays;
     /**
      * 变更申请金额
      */

+ 16 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/vo/ChangeTokenSelectVO.java

@@ -0,0 +1,16 @@
+package org.springblade.meter.vo;
+
+import lombok.Data;
+
+/**
+ * @Param
+ * @Author wangwl
+ * @Date 2024/1/23 11:00
+ **/
+@Data
+public class ChangeTokenSelectVO {
+    private Long contractId;
+    private Long nodeId;
+    private String ids;
+
+}

+ 14 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ArchiveExpertConclusionController.java

@@ -171,4 +171,18 @@ public class ArchiveExpertConclusionController extends BladeController {
         conclusionService.submitTable(conclusion);
         return R.data("结论生成成功");
     }
+
+    /**
+     * 编写结论-测回提交
+     */
+    @GetMapping("/repealTable")
+    @ApiOperationSupport(order = 12)
+    @ApiOperation(value = "编写结论-测回提交", notes = "传入结论id")
+    @ApiImplicitParams(value = {
+            @ApiImplicitParam(name = "id", value = "结论id", required = true)
+    })
+    public R repealTable(@RequestParam Long id) {
+        conclusionService.repealTable(id);
+        return R.data("测回成功");
+    }
 }

+ 6 - 5
blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ArchiveOfflineVersionInfoController.java

@@ -44,11 +44,12 @@ public class ArchiveOfflineVersionInfoController {
     @ApiOperation(value = "打包数据")
     @GetMapping("/packData")
     public R<String> packData(Long projectId) throws Exception {
-        //先获取状态,同时只能有一个项目打包
-        offlineVersionInfoService.getPackStatus();
-        //异步调用自动打包上传,完成后修改数据库信息
-        offlineVersionInfoService.packData(projectId);
-        return R.data("最新数据后台自动打包中,打包完成后会更新打包日期");
+//        //先获取状态,同时只能有一个项目打包
+//        offlineVersionInfoService.getPackStatus();
+//        //异步调用自动打包上传,完成后修改数据库信息
+//        offlineVersionInfoService.packData(projectId);
+//        return R.data("最新数据后台自动打包中,打包完成后会更新打包日期");
+        return R.data("暂时停止使用离线档案,请联系管理员");
     }
 
     /**

+ 9 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ArchiveExpertConclusionMapper.java

@@ -2,8 +2,12 @@ package org.springblade.archive.mapper;
 
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
 import org.springblade.archive.entity.ArchiveExpertConclusion;
 import org.springblade.archive.entity.ArchiveExpertScore;
+import org.springblade.manager.entity.SignPfxFile;
+
+import java.util.List;
 
 /**
  * <p>
@@ -15,4 +19,9 @@ import org.springblade.archive.entity.ArchiveExpertScore;
 public interface ArchiveExpertConclusionMapper extends BaseMapper<ArchiveExpertConclusion> {
 
 
+    List<SignPfxFile> getAllSign(@Param("ids") List<Long> userIds);
+
+    void updateAllArchiveByProject(@Param("projectId") Long projectId);
+
+    void updateAllNodeByProject(@Param("projectId") Long projectId);
 }

+ 15 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ArchiveExpertConclusionMapper.xml

@@ -1,6 +1,21 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="org.springblade.archive.mapper.ArchiveExpertConclusionMapper">
+    <update id="updateAllArchiveByProject">
+        update u_archives_auto set is_apply = 0,is_reviewed = 0,update_status=0,expert_id = null,is_inspect = 0
+        where project_id = #{projectId} and is_deleted = 0
+    </update>
+    <update id="updateAllNodeByProject">
+        update m_archive_tree_contract set expert_id = null,expert_name = null
+        where project_id = #{projectId} and is_deleted = 0
+    </update>
 
 
+    <select id="getAllSign" resultType="org.springblade.manager.entity.SignPfxFile">
+        select *
+        from m_sign_pfx_file where is_deleted = 0 and certificate_user_id in
+        <foreach collection="ids" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </select>
 </mapper>

+ 3 - 3
blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ArchivesAutoMapper.java

@@ -176,16 +176,16 @@ public interface ArchivesAutoMapper extends BaseMapper<ArchivesAuto> {
 	List<ArchivesAutoVO2> getUnitAllArchive2(@Param("firstNode") Long firstNode,@Param("grade") Integer grade,@Param("isApply") Integer isApply);
 
 	//查询所有的,树不加id,档案状态加
-	IPage<ArchiveInspectVO> getNodeArchives(IPage<ArchiveInspectVO> page,@Param("id") Long nodeId,@Param("projectId") Long projectId,@Param("searchType") Integer searchType,@Param("searchValue") String searchValue,@Param("userId") Long userId);
+	IPage<ArchiveInspectVO> getNodeArchives(IPage<ArchiveInspectVO> page,@Param("id") Long nodeId,@Param("projectId") Long projectId,@Param("searchType") Integer searchType,@Param("searchValue") String searchValue,@Param("userId") Long userId,@Param("conclusionId") Long conclusionId);
 
 	//查询专家的,树加id,档案状态加
-	IPage<ArchiveInspectVO> getNodeArchives2(IPage<ArchiveInspectVO> page,@Param("id") Long nodeId,@Param("projectId") Long projectId,@Param("searchType") Integer searchType,@Param("searchValue") String searchValue,@Param("userId") Long userId);
+	IPage<ArchiveInspectVO> getNodeArchives2(IPage<ArchiveInspectVO> page,@Param("id") Long nodeId,@Param("projectId") Long projectId,@Param("searchType") Integer searchType,@Param("searchValue") String searchValue,@Param("userId") Long userId,@Param("conclusionId") Long conclusionId);
 
     List<ArchivesAuto> getNodeAllArchive(@Param("ids") List<Long> ids);
 
     Integer getUserArchiveTotal(@Param("projectId") Long projectId,@Param("userId") Long userId);
 
-	Integer getUserReviewedTotal(@Param("projectId") Long projectId,@Param("userId") Long userId);
+	Integer getUserReviewedTotal(@Param("projectId") Long projectId,@Param("userId") Long userId,@Param("conclusionId") Long conclusionId);
 
 	IPage<ExpertInspectionVO> getUserInspectInfo(IPage<ExpertInspectionVO> page,@Param("projectId") Long projectId,@Param("userId") Long userId,@Param("conclusionId") Long conclusionId);
 

+ 9 - 8
blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ArchivesAutoMapper.xml

@@ -1045,15 +1045,15 @@
     </select>
     <select id="getNodeArchives" resultType="org.springblade.archive.vo.ArchiveInspectVO">
         select uaa.id ,uaa.file_number,uaa.name ,uaa.unit,
-               if((select count(1) from u_archive_expert_inspection aei where aei.is_deleted =0 and aei.archive_id = uaa.id
+               if((select count(1) from u_archive_expert_inspection aei where aei.is_deleted =0 and aei.archive_id = uaa.id and aei.conclusion_id = #{conclusionId}
                    and aei.expert_id = #{userId}) > 0,'已抽检','未抽检') as inspectStatusName,
-               (CASE when (select count(1) from u_archive_expert_inspection aei where aei.is_deleted =0 and aei.archive_id = uaa.id
+               (CASE when (select count(1) from u_archive_expert_inspection aei where aei.is_deleted =0 and aei.archive_id = uaa.id and aei.conclusion_id = #{conclusionId}
                     and aei.expert_id = #{userId} and is_pass = 1) = 1 then '合格'
-                   when (select count(1) from u_archive_expert_inspection aei where aei.is_deleted =0 and aei.archive_id = uaa.id
+                   when (select count(1) from u_archive_expert_inspection aei where aei.is_deleted =0 and aei.archive_id = uaa.id and aei.conclusion_id = #{conclusionId}
                     and aei.expert_id = #{userId} and is_pass = 0) > 0 then '整改' else null end) as updateStatusName
         from m_archive_tree_contract atc right join u_archives_auto uaa on atc.id = uaa.node_id
         WHERE uaa.is_apply = 1 and uaa.project_id = #{projectId} and atc.project_id = #{projectId} and atc.is_deleted = 0 and uaa.is_deleted =0
-          and FIND_IN_SET(#{id}, atc.ancestors)
+          and (atc.id =#{id} or FIND_IN_SET(#{id}, atc.ancestors))
         <if test="searchValue != null and searchValue != ''">
             <if test="searchType == 1">
                 and uaa.name like concat('%',#{searchValue},'%')
@@ -1065,15 +1065,15 @@
     </select>
     <select id="getNodeArchives2" resultType="org.springblade.archive.vo.ArchiveInspectVO">
         select uaa.id ,uaa.file_number,uaa.name ,uaa.unit,
-        if((select count(1) from u_archive_expert_inspection aei where aei.is_deleted =0 and aei.archive_id = uaa.id
+        if((select count(1) from u_archive_expert_inspection aei where aei.is_deleted =0 and aei.archive_id = uaa.id and aei.conclusion_id = #{conclusionId}
             and aei.expert_id = #{userId}) > 0,'已抽检','未抽检') as inspectStatusName,
-        (CASE when (select count(1) from u_archive_expert_inspection aei where aei.is_deleted =0 and aei.archive_id = uaa.id
+        (CASE when (select count(1) from u_archive_expert_inspection aei where aei.is_deleted =0 and aei.archive_id = uaa.id and aei.conclusion_id = #{conclusionId}
             and aei.expert_id = #{userId} and is_pass = 1) = 1 then '合格'
-            when (select count(1) from u_archive_expert_inspection aei where aei.is_deleted =0 and aei.archive_id = uaa.id
+            when (select count(1) from u_archive_expert_inspection aei where aei.is_deleted =0 and aei.archive_id = uaa.id and aei.conclusion_id = #{conclusionId}
             and aei.expert_id = #{userId} and is_pass = 0) > 0 then '整改' else null end) as updateStatusName
         from m_archive_tree_contract atc right join u_archives_auto uaa on atc.id = uaa.node_id
         WHERE uaa.is_apply = 1 and uaa.project_id = #{projectId} and atc.project_id = #{projectId} and atc.is_deleted = 0 and uaa.is_deleted =0
-        and FIND_IN_SET(#{id}, atc.ancestors)
+        and (atc.id =#{id} or FIND_IN_SET(#{id}, atc.ancestors))
         <if test="userId != null">
             and FIND_IN_SET(#{userId}, uaa.expert_id)
         </if>
@@ -1104,6 +1104,7 @@
               WHERE project_id = #{projectId}
                 and is_deleted = 0
                 and expert_id = #{userId}
+                and conclusion_id = #{conclusionId}
              ) a
     </select>
     <select id="getUserInspectInfo" resultType="org.springblade.archive.vo.ExpertInspectionVO">

+ 2 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/service/IArchiveExpertConclusionService.java

@@ -33,4 +33,6 @@ public interface IArchiveExpertConclusionService extends BaseService<ArchiveExpe
     Boolean checkSubmit(Long projectId);
 
     Long getCurrentId(Long projectId);
+
+    void repealTable(Long id);
 }

+ 77 - 17
blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchiveExpertConclusionServiceImpl.java

@@ -27,7 +27,10 @@ import org.springblade.core.oss.model.BladeFile;
 import org.springblade.core.secure.utils.AuthUtil;
 import org.springblade.core.tool.api.R;
 import org.springblade.core.tool.utils.Func;
+import org.springblade.evisa.feign.EVisaClient;
+import org.springblade.evisa.vo.SigInfoVO;
 import org.springblade.manager.entity.ProjectInfo;
+import org.springblade.manager.entity.SignPfxFile;
 import org.springblade.manager.feign.ProjectClient;
 import org.springblade.resource.feign.NewIOSSClient;
 import org.springblade.system.user.entity.User;
@@ -37,6 +40,8 @@ import org.springframework.transaction.annotation.Transactional;
 
 import java.io.FileOutputStream;
 import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
@@ -53,6 +58,8 @@ public class ArchiveExpertConclusionServiceImpl extends BaseServiceImpl<ArchiveE
     private final IUserClient userClient;
 
     private final NewIOSSClient newIOSSClient;
+
+    private final EVisaClient eVisaClient;
     //表格高度
     private static int high = 20;
     //表格宽度
@@ -78,6 +85,7 @@ public class ArchiveExpertConclusionServiceImpl extends BaseServiceImpl<ArchiveE
         conclusion.setIsBuildScore(0);
         conclusion.setStatus(1);
         conclusion.setTaskId(taskId);
+        conclusion.setExpertIds(expertIds);
         ProjectInfo info = projectClient.getById(projectId + "");
         if (info == null){
             return R.fail("获取项目信息失败");
@@ -219,41 +227,79 @@ public class ArchiveExpertConclusionServiceImpl extends BaseServiceImpl<ArchiveE
     @Override
     @Transactional
     public void submitTable(ArchiveExpertConclusion conclusion) {
+        //根据专家信息获取到具体的user
+        String expertIds = conclusion.getExpertIds();
+        if (StringUtils.isBlank(expertIds)){
+            throw new ServiceException("获取专家账号失败");
+        }
+        List<Long> userIds = Func.toLongList(expertIds);
+        List<User> users = userClient.userInfoByIds(userIds);
+        if (users.size() == 0 || users.size() != userIds.size()){
+            throw new ServiceException("专家账号信息错误,请联系管理员");
+        }
+        //获取所有专家个人证书
+        List<SignPfxFile> list = baseMapper.getAllSign(userIds);
+        if (list.size() == 0 || list.size() != userIds.size()){
+            throw new ServiceException("有专家没有配置电签,请联系管理员");
+        }
+        Map<Long, SignPfxFile> map = list.stream().collect(Collectors.toMap(l -> l.getCertificateUserId(), l -> l));
+
         String pdfUrl = "";
         try {
             //生成结论pdf
-            pdfUrl = buildPdf(conclusion);
+            pdfUrl = buildPdf(conclusion,users);
             if (StringUtils.isBlank(pdfUrl)){
                 throw new ServiceException("");
             }
         }catch (Exception e){
-            throw new ServiceException("生成PDF失败");
+            throw new ServiceException("生成PDF失败:"+e.getMessage());
+        }
+        //构建专家电签信息
+        SigInfoVO vo = new SigInfoVO();
+        List<SigInfoVO.SigInfo> infos = new ArrayList<>();
+        for (User user : users) {
+            SignPfxFile file = map.get(user.getId());
+            if (file == null){
+                throw new ServiceException("未获取到专家:"+user.getName()+"的电签信息");
+            }
+            SigInfoVO.SigInfo info = new SigInfoVO.SigInfo();
+            info.setUserName(user.getName());
+            info.setUserSig(user.getName()+user.getPhone().substring(0,3));
+            info.setSealCode("S_NEW_"+file.getCertificatePassword());
+            info.setSealPassword(file.getCertificatePassword());
+            if (StringUtils.isBlank(file.getSignatureFileUrl())){
+                throw new ServiceException("未获取到专家:"+user.getName()+"的签字图片");
+            }
+            info.setSigPic(file.getSignatureFileUrl());
+            infos.add(info);
         }
+        vo.setPdfUrl(pdfUrl);
+        vo.setList(infos);
         //PDF电签
-        try {
-//            pdfUrl = ;
-        }catch (Exception e){
-            throw new ServiceException("PDF电签失败");
+        R<String> visa = eVisaClient.batchEVisa(vo);
+        if (visa == null){
+            throw new ServiceException("调用电签服务失败");
+        }
+        if (visa.getCode() == 400){
+            throw new ServiceException(visa.getMsg());
         }
         //修改状态,插入结论PDF
-        conclusion.setTableUrl(pdfUrl);
+        conclusion.setTableUrl(visa.getData());
         conclusion.setStatus(2);
+        conclusion.setApproveDate(LocalDate.now());
+        //还原当前项目所有档案的:申请验收状态,查阅状态,抽检意见,专家id,是否抽检
+        baseMapper.updateAllArchiveByProject(conclusion.getProjectId());
+        //还原当前项目所有分配的树节点
+        baseMapper.updateAllNodeByProject(conclusion.getProjectId());
         this.updateById(conclusion);
     }
 
-    private String buildPdf(ArchiveExpertConclusion conclusion) throws Exception {
+    private String buildPdf(ArchiveExpertConclusion conclusion,List<User> users) throws Exception {
         //获取评分基础数据
         List<ArchiveExpertScore> list1 = scoreService.list(new LambdaQueryWrapper<ArchiveExpertScore>()
                 .eq(ArchiveExpertScore::getProjectId, conclusion.getProjectId())
                 .eq(ArchiveExpertScore::getConclusionId, conclusion.getId()));
         Map<Integer, List<ArchiveExpertScore>> map = list1.stream().collect(Collectors.groupingBy(ArchiveExpertScore::getUnitType));
-        //根据专家信息获取到具体的user
-        String expertIds = conclusion.getExpertIds();
-        if (StringUtils.isBlank(expertIds)){
-            throw new ServiceException("获取专家账号失败");
-        }
-        List<Long> userIds = Func.toLongList(expertIds);
-        List<User> users = userClient.userInfoByIds(userIds);
         //获取专家组长
         User us = null;
         for (User user : users) {
@@ -267,7 +313,8 @@ public class ArchiveExpertConclusionServiceImpl extends BaseServiceImpl<ArchiveE
         //新建一个pdf文档对象,前一个参数是纸张大小,后四个为边距
         Document document = new Document(PageSize.A4, 10, 10, 80, 85);
         //文件地址
-        String localFile = "C:\\Users\\泓创研发01\\Desktop\\"+conclusion.getId()+".pdf";
+//        String localFile = "C:\\Users\\泓创研发01\\Desktop\\"+conclusion.getId()+".pdf";
+        String localFile = "/www/wwwroot/Users/hongchuangyanfa/Desktop/archiveCheck/"+conclusion.getId()+".pdf";
         //建立一个书写器
         PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(localFile));
         //页尾
@@ -275,7 +322,8 @@ public class ArchiveExpertConclusionServiceImpl extends BaseServiceImpl<ArchiveE
         writer.setPageEvent(helper);
         document.open();
         //创建字体
-        BaseFont baseFont2 =BaseFont.createFont("C:/WINDOWS/Fonts/simsun.ttc,0", BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED);
+//        BaseFont baseFont2 =BaseFont.createFont("C:/WINDOWS/Fonts/simsun.ttc,0", BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED);
+        BaseFont baseFont2 =BaseFont.createFont("/usr/share/fonts/chinese/simsun.ttc,0", BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED);
         //字体对象,这里可以创建一个方法
         Font sigFont = new Font(baseFont2, 0.1F, Font.NORMAL); //大小为0.1的正常字体
         Font size13font = new Font(baseFont2, 13, Font.NORMAL); //大小为13的正常字体
@@ -468,5 +516,17 @@ public class ArchiveExpertConclusionServiceImpl extends BaseServiceImpl<ArchiveE
         }
         return one.getId();
     }
+
+    /**
+     * 编写结论-测回提交
+     */
+    @Override
+    public void repealTable(Long id) {
+       this.update(new LambdaUpdateWrapper<ArchiveExpertConclusion>()
+            .eq(ArchiveExpertConclusion::getId,id)
+            .set(ArchiveExpertConclusion::getApproveDate,null)
+            .set(ArchiveExpertConclusion::getStatus,1)
+            .set(ArchiveExpertConclusion::getTableUrl,null));
+    }
 }
 

+ 56 - 103
blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchivesAutoServiceImpl.java

@@ -41,9 +41,7 @@ import org.springblade.archive.dto.SaveApplyDTO;
 import org.springblade.archive.entity.*;
 import org.springblade.archive.mapper.ArchiveConclusionMapper;
 import org.springblade.archive.service.*;
-import org.springblade.archive.utils.ArchiveTreeUtil;
-import org.springblade.archive.utils.FileTransJavaDemo;
-import org.springblade.archive.utils.FileUtils;
+import org.springblade.archive.utils.*;
 import org.springblade.archive.vo.*;
 import org.springblade.archive.mapper.ArchivesAutoMapper;
 import org.springblade.business.entity.ArchiveFile;
@@ -2912,13 +2910,33 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 		ProjectInfo project = projectClient.getById(dto.getProjectId() + "");
 		List<Long> longs = Func.toLongList(dto.getArchiveIds());
 		List<ArchivesAutoVO2> vo2s = baseMapper.getAllArchive(longs);
+		String pdfUrl = null;
 		try {
-			String pdfUrl = createAppPdf(project.getProjectName(),vo2s);
-			task.setAttachmentPdfUrl(pdfUrl);
+			pdfUrl = createAppPdf(project.getProjectName(),vo2s);
+			if (StringUtils.isBlank(pdfUrl)){
+				throw new ServiceException("生成上报PDF失败");
+			}
 		}catch (Exception e){
 			throw new ServiceException(e.getMessage());
 		}
 		//如果存在附件则拼接
+		if (StringUtils.isNotBlank(dto.getAttachmentPdfUrl())){
+			String localFile = "/www/wwwroot/Users/hongchuangyanfa/Desktop/archiveCheck/"+dto.getProjectId()+".pdf";
+			List<String> urlList = new ArrayList<>();
+			urlList.add(pdfUrl);
+			urlList.add(dto.getAttachmentPdfUrl());
+			FileUtils.mergePdfPublicMethods(urlList, localFile);
+			System.out.println("生成的pdf:"+pdfUrl);
+			System.out.println("附件的pdf:"+dto.getAttachmentPdfUrl());
+			BladeFile bladeFile = this.newIOSSClient.uploadFile(  "123.pdf", localFile);
+			if (bladeFile == null || StringUtils.isBlank(bladeFile.getLink())){
+				throw new ServiceException("合并PDF失败");
+			}
+			System.out.println("合并的pdf:"+bladeFile.getLink());
+			task.setAttachmentPdfUrl(bladeFile.getLink());
+		}else {
+			task.setAttachmentPdfUrl(pdfUrl);
+		}
 		//保存任务
 		taskClient.saveTask(task);
 		//根据任务人设置task_parallel
@@ -2941,48 +2959,42 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 
 	//生成验收申请PDF
 	private String createAppPdf(String projectName, List<ArchivesAutoVO2> vo2s) throws Exception {
-		String fileUrl = "C:\\Users\\泓创研发01\\Desktop\\appPDF.pdf";
+		String fileUrl = "/www/wwwroot/Users/hongchuangyanfa/Desktop/archiveCheck/appPDF.pdf";
 		//新建一个pdf文档对象,前一个参数是纸张大小,后四个为边距
 		Document document = new Document(PageSize.A4, 5, 5, 30, 30);
+		//创建字体
+//		BaseFont baseFont =BaseFont.createFont("C:/WINDOWS/Fonts/simsun.ttc,0", BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED);
+		BaseFont baseFont =BaseFont.createFont("/usr/share/fonts/chinese/simsun.ttc,0", BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED);
+		//字体对象,这里可以创建一个方法
+		Font size10font = new Font(baseFont, 13, Font.NORMAL); //大小为10的正常字体
+		Font size17font = new Font(baseFont, 17, Font.BOLD);  //大小为17的正常字体
 		//建立一个书写器
 		PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(fileUrl));
+		MyPdfPageHelper2 helper = new MyPdfPageHelper2(baseFont,size10font,size17font,projectName);
+		writer.setPageEvent(helper);
 		document.open();
-		//创建字体
-		BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
-		//字体对象,这里可以创建一个方法
-		Font size10font = new Font(baseFont, 10, Font.NORMAL); //大小为10的正常字体
-		Font size17font = new Font(baseFont, 17, Font.NORMAL);  //大小为17的正常字体
 		//该数组是每个表格的宽度
 		float[] floats = new float[6];
-		floats[0] = 0.1f;
-		floats[1] = 0.1f;
-		floats[2] = 0.5f;
-		floats[3] = 0.1f;
-		floats[4] = 0.1f;
-		floats[5] = 0.1f;
-		tableHeader(projectName,document,size10font,size17font,floats);
+		floats[0] = 0.09f;
+		floats[1] = 0.14f;
+		floats[2] = 0.48f;
+		floats[3] = 0.09f;
+		floats[4] = 0.11f;
+		floats[5] = 0.09f;
 
 		//将数据放入表格中
-		int total = 0;
 		for (int i = 0; i < vo2s.size(); i++) {
 			ArchivesAutoVO2 vo2 = vo2s.get(i);
 			PdfPTable dataTable = new PdfPTable(6);
 			dataTable.setWidthPercentage(widthPercentage);
 			dataTable.setTotalWidth(floats);
-			dataTable.addCell(pdfTableStyle(i+1+"", size10font, high, true, true));
-			dataTable.addCell(pdfTableStyle(vo2.getFileNumber(), size10font, high, true, true));
-			dataTable.addCell(pdfTableStyle("		"+vo2.getName(), size10font, high, false, false));
-			dataTable.addCell(pdfTableStyle(vo2.getPageN()+"", size10font, high, true, true));
-			dataTable.addCell(pdfTableStyle(vo2.getStorageTimeValue(), size10font, high, true, true));
-			dataTable.addCell(pdfTableStyle(vo2.getRemark(), size10font, high, true, true));
-			total += (int)Math.ceil(new Double(vo2.getName().length()+4) / 50);
-			//如果出现不相同,则合并之前的
+			dataTable.addCell(ItextPdfUtils.pdfTableStyle11(i+1+"", size10font, 30, true, true));
+			dataTable.addCell(ItextPdfUtils.pdfTableStyle11(vo2.getFileNumber(), size10font, 30, true, true));
+			dataTable.addCell(ItextPdfUtils.pdfTableStyle12(vo2.getName(), size10font, 30, false, true));
+			dataTable.addCell(ItextPdfUtils.pdfTableStyle11(vo2.getPageN()+"", size10font, 30, true, true));
+			dataTable.addCell(ItextPdfUtils.pdfTableStyle11(vo2.getStorageTimeValue(), size10font, 30, true, true));
+			dataTable.addCell(ItextPdfUtils.pdfTableStyle11(vo2.getRemark(), size10font, 30, true, true));
 			document.add(dataTable);
-			if (total >= 40 && i !=  (vo2s.size()-1)) {
-				total = 0;
-				document.newPage();
-				tableHeader(projectName,document,size10font,size17font,floats);
-			}
 		}
 		document.close();
 		writer.close();
@@ -2993,46 +3005,6 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 		return bladeFile.getLink();
 	}
 
-	/**
-	 * 	表头
-	 * @param document	整个文档
-	 * @param size10font
-	 * @param size17font
-	 * @param floats	表头格式
-	 * @throws DocumentException
-	 */
-	void tableHeader(String projectName,Document document,Font size10font,Font size17font,float[] floats) throws DocumentException {
-		//添加标题
-		//创建第一行表格
-		PdfPTable tableName = new PdfPTable(1);
-		tableName.setWidthPercentage(widthPercentage);  //设置标题长度占纸张比例
-		PdfPCell cell = pdfTableStyle("单位科学技术档案归档目录", size17font, 50, true, true);
-		cell.disableBorderSide(15);
-		//给表格赋值
-		tableName.addCell(cell);
-		//将表格添加到文档对象中
-		document.add(tableName);
-		//项目名称
-		PdfPTable two = new PdfPTable(1);
-		two.setWidthPercentage(widthPercentage);
-		PdfPCell cell2 = pdfTableStyle("项目名称:"+projectName, size10font, 20, false, false);
-		cell2.disableBorderSide(15);
-		two.addCell(cell2);
-		document.add(two);
-		//表头文字
-		String[] name = new String[]{"序号", "档号", "案卷提名", "总页数","保管期限","备注"};
-		//创建第二行,并设置第二行中的表格数
-		PdfPTable twoTable = new PdfPTable(name.length);
-		twoTable.setWidthPercentage(widthPercentage);
-		//循环将表头数据添加到第二行表格中
-		for (int i = 0; i < name.length; i++) {
-			PdfPCell pdfPCell = pdfTableStyle(name[i], size10font, high, true, true);
-			twoTable.addCell(pdfPCell);
-		}
-		//设置表格的宽度
-		twoTable.setTotalWidth(floats);
-		document.add(twoTable);
-	}
 
 	@Override
 	public R<Boolean> batchUpdateIsApply(Integer isApply, List<Long> ids) {
@@ -3094,12 +3066,16 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 	@Override
 	public IPage<ArchiveInspectVO> getNodeArchives(Query query,Long nodeId,Long projectId,Integer searchType,String searchValue,Integer type) {
 		IPage<ArchiveInspectVO> page = new Page<>(query.getCurrent(),query.getSize());
+		ArchiveExpertConclusion one = expertConclusionService.getOne(new LambdaUpdateWrapper<ArchiveExpertConclusion>()
+				.eq(ArchiveExpertConclusion::getProjectId, projectId)
+				.orderByDesc(ArchiveExpertConclusion::getCreateTime)
+				.last("limit 1"));
 		if (type == 1){
 			//查询所有的,树不加id,档案状态加
-			return baseMapper.getNodeArchives(page,nodeId,projectId,searchType,searchValue,AuthUtil.getUserId());
+			return baseMapper.getNodeArchives(page,nodeId,projectId,searchType,searchValue,AuthUtil.getUserId(),one.getId());
 		}else {
 			//查询专家的,树加id,档案状态加
-			return baseMapper.getNodeArchives2(page,nodeId,projectId,searchType,searchValue,AuthUtil.getUserId());
+			return baseMapper.getNodeArchives2(page,nodeId,projectId,searchType,searchValue,AuthUtil.getUserId(),one.getId());
 		}
 	}
 
@@ -3117,7 +3093,12 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 		}
 		map.put("total",t1+"");
 		//获取用户归属档案所有已阅总数
-		Integer t2 = baseMapper.getUserReviewedTotal(projectId, userId);
+		//获取最新一期抽检
+		ArchiveExpertConclusion one = expertConclusionService.getOne(new LambdaUpdateWrapper<ArchiveExpertConclusion>()
+				.eq(ArchiveExpertConclusion::getProjectId, projectId)
+				.orderByDesc(ArchiveExpertConclusion::getCreateTime)
+				.last("limit 1"));
+		Integer t2 = baseMapper.getUserReviewedTotal(projectId, userId,one.getId());
 		if (t2 == null){
 			t2 = 0;
 		}
@@ -3602,32 +3583,4 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 		process.waitFor();
 	}
 
-	/**
-	 * 表格的样式
-	 *
-	 * @param content 内容
-	 * @param font    字体对象
-	 * @param high    表格高度
-	 * @return
-	 * @Param isAlignCenter 是否水平居中
-	 * @Param isAlignMidde  是否垂直居中
-	 */
-	static PdfPCell pdfTableStyle(String content, Font font, int high, boolean isAlignCenter, boolean isAlignMiddle) {
-		Paragraph phrase = new Paragraph(content, font);
-//		phrase.setAlignment(Paragraph.ALIGN_LEFT);
-		PdfPCell pdfPCell = new PdfPCell(phrase);
-		pdfPCell.setMinimumHeight(high);
-		pdfPCell.setUseAscender(true); // 设置可以居中
-		if (isAlignCenter) {
-			pdfPCell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER); // 设置水平居中
-		}else {
-			pdfPCell.setHorizontalAlignment(PdfPCell.ALIGN_LEFT); // 设置首行
-		}
-		if (isAlignMiddle) {
-			pdfPCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE); // 设置垂直居中
-		}else {
-			pdfPCell.setVerticalAlignment(PdfPCell.ALIGN_LEFT); // 设置垂直居中
-		}
-		return pdfPCell;
-	}
 }

+ 78 - 2
blade-service/blade-archive/src/main/java/org/springblade/archive/utils/ItextPdfUtils.java

@@ -199,8 +199,8 @@ public class ItextPdfUtils {
     public static void rowHeader2(Document document,String context,Font font,String sigCode,Font sigFont) throws DocumentException {
         //做左右分隔
         float[] floats = new float[3];
-        floats[0] = 0.15f;
-        floats[1] = 0.6f;
+        floats[0] = 0.22f;
+        floats[1] = 0.58f;
         floats[2] = 0.2f;
         //创建小标题
         PdfPTable tableName = new PdfPTable(3);
@@ -227,4 +227,80 @@ public class ItextPdfUtils {
         document.add(tableName);
     }
 
+    //档案申请验收表头
+    static void tableHeader2(Document document,Font size10font,Font size17font,float[] floats,String projectName) throws DocumentException {
+        //添加标题
+        //创建第一行表格
+        PdfPTable tableName = new PdfPTable(1);
+        tableName.setWidthPercentage(100);  //设置标题长度占纸张比例
+        PdfPCell cell = pdfTableStyle("单位科学技术档案归档目录", size17font, 50);
+        cell.disableBorderSide(15);
+        //给表格赋值
+        tableName.addCell(cell);
+        //将表格添加到文档对象中
+        document.add(tableName);
+        //项目名称
+        PdfPTable two = new PdfPTable(1);
+        two.setWidthPercentage(100);
+        PdfPCell cell2 = pdfTableStyle3("项目名称:"+projectName, size10font, false,true);
+        cell2.disableBorderSide(15);
+        two.addCell(cell2);
+        document.add(two);
+        ItextPdfUtils.blankRow(document,size10font,5);
+        //表头文字
+        String[] name = new String[]{"序号", "档号", "案卷提名", "总页数","保管期限","备注"};
+        //创建第二行,并设置第二行中的表格数
+        PdfPTable twoTable = new PdfPTable(name.length);
+        twoTable.setWidthPercentage(100);
+        //循环将表头数据添加到第二行表格中
+        for (int i = 0; i < name.length; i++) {
+            PdfPCell pdfPCell = pdfTableStyle7(name[i], size10font, 20);
+            twoTable.addCell(pdfPCell);
+        }
+        //设置表格的宽度
+        twoTable.setTotalWidth(floats);
+        document.add(twoTable);
+    }
+
+    //档案申请验收其他字段
+    public static PdfPCell pdfTableStyle11(String content, Font font, int high, boolean isAlignCenter, boolean isAlignMidle) {
+        Paragraph phrase = new Paragraph(content, font);
+        phrase.setAlignment(Paragraph.ALIGN_LEFT);
+        PdfPCell pdfPCell = new PdfPCell(phrase);
+        pdfPCell.setMinimumHeight(high);
+        pdfPCell.setUseAscender(true); // 设置可以居中
+        if (isAlignCenter) {
+            pdfPCell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER); // 设置水平居中
+        }else {
+            pdfPCell.setHorizontalAlignment(PdfPCell.ALIGN_LEFT); // 设置水平居中
+        }
+        if (isAlignMidle) {
+            pdfPCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE); // 设置垂直居中
+        }else {
+            pdfPCell.setVerticalAlignment(PdfPCell.ALIGN_LEFT); // 设置垂直居中
+        }
+
+        return pdfPCell;
+    }
+    //档案申请验收案卷提名
+    public static PdfPCell pdfTableStyle12(String content, Font font, int high, boolean isAlignCenter, boolean isAlignMidle) {
+        Paragraph phrase = new Paragraph("    "+content, font);
+        phrase.setAlignment(Paragraph.ALIGN_LEFT);
+        PdfPCell pdfPCell = new PdfPCell(phrase);
+        pdfPCell.setMinimumHeight(high);
+        pdfPCell.setUseAscender(true); // 设置可以居中
+        if (isAlignCenter) {
+            pdfPCell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER); // 设置水平居中
+        }else {
+            pdfPCell.setHorizontalAlignment(PdfPCell.ALIGN_LEFT); // 设置水平居中
+        }
+        if (isAlignMidle) {
+            pdfPCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE); // 设置垂直居中
+        }else {
+            pdfPCell.setVerticalAlignment(PdfPCell.ALIGN_LEFT); // 设置垂直居中
+        }
+
+        return pdfPCell;
+    }
+
 }

+ 153 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/utils/MyPdfPageHelper2.java

@@ -0,0 +1,153 @@
+package org.springblade.archive.utils;
+
+import com.itextpdf.text.*;
+import com.itextpdf.text.pdf.*;
+
+import java.io.IOException;
+
+/**
+ * @Param   itext生成页脚,标题,档案验收申请PDF使用
+ * @Author wangwl
+ * @Date 2024/1/17 16:31
+ **/
+public class MyPdfPageHelper2 extends PdfPageEventHelper {
+    /**
+     * 页眉
+     */
+    public String header = "";
+    /**
+     * 文档字体大小,页脚页眉最好和文本大小一致
+     */
+    public int presentFontSize = 12;
+    /**
+     * 文档页面大小,最好前面传入,否则默认为A4纸张
+     */
+    public Rectangle pageSize = PageSize.A4;
+    // 模板
+    public PdfTemplate total = null;
+    // 基础字体对象
+    public BaseFont bf = null;
+    // 利用基础字体生成的字体对象,一般用于生成中文文字
+    public Font fontDetail = null;
+    Font size10font = null;
+    Font size17font = null;
+    String projectName = "";
+
+    public MyPdfPageHelper2() {
+    }
+
+    public MyPdfPageHelper2(BaseFont bf, Font size10font, Font size17font, String projectName) {
+        this.bf = bf;
+        this.size10font = size10font;
+        this.size17font = size17font;
+        this.projectName = projectName;
+    }
+
+    /**
+     * @param yeMei
+     *            页眉字符串
+     * @param presentFontSize
+     *            数据体字体大小
+     * @param pageSize
+     *            页面文档大小,A4,A5,A6横转翻转等Rectangle对象
+     */
+    public MyPdfPageHelper2(String yeMei, int presentFontSize, Rectangle pageSize) {
+        this.header = yeMei;
+        this.presentFontSize = presentFontSize;
+        this.pageSize = pageSize;
+    }
+
+    public void setHeader(String header) {
+        this.header = header;
+    }
+
+    public void setPresentFontSize(int presentFontSize) {
+        this.presentFontSize = presentFontSize;
+    }
+
+    /**
+     * TODO 文档打开时创建模板
+     */
+    @Override
+    public void onOpenDocument(PdfWriter writer, Document document) {
+        total = writer.getDirectContent().createTemplate(50, 50);// 共页的矩形的长宽高
+    }
+
+    @Override
+    public void onStartPage(PdfWriter writer, Document document) {
+        float[] floats = new float[6];
+        floats[0] = 0.09f;
+        floats[1] = 0.14f;
+        floats[2] = 0.48f;
+        floats[3] = 0.09f;
+        floats[4] = 0.11f;
+        floats[5] = 0.09f;
+        try {
+            ItextPdfUtils.tableHeader2(document, size10font, size17font, floats,projectName);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * TODO 关闭每页的时候,写入页眉,写入'第几页共'这几个字。
+     */
+    @Override
+    public void onEndPage(PdfWriter writer, Document document) {
+        try {
+            if (bf == null) {
+                bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
+            }
+            if (fontDetail == null) {
+                fontDetail = new Font(bf, presentFontSize, Font.NORMAL);// 数据体字体
+            }
+        } catch (DocumentException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        // 拿到当前的PdfContentByte
+        PdfContentByte cb = writer.getDirectContent();
+
+        // 1.写入页眉     参数:要写入文本的页面对象,对齐,文字内容,X轴位置,Y轴位置,逆时针旋转的角度
+        ColumnText.showTextAligned(cb, Element.ALIGN_LEFT, new Phrase(header, fontDetail), document.left(), document.top() + 20, 45);
+
+        // 2.写入页脚    前半部分的 第 X页/共
+        int pageS = writer.getPageNumber();
+        String foot1 = "第 " + pageS + " 页";
+        Phrase footer = new Phrase(foot1, fontDetail);
+
+        // 计算前半部分的foot1的长度,后面好定位最后一部分的'Y页'这俩字的x轴坐标,字体长度也要计算进去 = len
+        float len = bf.getWidthPoint(foot1, presentFontSize);
+
+        /**
+         *  写入页脚   前半部分的    第 X页/共
+         * x轴就是(右margin+左margin + right() -left()- len)/2.0F 再给偏移20F适合人类视觉感受,否则肉眼看上去就太偏左了
+         * y轴就是底边界-20,否则就贴边重叠到数据体里了就不是页脚了;注意Y轴是从下往上累加的,最上方的Top值是大于Bottom好几百开外的。
+         */
+        ColumnText.showTextAligned(cb, Element.ALIGN_CENTER, footer, (document.rightMargin() + document.right() + document.leftMargin() - document.left() - len) / 2.0F + 20F, document.bottom() - 20, 0);
+
+        /**
+         *  写入页脚  后半部分   ?页
+         *  注:因为共?页   中的?值是在doc.close() 后才能得到,所以这一先加入模板,最后结束的时候后边用值替换
+         * x=(右margin+左margin + right() -left())/2.0F
+         * y 轴和之前的保持一致,底边界-20
+         */
+//        cb.addTemplate(total, (document.rightMargin() + document.right() + document.leftMargin() - document.left()) / 2.0F + 20F, document.bottom() - 20); // 调节模版显示的位置
+    }
+
+    /**
+     * TODO 关闭文档时,替换模板,完成整个页眉页脚组件
+     */
+    @Override
+    public void onCloseDocument(PdfWriter writer, Document document) {
+        // 7.最后一步了,就是关闭文档的时候,将模板替换成实际的 Y 值,至此,page x of y 制作完毕,完美兼容各种文档size。
+        total.beginText();
+        total.setFontAndSize(bf, presentFontSize);// 生成的模版的字体、颜色
+        String foot2 = " " + writer.getPageNumber() + " 页";
+        total.showText(foot2);// 模版显示的内容
+        total.endText();
+        total.closePath();
+    }
+}

+ 40 - 4
blade-service/blade-business/src/main/java/org/springblade/business/controller/ImageClassificationFileController.java

@@ -31,6 +31,7 @@ import org.springblade.core.mp.support.Query;
 import org.springblade.core.oss.model.BladeFile;
 import org.springblade.core.secure.utils.AuthUtil;
 import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.api.ResultCode;
 import org.springblade.core.tool.utils.Func;
 import org.springblade.core.tool.utils.IoUtil;
 import org.springblade.core.tool.utils.ObjectUtil;
@@ -49,6 +50,7 @@ import org.springblade.resource.vo.NewBladeFile;
 import org.springblade.system.entity.Dict;
 import org.springblade.system.feign.IDictClient;
 import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.mock.web.MockMultipartFile;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.RequestParam;
@@ -80,6 +82,7 @@ import static org.springblade.common.utils.CommonUtil.replaceOssUrl;
 @Api(tags = "影音资料接口(客户端)")
 public class ImageClassificationFileController extends BladeController {
 
+    @Autowired
     private final IImageClassificationFileService imageClassificationFileService;
 
     private final ImageClassificationShowService imageClassificationShowService;
@@ -160,7 +163,8 @@ public class ImageClassificationFileController extends BladeController {
         List<String> result = new ArrayList<>();
         if (excelTab != null) {
             //获取数据
-            List<ImageClassificationFile> fileResult = this.imageClassificationFileService.list(Wrappers.<ImageClassificationFile>lambdaQuery().in(ImageClassificationFile::getId, Arrays.asList(ids.split(","))));
+            List<String> strings = Arrays.asList(ids.split(","));
+            List<ImageClassificationFile> fileResult = this.imageClassificationFileService.list(Wrappers.<ImageClassificationFile>query().lambda().in(ImageClassificationFile::getId, strings));
 
             try {
                 if (fileResult != null && fileResult.size() > 0) {
@@ -609,8 +613,10 @@ public class ImageClassificationFileController extends BladeController {
     @ApiOperation(value = "修改影音资料信息", notes = "传入表单数据")
     public R<Boolean> update(@Valid @RequestBody ImageClassificationFileVO fileVO) {
         ImageClassificationFile newData = this.copyBeanData(fileVO, false);
-        newData.setMargePdfUrl("");
-        return R.data(this.imageClassificationFileService.updateById(newData));
+        boolean update = this.imageClassificationFileService.updateById(newData);
+        // 生成合并pdfUrl
+        generateMergePdfUrl(update, newData);
+        return R.data(update);
     }
 
     /**
@@ -637,8 +643,12 @@ public class ImageClassificationFileController extends BladeController {
             newFile.setClassifyId(fileVO.getClassifyId());
             newFile.setContractId(fileVO.getContractId());
             newFile.setProjectId(fileVO.getProjectId());
+            // 新增操作
+            boolean save = this.imageClassificationFileService.save(newFile);
+            // 生成合并pdfUrl
+            generateMergePdfUrl(save, newFile);
             //落库数据
-            return R.status(this.imageClassificationFileService.save(newFile));
+            return R.status(save);
         } catch (Exception e) {
             e.printStackTrace();
         }
@@ -777,4 +787,30 @@ public class ImageClassificationFileController extends BladeController {
         return R.data(this.imageClassificationFileService.getFileTitleName(pKeyId));
     }
 
+    /**
+     * 生成合并PdfUrl
+     * 写这个方法是因为声像资料同步那边,一定要有可以预览的东西,
+     * 以前的逻辑是有前端点击查看按钮时,才会根据type等条件调用preview()这个方法
+     */
+    private void generateMergePdfUrl(Boolean flag, ImageClassificationFile newFile){
+        if(true){
+            // 文件类型为图片时
+            if(newFile.getType() == 2){
+                // 生成合并的pdfUrl
+                R<String> preview = preview(newFile.getId().toString());
+                if(preview.getCode() == ResultCode.SUCCESS.getCode()){
+                    // 获得合并后的pdfUrl
+                    /**后续要多注意preview()这个方法所返回回来的值,现在固定的返回集合中的第一个**/
+                    String data = preview.getData();
+                    // 获取pdf页码
+                    String pdfPage  = commonFileClient.getPdfNum(data);
+                    // 更新mergePdfUrl
+                    this.imageClassificationFileService.update(Wrappers.<ImageClassificationFile>lambdaUpdate()
+                            .set(ImageClassificationFile::getMargePdfUrl, preview.getData())
+                            .set(ImageClassificationFile::getFilePage, Integer.valueOf(pdfPage))
+                            .eq(ImageClassificationFile::getId, newFile.getId()));
+                }
+            }
+        }
+    }
 }

+ 18 - 6
blade-service/blade-business/src/main/java/org/springblade/business/controller/InformationWriteQueryController.java

@@ -1962,13 +1962,13 @@ public class InformationWriteQueryController extends BladeController {
         return value;
     }
 
-    public  String imitate(String v, List<RangeJson> rjs) {
+    public String imitate(String v, List<RangeJson> rjs) {
         try {
             RangeJson best = rjs.stream().min(Comparator.comparingDouble(j -> Double.parseDouble(v) - Double.parseDouble(j.getDesign()))).orElse(rjs.get(0));
-           // int scale = Math.max(new StringBuilder(v).reverse().indexOf("."), 0);
-            int scale=BaseUtils.getScaleZero(v,best.getDev(),best.getDesign());
+            // int scale = Math.max(new StringBuilder(v).reverse().indexOf("."), 0);
+            int scale = BaseUtils.getScaleZero(v, best.getDev(), best.getDesign());
             return BaseUtils.rangeList(1, best.getDesign(), best.getDev(), 1, scale, 1).get(0).toString();
-        }catch (Exception e){
+        } catch (Exception e) {
             e.printStackTrace();
             return StringPool.EMPTY;
         }
@@ -2222,7 +2222,13 @@ public class InformationWriteQueryController extends BladeController {
                         //跨节点复制,更改父级id
                         obj.setParentId(toCopyNode.getId());
                     }
-                    obj.setOldId(needNode.getId() + "");
+
+                    if (ObjectUtil.isNotEmpty(needNode.getOldId())) {
+                        obj.setOldId(needNode.getOldId());
+                    } else {
+                        obj.setOldId(needNode.getId() + "");
+                    }
+
                     obj.setPKeyId(SnowFlakeUtil.getId());
                     obj.setId(id);
                     obj.setNodeName(toCopyNode.getNodeName());
@@ -2327,7 +2333,13 @@ public class InformationWriteQueryController extends BladeController {
                         newParentId = needCopyNode.getId();
                     }
                     obj.setPKeyId(SnowFlakeUtil.getId());
-                    obj.setOldId(node.getId().toString());
+
+                    if (ObjectUtil.isNotEmpty(node.getOldId())) {
+                        obj.setOldId(node.getOldId());
+                    } else {
+                        obj.setOldId(node.getId() + "");
+                    }
+
                     Long id = SnowFlakeUtil.getId();
                     //数据源节点的老id与新的id的Map,作为下一级的节点的父级id的替换
                     parentIdToId.put(node.getId(), id);

+ 2 - 1
blade-service/blade-business/src/main/java/org/springblade/business/controller/TaskController.java

@@ -668,7 +668,7 @@ public class TaskController extends BladeController {
                                     dto.setAccount("expert" + phone);
                                     //如果当前账户已经存在,则代表账号已经注册,直接跳过当前
                                     R<User> r = userClient.userByAccount(AuthUtil.getTenantId(), dto.getAccount());
-                                    if (r.getData() != null) {
+                                    if (r.getData() != null && r.getData().getId() != null) {
                                         if (expertIds.length() == 0){
                                             expertIds.append(r.getData().getId());
                                         }else {
@@ -681,6 +681,7 @@ public class TaskController extends BladeController {
                                     dto.setUserType("3");
                                     dto.setRealName(name);
                                     dto.setPhone(phone);
+                                    dto.setIdNumber(expertInfo.getIdNumber());
                                     //设置专家部门
                                     dto.setDeptId(AuthUtil.getDeptId());
                                     dto.setStatus(1);

+ 498 - 0
blade-service/blade-business/src/main/java/org/springblade/business/controller/TrialSummaryController.java

@@ -0,0 +1,498 @@
+package org.springblade.business.controller;
+
+import com.aspose.cells.PageSetup;
+import com.aspose.cells.PaperSizeType;
+import com.aspose.cells.PdfSaveOptions;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang.StringUtils;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.usermodel.*;
+import org.springblade.business.dto.TrialSummaryMonthlyEditDTO;
+import org.springblade.business.dto.TrialSummaryMonthlyPageDTO;
+import org.springblade.business.entity.TrialSelfInspectionRecord;
+import org.springblade.business.entity.TrialSummaryMonthlyRemarks;
+import org.springblade.common.utils.SnowFlakeUtil;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.oss.model.BladeFile;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.ObjectUtil;
+import org.springblade.resource.feign.NewIOSSClient;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.net.URLDecoder;
+import java.util.*;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@RestController
+@AllArgsConstructor
+@RequestMapping("/trial/summary")
+@Api(value = "试验、月报汇总", tags = "试验、月报汇总接口")
+public class TrialSummaryController {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final NewIOSSClient newIOSSClient;
+
+    @PostMapping("/monthly/page")
+    @ApiOperationSupport(order = 1)
+    @ApiOperation(value = "月报汇总分页查询", notes = "传入TrialSummaryMonthlyPageDTO")
+    public R<IPage<Map<String, Object>>> monthlyPage(@RequestBody TrialSummaryMonthlyPageDTO dto) {
+        if (ObjectUtil.isEmpty(dto.getContractId()) || ObjectUtil.isEmpty(dto.getType()) || ObjectUtil.isEmpty(dto.getIds()) || StringUtils.isBlank(dto.getStartTime()) || StringUtils.isBlank(dto.getEndTime())) {
+            throw new ServiceException("入参异常");
+        }
+        List<Object> params = new ArrayList<>();
+        StringBuilder sqlString = new StringBuilder("SELECT * FROM u_trial_self_inspection_record WHERE 1=1 AND is_deleted = 0");
+        if (ObjectUtil.isNotEmpty(dto.getContractId())) {
+            sqlString.append(" AND contract_id = ?");
+            params.add(dto.getContractId());
+        }
+        if (ObjectUtil.isNotEmpty(dto.getType())) {
+            sqlString.append(" AND detection_category = ?");
+            params.add(dto.getType());
+        } else {
+            sqlString.append(" AND detection_category = 1");
+        }
+        if (StringUtils.isNotBlank(dto.getStartTime()) && StringUtils.isNotBlank(dto.getEndTime())) {
+            sqlString.append(" AND report_date BETWEEN ? AND ?");
+            params.add(dto.getStartTime());
+            params.add(dto.getEndTime());
+        }
+        if (StringUtils.isNotEmpty(dto.getIds())) {
+            sqlString.append(" AND node_id in(?)");
+            params.add(dto.getIds());
+        }
+
+        String sqlPage = sqlString.append(" ORDER BY create_time;").toString();
+        List<TrialSelfInspectionRecord> resultList = jdbcTemplate.query(
+                sqlPage,
+                new BeanPropertyRowMapper<>(TrialSelfInspectionRecord.class),
+                params.toArray()
+        );
+
+        Map<String, List<TrialSelfInspectionRecord>> groupTOTrialProjectName = resultList.stream()
+                .collect(Collectors.groupingBy(TrialSelfInspectionRecord::getTrialProjectName, LinkedHashMap::new, Collectors.toList()));
+
+        int current = dto.getCurrent();
+        int size = dto.getSize();
+        int start = (current - 1) * size;
+        Map<String, List<TrialSelfInspectionRecord>> paginatedMap = groupTOTrialProjectName.entrySet()
+                .stream()
+                .skip(start)
+                .limit(size)
+                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
+
+        if (paginatedMap.size() <= 0) {
+            return R.data(null);
+        }
+
+        Set<String> names = paginatedMap.keySet();
+        String commaSeparatedQuotedNames = names.stream()
+                .map(name -> "'" + name + "'")
+                .collect(Collectors.joining(","));
+        String sql = "SELECT * FROM u_trial_self_inspection_record WHERE is_deleted = 0 AND contract_id = " + dto.getContractId() + " AND trial_project_name IN (" + commaSeparatedQuotedNames + ")";
+        List<TrialSelfInspectionRecord> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(TrialSelfInspectionRecord.class));
+        Map<String, List<TrialSelfInspectionRecord>> groupAllTOTrialProjectName = query.stream().collect(Collectors.groupingBy(TrialSelfInspectionRecord::getTrialProjectName));
+
+        String sqlRemarks = "SELECT record_id,remarks FROM m_trial_summary_monthly_remarks WHERE contract_id = ?";
+        List<TrialSummaryMonthlyRemarks> trialSummaryMonthlyRemarks = jdbcTemplate.query(sqlRemarks, new Object[]{dto.getContractId()}, new BeanPropertyRowMapper<>(TrialSummaryMonthlyRemarks.class));
+        Map<Long, TrialSummaryMonthlyRemarks> trialSummaryMonthlyRemarksMaps = trialSummaryMonthlyRemarks.stream().collect(Collectors.toMap(TrialSummaryMonthlyRemarks::getRecordId, Function.identity()));
+
+        IPage<Map<String, Object>> page = new Page<>();
+        List<Map<String, Object>> result = new LinkedList<>();
+
+        for (Map.Entry<String, List<TrialSelfInspectionRecord>> stringListEntry : paginatedMap.entrySet()) {
+            String key = stringListEntry.getKey();
+            if (ObjectUtil.isEmpty(key)) {
+                continue;
+            }
+
+            Map<String, Object> map = new LinkedHashMap<>();
+            map.put("trialProjectName", key);
+
+            List<TrialSelfInspectionRecord> currentMonthValue = stringListEntry.getValue();
+            Map<String, Object> currentMonthValueMap = new HashMap<>();
+            Optional<TrialSelfInspectionRecord> minIdRecord = currentMonthValue.stream()
+                    .min(Comparator.comparingLong(TrialSelfInspectionRecord::getId));
+            TrialSelfInspectionRecord minIdRecordOrNull = minIdRecord.orElse(null);
+            if (minIdRecordOrNull != null) {
+                long recordId = minIdRecordOrNull.getId() + 1; //防止与累加id重复,+1
+                currentMonthValueMap.put("recordId", recordId);
+                TrialSummaryMonthlyRemarks orDefault = trialSummaryMonthlyRemarksMaps.getOrDefault(recordId, null);
+                currentMonthValueMap.put("remarks", ObjectUtil.isNotEmpty(orDefault) ? ObjectUtil.isNotEmpty(orDefault.getRemarks()) ? orDefault.getRemarks() : "" : "");
+            }
+            currentMonthValueMap.put("qualifiedTotal", currentMonthValue.stream().filter(f -> f.getDetectionResult() == 1).count());
+            currentMonthValueMap.put("unQualifiedTotal", currentMonthValue.stream().filter(f -> f.getDetectionResult() == 0).count());
+            map.put("currentMonth", currentMonthValueMap);
+
+            List<TrialSelfInspectionRecord> totalMonthValue = groupAllTOTrialProjectName.getOrDefault(key, null);
+            Map<String, Object> totalMonthValueMap = new HashMap<>();
+            Optional<TrialSelfInspectionRecord> minIdRecordAll = totalMonthValue.stream()
+                    .min(Comparator.comparingLong(TrialSelfInspectionRecord::getId));
+            TrialSelfInspectionRecord minIdRecordOrNullAll = minIdRecordAll.orElse(null);
+            if (minIdRecordOrNullAll != null) {
+                long recordId = minIdRecordOrNullAll.getId() + 2; //防止与本月id重复,+2
+                totalMonthValueMap.put("recordId", recordId);
+                TrialSummaryMonthlyRemarks orDefault = trialSummaryMonthlyRemarksMaps.getOrDefault(recordId, null);
+                totalMonthValueMap.put("remarks", ObjectUtil.isNotEmpty(orDefault) ? ObjectUtil.isNotEmpty(orDefault.getRemarks()) ? orDefault.getRemarks() : "" : "");
+            }
+            totalMonthValueMap.put("qualifiedTotal", totalMonthValue.stream().filter(f -> f.getDetectionResult() == 1).count());
+            totalMonthValueMap.put("unQualifiedTotal", totalMonthValue.stream().filter(f -> f.getDetectionResult() == 0).count());
+            map.put("totalMonth", totalMonthValueMap);
+
+            result.add(map);
+        }
+
+        page.setRecords(result);
+        page.setSize(size);
+        page.setCurrent(current);
+        page.setTotal(groupTOTrialProjectName.size());
+        page.setPages((page.getTotal() + size - 1) / size);
+        return R.data(page);
+    }
+
+    @PostMapping("/monthly/edit")
+    @ApiOperationSupport(order = 2)
+    @ApiOperation(value = "月报汇总编辑备注", notes = "传入TrialSummaryMonthlyEditDTO")
+    public R<Object> monthlyEdit(@RequestBody TrialSummaryMonthlyEditDTO dto) {
+        if (ObjectUtil.isEmpty(dto.getRecordId()) || ObjectUtil.isEmpty(dto.getContractId())) {
+            throw new ServiceException("入参异常");
+        }
+        if (ObjectUtil.isNotEmpty(dto.getRemarks()) && dto.getRemarks().length() > 2000) {
+            throw new ServiceException("备注信息最长2000个字符,请重新输入");
+        }
+        try {
+            String sql = "SELECT COUNT(*) FROM m_trial_summary_monthly_remarks WHERE record_id = ? AND contract_id = ?";
+            Integer count = jdbcTemplate.queryForObject(sql, Integer.class, dto.getRecordId(), dto.getContractId());
+            int rowCount = (count != null) ? count : 0;
+            if (rowCount == 0) {
+                String insertSql = "INSERT INTO m_trial_summary_monthly_remarks(id, contract_id, record_id, remarks) VALUES (?, ?, ?, ?)";
+                Object[] insertParams = {
+                        SnowFlakeUtil.getId(),
+                        dto.getContractId(),
+                        dto.getRecordId(),
+                        ObjectUtil.isNotEmpty(dto.getRemarks()) ? dto.getRemarks() : null
+                };
+                jdbcTemplate.update(insertSql, insertParams);
+            } else {
+                String updateSql = "UPDATE m_trial_summary_monthly_remarks SET remarks = ? WHERE record_id = ? AND contract_id = ?";
+                Object[] updateParams = {
+                        ObjectUtil.isNotEmpty(dto.getRemarks()) ? dto.getRemarks() : null,
+                        dto.getRecordId(),
+                        dto.getContractId()
+                };
+                jdbcTemplate.update(updateSql, updateParams);
+            }
+
+            return R.data(200, null, "操作成功");
+        } catch (Exception e) {
+            return R.fail("操作失败" + e.getMessage());
+        }
+    }
+
+    @PostMapping("/monthly/download")
+    @ApiOperationSupport(order = 3)
+    @ApiOperation(value = "月报汇总下载", notes = "传入TrialSummaryMonthlyPageDTO")
+    public void monthlyDownload(@RequestBody TrialSummaryMonthlyPageDTO dto, HttpServletResponse response) {
+        List<Map<String, Object>> records = monthlyPage(dto).getData().getRecords();
+        try (Workbook workbook = new XSSFWorkbook()) {
+            Sheet sheet = workbook.createSheet("Sheet1");
+            Row titleRow_1 = sheet.createRow(0);
+            sheet.addMergedRegion(new CellRangeAddress(0, 1, 0, 1));
+            titleRow_1.setHeight((short) 500);
+            setCellValueWithNullCheck(titleRow_1.createCell(0), "", "");
+
+            CreationHelper helper = workbook.getCreationHelper();
+            XSSFDrawing xssfDrawing = (XSSFDrawing) sheet.createDrawingPatriarch();
+            ClientAnchor anchor = helper.createClientAnchor();
+            anchor.setCol1(0);
+            anchor.setCol2(2);
+            anchor.setRow1(0);
+            anchor.setRow2(2);
+            XSSFSimpleShape simpleShape = xssfDrawing.createSimpleShape((XSSFClientAnchor) anchor);
+            simpleShape.setShapeType(ShapeTypes.LINE);
+            simpleShape.setLineWidth(0.5);
+            simpleShape.setLineStyle(0);
+            simpleShape.setLineStyleColor(0, 0, 0);
+
+            int leftBottomX = anchor.getDx1();
+            int leftBottomY = anchor.getDy1();
+            int rightTopX = anchor.getDx2();
+            int rightTopY = anchor.getDy2();
+            XSSFDrawing xssfDrawingText1 = (XSSFDrawing) sheet.createDrawingPatriarch();
+            XSSFClientAnchor textBoxAnchor1 = new XSSFClientAnchor(leftBottomX, leftBottomY, leftBottomX, leftBottomY, 1, 0, 2, 1);
+            XSSFTextBox textBox1 = xssfDrawingText1.createTextbox(textBoxAnchor1);
+            textBox1.setText("试验组数");
+            XSSFDrawing xssfDrawingText2 = (XSSFDrawing) sheet.createDrawingPatriarch();
+            XSSFClientAnchor textBoxAnchor2 = new XSSFClientAnchor(rightTopX, rightTopY, rightTopX, rightTopY, 0, 1, 1, 2);
+            XSSFTextBox textBox2 = xssfDrawingText2.createTextbox(textBoxAnchor2);
+            textBox2.setText("试验项目");
+
+            sheet.addMergedRegion(new CellRangeAddress(0, 0, 2, 4));
+            setCellValueWithNullCheck(titleRow_1.createCell(2), "承包人", "");
+            Row titleRow_2 = sheet.createRow(1);
+            titleRow_2.setHeight((short) 500);
+            setCellValueWithNullCheck(titleRow_2.createCell(2), "合格数", "");
+            setCellValueWithNullCheck(titleRow_2.createCell(3), "不合格数", "");
+            setCellValueWithNullCheck(titleRow_2.createCell(4), "备注", "");
+
+            int rowNum = 2;
+            for (Map<String, Object> record : records) {
+                Row currentMonthRow = sheet.createRow(rowNum);
+                sheet.addMergedRegion(new CellRangeAddress(rowNum, rowNum + 1, 0, 0));
+                setCellValueWithNullCheck(currentMonthRow.createCell(0), "", record.get("trialProjectName"));
+
+                Map<String, Object> currentMonth = (Map<String, Object>) record.get("currentMonth");
+                setCellValueWithNullCheck(currentMonthRow.createCell(1), "本月", "");
+                setCellValueWithNullCheck(currentMonthRow.createCell(2), "", currentMonth.get("qualifiedTotal"));
+                setCellValueWithNullCheck(currentMonthRow.createCell(3), "", currentMonth.get("unQualifiedTotal"));
+                setCellValueWithNullCheck(currentMonthRow.createCell(4), "", currentMonth.get("remarks"));
+
+                Map<String, Object> totalMonth = (Map<String, Object>) record.get("totalMonth");
+                Row totalMonthRow = sheet.createRow(++rowNum);
+                setCellValueWithNullCheck(totalMonthRow.createCell(1), "累计", "");
+                setCellValueWithNullCheck(totalMonthRow.createCell(2), "", totalMonth.get("qualifiedTotal"));
+                setCellValueWithNullCheck(totalMonthRow.createCell(3), "", totalMonth.get("unQualifiedTotal"));
+                setCellValueWithNullCheck(totalMonthRow.createCell(4), "", totalMonth.get("remarks"));
+
+                rowNum++;
+            }
+
+            sheet.setColumnWidth(0, 5000);
+            sheet.setColumnWidth(1, 5000);
+            sheet.setColumnWidth(2, 5000);
+            sheet.setColumnWidth(3, 5000);
+            sheet.setColumnWidth(4, 5000);
+
+            try (ServletOutputStream outputStream = response.getOutputStream();
+                 ByteArrayOutputStream byteArrayOutputStreamResult = new ByteArrayOutputStream()) {
+                String fileName = dto.getStartTime() + "~" + dto.getEndTime() + ".xlsx";
+                String decodedFileName = URLDecoder.decode(fileName, "UTF-8");
+                response.setHeader("Content-Disposition", "attachment; filename=\"" + decodedFileName + "\"");
+                response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+                workbook.write(byteArrayOutputStreamResult);
+                byte[] excelBytesResult = byteArrayOutputStreamResult.toByteArray();
+                outputStream.write(excelBytesResult);
+
+            } catch (IOException e) {
+                e.printStackTrace();
+            } finally {
+                workbook.close();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void setCellValueWithNullCheck(Cell cell, String defaultValue, Object value) {
+        if (ObjectUtil.isNotEmpty(value)) {
+            cell.setCellValue(String.valueOf(value));
+        } else {
+            cell.setCellValue(defaultValue);
+        }
+    }
+
+    @PostMapping("/monthly/print")
+    @ApiOperationSupport(order = 4)
+    @ApiOperation(value = "月报汇总打印", notes = "传入TrialSummaryMonthlyPageDTO")
+    public R<Object> monthlyPrint(@RequestBody TrialSummaryMonthlyPageDTO dto) {
+        List<Map<String, Object>> records = monthlyPage(dto).getData().getRecords();
+        try (Workbook workbook = new XSSFWorkbook()) {
+            Sheet sheet = workbook.createSheet("Sheet1");
+            Row titleRow_1 = sheet.createRow(0);
+            sheet.addMergedRegion(new CellRangeAddress(0, 1, 0, 1));
+            titleRow_1.setHeight((short) 500);
+            setCellValueWithNullCheck(titleRow_1.createCell(0), "", "");
+
+            CreationHelper helper = workbook.getCreationHelper();
+            XSSFDrawing xssfDrawing = (XSSFDrawing) sheet.createDrawingPatriarch();
+            ClientAnchor anchor = helper.createClientAnchor();
+            anchor.setCol1(0);
+            anchor.setCol2(2);
+            anchor.setRow1(0);
+            anchor.setRow2(2);
+            XSSFSimpleShape simpleShape = xssfDrawing.createSimpleShape((XSSFClientAnchor) anchor);
+            simpleShape.setShapeType(ShapeTypes.LINE);
+            simpleShape.setLineWidth(0.5);
+            simpleShape.setLineStyle(0);
+            simpleShape.setLineStyleColor(0, 0, 0);
+
+            int leftBottomX = anchor.getDx1();
+            int leftBottomY = anchor.getDy1();
+            int rightTopX = anchor.getDx2();
+            int rightTopY = anchor.getDy2();
+            XSSFDrawing xssfDrawingText1 = (XSSFDrawing) sheet.createDrawingPatriarch();
+            XSSFClientAnchor textBoxAnchor1 = new XSSFClientAnchor(leftBottomX, leftBottomY, leftBottomX, leftBottomY, 1, 0, 2, 1);
+            XSSFTextBox textBox1 = xssfDrawingText1.createTextbox(textBoxAnchor1);
+            textBox1.setText("试验组数");
+            XSSFDrawing xssfDrawingText2 = (XSSFDrawing) sheet.createDrawingPatriarch();
+            XSSFClientAnchor textBoxAnchor2 = new XSSFClientAnchor(rightTopX, rightTopY, rightTopX, rightTopY, 0, 1, 1, 2);
+            XSSFTextBox textBox2 = xssfDrawingText2.createTextbox(textBoxAnchor2);
+            textBox2.setText("试验项目");
+
+            sheet.addMergedRegion(new CellRangeAddress(0, 0, 2, 4));
+            setCellValueWithNullCheck(titleRow_1.createCell(2), "承包人", "");
+            Row titleRow_2 = sheet.createRow(1);
+            titleRow_2.setHeight((short) 500);
+            setCellValueWithNullCheck(titleRow_2.createCell(2), "合格数", "");
+            setCellValueWithNullCheck(titleRow_2.createCell(3), "不合格数", "");
+            setCellValueWithNullCheck(titleRow_2.createCell(4), "备注", "");
+
+            int rowNum = 2;
+            for (Map<String, Object> record : records) {
+                Row currentMonthRow = sheet.createRow(rowNum);
+                sheet.addMergedRegion(new CellRangeAddress(rowNum, rowNum + 1, 0, 0));
+                setCellValueWithNullCheck(currentMonthRow.createCell(0), "", record.get("trialProjectName"));
+
+                Map<String, Object> currentMonth = (Map<String, Object>) record.get("currentMonth");
+                setCellValueWithNullCheck(currentMonthRow.createCell(1), "本月", "");
+                setCellValueWithNullCheck(currentMonthRow.createCell(2), "", currentMonth.get("qualifiedTotal"));
+                setCellValueWithNullCheck(currentMonthRow.createCell(3), "", currentMonth.get("unQualifiedTotal"));
+                setCellValueWithNullCheck(currentMonthRow.createCell(4), "", currentMonth.get("remarks"));
+
+                Map<String, Object> totalMonth = (Map<String, Object>) record.get("totalMonth");
+                Row totalMonthRow = sheet.createRow(++rowNum);
+                setCellValueWithNullCheck(totalMonthRow.createCell(1), "累计", "");
+                setCellValueWithNullCheck(totalMonthRow.createCell(2), "", totalMonth.get("qualifiedTotal"));
+                setCellValueWithNullCheck(totalMonthRow.createCell(3), "", totalMonth.get("unQualifiedTotal"));
+                setCellValueWithNullCheck(totalMonthRow.createCell(4), "", totalMonth.get("remarks"));
+
+                rowNum++;
+            }
+
+            sheet.setColumnWidth(0, 5000);
+            sheet.setColumnWidth(1, 5000);
+            sheet.setColumnWidth(2, 5000);
+            sheet.setColumnWidth(3, 5000);
+            sheet.setColumnWidth(4, 5000);
+
+            try (ByteArrayOutputStream byteArrayOutputStreamResult = new ByteArrayOutputStream()) {
+                workbook.write(byteArrayOutputStreamResult);
+                byte[] excelBytesResult = byteArrayOutputStreamResult.toByteArray();
+                MultipartFile multipartFile = convertExcelToPdf(excelBytesResult);
+                if (multipartFile != null) {
+                    BladeFile bladeFile = newIOSSClient.uploadFileByInputStream(multipartFile);
+                    if (bladeFile != null) {
+                        return R.data(bladeFile.getLink());
+                    }
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            } finally {
+                workbook.close();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return R.fail("操作失败");
+    }
+
+    private MultipartFile convertExcelToPdf(byte[] excelBytes) {
+        ByteArrayInputStream byteArrayInputStream = null;
+        ByteArrayOutputStream outReport = null;
+        try {
+            byteArrayInputStream = new ByteArrayInputStream(excelBytes);
+            Workbook ss = WorkbookFactory.create(byteArrayInputStream);
+            Sheet sheet = ss.getSheetAt(0);
+            for (int r = 0; r <= sheet.getLastRowNum(); r++) {
+                Row row = sheet.getRow(r);
+                if (row != null) {
+                    for (int c = 0; c < row.getLastCellNum(); c++) {
+                        Cell cell = row.getCell(c);
+                        if (cell != null) {
+                            CellStyle cellStyle = ss.createCellStyle();
+                            cellStyle.cloneStyleFrom(cell.getCellStyle());
+                            cellStyle.setBorderBottom(BorderStyle.THIN);
+                            cellStyle.setBorderTop(BorderStyle.THIN);
+                            cellStyle.setBorderRight(BorderStyle.THIN);
+                            cellStyle.setBorderLeft(BorderStyle.THIN);
+                            cell.setCellStyle(cellStyle);
+                            CellRangeAddress mergedRegion = getMergedRegion(sheet, r, c);
+                            if (mergedRegion != null) {
+                                setMergedRegionBorders(sheet, mergedRegion, cellStyle);
+                            }
+                        }
+                    }
+                }
+                sheet.setPrintGridlines(false);
+                sheet.setFitToPage(true);
+            }
+
+            outReport = new ByteArrayOutputStream();
+            ss.write(outReport);
+
+            PdfSaveOptions pdfSaveOptions = new PdfSaveOptions();
+            pdfSaveOptions.setOnePagePerSheet(true);
+
+            com.aspose.cells.Workbook wb = new com.aspose.cells.Workbook(new ByteArrayInputStream(outReport.toByteArray()));
+            PageSetup pageSetup = wb.getWorksheets().get(0).getPageSetup();
+            pageSetup.setPaperSize(PaperSizeType.PAPER_A_4);
+
+            ByteArrayOutputStream pdfByteArrayOutputStream = new ByteArrayOutputStream();
+            wb.save(pdfByteArrayOutputStream, pdfSaveOptions);
+
+            ByteArrayResource resource = new ByteArrayResource(pdfByteArrayOutputStream.toByteArray());
+
+            return new MockMultipartFile("file", SnowFlakeUtil.getId() + ".pdf", "application/pdf", resource.getInputStream());
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        } finally {
+            if (outReport != null) {
+                try {
+                    outReport.close();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            if (byteArrayInputStream != null) {
+                try {
+                    byteArrayInputStream.close();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    private void setMergedRegionBorders(Sheet sheet, CellRangeAddress region, CellStyle style) {
+        for (int row = region.getFirstRow(); row <= region.getLastRow(); row++) {
+            Row currentRow = sheet.getRow(row);
+            if (currentRow == null) {
+                currentRow = sheet.createRow(row);
+            }
+            for (int col = region.getFirstColumn(); col <= region.getLastColumn(); col++) {
+                Cell currentCell = currentRow.getCell(col);
+                if (currentCell == null) {
+                    currentCell = currentRow.createCell(col);
+                }
+                currentCell.setCellStyle(style);
+            }
+        }
+    }
+
+    private CellRangeAddress getMergedRegion(Sheet sheet, int row, int col) {
+        for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
+            CellRangeAddress mergedRegion = sheet.getMergedRegion(i);
+            if (mergedRegion.isInRange(row, col)) {
+                return mergedRegion;
+            }
+        }
+        return null;
+    }
+}

+ 1 - 1
blade-service/blade-business/src/main/java/org/springblade/business/controller/WeatherController.java

@@ -47,7 +47,7 @@ public class WeatherController {
     //测试定时
     @GetMapping("/testTask")
     public void testTask() {
-        weatherInfoServiceImpl.syncHistoryWeatherInfo();
+        weatherInfoServiceImpl.scanMissWeather();
     }
 
     /**

+ 10 - 0
blade-service/blade-business/src/main/java/org/springblade/business/feignClient/ArchiveFileClientImpl.java

@@ -17,6 +17,7 @@ import org.springblade.business.service.ITaskService;
 import org.springblade.business.vo.ArchiveFileVO;
 import org.springblade.common.utils.FileUtils;
 import org.springblade.manager.entity.ContractInfo;
+import org.springblade.manager.enums.StorageTypeEnum;
 import org.springblade.manager.feign.ContractClient;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
@@ -322,4 +323,13 @@ public class ArchiveFileClientImpl implements ArchiveFileClient {
         iArchiveFileService.updateById(archiveFile);
     }
 
+    @Override
+    public List<ArchiveFile> getListByContractIdAndArchiveFileStorageType(Long contractId, Integer getListByContractIdAndArchiveFileStorageType) {
+        LambdaQueryWrapper<ArchiveFile> wrapper = Wrappers.lambdaQuery();
+        wrapper.eq(ArchiveFile::getContractId, contractId);
+        wrapper.eq(ArchiveFile::getIsDeleted, 0);
+        wrapper.eq(ArchiveFile::getArchiveFileStorageType, getListByContractIdAndArchiveFileStorageType);
+        return this.iArchiveFileService.list(wrapper);
+    }
+
 }

+ 22 - 0
blade-service/blade-business/src/main/java/org/springblade/business/feignClient/ImageClassificationFileClientImpl.java

@@ -0,0 +1,22 @@
+package org.springblade.business.feignClient;
+
+import lombok.AllArgsConstructor;
+import org.springblade.business.dto.ImageClassificationFileDTO;
+import org.springblade.business.feign.ImageClassificationFileClient;
+import org.springblade.business.mapper.ImageClassificationFileMapper;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@AllArgsConstructor
+public class ImageClassificationFileClientImpl implements ImageClassificationFileClient {
+
+    private final ImageClassificationFileMapper imageClassificationFileMapper;
+
+    @Override
+    public List<ImageClassificationFileDTO> getImageClassificationFileListByContractId(Long contractId) {
+        return imageClassificationFileMapper.getImageClassificationFileListByContractId(contractId);
+    }
+
+}

+ 2 - 0
blade-service/blade-business/src/main/java/org/springblade/business/mapper/ArchiveFileMapper.xml

@@ -58,6 +58,8 @@
         <result column="pdf_page_url" property="pdfPageUrl"/>
         <result column="fid" property="fid"/>
         <result column="rectification" property="rectification"/>
+        <result column="m_wbs_tree_contract_p_key_id" property="mWbsTreeContractPKeyId"/>
+        <result column="u_image_classification_file_id" property="uImageClassificationFileId"/>
     </resultMap>
     <update id="recoveryByIds">
         update u_archive_file set is_deleted = 0 where

+ 6 - 0
blade-service/blade-business/src/main/java/org/springblade/business/mapper/ImageClassificationFileMapper.java

@@ -16,6 +16,7 @@
  */
 package org.springblade.business.mapper;
 
+import org.springblade.business.dto.ImageClassificationFileDTO;
 import org.springblade.business.entity.ArchiveFile;
 import org.springblade.business.entity.ImageClassificationFile;
 import org.springblade.business.vo.ImageClassificationFileVO;
@@ -75,4 +76,9 @@ public interface ImageClassificationFileMapper extends BaseMapper<ImageClassific
      */
     public List<ImageClassificationFile> getDeleteDataByIds(@Param("ids") List<String> ids);
 
+    /**
+     * 根据合同段id查询声像文件
+     */
+    List<ImageClassificationFileDTO> getImageClassificationFileListByContractId(@Param("contractId") Long contractId);
+
 }

+ 45 - 0
blade-service/blade-business/src/main/java/org/springblade/business/mapper/ImageClassificationFileMapper.xml

@@ -30,6 +30,9 @@
         <result column="text_content" property="textContent"/>
         <result column="pdf_url" property="pdfUrl"/>
         <result column="marge_pdf_url" property="margePdfUrl"/>
+        <result column="file_width" property="fileWidth"/>
+        <result column="file_height" property="fileHeight"/>
+        <result column="file_page" property="filePage"/>
     </resultMap>
     <update id="recoveryByIds">
         update u_image_classification_file set is_deleted = 0 where
@@ -131,4 +134,46 @@
         </foreach>
     </select>
 
+    <!-- 根据合同段id查询声像文件 -->
+    <select id="getImageClassificationFileListByContractId" resultType="org.springblade.business.dto.ImageClassificationFileDTO">
+        select
+            u_image_classification_file.id,
+            u_image_classification_file.classify_id,
+            u_image_classification_file.project_id,
+            u_image_classification_file.contract_id,
+            u_image_classification_file.wbs_id,
+            u_image_classification_file.title,
+            u_image_classification_file.type,
+            u_image_classification_file.image_url,
+            u_image_classification_file.pdf_url,
+            u_image_classification_file.file_size,
+            u_image_classification_file.file_name,
+            u_image_classification_file.shooting_user,
+            u_image_classification_file.shooting_time,
+            u_image_classification_file.photo_code,
+            u_image_classification_file.film_code,
+            u_image_classification_file.see_also_code,
+            u_image_classification_file.text_content,
+            u_image_classification_file.create_user,
+            u_image_classification_file.create_dept,
+            u_image_classification_file.create_time,
+            u_image_classification_file.update_user,
+            u_image_classification_file.update_time,
+            u_image_classification_file.upload_time,
+            u_image_classification_file.marge_pdf_url,
+            u_image_classification_file.file_width,
+            u_image_classification_file.file_height,
+            u_image_classification_file.file_page,
+            m_image_classification_config.storage_directory_format
+        from
+            u_image_classification_file u_image_classification_file
+        left join
+            m_image_classification_config m_image_classification_config
+        on
+            u_image_classification_file.classify_id = m_image_classification_config.id
+        where
+            u_image_classification_file.contract_id = #{contractId}
+        and u_image_classification_file.is_deleted = 0
+    </select>
+
 </mapper>

+ 16 - 0
blade-service/blade-business/src/main/java/org/springblade/business/mapper/WeatherInfoMapper.java

@@ -1,9 +1,25 @@
 package org.springblade.business.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
 import org.springblade.business.entity.WeatherInfo;
+import org.springblade.manager.entity.ContractInfo;
+import org.springblade.manager.entity.ProjectContractArea;
+import org.springblade.manager.vo.ContractInfoVO;
+
+import java.time.LocalDate;
+import java.util.List;
 
 public interface WeatherInfoMapper extends BaseMapper<WeatherInfo> {
 
 
+    ProjectContractArea selectAreaIdByContractId(@Param("contractId") Long contractId);
+
+    Integer deWeightWeather(@Param("ids") List<Long> ids);
+
+    List<Long> getRepetitionId(@Param("areaId") Long areaId);
+
+    List<LocalDate> getAllWeatherByAreaId(@Param("areaId") Long areaId);
+
+    List<ContractInfoVO> getAllContract();
 }

+ 28 - 0
blade-service/blade-business/src/main/java/org/springblade/business/mapper/WeatherInfoMapper.xml

@@ -20,5 +20,33 @@
         <result column="status" property="status"/>
         <result column="is_deleted" property="isDeleted"/>
     </resultMap>
+    <delete id="deWeightWeather">
+        DELETE FROM u_weather_info
+        WHERE id in
+        <foreach collection="ids" item="id" open="(" close=")" separator=",">
+            #{id}
+        </foreach>
+    </delete>
+
+    <select id="getRepetitionId" resultType="java.lang.Long">
+        select id
+        from u_weather_info WHERE contract_area_id = #{areaId}
+        group by DATE_FORMAT(record_time, '%Y-%m-%d')
+        HAVING COUNT(DATE_FORMAT(record_time, '%Y-%m-%d')) > 1
+    </select>
+    <select id="selectAreaIdByContractId" resultType="org.springblade.manager.entity.ProjectContractArea">
+        select * from m_project_contract_area where contract_id = #{contractId} and is_deleted = 0
+    </select>
+    <select id="getAllWeatherByAreaId" resultType="java.time.LocalDate">
+        select DATE_FORMAT(record_time, '%Y-%m-%d')
+        from u_weather_info WHERE contract_area_id = #{areaId}
+        order by DATE_FORMAT(record_time, '%Y-%m-%d')
+    </select>
+    <select id="getAllContract" resultType="org.springblade.manager.vo.ContractInfoVO">
+        select mpi.project_name as projectName,mci.*
+        from m_project_info mpi left join m_contract_info mci on mpi.id = mci.p_id
+        WHERE mpi.is_deleted = 0 and mci.is_deleted =0
+    </select>
+
 
 </mapper>

+ 138 - 6
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/WeatherInfoServiceImpl.java

@@ -2,12 +2,12 @@ package org.springblade.business.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.hankcs.hanlp.dictionary.py.Pinyin;
 import com.hankcs.hanlp.dictionary.py.PinyinDictionary;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
 import org.jsoup.Connection;
 import org.jsoup.Jsoup;
 import org.jsoup.nodes.Document;
@@ -20,22 +20,23 @@ import org.springblade.business.mapper.WeatherInfoMapper;
 import org.springblade.business.service.WeatherInfoService;
 //import org.springframework.scheduling.annotation.Scheduled;
 import org.springblade.core.tool.utils.Func;
+import org.springblade.manager.entity.ContractInfo;
 import org.springblade.manager.entity.ProjectContractArea;
 import org.springblade.manager.entity.ProjectInfo;
 import org.springblade.manager.feign.ProjectClient;
 import org.springblade.manager.feign.ProjectContractAreaClient;
+import org.springblade.manager.vo.ContractInfoVO;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import org.springblade.core.mp.support.Condition;
 
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.time.Duration;
-import java.time.Instant;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
+import java.time.*;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.stream.Collectors;
 
 @Slf4j
 @Service
@@ -232,6 +233,7 @@ public class WeatherInfoServiceImpl extends ServiceImpl<WeatherInfoMapper, Weath
                             weatherMap = this.getWeather(county.toString(), plainTime.format(DateTimeFormatter.ofPattern("yyyyMM")));
                             if (weatherMap == null || weatherMap.get(plainTime.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"))) == null) {
                                 weatherMap = this.getWeather(city.toString(), plainTime.format(DateTimeFormatter.ofPattern("yyyyMM")));
+                                county = city;
                                 if (weatherMap == null || weatherMap.get(plainTime.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"))) == null) {
                                     log.info("获取历史天气失败!contractAreaId:" + projectContractArea.getId() + ",syncTime:" + plainTime);
                                     break;
@@ -254,6 +256,133 @@ public class WeatherInfoServiceImpl extends ServiceImpl<WeatherInfoMapper, Weath
 
     }
 
+    //去除重复天气,并填充每个合同段缺失的天气
+    @Scheduled(cron = "0 0 9 * * ?")
+    public void scanMissWeather(){
+        //获取所有项目的合同段
+        List<ContractInfoVO> contractInfos = baseMapper.getAllContract();
+        for (ContractInfoVO contract : contractInfos) {
+            //获取合同段的区域信息
+            ProjectContractArea area = baseMapper.selectAreaIdByContractId(contract.getId());
+            if (area == null || StringUtils.isBlank(area.getCounty()) || StringUtils.isBlank(area.getCity())){
+                continue;
+            }
+            List<Pinyin> py = PinyinDictionary.convertToPinyin(area.getCounty().substring(0, area.getCounty().length() - 1));
+            StringBuilder county = new StringBuilder();
+            for (int i = 0; i < py.size(); i++) {
+                county.append(py.get(i).getPinyinWithoutTone());
+            }
+            List<Pinyin> py2 = PinyinDictionary.convertToPinyin(area.getCity().substring(0, area.getCity().length() - 1));
+            StringBuilder city = new StringBuilder();
+            for (int i = 0; i < py2.size(); i++) {
+                city.append(py2.get(i).getPinyinWithoutTone());
+            }
+            //获取当前合同下所有重复的天气id,根据区域id
+            List<Long> ids = new ArrayList<>();
+            ids = baseMapper.getRepetitionId(area.getId());
+            while (ids.size() > 0){
+                //为单个合同段去除重复天气,根据天气id集合
+                System.out.println(contract.getProjectName()+":"+contract.getContractName()+"重复日期:"+ids.size()+"条");
+                Integer total = baseMapper.deWeightWeather(ids);
+                System.out.println(contract.getProjectName()+":"+contract.getContractName()+"删除重复日期:"+total+"条");
+                ids = baseMapper.getRepetitionId(area.getId());
+            }
+
+            //补全缺失的天气
+            List<LocalDate> missDate = new ArrayList<>();
+            //获取当前合同段所有天气,并且按照日期排序
+            List<LocalDate> allDate = baseMapper.getAllWeatherByAreaId(area.getId());
+            //如果没有获取到天气则直接跳过
+            if (allDate.size() == 0){
+                continue;
+            }
+            //判断最小日期是否小于当前日期
+            if (LocalDate.now().compareTo(allDate.get(0)) < 1){
+                continue;
+            }
+            //循环结合,遗漏日期添加进集合
+            for (int i = 0; i < allDate.size()-1; i++) {
+                LocalDate today = allDate.get(i);
+                LocalDate tomorrow = allDate.get(i+1);
+                while (!today.plusDays(1).equals(tomorrow)){
+                    missDate.add(today.plusDays(1));
+                    today = today.plusDays(1);
+                }
+            }
+            //判断是否有遗漏
+            if (missDate.size() == 0){
+                continue;
+            }
+            Map<String, Map<String, String>> weatherMap = null;
+            List<WeatherInfo> list = new ArrayList<>();
+            ZoneId zone = ZoneId.systemDefault();
+            //遗漏日期根据年月分组
+            Map<String, List<LocalDate>> map = missDate.stream().collect(Collectors.groupingBy(l -> l.format(DateTimeFormatter.ofPattern("yyyyMM"))));
+            for (String s : map.keySet()) {
+                //获取整月天气
+                weatherMap = this.getWeather(county.toString(), s);
+                if (weatherMap == null || weatherMap.size() == 0) {
+                    weatherMap = this.getWeather(city.toString(), s);
+                    county = city;
+                    if (weatherMap == null || weatherMap.size() == 0) {
+                        log.info("获取历史天气失败:" + area.getCity()+"-"+area.getCounty() + ",syncTime:" + s);
+                        break;
+                    }
+                }
+                //为遗漏日期设置天气
+                List<LocalDate> dates = map.get(s);
+                for (LocalDate date : dates) {
+                    Map<String, String> day = weatherMap.get(date.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
+                    if (day == null || day.size() == 0){
+                        log.info("从整月天气中获取当天失败:" + area.getCity()+"-"+area.getCounty() + ",syncTime:" + date);
+                    }
+                    //计算平均气温
+                    BigDecimal aver = (new BigDecimal(day.get("high")).add(new BigDecimal(day.get("low")))).divide(new BigDecimal("2"), 1, BigDecimal.ROUND_HALF_UP);
+                    WeatherInfo newWeather = new WeatherInfo(String.valueOf(area.getId()), day.get("weather"), aver.toString(), day.get("high"), day.get("low"), day.get("windLevel"));
+                    newWeather.setRecordTime(Date.from(date.atStartOfDay().atZone(zone).toInstant()));
+                    list.add(newWeather);
+                }
+
+            }
+            //保存进天气表
+            this.saveBatch(list);
+        }
+
+        System.out.println("同步天气完成");
+
+    }
+    //测试循环
+    public void test7(){
+        List<LocalDate> missDate = new ArrayList<>();
+        //获取当前合同段所有天气,并且按照日期排序
+        List<LocalDate> allDate = baseMapper.getAllWeatherByAreaId(1697501022614122498L);
+        //如果没有获取到天气则直接跳过
+        if (allDate.size() == 0){
+            System.out.println("111");
+        }
+        //判断最小日期是否小于当前日期
+        if (LocalDate.now().compareTo(allDate.get(0)) < 1){
+            System.out.println(222);
+        }
+        //循环结合,遗漏日期添加进集合
+        for (int i = 0; i < allDate.size()-1; i++) {
+            LocalDate today = allDate.get(i).plusDays(1);
+            LocalDate tomorrow = allDate.get(i+1);
+            while (!today.equals(tomorrow)){
+                if (today.compareTo(tomorrow) == 1){
+                    System.out.println(today);
+                }
+                missDate.add(today);
+                today = today.plusDays(1);
+            }
+        }
+        //判断是否有遗漏
+        if (missDate.size() == 0){
+            System.out.println(88888);
+        }
+        System.out.println("9999");
+    }
+
     public Map<String, Map<String, String>> getWeather(String city, String month) {
         String html = "http://www.tianqihoubao.com/lishi/" + city + "/month/" + month + ".html";
         Map<String, Map<String, String>> map = new HashMap<>();
@@ -261,7 +390,8 @@ public class WeatherInfoServiceImpl extends ServiceImpl<WeatherInfoMapper, Weath
         try {
             System.out.println(html);
             Connection connect = Jsoup.connect(html);
-            connect.timeout(20000);
+            connect.timeout(10000);
+            connect.userAgent("Chrome");
             document = connect.get();
             Element body = document.body();
             Elements table = body.getElementsByTag("table");
@@ -285,6 +415,8 @@ public class WeatherInfoServiceImpl extends ServiceImpl<WeatherInfoMapper, Weath
             }
         } catch (IOException e) {
             e.printStackTrace();
+        } finally {
+
         }
         return map;
     }

+ 7 - 4
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/feign/EVisaClientImpl.java

@@ -4,11 +4,9 @@ import cfca.paperless.dto.bean.CertBean;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import lombok.AllArgsConstructor;
+import org.springblade.core.tool.api.R;
 import org.springblade.evisa.service.EVisaService;
-import org.springblade.evisa.vo.CertBeanVO;
-import org.springblade.evisa.vo.EVisaMakeSealVO;
-import org.springblade.evisa.vo.EVisaTaskApprovalVO;
-import org.springblade.evisa.vo.TaskArchiveDTO;
+import org.springblade.evisa.vo.*;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
@@ -32,6 +30,11 @@ public class EVisaClientImpl implements EVisaClient {
         return null;
     }
 
+    @Override
+    public R<String> batchEVisa(SigInfoVO vo) {
+        return eVisaService.batchEVisa(vo);
+    }
+
     @Override
     public String certification(String pdfUrl, String fileName, String contractId) {
         return this.eVisaService.certification(pdfUrl, fileName, contractId);

+ 7 - 0
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/service/EVisaService.java

@@ -1,8 +1,10 @@
 package org.springblade.evisa.service;
 
 import cfca.paperless.dto.bean.CertBean;
+import org.springblade.core.tool.api.R;
 import org.springblade.evisa.vo.EVisaMakeSealVO;
 import org.springblade.evisa.vo.EVisaTaskApprovalVO;
+import org.springblade.evisa.vo.SigInfoVO;
 
 import java.util.List;
 
@@ -38,4 +40,9 @@ public interface EVisaService {
      */
     List<CertBean> onlineCheckSeal(String pdfUrl);
 
+    /**
+     * 单张PDF批量电签,返回电签后的PDF
+     */
+    R<String> batchEVisa(SigInfoVO vo);
+
 }

+ 172 - 2
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/service/impl/EVisaServiceImpl.java

@@ -8,6 +8,7 @@ import cfca.paperless.base.BaseConstants;
 import cfca.paperless.base.util.Base64;
 import cfca.paperless.base.util.GUIDUtil;
 import cfca.paperless.base.util.ImageUtil;
+import cfca.paperless.base.util.IoUtil;
 import cfca.paperless.base.util.PwdEncryptUtil;
 import cfca.paperless.client.PaperlessClient;
 import cfca.paperless.dto.RequestHead;
@@ -16,15 +17,19 @@ import cfca.paperless.dto.ResponseHead;
 import cfca.paperless.dto.bean.*;
 import cfca.paperless.dto.request.requestbody.tx20.MakeSealRequestBody;
 import cfca.paperless.dto.request.requestbody.tx40.CompoundSealPdfListRequestBody;
+import cfca.paperless.dto.request.requestbody.tx40.SealPdfRequestBody;
 import cfca.paperless.dto.request.requestbody.tx40.VerifyPdfSealRequestBody;
 import cfca.paperless.dto.request.tx20.MakeSealRequest;
 import cfca.paperless.dto.request.tx40.CompoundSealPdfListDetachedRequest;
+import cfca.paperless.dto.request.tx40.SealPdfRequest;
 import cfca.paperless.dto.request.tx40.VerifyPdfSealRequest;
 import cfca.paperless.dto.response.responsebody.tx20.MakeSealResponseBody;
 import cfca.paperless.dto.response.responsebody.tx40.CompoundSealPdfListDetachedResponseBody;
+import cfca.paperless.dto.response.responsebody.tx40.SealPdfResponseBody;
 import cfca.paperless.dto.response.responsebody.tx40.VerifyPdfSealResponseBody;
 import cfca.paperless.dto.response.tx20.MakeSealResponse;
 import cfca.paperless.dto.response.tx40.CompoundSealPdfListDetachedResponse;
+import cfca.paperless.dto.response.tx40.SealPdfResponse;
 import cfca.paperless.dto.response.tx40.VerifyPdfSealResponse;
 import cn.hutool.core.io.file.FileReader;
 import com.alibaba.fastjson.JSONObject;
@@ -42,9 +47,11 @@ import org.springblade.common.constant.CommonConstant;
 import org.springblade.common.constant.EVisaConstant;
 import org.springblade.common.utils.CommonUtil;
 import org.springblade.common.utils.SnowFlakeUtil;
+import org.springblade.core.log.exception.ServiceException;
 import org.springblade.core.oss.model.BladeFile;
 import org.springblade.core.secure.utils.AuthUtil;
 import org.springblade.core.secure.utils.SecureUtil;
+import org.springblade.core.tool.api.R;
 import org.springblade.core.tool.utils.*;
 import org.springblade.evisa.redissionUtil.DistributedRedisLock;
 import org.springblade.evisa.service.EVisaService;
@@ -84,8 +91,8 @@ import java.util.*;
 @AllArgsConstructor
 public class EVisaServiceImpl implements EVisaService {
 
-    private static final String SIGN_HOST = "172.30.224.79";
-    //private static final String SIGN_HOST = "47.115.117.246";
+//    private static final String SIGN_HOST = "172.30.224.79";
+    private static final String SIGN_HOST = "47.115.117.246";
 
     private static final String SIGN_PORT = "8183";
 
@@ -1363,4 +1370,167 @@ public class EVisaServiceImpl implements EVisaService {
             return null;
         }
     }
+
+    /**
+     *      单张PDF批量电签,返回电签后的PDF
+     * @param vo
+     * @return
+     */
+    @Override
+    public R<String> batchEVisa(SigInfoVO vo) {
+        try {
+            List<SigInfoVO.SigInfo> list = vo.getList();
+            String pdfUrl = vo.getPdfUrl();
+
+            for (SigInfoVO.SigInfo sigInfo : list) {
+                pdfUrl = myDoWork(sigInfo, pdfUrl);
+            }
+            return R.data(pdfUrl);
+        }catch (Exception e){
+            return R.fail(e.getMessage());
+        }
+    }
+    public String myDoWork(SigInfoVO.SigInfo sig,String pdfUrl) throws Exception {
+
+        PaperlessClient paperlessClient = new PaperlessClient(SIGN_HOST, SIGN_PORT, 300000, 1800000);
+
+        paperlessClient.setSSL(false);
+        /*******************************************************************************/
+        SealPdfRequest sealPdfRequest = new SealPdfRequest();
+        RequestHead requestHead = new RequestHead();
+        String transactionNo = GUIDUtil.generateId();
+        //机构编码非空
+        String organizationCode = EVisaConstant.organizationCode;
+        //操作员编码 可为空
+        String operatorCode = "";
+        //渠道编码 可为空
+        String channelCode = "";
+        //设置属性
+        requestHead.setBasicInfo(transactionNo, organizationCode,operatorCode,channelCode);
+
+        sealPdfRequest.setHead(requestHead);
+        /*******************************************************************************/
+        SealPdfRequestBody requestBody = new SealPdfRequestBody();
+        //待签章文件
+        byte[] pdfData = CommonUtil.InputStreamToBytes(CommonUtil.getOSSInputStream(pdfUrl));
+        // 数据源类型 1 pdf文件路径 2 pdf文件字节流
+        String inputType = BaseConstants.INPUT_TYPE_FILEDATA;
+        //设置数据源,数据源类型为2时,为空,数据源类型为1时,上送数据源路径(服务器可以访问到的文件路径)
+        requestBody.setInputSource("");
+        //数据源类型
+        requestBody.setInputType(inputType);
+        //数据源为2时,需上送 pdf文件数据
+        requestBody.setPdfData(pdfData);
+        //签章后文件保存地址,不为空时,直接将签章文件保存在此地址,不再返回签章后文档数据
+        requestBody.setOutputFilepath("");
+        //时间戳方式,默认为0;0:实时访问CFCA 时间戳服务;1:使用从CFCA购置并在本地部署的时间戳服务器产品;
+        requestBody.setTimestampChannel(BaseConstants.TIME_STAMP_CHANNEL_CFCA);
+
+        /************************ ②-2.构造机构章策略 *********************************************/
+        SealStrategy sealStrategy = myGenerateSealStrategy(sig);
+
+        requestBody.setSealStrategy(sealStrategy);
+
+        sealPdfRequest.setBody(requestBody);
+
+        /************************请求签章 ********************************************/
+        ResponseDto responseDto = paperlessClient.execute(sealPdfRequest);
+
+        /************************解析响应结果 *********************************************/
+
+        SealPdfResponse sealPdfResponse = (SealPdfResponse)responseDto;
+
+        ResponseHead responseHead = sealPdfResponse.getHead();
+        SealPdfResponseBody responseBody = sealPdfResponse.getBody();
+        String resultOutputFilepath = "";
+        if(ClientConstants.CODE_SUCCESS.equals(responseHead.getCode())){
+
+            resultOutputFilepath = responseBody.getOutputFilepath();
+
+            byte[] pdf = responseBody.getPdf();
+            System.out.println(pdf == null ? 0 : pdf.length);
+
+            String resultPdfFileDataHash = responseBody.getPdfFileDataHash();
+            System.out.println("resultPdfFileDataHash=" + resultPdfFileDataHash);
+
+            if(cfca.paperless.base.util.StringUtil.isNotEmpty(resultOutputFilepath)) {
+                System.out.println("文件已输出到:"+ resultOutputFilepath);
+            } else {
+                Random random = new Random();
+                int i = random.nextInt(100000);
+                String pdfFile = "/www/wwwroot/Users/hongchuangyanfa/Desktop/archiveCheck/" + i +".pdf";
+                resultOutputFilepath = pdfFile;
+                IoUtil.write(resultOutputFilepath, pdf);
+                System.out.println("文件已输出到:"+ resultOutputFilepath );
+                BladeFile bladeFile = newIOSSClient.uploadFile("aa.pdf",pdfFile);
+                if (bladeFile == null || StringUtils.isBlank(bladeFile.getLink())){
+                    throw new ServiceException("上传阿里云失败");
+                }
+                resultOutputFilepath = bladeFile.getLink();
+            }
+
+        } else {
+            throw new ServiceException(" NG,Code:"+responseHead.getCode()+",Message:"+responseHead.getMessage());
+        }
+
+        return resultOutputFilepath;
+    }
+    public SealStrategy  myGenerateSealStrategy(SigInfoVO.SigInfo sig) throws Exception{
+
+        String type = BaseConstants.SEAL_TYPE_IMAGE;
+        String sealCode = sig.getSealCode();
+        String sealPassword = PwdEncryptUtil.encrypto(sig.getSealPassword());
+        SealStrategy  sealStrategy = new SealStrategy();
+        sealStrategy.setSealInfo(type, sealCode, sealPassword);
+
+        String pngFilePath = sig.getSigPic();
+        //外传图片
+        byte[] imageData = CommonUtil.InputStreamToBytes(CommonUtil.getOSSInputStream(pngFilePath));
+        //图片进行Base64编码/
+        String imageDataString =  new String(Base64.encode(imageData), StandardCharsets.UTF_8);
+        sealStrategy.setSealImage(imageDataString);
+        //算法,非空
+        String hashAlg = BaseConstants.HASHALG_SM3;
+        sealStrategy.setHashAlg(hashAlg);
+        //透明度,0-1.0f,默认1.0f,不透明
+        String fillOpacity = "1";
+        sealStrategy.setFillOpacity(fillOpacity);
+        //是否显示,默认1 显示
+        String visible = "1";
+        sealStrategy.setVisible(visible);
+        //签章图片显示尺寸  单位毫米  默认为图片本身尺寸
+        //签章图片显示尺寸,单位毫米;如果是圆形,则为半径实际尺寸;如果是矩形,则为较长的一边的实际尺寸;如果是不规则图形,则为较长的一边的实际尺寸
+        String displaySize = "50";
+        sealStrategy.setDisplaySize(displaySize);
+        // 业务码 businessCode ,businessCode为空,则不添加业务码,业务码相关属性都不需要设置
+        //业务码内容
+        String businessCode = "";
+        // 业务码字体大小,默认12,可以为空
+        String businessFontSize = "20";
+        //以图片底部为起点,向上占图片百分之多少的位置开始显示业务码,默认0.5
+        String textRectHeightPercent = "0.5";
+        //颜色值,默认黑色 000000
+        String businessColor = "FF0000";
+        //字体,默认宋体,可以为空
+        String bussinessFamily = "";
+        // 业务码  end
+        sealStrategy.setBusinessInfo(businessCode, businessFontSize, textRectHeightPercent, businessColor, bussinessFamily);
+        // 签章杂项信息
+        //签章人姓名,必填
+        String sealPerson = sig.getUserName();
+        //签章地点,可以为空
+        String sealLocation = "";
+        //签章原因,可以为空
+        String sealReason = "";
+        sealStrategy.setSealMiscInfo(sealPerson, sealLocation, sealReason);
+        /************************关键字签章****************************************************/
+        //关键字
+        String keyword =sig.getUserSig();
+        //x轴偏移,默认0
+        String offSetX = "0";
+        //y轴偏移,默认0
+        String offSetY = "0";
+        sealStrategy.setSignWithKeywordInfo(keyword, offSetX, offSetY);
+        return sealStrategy;
+    }
 }

+ 17 - 3
blade-service/blade-manager/src/main/java/com/mixsmart/utils/FormulaUtils.java

@@ -400,7 +400,7 @@ public class FormulaUtils {
 
 
     /*根据数据模型对象,生成数据集合按字段获取数据的函数*/
-    public static <T> Map<String, Function<List<T>, List<Object>>> fieldDataFcMap(Class<T> clazz) {
+    public static <T> Map<String, Function<List<T>, List<Object>>> fieldDataFcMap(Class<?> clazz) {
         Map<String, Function<T, Object>> fieldMap = functionMapBuilder(clazz);
         Map<String, Function<List<T>, List<Object>>> functionMap = new HashMap<>();
         for (Map.Entry<String, Function<T, Object>> functionEntry : fieldMap.entrySet()) {
@@ -412,7 +412,7 @@ public class FormulaUtils {
 
 
     /*数据模型按字段生成内容获取函数*/
-    public static <T> Map<String,Function<T,Object>> functionMapBuilder(Class<T> clazz){
+    public static <T> Map<String,Function<T,Object>> functionMapBuilder(Class<?> clazz){
         Map<String, Function<T, Object>> functionMap = new HashMap<>();
         for (Field field : clazz.getDeclaredFields()) {
             JSONField jf = field.getAnnotation(JSONField.class);
@@ -426,7 +426,7 @@ public class FormulaUtils {
         return functionMap;
     }
 
-    private static <T> Function<T, Object> getFunction(Class<T> clazz, String fieldName) {
+    private static <T> Function<T, Object> getFunction(Class<?> clazz, String fieldName) {
         return certificate -> {
             try {
                 Field field = clazz.getDeclaredField(fieldName);
@@ -473,6 +473,20 @@ public class FormulaUtils {
           }
     }
 
+    /*回溯标记*/
+    public static <T> void treeNodeChecked( TreeNode<T> tmp){
+        int loop = 10;
+        do{
+            if(tmp.isChecked()) {
+                /*已经检出则停止循环*/
+                loop=0;
+            }else{
+                tmp.setChecked(true);
+                tmp=tmp.getParent();
+            }
+        }while (tmp!=null&&loop-->0);
+    }
+
     /*获取层级链*/
     public static <T> List<T> treeNodeChains( TreeNode<T> tmp){
         List<T> result = new ArrayList<>();

+ 16 - 2
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ArchiveTreeContractController.java

@@ -506,6 +506,20 @@ public class ArchiveTreeContractController extends BladeController {
         return R.data(archiveTreeContractService.startInspect(projectId,type));
     }
 
-
-
+    @PostMapping("syncSoundImageData")
+    @ApiOperation(value = "同步合同段声像文件", notes = "传入当前节点id")
+    @ApiImplicitParams(value = {
+            @ApiImplicitParam(name = "projectId", value = "项目id", required = true, dataType = "Long"),
+            @ApiImplicitParam(name = "contractId", value = "合同段id", required = true, dataType = "Long"),
+            @ApiImplicitParam(name = "nodeId", value = "当前节点id", required = true, dataType = "Long"),
+    })
+    public R syncSoundImageData(Long projectId, Long contractId, Long nodeId) {
+        R returnObject = R.success("同步合同段声像文件完成" );
+        try {
+            archiveTreeContractSync.syncSoundImageData(projectId,contractId, nodeId);
+        }catch (Exception e) {
+            returnObject = R.fail(e.getMessage());
+        }
+        return returnObject;
+    }
 }

+ 5 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java

@@ -1901,6 +1901,7 @@ public class ExcelTabController extends BladeController {
         List<TableInfo> tableInfoList = this.excelTabService.getTableInfoList(dataArray);
         long start = System.currentTimeMillis();
         if (tableInfoList != null) {
+            tableInfoList.forEach(e->{e.setToBeUpdated(true);});
             tableAll = wbsTreeContractService.searchNodeAllTable(nodeId, "1", contractId, projectId, null);
             doForTableIst(tableAll,tableInfoList);
           /*  List<Long> tableAllIds = tableAll.stream().map(AppWbsTreeContractVO::getPKeyId).collect(Collectors.toList());
@@ -1935,6 +1936,10 @@ public class ExcelTabController extends BladeController {
         System.out.println("加载所有表单数据耗时:"+(System.currentTimeMillis()-start));
         //公式填充
         this.excelTabService.formulaFillData(tableInfoList, Long.parseLong(nodeId), ExecuteType.INSPECTION);
+        assert tableInfoList != null;
+        if(tableInfoList.size()==0){
+            R.success("数据未发生变化");
+        }
         //保存数据到数据库
         R<Object> result = this.excelTabService.saveOrUpdateInfo(tableInfoList);
         if (!result.isSuccess()) {

+ 14 - 80
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/FormulaController.java

@@ -38,10 +38,12 @@ import org.springblade.manager.vo.CurrentNode;
 import org.springblade.manager.wrapper.FormulaWrapper;
 import org.springframework.beans.BeanUtils;
 import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.http.HttpRequest;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.web.bind.annotation.*;
 import springfox.documentation.annotations.ApiIgnore;
 
+import javax.servlet.http.HttpServletRequest;
 import javax.validation.constraints.NotNull;
 import java.io.FileNotFoundException;
 import java.net.UnknownHostException;
@@ -570,83 +572,7 @@ public class FormulaController {
         return R.data(map);
     }
 
-  /*  public R<Object> chartInit(@RequestBody ChartOption option){
-        *//*1.获取公式配置,如果存在子公式,则一直查找到最终公式*//*
-        *//*2.查找所有依赖元素的所有数据,按表单排序*//*
-        *//*3.计算结果*//*
-        *//*4.查找当前表对应返回配置模版结合计算结果生成option*//*
-        Long pkeyId=option.getPkeyId();
-        String id=option.getId();
-        WbsTreePrivate wtp = this.wbsTreePrivateService.getOne(Wrappers.<WbsTreePrivate>lambdaQuery().eq(WbsTreePrivate::getPKeyId,pkeyId));
-        if(wtp!=null&&Func.isNotEmpty(id)){
-            *//*获取配置*//*
-            List<Map<String,Object>> listMap= this.jdbcTemplate.queryForList("select content from m_formula_panel  a  join m_wbs_tree_private b on a.id=b.init_table_id where b.p_key_id="+pkeyId);
-            if(Func.isNotEmpty(listMap)){
-                JSONObject job = JSON.parseObject(listMap.get(0).get("content").toString());
-                *//*获取关联数据*//*
-                List<Map<String,Object>>  configList= this.jdbcTemplate.queryForList("select d.rely ,b.e_key ekey from m_wbs_tree_private a join  m_wbs_form_element b on a.init_table_id=b.f_id join m_element_formula_mapping c on b.id=c.element_id join m_formula d on c.formula_id=d.id  where p_key_id="+wtp.getPKeyId()+" and b.is_deleted=0 and  d.formula like 'FC.chart%' limit 1");
-                if(Func.isNotEmpty(configList)){
-                    Map<String,Object> config =configList.get(0);
-                    String[] rely = Func.toStr(config.get("rely")).split(",");
-                    String ky=rely[0].split(":")[1];
-                    String kx=rely[1].split(":")[1];
-                    Map<String,Map<String,Object>> formMap = option.getData();
-                    List<Map<String,Object>> dataMap;
-                    if(formMap!=null&&formMap.size()>0){
-                        dataMap=new ArrayList<>();
-                        Map<String,Object> md= new HashMap<>();
-                        dataMap.add(md);
-                        StringBuilder sy= new StringBuilder();
-                        StringBuilder sx = new StringBuilder();
-                        formMap.forEach((o,t)->{
-                            t.forEach((k,v)->{
-                                String[] ka=k.split("__");
-                                if(ky.equals(ka[0])){
-                                    sy.append(v).append("_^_").append(ka[1]).append("☆");
-                                }else if(kx.equals(ka[0])){
-                                    sx.append(v).append("_^_").append(ka[1]).append("☆");
-                                }
-                            });
-                        });
-                        if(sy.length()>1){
-                            sy.deleteCharAt(sy.length()-1);
-                        }
-                        if(sx.length()>1){
-                            sx.deleteCharAt(sx.length()-1);
-                        }
-                        md.put(ky,sy.toString());
-                        md.put(kx,sx.toString());
-                    }else{
-                        dataMap= this.jdbcTemplate.queryForList("select "+ky+","+kx+" from "+wtp.getInitTableName() +" where group_id="+id+" and p_key_id ="+pkeyId);
-                    }
-                    if(Func.isNotEmpty(dataMap)){
-                        Map<String,Object> data = dataMap.get(0);
-                        String dy= Func.toStr(data.get(ky));
-                        String dx=Func.toStr(data.get(kx));
-                        if(dy.length()>0&&dx.length()>0){
-                            Map<String,String> coordsMap = FormulaUtils.getElementCell(wtp.getHtmlUrl());
-                            List<ElementData> ly = FormulaUtils.getElementDataList(coordsMap.get(ky),dy);
-                            List<ElementData> lx = FormulaUtils.getElementDataList(coordsMap.get(kx),dx);
-                            JSONArray jsonArray =new JSONArray();
-                            for(int i=0;i< Math.min(ly.size(),lx.size());i++){
-                                ElementData edy= ly.get(i);
-                                ElementData edx = lx.get(i);
-                                if(StringUtils.isNotEmpty(edy.stringValue(),edx.stringValue())){
-                                    jsonArray.add(new JSONArray().fluentAdd(edx.numberValue()).fluentAdd(edy.numberValue()));;
-                                }
-                            }
-                            if(!jsonArray.isEmpty()){
-                                JSONObject chartData= job.getJSONArray("series").getJSONObject(0);
-                                chartData.put("data",jsonArray);
-                            }
-                        }
-                    }
-                    return R.data(job);
-                }
-            }
-        }
-        return R.data("{}");
-    }*/
+
     @PostMapping("/chart-init")
     @ApiOperationSupport(order = 11)
     public R<Object> chartInit(@RequestBody ChartOption option){
@@ -862,6 +788,7 @@ public class FormulaController {
         return R.fail("失败");
     }
 
+    /*按项目向m_wbs_tree_contract刷入is_type_private_pid字段*/
     @GetMapping("/ref")
     public R<Object> ref(Long projectId) {
         if(!StringUtils.isNumber(projectId)){
@@ -938,12 +865,19 @@ public class FormulaController {
          return this.service.evaluate(tablePkeyId);
     }
 
+    /*根据p_key_id 获取各个元素的执行公式*/
+    @GetMapping("/formulaForNode")
+    public R<Object> formulaForNode(@NotNull Long pkeyId,String tableName,String elementName){
+        return R.data(this.service.formulasForNode(pkeyId,tableName, elementName));
+    }
+
     @GetMapping("/test")
-    public R<Object> meter(Long contractId,Long periodId ,Integer type){
-        /* public  List<ReportResult> execute3(Long contractId,Long periodId ,Integer type)*/
-        return R.data(this.service.execute3(contractId,periodId,type));
+    public R<Object> meter(Long contractId,Long reportId ,Integer type){
+        /*合同段id,报表Id,报表类型中间计量0,材料1,开工2*/
+        return R.data(this.service.execute3(contractId,reportId,type));
     }
 
 
 
+
 }

+ 23 - 1
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/TrialSummaryClassificationConfigurationController.java

@@ -9,9 +9,11 @@ import org.springblade.core.boot.ctrl.BladeController;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
 import org.springblade.core.tool.api.R;
+import org.springblade.manager.dto.TrialSummaryClassificationConfigurationMatchDTO;
+import org.springblade.manager.dto.TrialSummaryClassificationConfigurationRelevancyDTO;
 import org.springblade.manager.entity.TrialSummaryClassificationConfiguration;
 import org.springblade.manager.service.ITrialSummaryClassificationConfigurationService;
-import org.springblade.manager.vo.ContractInfoVO;
+import org.springblade.manager.vo.TrialTreeVO;
 import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
@@ -54,5 +56,25 @@ public class TrialSummaryClassificationConfigurationController extends BladeCont
         return R.data(pages);
     }
 
+    @ApiOperationSupport(order = 5)
+    @ApiOperation(value = "匹配划分", notes = "传入TrialSummaryClassificationConfigurationMatchDTO")
+    @RequestMapping(value = "/matching", method = RequestMethod.POST)
+    public R<Object> matching(@RequestBody TrialSummaryClassificationConfigurationMatchDTO dto) {
+        return R.data(iTrialSummaryClassificationConfigurationService.matching(dto));
+    }
+
+    @ApiOperationSupport(order = 6)
+    @ApiOperation(value = "关联清表", notes = "传入TrialSummaryClassificationConfigurationRelevancyDTO")
+    @RequestMapping(value = "/relevancy", method = RequestMethod.POST)
+    public R<Object> relevancy(@RequestBody TrialSummaryClassificationConfigurationRelevancyDTO dto) {
+        return R.data(iTrialSummaryClassificationConfigurationService.relevancy(dto));
+    }
+
+    @ApiOperationSupport(order = 7)
+    @ApiOperation(value = "试验树全加载", notes = "传入项目id、classId=page接口数据id、type=1匹配划分 =2关联清表")
+    @RequestMapping(value = "/tree", method = RequestMethod.GET)
+    public R<TrialTreeVO> tree(@RequestParam String projectId, @RequestParam String classId, @RequestParam String type) {
+        return R.data(iTrialSummaryClassificationConfigurationService.tree(projectId, classId, type));
+    }
 
 }

+ 3 - 3
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/WbsParamController.java

@@ -487,10 +487,10 @@ public class WbsParamController {
 
     /**按照项目id刷新无效文件题名*/
     @GetMapping("/file-title")
-    public R<Object> ref(Long projectId) {
+    public R<Object> ref(Long projectId,String info) {
         if (StringUtils.isNumber(projectId)) {
-            String sql ="select a.id, a.wbs_id pkeyId  from u_information_query a  join m_wbs_tree_contract b on a.wbs_id=b.p_Key_id where a.name='还未配置提名规则' and a.project_id=?";
-            List<Map<String,Object>> listMaps=this.jdbcTemplate.queryForList(sql,projectId);
+            String sql ="select a.id, b.p_key_id pkeyId  from u_information_query a  join m_wbs_tree_contract b on a.wbs_id=b.p_Key_id where a.name=? and a.project_id=?";
+            List<Map<String,Object>> listMaps=this.jdbcTemplate.queryForList(sql,info,projectId);
             if(listMaps.size()>0){
                 List<Long> pkeyIds= listMaps.stream().map(m->Long.parseLong(m.get("pkeyId").toString())).distinct().collect(Collectors.toList());
                 Map<Long,String> map = new HashMap<>(pkeyIds.size()*2);

+ 5 - 1
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/WbsTreeController.java

@@ -298,17 +298,21 @@ public class WbsTreeController extends BladeController {
          set.add(InterimPaymentSummary.ID);
          set.add(SubprojectInterimPaymentSummary.ID);
          set.add(SubInterimMeterPaySummary.ID);
+         set.add(InterimMeterPaySummary.ID);
+         set.add(InterimMeter.ID);
          return set.contains(id);
      };
 
    public final static Map<String,List<WbsFormElementVO>> MODEL_MAP = new HashMap<>();
-    {
+    static {
         MODEL_MAP.put(BaseInfo.ID,FormulaUtils.toElementVos(BaseInfo.class));
         MODEL_MAP.put(MeterPeriodInfo.ID,FormulaUtils.toElementVos(MeterPeriodInfo.class));
         MODEL_MAP.put(InterimPaymentCertificate.ID,FormulaUtils.toElementVos(InterimPaymentCertificate.class));
         MODEL_MAP.put(InterimPaymentSummary.ID,FormulaUtils.toElementVos(InterimPaymentSummary.class));
         MODEL_MAP.put(SubprojectInterimPaymentSummary.ID,FormulaUtils.toElementVos(SubprojectInterimPaymentSummary.class));
         MODEL_MAP.put(SubInterimMeterPaySummary.ID,FormulaUtils.toElementVos(SubInterimMeterPaySummary.class));
+        MODEL_MAP.put(InterimMeterPaySummary.ID,FormulaUtils.toElementVos(InterimMeterPaySummary.class));
+        MODEL_MAP.put(InterimMeter.ID,FormulaUtils.toElementVos(InterimMeter.class));
     }
     public List<WbsFormElementVO> dataModel(String id){
           return MODEL_MAP.getOrDefault(id,Collections.emptyList());

+ 1 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/NodeTable.java

@@ -12,6 +12,7 @@ public class NodeTable {
     private Long pKeyId;
     private String nodeName;
     private String initTableName;
+    private Long excelId;
     private String wbsId;
     private Integer tableType;
     private Long parentId;

+ 2 - 1
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/impl/ExecutorInit.java

@@ -39,7 +39,8 @@ public class ExecutorInit extends FormulaExecutor {
         tec.formDataMap.putAll(FormulaUtils.toFormDataMap(baseInfo));
         if(MeterType.MATERIAL.equals(tec.getMeterType())||MeterType.START.equals(tec.getMeterType())){
             /*加载计量期信息*/
-            MeterPeriodInfo meterPeriod=meterPeriodFc.apply(tec.getPeriodId());
+            MeterPeriodInfo meterPeriod=meterPeriodFc.apply(tec.getReportId());
+            tec.setPeriodId(meterPeriod.getId());
             tec.formDataMap.putAll(FormulaUtils.toFormDataMap(meterPeriod));
             tec.getConstantMap().put(MeterPeriodInfo.TBN,meterPeriod);
         }else if(MeterType.INTERIM.equals(tec.getMeterType())){

+ 197 - 31
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/impl/ExecutorSpecial.java

@@ -36,6 +36,7 @@ public class ExecutorSpecial extends FormulaExecutor {
     private Function<Long, MeterPeriodInfo> interimMeterPeriodFc;
     private Function<Long, List<Payment>> paymentListFc;
     private Function<Long, List<InventoryForm>> inventoryFormFc;
+    private Function<Long, List<MeterApply>> meterApplyFc;
     public ExecutorSpecial(TableElementConverter tec) {
         super(tec);
     }
@@ -73,7 +74,24 @@ public class ExecutorSpecial extends FormulaExecutor {
         return fn;
     }
 
-   public Function<String,String> preFixFc= this::getPrefix;
+    public Function<String,String> preFixFc= this::getPrefix;
+
+    public <T> LinkedHashMap<String,FormData>   builderFormDatas(T bean){
+        LinkedHashMap<String,FormData> fdm = FormulaUtils.toFormDataMap(bean);
+        Map<String,Function<List<T>,List<Object>>> functionMap =FormulaUtils.fieldDataFcMap(bean.getClass());
+        tec.getFormDataMap().putAll(fdm);
+        return fdm;
+    }
+
+    public Integer getLineSize(List<String> codeFind){
+        int line=10;
+        Optional<FormData> formDataOp=  tec.getFormDataList().stream().filter(e-> BaseUtils.inChain(codeFind,e.getCode())).findAny();
+        if(formDataOp.isPresent()){
+            FormData target = formDataOp.get();
+           line= target.getCoordsList().size();
+        }
+        return line;
+    }
 
     @Override
     public void handle() {
@@ -325,8 +343,7 @@ public class ExecutorSpecial extends FormulaExecutor {
         }
 
         /*合计字段*/
-        BiFunction<List<InterimPaymentSummary>,Function<InterimPaymentSummary,String>,String>
-                bfc= (list,fc) -> StringUtils.number2String(list.stream().map(fc).mapToDouble(Double::parseDouble).sum(),1);
+        BiFunction<List<InterimPaymentSummary>,Function<InterimPaymentSummary,String>,String> bfc= (list,fc) -> StringUtils.number2String(list.stream().map(fc).mapToDouble(Double::parseDouble).sum(),1);
 
 
         @Override
@@ -359,9 +376,6 @@ public class ExecutorSpecial extends FormulaExecutor {
         }
     }
 
-
-
-
     @Data
     public  class SubIPaySum implements Special{
          private Integer capacity=10;
@@ -439,6 +453,8 @@ public class ExecutorSpecial extends FormulaExecutor {
         @Override
         public void parse() {
             LinkedHashMap<String,FormData> fdm = FormulaUtils.toFormDataMap(new SubprojectInterimPaymentSummary());
+            /*获取实际输出行数*/
+            this.capacity=getLineSize(new ArrayList<>(fdm.keySet()));
             Map<String,Function<List<SubprojectInterimPaymentSummary>,List<Object>>> functionMap =FormulaUtils.fieldDataFcMap(SubprojectInterimPaymentSummary.class);
             tec.getFormDataMap().putAll(fdm);
             Map<String,Summary> currentSummary = toSummary(current);
@@ -464,14 +480,7 @@ public class ExecutorSpecial extends FormulaExecutor {
                 }
                 dataList.add(sis);
             }
-            List<String> codeFind = new ArrayList<>(fdm.keySet());
-            /*获取实际输出行数*/
-            Optional<FormData> formDataOp=  tec.getFormDataList().stream().filter(e-> BaseUtils.inChain(codeFind,e.getCode())).findAny();
             List<SubprojectInterimPaymentSummary> outPutList= new ArrayList<>();
-            if(formDataOp.isPresent()){
-                  FormData target = formDataOp.get();
-                  capacity=target.getCoordsList().size();
-            }
             LinkedHashMap<String,List<SubprojectInterimPaymentSummary>> chapterGroup= dataList.stream().collect(Collectors.groupingBy(e->getPrefix(e.getFormNumber()),LinkedHashMap::new,Collectors.toList()));
             AtomicInteger loop = new AtomicInteger(chapterGroup.size());
             chapterGroup.forEach((k,v)->{
@@ -529,49 +538,75 @@ public class ExecutorSpecial extends FormulaExecutor {
 
     @Data
     public class SubIMeterPay implements Special{
+        private Integer capacity=10;
 
         @Override
         public boolean ready() {
             return current.size()>0;
         }
 
+        public BiFunction<Payment,Payment,SubInterimMeterPaySummary> payment2Smps=(payment,prePayment)->{
+            SubInterimMeterPaySummary smps= new SubInterimMeterPaySummary();
+            smps.setItemName(payment.getName());
+            smps.setFormNumber(payment.getNumber());
+            smps.setUnit(payment.getUnit());
+            smps.setRevisedTotal(StringUtils.handleNull(payment.getChangeTotal()));
+            smps.setCurrentPeriodCompleted(StringUtils.handleNull(payment.getCompleted()));
+            smps.setCompleted(StringUtils.handleNull(payment.getCompleted()+prePayment.getCompleted()));
+            smps.setRevisedAmount(payment.getChangeMoney());
+            smps.setCurrentPeriodPay(payment.getMoney());
+            smps.setCurrentPeriodEndPay(StringUtils.handleNull(new BigDecimal(payment.getMoney()).add(new BigDecimal(prePayment.getMoney()))));
+            return smps;
+        };
+
         @Override
         public void parse() {
             /*按照计量单元和单元内的清单顺序排序显示计量清单,并且把计量单元的层级一并显示*/
             /*根据每一期的s_middle_meter_apply,s_inventory_form_apply 获取对应的计量清单,然后根据清单Id配合s_inventory_form_meter查找计量单元信息*/
             /*s_change_token_inventory每个清单关联的变更令,s_change_token_meter每个计量单元关联的变更令*/
+            LinkedHashMap<String,FormData> fdm = FormulaUtils.toFormDataMap(new SubInterimMeterPaySummary());
+            /*获取实际输出行数*/
+            this.capacity=getLineSize(new ArrayList<>(fdm.keySet()));
+            Map<String,Function<List<SubInterimMeterPaySummary>,List<Object>>> functionMap =FormulaUtils.fieldDataFcMap(SubInterimMeterPaySummary.class);
+            tec.getFormDataMap().putAll(fdm);
             try {
                 Map<Long, TreeNode<MeterTree>> treeNodeMap = tec.getMeterTreeMap().get();
                 Optional<TreeNode<MeterTree>> optionalTreeNode=  treeNodeMap.values().stream().filter(TreeNode::isTop).findAny();
                 if(optionalTreeNode.isPresent()) {
                     /*计量单元根节点*/
                     TreeNode<MeterTree> top= optionalTreeNode.get();
-                    List<TreeNode<MeterTree>> treeNodeList = new ArrayList<>();
                     Map<Long, List<Payment>> paymentGroup = current.stream().collect(Collectors.groupingBy(Payment::getMeterId));
                     Map<String,Payment> preMeterPaymentGroup = previous.stream().collect(Collectors.toMap(Payment::meterFormKey,p->p,(p1,p2)->p2));
                     FormulaUtils.treeNodeSort(0,top);
                     current.forEach(e -> {
                         TreeNode<MeterTree> meterTreeNode=treeNodeMap.get(e.getMeterId());
                         if(meterTreeNode!=null){
-                            treeNodeList.add(meterTreeNode);
+                            MeterTree meterUnit = meterTreeNode.getValue();
+                            List<Payment> paymentList = paymentGroup.get(meterUnit.getId());
+                            for(Payment payment:paymentList){
+                                Payment prePayment = preMeterPaymentGroup.get(meterUnit.getId().toString()+payment.getFormId());
+                                this.treeNodeCheckedCount(meterTreeNode,payment,prePayment);
+                            }
                         }
                     });
-                    if(treeNodeList.size()>0){
-                        treeNodeList.sort(Comparator.comparingInt(TreeNode::getSort));
-                        for(TreeNode<MeterTree> node:treeNodeList){
-                             MeterTree meterUnit = node.getValue();
-                             List<MeterTree> chains = FormulaUtils.treeNodeChains(node);
-                             List<Payment> paymentList = paymentGroup.get(meterUnit.getId());
-                             for(Payment payment:paymentList){
-                                 Payment prePayment = preMeterPaymentGroup.get(meterUnit.getId().toString()+payment.getFormId());
-                                 SubInterimMeterPaySummary smps= new SubInterimMeterPaySummary();
-                                 smps.setItemName(payment.getName());
-                                 smps.setFormNumber(payment.getNumber());
-                                 smps.setUnit(payment.getUnit());
-                             }
+                    /*分组列表,每一组都是一个章节*/
+                    List<List<SubInterimMeterPaySummary>> pageData =traversal(top);
+                    List<SubInterimMeterPaySummary> dataList=  pageData.stream().flatMap(list->{
+                        List<SubInterimMeterPaySummary> tmp =new ArrayList<>(list);
+                        int size = (int)Math.ceil(tmp.size()/(double)this.capacity);
+                        /*每一章需要留一行做小计,其他要填充空白行*/
+                        SubInterimMeterPaySummary summary=  new SubInterimMeterPaySummary("小计");
+                        summary.setCurrentPeriodEndPay(tmp.stream().map(SubInterimMeterPaySummary::getCurrentPeriodEndPay).filter(BaseUtils::isNumber).map(BigDecimal::new).reduce(BigDecimal.ZERO,BigDecimal::add).toString());
+                        int over = size*capacity-1-tmp.size();
+                        if(over>0){
+                            /*填充空白行*/
+                            tmp.addAll(Collections.nCopies(over,new SubInterimMeterPaySummary()));
                         }
-                    }
-
+                        tmp.add(summary);
+                       return tmp.stream();
+                    }).collect(Collectors.toList());
+                    /*内容输出*/
+                    FormulaUtils.put2FormData(fdm,functionMap,dataList);
                 }
             }catch (Exception e){
                 e.printStackTrace();
@@ -579,13 +614,144 @@ public class ExecutorSpecial extends FormulaExecutor {
 
         }
 
+        /*溯源并计算累计支付*/
+        public  void treeNodeCheckedCount(TreeNode<MeterTree> tmp,Payment payment, Payment prePayment ){
+            tmp.setChecked(true);
+            MeterTree meterTree = tmp.getValue();
+            if(meterTree.peer==null){
+                meterTree.peer= payment2Smps.apply(payment,prePayment);
+            }
+            int loop = 10;
+            TreeNode<MeterTree> node=tmp.getParent();
+            SubInterimMeterPaySummary sp= meterTree.peer;
+            while (node!=null&&loop-->0){
+                node.setChecked(true);
+                MeterTree tmpTree= node.getValue();
+                SubInterimMeterPaySummary ss=tmpTree.peer;
+                if(ss==null){
+                    /*不存在则新增*/
+                    ss = new SubInterimMeterPaySummary();
+                    ss.setItemName(meterTree.getNodeName());
+                    ss.setCurrentPeriodEndPay(sp.getCurrentPeriodEndPay());
+                    tmpTree.peer=ss;
+                }else{
+                    /*已存在则相加*/
+                    ss.currentPeriodEndPayAdd(sp.getCurrentPeriodEndPay());
+                }
+                node=node.getParent();
+            }
+        }
 
-        public void traversal(TreeNode<MeterTree> top){
+        /*遍历树获取smps*/
+        public  List<List<SubInterimMeterPaySummary>> traversal(TreeNode<MeterTree> top){
+            List<List<SubInterimMeterPaySummary>> total = new ArrayList<>();
+            if(top.hasChildren()) {
+                List<TreeNode<MeterTree>> parts = top.getChildren();
+                for(TreeNode<MeterTree> node :parts){
+                    List<SubInterimMeterPaySummary> list = new ArrayList<>();
+                    toSmps(node,list);
+                    if(list.size()>0){
+                        total.add(list);
+                    }
+                }
+            }
+            return total;
+        }
 
+        public void toSmps(TreeNode<MeterTree> node,List<SubInterimMeterPaySummary> list){
+            if(node.isChecked()){
+                list.add(node.getValue().getPeer());
+                if(node.hasChildren()){
+                      for(TreeNode<MeterTree> child:node.getChildren()){
+                          toSmps(child,list);
+                      }
+                }
+            }
         }
 
     }
 
+    @Data
+    public class IMeterPaySummary implements Special{
+        private Integer capacity=20;
+        @Override
+        public boolean ready() {
+            return current.size()>0;
+        }
+
+        @Override
+        public void parse() {
+            LinkedHashMap<String,FormData> fdm = FormulaUtils.toFormDataMap(new InterimMeterPaySummary());
+            /*获取实际输出行数*/
+            this.capacity=getLineSize(new ArrayList<>(fdm.keySet()));
+            Map<String,Function<List<InterimMeterPaySummary>,List<Object>>> functionMap =FormulaUtils.fieldDataFcMap(InterimMeterPaySummary.class);
+            tec.getFormDataMap().putAll(fdm);
+            List<InterimMeterPaySummary> dataList = new ArrayList<>();
+            for(Payment payment:current){
+                InterimMeterPaySummary imps = new InterimMeterPaySummary();
+                BeanUtils.copyProperties(payment,imps);
+                imps.setItemName(payment.getName());
+                imps.setFormNumber(payment.getNumber());
+                dataList.add(imps);
+            }
+            if(dataList.size()>0){
+                InterimMeterPaySummary summary = new InterimMeterPaySummary("合计");
+                double sum=dataList.stream().map(InterimMeterPaySummary::getMoney).filter(StringUtils::isNumber).mapToDouble(Double::parseDouble).sum();
+                summary.setMoney(StringUtils.number2StringZero(sum,2));
+                dataList.add(summary);
+            }
+            FormulaUtils.put2FormData(fdm,functionMap,dataList);
+        }
+    }
+
+    @Data
+    public class IMeter implements Special{
+        private Integer capacity=7;
+        @Override
+        public boolean ready() {
+            return current.size()>0;
+        }
+
+        @Override
+        public void parse() {
+            LinkedHashMap<String,FormData> fdm = FormulaUtils.toFormDataMap(new InterimMeter());
+            /*获取实际输出行数*/
+            this.capacity=getLineSize(new ArrayList<>(fdm.keySet()));
+            Map<String,Function<List<InterimMeter>,List<Object>>> functionMap =FormulaUtils.fieldDataFcMap(InterimMeter.class);
+            tec.getFormDataMap().putAll(fdm);
+            List<MeterApply> meterApplyList=meterApplyFc.apply(tec.getPeriodId());
+            Map<Long,List<Payment>> paymentGroup = current.stream().collect(Collectors.groupingBy(Payment::getMiddleMeterId));
+            List<InterimMeter> dataList = new ArrayList<>();
+            for(MeterApply meterApply:meterApplyList){
+                List<Payment> paymentList = paymentGroup.get(meterApply.getId());
+                List<List<Payment>> pageData = BaseUtils.splitList(paymentList,this.capacity);
+                for(List<Payment> payments:pageData){
+                    /*根据清单数量决定一条中间计量申请显示多少页*/
+                    InterimMeter iim = new InterimMeter();
+                    BeanUtils.copyProperties(meterApply,iim);
+                    int over = this.capacity-payments.size();
+                    List<String> formNumberList= payments.stream().map(Payment::getNumber).collect(Collectors.toList());
+                    List<String> itemNameList= payments.stream().map(Payment::getName).collect(Collectors.toList());
+                    List<String> unitList= payments.stream().map(Payment::getUnit).collect(Collectors.toList());
+                    List<String> completedList= payments.stream().map(p->StringUtils.handleNull(p.getCompleted())).collect(Collectors.toList());
+                    if(over>0){
+                        formNumberList.addAll(Collections.nCopies(over,StringPool.EMPTY));
+                        itemNameList.addAll(Collections.nCopies(over,StringPool.EMPTY));
+                        unitList.addAll(Collections.nCopies(over,StringPool.EMPTY));
+                        completedList.addAll(Collections.nCopies(over,StringPool.EMPTY));
+                    }
+                    iim.setFormNumberList(formNumberList);
+                    iim.setItemNameList(itemNameList);
+                    iim.setUnitList(unitList);
+                    iim.setCompletedList(completedList);
+                    dataList.add(iim);
+                }
+            }
+            FormulaUtils.put2FormData(fdm,functionMap,dataList);
+        }
+    }
+
+
     public interface  Special{
         /**是否满足执行条件*/
         boolean ready();

+ 2 - 1
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/impl/FormulaTurnPoint.java

@@ -226,7 +226,7 @@ public class FormulaTurnPoint implements FormulaStrategy {
 
     public void c8103(List<TurnPoint> data,List<NodeTable> nodeTableList,TableElementConverter tec){
         nodeTableList.stream().filter(e-> "m_20220928163111_1575040410223378432".equals(e.getInitTableName())).findFirst().ifPresent(t->{
-            List<FormData> fds= tec.formDataMap.values().stream().filter(k-> "m_20220928163111_1575040410223378432".equals(k.getTableName())&&k.getCoordsList().size()>1).collect(Collectors.toList());
+            List<FormData> fds= tec.formDataMap.values().stream().filter(k-> ("m_20220928163111_1575040410223378432".equals(k.getTableName()))&&k.getCoordsList().size()>1).collect(Collectors.toList());
             /*[{"key":"#2","m":"垫石编号"},{"key":"#3","m":"顶面高程设计"},{"key":"#4","m":"顶面高程实测"},{"key":"#5","m":"顶面高程偏差"},{"key":"#6","m":"顶面四角高差实测"}]*/
             FormData dsbh=null,dmgcsj=null, dmgcsc=null,dmgcpc=null,sjgcsc=null;
             for(FormData u:fds){
@@ -263,6 +263,7 @@ public class FormulaTurnPoint implements FormulaStrategy {
                 Map<Integer,List<Object>> group=tmpList.stream().collect(Collectors.groupingBy(e->ai.getAndIncrement()%fda.length));
                 for(int i=0;i<fda.length;i++){
                     FormData fd= fda[i];
+                    if(fd==null)continue;
                     FormulaUtils.write(fd,group.get(i),true);
                     fd.setUpdate(1);
                 }

+ 11 - 1
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/impl/TableElementConverter.java

@@ -109,6 +109,9 @@ public class TableElementConverter implements ITableElementConverter {
     private final Converter in;
     private final Converter out;
     /*计量属性*/
+    /**报表Id*/
+    private  Long reportId;
+    /**计量期Id*/
     private Long periodId;
     /**计量类型*/
     private MeterType meterType;
@@ -375,6 +378,8 @@ public class TableElementConverter implements ITableElementConverter {
             /*这个方法的核心就是把fds的数据按照既定格式(key_xxx_val_y_x)回写到tableInfo,对fds进行tableName分组,非空循环写人*/
             LinkedHashMap<String, List<KeyMapper>> tabs = keyMappers.stream().collect(Collectors.groupingBy(KeyMapper::getCode, LinkedHashMap::new, Collectors.toList()));
             List<FormData> updateList = formDataMap.values().stream().filter(e -> e.getUpdate() == 1 && e.isCurrentNodeElement).collect(Collectors.toList());
+            /*再次核对是否已经被修改*/
+            updateList=updateList.stream().filter(FormData::hasChange).collect(Collectors.toList());
             if (Func.isNotEmpty(updateList)) {
                 for (FormData fd : updateList) {
                     if (fd.getUpdate().equals(1)) {
@@ -405,6 +410,8 @@ public class TableElementConverter implements ITableElementConverter {
             } else {
                 log.put(FormulaLog.OTHER,"公式未插入或者修改任何数据");
             }
+            /*删除无需更新的表*/
+            tableInfoList.removeIf(e->!e.isToBeUpdated());
         }
     }
     public class AfterB implements Converter{
@@ -454,6 +461,7 @@ public class TableElementConverter implements ITableElementConverter {
                    rt.setUrl(report.getHtmlUrl());
                    rt.setInitTableName(report.getInitTableName());
                    rt.setName(report.getNodeName());
+                   rt.setExcelId(report.getExcelId());
                    reportResults.add(rt);
                    List<FormData> fds =group.get(report.getInitTableName());
                    if(fds.size()>0){
@@ -470,7 +478,9 @@ public class TableElementConverter implements ITableElementConverter {
                            if(!fd.empty()) {
                                List<ElementData> eds = fd.getValues();
                                for(ElementData ed:eds){
-                                   dataList.get(ed.getIndex()).put(ed.getCoords(),ed.getValue());
+                                   if(!ed.isEmpty()) {
+                                       dataList.get(ed.getIndex()).put(ed.getCoords(), ed.getValue());
+                                   }
                                }
                            }
                        }

+ 15 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ArchiveTreeContractMapper.java

@@ -131,4 +131,19 @@ public interface ArchiveTreeContractMapper extends BaseMapper<ArchiveTreeContrac
     List<MyInspectTreeVO> MyLazyTree(@Param("parentId") Long parentId,@Param("projectId") Long projectId,@Param("userId") Long userId);
 
     ArchiveExpertConclusion getNewTable(@Param("projectId") Long projectId);
+
+    /**
+     * 批量添加归档合同树
+     */
+    int batchInsertArchiveTreeContract(@Param("list") List<ArchiveTreeContract> list);
+
+    /**
+     * 批量删除归档合同树
+     */
+    int batchDeleteArchiveTreeContractByIdList(@Param("ids") List<Long> ids, @Param("isDeleted") Integer isDeleted);
+
+    /**
+     * 批量修改归档树
+     */
+    int batchUpdateArchiveTreeContract(@Param("list") List<Map> list);
 }

+ 47 - 1
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ArchiveTreeContractMapper.xml

@@ -189,7 +189,7 @@
         SELECT
         d.id,
         d.parent_id,
-        d.node_name AS title,
+        IFNULL(if(length(trim(d.full_name))>0,d.full_name,d.node_name),d.node_name)AS title,
         d.id AS "value",
         d.id AS "key",
         d.major_data_type AS "majorDataType",
@@ -699,4 +699,50 @@
 <!--          and contract_id = #{contractId} and is_deleted = 0;-->
 <!--    </select>-->
 
+    <!-- 批量添加归档合同树 -->
+    <insert id="batchInsertArchiveTreeContract" parameterType="java.util.List">
+        INSERT INTO m_archive_tree_contract (id, tenant_id, project_id, contract_id, parent_id, ancestors, node_name,
+        full_name, tree_code, ext_type, ext_id, ext_key_id, ext_attach_id, sort, status, is_deleted, from_id, node_type,
+        is_storage_node, post_type, major_data_type, project_type, storage_type, create_user, create_dept, create_time, tree_structure)
+        VALUES
+        <foreach collection="list" item="item" separator=",">
+            (#{item.id}, #{item.tenantId}, #{item.projectId}, #{item.contractId}, #{item.parentId},#{item.ancestors},
+            #{item.nodeName},#{item.fullName},#{item.treeCode},#{item.extType},#{item.extId},#{item.extKeyId},#{item.extAttachId},
+            #{item.sort},#{item.status},#{item.isDeleted},#{item.fromId},#{item.nodeType},#{item.isStorageNode},#{item.postType},#{item.majorDataType}
+            ,#{item.projectType},#{item.storageType},#{item.createUser},#{item.createDept},#{item.createTime}, #{item.treeStructure})
+        </foreach>
+    </insert>
+
+    <!-- 批量删除归档合同树 -->
+    <update id="batchDeleteArchiveTreeContractByIdList">
+        update
+            m_archive_tree_contract
+        set
+            is_deleted = #{isDeleted},
+            update_time = NOW()
+        where
+            id in
+        <foreach collection="ids" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </update>
+
+    <!-- 批量修改归档树 -->
+    <update id="batchUpdateArchiveTreeContract">
+        <foreach collection="list" item="item" index="index" separator=";">
+            update
+                m_archive_tree_contract
+            <set>
+                parent_id = #{item.parentId},
+                ancestors = #{item.ancestors},
+                node_name = #{item.nodeName},
+                node_type = #{item.nodeType},
+                full_name = #{item.fullName},
+                tree_structure = #{item.treeStructure},
+                update_time = NOW()
+            </set>
+            where id = #{item.id}
+        </foreach>
+    </update>
+
 </mapper>

+ 1 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ContractInfoMapper.xml

@@ -460,6 +460,7 @@
         d.parent_id AS "parentId",
         IFNULL(if(length(trim(full_name)) > 0, full_name, node_name),node_name) AS title,
         d.node_name AS "nodeName",
+        d.full_name AS "fullName",
         d.node_type ,
         d.id AS "value",
         d.id AS "key",

+ 4 - 3
blade-service/blade-manager/src/main/java/org/springblade/manager/service/IFormulaDao.java

@@ -15,14 +15,15 @@ public interface IFormulaDao {
     Function<Long, MeterPeriodInfo> getMeterPeriodFc();
     /**获取清单信息*/
     Function<Long, List<Material>> getMaterialFormFc();
-
     /**获取中间计量期*/
     Function<Long, MeterPeriodInfo> getInterimMeterPeriodFc();
     /**获取支付数据*/
     Function<Long, List<Payment>> getPaymentListFc();
-    /*获取合同计量清单*/
+    /**获取合同计量清单*/
     Function<Long,List<InventoryForm>> getInventoryFormFc();
-
+    /**获取计量单元树*/
     Function<Long,List<MeterTree>>  getMeterTreeFc();
+    /**获取中间计量申请*/
+    Function<Long,List<MeterApply>>  getMeterApplyFc();
 
 }

+ 2 - 1
blade-service/blade-manager/src/main/java/org/springblade/manager/service/IFormulaService.java

@@ -89,7 +89,8 @@ public interface IFormulaService extends BaseService<Formula> {
     R<Object> evaluate(Long pkeyId);
     /**获取当前节点的参数wbsNodeId:WBS级id ,wtpPkeyId:项目级PkeyId*/
     List<WbsParam> getNodeWps(Long wbsNodeId,Long wtpPkeyId);
-    /*计量公式执行 0中间,1材料,2开工*/
+    /**计量公式执行 0中间,1材料,2开工*/
     List<ReportResult> execute3(Long contractId, Long periodId, Integer type);
 
+    R<Object>formulasForNode(Long pkeyId,String tableName,String elementName);
 }

+ 9 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/ITrialSummaryClassificationConfigurationService.java

@@ -2,7 +2,10 @@ package org.springblade.manager.service;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.springblade.core.mp.base.BaseService;
+import org.springblade.manager.dto.TrialSummaryClassificationConfigurationMatchDTO;
+import org.springblade.manager.dto.TrialSummaryClassificationConfigurationRelevancyDTO;
 import org.springblade.manager.entity.TrialSummaryClassificationConfiguration;
+import org.springblade.manager.vo.TrialTreeVO;
 
 public interface ITrialSummaryClassificationConfigurationService extends BaseService<TrialSummaryClassificationConfiguration> {
 
@@ -12,4 +15,10 @@ public interface ITrialSummaryClassificationConfigurationService extends BaseSer
 
     IPage<Object> selectContractInfoPage(IPage<Object> page, TrialSummaryClassificationConfiguration obj);
 
+    boolean matching(TrialSummaryClassificationConfigurationMatchDTO dto);
+
+    boolean relevancy(TrialSummaryClassificationConfigurationRelevancyDTO dto);
+
+    TrialTreeVO tree(String projectId, String classId, String type);
+
 }

+ 712 - 6
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ArchiveTreeContractSyncImpl.java

@@ -1,35 +1,48 @@
 package org.springblade.manager.service.impl;
 
+import com.alibaba.nacos.common.utils.DateFormatUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.google.protobuf.ServiceException;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang.StringUtils;
 import org.springblade.archive.entity.ArchiveProjectConfig;
 import org.springblade.archive.feign.ArchiveAutoClient;
+import org.springblade.business.dto.ImageClassificationFileDTO;
 import org.springblade.business.entity.ArchiveFile;
 import org.springblade.business.entity.InformationQuery;
 import org.springblade.business.feign.ArchiveFileClient;
+import org.springblade.business.feign.ImageClassificationFileClient;
 import org.springblade.business.feign.InformationQueryClient;
+import org.springblade.business.vo.TreeVoTwo;
+import org.springblade.common.utils.ForestNodeMergerEx;
+import org.springblade.common.utils.SnowFlakeUtil;
 import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.utils.CollectionUtil;
+import org.springblade.core.tool.utils.ObjectUtil;
 import org.springblade.manager.entity.ArchiveTreeContract;
 import org.springblade.manager.entity.ContractInfo;
 import org.springblade.manager.entity.ContractRelationJlyz;
 import org.springblade.manager.entity.ProjectInfo;
-import org.springblade.manager.feign.ContractClient;
+import org.springblade.manager.enums.StorageTypeEnum;
+import org.springblade.manager.enums.TreeStructureEnum;
 import org.springblade.manager.mapper.ArchiveTreeContractMapper;
 import org.springblade.manager.service.IContractInfoService;
 import org.springblade.manager.service.IProjectInfoService;
-import org.springblade.common.utils.ForestNodeMergerEx;
-import org.springblade.manager.service.IWbsTreeContractService;
 import org.springblade.manager.utils.ForestNodeMerger;
+import org.springblade.manager.utils.YearTreeUtils;
+import org.springblade.manager.vo.ArchiveTreeContractVO;
 import org.springblade.manager.vo.ArchiveTreeContractVO2;
 import org.springblade.manager.vo.ArchiveTreeVO2;
 import org.springblade.manager.vo.WbsTreeContractVO6;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 
+import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.util.*;
 import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
 
 @Slf4j
 @Service
@@ -55,6 +68,7 @@ public class ArchiveTreeContractSyncImpl {
 
     private final ArchiveAutoClient archiveAutoClient;
 
+    private final ImageClassificationFileClient imageClassificationFileClient;
 
     /**
      * 普通同步
@@ -620,10 +634,10 @@ public class ArchiveTreeContractSyncImpl {
 
     public List<ArchiveTreeContract>  getArchiveTreeContractsWbs(Long contractId,Long attachId,Integer classify) {
 
-        List<ArchiveTreeContract> archiveTreeContracts = 	archiveTreeContractMapper.selectList(Wrappers.<ArchiveTreeContract>query().lambda()
+        List<ArchiveTreeContract> archiveTreeContracts = archiveTreeContractMapper.selectList(Wrappers.<ArchiveTreeContract>query().lambda()
                 .eq(ArchiveTreeContract::getContractId, contractId)
                 .eq(ArchiveTreeContract::getExtAttachId, attachId)
-                .eq(ArchiveTreeContract::getClassify, classify)
+                .eq(ObjectUtil.isNotEmpty(classify), ArchiveTreeContract::getClassify, classify)
                 .eq(ArchiveTreeContract::getExtType, 1));
         return archiveTreeContracts;
 
@@ -738,5 +752,697 @@ public class ArchiveTreeContractSyncImpl {
         return groupedFiles;
     }
 
+    /**
+     * 同步合同段声像文件
+     */
+    public void syncSoundImageData(Long projectId, Long contractId, Long nodeId) throws ServiceException {
+        // 合同信息
+        ContractInfo contractInfo = contractInfoService.getById(contractId);
+        // 判断是否正在同步
+        Integer isArchivesAuto = contractInfo.getIsArchivesAuto();
+        if(ObjectUtil.isNotEmpty(isArchivesAuto) && isArchivesAuto != 0) {
+            throw new ServiceException("当前合同段已经在同步中,请耐心等待");
+        }
+        // 判断节点存储类型是否为声像
+        ArchiveTreeContract archiveTreeContract = archiveTreeContractMapper.selectById(nodeId);
+        if(archiveTreeContract != null) {
+            if(!archiveTreeContract.getStorageType().equals(StorageTypeEnum.SOUND_IMAGE.getCode())){
+                throw new ServiceException("当前节点存储类型不是声像,无法同步声像文件");
+            }
+        }else{
+            throw new ServiceException("当前节点不存在,同步失败");
+        }
+        log.info("开始自动组卷...." + contractId);
+        //设置自动组卷中
+        contractInfoService.updateIsArchivesAutoById(contractId,1);
+        try {
+            // 同步文件
+            syncSoundImageData(projectId,contractInfo,archiveTreeContract);
+            //完成自动组卷
+            contractInfoService.updateIsArchivesAutoById(contractId, 0);
+        }catch (Exception e){
+            e.printStackTrace();
+            //设置自动组卷结束
+            contractInfoService.updateIsArchivesAutoById(contractId, 0);
+            log.error("同步声像文件报错,projectId:"+projectId+"、contractId:"+contractId+"nodeId:"+nodeId+",原因:"+e.getMessage());
+            throw new ServiceException("同步声像文件报错");
+        }
+    }
+
+    /**
+     * 同步某个合同段的声像资料到归档
+     */
+    public void syncSoundImageData(Long projectId, ContractInfo contractInfo, ArchiveTreeContract archiveTreeContract) {
+        Long contractId = contractInfo.getId();
+
+        log.info("同步声像文件,projectId:"+projectId+",contractId:"+contractId+",nodeId:"+archiveTreeContract.getId());
+
+        //项目信息
+        ProjectInfo projectInfo = projectInfoService.getOne(projectId);
+
+        //项目使用的wbs模板
+        Long lWbsId = projectInfo.getReferenceWbsTemplateId();
+
+        //获取合同段wbs划分树,用来组织树
+        List<WbsTreeContractVO6> wbsTrees = contractInfoService.tree8(lWbsId.toString(), projectId.toString(), contractId.toString());
+        if (CollectionUtil.isEmpty(wbsTrees)) {
+            return;
+        }
+
+        // wbs转换archive树形集合
+        List<ArchiveTreeContract> wbsCovertArchiveTreeContractList = new ArrayList<>();
+
+        // wbs转换archive,并初始化值
+        getTreeList(AuthUtil.getTenantId(), wbsTrees.get(0), wbsCovertArchiveTreeContractList, archiveTreeContract, archiveTreeContract.getId());
+
+        log.info("同步声像文件树  imageClassificationFileList :"+wbsCovertArchiveTreeContractList.size());
+
+        //同步质检关联节点
+        syncNodes(wbsCovertArchiveTreeContractList, contractInfo, archiveTreeContract);
+    }
+
+    /**
+     * 将树形节点对象转换为普通的list集合
+     */
+    public void getTreeList(String tenantId, WbsTreeContractVO6 tree, List<ArchiveTreeContract> wbsCovertArchiveTreeContractList, ArchiveTreeContract archiveTreeContract, Long archiveTreeContractId) {
+        if (ObjectUtil.isEmpty(tree)) {
+            return;
+        }
+        // wbs树转archive树
+        ArchiveTreeContract archiveTreeContractTemp = wbsTreeContractCovertArchiveTreeContract(tenantId, tree, archiveTreeContract, archiveTreeContractId);
+        wbsCovertArchiveTreeContractList.add(archiveTreeContractTemp);
+
+        // 循环遍历,转换树对象
+        List<WbsTreeContractVO6> childrenList = tree.getChildren();
+        if (childrenList != null) {
+            for (WbsTreeContractVO6 child : childrenList) {
+                getTreeList(tenantId, child, wbsCovertArchiveTreeContractList, archiveTreeContractTemp, archiveTreeContractId);
+            }
+        }
+    }
+
+    /**
+     * 将wbs树初始化为归档树
+     */
+    public ArchiveTreeContract wbsTreeContractCovertArchiveTreeContract(String tenantId, WbsTreeContractVO6 tree, ArchiveTreeContract archiveTreeContract, Long archiveTreeContractId) {
+        ArchiveTreeContract archiveTreeContractTemp = new ArchiveTreeContract();
+        //设置基本信息
+        archiveTreeContractTemp.setId(SnowFlakeUtil.getId());
+        archiveTreeContractTemp.setTenantId(tenantId);
+        archiveTreeContractTemp.setProjectId(archiveTreeContract.getProjectId());
+        archiveTreeContractTemp.setContractId(archiveTreeContract.getContractId());
+        archiveTreeContractTemp.setParentId(archiveTreeContract.getId());
+        archiveTreeContractTemp.setAncestors(archiveTreeContract.getAncestors()+","+archiveTreeContract.getId());
+        archiveTreeContractTemp.setNodeName(tree.getNodeName());
+        archiveTreeContractTemp.setNodeType(1);
+        archiveTreeContractTemp.setFullName(tree.getFullName());
+        archiveTreeContractTemp.setTreeCode(archiveTreeContract.getContractId().toString());
+        //设置关联
+        archiveTreeContractTemp.setExtType(1);
+        archiveTreeContractTemp.setExtId(tree.getId());
+        archiveTreeContractTemp.setExtKeyId(tree.getPKeyId());
+        archiveTreeContractTemp.setExtAttachId(archiveTreeContractId);
+        archiveTreeContractTemp.setSort(tree.getSort());
+        archiveTreeContractTemp.setStatus(1);
+        archiveTreeContractTemp.setIsDeleted(0);
+        archiveTreeContractTemp.setFromId(tree.getId());
+        if (StringUtils.isNotEmpty(tree.getOldId())) {
+            archiveTreeContractTemp.setFromId(Long.parseLong(tree.getOldId()));
+        }
+        archiveTreeContractTemp.setIsStorageNode(1);
+        //wbs_tree_contract与archive_tree_contract字段等存在差异,不清楚的字段值,我沿用同步节点的字段值进行初始化
+        archiveTreeContractTemp.setPostType(archiveTreeContract.getPostType());
+        archiveTreeContractTemp.setMajorDataType(archiveTreeContract.getMajorDataType());
+        archiveTreeContractTemp.setProjectType(archiveTreeContract.getProjectType());
+        archiveTreeContractTemp.setStorageType(archiveTreeContract.getStorageType());
+        archiveTreeContractTemp.setCreateUser(tree.getCreateUser());
+        archiveTreeContractTemp.setCreateDept(tree.getCreateDept());
+        archiveTreeContractTemp.setCreateTime(tree.getCreateTime());
+        archiveTreeContractTemp.setTreeStructure(TreeStructureEnum.ORDINARY_TREE.getCode());
+        return archiveTreeContractTemp;
+    }
+
+    private void syncNodes(List<ArchiveTreeContract> wbsCovertArchiveTreeContractList,
+                           ContractInfo contractInfo, ArchiveTreeContract archiveTreeContract) {
+        Long contractId = contractInfo.getId();
+
+        // 获取关联节点
+        List<ArchiveTreeContract> associatedNodes = Arrays.asList(archiveTreeContract);
+
+        log.info("同步声像文件树节点  syncNodes:"+associatedNodes.size());
+
+        // 新增的归档树聚合
+        List<ArchiveTreeContract> saveArchiveTreeContractList = new ArrayList<>();
+        // 新增的归档文件集合
+        List<ArchiveFile> saveArchiveFileList = new ArrayList<>();
+        // 修改的归档树聚合
+        List<Map> updateArchiveTreeContractList = new ArrayList<>();
+        // 修改的归档文件集合
+        List<ArchiveFile> updateArchiveFileList = new ArrayList<>();
+        // 删除的归档树聚合
+        List<ArchiveTreeContract> deleteArchiveTreeContractList = new ArrayList<>();
+        // 删除的归档文件集合
+        List<ArchiveFile> deleteArchiveFileList = new ArrayList<>();
+
+        for (ArchiveTreeContract node : associatedNodes) {
+            log.info("同步声像文件树节点  syncNode:" + node.getId() + "-" +  node.getNodeName());
+
+            /***同步树-开始*/
+            // 获取该合同段下的wbs节点
+            List<ArchiveTreeContract> originalArchiveTreeContractList = getArchiveTreeContractsWbs(contractId, node.getId(),null);
+
+            // 树形map
+            Map<Long, ArchiveTreeContract> originalArchiveTreeContractMap = buildNodeMap(originalArchiveTreeContractList);
+
+            // 转换类型
+            List<ArchiveTreeContractVO> archiveTreeContractVOList = wbsCovertArchiveTreeContractList.stream().map(tree -> {
+                ArchiveTreeContractVO vo = new ArchiveTreeContractVO();
+                BeanUtils.copyProperties(tree, vo);
+                return vo;
+            }).collect(Collectors.toList());
+
+            // list转换为树形
+            List<ArchiveTreeContractVO> mergeList = org.springblade.core.tool.node.ForestNodeMerger.merge(archiveTreeContractVOList);
+
+            // 与已有数据做合并(目的是将已有的树信息复制到对应的树)
+            archiveAndOldTreeMergeData(mergeList, originalArchiveTreeContractMap, TreeStructureEnum.ORDINARY_TREE);
+
+            // 重新刷新整个树结构(刷新整个树的父子节点id)
+            for (ArchiveTreeContractVO archiveTreeContractVO : mergeList) {
+                refreshTreeStructure(archiveTreeContractVO, node.getId(), node.getAncestors());
+            }
+
+            // 将树转换为list
+            List<ArchiveTreeContractVO> archiveTreeContractVOTempList = new ArrayList<>();
+            ForestNodeMerger.getTreeList(mergeList.get(0), archiveTreeContractVOTempList);
+
+            List<ArchiveTreeContract> newArchiveTreeContractList = archiveTreeContractVOTempList.stream().map(vo -> {
+                ArchiveTreeContract archiveTreeContractTemp = new ArchiveTreeContract();
+                BeanUtils.copyProperties(vo, archiveTreeContractTemp);
+                return vo;
+            }).collect(Collectors.toList());
+
+            // 树形map
+            Map<Long, ArchiveTreeContract> newArchiveTreeContractMap = buildNodeMap(newArchiveTreeContractList);
+
+            // 取两个树形集合中的差异,并输出新增、修改、删除三个集合
+            diffTrees(originalArchiveTreeContractList, newArchiveTreeContractList, originalArchiveTreeContractMap,
+                    newArchiveTreeContractMap, saveArchiveTreeContractList, deleteArchiveTreeContractList, updateArchiveTreeContractList);
+            /***同步树-结束*/
+
+            /***同步树型文件数据准备-开始*/
+            // 根据合同段id查询声像文件
+            List<ImageClassificationFileDTO> imageClassificationFileList = getImageClassificationFileListByContractId(contractId);
+
+            // 声像文件转换
+            List<ArchiveFile> newArchiveFileList = imageClassificationFileList.stream().map(file -> {
+                ArchiveFile archiveFile = imageClassificationFileCovertArchiveFile(file, node, newArchiveTreeContractMap);
+                return archiveFile;
+            }).collect(Collectors.toList());
+
+            // 查询合同段下的声像文件
+            List<ArchiveFile> originalArchiveFileList = archiveFileClient.getListByContractIdAndArchiveFileStorageType(contractId, StorageTypeEnum.SOUND_IMAGE.getCode());
+
+            // 构件原始文件TreeMap
+            Map<Long, ArchiveFile> originalArchiveFileMap = buildArchiveFileMap(originalArchiveFileList);
+
+            // 与已有数据做合并(目的是将已有的树信息复制到对应的树)
+            originalAndNewArchiveFileMergeData(newArchiveFileList, originalArchiveFileMap);
+
+            // 构件新文件TreeMap
+            Map<Long, ArchiveFile> newArchiveFileMap = buildArchiveFileMap(newArchiveFileList);
+
+            // 将声像文件集合遍历,分为时间储存和树形储存两个集合
+            List<ArchiveFile> newArchiveFileTreeList = new ArrayList<>();
+            List<ArchiveFile> newArchiveFileDateList = new ArrayList<>();
+            for (ArchiveFile archiveFile : newArchiveFileList) {
+                if(archiveFile.getNodeTreeStructure() == 1) {
+                    newArchiveFileTreeList.add(archiveFile);
+                }else if(archiveFile.getNodeTreeStructure() == 2) {
+                    newArchiveFileDateList.add(archiveFile);
+                }
+            }
+
+            // 将声像文件集合遍历,分为时间储存和树形储存两个集合
+            List<ArchiveFile> originalArchiveFileTreeList = new ArrayList<>();
+            List<ArchiveFile> originalArchiveFileDateList = new ArrayList<>();
+            for (ArchiveFile archiveFile : originalArchiveFileList) {
+                if(archiveFile.getNodeTreeStructure() == 1) {
+                    originalArchiveFileTreeList.add(archiveFile);
+                }else if(archiveFile.getNodeTreeStructure() == 2) {
+                    originalArchiveFileDateList.add(archiveFile);
+                }
+            }
+            /***同步树型文件数据准备-结束*/
+
+            /**同步树形文件-开始**/
+            // 取两个树形集合中的差异,并输出新增、修改、删除三个集合
+            diffFiles(originalArchiveFileTreeList, newArchiveFileTreeList, originalArchiveFileMap, newArchiveFileMap,
+                    saveArchiveFileList, deleteArchiveFileList, updateArchiveFileList, newArchiveTreeContractMap, TreeStructureEnum.ORDINARY_TREE);
+            /**同步树形文件-结束**/
+
+            /**同步时间树-开始**/
+            // 获取该合同段下的时间型节点
+            List<ArchiveTreeContract> originalDateArchiveTreeContractList = getArchiveTreeContractsByTreeStructure(contractId, node.getId(), TreeStructureEnum.DATE_TREE);
+
+            // 树形map
+            Map<Long, ArchiveTreeContract> originalDateArchiveTreeContractMap = buildDateMap(originalDateArchiveTreeContractList);
+
+            // 将最新文件的时间取出,创建时间树
+            List<String> dateList = newArchiveFileDateList.stream().map(archiveFile -> {
+                String format = DateFormatUtils.format(archiveFile.getCreateTime(), "yyyy-MM-dd");
+                return format;
+            }).collect(Collectors.toList());
+
+            // 转时间树
+            List<TreeVoTwo> dateTreeList = YearTreeUtils.yearMonthDayTree(dateList, "DESC");
+
+            // date转换archive树形集合
+            List<ArchiveTreeContractVO> dateCovertArchiveTreeContractVoList = new ArrayList<>();
+
+            // date转换archive,并初始化值
+            getTreeList(AuthUtil.getTenantId(), dateTreeList, dateCovertArchiveTreeContractVoList, archiveTreeContract, archiveTreeContract.getId());
+
+            // list转换为树形
+            List<ArchiveTreeContractVO> mergeDateList = org.springblade.core.tool.node.ForestNodeMerger.merge(dateCovertArchiveTreeContractVoList);
+
+            // 与已有数据做合并(目的是将已有的树信息复制到对应的树)
+            archiveAndOldTreeMergeData(mergeDateList, originalDateArchiveTreeContractMap, TreeStructureEnum.DATE_TREE);
+
+            // 重新刷新整个树结构(刷新整个树的父子节点id)
+            for (ArchiveTreeContractVO dateArchiveTreeContractVO : mergeDateList) {
+                refreshTreeStructure(dateArchiveTreeContractVO, node.getId(), node.getAncestors());
+            }
+
+            // 将树转换为list
+            List<ArchiveTreeContractVO> dateArchiveTreeContractVOTempList = new ArrayList<>();
+            for (ArchiveTreeContractVO archiveTreeContractVO : mergeDateList) {
+                ForestNodeMerger.getTreeList(archiveTreeContractVO, dateArchiveTreeContractVOTempList);
+            }
+            // 转实体类
+            List<ArchiveTreeContract> newDateArchiveTreeContractList = dateArchiveTreeContractVOTempList.stream().map(vo -> {
+                ArchiveTreeContract archiveTreeContractTemp = new ArchiveTreeContract();
+                BeanUtils.copyProperties(vo, archiveTreeContractTemp);
+                return vo;
+            }).collect(Collectors.toList());
+
+            // 树形map
+            Map<Long, ArchiveTreeContract> newDateArchiveTreeContractMap = buildDateMap(newDateArchiveTreeContractList);
+
+            // 取两个树形集合中的差异,并输出新增、修改、删除三个集合
+            diffDateTrees(originalDateArchiveTreeContractList, newDateArchiveTreeContractList, originalDateArchiveTreeContractMap,
+                    newDateArchiveTreeContractMap, saveArchiveTreeContractList, deleteArchiveTreeContractList, updateArchiveTreeContractList);
+            /**同步时间树-结束**/
+
+            /**同步时间树形文件-开始**/
+            // 取两个树形集合中的差异,并输出新增、修改、删除三个集合
+            diffFiles(originalArchiveFileDateList, newArchiveFileDateList, originalArchiveFileMap, newArchiveFileMap,
+                    saveArchiveFileList, deleteArchiveFileList, updateArchiveFileList, newDateArchiveTreeContractMap, TreeStructureEnum.DATE_TREE);
+            /**同步时间树形文件-结束**/
+
+            /**统一刷新树与文件**/
+            // 刷新树
+            refreshArchiveTreeContract(saveArchiveTreeContractList, updateArchiveTreeContractList, deleteArchiveTreeContractList);
+
+            // 刷新文件
+            refreshArchiveFile(saveArchiveFileList, updateArchiveFileList, deleteArchiveFileList);
+        }
+    }
+    /**
+     * 时间树中的差异
+     * 遍历循环,获取差异,并输出三个集合(归档树的fullName = 老树中的fullName)
+     */
+    private static void diffDateTrees(List<ArchiveTreeContract> originalDateArchiveTreeContractList, List<ArchiveTreeContract> newDateArchiveTreeContractList, Map<Long, ArchiveTreeContract> originalDateArchiveTreeContractMap,
+                                  Map<Long, ArchiveTreeContract> newDateArchiveTreeContractMap, List<ArchiveTreeContract> insertions,
+                                  List<ArchiveTreeContract> deletions, List<Map> modifications) {
+        for (ArchiveTreeContract originalDateArchiveTreeContract : originalDateArchiveTreeContractList) {
+            if (!newDateArchiveTreeContractMap.containsKey(Long.valueOf(originalDateArchiveTreeContract.getFullName()))) {
+                deletions.add(originalDateArchiveTreeContract);
+            } else {
+                ArchiveTreeContract node2 = newDateArchiveTreeContractMap.get(Long.valueOf(originalDateArchiveTreeContract.getFullName()));
+                if (originalDateArchiveTreeContract.getFullName().equals(node2.getFullName())) {
+                    Map map = new HashMap();
+                    map.put("id", originalDateArchiveTreeContract.getId());
+                    map.put("parentId", node2.getParentId());
+                    map.put("ancestors", node2.getAncestors());
+                    map.put("nodeName", node2.getNodeName());
+                    map.put("nodeType", node2.getNodeType());
+                    map.put("fullName", node2.getFullName());
+                    map.put("treeStructure", node2.getTreeStructure());
+                    modifications.add(map);
+                }
+            }
+        }
+        for (ArchiveTreeContract newArchiveTreeContract : newDateArchiveTreeContractList) {
+            if (!originalDateArchiveTreeContractMap.containsKey(Long.valueOf(newArchiveTreeContract.getFullName()))) {
+                insertions.add(newArchiveTreeContract);
+            }
+        }
+    }
+
+    /**
+     * 根据tree结构获取归档树
+     */
+    public List<ArchiveTreeContract>  getArchiveTreeContractsByTreeStructure(Long contractId,Long attachId, TreeStructureEnum treeStructure) {
+        List<ArchiveTreeContract> archiveTreeContracts = archiveTreeContractMapper.selectList(Wrappers.<ArchiveTreeContract>query().lambda()
+                .eq(ArchiveTreeContract::getContractId, contractId)
+                .eq(ArchiveTreeContract::getExtAttachId, attachId)
+                .eq(ArchiveTreeContract::getTreeStructure, treeStructure.getCode()));
+        return archiveTreeContracts;
+    }
+
+    /**
+     * 将时间树形节点对象转换为普通的list集合
+     */
+    public void getTreeList(String tenantId, List<TreeVoTwo> dateTreeList, List<ArchiveTreeContractVO> dateCovertArchiveTreeContractList, ArchiveTreeContract archiveTreeContract, Long archiveTreeContractId) {
+        if (CollectionUtil.isEmpty(dateTreeList)) {
+            return;
+        }
+        for (TreeVoTwo tree : dateTreeList) {
+            // wbs树转archive树
+            ArchiveTreeContractVO archiveTreeContractVoTemp = dateTreeCovertArchiveTreeContract(tenantId, tree, archiveTreeContract, archiveTreeContractId);
+            dateCovertArchiveTreeContractList.add(archiveTreeContractVoTemp);
+
+            // 循环遍历,转换树对象
+            List<TreeVoTwo> childrenList = tree.getChildren();
+            if (childrenList != null) {
+                getTreeList(tenantId, childrenList, dateCovertArchiveTreeContractList, archiveTreeContractVoTemp, archiveTreeContractId);
+            }
+        }
+    }
+
+    /**
+     * 将时间树初始化为归档树
+     */
+    public ArchiveTreeContractVO dateTreeCovertArchiveTreeContract(String tenantId, TreeVoTwo tree, ArchiveTreeContract archiveTreeContract, Long archiveTreeContractId) {
+        ArchiveTreeContractVO archiveTreeContractVoTemp = new ArchiveTreeContractVO();
+        //设置基本信息
+        archiveTreeContractVoTemp.setId(SnowFlakeUtil.getId());
+        archiveTreeContractVoTemp.setTenantId(tenantId);
+        archiveTreeContractVoTemp.setProjectId(archiveTreeContract.getProjectId());
+        archiveTreeContractVoTemp.setContractId(archiveTreeContract.getContractId());
+        archiveTreeContractVoTemp.setParentId(archiveTreeContract.getId());
+        archiveTreeContractVoTemp.setAncestors(archiveTreeContract.getAncestors()+","+archiveTreeContract.getId());
+        archiveTreeContractVoTemp.setNodeName(tree.getName());
+        archiveTreeContractVoTemp.setNodeType(1);
+        archiveTreeContractVoTemp.setFullName(tree.getHierarchy());
+        archiveTreeContractVoTemp.setTreeCode(archiveTreeContract.getContractId().toString());
+        //设置关联
+        archiveTreeContractVoTemp.setExtAttachId(archiveTreeContractId);
+        archiveTreeContractVoTemp.setSort(archiveTreeContract.getSort());
+        archiveTreeContractVoTemp.setStatus(1);
+        archiveTreeContractVoTemp.setIsDeleted(0);
+        archiveTreeContractVoTemp.setIsStorageNode(1);
+        //时间树不同于wbs树,不清楚的字段值,我沿用同步节点的字段值进行初始化
+        archiveTreeContractVoTemp.setPostType(archiveTreeContract.getPostType());
+        archiveTreeContractVoTemp.setMajorDataType(archiveTreeContract.getMajorDataType());
+        archiveTreeContractVoTemp.setProjectType(archiveTreeContract.getProjectType());
+        archiveTreeContractVoTemp.setStorageType(archiveTreeContract.getStorageType());
+        archiveTreeContractVoTemp.setCreateUser(archiveTreeContract.getCreateUser());
+        archiveTreeContractVoTemp.setCreateDept(archiveTreeContract.getCreateDept());
+        archiveTreeContractVoTemp.setCreateTime(archiveTreeContract.getCreateTime());
+        archiveTreeContractVoTemp.setTreeStructure(TreeStructureEnum.DATE_TREE.getCode());
+        return archiveTreeContractVoTemp;
+    }
+
+    /**
+     * 刷新归档文件
+     */
+    void refreshArchiveFile(List<ArchiveFile> saveArchiveFileList, List<ArchiveFile> updateArchiveFileList,
+                            List<ArchiveFile> deleteArchiveFileList) {
+        if (CollectionUtil.isNotEmpty(saveArchiveFileList)) {
+            archiveFileClient.addArchiveFile(saveArchiveFileList);
+        }
+        if (CollectionUtil.isNotEmpty(updateArchiveFileList)) {
+            archiveFileClient.updateArchiveFile(updateArchiveFileList);
+        }
+        if (CollectionUtil.isNotEmpty(deleteArchiveFileList)) {
+            List<Long> idList = deleteArchiveFileList.stream().map(ArchiveFile::getId).collect(Collectors.toList());
+            archiveFileClient.removeFile(idList);
+        }
+    }
+
+    /**
+     * 现有的树与wbs的树做合并
+     */
+    public void originalAndNewArchiveFileMergeData(List<ArchiveFile> mergeList, Map<Long, ArchiveFile> archiveFileMap) {
+        if (CollectionUtil.isEmpty(mergeList)) {
+            return;
+        }
+        // 循环遍历,将已有的赋值
+        for (ArchiveFile archiveFile : mergeList) {
+            ArchiveFile archiveFileTemp = archiveFileMap.get(archiveFile.getUImageClassificationFileId());
+            if(ObjectUtil.isNotEmpty(archiveFileTemp)){
+                archiveFile.setId(archiveFileTemp.getId());
+                archiveFile.setNodeId(archiveFileTemp.getNodeId());
+            }
+        }
+    }
+
+    /**
+     * 遍历循环,获取差异,并输出三个集合(archiveFile表的uImageClassificationFileId = ImageClassificationFile 的 id)
+     */
+    private static void diffFiles(List<ArchiveFile> originalArchiveFileList, List<ArchiveFile> newArchiveFileList, Map<Long, ArchiveFile> originalArchiveFileTreeMap,
+                                  Map<Long, ArchiveFile> newArchiveFileTreeMap, List<ArchiveFile> insertions, List<ArchiveFile> deletions, List<ArchiveFile> modifications,
+                                  Map<Long, ArchiveTreeContract> newArchiveTreeContractMap, TreeStructureEnum treeStructureEnum) {
+        for (ArchiveFile originalArchiveFile : originalArchiveFileList) {
+            if (!newArchiveFileTreeMap.containsKey(originalArchiveFile.getUImageClassificationFileId())) {
+                deletions.add(originalArchiveFile);
+            } else {
+                ArchiveFile node2 = newArchiveFileTreeMap.get(originalArchiveFile.getUImageClassificationFileId());
+                if (originalArchiveFile.getUImageClassificationFileId().equals(node2.getUImageClassificationFileId())) {
+                    modifications.add(node2);
+                }
+            }
+        }
+        for (ArchiveFile newArchiveFile : newArchiveFileList) {
+            if (!originalArchiveFileTreeMap.containsKey(newArchiveFile.getUImageClassificationFileId())) {
+                ArchiveTreeContract archiveTreeContract = null;
+                if(treeStructureEnum.getCode() == TreeStructureEnum.ORDINARY_TREE.getCode()){
+                    archiveTreeContract = newArchiveTreeContractMap.get(newArchiveFile.getMWbsTreeContractPKeyId());
+                } else if(treeStructureEnum.getCode() == TreeStructureEnum.DATE_TREE.getCode()) {
+                    archiveTreeContract = newArchiveTreeContractMap.get(Long.valueOf(newArchiveFile.getDateName()));
+                }
+                if(ObjectUtil.isNotEmpty(archiveTreeContract)){
+                    newArchiveFile.setNodeId(String.valueOf(archiveTreeContract.getId()));
+                }
+                insertions.add(newArchiveFile);
+            }
+        }
+    }
+
+    /**
+     * 构建节点map
+     */
+    private static Map<Long, ArchiveFile> buildArchiveFileMap(List<ArchiveFile> archiveFileList) {
+        Map<Long, ArchiveFile> map = new HashMap<>();
+        for (ArchiveFile archiveFile : archiveFileList) {
+            map.put(archiveFile.getUImageClassificationFileId(), archiveFile);
+        }
+        return map;
+    }
+
+    /**
+     * imageClassificationFile 转换为 archiveFile
+     */
+    private ArchiveFile imageClassificationFileCovertArchiveFile(ImageClassificationFileDTO file,
+                                                                 ArchiveTreeContract node,
+                                                                 Map<Long, ArchiveTreeContract> wbsCovertArchiveTreeContractMap)
+    {
+        ArchiveFile archiveFile = new ArchiveFile();
+        archiveFile.setId(SnowFlakeUtil.getId());
+        archiveFile.setProjectId(String.valueOf(node.getProjectId()));
+        archiveFile.setContractId(String.valueOf(node.getContractId()));
+        ArchiveTreeContract archiveTreeContractTemp = wbsCovertArchiveTreeContractMap.get(Long.valueOf(file.getWbsId()));
+        if(archiveTreeContractTemp != null){
+            archiveFile.setNodeId(String.valueOf(archiveTreeContractTemp.getId()));
+        }
+        archiveFile.setFileNumber(String.valueOf(file.getId()));
+        archiveFile.setFileName(file.getTitle());
+        archiveFile.setFileTime(file.getUploadTime());
+        archiveFile.setFileUrl(file.getImageUrl());
+        // 根据type判断,设置pdfUrl, 1=视频格式,取原本的视频地址就好,2=图片格式,就取合并后的pdfUrl
+        if(file.getType() == 1){
+            archiveFile.setPdfFileUrl(file.getImageUrl());
+        }else{
+            archiveFile.setPdfFileUrl(StringUtils.isNotEmpty(file.getMargePdfUrl()) ? file.getMargePdfUrl() : file.getPdfUrl());
+        }
+        archiveFile.setFilePage(file.getFilePage());
+        archiveFile.setIsApproval(0);
+        archiveFile.setIsCertification(1);
+        archiveFile.setIsNeedCertification(0);
+        archiveFile.setDutyUser(file.getShootingUser());
+        archiveFile.setCreateUser(file.getCreateUser());
+        archiveFile.setCreateDept(file.getCreateDept());
+        archiveFile.setCreateTime(file.getCreateTime());
+        archiveFile.setUpdateUser(file.getUpdateUser());
+        archiveFile.setUpdateTime(file.getUpdateTime());
+        archiveFile.setStatus(0);
+        archiveFile.setIsDeleted(0);
+        archiveFile.setFileType(Long.valueOf(file.getType()));
+        archiveFile.setFilmingTime(file.getShootingTime());
+        archiveFile.setFilmingorTime(file.getShootingTime());
+        archiveFile.setPicCode(file.getPhotoCode());
+        archiveFile.setFilmCode(file.getFilmCode());
+        archiveFile.setReferCode(file.getSeeAlsoCode());
+        archiveFile.setWidth(file.getFileWidth());
+        archiveFile.setHeight(file.getFileHeight());
+        // 创建 LocalDateTime 时间爱你
+        LocalDateTime localDateTime = new Date().toInstant()
+                .atZone(ZoneId.systemDefault())
+                .toLocalDateTime();
+        archiveFile.setFtime(localDateTime);
+        archiveFile.setUtime(localDateTime);
+        archiveFile.setSort(0);
+        archiveFile.setPageNum(String.valueOf(file.getFilePage()));
+        archiveFile.setFileSize(convertSizeToBytes(file.getFileSize()));
+        archiveFile.setSourceType(1);
+        archiveFile.setIsElement(0);
+        archiveFile.setRectification(0);
+        archiveFile.setMWbsTreeContractPKeyId(file.getWbsId());
+        archiveFile.setUImageClassificationFileId(file.getId());
+        archiveFile.setArchiveFileStorageType(StorageTypeEnum.SOUND_IMAGE.getCode());
+        archiveFile.setNodeTreeStructure(file.getStorageDirectoryFormat());
+        archiveFile.setDateName(DateFormatUtils.format(file.getCreateTime(), "yyyyMMdd"));
+        return archiveFile;
+    }
+
+    /**
+     * imageClassificationFile里面的fileSize,是已经转换过的(230.6MB),是字符串
+     * 而archiveFile里面的fileSize是纯数字大小,现在要转换回来
+     */
+    private static Long convertSizeToBytes(String size) {
+        Long value = Double.valueOf(size.replaceAll("[^\\d.]", "")).longValue();
+        String unit = size.replaceAll("[\\d.]", "").trim();
+        switch (unit) {
+            case "B":
+                return value;
+            case "KB":
+                return value * 1024;
+            case "MB":
+                return value * 1024 * 1024;
+            case "GB":
+                return value * 1024 * 1024 * 1024;
+            default:
+                throw new IllegalArgumentException("Invalid size unit: " + unit);
+        }
+    }
+
+    /**
+     * 重新刷新整个树结构
+     */
+    public void refreshTreeStructure(ArchiveTreeContractVO archiveTreeContractVO, Long id, String ancestors) {
+        archiveTreeContractVO.setParentId(id);
+        archiveTreeContractVO.setAncestors(ancestors+","+id);
+        List<ArchiveTreeContractVO> children = archiveTreeContractVO.getChildren();
+        if (CollectionUtil.isNotEmpty(children)) {
+            for (ArchiveTreeContractVO child : children) {
+                refreshTreeStructure(child, archiveTreeContractVO.getId(), archiveTreeContractVO.getAncestors());
+            }
+        }
+    }
+
+    /**
+     * 现有的树与老的树做合并
+     */
+    public void archiveAndOldTreeMergeData(List<ArchiveTreeContractVO> mergeList, Map<Long, ArchiveTreeContract> archiveTreeContractMap, TreeStructureEnum treeStructureEnum) {
+        if (CollectionUtil.isEmpty(mergeList)) {
+            return;
+        }
+        // 循环遍历,将已有的赋值,随后再次将整个树的上下节点,重新刷新
+        for (ArchiveTreeContractVO archiveTreeContractVO : mergeList) {
+            ArchiveTreeContract archiveTreeContract = null;
+            if(treeStructureEnum.getCode() == TreeStructureEnum.ORDINARY_TREE.getCode()){
+                archiveTreeContract = archiveTreeContractMap.get(archiveTreeContractVO.getExtKeyId());
+            } else if(treeStructureEnum.getCode() == TreeStructureEnum.DATE_TREE.getCode()) {
+                archiveTreeContract = archiveTreeContractMap.get(Long.valueOf(archiveTreeContractVO.getFullName()));
+            }
+            if(ObjectUtil.isNotEmpty(archiveTreeContract)){
+                archiveTreeContractVO.setId(archiveTreeContract.getId());
+                archiveTreeContractVO.setParentId(archiveTreeContract.getParentId());
+                archiveTreeContractVO.setAncestors(archiveTreeContract.getAncestors());
+            }
+            archiveAndOldTreeMergeData(archiveTreeContractVO.getChildren(), archiveTreeContractMap, treeStructureEnum);
+        }
+    }
+
+    /**
+     * 刷新归档树
+     */
+    void refreshArchiveTreeContract(List<ArchiveTreeContract> saveArchiveTreeContractList, List<Map> updateArchiveTreeContractList,
+                                    List<ArchiveTreeContract> deleteArchiveTreeContractList) {
+        if (CollectionUtil.isNotEmpty(saveArchiveTreeContractList)) {
+            archiveTreeContractMapper.batchInsertArchiveTreeContract(saveArchiveTreeContractList);
+        }
+        if (CollectionUtil.isNotEmpty(updateArchiveTreeContractList)) {
+            archiveTreeContractMapper.batchUpdateArchiveTreeContract(updateArchiveTreeContractList);
+        }
+        if (CollectionUtil.isNotEmpty(deleteArchiveTreeContractList)) {
+            List<Long> idList = deleteArchiveTreeContractList.stream().map(ArchiveTreeContract::getId).collect(Collectors.toList());
+            archiveTreeContractMapper.batchDeleteArchiveTreeContractByIdList(idList, 1);
+        }
+    }
+
+    /**
+     * 根据合同段id查询声像文件
+     */
+    public List<ImageClassificationFileDTO> getImageClassificationFileListByContractId(Long contractId) {
+        return imageClassificationFileClient.getImageClassificationFileListByContractId(contractId);
+    }
+
+    /**
+     * 遍历循环,获取差异,并输出三个集合(归档树的ExtKeyId = Wbs树的pKeyId)
+     */
+    private static void diffTrees(List<ArchiveTreeContract> originalArchiveTreeContractList, List<ArchiveTreeContract> newArchiveTreeContractList, Map<Long, ArchiveTreeContract> originalArchiveTreeContractMap,
+                                  Map<Long, ArchiveTreeContract> newArchiveTreeContractMap, List<ArchiveTreeContract> insertions,
+                                  List<ArchiveTreeContract> deletions, List<Map> modifications) {
+        for (ArchiveTreeContract originalArchiveTreeContract : originalArchiveTreeContractList) {
+            if (!newArchiveTreeContractMap.containsKey(originalArchiveTreeContract.getExtKeyId())) {
+                deletions.add(originalArchiveTreeContract);
+            } else {
+                ArchiveTreeContract node2 = newArchiveTreeContractMap.get(originalArchiveTreeContract.getExtKeyId());
+                if (originalArchiveTreeContract.getExtKeyId().equals(node2.getExtKeyId())) {
+                    // 更新修改数据
+                    Map map = new HashMap();
+                    map.put("id", originalArchiveTreeContract.getId());
+                    map.put("parentId", node2.getParentId());
+                    map.put("ancestors", node2.getAncestors());
+                    map.put("nodeName", node2.getNodeName());
+                    map.put("nodeType", node2.getNodeType());
+                    map.put("fullName", node2.getFullName());
+                    map.put("treeStructure", node2.getTreeStructure());
+                    modifications.add(map);
+                }
+            }
+        }
+        for (ArchiveTreeContract newArchiveTreeContract : newArchiveTreeContractList) {
+            if (!originalArchiveTreeContractMap.containsKey(newArchiveTreeContract.getExtKeyId())) {
+                insertions.add(newArchiveTreeContract);
+            }
+        }
+    }
+
+    /**
+     * 构建节点map
+     */
+    private static Map<Long, ArchiveTreeContract> buildNodeMap(List<ArchiveTreeContract> tree) {
+        Map<Long, ArchiveTreeContract> map = new HashMap<>();
+        for (ArchiveTreeContract node : tree) {
+            map.put(node.getExtKeyId(), node);
+        }
+        return map;
+    }
+
+    /**
+     * 构建时间map
+     */
+    private static Map<Long, ArchiveTreeContract> buildDateMap(List<ArchiveTreeContract> tree) {
+        Map<Long, ArchiveTreeContract> map = new HashMap<>();
+        for (ArchiveTreeContract node : tree) {
+            // 时间树中,fullName表示该节点的详细时间,如20240109,唯一性
+            map.put(Long.valueOf(node.getFullName()), node);
+        }
+        return map;
+    }
 
 }

+ 19 - 14
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ContractInfoServiceImpl.java

@@ -976,20 +976,25 @@ public class ContractInfoServiceImpl extends BaseServiceImpl<ContractInfoMapper,
                 projectContractArea.setProjectId(contractInfo.getPId());
                 projectContractArea.setIsDeleted(0);
                 projectContractArea.setContractId(String.valueOf(contractInfo.getId()));
-                QueryWrapper<ProjectContractArea> queryWrapper = new QueryWrapper<>();
-                queryWrapper.eq("project_id", contractInfo.getPId());
-                queryWrapper.eq("contract_id", contractInfo.getId());
-                queryWrapper.eq("province", position.get("province"));
-                queryWrapper.eq("city", position.get("city"));
-                queryWrapper.eq("county", position.get("district"));
-                queryWrapper.eq("city_code", position.get("adcode"));
-                queryWrapper.eq("is_deleted", contractInfo.getIsDeleted());
-                ProjectContractArea projectContractArea1 = projectContractAreaMapper.selectOne(queryWrapper);
-                if (projectContractArea1 == null) {
-                    int res = projectContractAreaMapper.insert(projectContractArea);
-                    return res > 0;
-                }
-                return true;
+//                QueryWrapper<ProjectContractArea> queryWrapper = new QueryWrapper<>();
+//                queryWrapper.eq("project_id", contractInfo.getPId());
+//                queryWrapper.eq("contract_id", contractInfo.getId());
+//                queryWrapper.eq("province", position.get("province"));
+//                queryWrapper.eq("city", position.get("city"));
+//                queryWrapper.eq("county", position.get("district"));
+//                queryWrapper.eq("city_code", position.get("adcode"));
+//                queryWrapper.eq("is_deleted", contractInfo.getIsDeleted());
+//                ProjectContractArea projectContractArea1 = projectContractAreaMapper.selectOne(queryWrapper);
+//                if (projectContractArea1 == null) {
+//                    int res = projectContractAreaMapper.insert(projectContractArea);
+//                    return res > 0;
+//                }
+                //删除之前所有的地址,再新增
+                projectContractAreaMapper.delete(new LambdaQueryWrapper<ProjectContractArea>()
+                    .eq(ProjectContractArea::getContractId,projectContractArea.getContractId()));
+                int res = projectContractAreaMapper.insert(projectContractArea);
+                return res > 0;
+//                return true;
             } catch (IOException e) {
                 throw new ServiceException("施工台账初始化失败");
             }

+ 1 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ExcelTabServiceImpl.java

@@ -398,6 +398,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
     }
 
 
+
     @Override
     public void formulaFillData2(JSONArray dataArray, ExecuteType type) {
         try {

+ 11 - 7
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/FormulaDaoImpl.java

@@ -50,7 +50,7 @@ public class FormulaDaoImpl implements IFormulaDao {
     @Override
     public Function<Long,MeterPeriodInfo> getMeterPeriodFc(){
         return id-> {
-           List<MeterPeriodInfo> beans = this.jdbcTemplate.query("select b.period_number periodNumber,b.period_name periodName,a.print_date formPrintDate ,a.repayment_cause cause from s_material_start_statement a join  s_meter_period b on a.meter_period_id=b.id where a.id="+id, new BeanPropertyRowMapper<>(MeterPeriodInfo.class));
+           List<MeterPeriodInfo> beans = this.jdbcTemplate.query("select b.id,b.period_number periodNumber,b.period_name periodName,a.print_date formPrintDate ,a.repayment_cause cause from s_material_start_statement a join  s_meter_period b on a.meter_period_id=b.id where a.id="+id, new BeanPropertyRowMapper<>(MeterPeriodInfo.class));
             if(beans.size()>0){
                 MeterPeriodInfo bean = beans.get(0);
                 List<Map<String,Object>> listMap= this.jdbcTemplate.queryForList("select sum(b.current_amount) currentAmount from s_meter_period a join s_meter_period b on (a.contract_id=b.contract_id and a.type=b.type) where a.id="+id+" and b.id<>"+id);
@@ -69,11 +69,8 @@ public class FormulaDaoImpl implements IFormulaDao {
     @Override
     public Function<Long, List<Material>> getMaterialFormFc() {
         return id->{
-            List<Map<String,Object>> mapList = this.jdbcTemplate.queryForList("select  b.material_name name,b.unit,b.price,a.meter_amount amount,meter_money sum,a.material_source source,material_conform  materialConform,a.storage_place storagePlace,a.storage_status storageStatus,a.storage_conform storageConform,a.remark from s_material_meter_form a left join s_contract_material b on a.contract_material_id = b.id where a.meter_period_id="+id+"  and a.is_deleted=0");
-            if(Func.isNotEmpty(mapList)){
-               return mapList.stream().map(m->BeanUtil.toBean(m,Material.class)).collect(Collectors.toList());
-            }
-            return Collections.emptyList();
+            String sql="select  b.material_name name,b.unit,b.price,a.meter_amount amount,meter_money sum,a.material_source source,material_conform  materialConform,a.storage_place storagePlace,a.storage_status storageStatus,a.storage_conform storageConform,a.remark from s_material_meter_form a left join s_contract_material b on a.contract_material_id = b.id where a.meter_period_id="+id+"  and a.is_deleted=0";
+            return  getEntityList(sql,Material.class);
         };
     }
 
@@ -90,7 +87,7 @@ public class FormulaDaoImpl implements IFormulaDao {
     @Override
     public Function<Long, List<Payment>> getPaymentListFc() {
         return contractId->{
-            String paySql="select a.form_number number,a.form_name name ,a.current_meter_total completed ,current_meter_money money ,contract_meter_id meterId,b.id formId,b.chapter_number chapter, b.contract_money contractMoney,b.change_money,b.unit,contract_total ,change_total,c.sort,c.id periodId from s_inventory_form_apply a join s_contract_inventory_form b on a.contract_form_id=b.id join s_contract_meter_period c on a.contract_period_id=c.id where a.is_deleted=0 and a.approve_status=2 and a.contract_id="+contractId;
+            String paySql="select a.form_number number,a.form_name name ,a.middle_meter_id,a.current_meter_total completed ,a.current_price price,current_meter_money money,meter_number meterNumber ,contract_meter_id meterId,b.id formId,b.chapter_number chapter, b.contract_money contractMoney,b.change_money,b.unit,contract_total ,change_total,c.sort,c.id periodId from s_inventory_form_apply a join s_contract_inventory_form b on a.contract_form_id=b.id join s_contract_meter_period c on a.contract_period_id=c.id where a.is_deleted=0 and a.approve_status=2 and a.contract_id="+contractId;
             return this.jdbcTemplate.query(paySql,new BeanPropertyRowMapper<>(Payment.class));
         };
     }
@@ -111,6 +108,13 @@ public class FormulaDaoImpl implements IFormulaDao {
         };
     }
 
+    @Override
+    public Function<Long, List<MeterApply>> getMeterApplyFc() {
+        return   MeterPeriodId->{
+            String sql="select a.id,contract_unit_id,contract_period_id,period_number,meter_number,engineer_divide,part_name,certificate_number,meter_money,change_token_number,picture_url,picture_name,calculate_formula ,b.contract_picture from  s_middle_meter_apply a join s_meter_tree_contract b on a.contract_unit_id= b.id where a.contract_period_id="+MeterPeriodId;
+            return getEntityList(sql,MeterApply.class);
+        };
+    }
 
     public <T> List<T> getEntityList(String sql, Class<T> entityClass) {
         return  jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(entityClass));

+ 78 - 9
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/FormulaServiceImpl.java

@@ -37,6 +37,7 @@ import org.springblade.manager.mapper.WbsTreePrivateMapper;
 import org.springblade.manager.service.*;
 import org.springblade.manager.utils.FileUtils;
 import org.springblade.manager.vo.*;
+import org.springframework.beans.BeanUtils;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
@@ -324,9 +325,21 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
                                         FormulaUtils.write(t.getJudge(), "合格", false);
                                     }
 //                                    itemBlockList.sort(Comparator.comparingInt(a -> ids.indexOf(a.getPkeyId())));
-                                    List<String> values = itemBlockList.stream().map(ItemBlock::getData).flatMap(v -> v.stream().flatMap(Collection::stream)).map(Object::toString).collect(Collectors.toList());
+                                    List<String> values;
+                                    Predicate<String> predicate=s->Pattern.matches("^[,]*$",s);
+                                    if(itemBlockList.stream().map(ItemBlock::getData).flatMap(v -> v.stream().flatMap(Collection::stream)).map(Func::toStr).anyMatch(CustomFunction::containsZH)) {
+                                        values=itemBlockList.stream().map(ItemBlock::getData).flatMap(v->v.stream().map(l->l.stream().map(Func::toStr).collect(Collectors.joining(","))).filter(predicate.negate()).distinct().flatMap(s-> Arrays.stream(s.split(",")))).collect(Collectors.toList());
+                                    }else{
+                                        values=  itemBlockList.stream().map(ItemBlock::getData).flatMap(v -> v.stream().flatMap(Collection::stream)).map(Func::toStr).collect(Collectors.toList());
+                                    }
+
                                     int scale = StringUtils.getScale(values);
-                                    FormulaUtils.write(t.getValue(), values.stream().map(u -> StringUtils.number2String(u, scale)).collect(Collectors.toList()), true);
+                                    FormulaUtils.write(t.getValue(), values.stream().filter(StringUtils::isNotEmpty).map(u -> {
+                                              if(StringUtils.isNumber(u)){
+                                                  return  StringUtils.number2String(u, scale);
+                                              }  return u;
+                                            }
+                                    ).collect(Collectors.toList()), true);
 //                                   if(t.getValue().getEName().contains("±")){
 //                                       /*存在偏差范围则获取的是偏差值:实测-设计*/
 //                                       FormulaUtils.write(t.getValue(),itemBlockList.stream().flatMap(b->{
@@ -1385,9 +1398,10 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
                             AtomicInteger index = new AtomicInteger();
                             if (targetItem.getDesigns()!=null&&targetItem.getDesigns().size()>0) {
                                 int len = targetItem.getDesigns().size();
-                                targetItem.setData(new ArrayList<>(list.stream().map(StringUtils::obj2Double).collect(Collectors.groupingBy(k -> index.getAndIncrement() / len, LinkedHashMap::new, Collectors.toList())).values()));
+                                targetItem.setData(new ArrayList<>(list.stream().collect(Collectors.groupingBy(k -> index.getAndIncrement() / len, LinkedHashMap::new, Collectors.toList())).values()));
                             } else {
-                                targetItem.setData(Collections.singletonList(list.stream().map(StringUtils::obj2Double).collect(Collectors.toList())));
+                                /*targetItem.setData(Collections.singletonList(list.stream().map(StringUtils::obj2Double).collect(Collectors.toList())));*/
+                                targetItem.setData(Collections.singletonList(list));
                             }
                             if (pass != null && !pass.empty()) {
                                 targetItem.setSubPass((int) Math.round(list.size() * StringUtils.obj2Double(pass.getValues().get(0).getValue()) / 100));
@@ -1696,6 +1710,58 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
         return Collections.emptyList();
     }
 
+    @Override
+    public R<Object> formulasForNode(Long pkeyId ,String tableName,String elementName) {
+            long start = System.currentTimeMillis();
+            WbsTreeContract wtc = this.wbsTreeContractService.getOne(Wrappers.<WbsTreeContract> lambdaQuery().eq(WbsTreeContract::getPKeyId,pkeyId));
+            if(wtc!=null) {
+                System.out.println("A0耗时"+(System.currentTimeMillis()-start));
+                List<Map<String, Object>> listMap = this.jdbcTemplate.queryForList("select init_table_name number ,node_name nodeName ,b.tab_ch_name tableName from m_wbs_tree_contract a  join m_table_info b on a.init_table_name = b.tab_en_name where parent_id =?  and a.is_deleted=0 and contract_id = ?", wtc.getId(), wtc.getContractId());
+                Predicate<Map<String, Object>> tableNameFilter = map ->true;
+                if(Func.isNotEmpty(tableName)){
+                    tableNameFilter=map->map.get("nodeName").toString().contains(tableName);
+                }
+                Map<String, Map<String, Object>> tablesMap = listMap.stream().filter(tableNameFilter).collect(Collectors.toMap(m -> m.get("number").toString(), m -> m, (m1, m2) -> m1));
+                System.out.println("A1耗时"+(System.currentTimeMillis()-start));
+                if(tablesMap.size()>0) {
+                    String tableNames = String.join("','", tablesMap.keySet());
+                    List<FormData> processFds = this.createFormDataByTableName(tableNames);
+                    System.out.println("B耗时"+(System.currentTimeMillis()-start));
+                    if(processFds.size()>0) {
+                        Predicate<FormData> elementFilter = ele ->true;
+                        if(Func.isNotEmpty(elementName)){
+                            elementFilter=ele->ele.getEName().contains(elementName);
+                        }
+                        Map<String,String> dictMap = processFds.stream().collect(Collectors.toMap(FormData::getCode,fd->fd.getTableChName()+":"+fd.getEName(),(p,n)->p));
+                        processFds=processFds.stream().filter(elementFilter).collect(Collectors.toList());
+                        this.formulaInto(processFds, wtc.getProjectId(), String.valueOf(pkeyId), ExecuteType.INSPECTION);
+                        System.out.println("C耗时"+(System.currentTimeMillis()-start));
+                        Map<String, List<Map<String, Object>>> fdGroup = processFds.stream()
+                                .filter(FormData::executable)
+                                .collect(Collectors.groupingBy(
+                                        FormData::getTableChName,
+                                        Collectors.mapping(formData -> {
+                                                    Map<String, Object> show = new HashMap<>();
+                                                    FormulaVo vo= new FormulaVo();
+                                                    BeanUtils.copyProperties(formData.getFormula(),vo);
+                                                    vo.setFormulaParse(dictMap);
+                                                    show.put("formula", vo);
+                                                    show.put("name", formData.getEName());
+                                                    show.put("code",formData.getCode());
+                                                    return show;
+                                                },
+                                                Collectors.toList()
+                                        )));
+                        System.out.println("D耗时"+(System.currentTimeMillis()-start));
+                        return R.data(fdGroup);
+                    }
+                    return R.data("元素不存在");
+                }
+                return R.data("表不存在");
+            }
+            return R.data("节点不存在");
+    }
+
     @Override
     public void formulaInto(List<FormData> curFormDatas, String projectId, String nodeId, ExecuteType executeType) {
         if(executeType.equals(ExecuteType.INSPECTION)){
@@ -1793,7 +1859,8 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
        }
        return this.wbsTreePrivateMapper.selectOne(Wrappers.<WbsTreePrivate>lambdaQuery().eq(WbsTreePrivate::getId,wtc.getId()).eq(WbsTreePrivate::getProjectId,wtc.getProjectId()));
     }
-    private List<KeyMapper> listForContract(List<Long> ids,String projectId,String nodeId){
+    /*表id,项目id,节点Id*/
+    public List<KeyMapper> listForContract(List<Long> ids,String projectId,String nodeId){
         List<Map<String,Object>> listMap = listMap(ids,ExecuteType.INSPECTION);
         WbsTreePrivate wtp =wtpId(Long.parseLong(nodeId));
         if(Func.isNotEmpty(listMap)){
@@ -2549,13 +2616,14 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
     }
 
     @Override
-    public  List<ReportResult> execute3(Long contractId,Long periodId ,Integer type) {
+    /*合同段id,报表Id,报表类型中间计量0,材料1,开工2*/
+    public  List<ReportResult> execute3(Long contractId,Long reportId ,Integer type) {
         ContractInfo contractInfo = this.contractInfoService.getById(contractId);
         MeterType meterType = MeterType.getByIndex(type);
 
         /*元素创建*/
-        Map<String,String> parent=  getWtpParent(meterType.name(),contractInfo.getPId());
-        List<NodeTable> tableList=  this.getSqlList("select p_key_id pkeyId, node_name nodeName ,init_table_name initTableName,html_url htmlUrl  from  m_wbs_tree_private where ancestors like ? and LENGTH(html_url)>0 and is_deleted=0 and project_id=? and wbs_id=?",NodeTable.class,parent.get("path")+"%",contractInfo.getPId(),parent.get("wbsId"));
+        Map<String,String> parent=  getWtpParent(meterType.getName(),contractInfo.getPId());
+        List<NodeTable> tableList=  this.getSqlList("select p_key_id pkeyId, node_name nodeName ,init_table_name initTableName,html_url htmlUrl ,excel_id excelId from  m_wbs_tree_private where ancestors like ? and LENGTH(html_url)>0 and is_deleted=0 and project_id=? and wbs_id=?",NodeTable.class,parent.get("path")+"%",contractInfo.getPId(),parent.get("wbsId"));
         Map<String,Map<String,String>> coordinateMap=tableList.stream().collect(Collectors.toMap(NodeTable::getInitTableName,m->FormulaUtils.getElementCell(m.getHtmlUrl()),(v1,v2)->v2));
         List<FormData> processFds = this.createFormDataByTableName(tableList.stream().map(NodeTable::getInitTableName).collect(Collectors.joining("','")));
         listForMeter(processFds,contractInfo.getPId(),parent.get("id"));
@@ -2564,7 +2632,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
         TableElementConverter tec = new TableElementConverter(processFds,coordinateMap,tableList);
         tec.setProjectId(Long.parseLong(contractInfo.getPId()));
         tec.setContractId(contractInfo.getId());
-        tec.setPeriodId(periodId);
+        tec.setReportId(reportId);
         tec.setLog(new FormulaLog());
         tec.setMeterType(meterType);
         tec.before();
@@ -2584,6 +2652,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
         special.setMaterialFormFc(this.formulaDao.getMaterialFormFc());
         special.setInterimMeterPeriodFc(this.formulaDao.getInterimMeterPeriodFc());
         special.setPaymentListFc(this.formulaDao.getPaymentListFc());
+        special.setMeterApplyFc(this.formulaDao.getMeterApplyFc());
         formulaHandleChains.add(special);
         /*通用计算*/
         formulaHandleChains.add(new ExecutorCalc(tec));

+ 106 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/TrialSummaryClassificationConfigurationServiceImpl.java

@@ -1,14 +1,27 @@
 package org.springblade.manager.service.impl;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.AllArgsConstructor;
+import org.springblade.common.utils.SnowFlakeUtil;
 import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.core.tool.utils.BeanUtil;
+import org.springblade.core.tool.utils.ObjectUtil;
+import org.springblade.manager.dto.TrialSummaryClassificationConfigurationMatchDTO;
+import org.springblade.manager.dto.TrialSummaryClassificationConfigurationRelevancyDTO;
 import org.springblade.manager.entity.TrialSummaryClassificationConfiguration;
+import org.springblade.manager.entity.WbsTreePrivate;
 import org.springblade.manager.mapper.TrialSummaryClassificationConfigurationMapper;
 import org.springblade.manager.service.ITrialSummaryClassificationConfigurationService;
+import org.springblade.manager.vo.TrialTreeVO;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 @Service
 @AllArgsConstructor
@@ -16,6 +29,9 @@ public class TrialSummaryClassificationConfigurationServiceImpl
         extends BaseServiceImpl<TrialSummaryClassificationConfigurationMapper, TrialSummaryClassificationConfiguration>
         implements ITrialSummaryClassificationConfigurationService {
 
+    private final JdbcTemplate jdbcTemplate;
+    private final WbsTreePrivateServiceImpl wbsTreePrivateServiceImpl;
+
     @Override
     public TrialSummaryClassificationConfiguration detail(Long id) {
         return baseMapper.selectById(id);
@@ -32,4 +48,94 @@ public class TrialSummaryClassificationConfigurationServiceImpl
         return page.setRecords(data);
     }
 
+    @Override
+    public boolean matching(TrialSummaryClassificationConfigurationMatchDTO dto) {
+        if (ObjectUtil.isNotEmpty(dto.getIds()) && ObjectUtil.isNotEmpty(dto.getClassId()) && ObjectUtil.isNotEmpty(dto.getProjectId())) {
+            String delSql = "DELETE FROM m_trial_summary_class_config_record WHERE type = ? AND class_id = ?";
+            Object[] delParams = {1, dto.getClassId(),};
+            jdbcTemplate.update(delSql, delParams);
+
+            for (String pKeyId : dto.getIds().split(",")) {
+                String insertSql = "INSERT INTO m_trial_summary_class_config_record(id,type,project_id,class_id,p_key_id) VALUES (?,?,?,?,?)";
+                Object[] insertParams = {
+                        SnowFlakeUtil.getId(),
+                        1,
+                        dto.getProjectId(),
+                        dto.getClassId(),
+                        pKeyId
+                };
+                jdbcTemplate.update(insertSql, insertParams);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean relevancy(TrialSummaryClassificationConfigurationRelevancyDTO dto) {
+        if (ObjectUtil.isNotEmpty(dto.getIds()) && ObjectUtil.isNotEmpty(dto.getClassId()) && ObjectUtil.isNotEmpty(dto.getProjectId())) {
+            String delSql = "DELETE FROM m_trial_summary_class_config_record WHERE type = ? AND class_id = ?";
+            Object[] delParams = {2, dto.getClassId(),};
+            jdbcTemplate.update(delSql, delParams);
+
+            for (String pKeyId : dto.getIds().split(",")) {
+                String sql = "INSERT INTO m_trial_summary_class_config_record(id,type,project_id,class_id,p_key_id) VALUES (?,?,?,?,?)";
+                Object[] insertParams = {
+                        SnowFlakeUtil.getId(),
+                        2,
+                        dto.getProjectId(),
+                        dto.getClassId(),
+                        pKeyId
+                };
+                jdbcTemplate.update(sql, insertParams);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public TrialTreeVO tree(String projectId, String classId, String type) {
+        List<WbsTreePrivate> wbsTreePrivatesNodes = wbsTreePrivateServiceImpl.getBaseMapper().selectList(Wrappers.<WbsTreePrivate>lambdaQuery()
+                .select(WbsTreePrivate::getId, WbsTreePrivate::getPKeyId, WbsTreePrivate::getParentId, WbsTreePrivate::getNodeName)
+                .eq(WbsTreePrivate::getProjectId, projectId)
+                .eq(WbsTreePrivate::getWbsType, 2)
+                .eq(WbsTreePrivate::getType, 1)
+                .eq(WbsTreePrivate::getStatus, 1)
+        );
+        return buildTree(wbsTreePrivatesNodes, projectId, classId, type);
+    }
+
+    private TrialTreeVO buildTree(List<WbsTreePrivate> wbsTreePrivatesNodes, String projectId, String classId, String type) {
+        List<TrialTreeVO> trialTreeVOS = BeanUtil.copyProperties(wbsTreePrivatesNodes, TrialTreeVO.class);
+        if (!trialTreeVOS.isEmpty()) {
+            String sql = "SELECT p_key_id FROM m_trial_summary_class_config_record WHERE class_id = ? AND project_id = ? AND type = ?";
+            Object[] params = {classId, projectId, type};
+            List<TrialTreeVO> query = jdbcTemplate.query(sql, params, new BeanPropertyRowMapper<>(TrialTreeVO.class));
+            Map<Long, TrialTreeVO> statusMap = query.stream().collect(Collectors.toMap(TrialTreeVO::getPKeyId, Function.identity()));
+
+            Map<Long, List<TrialTreeVO>> map = trialTreeVOS.stream().collect(Collectors.groupingBy(TrialTreeVO::getParentId));
+            List<TrialTreeVO> list = trialTreeVOS.stream().filter(f -> f.getParentId() == 0L).collect(Collectors.toList());
+            buildChildNodes(list, map, statusMap);
+            return trialTreeVOS.get(0);
+        }
+        return null;
+    }
+
+    private void buildChildNodes(List<TrialTreeVO> list, Map<Long, List<TrialTreeVO>> map, Map<Long, TrialTreeVO> statusMap) {
+        for (TrialTreeVO trialTreeVO : list) {
+            TrialTreeVO orDefault = statusMap.getOrDefault(trialTreeVO.getPKeyId(), null);
+            if (orDefault != null) {
+                trialTreeVO.setStatus(1);
+            } else {
+                trialTreeVO.setStatus(0);
+            }
+            List<TrialTreeVO> childrenList = map.getOrDefault(trialTreeVO.getId(), null);
+            if (childrenList != null && childrenList.size() > 0) {
+                trialTreeVO.setChildNode(childrenList);
+                trialTreeVO.setHasChild(true);
+                buildChildNodes(childrenList, map, statusMap);
+            }
+        }
+    }
 }

+ 24 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsParamServiceImpl.java

@@ -19,6 +19,7 @@ import org.springblade.manager.mapper.WbsParamMapper;
 import org.springblade.manager.mapper.WbsTreePrivateMapper;
 import org.springblade.manager.service.*;
 import org.springframework.cache.annotation.Cacheable;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -64,10 +65,18 @@ public class WbsParamServiceImpl extends BaseServiceImpl<WbsParamMapper, WbsPara
                     }
                     if (Func.isNotEmpty(wp)) {
                         return CustomFunction.tree(nodes.stream().map(w->StringUtils.isNotEmpty(w.getFullName())?w.getFullName():w.getNodeName()).collect(Collectors.toList()), wp.getV()).toString();
+                    }else{
+                        System.out.println(wtp.getNodeName()+"找不到文件题名配置");
                     }
+                }else{
+                    return "找不到项目配置信息";
                 }
 
+            }else{
+                return "层级断链";
             }
+        }else {
+            return "找不到节点";
         }
         return NOT_SET;
     }
@@ -110,6 +119,21 @@ public class WbsParamServiceImpl extends BaseServiceImpl<WbsParamMapper, WbsPara
     @Override
     public String createFileTitle(Long pkeyId) {
         WbsTreeContract wtc =this.treeContractService.getOne(Wrappers.<WbsTreeContract>lambdaQuery().eq(WbsTreeContract::getPKeyId,pkeyId));
+        int loop=10;
+        do{
+            try {
+                WbsTreeContract  tmp = this.jdbcTemplate.queryForObject("select p_key_id, id,old_id,contract_id from m_wbs_tree_contract where id="+wtc.getOldId()+" and contract_id ="+wtc.getContractId()+" limit 1",new BeanPropertyRowMapper<>(WbsTreeContract.class));
+                if(tmp==null){
+                    loop=0;
+                }else{
+                    wtc=tmp;
+                    loop--;
+                }
+            }catch (Exception e){
+                loop=0;
+                e.printStackTrace();
+            }
+        }while (loop>0);
         return createFileTitle(wtc);
     }
 

+ 14 - 3
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreePrivateServiceImpl.java

@@ -1936,18 +1936,29 @@ public class WbsTreePrivateServiceImpl extends BaseServiceImpl<WbsTreePrivateMap
     public void batchResetHtmlUrl(List<WbsTreePrivate> wbsTreePrivateList) throws IOException, InterruptedException {
         //Thread.sleep(10000L);
         if (wbsTreePrivateList != null) {
+            String file_path = ParamCache.getValue(CommonConstant.SYS_LOCAL_URL);
+            String projectId = wbsTreePrivateList.get(0).getProjectId();
+            String dir = file_path + "/privateUrlCopy/" +projectId;
+            File directory = new File(dir);
+            if (!directory.exists()){
+                directory.mkdir();
+            }
             //存在htmlUrl才重新保存
+            System.out.println("copyHtml-去除空路径之前:"+wbsTreePrivateList.size());
             wbsTreePrivateList = wbsTreePrivateList.stream().filter(wtp -> StringUtils.isNotBlank(wtp.getHtmlUrl())).collect(Collectors.toList());
-            String file_path = ParamCache.getValue(CommonConstant.SYS_LOCAL_URL);
+            System.out.println("copyHtml-去除重复路径之前:"+wbsTreePrivateList.size());
+            wbsTreePrivateList = wbsTreePrivateList.stream().collect(Collectors.toMap(WbsTreePrivate::getHtmlUrl,wbs->wbs,(w1,w2)->w1))
+                                    .values().stream().collect(Collectors.toList());
+            System.out.println("copyHtml-去除重复路径之后:"+wbsTreePrivateList.size());
             //批量复制保存html
             for (WbsTreePrivate tree : wbsTreePrivateList) {
                 String[] split = tree.getHtmlUrl().split("/");
                 File file_in = ResourceUtil.getFile(tree.getHtmlUrl());
-                //File file_in = ResourceUtil.getFile("C:\\Users\\泓创研发01\\Desktop\\privateUrl\\"+split[split.length-1]);
+//                File file_in = ResourceUtil.getFile("C:\\Users\\泓创研发01\\Desktop\\privateUrl\\"+split[split.length-1]);
                 if (!file_in.exists() || file_in.length() == 0) {
                     continue;
                 }
-                String htmlUrl = file_path + "/privateUrlCopy/" + split[split.length - 1];
+                String htmlUrl = file_path + "/privateUrlCopy/" +projectId+"/"+ split[split.length - 1];
                 //String htmlUrl = "C:\\Users\\泓创研发01\\Desktop\\privateUrlCopy\\" + split[split.length-1];
                 File file_out = ResourceUtil.getFile(htmlUrl);
                 FileUtil.copy(file_in, file_out);

+ 83 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/utils/YearTreeUtils.java

@@ -0,0 +1,83 @@
+package org.springblade.manager.utils;
+
+import org.springblade.business.vo.TreeVo;
+import org.springblade.business.vo.TreeVoTwo;
+import org.springblade.common.utils.SnowFlakeUtil;
+
+import java.util.*;
+
+public class YearTreeUtils {
+
+    public static List<TreeVoTwo> yearMonthDayTree(List<String> yearMonthDayList, String sort) {
+
+        //最终集合
+        List<TreeVoTwo> result = new ArrayList<>();
+        if (yearMonthDayList == null || yearMonthDayList.size() <= 0) {
+            return result;
+        }
+
+        //年
+        Map<String, List<String>> yearMap = new HashMap<>();
+        for (String dateStr : yearMonthDayList) {
+            //拆分获取年月日
+            String[] dateArray = dateStr.split("-");
+
+            List<String> monthList;
+            if (yearMap.containsKey(dateArray[0] + "年")) {
+                //存在,获取集合
+                monthList = yearMap.get(dateArray[0] + "年");
+            } else {
+                //不存在,创建
+                monthList = new ArrayList<>();
+            }
+            monthList.add(dateArray[1] + "-" + dateArray[2]);
+            yearMap.put(dateArray[0] + "年", monthList);
+        }
+
+        //循环年
+        for (Map.Entry<String, List<String>> yearMaps : yearMap.entrySet()) {
+            String year = yearMaps.getKey().replace("年", "");
+            List<String> monthList = yearMaps.getValue();
+
+            //月集合
+            List<TreeVoTwo> monthResult = new ArrayList<>();
+
+            //月
+            TreeVoTwo monthMap = new TreeVoTwo();
+
+            //循环月份
+            String month = monthList.get(0).split("-")[0];
+            int index = 0;
+            for (String monthDay : monthList) {
+                //拆分
+                String[] monthDays = monthDay.split("-");
+
+                if (!month.equals(monthDays[0])) {
+                    month = monthDays[0];
+                    monthResult.add(monthMap);
+                    monthMap = new TreeVoTwo();
+                }
+                monthMap.setName(month + "月");
+                monthMap.setHierarchy(year + month);
+                monthMap.setParentId(Long.valueOf(year));
+                monthMap.getChildren().add(new TreeVoTwo(monthDays[1] + "日", new ArrayList<>(), year + month + monthDays[1], Long.valueOf(year + month)));
+                index++;
+                if (index == monthList.size()) {
+                    monthResult.add(monthMap);
+                }
+            }
+            //年
+            TreeVoTwo yearResult = new TreeVoTwo(year, monthResult, year, 0L);
+            result.add(yearResult);
+        }
+
+        if ("DESC".equals(sort)) {
+            result.sort(Comparator.comparing(TreeVoTwo::getName).reversed());
+        } else {
+            result.sort(Comparator.comparing(TreeVoTwo::getName));
+        }
+
+        return result;
+    }
+
+}

+ 3 - 3
blade-service/blade-meter/src/main/java/org/springblade/meter/controller/ChangeTokenFormController.java

@@ -118,7 +118,7 @@ public class ChangeTokenFormController extends BladeController {
 	/**
 	 * 新增-确认选择清单
 	 */
-	@GetMapping("/selectForm")
+	@PostMapping("/selectForm")
 	@ApiOperationSupport(order = 5)
 	@ApiOperation(value = "新增确认选择清单", notes = "传入选中的清单id,返回变更申请清单集合")
 	@ApiImplicitParams(value = {
@@ -126,8 +126,8 @@ public class ChangeTokenFormController extends BladeController {
 			@ApiImplicitParam(name = "nodeId", value = "节点id", required = true),
 			@ApiImplicitParam(name = "ids", value = "选中的清单id,逗号拼接", required = true)
 	})
-	public R<List<ChangeFormVO2>> selectForm(Long contractId,Long nodeId,String ids) {
-		List<ChangeFormVO2> vos = changeTokenFormService.selectForm(contractId,nodeId,ids);
+	public R<List<ChangeFormVO2>> selectForm(@RequestBody ChangeTokenSelectVO vo) {
+		List<ChangeFormVO2> vos = changeTokenFormService.selectForm(vo);
 		return R.data(vos);
 	}
 

+ 14 - 1
blade-service/blade-meter/src/main/java/org/springblade/meter/controller/MiddleMeterApplyController.java

@@ -226,5 +226,18 @@ public class MiddleMeterApplyController extends BladeController {
         return R.data(middleMeterApplyService.test());
     }
 
-
+    /**
+     * 获取当前节点下,当期未被计量的所有清单
+     */
+    @PostMapping("/getCurrentNodeAllForm")
+    @ApiOperationSupport(order = 13)
+    @ApiOperation(value = "获取当前节点下,当期未被计量的所有清单", notes = "返回当前节点下,当期未被计量的所有清单集合")
+    @ApiImplicitParams(value = {
+            @ApiImplicitParam(name = "contractId", value = "合同id", required = true),
+            @ApiImplicitParam(name = "contractUnitId", value = "节点id", required = true),
+            @ApiImplicitParam(name = "contractPeriodId", value = "合同计量期id", required = true)
+    })
+    public R<List<MeterInventoryVO>> getCurrentNodeAllForm(@RequestBody MiddleMeterApply middleMeterApply) {
+        return R.data(middleMeterApplyService.getCurrentNodeAllForm(middleMeterApply));
+    }
 }

+ 18 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/controller/TaskController.java

@@ -2037,12 +2037,30 @@ public class TaskController extends BladeController {
                 aopParamsSet.add(param);
             }
 
+            // 判断是否为审批 最后一人 如果是 即生成 pdf 并开始电签
+            /*当前审批轮次*/
+            String sql_3 = "SELECT * FROM u_task_parallel WHERE status=1 and process_instance_id = ? ORDER BY sort";
+            List<TaskParallel> lastTaskParallels = jdbcTemplate.query(sql_3, new Object[]{task.getProcessInstanceId()}, new BeanPropertyRowMapper<>(TaskParallel.class));
+            if(lastTaskParallels == null || lastTaskParallels.size() == 0 ){
+                meterPdfInfo(task);
+            }
             return R.data(200, aopParamsSet, "操作成功");
         }
 
         return R.fail("操作失败");
     }
 
+
+    // pdf 生成
+
+    private boolean meterPdfInfo(Task task) {
+
+
+
+
+        return true;
+    }
+
     private void check(Task task) {
         String sql_2 = "SELECT * FROM u_task_parallel WHERE process_instance_id = ? ORDER BY sort";
         List<TaskParallel> taskParallels = jdbcTemplate.query(sql_2, new Object[]{task.getProcessInstanceId()}, new BeanPropertyRowMapper<>(TaskParallel.class));

+ 3 - 3
blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/ChangeTokenFormMapper.xml

@@ -162,10 +162,10 @@
     <select id="selectForm" resultType="org.springblade.meter.vo.ChangeFormVO2">
         select id,form_number,form_name,current_price,
                 IFNULL((select change_build_picture_total
-        from s_inventory_form_meter ifm
-        WHERE contract_id = #{contractId} and is_deleted = 0 and ifm.contract_form_id = cif.id and ifm.contract_meter_id = #{nodeId}),0) as contractTotal
+                        from s_inventory_form_meter ifm
+                        WHERE contract_id = #{contractId} and is_deleted = 0 and ifm.contract_form_id = cif.id and ifm.contract_meter_id = #{nodeId}),0) as contractTotal
         from s_contract_inventory_form cif
-        where contract_id = #{contractId}
+        where contract_id = #{contractId} and current_price is not null
         and id in
         <foreach collection="ids" item="id" open="(" separator="," close=")">
             #{id}

+ 1 - 1
blade-service/blade-meter/src/main/java/org/springblade/meter/service/IChangeTokenFormService.java

@@ -58,7 +58,7 @@ public interface IChangeTokenFormService extends BaseService<ChangeTokenForm> {
     /**
      * 新增-确认选择清单
      */
-    List<ChangeFormVO2> selectForm(Long contractId,Long nodeId,String ids);
+    List<ChangeFormVO2> selectForm(ChangeTokenSelectVO vo);
 
     /**
      * 新增 变更令

+ 1 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/service/IMiddleMeterApplyService.java

@@ -66,4 +66,5 @@ public interface IMiddleMeterApplyService extends BaseService<MiddleMeterApply>
 
     String test();
 
+    List<MeterInventoryVO> getCurrentNodeAllForm(MiddleMeterApply middleMeterApply);
 }

+ 5 - 5
blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/ChangeTokenFormServiceImpl.java

@@ -289,15 +289,15 @@ public class ChangeTokenFormServiceImpl extends BaseServiceImpl<ChangeTokenFormM
      * 新增-确认选择清单
      */
     @Override
-    public List<ChangeFormVO2> selectForm(Long contractId,Long nodeId,String ids) {
+    public List<ChangeFormVO2> selectForm(ChangeTokenSelectVO v) {
         List<ChangeFormVO2> vos = new ArrayList<>();
-        if (StringUtils.isBlank(ids)){
+        if (StringUtils.isBlank(v.getIds())){
             return vos;
         }
-        List<Long> longs = Func.toLongList(ids);
-        vos = baseMapper.selectForm(contractId,longs,nodeId);
+        List<Long> longs = Func.toLongList(v.getIds());
+        vos = baseMapper.selectForm(v.getContractId(),longs,v.getNodeId());
         for (ChangeFormVO2 vo : vos) {
-            vo.setContractMeterId(nodeId);
+            vo.setContractMeterId(v.getNodeId());
             vo.setCurrentChangeTotal(BigDecimal.ZERO);
             vo.setChangeTotal(vo.getContractTotal());
             vo.setContractMoney(vo.getCurrentPrice().multiply(vo.getContractTotal()));

+ 17 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/MiddleMeterApplyServiceImpl.java

@@ -538,6 +538,23 @@ public class MiddleMeterApplyServiceImpl extends BaseServiceImpl<MiddleMeterAppl
         return "";
     }
 
+    /**
+     * 获取当前节点下,当期未被计量的所有清单
+     */
+    @Override
+    public List<MeterInventoryVO> getCurrentNodeAllForm(MiddleMeterApply apply) {
+        List<MeterInventoryVO> voList = new ArrayList<>();
+        //获取当前节点下所有未被计量的清单
+        List<ResolveInventoryVO> vos = this.addFormList(null, apply.getContractId(), apply.getContractPeriodId(), apply.getContractUnitId(), null, null);
+        if (vos.size() == 0){
+            return voList;
+        }
+        List<String> list = vos.stream().map(l -> l.getId()+"").collect(Collectors.toList());
+        String join = String.join(",", list);
+        voList = this.addResolveForm(apply.getContractId(), apply.getContractUnitId(), join);
+        return voList;
+    }
+
     //递归方法
     private void gatherSortNode(List<NodeSortVO> list,List<Long> ids){
         for (NodeSortVO vo : list) {