Преглед изворни кода

Merge branch 'test-merge' of http://219.151.181.73:3000/zhuwei/bladex into test-merge

# Conflicts:
#	blade-service/blade-business/src/main/java/org/springblade/business/controller/TaskController.java
lvy пре 2 недеља
родитељ
комит
9bb8b1b681
68 измењених фајлова са 2604 додато и 463 уклоњено
  1. 14 0
      blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/dto/ScanFileMoveDTO.java
  2. 52 0
      blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/entity/ScanFile.java
  3. 35 0
      blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/entity/ScanFolder.java
  4. 18 0
      blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/vo/ScanFolderVO.java
  5. 1 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/PreviewNodeNameDTO.java
  6. 2 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/PreviewNodeNameDTO1.java
  7. 6 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/feign/ArchiveFileClient.java
  8. 1 0
      blade-service-api/blade-dingding-api/src/main/java/org/springblade/dingding/vo/MeetingVo.java
  9. 12 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/ArchiveTreeContract.java
  10. 3 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/ContractInfo.java
  11. 3 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/ArchiveSyncTrialVO.java
  12. 2 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/NodeBaseInfoVO.java
  13. 6 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/feign/InterimPayCertificateItemClient.java
  14. 1 1
      blade-service-api/blade-websocket-api/src/main/java/org/springblade/websocket/vo/UserInfoVO.java
  15. 49 20
      blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ArchivesAutoController.java
  16. 178 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ScanFileController.java
  17. 48 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/external/impl/ExternalDataArchiveTreeService.java
  18. 2 1
      blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ArchivesAutoMapper.xml
  19. 26 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFileMapper.java
  20. 30 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFileMapper.xml
  21. 15 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFolderMapper.java
  22. 12 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFolderMapper.xml
  23. 10 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/IArchivesAutoService.java
  24. 31 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/ScanFileService.java
  25. 5 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/ScanFolderService.java
  26. 9 2
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchiveNameServiceImpl.java
  27. 176 6
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchivesAutoServiceImpl.java
  28. 520 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ScanFileServiceImpl.java
  29. 16 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ScanFolderServiceImpl.java
  30. 18 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/utils/FileUtils.java
  31. 72 28
      blade-service/blade-business/src/main/java/org/springblade/business/controller/InformationWriteQueryController.java
  32. 1 1
      blade-service/blade-business/src/main/java/org/springblade/business/controller/NeiWaiYeProgressController.java
  33. 553 194
      blade-service/blade-business/src/main/java/org/springblade/business/controller/TaskController.java
  34. 0 2
      blade-service/blade-business/src/main/java/org/springblade/business/controller/TrialSummaryController.java
  35. 9 0
      blade-service/blade-business/src/main/java/org/springblade/business/feignClient/ArchiveFileClientImpl.java
  36. 1 1
      blade-service/blade-business/src/main/java/org/springblade/business/feignClient/TaskClientImpl.java
  37. 2 0
      blade-service/blade-business/src/main/java/org/springblade/business/mapper/ArchiveFileMapper.java
  38. 11 2
      blade-service/blade-business/src/main/java/org/springblade/business/mapper/ArchiveFileMapper.xml
  39. 1 1
      blade-service/blade-business/src/main/java/org/springblade/business/mapper/InformationQueryMapper.java
  40. 17 3
      blade-service/blade-business/src/main/java/org/springblade/business/mapper/InformationQueryMapper.xml
  41. 2 0
      blade-service/blade-business/src/main/java/org/springblade/business/service/IArchiveFileService.java
  42. 19 3
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/ArchiveFileServiceImpl.java
  43. 2 0
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/InformationQueryServiceImpl.java
  44. 2 1
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/TaskServiceImpl.java
  45. 4 11
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/TrialSelfInspectionRecordServiceImpl.java
  46. 51 0
      blade-service/blade-dingding/src/main/java/org/springblade/dingding/service/impl/MeetingServiceImpl.java
  47. 6 3
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/controller/ChekSignData.java
  48. 1 1
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/controller/EVController.java
  49. 17 25
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/service/impl/EVDataServiceImpl.java
  50. 213 55
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/service/impl/ScrDataServiceImpl.java
  51. 10 2
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/utils/PdfAddimgUtil.java
  52. 15 8
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java
  53. 62 5
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/NodeBaseInfoController.java
  54. 1 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/TrialSummaryClassificationConfigurationController.java
  55. 4 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/WbsTreeContractController.java
  56. 49 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/feign/ArchiveTreeContractImpl.java
  57. 2 2
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ArchiveTreeContractMapper.xml
  58. 3 3
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreeContractMapper.xml
  59. 3 5
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreePrivateMapper.xml
  60. 36 13
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ArchiveTreeContractSyncImpl.java
  61. 59 51
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/FormulaServiceImpl.java
  62. 29 2
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeContractServiceImpl.java
  63. 8 7
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeSynchronousRecordServiceImpl.java
  64. 1 1
      blade-service/blade-meter/src/main/java/org/springblade/meter/controller/TaskController.java
  65. 4 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/IInterimPayCertificateItemService.java
  66. 10 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/InterimPayCertificateItemClientImpl.java
  67. 21 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/InterimPayCertificateItemServiceImpl.java
  68. 2 2
      blade-service/blade-repair/src/main/java/org/springblade/repair/controller/CheckAndRepairController.java

+ 14 - 0
blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/dto/ScanFileMoveDTO.java

@@ -0,0 +1,14 @@
+package org.springblade.archive.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ScanFileMoveDTO {
+   private List<Long> ids;
+   private Long nodeId;
+}

+ 52 - 0
blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/entity/ScanFile.java

@@ -0,0 +1,52 @@
+package org.springblade.archive.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.omg.CORBA.PRIVATE_MEMBER;
+
+@TableName(value = "scan_file")
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ScanFile {
+    @TableId(
+            value = "id",
+            type = IdType.ASSIGN_ID
+    )
+    private Long id;
+    @ApiModelProperty(value = "项目id")
+    private Long projectId;
+    @ApiModelProperty(value = "合同id")
+    private Long contractId;
+    @ApiModelProperty(value = "文件夹id")
+    private Long folderId;
+    @ApiModelProperty(value = "序号")
+    private Integer sort;
+    @ApiModelProperty(value = "数字编号")
+    private Integer digitalNum;
+    @ApiModelProperty(value = "文件编号")
+    private String fileNum;
+    @ApiModelProperty(value = "文件题名")
+    private String fileName;
+    @ApiModelProperty(value = "文件页数")
+    private String fileSize;
+    @ApiModelProperty(value = "文件日期")
+    private String fileDate;
+    @ApiModelProperty(value = "创建时间")
+    private String creatTime;
+    @ApiModelProperty(value = "文件路径")
+    private String filePath;
+    @ApiModelProperty(value = "OSS路径")
+    private String ossUrl;
+    @ApiModelProperty(value = "负责人")
+    private String responsible;
+    @ApiModelProperty(value = "是否删除")
+    private Integer isDeleted;
+    @ApiModelProperty(value = "是否移动")
+    private Integer isMove;
+}

+ 35 - 0
blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/entity/ScanFolder.java

@@ -0,0 +1,35 @@
+package org.springblade.archive.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@TableName("scan_folder")
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ScanFolder {
+    @TableId(
+            value = "id",
+            type = IdType.ASSIGN_ID
+    )
+    @ApiModelProperty(value = "id")
+    private Long id;
+    @ApiModelProperty(value = "父级id")
+    private Long parentId;
+    @ApiModelProperty(value = "项目id")
+    private Long projectId;
+    @ApiModelProperty(value = "合同id")
+    private Long contractId;
+    @ApiModelProperty(value = "文件夹名称")
+    private String folderName;
+    @ApiModelProperty(value = "文件夹路径")
+    private String folderPath;
+    @ApiModelProperty(value = "是否删除")
+    private Integer isDeleted;
+}

+ 18 - 0
blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/vo/ScanFolderVO.java

@@ -0,0 +1,18 @@
+package org.springblade.archive.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springblade.archive.entity.ScanFolder;
+
+import java.util.List;
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ScanFolderVO extends ScanFolder {
+    @ApiModelProperty(value = "是否有子级")
+    private Boolean hasChildren;
+    @ApiModelProperty(value = "子级节点")
+    private List<ScanFolderVO> childs;
+}

+ 1 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/PreviewNodeNameDTO.java

@@ -4,6 +4,7 @@ import lombok.Data;
 
 @Data
 public class PreviewNodeNameDTO {
+    private Long id;
     private Long wbsId;
     private Long projectId;
 }

+ 2 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/PreviewNodeNameDTO1.java

@@ -4,6 +4,8 @@ import lombok.Data;
 
 @Data
 public class PreviewNodeNameDTO1 {
+    private Long id;
     private String wbsId;
     private String nameRule;
+    private Long projectId;
 }

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

@@ -175,4 +175,10 @@ public interface ArchiveFileClient {
 
     @PostMapping(API_PREFIX + "/getAllArchiveFileByIds")
     List<ArchiveFile> getAllArchiveFileByIds(@RequestBody List<String> strList);
+
+    @PostMapping(API_PREFIX + "/selectMaxSortByContractId")
+    Integer selectMaxSortByContractId(@RequestParam Long contractId);
+
+    @PostMapping(API_PREFIX + "/saveBatchArchiveFile")
+    void saveBatchArchiveFile(@RequestBody List<ArchiveFile> list);
 }

+ 1 - 0
blade-service-api/blade-dingding-api/src/main/java/org/springblade/dingding/vo/MeetingVo.java

@@ -11,6 +11,7 @@ import java.util.List;
 public class MeetingVo {
     private String roomId;
     private String fixedData; //固定数据
+    private String floor; //楼层
     private String meetingTheme; //会议主题
     private String meetingDept;  //召开部门
     private String meetingBooker;//会议预定者,主持人

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

@@ -406,6 +406,18 @@ public class ArchiveTreeContract extends BaseEntity {
         this.associationType = 1;
         this.setExtType(1);
 
+
+        if (archiveTree.getExtNodeType() != null) {
+            Integer externalExtNodeType = archiveTree.getExtNodeType();
+            if (externalExtNodeType == 1) {
+                this.extNodeType = 1; // 如果为1,则this同名属性也取1
+            } else if (externalExtNodeType == 2) {
+                this.extNodeType = 2; // 如果为2,则this同名属性取2
+            } else if (externalExtNodeType == 3) {
+                this.extNodeType = 4; // 如果为3,则同名属性取值为4
+            }
+        }
+
 //        this.majorDataType = archiveTree.getMajorDataType();
 //        this.displayHierarchy = archiveTree.getDisplayHierarchy();
         this.isStorageNode = archiveTree.getIsStorage();

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

@@ -181,6 +181,9 @@ public class ContractInfo extends BaseEntity {
     @ApiModelProperty(value = "卷盒规格")
     private String specification;
 
+    @ApiModelProperty(value = "1正在扫描中 ,2没有扫描")
+    private Integer isScan;
+
     /**
      * 排序
      */

+ 3 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/ArchiveSyncTrialVO.java

@@ -41,4 +41,7 @@ public class ArchiveSyncTrialVO {
 
     @ApiModelProperty(value = "排序")
     private Integer sort;
+
+    @ApiModelProperty(value = "父节点id")
+    private Long pId;
 }

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

@@ -31,4 +31,6 @@ import lombok.EqualsAndHashCode;
 public class NodeBaseInfoVO extends NodeBaseInfo {
 	private static final long serialVersionUID = 1L;
 
+	private Integer isDisable;
+
 }

+ 6 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/feign/InterimPayCertificateItemClient.java

@@ -20,4 +20,10 @@ public interface InterimPayCertificateItemClient {
 
     @PostMapping("/updateInterimPayCertificateItem")
     void updateInterimPayCertificateItem(@RequestBody List<InterimPayCertificateItem> list4);
+
+    @PostMapping("/addInterimPayCertificateItem1")
+    void addInterimPayCertificateItem1(@RequestParam Long reportId, @RequestBody MonthlyReportVo vo);
+
+    @PostMapping("/updateInterimPayCertificateItem1")
+    void updateInterimPayCertificateItem1(@RequestBody InterimPayCertificateItem item);
 }

+ 1 - 1
blade-service-api/blade-websocket-api/src/main/java/org/springblade/websocket/vo/UserInfoVO.java

@@ -34,7 +34,7 @@ public class UserInfoVO implements Serializable {
     @ApiModelProperty("公告类型1维护2普通,拼接字符串")
     private String msgType;
 
-    @ApiModelProperty("数据信息")
+    @ApiModelProperty("传输数据信息")
     private List<MeetingVo> object;
 
     public UserInfoVO() {

+ 49 - 20
blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ArchivesAutoController.java

@@ -592,7 +592,33 @@ public class ArchivesAutoController extends BladeController {
 	 * @return
 	 */
 	@PostMapping("/archiveAutoMethod")
-	public R archiveAutoMethod(Long projectId,Long contractId,Long nodeId) {
+	public R archiveAutoMethod(Long projectId, Long contractId, Long nodeId,
+							   @RequestParam(required = false) String childNodeIds) {
+		List<Long> childNodes = null;
+		if (StringUtils.isNotBlank(childNodeIds)) {
+			childNodes = Arrays.stream(childNodeIds.split(","))
+					.map(String::trim)
+					.filter(s -> !s.isEmpty())
+					.map(Long::valueOf)
+					.collect(Collectors.toList());
+		}
+		return archiveAutoMethod(projectId, contractId, nodeId, childNodes);
+	}
+
+	/**
+	 * 自动组卷入口(带childNodes参数)
+	 * projectId 为当前项目
+	 * contractId 为客户端当前选定的合同段
+	 * nodeId 为左侧树选定的节点id
+	 * childNodes 为指定的子节点列表,用于过滤
+	 * @param projectId
+	 * @param contractId
+	 * @param nodeId
+	 * @param childNodes
+	 * @return
+	 */
+	@PostMapping("/archiveAutoMethodWithChildNodes")
+	public R archiveAutoMethod(Long projectId,Long contractId,Long nodeId, @RequestParam(required = false) List<Long> childNodes) {
 		try{
 			//先验证当前项目是否在自动组卷中,组卷中直接返回
 			//ProjectInfo projectInfo = projectClient.getById(String.valueOf(projectId));
@@ -610,7 +636,7 @@ public class ArchivesAutoController extends BladeController {
 			//设置自动组卷中
 			contractClient.updateIsArchivesAutoById(contractId,1);
 
-			archivesAutoService.archiveAutoMethodThread(projectId,contractId,nodeId,traceId);
+			archivesAutoService.archiveAutoMethodThread(projectId,contractId,nodeId,traceId, childNodes);
 			return R.data("开始自动组卷中,请耐心等待");
 		}catch (Exception e){
 			e.printStackTrace();
@@ -625,21 +651,15 @@ public class ArchivesAutoController extends BladeController {
 	@ApiOperationSupport(order = 13)
 	@ApiOperation(value = "重新生成案卷", notes = "传入ids")
 	@Transactional
-	public R reCreateArchiveAuto(@ApiParam(value = "主键集合", required = true) @RequestParam String ids,@ApiParam(value = "合并后的文件题目", required = true)String name){
-		//先查出勾选的案卷
-		List<ArchivesAuto> archivesAutoList=archivesAutoService.listByIds(Func.toLongList(ids));
-//		if(archivesAutoList.size()<=1){
-//			return R.fail("请选择多个案卷进行合并");
-//		}
-		//查出所有案卷文件
-		List<ArchiveFile>archiveFileList=new ArrayList<>();
-		List<Long> longList = Func.toLongList(ids);
-		for (Long id : longList) {
-			archiveFileList.addAll(archiveFileClient.getArchiveFileByArchiveIds(id+""));
-		}
-		//根据档号后缀排序 拿到第一个
-		ArchivesAuto auto = archivesAutoList.get(0);
-		if(longList.size()>1){
+	public R reCreateArchiveAuto(@ApiParam(value = "主键集合", required = true) @RequestParam String ids,@ApiParam(value = "合并后的文件题目", required = true)String name,Integer type){
+		if(type==1){
+			//先查出勾选的案卷
+			List<ArchivesAuto> archivesAutoList=archivesAutoService.listByIds(Func.toLongList(ids));
+			if(archivesAutoList.size()<=1){
+				return R.fail("请选择多个案卷进行合并");
+			}
+			//根据档号后缀排序 拿到第一个
+			ArchivesAuto auto = archivesAutoList.get(0);
 			archivesAutoList.sort(Comparator.comparingInt(a -> {
 				String fileNumber = a.getFileNumber();
 				if (fileNumber == null || fileNumber.isEmpty()) {
@@ -664,6 +684,12 @@ public class ArchivesAutoController extends BladeController {
 			}));
 			//将除第一个以外的案卷文件archiveId 设置成第一个的id
 			List<ArchiveFile>updateArchiveFileList=new ArrayList<>();
+			//查出所有案卷文件
+			List<ArchiveFile>archiveFileList=new ArrayList<>();
+			List<Long> longList = Func.toLongList(ids);
+			for (Long id : longList) {
+				archiveFileList.addAll(archiveFileClient.getArchiveFileByArchiveIds(id+""));
+			}
 			int i=1;
 			for (ArchiveFile file : archiveFileList) {
 				if (!file.getArchiveId().equals(auto.getId())) {
@@ -677,10 +703,13 @@ public class ArchivesAutoController extends BladeController {
 			//删除其他案卷
 			archivesAutoList.remove(auto);
 			archivesAutoService.deleteLogic(archivesAutoList.stream().map(o->o.getId()).collect(Collectors.toList()));
+			//设置案卷页码和四要素
+			archivesAutoService.reCreateArchiveAuto(auto, archiveFileList);
+			return R.status(true);
+		}else {
+			archivesAutoService.reCreateArchiveAuto1(ids);
+			return R.success("正在重组,请稍后查看");
 		}
-		//设置案卷页码和四要素
-		archivesAutoService.reCreateArchiveAuto(auto, archiveFileList);
-		return R.status(true);
 	}
 
 	@PostMapping("/creatFileNameFormAI")

+ 178 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ScanFileController.java

@@ -0,0 +1,178 @@
+package org.springblade.archive.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.springblade.archive.dto.ScanFileMoveDTO;
+import org.springblade.archive.entity.ScanFile;
+import org.springblade.archive.entity.ScanFolder;
+import org.springblade.archive.service.ScanFileService;
+import org.springblade.archive.service.ScanFolderService;
+import org.springblade.archive.vo.ScanFolderVO;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+import org.springblade.manager.entity.ContractInfo;
+import org.springblade.meter.vo.InterimPayCertificateVO;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+@RestController
+@AllArgsConstructor
+@RequestMapping("/scanFile")
+@Api(value = "扫描文件接口", tags = "扫描文件接口")
+public class ScanFileController {
+    private final ScanFileService scanFileService;
+    private final JdbcTemplate jdbcTemplate;
+
+    // 线程池
+    @Resource(name = "taskExecutor1")
+    private ThreadPoolExecutor executor;
+
+
+    @GetMapping("/startOrEndScan")
+    @ApiOperation("开始或结束扫描")
+    @ApiImplicitParams({@ApiImplicitParam(name = "contractId", value = "合同ID"),@ApiImplicitParam(name = "type", value = "1:开始扫描 0:结束扫描")})
+    public R startOrEndScan(Long contractId,Integer type){
+        String sql="update m_contract_info set is_scan="+type+" where id="+contractId;
+        jdbcTemplate.update(sql);
+        return R.success("操作成功"+ (type==1?",正在扫描中":",已结束扫描"));
+    }
+
+    @Scheduled(fixedDelay = 120000)
+    public void scan (){
+        String sql = "select * from m_contract_info where is_scan=1 and is_deleted=0";
+        List<ContractInfo> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(ContractInfo.class));
+        if (!list.isEmpty()) {
+            // 创建线程池
+            int threadCount = Math.min(list.size(), Runtime.getRuntime().availableProcessors() * 2);
+            ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
+            try {
+                // 存储Future对象以获取执行结果
+                List<Future<?>> futures = new ArrayList<>();
+                // 提交任务到线程池
+                for (ContractInfo contractInfo : list) {
+                    Future<?> future = executorService.submit(() -> {
+                        try {
+                            scanFileService.scanAndSave(contractInfo.getId(), Long.parseLong(contractInfo.getPId()));
+                            System.out.println("合同ID " + contractInfo.getId() + " 扫描完成");
+                        } catch (Exception e) {
+                            System.err.println("处理合同ID " + contractInfo.getId() + " 时出错: " + e.getMessage());
+                            throw e; // 重新抛出异常以便Future能捕获
+                        }
+                    });
+                    futures.add(future);
+                }
+                // 关闭线程池
+                executorService.shutdown();
+                // 等待所有任务完成并检查结果
+                for (int i = 0; i < futures.size(); i++) {
+                    try {
+                        futures.get(i).get(); // 这会阻塞直到任务完成
+                        System.out.println("任务 " + i + " 已成功完成");
+                    } catch (Exception e) {
+                        System.err.println("任务 " + i + " 执行失败: " + e.getMessage());
+                    }
+                }
+                // 等待线程池完全终止
+                if (executorService.awaitTermination(30, TimeUnit.MINUTES)) {
+                    System.out.println("所有扫描任务已完成");
+                } else {
+                    System.err.println("部分扫描任务超时未完成");
+                    executorService.shutdownNow();
+                }
+
+            } catch (InterruptedException e) {
+                System.err.println("线程执行被中断: " + e.getMessage());
+                executorService.shutdownNow();
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    @GetMapping("/getScanFolder")
+    @ApiOperation("获取扫描文件夹")
+    @ApiImplicitParams({@ApiImplicitParam(name = "contractId", value = "合同ID"),@ApiImplicitParam(name = "projectId", value = "项目ID")})
+    public R<List<ScanFolderVO>> getScanFolder(Long contractId, Long projectId){
+        List<ScanFolderVO> list = scanFileService.getScanFolder(contractId,projectId);
+        return R.data( list);
+    }
+
+    @GetMapping("/getScanFile")
+    @ApiOperation("获取扫描文件")
+    @ApiImplicitParams({@ApiImplicitParam(name = "contractId", value = "合同ID"),@ApiImplicitParam(name = "projectId", value = "项目ID"),@ApiImplicitParam(name = "folderId", value = "文件夹ID"),@ApiImplicitParam(name = "query", value = "查询参数")})
+    public R<IPage<ScanFile>> getScanFile(Long contractId, Long projectId, Long folderId, Query query,Integer move){
+        IPage<ScanFile> page=scanFileService.getScanFile(contractId,projectId,folderId,query,move);
+        return R.data(page);
+    }
+    @GetMapping("/getDetil")
+    @ApiOperation("获取扫描文件详情")
+    @ApiImplicitParam(name = "id", value = "文件ID")
+    public R<ScanFile> getDetil(Long id){
+        return R.data(scanFileService.getById(id));
+    }
+    @PostMapping("/updateScanFile")
+    @ApiOperation("更新扫描文件")
+    @ApiImplicitParam(name = "scanFiles", value = "扫描文件")
+    public R updateScanFile(@RequestBody List<ScanFile> scanFiles){
+        return R.data(scanFileService.updateBatchById(scanFiles));
+    }
+
+    @GetMapping("/deleteScanFile")
+    @ApiOperation("删除扫描文件")
+    @ApiImplicitParam(name = "fileNames", value = "文件名称")
+    public R deleteScanFile(String ids){
+        return R.success(scanFileService.deleteScanFile(ids));
+    }
+
+    @GetMapping("/getContractStatus")
+    @ApiOperation("获取合同状态")
+    @ApiImplicitParam(name = "contractId", value = "合同ID")
+    public R<Integer> getContractStatus(Long contractId){
+        if (contractId == null) {
+            return R.data(2);
+        }
+        String sql = "SELECT is_scan FROM m_contract_info WHERE id = ?";
+        try {
+            Integer result = jdbcTemplate.queryForObject(sql, Integer.class, contractId);
+            return R.data(result != null ? result : 2);
+        } catch (Exception e) {
+            // 记录异常日志(可选)
+            System.err.println("查询合同扫描状态出错: " + e.getMessage());
+            return R.data(2);
+        }
+    }
+
+    @PostMapping("/autoRecognize")
+    @ApiOperation("自动识别")
+    @ApiImplicitParam(name = "ids", value = "文件IDs")
+    public R autoRecognize(String ids){
+       return R.status(scanFileService.autoRecognize(ids));
+    }
+
+    @PostMapping("/moveScanFile")
+    @ApiOperation("移动文件")
+    @ApiImplicitParams({@ApiImplicitParam(name = "ids", value = "文件IDs"),@ApiImplicitParam(name = "nodeId", value = "目标文件夹ID") })
+    public R moveScanFile(@RequestBody ScanFileMoveDTO dto){
+        return R.status(scanFileService.moveScanFile(dto.getIds(),dto.getNodeId()));
+    }
+
+
+
+}

+ 48 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/external/impl/ExternalDataArchiveTreeService.java

@@ -220,6 +220,7 @@ public class ExternalDataArchiveTreeService {
         }
     }
 
+
     private void getAddAndUpdateContracts(
             ExternalDataInfo externalDataInfo,
             List<ArchiveTreeContract> externalContracts,
@@ -227,6 +228,53 @@ public class ExternalDataArchiveTreeService {
             List<ArchiveTreeContract> addContracts,
             List<ArchiveTreeContract> upContracts) {
 
+        // 1. 创建本地合同的 Map:key 为 outId,value 为合同对象
+        Map<String, ArchiveTreeContract> localContractMap = new HashMap<>();
+        for (ArchiveTreeContract localContract : localContracts) {
+            String outId = localContract.getOutId();
+            if (outId != null && !outId.isEmpty()) {
+                localContractMap.put(outId, localContract);
+            }
+        }
+
+        // 2. 遍历外部合同,判断新增或更新
+        for (ArchiveTreeContract externalContract : externalContracts) {
+            String externalOutId = externalContract.getOutId();
+
+            // 处理空 outId(根据业务需求决定是否跳过或新增)
+            if (externalOutId == null || externalOutId.isEmpty()) {
+                // addContracts.add(externalContract); // 如果需要处理空 outId 则取消注释
+                continue;
+            }
+
+            // 通过 Map 查找对应的本地合同
+            ArchiveTreeContract localContract = localContractMap.get(externalOutId);
+
+            if (localContract == null) {
+                // 本地不存在该合同,需要新增
+                addContracts.add(externalContract);
+            } else {
+                // 新增的判断逻辑:如果 localContract 的 extNodeType 为 null 或 -1,
+                // 且 externalContract 的 extNodeType 不为 null,则添加到更新列表
+                Integer localExtNodeType = localContract.getExtNodeType();
+                Integer externalExtNodeType = externalContract.getExtNodeType();
+
+                if ((localExtNodeType == null || localExtNodeType == -1)
+                        && externalExtNodeType != null) {
+                    localContract.setExtNodeType(externalExtNodeType);
+                    upContracts.add(localContract);
+                }
+                // 这里可以添加其他更新条件的判断,例如时间戳比较
+            }
+        }
+    }
+    private void getAddAndUpdateContracts1(
+            ExternalDataInfo externalDataInfo,
+            List<ArchiveTreeContract> externalContracts,
+            List<ArchiveTreeContract> localContracts,
+            List<ArchiveTreeContract> addContracts,
+            List<ArchiveTreeContract> upContracts) {
+
         // 1. 提取本地合同的 outId 集合(用于快速判断外部合同是否已存在)
         Set<String> localOutIdSet = localContracts.stream()
                 .map(ArchiveTreeContract::getOutId)  // 直接使用本地合同的 outId 字段

+ 2 - 1
blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ArchivesAutoMapper.xml

@@ -153,6 +153,7 @@
         <result column="archive_name_suffix" property="archiveNameSuffix"/>
         <result column="ext_key_id" property="extKeyId"/>
         <result column="out_id" property="outId"/>
+        <result column="ext_node_type" property="extNodeType"/>
     </resultMap>
 
     <!-- 通用查询映射结果 -->
@@ -1566,7 +1567,7 @@
         where is_deleted = 0
     </select>
     <select id="getOutNodesByOutIds" resultMap="archiveTreeContractResultMap">
-        select id,node_name,parent_id,out_id
+        select id,node_name,parent_id,out_id,ext_node_type
         from m_archive_tree_contract
         where project_id = #{projectId} and is_deleted = 0 and
         out_id in

+ 26 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFileMapper.java

@@ -0,0 +1,26 @@
+package org.springblade.archive.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import feign.Param;
+import org.springblade.archive.entity.ScanFile;
+import org.springblade.archive.entity.ScanFolder;
+
+import java.util.List;
+
+public interface ScanFileMapper extends BaseMapper<ScanFile> {
+
+    Integer exists(@Param("projectId") Long projectId, @Param("contractId") Long contractId, @Param("fileName") String fileName);
+
+    int insert(ScanFile scanFile);
+
+    Integer selectMaxDigitalNum(@Param("contractId") Long contractId, @Param("projectId") Long projectId);
+
+    Integer selectMaxSort(@Param("contractId") Long contractId, @Param("projectId") Long projectId, @Param("folderId") Long folderId);
+
+    List<ScanFolder> getScanFolder(@Param("contractId") Long contractId, @Param("projectId") Long projectId);
+
+    IPage<ScanFile> getScanFile(IPage<ScanFile> page, @Param("contractId") Long contractId, @Param("projectId") Long projectId, @Param("folderId") Long folderId,@Param("move")Integer  move);
+
+    void removeScan(@Param("longList") List<Long> longList);
+}

+ 30 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFileMapper.xml

@@ -0,0 +1,30 @@
+<?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.ScanFileMapper">
+    <update id="removeScan">
+        update scan_file set is_deleted = 1 where project_id = #{projectId} and contract_id = #{contractId}
+        and id in
+        <foreach collection="longList" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </update>
+    <select id="exists" resultType="java.lang.Integer">
+        select COUNT(*) FROM scan_file WHERE project_id = #{projectId} AND contract_id = #{contractId}  AND file_name = #{fileName}
+    </select>
+    <select id="selectMaxDigitalNum" resultType="java.lang.Integer">
+        select MAX(digital_num) FROM scan_file WHERE project_id = #{projectId} AND contract_id = #{contractId}
+    </select>
+    <select id="selectMaxSort" resultType="java.lang.Integer">
+        select MAX(sort) FROM scan_file WHERE project_id = #{projectId} AND contract_id = #{contractId} And folder_id = #{folderId}
+    </select>
+    <select id="getScanFolder" resultType="org.springblade.archive.entity.ScanFolder">
+        select * from scan_folder where project_id = #{projectId} and contract_id = #{contractId} and is_deleted = 0
+    </select>
+    <select id="getScanFile" resultType="org.springblade.archive.entity.ScanFile">
+        select * from scan_file where project_id = #{projectId} and contract_id = #{contractId} and folder_id = #{folderId} and is_deleted = 0
+        <if test="move!= null">
+            and is_move = #{move}
+        </if>
+        order by digital_num
+    </select>
+</mapper>

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

@@ -0,0 +1,15 @@
+package org.springblade.archive.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+import org.springblade.archive.entity.ScanFolder;
+
+public interface ScanFolderMapper extends BaseMapper<ScanFolder> {
+
+    int exists(@Param("projectId") Long projectId, @Param("contractId") Long contractId, @Param("folderName") String folderName);
+
+    int insert(ScanFolder scanFolder);
+
+
+    Long getId(@Param("folderName") String folderName, @Param("contractId") Long contractId, @Param("projectId") Long projectId);
+}

+ 12 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFolderMapper.xml

@@ -0,0 +1,12 @@
+<?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.ScanFolderMapper">
+
+
+    <select id="exists" resultType="java.lang.Integer">
+        select COUNT(*) FROM scan_folder WHERE project_id = #{projectId} AND contract_id = #{contractId} AND folder_name = #{folderName}
+    </select>
+    <select id="getId" resultType="java.lang.Long">
+        select id from scan_folder where project_id = #{projectId} AND contract_id = #{contractId} AND folder_name = #{folderName}
+    </select>
+</mapper>

+ 10 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/service/IArchivesAutoService.java

@@ -76,8 +76,14 @@ public interface IArchivesAutoService extends BaseService<ArchivesAuto> {
 
 	void archiveAutoMethod(Long project,Long contractId,Long nodeId,Long traceId);
 
+	void archiveAutoMethod(Long project,Long contractId,Long nodeId,Long traceId,List<Long> childNodes);
+
+	void archiveAutoMethodThread(Long projectId, Long contractId, Long nodeId, Long traceId, List<Long> childNodes);
+
 	void splitArchvies(Long project,Long contractId,Long nodeId);
 
+	void splitArchvies(Long project,Long contractId,Long nodeId,List<Long> childNodes);
+
 	public String getMergeArchivesFile(Long archiveId);
 
 	//拆卷
@@ -90,6 +96,8 @@ public interface IArchivesAutoService extends BaseService<ArchivesAuto> {
 	//刷新某个项目的档号
 	void refreshFileNumberNoSlipt(Long projectId,Long contractId,Long nodeId,boolean bforce, Long traceId);
 
+	void refreshFileNumberNoSlipt(Long projectId,Long contractId,Long nodeId,boolean bforce, Long traceId,List<Long> childNodes);
+
 	void test666();
 
     List<DictBiz> getCarrierTypeByDict();
@@ -177,4 +185,6 @@ public interface IArchivesAutoService extends BaseService<ArchivesAuto> {
 	List<Long> getArchiveIdsByNodes(List<Long> ids);
 
 	void reomoveArchiveAndFile(List<Long> archiveIds);
+
+    void reCreateArchiveAuto1(String ids);
 }

+ 31 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/service/ScanFileService.java

@@ -0,0 +1,31 @@
+package org.springblade.archive.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.springblade.archive.entity.ScanFile;
+import org.springblade.archive.vo.ScanFolderVO;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+
+import java.util.List;
+
+
+public interface ScanFileService extends IService<ScanFile> {
+    R scanAndSaveFolder(Long contractId, Long projectId);
+
+    R scanAndSaveFiles(Long contractId, Long projectId);
+
+    void sortNumber(Long contractId, Long projectId);
+
+    void scanAndSave(Long contractId,Long projectId);
+
+    List<ScanFolderVO> getScanFolder(Long contractId, Long projectId);
+
+    IPage<ScanFile> getScanFile(Long contractId, Long projectId, Long folderId, Query query,Integer move);
+
+    String deleteScanFile(String ids);
+
+    Boolean autoRecognize(String ids);
+
+    boolean moveScanFile(List<Long> ids, Long nodeId);
+}

+ 5 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/service/ScanFolderService.java

@@ -0,0 +1,5 @@
+package org.springblade.archive.service;
+
+public interface ScanFolderService {
+
+}

+ 9 - 2
blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchiveNameServiceImpl.java

@@ -283,7 +283,11 @@ public class ArchiveNameServiceImpl implements IArchiveNameService {
                                 }
                             }
 
-                            if (allGroupsFullyCovered && useCovering) {
+                            // 检查祖父节点是否为单位工程,如果是则跳过此处理
+                            boolean isUnitProject = grandParentNode.getExtNodeType() != null && 
+                                                  grandParentNode.getExtNodeType() == 1;
+                            
+                            if (allGroupsFullyCovered && useCovering && !isUnitProject) {
                                 nameParts.add(getLevelName(hierarchy, grandParentId));
                                 canUseGrandParentName = true;
                             }
@@ -316,8 +320,11 @@ public class ArchiveNameServiceImpl implements IArchiveNameService {
                 List<ArchiveTreeContract> allChildren = hierarchy.getChildren(groupId);
                 int childrenCount = (allChildren != null) ? allChildren.size() : 0;
 
+                boolean isUnitProject = parent.getExtNodeType() != null &&
+                        parent.getExtNodeType() == 1;
+
                 // 检查是否全覆盖所有子节点
-                if (childrenCount > 0 && groupNodes.size() == childrenCount ) {
+                if (childrenCount > 0 && groupNodes.size() == childrenCount && !isUnitProject) {
                     // 只有组内第一个父节点使用完整层级名称
                     String parentName = firstParentInGroup ?
                             getLevelName(hierarchy, groupId) :

+ 176 - 6
blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchivesAutoServiceImpl.java

@@ -771,7 +771,69 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 
 	@Override
 	public void splitArchvies(Long projectId, Long contractId, Long nodeId) {
+		splitArchvies(projectId, contractId, nodeId, null);
+	}
+
+	public void splitArchvies(Long projectId, Long contractId, Long nodeId, List<Long> childNodes) {
 		List<String> removeFiles = new ArrayList<>();
+		
+		// 如果 childNodes 不为空,使用 childNodes 进行拆卷
+		if (childNodes != null && !childNodes.isEmpty()) {
+			log.info("[自动组卷-拆卷]{}", "对指定childNodes进行拆卷,childNodes: " + childNodes);
+			
+			// 从 childNodes 分别获取节点信息并合并处理
+			for (Long childNodeId : childNodes) {
+				ArchiveTreeContract childNode = archiveTreeContractClient.getArchiveTreeContractById(childNodeId);
+				if (childNode != null) {
+					// 如果指定了 contractId,检查节点是否属于该合同段
+					if (contractId != null && !contractId.equals(childNode.getContractId())) {
+						continue; // 跳过不属于指定合同段的节点
+					}
+					
+					String ancestors = childNode.getAncestors() + "," + childNodeId;
+					String strChildNodeId = childNodeId.toString();
+					
+					// 清除案卷封面等pdf
+					List<String> archivesOutUrlList = baseMapper.getArchivesOutUrlListByNode(projectId, ancestors, strChildNodeId);
+					if (archivesOutUrlList != null && archivesOutUrlList.size() > 0) {
+						for (String outUrl : archivesOutUrlList) {
+							String[] splits = outUrl.split(",");
+							for (String url : splits) {
+								if (StringUtils.isNotEmpty(url) && StringUtils.isNotEmpty(url.trim())) {
+									removeFiles.add(FileUtils.getAliYunSubUrl(url));
+								}
+							}
+						}
+						// 删除四要素文件记录
+						baseMapper.deleteIsElementFileByNode(projectId, ancestors, strChildNodeId);
+					}
+					
+					// 清除文件页面pdf
+					List<String> filePageUrlList = baseMapper.getFilePageUrlListByNode(projectId, ancestors, strChildNodeId);
+					if (filePageUrlList != null && filePageUrlList.size() > 0) {
+						for (String url : filePageUrlList) {
+							if (StringUtils.isNotEmpty(url) && StringUtils.isNotEmpty(url.trim())) {
+								removeFiles.add(FileUtils.getAliYunSubUrl(url));
+							}
+						}
+					}
+					
+					// 执行拆卷操作
+					baseMapper.splitArchviesByNode(projectId, ancestors, strChildNodeId);
+				}
+			}
+			
+			// 异步删除文件
+			if (!removeFiles.isEmpty()) {
+				executorService.execute(() -> {
+					log.info("[自动组卷-拆卷]{}", "删除旧封面,页码文件开始" + removeFiles);
+					iossClient.removeFiles(removeFiles);
+					log.info("[自动组卷-拆卷]{}", "删除旧封面,页码文件结束" + removeFiles);
+				});
+			}
+			return;
+		}
+		
 		//没合同段的默认整个项目
 		if (contractId == null) {
 			log.info("[自动组卷-拆卷]{}", "对全项目未锁定案卷拆卷");
@@ -901,6 +963,10 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 
 	@Override
 	public void archiveAutoMethod(Long projectId, Long contractId, Long nodeId, Long traceId) {
+		archiveAutoMethod(projectId, contractId, nodeId, traceId, null);
+	}
+
+	public void archiveAutoMethod(Long projectId, Long contractId, Long nodeId, Long traceId, List<Long> childNodes) {
 		//步骤一:把档号集合初始化
 		indexMap = new HashMap<>();
 		//步list = {ArrayList@18238}  size = 19骤二:查询归档树节点。存在未归档文件的节点。
@@ -919,6 +985,40 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 		//按合同段过滤
 		List<ArchiveTreeContract> authlist = authFilter(list, contractId, nodeId);
 
+		// 新增:处理 childNodes 参数
+		if (childNodes != null && !childNodes.isEmpty()) {
+			// 创建合并列表(包含原始 authlist)
+			List<ArchiveTreeContract> mergedAuthlist = new ArrayList<>();
+
+			// 遍历每个子节点ID,应用过滤并合并结果
+			for (Long childNodeId : childNodes) {
+				List<ArchiveTreeContract> childAuthlist = authFilter(list, contractId, childNodeId);
+				mergedAuthlist.addAll(childAuthlist);
+			}
+
+			// 去重处理(根据节点ID)
+			authlist = mergedAuthlist;
+		}
+		
+		// 如果 childNodes 不为空,进行祖先节点过滤
+//		if (childNodes != null && !childNodes.isEmpty()) {
+//			authlist = authlist.stream()
+//				.filter(node -> {
+//					// 检查节点的祖先是否包含 childNodes 中的任一元素
+//					if (StringUtils.isNotEmpty(node.getAncestors())) {
+//						String[] ancestors = node.getAncestors().split(",");
+//						for (String ancestor : ancestors) {
+//							if (StringUtils.isNotEmpty(ancestor.trim()) && childNodes.contains(Long.valueOf(ancestor.trim()))) {
+//								return true;
+//							}
+//						}
+//					}
+//					// 检查节点本身是否在 childNodes 中
+//					return childNodes.contains(node.getId());
+//				})
+//				.collect(Collectors.toList());
+//		}
+
 		//步骤三:遍历归档树节点整理出 默认规则节点,分类并卷节点,单独组卷节点 三个集合。
 		List<ArchiveTreeContract> list0 = new ArrayList<>();
 		List<ArchiveTreeContract> list1 = new ArrayList<>();
@@ -954,7 +1054,7 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 		archiveAutoMethod2_new(list2, listTop, projectId, boxMap, boxFileMap, traceId);//分类组卷
 		//设置完成度50%
 		projectClient.updateIsArchivesAutoById(projectId, 50);
-		archiveAutoMethod1(list1, boxMap, boxFileMap, traceId);//默认组卷
+		archiveAutoMethod3(list1, boxMap, boxFileMap, traceId);//默认组卷
 
 		//分盒组卷的且节点没配置组卷类型的
 		archiveAutoMethod0(list0, boxMap, boxFileMap);//默认组卷
@@ -963,6 +1063,8 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 		archiveAutoMethodBox(boxMap, traceId);//分盒组卷
 	}
 
+
+
 	private void addBoxMap(ArchiveFile file, Map<String, List<ArchiveFile>> boxMap, Map<Long, String> boxFileMap) {
 		String boxName = file.getBoxName();
 		if (boxFileMap.containsKey(file.getId())) {
@@ -2618,6 +2720,10 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 	 * @param nodeId
 	 */
 	public void refreshFileNumberNoSlipt(Long projectId, Long contractId, Long nodeId, boolean bforce, Long traceId) {
+		refreshFileNumberNoSlipt(projectId, contractId, nodeId, bforce, traceId, null);
+	}
+
+	public void refreshFileNumberNoSlipt(Long projectId, Long contractId, Long nodeId, boolean bforce, Long traceId, List<Long> childNodes) {
 		List<ArchiveTreeContract> list = archiveTreeContractClient.getListByProjectId(projectId);
 		if (nodeId != null) {
 			ArchiveTreeContract node = archiveTreeContractClient.getArchiveTreeContractById(nodeId);
@@ -2629,7 +2735,7 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 		Integer indexType = 0;
 		ArchiveProjectConfig config = archiveProjectConfigService.getByProjectIdOrNew(projectId);
 
-		this.refreshFileNumberNoSlipt(list, contractId, nodeId, bforce, config.getIndexType(), config.getDirType(), config.getIndexNum(), traceId);
+		this.refreshFileNumberNoSlipt(list, contractId, nodeId, bforce, config.getIndexType(), config.getDirType(), config.getIndexNum(), traceId, childNodes);
 	}
 
 	/**
@@ -2639,6 +2745,11 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 	 */
 	public void refreshFileNumberNoSlipt(List<ArchiveTreeContract> archiveTreeContracts, Long contractId, Long nodeId,
 										 boolean bforce, Integer indexType, Integer dirType, Integer indexNum, Long traceId) {
+		refreshFileNumberNoSlipt(archiveTreeContracts, contractId, nodeId, bforce, indexType, dirType, indexNum, traceId, null);
+	}
+
+	public void refreshFileNumberNoSlipt(List<ArchiveTreeContract> archiveTreeContracts, Long contractId, Long nodeId,
+										 boolean bforce, Integer indexType, Integer dirType, Integer indexNum, Long traceId, List<Long> childNodes) {
 
 		List<ArchiveTreeContractVO2> subTreeList = new ArrayList<>();
 		List<List<ArchiveTreeContract>> subGroupedList = new ArrayList<>();
@@ -2659,6 +2770,26 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 
 			//根据 authFilter函数过滤
 			subList = authFilterNum(subList, contractId, nodeId);
+			
+			// 如果 childNodes 不为空,进行祖先节点过滤
+			if (childNodes != null && !childNodes.isEmpty()) {
+				subList = subList.stream()
+					.filter(node -> {
+						// 检查节点的祖先是否包含 childNodes 中的任一元素
+						if (StringUtils.isNotEmpty(node.getAncestors())) {
+							String[] ancestors = node.getAncestors().split(",");
+							for (String ancestor : ancestors) {
+								if (StringUtils.isNotEmpty(ancestor.trim()) && childNodes.contains(Long.valueOf(ancestor.trim()))) {
+									return true;
+								}
+							}
+						}
+						// 检查节点本身是否在 childNodes 中
+						return childNodes.contains(node.getId());
+					})
+					.collect(Collectors.toList());
+			}
+			
 			List<Long> ids = subList.stream()
 					.map(ArchiveTreeContract::getId)
 					.collect(Collectors.toList());
@@ -3284,10 +3415,17 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 
 	@Override
 	public void archiveAutoMethodThread(Long projectId, Long contractId, Long nodeId, Long traceId) {
+		archiveAutoMethodThread(projectId, contractId, nodeId, traceId, null);
+	}
+
+	public void archiveAutoMethodThread(Long projectId, Long contractId, Long nodeId, Long traceId, List<Long> childNodes) {
 		executorService.execute(() -> {
 			try {
 				// 将项目未锁定案卷拆卷
 				String startSplitMsg = "[自动组卷] 开始对未锁定案卷拆卷。projectId:" + projectId + "-contractId:" + contractId + "-nodeId:" + nodeId + "-traceId:" + traceId;
+				if (childNodes != null && !childNodes.isEmpty()) {
+					startSplitMsg += "-childNodes:" + childNodes;
+				}
 				//log.info(startSplitMsg);
 				iTraceLogService.saveLog(traceId, startSplitMsg);
 
@@ -3296,31 +3434,49 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 				//     return;
 				// }
 
-				splitArchvies(projectId, contractId, nodeId);
+				splitArchvies(projectId, contractId, nodeId, childNodes);
 				// 设置完成度10%
 				contractClient.updateIsArchivesAutoById(contractId, 10);
 
 				// 项目自动组卷入口
 				String startAutoArchiveMsg = "[自动组卷] 开始自动组卷。projectId:" + projectId + "-contractId:" + contractId + "-nodeId:" + nodeId + "-traceId:" + traceId;
+				if (childNodes != null && !childNodes.isEmpty()) {
+					startAutoArchiveMsg += "-childNodes:" + childNodes;
+				}
 				//log.info(startAutoArchiveMsg);
 				iTraceLogService.saveLog(traceId, startAutoArchiveMsg);
 
 				archiveAutoPdfService.assignArchiveTableUrl();
 
-				archiveAutoMethod(projectId, contractId, nodeId, traceId);
+				if (childNodes != null && !childNodes.isEmpty()) {
+					archiveAutoMethod(projectId, contractId, nodeId, traceId, childNodes);
+				} else {
+					archiveAutoMethod(projectId, contractId, nodeId, traceId);
+				}
 				// 设置完成度80%
 				contractClient.updateIsArchivesAutoById(contractId, 80);
 
 				// 刷新项目档号
 				String startRefreshMsg = "[自动组卷] 开始刷新组卷档号。projectId:" + projectId + "-contractId:" + contractId + "-nodeId:" + nodeId + "-traceId:" + traceId;
+				if (childNodes != null && !childNodes.isEmpty()) {
+					startRefreshMsg += "-childNodes:" + childNodes;
+				}
 				//log.info(startRefreshMsg);
 				iTraceLogService.saveLog(traceId, startRefreshMsg);
 
 				refreshFileNumberNoSlipt(projectId, contractId, nodeId, true, traceId);
+//				if (childNodes != null && !childNodes.isEmpty()) {
+//					refreshFileNumberNoSlipt(projectId, contractId, nodeId, true, traceId, childNodes);
+//				} else {
+//					refreshFileNumberNoSlipt(projectId, contractId, nodeId, true, traceId);
+//				}
 
 				// 设置自动组卷结束
 				contractClient.updateIsArchivesAutoById(contractId, 0);
 				String completeMsg = "[自动组卷] 自动组卷完成。projectId:" + projectId + "-contractId:" + contractId + "-nodeId:" + nodeId + "-traceId:" + traceId;
+				if (childNodes != null && !childNodes.isEmpty()) {
+					completeMsg += "-childNodes:" + childNodes;
+				}
 				//log.info(completeMsg);
 				iTraceLogService.saveLog(traceId, completeMsg);
 
@@ -4678,7 +4834,8 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 		for (ArchivesAutoVO4 v : value) {
 			ArchivesAuto auto = new ArchivesAuto();
 			if (config.getIndexType() == null || config.getIndexType() == 0) {
-				v.setFileNumber(v.getFileNumberPrefix() + "_" + i);
+				v.setFileNumber(v.getFileNumberPrefix() + "—" + i);
+
 			} else {
 				String prefix = v.getFileNumberPrefix();
 				int index = i; // 编号从 startNumber 开始
@@ -4691,7 +4848,7 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 				// 使用 %0xd 格式化数字,x 表示总长度
 				String formattedIndex = String.format("%0" + numLength + "d", index);
 				// 设置最终档号
-				v.setFileNumber(prefix + "_" + formattedIndex);
+				v.setFileNumber(prefix + "" + formattedIndex);
 			}
 			auto.setId(v.getId());
 			auto.setFileNumber(v.getFileNumber());
@@ -5001,6 +5158,19 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 		autoMapper.removeFilesByArchiveIds(archiveIds);
 	}
 
+	@Override
+	@Async
+	public void reCreateArchiveAuto1(String ids) {
+		for (Long id : Func.toLongList(ids)) {
+			//先查出勾选的案卷
+			ArchivesAuto archivesAuto=this.getById(id);
+			//查出所有案卷文件
+			List<ArchiveFile>archiveFileList=archiveFileClient.getArchiveFileByArchiveIds(id+"");
+			//设置案卷页码和四要素
+			this.reCreateArchiveAuto(archivesAuto, archiveFileList);
+		}
+	}
+
 	/**
 	 * 分组适配方法:按nodeId分组文件并进行页数合并处理
 	 *

+ 520 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ScanFileServiceImpl.java

@@ -0,0 +1,520 @@
+package org.springblade.archive.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import org.springblade.archive.entity.ArchivesAuto;
+import org.springblade.archive.entity.ScanFile;
+import org.springblade.archive.entity.ScanFolder;
+import org.springblade.archive.mapper.ScanFileMapper;
+import org.springblade.archive.mapper.ScanFolderMapper;
+import org.springblade.archive.service.ScanFileService;
+import org.springblade.archive.utils.FileUtils;
+import org.springblade.archive.vo.ScanFolderVO;
+import org.springblade.business.entity.ArchiveFile;
+import org.springblade.business.feign.ArchiveFileClient;
+import org.springblade.common.utils.SnowFlakeUtil;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.oss.model.BladeFile;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.manager.entity.ArchiveTreeContract;
+import org.springblade.manager.feign.ArchiveTreeContractClient;
+import org.springblade.resource.feign.NewIOSSClient;
+import org.springframework.beans.BeanUtils;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+
+@Service
+@AllArgsConstructor
+public class ScanFileServiceImpl  extends ServiceImpl<ScanFileMapper, ScanFile> implements ScanFileService {
+//    //文件根目录
+//    private static final String ROOT_PREFIX = "D:" + File.separator + "ScanPDF";
+//    //备份文件根目录
+//    private static final String ROOT_PREFIX_back = "D:" + File.separator + "ScanBackPDF";
+    //文件根目录
+    private static final String ROOT_PREFIX = "/mnt/sdc/ScanPDF";
+    //备份文件根目录
+    private static final String ROOT_PREFIX_back = "/mnt/sdc/ScanBackPDF";
+
+    private final ScanFolderMapper scanFolderMapper;
+    private final ScanFileMapper scanFileMapper;
+    private final NewIOSSClient newIOSSClient;
+    private final ArchiveTreeContractClient archiveTreeContractClient;
+    private final JdbcTemplate jdbcTemplate;
+    private final ArchiveFileClient archiveFileClient;
+
+
+
+
+    @Override
+    public void scanAndSave(Long contractId, Long projectId) {
+        //扫描文件夹
+        scanAndSaveFolder(contractId, projectId);
+        //扫描文件
+        scanAndSaveFiles(contractId,projectId);
+        //整理编号,序号
+        sortNumber(contractId, projectId);
+    }
+
+    @Override
+    public List<ScanFolderVO> getScanFolder(Long contractId, Long projectId) {
+        List<ScanFolder> list = scanFileMapper.getScanFolder(contractId,projectId);
+        // 将ScanFolder转换为ScanFolderVO
+        List<ScanFolderVO> voList = list.stream().map(scanFolder -> {
+            ScanFolderVO vo = new ScanFolderVO();
+            // 复制属性
+            BeanUtils.copyProperties(scanFolder, vo);
+            // 初始化子节点列表
+            vo.setChilds(new ArrayList<>());
+            // 默认为没有子节点,后续会重新判断
+            vo.setHasChildren(false);
+            return vo;
+        }).collect(Collectors.toList());
+
+        // 构建父子关系
+        Map<Long, ScanFolderVO> voMap = voList.stream()
+                .collect(Collectors.toMap(ScanFolderVO::getId, v -> v));
+
+        List<ScanFolderVO> result = new ArrayList<>();
+
+        for (ScanFolderVO vo : voList) {
+            Long parentId = vo.getParentId();
+            if (parentId == null || parentId == 0) {
+                // 没有父节点的作为根节点
+                result.add(vo);
+            } else {
+                // 有父节点的添加到对应父节点的子列表中
+                ScanFolderVO parent = voMap.get(parentId);
+                if (parent != null) {
+                    parent.getChilds().add(vo);
+                    parent.setHasChildren(true); // 标记父节点有子节点
+                }
+            }
+        }
+
+        return result;
+
+    }
+
+    @Override
+    public IPage<ScanFile> getScanFile(Long contractId, Long projectId, Long folderId, Query query,Integer move) {
+        IPage<ScanFile> page = new Page<>(query.getCurrent(), query.getSize());
+        page=baseMapper.getScanFile(page,contractId,projectId,folderId,move);
+        return page;
+    }
+
+    @Override
+    public String deleteScanFile(String ids) {
+        List<Long> longList = Func.toLongList(ids);
+        List<ScanFile> scanFiles = baseMapper.selectList(new LambdaQueryWrapper<>(ScanFile.class).in(ScanFile::getId, longList));
+        List<String> fileNames = scanFiles.stream().filter(o-> !StringUtil.isBlank(o.getFileName())).map(o -> o.getFileName()).collect(Collectors.toList());
+        baseMapper.removeScan(longList);
+        newIOSSClient.removeFiles(fileNames);
+        return "";
+    }
+
+    @Override
+    public Boolean autoRecognize(String ids) {
+        List<Long> longList = Func.toLongList(ids);
+        List<ScanFile> scanFiles = baseMapper.selectList(new LambdaQueryWrapper<>(ScanFile.class).in(ScanFile::getId, longList));
+        return true;
+    }
+
+
+    @Override
+    public boolean moveScanFile(List<Long> ids, Long nodeId) {
+        try {
+            List<ScanFile> scanFiles = baseMapper.selectList(new LambdaQueryWrapper<>(ScanFile.class).in(ScanFile::getId, ids));
+            ArchiveTreeContract contract = archiveTreeContractClient.getArchiveTreeContractById(nodeId);
+            String sql="select IFNULL(max(sort),0) from u_archive_file where project_id="+contract.getProjectId()+" and contract_id="+contract.getContractId()+" and is_deleted=0";
+            Integer sort = jdbcTemplate.queryForObject(sql, Integer.class);
+            List<ArchiveFile>list=new ArrayList<>();
+            if(contract!=null){
+                for (ScanFile file : scanFiles) {
+                    ArchiveFile archiveFile = new ArchiveFile();
+                    archiveFile.setId(SnowFlakeUtil.getId());
+                    archiveFile.setProjectId(contract.getProjectId()+"");
+                    archiveFile.setContractId(contract.getContractId()+"");
+                    archiveFile.setNodeId(nodeId+"");
+                    archiveFile.setFileNumber(file.getFileNum());
+                    archiveFile.setFileName(file.getFileName());
+                    archiveFile.setFileTime(file.getFileDate());
+                    archiveFile.setFileUrl(file.getOssUrl());
+                    archiveFile.setPdfFileUrl(file.getOssUrl());
+                    archiveFile.setFilePage(file.getFileSize()!=null?Integer.parseInt(file.getFileSize()):0);
+                    archiveFile.setIsApproval(0);
+                    archiveFile.setIsNeedCertification(0);
+                    archiveFile.setIsCertification(0);
+                    archiveFile.setDutyUser(file.getResponsible());
+                    archiveFile.setIsArchive(0);
+                    archiveFile.setSourceType(2);
+                    archiveFile.setIsElement(0);
+                    archiveFile.setClassify(contract.getClassify());
+                    archiveFile.setNodeTreeStructure(contract.getTreeStructure());
+                    archiveFile.setSort(sort++);
+                    list.add(archiveFile);
+                }
+                archiveFileClient.saveBatchArchiveFile(list);
+                for (ScanFile file : scanFiles) {
+                    file.setIsMove(1);
+                }
+                this.updateBatchById(scanFiles);
+            }
+            return true;
+        }catch (Exception e){
+            return false;
+        }
+    }
+
+    /**
+     * 入口方法:扫描并入库指定contractId的所有文件夹
+     * @param contractId 传入的合同ID(对应D:\PDF下的文件夹名)
+     * @param projectId 传入的项目ID
+     */
+    public R scanAndSaveFolder(Long contractId, Long projectId) {
+        // 1. 构建合同对应的根文件夹路径(D:\PDF\${contractId})
+        String contractFolderPath = ROOT_PREFIX + File.separator + contractId;
+        System.out.println("contractFolderPath=" + contractFolderPath);
+        File contractFolder = new File(contractFolderPath);
+
+        // 校验根文件夹是否存在且为目录
+        if (!contractFolder.exists() || !contractFolder.isDirectory()) {
+            return R.fail("错误:根文件夹不存在或非法,路径=" + contractFolderPath);
+        }
+
+        // 2. 扫描一级文件夹(parent_id=0)并入库,同时递归子文件夹
+        File[] firstLevelFolders = contractFolder.listFiles(File::isDirectory);
+        if (firstLevelFolders == null || firstLevelFolders.length == 0) {
+            return R.fail("提示:合同ID=" + contractId + "的根文件夹下无任何子文件夹");
+        }
+
+        // 遍历一级文件夹,逐个入库并递归子目录
+        for (File firstLevelFolder : firstLevelFolders) {
+            String folderName = firstLevelFolder.getName();
+            Long folderId = scanFolderMapper.getId(folderName,contractId,projectId);
+            if(folderId==null){
+                folderId=SnowFlakeUtil.getId(); // 生成随机ID
+            }
+            String filePath = firstLevelFolder.getAbsolutePath();
+            // 创建一级目录的ScanFolder对象(parent_id=0)
+            ScanFolder firstLevelScanFolder = new ScanFolder(
+                    folderId,
+                    0L,          // 一级目录父ID固定为0
+                    projectId,
+                    contractId,
+                    folderName,
+                    filePath,
+                    0
+            );
+                 // 入库一级目录
+                 checkInsertScanFolder(firstLevelScanFolder);
+                // 递归扫描当前一级目录下的所有子文件夹(子目录的父ID=当前一级目录ID)
+                recursiveScanSubFolders(firstLevelFolder, folderId, projectId, contractId);
+
+        }
+        return R.success("扫描并入库成功");
+    }
+
+    /**
+     * 递归扫描子文件夹并入库
+     * @param parentFolder 父文件夹对象(当前要扫描子目录的文件夹)
+     * @param parentFolderId 父文件夹在数据库中的ID(子目录的parent_id)
+     * @param projectId 项目ID(透传)
+     * @param contractId 合同ID(透传)
+     */
+    private void recursiveScanSubFolders(File parentFolder, Long parentFolderId, Long projectId, Long contractId) {
+        // 获取父文件夹下的所有子文件夹
+        File[] subFolders = parentFolder.listFiles(File::isDirectory);
+        if (subFolders == null || subFolders.length == 0) {
+            System.out.println("递归终止:父ID=" + parentFolderId + "(名称=" + parentFolder.getName() + ")下无子文件夹");
+            return;
+        }
+
+        // 遍历每个子文件夹,入库并继续递归其下的子目录
+        for (File subFolder : subFolders) {
+            String subFolderName = subFolder.getName();
+            Long subFolderId = scanFolderMapper.getId(subFolderName,contractId,projectId);
+            if(subFolderId==null){
+                subFolderId=SnowFlakeUtil.getId(); // 生成随机ID
+            }
+            String filePath = subFolder.getAbsolutePath();
+
+            // 创建子目录的ScanFolder对象(parent_id=父文件夹ID)
+            ScanFolder subScanFolder = new ScanFolder(
+                    subFolderId,
+                    parentFolderId, // 关联父目录ID
+                    projectId,
+                    contractId,
+                    subFolderName,
+                    filePath,
+                    0
+            );
+                // 入库子目录
+                 checkInsertScanFolder(subScanFolder);
+                // 递归扫描当前子目录下的子文件夹
+                recursiveScanSubFolders(subFolder, subFolderId, projectId, contractId);
+        }
+    }
+
+    /**
+     * 检查文件夹是否存在,不存在则新增
+     * @param scanFolder
+     * @return
+     */
+    public boolean checkInsertScanFolder(ScanFolder scanFolder) {
+        int result = scanFolderMapper.exists(scanFolder.getProjectId(), scanFolder.getContractId(), scanFolder.getFolderName());
+        if(result>0){
+            return false;
+        }else {
+            scanFolderMapper.insert(scanFolder);
+        }
+        return true;
+    }
+
+
+    /**
+     * 扫描文件并入库
+     * @param contractId
+     * @param projectId
+     */
+    public R scanAndSaveFiles(Long contractId, Long projectId) {
+        // 1. 构建合同对应的根文件夹路径(D:\PDF\${contractId})
+        String contractFolderPath = ROOT_PREFIX + File.separator + contractId;
+        System.out.println("contractFolderPath=" + contractFolderPath);
+        File contractFolder = new File(contractFolderPath);
+
+        // 校验根文件夹是否存在且为目录
+        if (!contractFolder.exists() || !contractFolder.isDirectory()) {
+            return R.fail("错误:根文件夹不存在或非法,路径=" + contractFolderPath);
+        }
+
+        System.out.println("开始扫描合同ID=" + contractId + "下的所有文件...");
+        // 递归扫描所有文件
+        recursiveScanFiles(contractFolder, projectId, contractId);
+        System.out.println("合同ID=" + contractId + "的文件扫描入库完成");
+        return R.success("扫描并入库完成");
+    }
+
+    @Override
+    public void sortNumber(Long contractId, Long projectId) {
+        List<ScanFile> scanFiles = scanFileMapper.selectList(new LambdaQueryWrapper<ScanFile>().eq(ScanFile::getContractId, contractId).eq(ScanFile::getProjectId, projectId).isNull(ScanFile::getSort).isNull(ScanFile::getDigitalNum).orderByAsc(ScanFile::getCreatTime));
+        Integer maxDigitalNum=scanFileMapper.selectMaxDigitalNum(contractId,projectId);
+        if(maxDigitalNum==null||maxDigitalNum<1){
+            maxDigitalNum=0;
+        }
+        Map<Long, List<ScanFile>> scanFilesMap = scanFiles.stream()
+                .collect(Collectors.groupingBy(ScanFile::getFolderId));
+        for (Map.Entry<Long, List<ScanFile>> entry : scanFilesMap.entrySet()) {
+            Integer MaxSort=scanFileMapper.selectMaxSort(contractId,projectId,entry.getKey());
+            if(MaxSort==null||MaxSort<1){
+                MaxSort=0;
+            }
+            List<ScanFile> list = entry.getValue();
+            for (ScanFile scanFile : list) {
+                scanFile.setSort(++MaxSort);
+                scanFile.setDigitalNum(++maxDigitalNum);
+                scanFileMapper.updateById(scanFile);
+            }
+        }
+    }
+
+
+    /**
+     * 递归扫描文件夹中的所有文件并入库
+     * @param currentFolder 当前扫描的文件夹
+     * @param projectId 项目ID
+     * @param contractId 合同ID
+     */
+    private void recursiveScanFiles(File currentFolder, Long projectId, Long contractId) {
+        // 1. 先处理当前文件夹中的文件
+        File[] files = currentFolder.listFiles(File::isFile);
+        if (files != null && files.length > 0) {
+            for (File file : files) {
+                processFile(file, currentFolder, projectId, contractId);
+            }
+        }
+
+        // 2. 递归处理子文件夹
+        File[] subFolders = currentFolder.listFiles(File::isDirectory);
+        if (subFolders != null && subFolders.length > 0) {
+            for (File subFolder : subFolders) {
+                recursiveScanFiles(subFolder, projectId, contractId);
+            }
+        }
+    }
+
+    /**
+     * 处理单个文件:获取信息并入库
+     */
+    private void processFile(File file, File parentFolder, Long projectId, Long contractId) {
+        String fileMD5 = calculateFileMD5(file);
+        try {
+            // 获取文件创建时间
+            BasicFileAttributes attr = Files.readAttributes(file.toPath(), BasicFileAttributes.class);
+            Date date = new Date(attr.creationTime().toMillis());
+            SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            String createTime = sdf1.format(date); // 2023-12-25 14:30:45
+            // 获取文件信息
+            String fileName = file.getName();
+            String filePath = file.getAbsolutePath();
+            String folderName = parentFolder.getName();
+            // 查询对应的folder_id
+            Long folderId = scanFolderMapper.getId(folderName,contractId,projectId);
+            if (folderId == null) {
+                System.err.println("警告:未找到对应的文件夹记录,跳过文件:" + filePath +
+                        " (folderName=" + folderName + ")");
+                return;
+            }
+            // 检查文件是否已存在(根据project_id, contract_id, file_path判断)
+            if (fileExists(projectId, contractId, folderName)) {
+                System.out.println("文件已存在,跳过:" + filePath);
+                return;
+            }
+            BladeFile bladeFile = newIOSSClient.uploadFile(fileName, filePath);
+            if(bladeFile==null){
+                return;
+            }
+            String oSSFileMD5 = calculateOSSFileMD5(bladeFile.getLink());
+            if (fileMD5 != null && !fileMD5.equals(oSSFileMD5)) {
+                return;
+            }
+            String pdfNum = FileUtils.getPdfNum(bladeFile.getLink());
+            // 生成随机ID
+            Long fileId = SnowFlakeUtil.getId();
+            // 创建文件实体
+            ScanFile scanFile = new ScanFile(
+                    fileId,
+                    projectId,
+                    contractId,
+                    folderId,
+                    null,//序号
+                    null,//数字编号
+                    null,//文件编号
+                    fileName,
+                    pdfNum,//文件页数
+                    null,//文件日期
+                    createTime,
+                    filePath,
+                    bladeFile.getLink(),//OSS路径
+                    null,//负责人
+                    0,
+                    0
+            );
+            // 入库
+            if (scanFileMapper.insert(scanFile)>0) {
+                System.out.println("文件入库成功:" + filePath);
+                try {
+                    // 构建备份文件路径,保持原有的文件夹结构
+                    if (filePath.startsWith(ROOT_PREFIX)) {
+                        // 截取原始路径中除根目录外的部分
+                        String relativePath = filePath.substring(ROOT_PREFIX.length());
+                        System.out.println("relativePath: " + relativePath);
+                        // 构建备份文件完整路径
+                        String backupFilePath = ROOT_PREFIX_back + relativePath;
+                        System.out.println("backupFilePath: " + backupFilePath);
+                        File backupFile = new File(backupFilePath);
+                        // 创建备份文件所在的目录
+                        File backupDir = backupFile.getParentFile();
+                        if (!backupDir.exists()) {
+                            boolean dirCreated = backupDir.mkdirs();
+                            if (!dirCreated) {
+                                throw new IOException("无法创建备份目录: " + backupDir.getAbsolutePath());
+                            }
+                        }
+                        // 复制文件到备份目录
+                        Files.copy(file.toPath(), backupFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+                        String fileMD5New = calculateFileMD5(backupFile);
+                        if (fileMD5New != null && fileMD5New.equals(fileMD5)) {
+                            // 备份成功后删除源文件
+                            file.delete();
+                        }
+                    }
+                } catch (IOException e) {
+                    System.err.println("文件备份失败:" + filePath + ",错误信息:" + e.getMessage());
+                }
+            }
+        } catch (IOException e) {
+            System.err.println("处理文件时出错:" + file.getAbsolutePath() + ",错误信息:" + e.getMessage());
+        }
+    }
+    private Boolean fileExists(Long projectId, Long contractId, String folderName){
+        Integer i = scanFileMapper.exists(projectId, contractId, folderName);
+        if(i==1){
+            return true;
+        }else {
+            return false;
+        }
+    }
+
+    private String calculateFileMD5(File file) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            try (InputStream is = Files.newInputStream(file.toPath());
+                 DigestInputStream dis = new DigestInputStream(is, md)) {
+                byte[] buffer = new byte[8192];
+                while (dis.read(buffer) != -1) {
+                    // 读取文件内容以计算MD5
+                }
+            }
+            byte[] digest = md.digest();
+            StringBuilder sb = new StringBuilder();
+            for (byte b : digest) {
+                sb.append(String.format("%02x", b));
+            }
+            return sb.toString();
+        } catch (Exception e) {
+            System.err.println("计算本地文件MD5失败:" + file.getAbsolutePath() + ",错误信息:" + e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 计算OSS文件的MD5值
+     * @param ossUrl OSS文件路径
+     * @return MD5值,如果出错返回null
+     */
+    private String calculateOSSFileMD5(String ossUrl) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+
+            // 使用URL下载OSS文件
+            URL url = new URL(ossUrl);
+            try (InputStream is = url.openStream();
+                 DigestInputStream dis = new DigestInputStream(is, md)) {
+                byte[] buffer = new byte[8192];
+                while (dis.read(buffer) != -1) {
+                    // 读取文件内容以计算MD5
+                }
+            }
+
+            byte[] digest = md.digest();
+            StringBuilder sb = new StringBuilder();
+            for (byte b : digest) {
+                sb.append(String.format("%02x", b));
+            }
+            return sb.toString();
+        } catch (Exception e) {
+            System.err.println("计算OSS文件MD5失败:" + ossUrl + ",错误信息:" + e.getMessage());
+            return null;
+        }
+    }
+}

+ 16 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ScanFolderServiceImpl.java

@@ -0,0 +1,16 @@
+package org.springblade.archive.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.AllArgsConstructor;
+import org.springblade.archive.entity.ArchivesAuto;
+import org.springblade.archive.entity.ScanFolder;
+import org.springblade.archive.mapper.ArchivesAutoMapper;
+import org.springblade.archive.service.ScanFolderService;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.manager.entity.WbsTreeSynchronousRecord;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class ScanFolderServiceImpl  implements ScanFolderService {
+}

+ 18 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/utils/FileUtils.java

@@ -9,6 +9,7 @@ import com.sun.image.codec.jpeg.JPEGImageEncoder;
 import lombok.extern.slf4j.Slf4j;
 
 import org.apache.commons.lang.StringUtils;
+import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.poi.hssf.usermodel.HSSFPrintSetup;
 import org.apache.poi.ss.usermodel.ClientAnchor;
 import org.apache.poi.ss.usermodel.Sheet;
@@ -879,4 +880,21 @@ public class FileUtils {
         return String.valueOf(pageNumber);
     }
 
+    public static String getPdfNum(String url) {
+        try {
+            if (url.isEmpty() || url.equals("")) {
+                return "0";
+            }
+            InputStream pdfInputStream = CommonUtil.getOSSInputStream(url);
+            //获取这份文件的页数并设置签章策略
+            //获取PDF文件
+            PDDocument document = PDDocument.load(pdfInputStream);
+            int page = document.getPages().getCount();
+            return page + "";
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
 }

+ 72 - 28
blade-service/blade-business/src/main/java/org/springblade/business/controller/InformationWriteQueryController.java

@@ -234,16 +234,7 @@ public class InformationWriteQueryController extends BladeController {
                 String sql="select * from m_wbs_tree_contract where p_key_id="+query.getWbsId()+" and is_deleted=0";
                 WbsTreeContract contract = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(WbsTreeContract.class));
                 if(dto.getType()==1){
-                    R<List<String>>  nameRuleList = wbsParamClient.getNameRuleByPkeyId(contract.getPKeyId(), Long.valueOf(contract.getProjectId()));
-                    String nameRule = "";
-                    if (nameRuleList != null && nameRuleList.getData() != null && !nameRuleList.getData().isEmpty()) {
-                        nameRule = String.join("-", nameRuleList.getData());
-                    }
-                    if(projectInfos.get(0)!=null&&projectInfos.get(0).getTemplateType()==2){
-                        result  = nodeBaseInfoServiceClient.getNameRuleByRule(nameRule, contract.getPKeyId()+"");
-                    }else {
-                        result=wbsParamClient.createFileTitle(contract);
-                    }
+                    result=wbsParamClient.createFileTitle(contract);
                 }else {
                     result=nodeNameMap.get(query.getId());
                     //同时修改工程划分节点命名规则
@@ -256,12 +247,16 @@ public class InformationWriteQueryController extends BladeController {
                         result="";
                     }
                     if(query.getClassify()!=null&&query.getClassify()==1&&StringUtils.isNotEmpty(sgSuffix)){
-                        result=result+sgSuffix;
+                        if(!result.contains(sgSuffix)){
+                            result=result+sgSuffix;
+                        }
                         if(!query.getName().equals(result)){
                             query.setName(result);
                         }
                     }else if(query.getClassify()!=null&&query.getClassify()==2&&StringUtils.isNotEmpty(jlSuffix)){
-                        result=result+jlSuffix;
+                        if(!result.contains(jlSuffix)){
+                            result=result+jlSuffix;
+                        }
                         if(!query.getName().equals(result)){
                             query.setName(result);
                         }
@@ -408,17 +403,37 @@ public class InformationWriteQueryController extends BladeController {
     })
     public R<List<PreviewNodeNameVO1>> previewNodeName(@RequestBody List<PreviewNodeNameDTO1>dtos){
         List<PreviewNodeNameVO1> list = new ArrayList<>();
+        String sql1="Select sg_suffix,jl_suffix,template_type from m_project_info where id="+dtos.get(0).getProjectId()+" and is_deleted=0";
+        List<ProjectInfo> projectInfos = jdbcTemplate.query(sql1, new BeanPropertyRowMapper<>(ProjectInfo.class));
+        String sgSuffix="";
+        String jlSuffix="";
+        if(projectInfos.size()>0){
+            sgSuffix=projectInfos.get(0).getSgSuffix()==null?"":projectInfos.get(0).getSgSuffix();
+            jlSuffix=projectInfos.get(0).getJlSuffix()==null?"":projectInfos.get(0).getJlSuffix();
+        }
         for (PreviewNodeNameDTO1 dto1 : dtos) {
             if(StringUtils.isEmpty(dto1.getNameRule())){
                 throw new ServiceException("请输入题名规则");
             }
-           String result=nodeBaseInfoServiceClient.getNameRuleByRule(dto1.getNameRule(),dto1.getWbsId());
-//            String sql="select p_key_id,ancestors_p_id from m_wbs_tree_contract where p_key_id="+dto1.getWbsId()+" and is_deleted=0";
-//            WbsTreeContract contract = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(WbsTreeContract.class));
-//            String result="";
-//            if(contract!=null){
-//                result= createFileTitle(contract, dto1.getNameRule());
-//            }
+//           String result=nodeBaseInfoServiceClient.getNameRuleByRule(dto1.getNameRule(),dto1.getWbsId());
+            String sql="select p_key_id,ancestors_p_id,major_data_type,table_owner from m_wbs_tree_contract where p_key_id="+dto1.getWbsId()+" and is_deleted=0";
+            WbsTreeContract contract = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(WbsTreeContract.class));
+            String sql2="select IFNULL(classify,0) from u_information_query where id="+dto1.getId();
+            Integer classify = jdbcTemplate.queryForObject(sql2, Integer.class);
+            String result="";
+           if(contract!=null){
+                result= createFileTitle(contract, dto1.getNameRule());
+               if(contract!=null&&contract.getMajorDataType()!=null&&contract.getMajorDataType()==4){
+                   if(result==null){
+                       result="";
+                   }
+                   if(classify==1&&StringUtils.isNotEmpty(sgSuffix)){
+                       result=result+sgSuffix;
+                   }else if(classify==2&&StringUtils.isNotEmpty(jlSuffix)){
+                       result=result+jlSuffix;
+                   }
+               }
+            }
             list.add(new PreviewNodeNameVO1(dto1.getWbsId(),result.toString()));
         }
         return R.data(list);
@@ -430,11 +445,34 @@ public class InformationWriteQueryController extends BladeController {
         List<PreviewNodeNameVO>list=new ArrayList<>();
         for (PreviewNodeNameDTO dto : dtos) {
             List<String> nameRules = getNameRuleByPkeyId(dto.getWbsId(), dto.getProjectId());
-            String sql="select p_key_id,ancestors_p_id from m_wbs_tree_contract where p_key_id="+dto.getWbsId()+" and is_deleted=0";
+            String sql="select p_key_id,ancestors_p_id,major_data_type from m_wbs_tree_contract where p_key_id="+dto.getWbsId()+" and is_deleted=0";
             WbsTreeContract contract = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(WbsTreeContract.class));
+            String sql2="select IFNULL(classify,0) from u_information_query where id="+dto.getId();
+            Integer classify = jdbcTemplate.queryForObject(sql2, Integer.class);
             String result="";
             if(nameRules!=null){
-                result = nodeBaseInfoServiceClient.getNameRuleByRule(String.join("-", nameRules), contract.getPKeyId() + "");
+                result= createFileTitle(contract, String.join("-", nameRules));
+                //result = nodeBaseInfoServiceClient.getNameRuleByRule(String.join("-", nameRules), contract.getPKeyId() + "");
+                if(contract!=null){
+                    String sql1="Select sg_suffix,jl_suffix,template_type from m_project_info where id="+dtos.get(0).getProjectId()+" and is_deleted=0";
+                    List<ProjectInfo> projectInfos = jdbcTemplate.query(sql1, new BeanPropertyRowMapper<>(ProjectInfo.class));
+                    String sgSuffix="";
+                    String jlSuffix="";
+                    if(projectInfos.size()>0){
+                        sgSuffix=projectInfos.get(0).getSgSuffix()==null?"":projectInfos.get(0).getSgSuffix();
+                        jlSuffix=projectInfos.get(0).getJlSuffix()==null?"":projectInfos.get(0).getJlSuffix();
+                    }
+                    if(contract!=null&&contract.getMajorDataType()!=null&&contract.getMajorDataType()==4){
+                        if(result==null){
+                            result="";
+                        }
+                        if(classify==1&&StringUtils.isNotEmpty(sgSuffix)){
+                            result=result+sgSuffix;
+                        }else if(classify==2&&StringUtils.isNotEmpty(jlSuffix)){
+                            result=result+jlSuffix;
+                        }
+                    }
+                }
             }
             list.add(new PreviewNodeNameVO(dto.getWbsId(),result,nameRules));
         }
@@ -1252,6 +1290,9 @@ public class InformationWriteQueryController extends BladeController {
                             oldHtml.setId(SnowFlakeUtil.getId());
                             oldHtml.setCreateUser(getUser().getUserId());
                             String htmlUrl = wbsTreeContract.getHtmlUrl();
+                            if (htmlUrl ==  null || htmlUrl.isEmpty()|| "null".equals(htmlUrl)) {
+                                continue;
+                            }
                             // 获取或下载文件
                             Path sourcePath = FileUtils.getOrDownloadFile(htmlUrl);
                             // 生成副本路径
@@ -1697,7 +1738,7 @@ public R<String> batchDownloadFileToZip(String ids, HttpServletResponse response
                 String sql = "";
                 String sql1 = "";
                 if (primaryKeyId != null && primaryKeyId.indexOf("*") > 0) {
-                    primaryKeyId = primaryKeyId.substring(0, primaryKeyId.length() - 1);
+                    primaryKeyId=primaryKeyId.replaceAll("\\*","");
                     sql = "update u_trial_self_inspection_record set task_status = '未上报' where id=" + primaryKeyId;
                     sql1 = "SELECT e.* from u_entrust_info e left join u_trial_self_inspection_record t on t.entrust_id=e.id where t.id=" + primaryKeyId;
                 } else {
@@ -2749,7 +2790,7 @@ private String reviseCols(Map<String, String> p2, String cols, Long pkeyId, Stri
                     if (!map1.isEmpty()) {
                         int i = target.indexOf("key_201");
                         if (i >= 0) {
-                            target.set(i, DataStructureFormatUtils.buildDataObject(map1));
+                            target.set(i, " '" + DataStructureFormatUtils.buildDataObject(map1) + "' ");
                         }
                     }
                 }
@@ -4005,7 +4046,7 @@ public R<Boolean> saveContractTreeNode(@RequestBody AddContractTreeNodeVO vo) {
 
                 //处理重复的数据
                 selectedNodeList = selectedNodeList.stream().collect(Collectors.collectingAndThen(
-                    Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(WbsTreePrivate::getId))),
+                    Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(WbsTreePrivate::getPKeyId))),
                     ArrayList::new
                 ));
 
@@ -4118,11 +4159,11 @@ public R<Boolean> saveContractTreeNode(@RequestBody AddContractTreeNodeVO vo) {
                 WbsTreeContract newData = new WbsTreeContract();
                 BeanUtils.copyProperties(half, newData);
 
-            //重塑pKeyId、id和parentId
-            newData.setPKeyId(OldPKeyIdToNewPKeyIdMap.get(half.getPKeyId()));
-            if (new Integer("1").equals(half.getType())) {
+              //重塑pKeyId、id和parentId
+              newData.setPKeyId(OldPKeyIdToNewPKeyIdMap.get(half.getPKeyId()));
+              if (new Integer("1").equals(half.getType())) {
                 newData.setId(OldIdToNewIdMap.containsKey(half.getId()) ? OldIdToNewIdMap.get(half.getId()) : SnowFlakeUtil.getId());
-            }
+              }
 
                 //设置父级id
                 boolean var = true;
@@ -4418,6 +4459,9 @@ public void updateTextDictInfos(List<WbsTreeContract> nowTabs, List<Long> oldTab
                 nowTab.setExcelId(tab.getExcelId());
                 nowTab.setIsTypePrivatePid(tab.getPKeyId());
                 nowTab.setInitTableName(tab.getInitTableName());
+                if(nowTab.getHtmlUrl()==null||nowTab.getHtmlUrl().equals("null")){
+                    nowTab.setHtmlUrl("");
+                }
                 String updateSql = "update m_wbs_tree_contract set html_url = '" + nowTab.getHtmlUrl() + "',excel_id = " + nowTab.getExcelId() + ",init_table_name = '" + nowTab.getInitTableName() + "',is_type_private_pid = " + nowTab.getIsTypePrivatePid() + " where p_key_id = " + nowTab.getPKeyId();
                 resultSQL.add(updateSql);
             }

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

@@ -76,7 +76,7 @@ public class NeiWaiYeProgressController {
                     queryProcessDataVOList.addAll(this.informationQueryService.queryProcessDataByParentIdAndContractId2(contract.getId().toString(), classFy, contractId));
                 }
 
-            } else if (!new Integer("6").equals(node.getNodeType()) && !Arrays.asList("1,2,3,4".split(",")).contains(node.getMajorDataType().toString())) {
+            } else if (!new Integer("6").equals(node.getNodeType()) &&(node.getMajorDataType() == null || !Arrays.asList("1,2,3,4".split(",")).contains(node.getMajorDataType().toString()))) {
                 //查询出节点下所有填报节点
 //                List<WbsTreeContract> list = this.wbsTreeContractClient.getAllChildren(node.getId(), Long.parseLong(node.getContractId()));
 //                List<Long> keys = list.stream().map(l -> l.getPKeyId()).collect(Collectors.toList());

+ 553 - 194
blade-service/blade-business/src/main/java/org/springblade/business/controller/TaskController.java

@@ -1282,6 +1282,327 @@ public class TaskController extends BladeController {
         return this.taskService.startApproval(taskVO) ? R.data(200, true, "操作成功") : R.data(200, false, "操作失败,请联系管理员");
     }
 
+//    /**
+//     * 资料填报-任务管理-任务查看分页查询
+//     */
+//    @PostMapping("/page")
+//    @ApiOperationSupport(order = 1)
+//    @ApiOperation(value = "资料填报-任务管理-任务查看分页查询", notes = "传入BusinessTaskDTO")
+//    public R<IPage<BusinessTaskPageVO>> page(@RequestBody BusinessTaskDTO dto) throws IOException {
+//        if (ObjectUtil.isEmpty(SecureUtil.getUser())) {
+//            throw new ServiceException("未获取到当前用户信息,请联系管理员");
+//        }
+//        if (ObjectUtil.isEmpty(dto.getProjectIdValue())) {
+//            throw new ServiceException("未获取到当前用户所在的项目信息,请联系管理员");
+//        }
+//        if (ObjectUtil.isEmpty(dto.getCurrentContractId())) {
+//            throw new ServiceException("未获取到当前用户所在的合同段信息,请联系管理员");
+//        }
+//        ProjectInfo projectInfo = jdbcTemplate.query("select approval_type from m_project_info where id = " + dto.getProjectIdValue(), new BeanPropertyRowMapper<>(ProjectInfo.class)).stream().findAny().orElse(null);
+//        int current = dto.getCurrent();
+//        int size = dto.getSize();
+//        //封装入参SQL
+//        List<Object> params = new ArrayList<>();
+//        String sqlResult = "SELECT * ";
+//        StringBuilder sqlString = new StringBuilder(" FROM u_task WHERE 1=1 AND is_deleted = 0 AND approval_type in(1,3,4,8,9,10)");
+//        if (ObjectUtil.isNotEmpty(dto.getTypeValue())) {
+//            sqlString.append(" AND type = ?");
+//            params.add(dto.getTypeValue());
+//        }
+//        if (ObjectUtil.isNotEmpty(dto.getStatusValue())) {
+//            sqlString.append(" AND status = ?");
+//            params.add(dto.getStatusValue());
+//        }
+//
+//        ContractInfo contractInfo = jdbcTemplate.queryForObject("select contract_type from m_contract_info where id = " + dto.getCurrentContractId(), new BeanPropertyRowMapper<>(ContractInfo.class));
+//        if (contractInfo != null && contractInfo.getContractType().equals(1)) {
+//            //施工合同段正常查询
+//            if (ObjectUtil.isNotEmpty(dto.getContractIdValue())) {
+//                sqlString.append(" AND contract_id = ?");
+//                params.add(dto.getContractIdValue());
+//            } else {
+//                //如果合同段id=null,那么查询当前项目下所有合同段任务
+//                if (ObjectUtil.isNotEmpty(dto.getProjectIdValue())) {
+//                    sqlString.append(" AND project_id = ?");
+//                    params.add(dto.getProjectIdValue());
+//                } else {
+//                    throw new ServiceException("未获取到当前用户所在的项目信息,请联系管理员");
+//                }
+//            }
+//        } else if (contractInfo != null && (contractInfo.getContractType().equals(2) || contractInfo.getContractType().equals(3))) {
+//            if (ObjectUtil.isEmpty(dto.getOrdType())) { //Web端根据合同段查询
+//                if (ObjectUtil.isNotEmpty(dto.getContractIdValue())) {
+//                    if (dto.getContractIdValue().equals(dto.getCurrentContractId())) {
+//                        //如果下拉框合同段选择框合同段=当前用户登陆合同段,那么查询全部合同段的数据
+//                        List<ContractRelationJlyz> contractRelationJLYZ = jdbcTemplate.query("select contract_id_sg from m_contract_relation_jlyz where contract_id_jlyz = " + dto.getCurrentContractId(), new BeanPropertyRowMapper<>(ContractRelationJlyz.class));
+//                        Set<Long> ids = contractRelationJLYZ.stream().map(ContractRelationJlyz::getContractIdSg).collect(Collectors.toSet());
+//                        ids.add(dto.getCurrentContractId()); //把监理本身也加入查询
+//                        sqlString.append(" AND contract_id in(").append(StringUtils.join(ids, ",")).append(")");
+//
+//                        // 排除监理创建的数据
+//                        sqlString.append(" AND form_data_id not in( SELECT id from u_information_query where type=1 and contract_id='"+dto.getCurrentContractId()+"' )");
+//
+//                    } else {
+//                        //如果下拉框合同段选择框合同段!=当前用户登陆合同段,那么查询下拉框合同段数据
+//                        sqlString.append(" AND contract_id = ?");
+//                        params.add(dto.getContractIdValue());
+//                    }
+//                } else {
+//                    //如果下拉框合同段id=null,那么查询当前整个项目的数据
+//                    sqlString.append(" AND project_id = ?");
+//                    params.add(dto.getProjectIdValue());
+//                }
+//            } else { //App直接查询全部合同段,整个项目
+//                sqlString.append(" AND project_id = ?");
+//                params.add(dto.getProjectIdValue());
+//            }
+//        }
+//        if (ObjectUtil.isNotEmpty(dto.getBatchValue())) {
+//            sqlString.append(" AND batch = ?");
+//            params.add(dto.getBatchValue());
+//        }
+//        if (StringUtils.isNotBlank(dto.getStartTimeValue()) && StringUtils.isNotBlank(dto.getEndTimeValue())) {
+//            sqlString.append(" AND start_time >= ?")
+//                    .append(" AND end_time <= ?");
+//            params.add(dto.getStartTimeValue());
+//            params.add(dto.getEndTimeValue());
+//        }
+//        if (StringUtils.isNotBlank(dto.getQueryValue())) {
+//            sqlString.append(" AND task_name LIKE ?");
+//            params.add("%" + dto.getQueryValue() + "%");
+//        }
+//        if (ObjectUtil.isNotEmpty(dto.getSelectedType())) {
+//            sqlString.append(" AND (");
+//            if (dto.getSelectedType().equals(1)) { //待办页面
+//
+//                //如果是待办页面,且 任务状态下拉框 选择的是 待审批状态 的任务,那么才查询数据
+//                if (ObjectUtil.isNotEmpty(dto.getStatusValue()) && dto.getStatusValue().equals(1)) {
+//                    sqlString.append("EXISTS (SELECT 1 FROM u_task_parallel WHERE u_task.process_instance_id = u_task_parallel.process_instance_id AND u_task_parallel.status = ? AND u_task_parallel.task_user = ?)");
+//                    params.add(1);
+//                    params.add(SecureUtil.getUserId());
+//                    sqlResult = "SELECT *, (SELECT ifnull(e_visa_status, -1)  FROM u_task_parallel WHERE u_task.process_instance_id = u_task_parallel.process_instance_id AND u_task_parallel.STATUS = 1 " +
+//                            "AND u_task_parallel.task_user = " + SecureUtil.getUserId() + " and is_deleted = 0 order by id desc limit 1) as e_status";
+//                    //如果是待办页面,且 任务状态下拉框 选择的不是 待审批状态 的任务,那么直接返回null
+//                } else if (ObjectUtil.isNotEmpty(dto.getStatusValue()) && !dto.getStatusValue().equals(1)) {
+//                    return null;
+//
+//                    //如果是待办页面,没选择 任务状态下拉框,那么就默认查询 待审批状态 任务
+//                } else if (ObjectUtil.isEmpty(dto.getStatusValue())) {
+//                    sqlString.append("EXISTS (SELECT 1 FROM u_task_parallel WHERE u_task.process_instance_id = u_task_parallel.process_instance_id AND u_task_parallel.status = ? AND u_task_parallel.task_user = ?)");
+//                    params.add(1);
+//                    params.add(SecureUtil.getUserId());
+//                    sqlString.append(" AND status = 1");
+//                    sqlResult = "SELECT *, (SELECT ifnull(e_visa_status, -1)  FROM u_task_parallel WHERE u_task.process_instance_id = u_task_parallel.process_instance_id AND u_task_parallel.STATUS = 1 " +
+//                            "AND u_task_parallel.task_user = " + SecureUtil.getUserId() + " and is_deleted = 0 order by id desc limit 1) as e_status";
+//                }
+//
+//            } else if (dto.getSelectedType().equals(2)) { //已办页面
+//                sqlString.append("EXISTS (SELECT 1 FROM u_task_parallel WHERE u_task.process_instance_id = u_task_parallel.process_instance_id AND u_task_parallel.status in(2,3) AND u_task_parallel.task_user = ?)");
+//                //当前自己的任务必须是已审批、已废除 status = 2,3 ,才视为已办
+//                params.add(SecureUtil.getUserId());
+//
+//            } else if (dto.getSelectedType().equals(3)) { //我发起页面
+//                sqlString.append("report_user = ?");
+//                params.add(SecureUtil.getUserId());
+//            }
+//            sqlString.append(")");
+//        }
+//        //是否填报过
+////        sqlString.append(" AND (SELECT COUNT(1)  FROM u_information_query WHERE u_task.form_data_id = id and is_deleted=0) > 0");
+//
+//        //总数量
+//        String sqlCount = "select count(1) " +  sqlString;
+//        Optional<Integer> totalCountOptional = Optional.ofNullable(jdbcTemplate.queryForObject(sqlCount, Integer.class, params.toArray()));
+//        int totalCount = totalCountOptional.orElse(0);
+//
+//        //分页
+//        if (ObjectUtil.isNotEmpty(dto.getOrdType())) {
+//            //App端排序
+//            if (dto.getOrdType() == 1) {
+//                sqlString.append(" ORDER BY create_time DESC LIMIT ? OFFSET ?");
+//            } else if (dto.getOrdType() == 2) {
+//                sqlString.append(" ORDER BY create_time ASC LIMIT ? OFFSET ?");
+//            } else {
+//                sqlString.append(" ORDER BY create_time DESC LIMIT ? OFFSET ?");
+//            }
+//        } else {
+//            //Web端默认倒叙
+//            if (!sqlResult.equals("SELECT * ")) {
+//                sqlString.append(" ORDER BY e_status asc, create_time DESC LIMIT ? OFFSET ?");
+//            } else {
+//                sqlString.append(" ORDER BY create_time DESC LIMIT ? OFFSET ?");
+//            }
+//        }
+//        params.add(size);
+//        params.add((current - 1) * size);
+//
+//        //执行SQL获取数据
+//        String sqlPage = sqlResult + sqlString;
+//        List<Task> resultList = jdbcTemplate.query(
+//                sqlPage,
+//                new BeanPropertyRowMapper<>(Task.class),
+//                params.toArray()
+//        );
+//
+//        //获取任务详情信息Map
+//        Set<String> processInstanceIds = resultList.stream().map(Task::getProcessInstanceId).collect(Collectors.toSet());
+//        Map<String, List<TaskParallel>> taskParallelGroupMap = new HashMap<>();
+//        if (processInstanceIds.size() > 0) {
+//            String resultIds = processInstanceIds.stream()
+//                    .map(id -> "'" + id + "'")
+//                    .collect(Collectors.joining(","));
+//            taskParallelGroupMap = jdbcTemplate.query("select process_instance_id,task_user,task_user_name,e_visa_status,e_visa_content,parallel_process_instance_id,status from u_task_parallel where process_instance_id in(" + resultIds + ") order by id", new BeanPropertyRowMapper<>(TaskParallel.class)).stream().collect(Collectors.groupingBy(TaskParallel::getProcessInstanceId));
+//        }
+//
+//        Map<String, List<TaskParallel>> finalTaskParallelGroupMap = taskParallelGroupMap;
+//        //获取用户信息Map
+//        Map<Long, String> nameMap = jdbcTemplate.query("select id,name from blade_user where is_deleted = 0", new BeanPropertyRowMapper<>(User.class)).stream().collect(Collectors.toMap(User::getId, User::getName, (key1, key2) -> key1));
+//
+//        //解析page分页数据
+//        IPage<BusinessTaskPageVO> page = new Page<>(current, size);
+//        List<BusinessTaskPageVO> pageList = resultList.stream()
+//                .map(task -> {
+//                    BusinessTaskPageVO vo = new BusinessTaskPageVO();
+//                    vo.setId(task.getId());
+//                    vo.setTaskId(task.getId());
+//                    vo.setTaskName(task.getTaskName());
+//                    vo.setTaskTypeName(task.getType().equals(1) ? "普通任务" : task.getType().equals(2) ? "验收任务" : "移交任务");
+//                    vo.setTaskStatusName(task.getStatus().equals(1) ? "待审批" : task.getStatus().equals(2) ? "已审批" : "已废除");
+//                    vo.setStartTime(task.getStartTime());
+//                    vo.setEndTime(task.getEndTime());
+//                    vo.setTaskDesc(task.getTaskContent());
+//                    vo.setTaskReportUserName(nameMap.get(Long.parseLong(task.getReportUser())));
+//                    vo.setApprovalType(task.getApprovalType());
+//                    vo.setFormDataId(task.getFormDataId());
+//                    vo.setProcessInstanceId(task.getProcessInstanceId());
+//                    vo.setEStatus(task.getEStatus());
+//                    List<TaskParallel> taskParallelList = finalTaskParallelGroupMap.get(task.getProcessInstanceId());
+//                    if (taskParallelList != null && taskParallelList.size() > 0) {
+//                        //如果是垂直签,且是待办页面,判断是否是当前用户审批轮次,不是当前用户审批轮次就不显示该任务
+//                        if (projectInfo != null && projectInfo.getApprovalType() == 1 && dto.getSelectedType() == 1) {
+//                            boolean shouldDisplayTask = false; //标记是否显示当前任务
+//                            for (TaskParallel taskParallel : taskParallelList) {
+//                                if (SecureUtil.getUserId().equals(Long.parseLong(taskParallel.getTaskUser()))) {
+//                                    shouldDisplayTask = true; //当前用户是审批人,需要显示任务
+//                                    break;
+//                                } else if (taskParallel.getStatus() != 2) {
+//                                    //之前的审批人员任务未完成,不显示任务
+//                                    break;
+//                                }
+//                            }
+//                            if (!shouldDisplayTask) {
+//                                //跳过当前任务
+//                                return null;
+//                            }
+//                        }
+//
+//                        List<String> names = taskParallelList.stream().map(TaskParallel::getTaskUserName).collect(Collectors.toList());
+//                        if (names.size() > 0) {
+//                            vo.setTaskApproveUserNames(StringUtils.join(names, ","));
+//                        }
+//
+//                        //电签状态(始终只获取自己的电签任务状态信息)
+//                        TaskParallel taskParallel = taskParallelList.stream().filter(f -> ObjectUtil.isNotEmpty(f.getTaskUser()) && f.getTaskUser().equals(SecureUtil.getUserId().toString())).findAny().orElse(null);
+//                        if (taskParallel != null) {
+//                            vo.setEVisaStatus(taskParallel.getEVisaContent() != null ? taskParallel.getEVisaContent() : "");
+//                            vo.setParallelProcessInstanceId(taskParallel.getParallelProcessInstanceId());
+//                        } else {
+//                            taskParallel = taskParallelList.get(0);
+//                            vo.setEVisaStatus(taskParallel.getEVisaContent() != null ? taskParallel.getEVisaContent() : "");
+//                        }
+//                        if (vo.getTaskStatusName().equals("已废除")) {
+//                            if (!"撤回成功".equals(vo.getEVisaStatus()) && !"驳回成功".equals(vo.getEVisaStatus())) {
+//                                vo.setEVisaStatus("废除成功");
+//                            }
+//                        }
+//
+//                        //判断签字人的验证 2=绿色 3=黄色 999=红色 其他代表=灰色
+//                        List<TaskParallel> statList = new ArrayList<>();
+//                        for (TaskParallel taskPa : taskParallelList) {
+//                            if (taskPa.getStatus() == 2 && ObjectUtil.isNotEmpty(taskPa.getEVisaStatus()) && taskPa.getEVisaStatus() == 1) {
+//                                taskPa.setEVisaStatus(2);
+//                            } else if (taskPa.getStatus() == 3 && taskPa.getTaskUser().equals(SecureUtil.getUserId().toString())) {
+//                                taskPa.setEVisaStatus(3);
+//                            } else if (ObjectUtil.isNotEmpty(taskPa.getEVisaStatus()) && taskPa.getEVisaStatus() == 99) {
+//                                taskPa.setEVisaStatus(999);
+//                            } else {
+//                                taskPa.setEVisaStatus(1);
+//                            }
+//                            statList.add(taskPa);
+//                        }
+//                        vo.setTaskApproveUserNamesList(statList);
+//                    }
+//                    return vo;
+//                })
+//                .filter(Objects::nonNull) //过滤掉为null的任务
+//                .collect(Collectors.toList());
+//        //所有满足条件的任务 转map
+//        Map<Long, Task> taskMap = new HashMap<>();
+//        Map<String, List<OperationLog>> operationLogMap = new HashMap<>();
+//        List<Long> taskIds = pageList.stream().filter(b -> "已废除".equals(b.getTaskStatusName())).map(BusinessTaskPageVO::getTaskId).collect(Collectors.toList());
+//        if (ObjectUtil.isNotEmpty(taskIds)){
+//            String joinTaskIds = StringUtils.join(taskIds, ",");
+//            List<Task> tasks = jdbcTemplate.query("select id, create_time,form_data_id from u_task where id in ( " + joinTaskIds+")", new BeanPropertyRowMapper<>(Task.class));
+//             taskMap = tasks.stream()
+//                    .collect(Collectors.toMap(
+//                            Task::getId,
+//                            task -> task));
+//        }
+//        if(CollectionUtil.isNotEmpty(pageList)){
+//            //所有满足条件的日志 转map
+//            Set<String> collect = pageList.stream().map(BusinessTaskPageVO::getFormDataId).collect(Collectors.toSet());
+//            String logQuery = "select business_id,create_user_name ,create_time, operation_type, save_data from u_operation_log where business_id in (" + StringUtil.join(collect) + ") and operation_type in (6,9,12,15,18,21,24,26,61,62)";
+//            List<OperationLog> operationLogs = jdbcTemplate.query(logQuery, new BeanPropertyRowMapper<>(OperationLog.class));
+//            if(ObjectUtil.isNotEmpty(operationLogs)){
+//                operationLogMap = operationLogs.stream().collect(Collectors.groupingBy(OperationLog::getBusinessId));
+//            }
+//            for (BusinessTaskPageVO businessTaskPageVO : pageList) {
+//                if("已废除".equals(businessTaskPageVO.getTaskStatusName())){
+//                    Long taskId = businessTaskPageVO.getTaskId();
+//                    Task task = taskMap.get(taskId);
+//                    if (ObjectUtil.isEmpty(task)){
+//                        continue;
+//                    }
+//                    //任务创建时间
+//                    Date createTime = task.getCreateTime();
+//                    //废除时间肯定在任务创建时间之后  选择距离当前任务创建时间最近的一条数据
+//                    List<OperationLog> operationLogsF = operationLogMap.get(task.getFormDataId());
+//
+//                    if(ObjectUtil.isNotEmpty(operationLogsF)){
+//                        Optional<OperationLog> min = operationLogsF.stream().filter(o -> o.getCreateTime().compareTo(createTime) >= 0)
+//                                .min(Comparator.comparing(t -> t.getCreateTime().getTime() - createTime.getTime()));
+//                        if (min.isPresent()){
+//                            OperationLog operationLog = min.get();
+//                            String createUserName = operationLog.getCreateUserName();
+//                            String date = DateUtil.formatDateTime(operationLog.getCreateTime());
+//                            if (operationLog.getOperationType() != null && operationLog.getOperationType() == 61) {
+//                                businessTaskPageVO.setEVisaStatus("驳回成功:"+ createUserName +"-"+ date);
+//                                String saveData = operationLog.getSaveData();
+//                                if (saveData != null && !saveData.isEmpty()) {
+//                                    try  {
+//                                        JSONObject data = JSONObject.parseObject(saveData);
+//                                        if (data != null && data.containsKey("comment")) {
+//                                            businessTaskPageVO.setEVisaStatus("驳回成功:"+ createUserName +"-"+ date + ";驳回原因:"+data.getString("comment"));
+//                                        }
+//                                    } catch (Exception e) {
+//                                        e.printStackTrace();
+//                                    }
+//                                }
+//                            } else if (operationLog.getOperationType() != null && operationLog.getOperationType() == 62) {
+//                                businessTaskPageVO.setEVisaStatus("撤回成功:"+ createUserName +"-"+ date);
+//                            } else {
+//                                businessTaskPageVO.setEVisaStatus(businessTaskPageVO.getEVisaStatus()+":"+ createUserName +"-"+ date);
+//                            }
+//                        }
+//                    }
+//                }
+//            }
+//        }
+//        page.setRecords(pageList);
+//        page.setTotal(totalCount);
+//        return R.data(page);
+//    }
+
     /**
      * 资料填报-任务管理-任务查看分页查询
      */
@@ -1289,6 +1610,7 @@ public class TaskController extends BladeController {
     @ApiOperationSupport(order = 1)
     @ApiOperation(value = "资料填报-任务管理-任务查看分页查询", notes = "传入BusinessTaskDTO")
     public R<IPage<BusinessTaskPageVO>> page(@RequestBody BusinessTaskDTO dto) throws IOException {
+        // 1. 基础参数校验
         if (ObjectUtil.isEmpty(SecureUtil.getUser())) {
             throw new ServiceException("未获取到当前用户信息,请联系管理员");
         }
@@ -1298,96 +1620,109 @@ public class TaskController extends BladeController {
         if (ObjectUtil.isEmpty(dto.getCurrentContractId())) {
             throw new ServiceException("未获取到当前用户所在的合同段信息,请联系管理员");
         }
-        ProjectInfo projectInfo = jdbcTemplate.query("select approval_type from m_project_info where id = " + dto.getProjectIdValue(), new BeanPropertyRowMapper<>(ProjectInfo.class)).stream().findAny().orElse(null);
-        int current = dto.getCurrent();
-        int size = dto.getSize();
-        //封装入参SQL
+
+        // 2. 查询项目审批类型、合同类型(复用原逻辑)
+        ProjectInfo projectInfo = jdbcTemplate.query(
+                "select approval_type from m_project_info where id = " + dto.getProjectIdValue(),
+                new BeanPropertyRowMapper<>(ProjectInfo.class)
+        ).stream().findAny().orElse(null);
+
+        ContractInfo contractInfo = jdbcTemplate.queryForObject(
+                "select contract_type from m_contract_info where id = " + dto.getCurrentContractId(),
+                new BeanPropertyRowMapper<>(ContractInfo.class)
+        );
+
+        // 3. 构建SQL查询条件(不含分页LIMIT/OFFSET)
         List<Object> params = new ArrayList<>();
         String sqlResult = "SELECT * ";
         StringBuilder sqlString = new StringBuilder(" FROM u_task WHERE 1=1 AND is_deleted = 0 AND approval_type in(1,3,4,8,9,10)");
+
+        // 3.1 任务类型过滤
         if (ObjectUtil.isNotEmpty(dto.getTypeValue())) {
             sqlString.append(" AND type = ?");
             params.add(dto.getTypeValue());
         }
+        // 3.2 任务状态过滤
         if (ObjectUtil.isNotEmpty(dto.getStatusValue())) {
             sqlString.append(" AND status = ?");
             params.add(dto.getStatusValue());
         }
-
-        ContractInfo contractInfo = jdbcTemplate.queryForObject("select contract_type from m_contract_info where id = " + dto.getCurrentContractId(), new BeanPropertyRowMapper<>(ContractInfo.class));
+        // 3.3 合同段过滤(区分合同类型)
         if (contractInfo != null && contractInfo.getContractType().equals(1)) {
-            //施工合同段正常查询
+            // 施工合同段逻辑
             if (ObjectUtil.isNotEmpty(dto.getContractIdValue())) {
                 sqlString.append(" AND contract_id = ?");
                 params.add(dto.getContractIdValue());
             } else {
-                //如果合同段id=null,那么查询当前项目下所有合同段任务
-                if (ObjectUtil.isNotEmpty(dto.getProjectIdValue())) {
-                    sqlString.append(" AND project_id = ?");
-                    params.add(dto.getProjectIdValue());
-                } else {
-                    throw new ServiceException("未获取到当前用户所在的项目信息,请联系管理员");
-                }
+                sqlString.append(" AND project_id = ?");
+                params.add(dto.getProjectIdValue());
             }
         } else if (contractInfo != null && (contractInfo.getContractType().equals(2) || contractInfo.getContractType().equals(3))) {
-            if (ObjectUtil.isEmpty(dto.getOrdType())) { //Web端根据合同段查询
+            // 监理/其他合同段逻辑
+            if (ObjectUtil.isEmpty(dto.getOrdType())) {
+                // Web端逻辑
                 if (ObjectUtil.isNotEmpty(dto.getContractIdValue())) {
                     if (dto.getContractIdValue().equals(dto.getCurrentContractId())) {
-                        //如果下拉框合同段选择框合同段=当前用户登陆合同段,那么查询全部合同段的数据
-                        List<ContractRelationJlyz> contractRelationJLYZ = jdbcTemplate.query("select contract_id_sg from m_contract_relation_jlyz where contract_id_jlyz = " + dto.getCurrentContractId(), new BeanPropertyRowMapper<>(ContractRelationJlyz.class));
-                        Set<Long> ids = contractRelationJLYZ.stream().map(ContractRelationJlyz::getContractIdSg).collect(Collectors.toSet());
-                        ids.add(dto.getCurrentContractId()); //把监理本身也加入查询
-                        sqlString.append(" AND contract_id in(").append(StringUtils.join(ids, ",")).append(")");
-
-                        // 排除监理创建的数据
-                        sqlString.append(" AND form_data_id not in( SELECT id from u_information_query where type=1 and contract_id='"+dto.getCurrentContractId()+"' )");
-
+                        // 查关联合同段+自身
+                        List<ContractRelationJlyz> relationList = jdbcTemplate.query(
+                                "select contract_id_sg from m_contract_relation_jlyz where contract_id_jlyz = " + dto.getCurrentContractId(),
+                                new BeanPropertyRowMapper<>(ContractRelationJlyz.class)
+                        );
+                        Set<Long> ids = relationList.stream().map(ContractRelationJlyz::getContractIdSg).collect(Collectors.toSet());
+                        ids.add(dto.getCurrentContractId());
+                        sqlString.append(" AND contract_id in(").append(StringUtils.join(ids,",")).append(")");
+                        // 排除监理创建数据
+                        sqlString.append(" AND form_data_id not in( SELECT id from u_information_query where type=1 and contract_id='").append(dto.getCurrentContractId()).append("' )");
                     } else {
-                        //如果下拉框合同段选择框合同段!=当前用户登陆合同段,那么查询下拉框合同段数据
                         sqlString.append(" AND contract_id = ?");
                         params.add(dto.getContractIdValue());
                     }
                 } else {
-                    //如果下拉框合同段id=null,那么查询当前整个项目的数据
                     sqlString.append(" AND project_id = ?");
                     params.add(dto.getProjectIdValue());
                 }
-            } else { //App直接查询全部合同段,整个项目
+            } else {
+                // App端逻辑(查整个项目)
                 sqlString.append(" AND project_id = ?");
                 params.add(dto.getProjectIdValue());
             }
         }
+        // 3.4 批次过滤
         if (ObjectUtil.isNotEmpty(dto.getBatchValue())) {
             sqlString.append(" AND batch = ?");
             params.add(dto.getBatchValue());
         }
+        // 3.5 时间范围过滤
         if (StringUtils.isNotBlank(dto.getStartTimeValue()) && StringUtils.isNotBlank(dto.getEndTimeValue())) {
-            sqlString.append(" AND start_time >= ?")
-                    .append(" AND end_time <= ?");
+            sqlString.append(" AND start_time >= ? AND end_time <= ?");
             params.add(dto.getStartTimeValue());
             params.add(dto.getEndTimeValue());
         }
+        // 3.6 任务名称模糊查询
         if (StringUtils.isNotBlank(dto.getQueryValue())) {
             sqlString.append(" AND task_name LIKE ?");
             params.add("%" + dto.getQueryValue() + "%");
         }
+        // 3.7 页面类型过滤(待办/已办/我发起)
         if (ObjectUtil.isNotEmpty(dto.getSelectedType())) {
             sqlString.append(" AND (");
-            if (dto.getSelectedType().equals(1)) { //待办页面
-
-                //如果是待办页面,且 任务状态下拉框 选择的是 待审批状态 的任务,那么才查询数据
+            if (dto.getSelectedType().equals(1)) {
+                // 待办页面逻辑
                 if (ObjectUtil.isNotEmpty(dto.getStatusValue()) && dto.getStatusValue().equals(1)) {
                     sqlString.append("EXISTS (SELECT 1 FROM u_task_parallel WHERE is_resign = 0 and u_task.process_instance_id = u_task_parallel.process_instance_id AND u_task_parallel.status = ? AND u_task_parallel.task_user = ?)");
                     params.add(1);
                     params.add(SecureUtil.getUserId());
+                    // 关联电签状态字段
                     sqlResult = "SELECT *, (SELECT ifnull(e_visa_status, -1)  FROM u_task_parallel WHERE is_resign = 0 and u_task.process_instance_id = u_task_parallel.process_instance_id AND u_task_parallel.STATUS = 1 " +
                             "AND u_task_parallel.task_user = " + SecureUtil.getUserId() + " and is_deleted = 0 order by id desc limit 1) as e_status";
-                    //如果是待办页面,且 任务状态下拉框 选择的不是 待审批状态 的任务,那么直接返回null
                 } else if (ObjectUtil.isNotEmpty(dto.getStatusValue()) && !dto.getStatusValue().equals(1)) {
-                    return null;
-
-                    //如果是待办页面,没选择 任务状态下拉框,那么就默认查询 待审批状态 任务
-                } else if (ObjectUtil.isEmpty(dto.getStatusValue())) {
+                    // 待办页选非待审批状态:直接返回空分页
+                    IPage<BusinessTaskPageVO> emptyPage = new Page<>(dto.getCurrent(), dto.getSize());
+                    emptyPage.setTotal(0);
+                    emptyPage.setRecords(Collections.emptyList());
+                    return R.data(emptyPage);
+                } else {
+                    // 待办页未选状态:默认查待审批
                     sqlString.append("EXISTS (SELECT 1 FROM u_task_parallel WHERE is_resign = 0 and u_task.process_instance_id = u_task_parallel.process_instance_id AND u_task_parallel.status = ? AND u_task_parallel.task_user = ?)");
                     params.add(1);
                     params.add(SecureUtil.getUserId());
@@ -1395,74 +1730,122 @@ public class TaskController extends BladeController {
                     sqlResult = "SELECT *, (SELECT ifnull(e_visa_status, -1)  FROM u_task_parallel WHERE is_resign = 0 and u_task.process_instance_id = u_task_parallel.process_instance_id AND u_task_parallel.STATUS = 1 " +
                             "AND u_task_parallel.task_user = " + SecureUtil.getUserId() + " and is_deleted = 0 order by id desc limit 1) as e_status";
                 }
-
-            } else if (dto.getSelectedType().equals(2)) { //已办页面
+            } else if (dto.getSelectedType().equals(2)) {
+                // 已办页面逻辑
                 sqlString.append("EXISTS (SELECT 1 FROM u_task_parallel WHERE is_resign = 0 and u_task.process_instance_id = u_task_parallel.process_instance_id AND u_task_parallel.status in(2,3) AND u_task_parallel.task_user = ?)");
-                //当前自己的任务必须是已审批、已废除 status = 2,3 ,才视为已办
                 params.add(SecureUtil.getUserId());
-
-            } else if (dto.getSelectedType().equals(3)) { //我发起页面
+            } else if (dto.getSelectedType().equals(3)) {
+                // 我发起页面逻辑
                 sqlString.append("report_user = ?");
                 params.add(SecureUtil.getUserId());
             }
             sqlString.append(")");
         }
-        //是否填报过
-//        sqlString.append(" AND (SELECT COUNT(1)  FROM u_information_query WHERE u_task.form_data_id = id and is_deleted=0) > 0");
 
-        //总数量
-        String sqlCount = "select count(1) " +  sqlString;
-        Optional<Integer> totalCountOptional = Optional.ofNullable(jdbcTemplate.queryForObject(sqlCount, Integer.class, params.toArray()));
-        int totalCount = totalCountOptional.orElse(0);
-
-        //分页
+        // 4. 执行完整SQL查询(获取所有符合条件的原始数据,不含分页)
+        String sqlFull = sqlResult + sqlString;
+        // 处理排序(复用原逻辑,不含LIMIT/OFFSET)
         if (ObjectUtil.isNotEmpty(dto.getOrdType())) {
-            //App端排序
+            // App端排序
             if (dto.getOrdType() == 1) {
-                sqlString.append(" ORDER BY create_time DESC LIMIT ? OFFSET ?");
+                sqlFull += " ORDER BY create_time DESC";
             } else if (dto.getOrdType() == 2) {
-                sqlString.append(" ORDER BY create_time ASC LIMIT ? OFFSET ?");
+                sqlFull += " ORDER BY create_time ASC";
             } else {
-                sqlString.append(" ORDER BY create_time DESC LIMIT ? OFFSET ?");
+                sqlFull += " ORDER BY create_time DESC";
             }
         } else {
-            //Web端默认倒叙
-            if (!sqlResult.equals("SELECT * ")) {
-                sqlString.append(" ORDER BY e_status asc, create_time DESC LIMIT ? OFFSET ?");
+            // Web端排序
+            if (!"SELECT * ".equals(sqlResult)) {
+                sqlFull += " ORDER BY e_status asc, create_time DESC";
             } else {
-                sqlString.append(" ORDER BY create_time DESC LIMIT ? OFFSET ?");
+                sqlFull += " ORDER BY create_time DESC";
             }
         }
-        params.add(size);
-        params.add((current - 1) * size);
-
-        //执行SQL获取数据
-        String sqlPage = sqlResult + sqlString;
-        List<Task> resultList = jdbcTemplate.query(
-                sqlPage,
+        List<Task> allResultList = jdbcTemplate.query(
+                sqlFull,
                 new BeanPropertyRowMapper<>(Task.class),
                 params.toArray()
         );
 
-        //获取任务详情信息Map
-        Set<String> processInstanceIds = resultList.stream().map(Task::getProcessInstanceId).collect(Collectors.toSet());
+        // 5. 预查询关联数据(任务并行信息、用户信息、操作日志,复用原逻辑)
+        // 5.1 任务并行信息Map
+        Set<String> processInstanceIds = allResultList.stream().map(Task::getProcessInstanceId).collect(Collectors.toSet());
         Map<String, List<TaskParallel>> taskParallelGroupMap = new HashMap<>();
-        if (processInstanceIds.size() > 0) {
-            String resultIds = processInstanceIds.stream()
-                    .map(id -> "'" + id + "'")
-                    .collect(Collectors.joining(","));
-            taskParallelGroupMap = jdbcTemplate.query("select process_instance_id,task_user,task_user_name,e_visa_status,e_visa_content,parallel_process_instance_id,status from u_task_parallel where process_instance_id in(" + resultIds + ") order by id", new BeanPropertyRowMapper<>(TaskParallel.class)).stream().collect(Collectors.groupingBy(TaskParallel::getProcessInstanceId));
+        if (CollectionUtil.isNotEmpty(processInstanceIds)) {
+            String idsStr = processInstanceIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(","));
+            taskParallelGroupMap = jdbcTemplate.query(
+                    "select process_instance_id,task_user,task_user_name,e_visa_status,e_visa_content,parallel_process_instance_id,status from u_task_parallel where process_instance_id in(" + idsStr + ") order by id",
+                    new BeanPropertyRowMapper<>(TaskParallel.class)
+            ).stream().collect(Collectors.groupingBy(TaskParallel::getProcessInstanceId));
+        }
+        // 5.2 用户姓名Map
+        Map<Long, String> nameMap = jdbcTemplate.query(
+                "select id,name from blade_user where is_deleted = 0",
+                new BeanPropertyRowMapper<>(User.class)
+        ).stream().collect(Collectors.toMap(User::getId, User::getName, (k1, k2) -> k1));
+        // 5.3 操作日志Map(后续用于已废除任务状态补充)
+        Map<String, List<OperationLog>> operationLogMap = new HashMap<>();
+        if (CollectionUtil.isNotEmpty(allResultList)) {
+            Set<String> formDataIds = allResultList.stream().map(Task::getFormDataId).collect(Collectors.toSet());
+            if (CollectionUtil.isNotEmpty(formDataIds)) {
+                String logSql = "select business_id,create_user_name ,create_time, operation_type, save_data from u_operation_log where business_id in (" + StringUtils.join(formDataIds,",") + ") and operation_type in (6,9,12,15,18,21,24,26,61,62)";
+                List<OperationLog> logs = jdbcTemplate.query(logSql, new BeanPropertyRowMapper<>(OperationLog.class));
+                if (CollectionUtil.isNotEmpty(logs)) {
+                    operationLogMap = logs.stream().collect(Collectors.groupingBy(OperationLog::getBusinessId));
+                }
+            }
         }
 
+        // 6. 对原始数据执行业务过滤(核心:垂直签待办任务可见性判断)
         Map<String, List<TaskParallel>> finalTaskParallelGroupMap = taskParallelGroupMap;
-        //获取用户信息Map
-        Map<Long, String> nameMap = jdbcTemplate.query("select id,name from blade_user where is_deleted = 0", new BeanPropertyRowMapper<>(User.class)).stream().collect(Collectors.toMap(User::getId, User::getName, (key1, key2) -> key1));
+        List<Task> filteredList = allResultList.stream()
+                .filter(task -> {
+                    // 仅处理垂直签+待办页面的过滤逻辑
+                    if (projectInfo != null && projectInfo.getApprovalType() == 1 && dto.getSelectedType() == 1) {
+                        List<TaskParallel> parallelList = finalTaskParallelGroupMap.get(task.getProcessInstanceId());
+                        if (CollectionUtil.isEmpty(parallelList)) {
+                            return false;
+                        }
+                        boolean shouldDisplay = false;
+                        for (TaskParallel tp : parallelList) {
+                            // 当前用户是审批人:显示
+                            if (SecureUtil.getUserId().equals(Long.parseLong(tp.getTaskUser()))) {
+                                shouldDisplay = true;
+                                break;
+                            }
+                            // 前序审批未完成:不显示
+                            if (tp.getStatus() != 2) {
+                                break;
+                            }
+                        }
+                        return shouldDisplay;
+                    }
+                    // 其他场景:直接保留
+                    return true;
+                })
+                .collect(Collectors.toList());
 
-        //解析page分页数据
-        IPage<BusinessTaskPageVO> page = new Page<>(current, size);
-        List<BusinessTaskPageVO> pageList = resultList.stream()
+        // 7. 基于过滤后的数据计算分页
+        int current = dto.getCurrent();
+        int size = dto.getSize();
+        int totalCount = filteredList.size();
+        // 计算分页截取范围(避免索引越界)
+        int startIndex = Math.max((current - 1) * size, 0);
+        int endIndex = Math.min(startIndex + size, totalCount);
+        // 截取当前页数据(subList左闭右开,需处理startIndex >= endIndex的空页场景)
+        List<Task> currentPageTaskList = new ArrayList<>();
+        if (startIndex < endIndex && CollectionUtil.isNotEmpty(filteredList)) {
+            currentPageTaskList = filteredList.subList(startIndex, endIndex);
+        }
+
+        // 8. 转换当前页数据为VO(复用原逻辑,补充已废除任务日志信息)
+        Map<String, List<TaskParallel>> finalTaskParallelGroupMap1 = taskParallelGroupMap;
+        Map<String, List<OperationLog>> finalOperationLogMap = operationLogMap;
+        List<BusinessTaskPageVO> pageList = currentPageTaskList.stream()
                 .map(task -> {
                     BusinessTaskPageVO vo = new BusinessTaskPageVO();
+                    // 基础字段赋值
                     vo.setId(task.getId());
                     vo.setTaskId(task.getId());
                     vo.setTaskName(task.getTaskName());
@@ -1476,128 +1859,91 @@ public class TaskController extends BladeController {
                     vo.setFormDataId(task.getFormDataId());
                     vo.setProcessInstanceId(task.getProcessInstanceId());
                     vo.setEStatus(task.getEStatus());
-                    List<TaskParallel> taskParallelList = finalTaskParallelGroupMap.get(task.getProcessInstanceId());
-                    if (taskParallelList != null && taskParallelList.size() > 0) {
-                        //如果是垂直签,且是待办页面,判断是否是当前用户审批轮次,不是当前用户审批轮次就不显示该任务
-                        if (projectInfo != null && projectInfo.getApprovalType() == 1 && dto.getSelectedType() == 1) {
-                            boolean shouldDisplayTask = false; //标记是否显示当前任务
-                            for (TaskParallel taskParallel : taskParallelList) {
-                                if (SecureUtil.getUserId().equals(Long.parseLong(taskParallel.getTaskUser()))) {
-                                    shouldDisplayTask = true; //当前用户是审批人,需要显示任务
-                                    break;
-                                } else if (taskParallel.getStatus() != 2) {
-                                    //之前的审批人员任务未完成,不显示任务
-                                    break;
-                                }
-                            }
-                            if (!shouldDisplayTask) {
-                                //跳过当前任务
-                                return null;
-                            }
-                        }
 
-                        List<String> names = taskParallelList.stream().map(TaskParallel::getTaskUserName).collect(Collectors.toList());
-                        if (names.size() > 0) {
-                            vo.setTaskApproveUserNames(StringUtils.join(names, ","));
-                        }
-
-                        //电签状态(始终只获取自己的电签任务状态信息)
-                        TaskParallel taskParallel = taskParallelList.stream().filter(f -> ObjectUtil.isNotEmpty(f.getTaskUser()) && f.getTaskUser().equals(SecureUtil.getUserId().toString())).findAny().orElse(null);
-                        if (taskParallel != null) {
-                            vo.setEVisaStatus(taskParallel.getEVisaContent() != null ? taskParallel.getEVisaContent() : "");
-                            vo.setParallelProcessInstanceId(taskParallel.getParallelProcessInstanceId());
-                        } else {
-                            taskParallel = taskParallelList.get(0);
-                            vo.setEVisaStatus(taskParallel.getEVisaContent() != null ? taskParallel.getEVisaContent() : "");
-                        }
-                        if (vo.getTaskStatusName().equals("已废除")) {
-                            if (!"撤回成功".equals(vo.getEVisaStatus()) && !"驳回成功".equals(vo.getEVisaStatus())) {
-                                vo.setEVisaStatus("废除成功");
+                    // 任务并行信息处理
+                    List<TaskParallel> parallelList = finalTaskParallelGroupMap1.get(task.getProcessInstanceId());
+                    if (CollectionUtil.isNotEmpty(parallelList)) {
+                        // 审批人姓名拼接
+                        List<String> approveNames = parallelList.stream().map(TaskParallel::getTaskUserName).collect(Collectors.toList());
+                        vo.setTaskApproveUserNames(StringUtils.join(approveNames,","));
+
+                        // 电签状态处理(优先当前用户)
+                        TaskParallel currentUserParallel = parallelList.stream()
+                                .filter(tp -> ObjectUtil.isNotEmpty(tp.getTaskUser()) && tp.getTaskUser().equals(SecureUtil.getUserId().toString()))
+                                .findAny().orElse(parallelList.get(0));
+                        vo.setEVisaStatus(ObjectUtil.isNotEmpty(currentUserParallel.getEVisaContent()) ? currentUserParallel.getEVisaContent() : "");
+                        vo.setParallelProcessInstanceId(currentUserParallel.getParallelProcessInstanceId());
+
+                        // 已废除任务:补充操作日志信息
+                        if ("已废除".equals(vo.getTaskStatusName())) {
+                            List<OperationLog> logs = finalOperationLogMap.get(task.getFormDataId());
+                            if (CollectionUtil.isNotEmpty(logs)) {
+                                // 筛选任务创建时间之后的日志,取最近一条
+                                Optional<OperationLog> nearestLog = logs.stream()
+                                        .filter(log -> log.getCreateTime().compareTo(task.getCreateTime()) >= 0)
+                                        .min(Comparator.comparing(log -> log.getCreateTime().getTime() - task.getCreateTime().getTime()));
+                                if (nearestLog.isPresent()) {
+                                    OperationLog log = nearestLog.get();
+                                    String logTime = DateUtil.formatDateTime(log.getCreateTime());
+                                    String operator = log.getCreateUserName();
+                                    if (log.getOperationType() != null && log.getOperationType() == 61) {
+                                        // 驳回日志
+                                        vo.setEVisaStatus("驳回成功:" + operator + "-" + logTime);
+                                        // 解析驳回原因
+                                        if (StringUtils.isNotBlank(log.getSaveData())) {
+                                            try {
+                                                JSONObject data = JSONObject.parseObject(log.getSaveData());
+                                                if (data.containsKey("comment")) {
+                                                    vo.setEVisaStatus(vo.getEVisaStatus() + ";驳回原因:" + data.getString("comment"));
+                                                }
+                                            } catch (Exception e) {
+                                                e.printStackTrace();
+                                            }
+                                        }
+                                    } else if (log.getOperationType() != null && log.getOperationType() == 62) {
+                                        // 撤回日志
+                                        vo.setEVisaStatus("撤回成功:" + operator + "-" + logTime);
+                                    } else {
+                                        // 其他废除日志
+                                        vo.setEVisaStatus(vo.getEVisaStatus() + ":" + operator + "-" + logTime);
+                                    }
+                                }
                             }
                         }
 
-                        //判断签字人的验证 2=绿色 3=黄色 999=红色 其他代表=灰色
-                        List<TaskParallel> statList = new ArrayList<>();
-                        for (TaskParallel taskPa : taskParallelList) {
-                            if (taskPa.getStatus() == 2 && ObjectUtil.isNotEmpty(taskPa.getEVisaStatus()) && taskPa.getEVisaStatus() == 1) {
-                                taskPa.setEVisaStatus(2);
-                            } else if (taskPa.getStatus() == 3 && taskPa.getTaskUser().equals(SecureUtil.getUserId().toString())) {
-                                taskPa.setEVisaStatus(3);
-                            } else if (ObjectUtil.isNotEmpty(taskPa.getEVisaStatus()) && taskPa.getEVisaStatus() == 99) {
-                                taskPa.setEVisaStatus(999);
-                            } else {
-                                taskPa.setEVisaStatus(1);
-                            }
-                            statList.add(taskPa);
-                        }
-                        vo.setTaskApproveUserNamesList(statList);
+                        // 审批人状态标记(2=绿/3=黄/999=红/1=灰)
+                        List<TaskParallel> statusList = parallelList.stream()
+                                .map(tp -> {
+                                    TaskParallel newTp = new TaskParallel();
+                                    newTp.setProcessInstanceId(tp.getProcessInstanceId());
+                                    newTp.setTaskUser(tp.getTaskUser());
+                                    newTp.setTaskUserName(tp.getTaskUserName());
+                                    newTp.setEVisaContent(tp.getEVisaContent());
+                                    newTp.setParallelProcessInstanceId(tp.getParallelProcessInstanceId());
+                                    newTp.setStatus(tp.getStatus());
+
+                                    if (tp.getStatus() == 2 && ObjectUtil.isNotEmpty(tp.getEVisaStatus()) && tp.getEVisaStatus() == 1) {
+                                        newTp.setEVisaStatus(2);
+                                    } else if (tp.getStatus() == 3 && tp.getTaskUser().equals(SecureUtil.getUserId().toString())) {
+                                        newTp.setEVisaStatus(3);
+                                    } else if (ObjectUtil.isNotEmpty(tp.getEVisaStatus()) && tp.getEVisaStatus() == 99) {
+                                        newTp.setEVisaStatus(999);
+                                    } else {
+                                        newTp.setEVisaStatus(1);
+                                    }
+                                    return newTp;
+                                })
+                                .collect(Collectors.toList());
+                        vo.setTaskApproveUserNamesList(statusList);
                     }
+
                     return vo;
                 })
-                .filter(Objects::nonNull) //过滤掉为null的任务
+                .filter(Objects::nonNull) // 兜底过滤null(理论上已提前过滤)
                 .collect(Collectors.toList());
-        //所有满足条件的任务 转map
-        Map<Long, Task> taskMap = new HashMap<>();
-        Map<String, List<OperationLog>> operationLogMap = new HashMap<>();
-        List<Long> taskIds = pageList.stream().filter(b -> "已废除".equals(b.getTaskStatusName())).map(BusinessTaskPageVO::getTaskId).collect(Collectors.toList());
-        if (ObjectUtil.isNotEmpty(taskIds)){
-            String joinTaskIds = StringUtils.join(taskIds, ",");
-            List<Task> tasks = jdbcTemplate.query("select id, create_time,form_data_id from u_task where id in ( " + joinTaskIds+")", new BeanPropertyRowMapper<>(Task.class));
-             taskMap = tasks.stream()
-                    .collect(Collectors.toMap(
-                            Task::getId,
-                            task -> task));
-        }
-        if(CollectionUtil.isNotEmpty(pageList)){
-            //所有满足条件的日志 转map
-            Set<String> collect = pageList.stream().map(BusinessTaskPageVO::getFormDataId).collect(Collectors.toSet());
-            String logQuery = "select business_id,create_user_name ,create_time, operation_type, save_data from u_operation_log where business_id in (" + StringUtil.join(collect) + ") and operation_type in (6,9,12,15,18,21,24,26,61,62)";
-            List<OperationLog> operationLogs = jdbcTemplate.query(logQuery, new BeanPropertyRowMapper<>(OperationLog.class));
-            if(ObjectUtil.isNotEmpty(operationLogs)){
-                operationLogMap = operationLogs.stream().collect(Collectors.groupingBy(OperationLog::getBusinessId));
-            }
-            for (BusinessTaskPageVO businessTaskPageVO : pageList) {
-                if("已废除".equals(businessTaskPageVO.getTaskStatusName())){
-                    Long taskId = businessTaskPageVO.getTaskId();
-                    Task task = taskMap.get(taskId);
-                    if (ObjectUtil.isEmpty(task)){
-                        continue;
-                    }
-                    //任务创建时间
-                    Date createTime = task.getCreateTime();
-                    //废除时间肯定在任务创建时间之后  选择距离当前任务创建时间最近的一条数据
-                    List<OperationLog> operationLogsF = operationLogMap.get(task.getFormDataId());
-
-                    if(ObjectUtil.isNotEmpty(operationLogsF)){
-                        Optional<OperationLog> min = operationLogsF.stream().filter(o -> o.getCreateTime().compareTo(createTime) >= 0)
-                                .min(Comparator.comparing(t -> t.getCreateTime().getTime() - createTime.getTime()));
-                        if (min.isPresent()){
-                            OperationLog operationLog = min.get();
-                            String createUserName = operationLog.getCreateUserName();
-                            String date = DateUtil.formatDateTime(operationLog.getCreateTime());
-                            if (operationLog.getOperationType() != null && operationLog.getOperationType() == 61) {
-                                businessTaskPageVO.setEVisaStatus("驳回成功:"+ createUserName +"-"+ date);
-                                String saveData = operationLog.getSaveData();
-                                if (saveData != null && !saveData.isEmpty()) {
-                                    try  {
-                                        JSONObject data = JSONObject.parseObject(saveData);
-                                        if (data != null && data.containsKey("comment")) {
-                                            businessTaskPageVO.setEVisaStatus("驳回成功:"+ createUserName +"-"+ date + ";驳回原因:"+data.getString("comment"));
-                                        }
-                                    } catch (Exception e) {
-                                        e.printStackTrace();
-                                    }
-                                }
-                            } else if (operationLog.getOperationType() != null && operationLog.getOperationType() == 62) {
-                                businessTaskPageVO.setEVisaStatus("撤回成功:"+ createUserName +"-"+ date);
-                            } else {
-                                businessTaskPageVO.setEVisaStatus(businessTaskPageVO.getEVisaStatus()+":"+ createUserName +"-"+ date);
-                            }
-                        }
-                    }
-                }
-            }
-        }
+
+        // 9. 组装分页结果并返回
+        IPage<BusinessTaskPageVO> page = new Page<>(current, size);
         page.setRecords(pageList);
         page.setTotal(totalCount);
         return R.data(page);
@@ -1884,7 +2230,7 @@ public class TaskController extends BladeController {
             for (TaskParallel taskParallel : taskApproveUserNamesList) {
                 String eVisaContent = taskParallel.getEVisaContent();
                 if(ObjectUtil.isNotEmpty(eVisaContent) && eVisaContent.contains("当前等待电签的批次较多")){
-                    String sql = "INSERT INTO  `u_task_batch` (id,json_data,create_user,update_user,nick_name)VALUES("+i+",'{\"approvalFileList\":[],\"approvalType\":"+businessTaskPageVO.getApprovalType()+",\"comment\":\"OK\",\"flag\":\"OK\",\"formDataId\":\""+businessTaskPageVO.getFormDataId()+"\",\"parallelProcessInstanceId\":\""+taskParallel.getParallelProcessInstanceId()+"\",\"pass\":true,\"taskId\":\""+businessTaskPageVO.getTaskId()+"\"}',"+taskParallel.getTaskUser()+","+taskParallel.getTaskUser()+",'"+taskParallel.getTaskUserName()+"')";
+                    String sql = "INSERT INTO  `u_task_batch` (id,json_data,create_user,update_user,nick_name,update_time)VALUES("+i+",'{\"approvalFileList\":[],\"approvalType\":"+businessTaskPageVO.getApprovalType()+",\"comment\":\"OK\",\"flag\":\"OK\",\"formDataId\":\""+businessTaskPageVO.getFormDataId()+"\",\"parallelProcessInstanceId\":\""+taskParallel.getParallelProcessInstanceId()+"\",\"pass\":true,\"taskId\":\""+businessTaskPageVO.getTaskId()+"\"}',"+taskParallel.getTaskUser()+","+taskParallel.getTaskUser()+",'"+taskParallel.getTaskUserName()+"',SYSDATE())";
                     jdbcTemplate.execute(sql);
                     i++;
                 }
@@ -1923,11 +2269,21 @@ public class TaskController extends BladeController {
         int current = dto.getCurrent();
         int size = dto.getSize();
 
+        String sqlContractType="select contract_type from m_contract_info where id="+dto.getContractId()+" and is_deleted=0";
+        Integer contractType = jdbcTemplate.queryForObject(sqlContractType, Integer.class);
         //封装入参SQL
         List<Object> params = new ArrayList<>();
         Integer eVisaStatus = dto.getVisaStatus();
-        StringBuilder sqlString = new StringBuilder("SELECT * FROM u_task WHERE approval_type not in (4, 5, 6, 7, 11) AND status != 3 AND is_deleted=0 AND contract_id = " + dto.getContractId()); //approval_type != 4 非档案的任务就是1填报资料,2工程文件,3日志资料
-
+        StringBuilder sqlString = new StringBuilder("SELECT * FROM u_task WHERE approval_type not in (4, 5, 6, 7, 11) AND status != 3 AND is_deleted=0 " ); //approval_type != 4 非档案的任务就是1填报资料,2工程文件,3日志资料
+        if(contractType==2){
+            String sql="select contract_id_sg from m_contract_relation_jlyz where contract_id_jlyz="+dto.getContractId();
+            List<String> list = jdbcTemplate.query(sql, new SingleColumnRowMapper<>(String.class));
+            list.add(dto.getContractId());
+            String join = String.join(",", list);
+            sqlString.append(" AND contract_id in ("+join+")");
+        }else {
+            sqlString.append(" AND contract_id ="+ dto.getContractId());
+        }
         if (StringUtils.isNotBlank(dto.getStartTimeValue()) && StringUtils.isNotBlank(dto.getEndTimeValue())) {
             if (dto.getStartTimeValue().equals(dto.getEndTimeValue())) {
                 sqlString.append(" AND start_time = ?");
@@ -1964,6 +2320,9 @@ public class TaskController extends BladeController {
         }
         //是否填报过
         sqlString.append(" AND (SELECT COUNT(1)  FROM u_information_query WHERE u_task.form_data_id = id and is_deleted=0) > 0");
+        if(contractType==2){
+            sqlString.append(" AND (SELECT classify FROM u_information_query where  u_task.form_data_id = id and is_deleted=0)=2");
+        }
 
         //总数量
         String sqlCount = sqlString.toString().replace("*", "count(1)");

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

@@ -335,8 +335,6 @@ public class TrialSummaryController {
                  }
              }
 
-
-
             String htmlString = this.html(classC.getHtmlUrl());
             Map<String, String> indexMap = this.indexMap(htmlString, map2);
 

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

@@ -455,4 +455,13 @@ public class ArchiveFileClientImpl implements ArchiveFileClient {
         return fileMapper.getAllArchiveFileByIds(strList);
     }
 
+    @Override
+    public Integer selectMaxSortByContractId(Long contractId) {
+        return iArchiveFileService.selectMaxSortByContractId(contractId);
+    }
+    @Override
+    public void saveBatchArchiveFile(List<ArchiveFile> list) {
+        iArchiveFileService.saveBatch(list);
+    }
+
 }

+ 1 - 1
blade-service/blade-business/src/main/java/org/springblade/business/feignClient/TaskClientImpl.java

@@ -305,8 +305,8 @@ public class TaskClientImpl implements TaskClient {
                     map.put("isUse", ids.contains(fixedFlow.getId()) ? 1 : 0);
                     map.put("fixedFlowName", fixedFlow.getFixedFlowName());
                     map.put("linkUserJoinString", StringUtils.join(names, ","));
+                    result.add(map);
                 }
-                result.add(map);
             }
             return result;
         }

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

@@ -122,4 +122,6 @@ public interface ArchiveFileMapper extends BaseMapper<ArchiveFile> {
     Map<String, BigDecimal> getAllArchiveAutoByContractTypeSummary(@Param("projectId") Long projectId);
 
     List<ArchiveFile> getAllArchiveFileByIds(@Param("strList") List<String> strList);
+
+    Integer selectMaxSortByContractId(@Param("contractId") Long contractId);
 }

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

@@ -156,7 +156,7 @@
     </select>
 
     <select id="selectArchiveFilePage" resultMap="archiveFileResultMap">
-        select u.id,u.project_id,u.contract_id,u.node_id,u.file_number,u.file_name,u.file_time,u.file_url,u.pdf_file_url,u.file_page,u.is_approval,u.is_certification,u.is_need_certification,u.duty_user,u.create_user,u.create_dept,u.create_time,u.update_user,u.update_time,u.status,u.is_deleted,u.sheet_type,u.sheet_source,u.drawing_no,u.cite_change_number,u.certification_time,u.e_visa_file,u.node_ext_id,u.file_type,u.archive_id,u.origin_id,u.filming_time,u.filmingor_time,u.tag_id,u.pic_code,u.refer_code,u.film_code,u.width,u.height,u.ftime,u.del_time,u.sort,u.box_name,u.box_number,u.is_auto_file,u.is_archive,u.page_num,u.file_size,u.source_type,u.is_element,u.pdf_page_url,u.fid,u.rectification,u.classify,u.m_wbs_tree_contract_p_key_id,u.u_image_classification_file_id,u.archive_file_storage_type,u.node_tree_structure,u.date_name,u.archive_file_stroage_type,u.out_id,u.sort_num,u.is_lock,u.archive_sort,u.remark,u.m_album_id
+        select u.id,u.project_id,u.contract_id,u.node_id,u.file_number,u.file_name,u.file_time,u.file_url,u.pdf_file_url,u.file_page,u.is_approval,u.is_certification,u.is_need_certification,u.duty_user,u.create_user,u.create_dept,u.create_time,u.update_user,u.update_time,u.status,u.is_deleted,u.sheet_type,u.sheet_source,u.drawing_no,u.cite_change_number,u.certification_time,u.e_visa_file,u.node_ext_id,u.file_type,u.archive_id,u.origin_id,u.tag_id,u.pic_code,u.refer_code,u.film_code,u.width,u.height,u.sort,u.box_name,u.box_number,u.is_auto_file,u.is_archive,u.page_num,u.file_size,u.source_type,u.is_element,u.pdf_page_url,u.fid,u.rectification,u.classify,u.m_wbs_tree_contract_p_key_id,u.u_image_classification_file_id,u.archive_file_storage_type,u.node_tree_structure,u.date_name,u.archive_file_stroage_type,u.out_id,u.sort_num,u.is_lock,u.archive_sort,u.remark,u.m_album_id
         from u_archive_file u
         <if test="vo.nodeIds != null and vo.nodeIds != ''">
             left join m_archive_tree_contract t on t.id = u.node_id and t.is_deleted= 0
@@ -240,10 +240,16 @@
             )
         </if>
         order by
+        u.sort,
         <if test="vo.nodeIds != null and vo.nodeIds != ''">
+            case
+            when t.tree_sort regexp '^[a-zA-Z]' then 0  -- 字母开头的排在前面
+            when t.tree_sort regexp '^[0-9]' then 1    -- 数字开头的排在后面
+            else 2                                   -- 其他情况
+            end,
             t.tree_sort,
         </if>
-        u.sort,u.sort_num,u.create_time
+        u.sort_num,u.create_time
         limit #{current}, #{size}
     </select>
 
@@ -564,4 +570,7 @@
             </foreach>
         </if>
     </select>
+    <select id="selectMaxSortByContractId" resultType="java.lang.Integer">
+        select max(sort) from u_archive_file where contract_id = #{contractId}
+    </select>
 </mapper>

+ 1 - 1
blade-service/blade-business/src/main/java/org/springblade/business/mapper/InformationQueryMapper.java

@@ -151,5 +151,5 @@ public interface InformationQueryMapper extends BaseMapper<InformationQuery> {
 
     int addCheckPdfInfoByIds(@Param("ids") List<String> ids,@Param("classify") String classify);
 
-    ChekPdfPaceVo getCheckPdfPaceInfo(@Param("contractId") String contractI, @Param("classify") String classify);
+    ChekPdfPaceVo getCheckPdfPaceInfo(@Param("contractId") String contractId, @Param("classify") String classify);
 }

+ 17 - 3
blade-service/blade-business/src/main/java/org/springblade/business/mapper/InformationQueryMapper.xml

@@ -1024,10 +1024,24 @@
     </update>
 
 
+<!--    <select id="getCheckPdfPaceInfo" resultType="org.springblade.business.vo.ChekPdfPaceVo">-->
+<!--          SELECT c.TotalCount,c.finishCount, IF(FLOOR(finishCount/TotalCount),null,0) as pace from (-->
+<!--             SELECT COUNT(1) as TotalCount,IF(SUM(IF(status = 'chek_status', 1, 0)),null,0) AS finishCount from u_information_query where contract_id=#{contractId} and classify=#{classify} and chek_status in(1,2,3)-->
+<!--          ) c-->
+<!--    </select>-->
+
     <select id="getCheckPdfPaceInfo" resultType="org.springblade.business.vo.ChekPdfPaceVo">
-        SELECT c.TotalCount,c.finishCount, IF(FLOOR(finishCount/TotalCount),null,0) as pace from (
-           SELECT COUNT(1) as TotalCount,IF(SUM(IF(status = 'chek_status', 1, 0)),null,0) AS finishCount from u_information_query where contract_id=#{contractId} and classify=#{classify} and chek_status in(1,2,3)
-        ) c
+        SELECT
+            COUNT(1) as totalCount,
+            SUM(CASE WHEN chek_status IN (1,2,3) THEN 1 ELSE 0 END) as finishCount,
+            CASE
+                WHEN COUNT(1) = 0 THEN 0
+                ELSE ROUND(SUM(CASE WHEN chek_status IN (1,2,3) THEN 1 ELSE 0 END) * 100.0 / COUNT(1), 2)
+                END as pace
+        FROM u_information_query
+        WHERE contract_id = #{contractId,jdbcType=VARCHAR}
+          AND classify = #{classify,jdbcType=VARCHAR}
+          AND chek_status IN (0,1,2,3)
     </select>
 
 </mapper>

+ 2 - 0
blade-service/blade-business/src/main/java/org/springblade/business/service/IArchiveFileService.java

@@ -76,4 +76,6 @@ public interface IArchiveFileService extends BaseService<ArchiveFile> {
     public void updateArchiveFileEx(List<ArchiveFile> files);
 
     boolean sortByFileTime(Long nodeId);
+
+    Integer selectMaxSortByContractId(Long contractId);
 }

+ 19 - 3
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/ArchiveFileServiceImpl.java

@@ -328,13 +328,20 @@ public class ArchiveFileServiceImpl extends BaseServiceImpl<ArchiveFileMapper, A
 
     @Override
     public boolean sortByFileTime(Long nodeId) {
-        String sql = "SELECT id,file_time,node_id FROM u_archive_file WHERE node_id IN (SELECT id FROM m_archive_tree_contract WHERE  FIND_IN_SET(" + nodeId + ",ancestors)) AND is_deleted=0";
+        String sql = "SELECT id,file_time,node_id,sort FROM u_archive_file WHERE node_id IN (SELECT id FROM m_archive_tree_contract WHERE  FIND_IN_SET(" + nodeId + ",ancestors)) AND is_deleted=0";
         List<ArchiveFile> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(ArchiveFile.class));
         if (list.size() > 0) {
             Map<String, List<ArchiveFile>> group = list.stream()
                     .collect(Collectors.groupingBy(ArchiveFile::getNodeId));
             for (Map.Entry<String, List<ArchiveFile>> entry : group.entrySet()) {
                 List<ArchiveFile> archiveFiles = entry.getValue();
+                // 从archiveFiles列表中提取sort字段,处理null值
+                List<Integer> sortValues = archiveFiles.stream()
+                        .map(file -> {
+                            // 如果sort字段为null,返回0;否则返回实际值
+                            return file.getSort() != null ? file.getSort() : 0;
+                        })
+                        .collect(Collectors.toList());
                 archiveFiles.sort((a, b) -> {
                     if (a.getFileTime() == null && b.getFileTime() == null) return 0;
                     if (a.getFileTime() == null) return -1;
@@ -342,8 +349,13 @@ public class ArchiveFileServiceImpl extends BaseServiceImpl<ArchiveFileMapper, A
                     return a.getFileTime().compareTo(b.getFileTime());
                 });
                 int sort = 0;
-                for (ArchiveFile file : archiveFiles) {
-                    file.setSort(sort++);
+                for (int i = 0; i < archiveFiles.size(); i++ ) {
+                    Integer sortValue = sortValues.get(i);
+                    if(sortValue!=0){
+                        archiveFiles.get(i).setSort(sortValue);
+                    }else {
+                        archiveFiles.get(i).setSort(sort++);
+                    }
                 }
                 this.updateBatchById(archiveFiles);
             }
@@ -353,4 +365,8 @@ public class ArchiveFileServiceImpl extends BaseServiceImpl<ArchiveFileMapper, A
         }
     }
 
+    @Override
+    public Integer selectMaxSortByContractId(Long contractId) {
+        return baseMapper.selectMaxSortByContractId(contractId);
+    }
 }

+ 2 - 0
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/InformationQueryServiceImpl.java

@@ -44,6 +44,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.jdbc.core.BeanPropertyRowMapper;
 import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import java.io.File;
@@ -1116,6 +1117,7 @@ public class InformationQueryServiceImpl extends BaseServiceImpl<InformationQuer
     }
 
     @Override
+//    @Async
     public R updateCheckPdfInfo(String type, String ids,String classify) {
         if(type==null || Func.isNull(type)){
             return R.fail("type不能weinull");

+ 2 - 1
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/TaskServiceImpl.java

@@ -656,6 +656,7 @@ public class TaskServiceImpl extends BaseServiceImpl<TaskMapper, Task> implement
                     }
                     taskBatch.setNickName(nickName);
                     taskBatch.setCreateTime(new Date());
+                    taskBatch.setUpdateTime(new Date());
                     taskList.add(taskBatch);
                     //设置委托单的状态
                     try {
@@ -2357,7 +2358,7 @@ public class TaskServiceImpl extends BaseServiceImpl<TaskMapper, Task> implement
         String sqlForTaskPall = "UPDATE u_task_parallel a  set a.is_resign = 1, a.`status`=2 , a.e_visa_status=1 ,a.e_visa_content='电签成功' where a.process_instance_id in(select process_instance_id  from u_task b  where b.form_data_id in( " + ids2 + ") and b.status in(0,1,2) and b.is_deleted = 0)  and a.`status` in(0,1)";
 
         String taskBtech = "insert into u_task_batch(id,task_parallel_id,json_data,create_user,create_dept,create_time,update_user,update_time,status,is_deleted,nick_name,sign_format,sign_type)  " +
-                " SELECT a.id,a.process_instance_id,json_object('approvalFileList',json_array(),'approvalType',b.approval_type,'comment','','flag','OK','formDataId',b.form_data_id,'parallelProcessInstanceId',a.parallel_process_instance_id,'pass',true,'taskId',b.id) as  json_data,a.task_user,a.create_dept,a.create_time,a.update_user,a.update_time,1 as status,0 as is_deleted,a.task_user_name as nick_name ,1 as sign_format,1 as sign_type from u_task_parallel a,u_task b where b.`status` in(1,2) and a.`status` in(2)  and   a.process_instance_id=b.process_instance_id " +
+                " SELECT a.id,a.process_instance_id,json_object('approvalFileList',json_array(),'approvalType',b.approval_type,'comment','','flag','OK','formDataId',b.form_data_id,'parallelProcessInstanceId',a.parallel_process_instance_id,'pass',true,'taskId',b.id) as  json_data,a.task_user,a.create_dept,a.create_time,a.update_user,SYSDATE(),1 as status,0 as is_deleted,a.task_user_name as nick_name ,1 as sign_format,1 as sign_type from u_task_parallel a,u_task b where b.`status` in(1,2) and a.`status` in(2)  and   a.process_instance_id=b.process_instance_id " +
                 " and b.form_data_id in( " + ids2 + ") and a.parallel_process_instance_id not in(SELECT JSON_EXTRACT(c.json_data, '$.parallelProcessInstanceId') from u_task_batch c)";
 
         if(userIds!=null && userIds.length()>=1){

+ 4 - 11
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/TrialSelfInspectionRecordServiceImpl.java

@@ -147,17 +147,7 @@ public class TrialSelfInspectionRecordServiceImpl extends BaseServiceImpl<TrialS
             if (org.apache.commons.lang.StringUtils.isNotEmpty(dto.getQueryStatus())) {
                 queryWrapper.lambda().eq(TrialSelfInspectionRecord::getDetectionResult, dto.getQueryStatus());
             }
-            if (dto.getSortType() != null) {
-                if (dto.getSortType().equals("1")) {
-                    queryWrapper.lambda().orderByAsc(TrialSelfInspectionRecord::getReportNo);
-                } else if (dto.getSortType().equals("2")) {
-                    queryWrapper.lambda().orderByDesc(TrialSelfInspectionRecord::getReportNo);
-                } else if (dto.getSortType().equals("3")) {
-                    queryWrapper.lambda().orderByAsc(Arrays.asList(TrialSelfInspectionRecord::getSpecificationModel, TrialSelfInspectionRecord::getReportNo));
-                } else if (dto.getSortType().equals("4")) {
-                    queryWrapper.lambda().orderByDesc(Arrays.asList(TrialSelfInspectionRecord::getSpecificationModel, TrialSelfInspectionRecord::getReportNo));
-                }
-            }
+            queryWrapper.lambda().orderByAsc(TrialSelfInspectionRecord::getSort);
 
             IPage<TrialSelfInspectionRecord> pages = this.page(page, queryWrapper.lambda().orderByDesc(true, TrialSelfInspectionRecord::getCreateTime));
             IPage<TrialSelfInspectionRecordVO> trialSelfInspectionRecordVOIPage = TrialSelfInspectionRecordWarpper.build().pageVO(pages);
@@ -2399,6 +2389,8 @@ public class TrialSelfInspectionRecordServiceImpl extends BaseServiceImpl<TrialS
     public void updateSort(TrialSelfInspectionRecordPageDTO dto) {
         LambdaQueryWrapper<TrialSelfInspectionRecord> queryWrapper = Wrappers.<TrialSelfInspectionRecord>lambdaQuery()
                 .eq(TrialSelfInspectionRecord::getNodeId, dto.getNodeId())
+                .eq(TrialSelfInspectionRecord::getType, dto.getType())
+                .eq(TrialSelfInspectionRecord::getContractId, dto.getContractId())
                 .eq(TrialSelfInspectionRecord::getIsDeleted, 0);
         if (dto.getSortType() != null) {
             if (dto.getSortType().equals("1")) {
@@ -2421,6 +2413,7 @@ public class TrialSelfInspectionRecordServiceImpl extends BaseServiceImpl<TrialS
             updateBatchById(trialSelfInspectionRecords);
         }
     }
+
     //    @Override
     public List<Map<String, Object>> getTrialTableData(List<Long> recordIds){
         List<TrialSelfInspectionRecord> trialSelfInspectionRecords = this.listByIds(recordIds);

+ 51 - 0
blade-service/blade-dingding/src/main/java/org/springblade/dingding/service/impl/MeetingServiceImpl.java

@@ -103,6 +103,16 @@ public class MeetingServiceImpl implements MeetingService {
                     .get(25, TimeUnit.SECONDS);
             // 25秒总超时
             if(!vos.isEmpty()){
+                vos.sort((vo1, vo2) -> {
+                    String fixedData1 = vo1.getFixedData();
+                    String fixedData2 = vo2.getFixedData();
+
+                    // 提取会议室编号(去除"会议室"字符,只保留前面的中文数字)
+                    String number1 = extractChineseNumber(fixedData1);
+                    String number2 = extractChineseNumber(fixedData2);
+
+                    return compareChineseNumbers(number1, number2);
+                });
                 UserInfoVO vo = new UserInfoVO();
                 vo.setSystem("dingding");
                 vo.setContractId(1L);
@@ -121,6 +131,47 @@ public class MeetingServiceImpl implements MeetingService {
         }
     }
 
+    // 提取会议室名称中的中文数字部分
+    private String extractChineseNumber(String roomName) {
+        // 假设格式为 "一会议室"、"二会议室" 等,提取前面的中文数字
+        if (roomName != null) {
+            return roomName.substring(0, 1); // 去掉"会议室"三个字
+        }
+        return roomName;
+    }
+
+    // 中文数字比较器
+    private int compareChineseNumbers(String chineseNum1, String chineseNum2) {
+        // 定义中文数字映射表
+        Map<String, Integer> chineseNumberMap = new HashMap<>();
+        chineseNumberMap.put("一", 1);
+        chineseNumberMap.put("二", 2);
+        chineseNumberMap.put("三", 3);
+        chineseNumberMap.put("四", 4);
+        chineseNumberMap.put("五", 5);
+        chineseNumberMap.put("六", 6);
+        chineseNumberMap.put("七", 7);
+        chineseNumberMap.put("八", 8);
+        chineseNumberMap.put("九", 9);
+        chineseNumberMap.put("十", 10);
+        chineseNumberMap.put("十一", 11);
+        chineseNumberMap.put("十二", 12);
+        chineseNumberMap.put("十三", 13);
+        chineseNumberMap.put("十四", 14);
+        chineseNumberMap.put("十五", 15);
+        chineseNumberMap.put("十六", 16);
+        chineseNumberMap.put("十七", 17);
+        chineseNumberMap.put("十八", 18);
+        chineseNumberMap.put("十九", 19);
+        chineseNumberMap.put("二十", 20);
+        // 可以根据需要继续添加更多数字
+
+        Integer num1 = chineseNumberMap.getOrDefault(chineseNum1, Integer.MAX_VALUE);
+        Integer num2 = chineseNumberMap.getOrDefault(chineseNum2, Integer.MAX_VALUE);
+
+        return num1.compareTo(num2);
+    }
+
     private String[] getTodayTimeRange() {
         LocalDateTime startOfDay = LocalDateTime.now().with(LocalTime.MIDNIGHT);
         ZonedDateTime startOfDayZoned = startOfDay.atZone(ZoneId.systemDefault());

+ 6 - 3
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/controller/ChekSignData.java

@@ -14,9 +14,11 @@ import org.springframework.web.bind.annotation.RestController;
 
 import javax.annotation.Resource;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * 清表基础数据表 控制器
@@ -55,16 +57,17 @@ public class ChekSignData {
             for (ScrSignInfoVO dataInfo : query) {
                 if (executor.getQueue().size()<=30 ) {
                     Long nodeId = dataInfo.getId();
-                    Boolean aBoolean = RedisTemplate.hasKey("chek-" + nodeId);
+                    // 兼容以前 u_information_query 与 u_task 一对多的关系
+                    Boolean aBoolean = RedisTemplate.hasKey("chek-" + nodeId + dataInfo.getProcessInstanceId());
 
                     if (!aBoolean) {
-                        RedisTemplate.opsForValue().set("chek-" + nodeId, "1",3600, TimeUnit.SECONDS);
+                        RedisTemplate.opsForValue().set("chek-" + nodeId + dataInfo.getProcessInstanceId(), "1",3600, TimeUnit.SECONDS);
                         CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
                             try {
                                 /*===============执行批量任务===============*/
                                 scrDataService.sctTaskBatch(dataInfo);
                             } catch (Exception e) {
-                                RedisTemplate.delete("chek-" + nodeId);
+                                RedisTemplate.delete("chek-" + nodeId + dataInfo.getProcessInstanceId());
                                 e.printStackTrace();
                             }
                         }, executor);

+ 1 - 1
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/controller/EVController.java

@@ -58,7 +58,7 @@ public class EVController {
     @Resource(name = "taskExecutor1")
     private ThreadPoolExecutor executor;
 
-    //@Scheduled(cron = "0/10 * * * * ?")
+    @Scheduled(cron = "0/10 * * * * ?")
     public void SignInfo() {
         //执行代码
 

+ 17 - 25
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/service/impl/EVDataServiceImpl.java

@@ -210,7 +210,7 @@ public class EVDataServiceImpl implements EVDataService {
                 }
 
                 String sql2 = "INSERT into u_task_batch(id,task_parallel_id,json_data,create_user,create_dept,create_time,update_user,update_time,status,is_deleted,nick_name,sign_format,sign_type)" +
-                        "SELECT " + id + " as id,task_parallel_id,json_data,create_user,create_dept,create_time,update_user,update_time,status,is_deleted,nick_name,sign_format,2 as sign_type from u_task_batch where id=" + batchId + "";
+                        "SELECT " + id + " as id,task_parallel_id,json_data,create_user,create_dept,create_time,update_user,SYSDATE(),status,is_deleted,nick_name,sign_format,2 as sign_type from u_task_batch where id=" + batchId + "";
                 jdbcTemplate.execute(sql2);
             }
         }
@@ -342,11 +342,13 @@ public class EVDataServiceImpl implements EVDataService {
                             }
                         }
                     }
+                    //电签成功后需要将check_status改为1,进行电签检测
+                    String updateCheckStatus="update  u_information_query set check_status=1 where id='" + taskApp.getFormDataId() + "' and check_status!=0 ";
+                    jdbcTemplate.execute(updateCheckStatus);
                     updateSql = "update u_information_query set pdf_trial_url_position='" + pdfTrialUrlPosition + "',business_time='" + taskApp.getPdfDate() + "',node_pdf_url='" + nodePdfUrl + "',e_visa_pdf_page=" + pdfPage + ",e_visa_pdf_size=" + pdfSize + ",e_visa_pdf_url='" + taskApp.getLastFilePdfUrl() + "',status='" + taskApp.getSigType() + "',update_time=SYSDATE() where id='" + taskApp.getFormDataId() + "' ";
                     //修改 计量 需要引用的 附件信息
                     String updataFile = "update s_attachment_form set file_url='"+nodePdfUrl+"' ,file_pdf_url='"+nodePdfUrl+"' where select_id='"+taskApp.getFormDataId()+"' ";
                     jdbcTemplate.execute(updataFile);
-
                     String upTsk = "update s_attachment_form_task set file_url='"+nodePdfUrl+"' ,file_pdf_url='"+nodePdfUrl+"' where select_id='"+taskApp.getFormDataId()+"' ";
                     jdbcTemplate.execute(upTsk);
                 } else if (taskApp.getApprovalType() == 2) {
@@ -465,24 +467,14 @@ public class EVDataServiceImpl implements EVDataService {
 
             } else if (taskApp.getApprovalType() == 5) {
                 map = this.jdbcTemplate.queryForMap("select * from s_interim_pay_certificate where  is_deleted=0 and contract_period_id = " + taskApp.getFormDataId());
-                String pdfUrl=map.get("raw_url") + "";
+                String pdfUrl = "";
                 //中间计量用逗号拼接pagePdfUrl
-               /* String pageUrl = map.get("page_pdf_url")+"";
-                if(StringUtils.isNotEmpty(pageUrl) && pageUrl.length()>=20 ){
-                    String jsonStr=map.get("page_pdf_url")+"";
-                    ObjectMapper mapper = new ObjectMapper();
-                    JsonNode jsonNode = mapper.readTree(jsonStr);
-                    StringBuilder result = new StringBuilder();
-                    Iterator<Map.Entry<String, JsonNode>> fields = jsonNode.fields();
-                    while (fields.hasNext()) {
-                        Map.Entry<String, JsonNode> entry = fields.next();
-                        result.append(entry.getValue().asText());
-                        if (fields.hasNext()) {
-                            result.append(",");
-                        }
-                    }
-                    pdfUrl=result.toString();
-                }*/
+                String pageUrl = map.get("page_pdf_url")+"";
+                if(StringUtils.isNotEmpty(pageUrl) && pageUrl.length()>=20 ) {
+                    pageUrl = pageUrl.replaceAll(" ", "");
+                }else{
+                    pdfUrl=map.get("raw_url") + "";
+                }
                 taskApp.setSignPdfUrl(pdfUrl);
             } else if (taskApp.getApprovalType() == 6 || taskApp.getApprovalType() == 7) {
                 map = this.jdbcTemplate.queryForMap("select * from  s_material_start_statement where is_deleted=0 and meter_period_id = " + taskApp.getFormDataId());
@@ -630,7 +622,7 @@ public class EVDataServiceImpl implements EVDataService {
     public void addSignatureDataByAXQZ(List<SealStrategyVO> strategyListByAXQ,String formDataId,String markType) {
         System.out.println("www");
         String deleteSql = "delete from m_sign_data where query_id = '" + formDataId + "' and user_id in( ";
-        String insertsql = "INSERT INTO m_sign_data (id, query_id, text_id, user_id, sign_time, type, img_url, sign_type) VALUES (?, ?, ?, ?, SYSDATE(), ?, ?, ?)";
+        String insertsql = "INSERT INTO m_sign_data (id, query_id, text_id, user_id, sign_time, type, img_url, sign_type, pyzbx, pyzby) VALUES (?, ?, ?, ?, SYSDATE(), ?, ?, ?, ?, ?)";
         List<Object[]> batchArgs = new ArrayList<>();
         for (SealStrategyVO sealStrategyVO : strategyListByAXQ) {
             long newPkId = SnowFlakeUtil.getId(); //主键Id
@@ -638,7 +630,7 @@ public class EVDataServiceImpl implements EVDataService {
             if(sealStrategyVO.isCompanySeal()){
                 type = "2";
             }
-            batchArgs.add(new Object[]{newPkId, formDataId, sealStrategyVO.getKeyword(), sealStrategyVO.getUserId(), type, sealStrategyVO.getImageUrl(), markType});
+            batchArgs.add(new Object[]{newPkId, formDataId, sealStrategyVO.getKeyword(), sealStrategyVO.getUserId(), type, sealStrategyVO.getImageUrl(), markType, sealStrategyVO.getOffSetX(), sealStrategyVO.getOffSetY()});
             deleteSql+= sealStrategyVO.getUserId() +",";
         }
         // 添加更多数据...
@@ -651,13 +643,13 @@ public class EVDataServiceImpl implements EVDataService {
     public void addSignatureDataByDFZX(List<Map<String, Object>> strategyListByAXQ,String formDataId,String markType) {
         System.out.println("www");
         String deleteSql = "delete from m_sign_data where query_id = '" + formDataId + "' and user_id in( ";
-        String insertsql = "INSERT INTO m_sign_data (id, query_id, text_id, user_id, sign_time, type, img_url, sign_type) VALUES (?, ?, ?, ?, SYSDATE(), ?, ?, ?)";
+        String insertsql = "INSERT INTO m_sign_data (id, query_id, text_id, user_id, sign_time, type, img_url, sign_type, pyzbx, pyzby) VALUES (?, ?, ?, ?, SYSDATE(), ?, ?, ?, ?, ?)";
         List<Object[]> batchArgs = new ArrayList<>();
         for (Map<String, Object> map : strategyListByAXQ) {
             long newPkId = SnowFlakeUtil.getId(); //主键Id
             String userId = map.get("userId")+"";
             String type = map.get("type")+"";
-            batchArgs.add(new Object[]{newPkId, formDataId, map.get("keyWord"), userId, type, map.get("sealId"), markType});
+            batchArgs.add(new Object[]{newPkId, formDataId, map.get("keyWord"), userId, type, map.get("sealId"), markType, map.get("pyzbx"), map.get("pyzby")});
             deleteSql+=userId +",";
          }
         // 添加更多数据...
@@ -946,7 +938,7 @@ public class EVDataServiceImpl implements EVDataService {
             int i = SignFtpUtil.uploadFile(locPdfUrl, inputStream);
             if (i == 1) {
                  result = eVisaService.signPdfByAXQZ(pdfVO, locPdfUrl, OutPdfUrl);
-                if(result!=null){
+                if(result!=null && result[0]!=null){
                     int i1 = SignFtpUtil.downloadFile(dataFileUrl, OutPdfUrl);
                     fileUrl = dataFileUrl;
                     SignFtpUtil.FTPDeleteDir(locPdfUrl);
@@ -961,7 +953,7 @@ public class EVDataServiceImpl implements EVDataService {
         } else {
             result = eVisaService.signPdfByAXQZ(pdfVO, fileByte);
             //执行电签
-            if (result != null) {
+            if(result!=null && result[0]!=null){
                 try {
                     //对文件流做判断
                     byte[] bytes = (byte[]) result[0];

+ 213 - 55
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/service/impl/ScrDataServiceImpl.java

@@ -7,11 +7,10 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle;
 import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
 import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
 import org.springblade.business.vo.ScrSignInfoVO;
-import org.springblade.business.vo.TaskSignInfoVO;
 import org.springblade.common.utils.CommonUtil;
-import org.springblade.core.tool.utils.BeanUtil;
 import org.springblade.core.tool.utils.CollectionUtil;
 import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringUtil;
 import org.springblade.evisa.service.ScrDataService;
 import org.springblade.evisa.utils.PdfAddimgUtil;
 import org.springblade.evisa.vo.SignKeyVO;
@@ -34,7 +33,7 @@ public class ScrDataServiceImpl implements ScrDataService {
     @Override
     public void sctTaskBatch(ScrSignInfoVO taskApp) throws Exception {
         // 电签检查
-        this.sctTaskBatch2(taskApp);
+        this.sctTaskBatch3(taskApp);
     }
 
 
@@ -70,10 +69,10 @@ public class ScrDataServiceImpl implements ScrDataService {
 
         if(strategyListByDFZX==null || strategyListByDFZX.size()==0){
 
-            String sql2 = "UPDATE u_information_query set chek_status=10 where id='"+taskApp.getId()+"'";
+            String sql2 = "UPDATE u_information_query set chek_status=3 where id='"+taskApp.getId()+"'";
             jdbcTemplate.execute(sql2);
 
-            RedisTemplate.delete("chek-" + taskApp.getId());
+            RedisTemplate.delete("chek-" + taskApp.getId() + taskApp.getProcessInstanceId());
             System.out.println(taskApp.getProcessInstanceId()+"-"+"总共:" );
             return;
         }
@@ -221,7 +220,7 @@ public class ScrDataServiceImpl implements ScrDataService {
             String sql2 = "UPDATE u_information_query set chek_status=3 where id='"+taskApp.getId()+"'";
             jdbcTemplate.execute(sql2);
         }
-        RedisTemplate.delete("chek-" + taskApp.getId());
+        RedisTemplate.delete("chek-" + taskApp.getId() + taskApp.getProcessInstanceId());
       //  System.out.println(taskApp.getProcessInstanceId()+"-"+"总共:" );
       //  System.out.println(taskApp.getId() +"-"+"总共:" + positions.size() + "-剩下-" + differentElements.size());
     }
@@ -230,19 +229,17 @@ public class ScrDataServiceImpl implements ScrDataService {
 
     // 添加电签策略 -- 安心签
     public List<Map<String, Object>> getStrategyListByAXQ(ScrSignInfoVO task, String ids) {
-        String sql = "select task_user,count(1) as total,initiative,status,(select  count(1) from u_task_parallel where process_instance_id = '" + task.getProcessInstanceId() + "' and initiative=2 and status=2) as pCount from u_task_parallel where process_instance_id = '" + task.getProcessInstanceId() + "'";
+        String sql = "select task_user,initiative,status,(select  count(1) from u_task_parallel where process_instance_id = '" + task.getProcessInstanceId() + "' and initiative=2 and status=2) as pCount from u_task_parallel where process_instance_id = '" + task.getProcessInstanceId() + "'";
         List<Map<String, Object>> mapList = jdbcTemplate.queryForList(sql);
         ArrayList<String> strArray = new ArrayList();
-        int total = 0;
         int pCount = 0;
         if (mapList != null && mapList.size() >= 1) {
             for (int i = 0; i < mapList.size(); i++) {
                 Map<String, Object> map = mapList.get(i);
                 if(i==0){
-                    total = Func.toInt(map.get("total"));
                     pCount = Func.toInt(map.get("pCount"));
                 }
-                if(map.get("initiative").equals("2") && map.get("status").equals("2")){
+                if((map.get("initiative")+"").equals("2") && (map.get("status")+"").equals("2")){
                     strArray.add(map.get("task_user").toString());
                 }
             }
@@ -270,14 +267,8 @@ public class ScrDataServiceImpl implements ScrDataService {
             for (int i = 0; i < strArray.size(); i++) {
                 List<Map<String, Object>> maps2 = null;
                 if (dqIds.length() > 0) {
-                    String sqlinfo = " SELECT * from ( SELECT a.id as keyWord,a.project_id,a.pyzbx ,a.pyzby,(SELECT acc_code from blade_user where id='" + strArray.get(i) + "' and is_deleted=0  ) as sealId from m_textdict_info a where  a.type =2 and a.id in (" + dqIds + ")  and sig_role_id in (SELECT DISTINCT c.role_id from m_project_assignment_user c  where c.contract_id=" + task.getContractId() + " and user_id=" + strArray.get(i) + " and c.is_deleted=0 ) ) x where x.sealId is not null ";
-                    if (total==pCount) {
-                        sqlinfo = "SELECT a.id as keyWord,a.pyzbx,a.pyzby,b.certificate_number as sealId from m_textdict_info a ,m_sign_pfx_file b where a.sig_role_id = b.pfx_type and b.project_contract_role like '%" + task.getContractId() + "%' and a.is_deleted=0 and b.is_deleted=0 and a.type=6 and a.id in(" + dqIds + ")";
-                        System.out.println("东方中讯--签章--" + sqlinfo);
-                    } else {
-                        System.out.println("东方中讯--签字--" + sqlinfo);
-                    }
-                    maps2 = jdbcTemplate.queryForList(sqlinfo);
+                    String sqlinfo = " SELECT * from ( SELECT a.id as keyWord,a.project_id,a.pyzbx ,a.pyzby,(SELECT signature_file_url from m_sign_pfx_file where is_register=1 and certificate_user_id='" + strArray.get(i) + "' and is_deleted=0  ) as signature_file_url, '1' as type from m_textdict_info a where  a.type =2 and a.id in (" + dqIds + ")  and sig_role_id in (SELECT DISTINCT c.role_id from m_project_assignment_user c  where c.contract_id=" + task.getContractId() + " and user_id=" + strArray.get(i) + " and c.is_deleted=0 ) ) x where x.signature_file_url is not null ";
+                    System.out.println("安心签--签字--" + sqlinfo);
                     maps2 = jdbcTemplate.queryForList(sqlinfo);
                 }
                 if(CollectionUtil.isNotEmpty(maps2)) {
@@ -301,26 +292,43 @@ public class ScrDataServiceImpl implements ScrDataService {
                             maps.add(keyList.get(0));
                         }
                     }
+                } else {
+                    // 查询 sign_data 表
+                    List<Map<String, Object>> list = jdbcTemplate.queryForList("SELECT * from m_sign_data where query_id = ? and text_id in (?) and user_id = ? and type = 1", task.getId(), dqIds, strArray.get(i));
+                    if (!list.isEmpty()) {
+                        System.out.println("1111111111111111111");
+                    }
                 }
-             /*   if (signIds.length() > 0 && task.getSigType() != 2) {
-                    String sql = "SELECT * from ( SELECT a.conf_id as keyWord,0.0 as pyzbx ,0.0 as pyzby,(SELECT acc_code from blade_user where id = " + strArray[i] + " and is_deleted=0  ) as sealId " +
-                            "from m_sign_config_relation a where a.type = 1 and a.is_deleted = 0 and a.conf_id in (" + signIds + ") " +
-                            "and a.relation_id in (SELECT DISTINCT c.role_id from m_project_assignment_user c  where c.contract_id= " + task.getContractId() + " and user_id= " + strArray[i] + " and c.is_deleted=0 ) ) x where x.sealId is not null ";
-                    List<Map<String, Object>> maps3 = jdbcTemplate.queryForList(sql);
-                    System.out.println("东方中讯--签字--key =" + sql);
-                    if (!maps3.isEmpty()) {
-                        Map<String, List<Map<String, Object>>> peopleByAge = maps2.stream()
-                                .collect(Collectors.groupingBy(hada -> (Func.toStr(hada.get("keyWord")))));
-                        for (String keyId : peopleByAge.keySet()) {
-                            List<Map<String, Object>> keyList = peopleByAge.get(keyId);
-                            if (keyList != null && !keyList.isEmpty()) {
-                                Map<String, Object> map = keyList.get(0);
-                                map.put("keyWord", "✹" + map.get("keyWord"));
-                                maps.add(map);
+            }
+
+
+
+            String sqlinfo = "SELECT a.id as keyWord,a.pyzbx,a.pyzby,b.certificate_number as sealId, '2' as type from m_textdict_info a ,m_sign_pfx_file b where a.sig_role_id = b.pfx_type and b.project_contract_role like '%" + task.getContractId() + "%' and a.is_deleted=0 and b.is_deleted=0 and a.type=6 and a.id in(" + dqIds + ")";
+            System.out.println("安心签--签章--" + sqlinfo);
+            List<Map<String, Object>> maps2 = jdbcTemplate.queryForList(sqlinfo);
+            if(CollectionUtil.isNotEmpty(maps2)) {
+                Map<String, List<Map<String, Object>>> peopleByAge = maps2.stream()
+                        .collect(Collectors.groupingBy(hada -> (Func.toStr(hada.get("keyWord")))));
+                for (String keyId : peopleByAge.keySet()) {
+                    int exId = 0;
+                    List<Map<String, Object>> keyList = peopleByAge.get(keyId);
+                    if (keyList != null && keyList.size() == 1) {
+                        maps.addAll(keyList);
+                        exId = 1;
+                    } else if (keyList != null && keyList.size() >= 2) {
+                        for (Map<String, Object> datax : keyList) {
+                            if ((datax.get("project_id") + "").equals(task.getProjectId())) {
+                                maps.add(datax);
+                                exId = 1;
                             }
                         }
                     }
-                }*/
+                    if (exId == 0) {
+                        maps.add(keyList.get(0));
+                    }
+                }
+            } else {
+                System.out.println("22222222222222222222");
             }
         }
         return maps;
@@ -406,6 +414,8 @@ public class ScrDataServiceImpl implements ScrDataService {
             List<String> positions = pdfSignIds.getEVisaConfigList();
             Map<String, String> dataMap = pdfSignIds.getDataMap();
 
+            Map<String, String> dataMap1 = checkSignByPdf(taskApp, pdfSignIds, pdfData);
+
             List<String> sucessUser = new ArrayList<>();
             List<String> sucessCompan = new ArrayList<>();
             String ids = String.join(",", positions);
@@ -420,10 +430,10 @@ public class ScrDataServiceImpl implements ScrDataService {
 
             if (strategyListByDFZX == null || strategyListByDFZX.size() == 0) {
 
-                String sql2 = "UPDATE u_information_query set chek_status=10 where id='" + taskApp.getId() + "'";
+                String sql2 = "UPDATE u_information_query set chek_status=3 where chek_status != 2 and id='" + taskApp.getId() + "'";
                 jdbcTemplate.execute(sql2);
 
-                RedisTemplate.delete("chek-" + taskApp.getId());
+                RedisTemplate.delete("chek-" + taskApp.getId() + taskApp.getProcessInstanceId());
                 System.out.println(taskApp.getProcessInstanceId() + "-" + "总共:");
                 return;
             }
@@ -566,8 +576,8 @@ public class ScrDataServiceImpl implements ScrDataService {
                                 if (pkeyid.length() < 19) {
                                     keyw = keyw - (float) (10 - pkeyid.length() / 2.0);
                                 }
-//                                System.out.println("page = " + (i + 1) + ", type = " + type + ", keyid = " + pkeyid + ", imgX : " + imgX + ", keyw : " + keyw + ", imgY : "
-//                                        + imgY + ", keyh : " + keyh + ", keyword =" + keyData.replace("\r", "\\r") + ", remarkType = " + taskApp.getRemarkType());
+                                System.out.println("page = " + (i + 1) + ", type = " + type + ", keyid = " + pkeyid + ", imgX : " + imgX + ", keyw : " + keyw + ", imgY : "
+                                        + imgY + ", keyh : " + keyh + ", keyword =" + keyData.replace("\r", "\\r") + ", remarkType = " + taskApp.getRemarkType());
                                 if (Math.abs(imgX - keyw) <= threshold && Math.abs(imgY - keyh) <= threshold) {
                                     if (type.equals("1")) { //个人
                                         sucessUser.add(pkeyid);
@@ -582,7 +592,7 @@ public class ScrDataServiceImpl implements ScrDataService {
                     }
                 }
             }
-
+            Map<String, String> dataUserMap = new HashMap<>(dataMap);
             boolean isSign = true;
             //判断章是否签完
             List<String> companList = strategyListByDFZX.stream().filter(item -> item.get("type").equals("2")).map(map -> map.get("keyWord").toString()).collect(Collectors.toList());
@@ -600,30 +610,178 @@ public class ScrDataServiceImpl implements ScrDataService {
                 }
             }
 
-            boolean isSign1 = true;
-            //判断个人是否签完
-            List<String> userList = strategyListByDFZX.stream().filter(item -> item.get("type").equals("1")).map(map -> map.get("keyWord").toString()).collect(Collectors.toList());
-            if (!userList.isEmpty()) {
-                Set<String> differentElements = new HashSet<>(userList);
-                sucessUser.forEach(differentElements::remove);
-                if (!differentElements.isEmpty()) {
-                    isSign1 = false;
-                    StringBuilder sb = new StringBuilder();
-                    sb.append("id = ").append(taskApp.getId()).append(", 人总共:").append(userList.size()).append(", 剩下:").append(differentElements.size());
-                    for (String element : differentElements) {
-                        sb.append(", ").append(element);
+            if(!sucessUser.isEmpty() && isSign){
+                for(String user:sucessUser){
+                    for(String mapkey:dataMap.keySet()){
+                        String mapval = dataMap.get(mapkey);
+                        if(mapval.contains(user)){
+                            dataUserMap.remove(mapkey) ;
+                        }
                     }
-                    System.err.println(sb);
                 }
+                for(String company:sucessCompan){
+                    for(String mapkey:dataMap.keySet()){
+                        String mapval = dataMap.get(mapkey);
+                        if(mapval != null && (mapval.equals(company) || mapval.equals(company+"\r"))){
+                            dataUserMap.remove(mapkey) ;
+                        }
+                    }
+                }
+                if(!dataUserMap.isEmpty()){
+                    Map<String, float[]> allKeyPositions = getKeyPositions(pdfSignIds, pdfData);
+                    Set<String> removeKeys = new HashSet<>();
+                    dataUserMap.forEach((id, value) -> {
+                        float[] floats = allKeyPositions.get(value);
+                        if (floats == null || floats.length < 6) {
+                            return;
+                        }
+                        dataMap.forEach((key, value1) -> {
+                            float[] floats1 = allKeyPositions.get(value);
+                            if (floats1.length < 2) {
+                                return;
+                            }
+                            if (floats[0] != floats1[0]) {
+                                return;
+                            }
+                            PDPage page = document.getPage((int) floats[0] - 1);
+                            float pageHeight = page.getMediaBox().getHeight();
+                            float pageWidth = page.getMediaBox().getWidth();
+                            if (Math.abs(floats[5] * pageHeight - floats1[3] * pageHeight) < 1 && Math.abs(floats[4] * pageWidth - floats1[1] * pageWidth) < 1 && !dataUserMap.containsKey(key)) {
+                                removeKeys.add(id);
+                            }
+                        });
+                    });
+                    if (!removeKeys.isEmpty()) {
+                        dataUserMap.entrySet().removeIf(entry -> removeKeys.contains(entry.getKey()));
+                    }
+                    if (!dataUserMap.isEmpty()) {
+                        if (dataMap1 == null) {
+                            dataUserMap.entrySet().removeIf(item -> true);
+                        } else {
+                            dataUserMap.entrySet().removeIf(entry -> !dataMap1.containsKey(entry.getKey()));
+                        }
+                    }
+                    if (!dataUserMap.isEmpty()) {
+                        isSign = false ;
+                    }
+                }
+                System.out.println(taskApp.getId() +"-"+"个人总共:" + sucessUser.size() + "-剩下-" +dataUserMap.keySet().size());
             }
-            if (isSign && isSign1) {
+            if (isSign ) {
                 String sql2 = "UPDATE u_information_query set chek_status=2 where id='" + taskApp.getId() + "'";
                 jdbcTemplate.execute(sql2);
             } else {
-                String sql2 = "UPDATE u_information_query set chek_status=3 where id='" + taskApp.getId() + "'";
+                String sql2 = "UPDATE u_information_query set chek_status=3 where chek_status != 2 and id='" + taskApp.getId() + "'";
                 jdbcTemplate.execute(sql2);
             }
-            RedisTemplate.delete("chek-" + taskApp.getId());
+            RedisTemplate.delete("chek-" + taskApp.getId() + taskApp.getProcessInstanceId());
+        }
+    }
+
+    /**
+     * 获取所有电签关键字的位置
+     */
+    public Map<String, float[]> getKeyPositions(SignKeyVO pdfSignIds,byte[] pdfData) throws Exception {
+        if (pdfSignIds == null) {
+            return  null;
+        }
+        Map<String, String> dataMap = pdfSignIds.getDataMap();
+        Collection<String> keys = dataMap.values();
+        if (keys.isEmpty()) {
+            return null;
+        }
+        List<PDFIndexInfo> pdfIndexInfo = PdfAddimgUtil.findKeywordPostions(pdfData,  String.join(",", keys));
+        if (pdfIndexInfo.isEmpty()) {
+            return  null;
+        }
+        Map<String, float[]> map = new HashMap<>();
+        for (PDFIndexInfo info : pdfIndexInfo) {
+            map.put(info.getPkeyid(), info.getDataInfo());
+        }
+        return map;
+    }
+
+    public Map<String, String> checkSignByPdf(ScrSignInfoVO taskApp,SignKeyVO pdfSignIds,byte[] pdfData) throws Exception {
+        int threshold = 5;
+        //转换
+        try (PDDocument document = PDDocument.load(pdfData);) {
+            List<String> positions = pdfSignIds.getEVisaConfigList();
+            Map<String, String> dataMap = pdfSignIds.getDataMap();
+            String keyWord = String.join(",", positions);
+            List<String> sucessList = new ArrayList<>();
+            List<PDFIndexInfo> pdfIndexInfo = PdfAddimgUtil.findKeywordPostions(pdfData, keyWord);
+            Map<String, List<PDFIndexInfo>> groupBy = pdfIndexInfo.stream().collect(Collectors.groupingBy(da -> Func.toStr(da.getDataInfo()[0])));
+            for (int i = 0; i < document.getPages().getCount(); i++) {
+                PDPage page = document.getPage(i);
+                List<PDAnnotation> annotations = page.getAnnotations();
+                for (PDAnnotation annotation : annotations) {
+                    if (annotation instanceof PDAnnotationWidget) {
+                        PDRectangle rect = annotation.getRectangle();
+                        float imgW = rect.getWidth();
+                        float imgH = rect.getHeight();
+                        float imgX = rect.getLowerLeftX() + imgW / 2;
+                        float imgY = rect.getLowerLeftY() + imgH / 2;
+                        List<PDFIndexInfo> pdfIndexInfos = groupBy.get((i + 1) + ".0");
+                        if (pdfIndexInfos != null && !pdfIndexInfos.isEmpty()) {
+                            for (PDFIndexInfo pdfInfo : pdfIndexInfos) {
+                                String pkeyid = pdfInfo.getPkeyid();
+                                float[] dataInfo = pdfInfo.getDataInfo();
+                                float keyX = dataInfo[1];
+                                float keyY = dataInfo[2];
+                                float pageHeight = page.getMediaBox().getHeight();
+                                float pageWidth = page.getMediaBox().getWidth();
+
+                                float keyw = keyX * pageWidth;
+                                float keyh = pageHeight - keyY * pageHeight;
+                                float pyzbx = 0.0f;
+                                float pyzby = 0.0f;
+                                if (taskApp.getRemarkType().equals("3")) { //东方中讯
+                                    if (imgH >= 100) {
+                                        // 签章
+                                        keyw = keyw + pyzbx - 21;
+                                        keyh = keyh + pyzby - 16;
+                                    } else {
+                                        keyw = keyw + pyzbx + 10;
+                                        keyh = keyh + pyzby;
+                                    }
+                                } else if (taskApp.getRemarkType().equals("2")) { //东方中讯
+                                    if (imgH >= 100) {
+                                        // 签章
+                                        keyw = keyw + pyzbx - 21;
+                                        keyh = keyh + pyzby - 15;
+                                    } else {
+                                        keyw = keyw + pyzbx + 10;
+                                        keyh = keyh + pyzby;
+                                    }
+                                } else {
+                                    keyw = keyw + pyzbx + 5;
+                                    keyh = keyh + pyzby;
+                                }
+                                if (pkeyid.length() < 19) {
+                                    keyw = keyw - (float) (10 - pkeyid.length() / 2.0);
+                                }
+                                if (Math.abs(imgX - keyw) <= threshold && Math.abs(imgY - keyh) <= threshold) {
+                                    sucessList.add(pkeyid);
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            Map<String, String> dataUserMap = new HashMap<>(dataMap);
+            //判断章是否签完
+            if (!sucessList.isEmpty()) {
+                for (String user : sucessList) {
+                    for (String mapkey : dataMap.keySet()) {
+                        String mapval = dataMap.get(mapkey);
+                        if (mapval.contains(user)) {
+                            dataUserMap.remove(mapkey);
+                        }
+                    }
+                }
+            }
+            return dataUserMap;
         }
     }
 }

+ 10 - 2
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/utils/PdfAddimgUtil.java

@@ -265,6 +265,16 @@ public class PdfAddimgUtil {
                 }
 
                 float[] postions = charPositions.get(positionIndex);
+                try {
+                    // 获取text的结束位置
+                    int endPos = positionIndex + text.length();
+                    if (endPos < content.length()) {
+                        float[] floats = charPositions.get(endPos);
+                        postions = new float[]{postions[0], postions[1], postions[2], floats[0], floats[1], floats[2]};
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
                 data.setDataInfo(postions);
                 data.setPkeyid(text);
                 result.add(data);
@@ -406,8 +416,6 @@ public class PdfAddimgUtil {
             String text = stripper.getText(document);
             String[] lines = text.split("[ \\n]+");
 
-            Pattern pattern = Pattern.compile("(\\d{4}[年-]\\d{2}[月-]\\d{2}日?)");
-
             for(int k=0;k<lines.length;k++){
                 String textStr = lines[k];
                 int index = textStr.indexOf("*");

+ 15 - 8
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java

@@ -22,6 +22,7 @@ import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.SneakyThrows;
 import org.apache.commons.codec.Charsets;
+import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang3.ObjectUtils;
 import org.apache.poi.ss.usermodel.WorkbookFactory;
@@ -78,6 +79,8 @@ import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 import org.springframework.jdbc.core.BeanPropertyRowMapper;
 import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.context.request.RequestContextHolder;
@@ -98,6 +101,7 @@ import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
 import java.sql.SQLException;
 import java.text.SimpleDateFormat;
@@ -4343,17 +4347,20 @@ public class ExcelTabController extends BladeController {
         }else {
             WbsTreeContract wbsInfo = wbsTreeContractService.getBaseMapper().selectOne(Wrappers.<WbsTreeContract>query().lambda()
                     .eq(WbsTreeContract::getPKeyId, pkeyId));
-            String sql="select * from m_wbs_tree_contract where p_id="+wbsInfo.getPId()+" and is_type_private_pid="+wbsInfo.getIsTypePrivatePid();
-            List<WbsTreeContract> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(WbsTreeContract.class));
-            if(!list.isEmpty()){
-                for (WbsTreeContract wbsTreeContract : list) {
-                    String tabName = wbsTreeContract.getInitTableName();
-                    if (StringUtils.isNotEmpty(tabName)) {
-                        String delSql = "delete from " + tabName + " where p_key_id = " + wbsTreeContract.getPKeyId();
-                        jdbcTemplate.execute(delSql);
+            if(wbsInfo.getIsTypePrivatePid()!=null){
+                String sql="select * from m_wbs_tree_contract where p_id="+wbsInfo.getPId()+" and is_type_private_pid="+wbsInfo.getIsTypePrivatePid()+" and project_id="+wbsInfo.getProjectId();
+                List<WbsTreeContract> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(WbsTreeContract.class));
+                if(!list.isEmpty()){
+                    for (WbsTreeContract wbsTreeContract : list) {
+                        String tabName = wbsTreeContract.getInitTableName();
+                        if (StringUtils.isNotEmpty(tabName)) {
+                            String delSql = "delete from " + tabName + " where p_key_id = " + wbsTreeContract.getPKeyId();
+                            jdbcTemplate.execute(delSql);
+                        }
                     }
                 }
             }
+
         }
         return R.data("成功!");
     }

+ 62 - 5
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/NodeBaseInfoController.java

@@ -19,6 +19,7 @@ package org.springblade.manager.controller;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.alibaba.nacos.shaded.com.google.protobuf.ServiceException;
 import com.alibaba.nacos.shaded.io.opencensus.metrics.LongGauge;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.google.gson.Gson;
@@ -26,13 +27,16 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.models.auth.In;
 import lombok.AllArgsConstructor;
 import javax.validation.Valid;
 
 import org.apache.commons.lang.StringUtils;
+import org.springblade.business.entity.InformationQuery;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
 import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.BeanUtil;
 import org.springblade.core.tool.utils.Func;
 import org.springblade.manager.entity.WbsTreeContract;
 import org.springblade.manager.service.IExcelTabService;
@@ -52,6 +56,7 @@ import org.springblade.core.boot.ctrl.BladeController;
 
 import java.math.BigDecimal;
 import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  *  控制器
@@ -171,8 +176,46 @@ public class NodeBaseInfoController extends BladeController {
     @GetMapping("/getNameRuleByRule")
     @ApiOperationSupport(order = 32)
     @ApiOperation(value = "节点配置根据选择的文件题名规则获取文件题名", notes = "传入规则和节点pkeyId")
-    public R getNameRuleByRule(@RequestParam String nameRule,@RequestParam Long pkeyId){
-        return R.data( nodeBaseInfoService.getNameRuleByRule(nameRule,pkeyId));
+    public R getNameRuleByRule(@RequestParam String nameRule,@RequestParam Long pkeyId) throws ServiceException {
+            String sql="select * from m_wbs_tree_contract where p_key_id="+pkeyId;
+            WbsTreeContract contract = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(WbsTreeContract.class));
+            if(contract==null){
+                throw new ServiceException("节点不存在");
+            }
+            String ancestorsPId = contract.getAncestorsPId();
+            if (ancestorsPId.startsWith("0,")) {
+                ancestorsPId = ancestorsPId.substring(2);
+            }
+            ancestorsPId=ancestorsPId+","+contract.getPKeyId();
+            String sql1="select  REPLACE(REPLACE(node_name, CHAR(10), ''), CHAR(13), '') as node_name,node_type from m_wbs_tree_contract where p_key_id in ("+ancestorsPId+") and is_deleted=0";
+            List<WbsTreeContract> nodeNames = jdbcTemplate.query(sql1, new BeanPropertyRowMapper<>(WbsTreeContract.class));
+             nameRule = nameRule.trim().replaceAll("(?i:c)", "");
+            List<String> list = Arrays.asList(nameRule.split("[^.\\d]"));
+            List<Integer> index = list.stream().map(Integer::parseInt).collect(Collectors.toList());
+            Map<Integer, String> map = nodeNames.stream()
+                    .collect(Collectors.toMap(
+                            WbsTreeContract::getNodeType,
+                            WbsTreeContract::getNodeName,
+                            (existing, replacement) -> replacement // 如果键重复,保留后者
+                    ));
+            StringBuilder result = new StringBuilder("");
+            for (Integer i : index) {
+                if(i==0){
+                    if(map.containsKey(1)){
+                        result.append(map.get(1));
+                    }
+                }
+                else if(i==1){
+                    if(map.containsKey(18)){
+                        result.append(map.get(18));
+                    }
+                }else {
+                    if(map.containsKey(i)){
+                        result.append(map.get(i));
+                    }
+                }
+            }
+        return R.data(result.toString());
     }
 
     public R synPDFInfo(String contractId, String nodeId, String classify, String projectId,Map<Long,Map<String,Object>>dataMap) {
@@ -247,12 +290,26 @@ public class NodeBaseInfoController extends BladeController {
     @GetMapping("/getNodeData")
     @ApiOperationSupport(order = 9)
     @ApiOperation(value = "获取节点基础数据", notes = "获取节点基础数据")
-    public R getNodeData(@RequestParam Long pKeyId){
+    public R getNodeData(@RequestParam Long pKeyId, Integer classify){
         NodeBaseInfo nodeBaseInfo = nodeBaseInfoService.getBaseMapper().selectOne(new QueryWrapper<NodeBaseInfo>().eq("node_id", pKeyId));
+        String sql="select * from u_information_query where wbs_id="+pKeyId+" and classify="+classify+" and is_deleted=0 order by  create_time DESC";
+        List<InformationQuery> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(InformationQuery.class));
+        NodeBaseInfoVO baseInfoVO=new NodeBaseInfoVO();
+        BeanUtil.copy(nodeBaseInfo, baseInfoVO);
+        if(!query.isEmpty()){
+            InformationQuery informationQuery = query.get(0);
+            if(informationQuery!=null&&(informationQuery.getStatus()==1||informationQuery.getStatus()==2)){
+                baseInfoVO.setIsDisable(1);
+            }
+        }else {
+            baseInfoVO.setIsDisable(0);
+        }
         if(nodeBaseInfo!=null){
-            return R.data(nodeBaseInfo);
+            return R.data(baseInfoVO);
         }else {
-            return R.data(nodeBaseInfoService.getOrSaveNodeBaseInfo(pKeyId));
+            NodeBaseInfo info = nodeBaseInfoService.getOrSaveNodeBaseInfo(pKeyId);
+            BeanUtil.copy(info, NodeBaseInfoVO.class);
+            return R.data(baseInfoVO);
         }
     }
 

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

@@ -175,6 +175,7 @@ public class TrialSummaryClassificationConfigurationController extends BladeCont
                         .in(WbsTreePrivate::getParentId, ids).eq(WbsTreePrivate::getProjectId, projectId)
                         .eq(WbsTreePrivate::getWbsType, wbsType)
                         .eq(WbsTreePrivate::getType, 2)
+                        .isNull(WbsTreePrivate::getTrialTabContractId)
                         .eq(WbsTreePrivate::getWbsId, wbsId));
                 for (WbsTreePrivate wbsTreePrivate : wbsTreePrivates) {
                     SelectedTabVO vo = new SelectedTabVO();

+ 4 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/WbsTreeContractController.java

@@ -565,6 +565,10 @@ public class WbsTreeContractController extends BladeController {
                 Elements tdElements2 = tr2.select("td");
                 for (int j = 0; j < tdElements1.size(); j++) {
                     Element td1 = tdElements1.get(j);
+                    // 检查td1是否包含dqid属性,如果包含则跳过
+                    if (td1.attr("dqid").length() > 0) {
+                        continue;
+                    }
                     Element td2 = tdElements2.get(j);
                     String keyName = getKeyNameFromChildElement(td1);
                     if (StringUtils.isNotEmpty(keyName)) {

+ 49 - 1
blade-service/blade-manager/src/main/java/org/springblade/manager/feign/ArchiveTreeContractImpl.java

@@ -1,5 +1,7 @@
 package org.springblade.manager.feign;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.AllArgsConstructor;
 import org.apache.ibatis.annotations.Param;
 import org.springblade.archive.dto.JiLinQueryDto;
@@ -13,6 +15,8 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 @RestController
 @AllArgsConstructor
@@ -166,10 +170,54 @@ public class ArchiveTreeContractImpl implements ArchiveTreeContractClient {
         return archiveTreeContractMapper.getOutNodesByOutIds(projectId,outIds);
     }
 
+//    @Override
+//    public void updateArchiveTreeContract(List<ArchiveTreeContract> archiveTreeContracts) {
+//        archiveTreeContractService.updateBatchById(archiveTreeContracts);
+//    }
+
     @Override
     public void updateArchiveTreeContract(List<ArchiveTreeContract> archiveTreeContracts) {
-        archiveTreeContractService.updateBatchById(archiveTreeContracts);
+        // 1. 提取传入合同列表中的所有ID(过滤空值)
+        List<Long> ids = archiveTreeContracts.stream()
+                .map(ArchiveTreeContract::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        if (ids.isEmpty()) {
+            return;
+        }
+
+        // 2. 根据ID批量查询现有记录
+        LambdaQueryWrapper<ArchiveTreeContract> wrapper = Wrappers.lambdaQuery();
+        wrapper.in(ArchiveTreeContract::getId, ids);
+        List<ArchiveTreeContract> existContracts = archiveTreeContractService.list(wrapper);
+
+        // 3. 将传入合同列表转换为Map<ID, 合同对象>便于查找
+        Map<Long, ArchiveTreeContract> externalContractMap = archiveTreeContracts.stream()
+                .collect(Collectors.toMap(
+                        ArchiveTreeContract::getId,
+                        Function.identity(),
+                        (existing, replacement) -> existing // 处理重复ID,保留第一个
+                ));
+
+        // 4. 准备更新列表 - 只更新ext_node_type字段
+        List<ArchiveTreeContract> updateList = new ArrayList<>();
+
+        for (ArchiveTreeContract local : existContracts) {
+            ArchiveTreeContract external = externalContractMap.get(local.getId());
+            if (external != null) {
+                // 只更新ext_node_type字段
+                local.setExtNodeType(external.getExtNodeType());
+                updateList.add(local);
+            }
+        }
+
+        // 5. 批量更新(建议分批处理,如每500条一次)
+        if (!updateList.isEmpty()) {
+            archiveTreeContractService.updateBatchById(updateList);
+        }
     }
+
     @Override
     public void addArchiveTreeContract(@RequestBody List<ArchiveTreeContract> archiveTreeContracts, @RequestParam Long rootId){
 //        archiveTreeContractService.saveBatch(archiveTreeContracts);

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

@@ -968,7 +968,7 @@
         select * from blade_dict_biz where parent_id != 0 and code = 'contract_log_type' and is_deleted = 0
     </select>
     <select id="getAllTrialNode" resultType="org.springblade.manager.vo.ArchiveSyncTrialVO">
-        select p_key_id,id,parent_id,node_name,full_name,node_type,sort
+        select p_key_id,id,parent_id,node_name,full_name,node_type,sort,p_id
         from m_wbs_tree_private
         where wbs_type = 2 and project_id = #{projectId} and `type` = 1 and is_deleted = 0
           and node_type in (51,52,53)
@@ -977,7 +977,7 @@
     <select id="getAllTrialRecord" resultType="org.springblade.business.entity.TrialSelfInspectionRecord">
         select *
         from u_trial_self_inspection_record
-        where contract_id = #{contractId} and is_deleted = 0 and task_status = '已审批'
+        where contract_id = #{contractId} and is_deleted = 0 and task_status = '已审批' order by sort
     </select>
     <select id="getAllTrialRecordFile"
             resultType="org.springblade.business.vo.TrialSelfInspectionRecordFileVO">

+ 3 - 3
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreeContractMapper.xml

@@ -1057,17 +1057,17 @@
 
     </select>
     <select id="getSiblingWbsContract" resultType="org.springblade.manager.entity.WbsTreeContract">
-        select * from m_wbs_tree_contract where p_id= (select p_id from m_wbs_tree_contract where p_key_id=#{pKeyId}) and is_deleted=0;
+        select * from m_wbs_tree_contract where p_id= (select p_id from m_wbs_tree_contract where p_key_id=#{pKeyId}) and is_deleted=0 order by sort;
     </select>
     <select id="getWbsTreeContractsByPKeyIds" resultType="org.springblade.manager.entity.WbsTreeContract">
-        select p_key_id,parent_id,p_id,ancestors,ancestors_p_id from m_wbs_tree_contract where p_key_id in (
+        select p_key_id,parent_id,p_id,ancestors,ancestors_p_id,node_type from m_wbs_tree_contract where p_key_id in (
         <foreach collection="pKeyIds" item="pkeyId" separator=",">
             #{pkeyId}
         </foreach>
         ) and is_deleted=0
     </select>
     <select id="getChildWbsTreeContracts" resultType="org.springblade.manager.entity.WbsTreeContract">
-        select p_key_id,ancestors,ancestors_p_id from m_wbs_tree_contract where ancestors_p_id like #{pKeyId}  and is_deleted=0
+        select p_key_id,ancestors,ancestors_p_id from m_wbs_tree_contract where FIND_IN_SET(#{pKeyId}, ancestors_p_id)   and is_deleted=0
     </select>
 
 </mapper>

+ 3 - 5
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreePrivateMapper.xml

@@ -891,11 +891,9 @@
           and project_id = #{projectId} AND type in(2, 10)
     </delete>
 
-    <delete id="delTableById">
-        delete
-        from m_table_info
-        WHERE id = #{pKeyId}
-    </delete>
+    <update id="delTableById">
+        update m_table_info set is_deleted = 1 WHERE id = #{pKeyId}
+    </update>
     <update id="cancelLinkNodeTreeInfoByJL">
         UPDATE m_wbs_tree_private
         SET jler_tree_id = NULL

+ 36 - 13
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ArchiveTreeContractSyncImpl.java

@@ -1,5 +1,6 @@
 package org.springblade.manager.service.impl;
 
+import cn.hutool.core.lang.mutable.MutableInt;
 import com.alibaba.nacos.common.utils.DateFormatUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.AllArgsConstructor;
@@ -1032,6 +1033,17 @@ public class ArchiveTreeContractSyncImpl {
         Map<Long, List<TrialSelfInspectionRecord>> recordsMap = records.stream().collect(Collectors.groupingBy(TrialSelfInspectionRecord::getNodeId));
         List<ArchiveTreeContract> addNode = new ArrayList<>();
         List<ArchiveFile> addFile = new ArrayList<>();
+        //试验节点id 添加到档案的节点id映射关系 用与父节点创建
+        HashMap<Long, Long> map = new HashMap<>();
+        for (ArchiveSyncTrialVO trial : trials) {
+            map.put(trial.getPKeyId(),SnowFlakeUtil.getId());
+        }
+
+        //获取文档文件最大 sort
+        Integer sort = archiveFileClient.selectMaxSortByContractId(contractInfo.getId());
+        MutableInt mutableInt = new MutableInt();
+        mutableInt.set(sort);
+
         /**循环指定试验类型 */
         for (Integer type : TrialType) {
             //根据类型获取试验节点
@@ -1042,7 +1054,7 @@ public class ArchiveTreeContractSyncImpl {
             //循环试验节点
             for (ArchiveSyncTrialVO vo : trialVOS) {
                 //把节点转换为档案节点,挂载到同步节点下
-                ArchiveTreeContract trialNode = createTypeNode(vo.getNodeName(),vo.getSort(), archiveTreeContract);
+                ArchiveTreeContract trialNode = createTypeNode(vo.getNodeName(), vo.getSort(), vo.getPKeyId(), vo.getPId(), archiveTreeContract, map);
                 addNode.add(trialNode);
                 //获取出节点下面的报告,并转换为档案文件
                 List<TrialSelfInspectionRecord> files = recordsMap.get(vo.getPKeyId());
@@ -1051,7 +1063,7 @@ public class ArchiveTreeContractSyncImpl {
                 }
                 //挂载到子节点下
                 List<ArchiveFile> newArchiveFileList = files.stream().map(file -> {
-                    ArchiveFile archiveFile = trialFileCovertArchiveFile(file, trialNode, contractInfo);
+                    ArchiveFile archiveFile = trialFileCovertArchiveFile(file, trialNode, contractInfo, mutableInt);
                     return archiveFile;
                 }).collect(Collectors.toList());
                 addFile.addAll(newArchiveFileList);
@@ -1066,10 +1078,17 @@ public class ArchiveTreeContractSyncImpl {
             List<TrialDetectionData> dataList = archiveTreeContractMapper.getAllTrialTestRecord(contractId);
             //检测报告,按照节点分组
             Map<Long, List<TrialDetectionData>> dataMap = dataList.stream().collect(Collectors.groupingBy(l -> l.getNodeId()));
+
+            //试验节点id 添加到档案的节点id映射关系 用与父节点创建
+            HashMap<Long, Long> map2 = new HashMap<>();
+            for (TrialClassificationConfiguration configuration : threeTestNodes) {
+                map2.put(configuration.getId(),SnowFlakeUtil.getId());
+            }
+
             //循环所有节点
             for (TrialClassificationConfiguration vo : threeTestNodes) {
                 //把节点转换为档案节点,挂载到同步节点下
-                ArchiveTreeContract trialNode = createTypeNode(vo.getNodeName(),vo.getNodeType() == 1 ?  vo.getSort() : vo.getSort() + 100, archiveTreeContract);
+                ArchiveTreeContract trialNode = createTypeNode(vo.getNodeName(), vo.getNodeType() == 1 ? vo.getSort() : vo.getSort() + 100, vo.getId(), vo.getParentId(), archiveTreeContract, map2);
                 addNode.add(trialNode);
                 //获取出节点下面的报告,并转换为档案文件
                 List<TrialDetectionData> files = dataMap.get(vo.getId());
@@ -1078,7 +1097,7 @@ public class ArchiveTreeContractSyncImpl {
                 }
                 //挂载到子节点下
                 List<ArchiveFile> newArchiveFileList = files.stream().map(file -> {
-                    ArchiveFile archiveFile = trialTestFileCovertArchiveFile(file, trialNode);
+                    ArchiveFile archiveFile = trialTestFileCovertArchiveFile(file, trialNode, mutableInt);
                     return archiveFile;
                 }).collect(Collectors.toList());
                 addFile.addAll(newArchiveFileList);
@@ -1200,6 +1219,7 @@ public class ArchiveTreeContractSyncImpl {
         if (CollectionUtil.isEmpty(logs)){
             throw new ServiceException("当前合同段下没有日志资料");
         }
+        HashMap<Long, Long> map = new HashMap<>();
         //日志按照类型分组
         Map<Integer, List<ArchiveSyncLogVO>> logMap = logs.stream().collect(Collectors.groupingBy(ArchiveSyncLogVO::getLogType));
         //循环选择的日志类型
@@ -1209,7 +1229,7 @@ public class ArchiveTreeContractSyncImpl {
                 throw new ServiceException("日志存在字典中未配置的类型,请检查后再同步");
             }
             //生成子节点
-            ArchiveTreeContract logNode = createTypeNode(typeName, 0,archiveTreeContract);
+            ArchiveTreeContract logNode = createTypeNode(typeName, 0, null, null, archiveTreeContract, map);
             /** 如果到时候修改需求,只显示有日志的,则下面文件存在再add*/
             addNode.add(logNode);
             //然后再从分组中获取,如果存在值则转换为档案文件
@@ -1288,10 +1308,11 @@ public class ArchiveTreeContractSyncImpl {
 
         List<ArchiveTreeContract> addNode = new ArrayList<>();
         List<ArchiveFile> addFile = new ArrayList<>();
+        HashMap<Long, Long> map = new HashMap<>();
         //循环下级节点
         for (ImageClassificationConfig imageType : imageTypes) {
             //把分类生成为影像资料的下级节点
-            ArchiveTreeContract imageNode = createTypeNode(imageType.getClassfName(), 0, archiveTreeContract);
+            ArchiveTreeContract imageNode = createTypeNode(imageType.getClassfName(), 0, null, null, archiveTreeContract, map);
             addNode.add(imageNode);
             //如果当前分类下存在文件,则根据类型生成树,还是日期
 //            List<ImageClassificationFileDTO> files = imageMap.get(imageType.getId());
@@ -1564,7 +1585,7 @@ public class ArchiveTreeContractSyncImpl {
     /**
      * 质检试验检测资料 转换为 档案文件
      */
-    private ArchiveFile trialTestFileCovertArchiveFile(TrialDetectionData vo,ArchiveTreeContract parentNode)
+    private ArchiveFile trialTestFileCovertArchiveFile(TrialDetectionData vo,ArchiveTreeContract parentNode, MutableInt sort)
     {
         ArchiveFile archiveFile = new ArchiveFile();
         archiveFile.setId(SnowFlakeUtil.getId());
@@ -1607,7 +1628,8 @@ public class ArchiveTreeContractSyncImpl {
                 .toLocalDateTime();
         archiveFile.setFtime(localDateTime);
         archiveFile.setUtime(localDateTime);
-        archiveFile.setSort(0);
+        sort.set(sort.get() + 1);
+        archiveFile.setSort(sort.get());
         archiveFile.setPageNum("1");
         archiveFile.setSourceType(1);
         archiveFile.setIsElement(0);
@@ -1619,7 +1641,7 @@ public class ArchiveTreeContractSyncImpl {
     /**
      * 质检试验资料 转换为 档案文件
      */
-    private ArchiveFile trialFileCovertArchiveFile(TrialSelfInspectionRecord vo,ArchiveTreeContract parentNode, ContractInfo contractInfo)
+    private ArchiveFile trialFileCovertArchiveFile(TrialSelfInspectionRecord vo,ArchiveTreeContract parentNode, ContractInfo contractInfo, MutableInt sort )
     {
         ArchiveFile archiveFile = new ArchiveFile();
         archiveFile.setId(SnowFlakeUtil.getId());
@@ -1663,7 +1685,8 @@ public class ArchiveTreeContractSyncImpl {
                 .toLocalDateTime();
         archiveFile.setFtime(localDateTime);
         archiveFile.setUtime(localDateTime);
-        archiveFile.setSort(0);
+        sort.set(sort.get() + 1);
+        archiveFile.setSort(sort.get());
         archiveFile.setPageNum("1");
         archiveFile.setSourceType(1);
         archiveFile.setIsElement(0);
@@ -1755,14 +1778,14 @@ public class ArchiveTreeContractSyncImpl {
      * 档案同步质检影像资料时,为影像资料-日志资料   节点下级创建分类节点
      * 传入分类名称
      */
-    public ArchiveTreeContract createTypeNode(String nodeName, Integer sort,ArchiveTreeContract archiveTreeContract){
+    public ArchiveTreeContract createTypeNode(String nodeName, Integer sort,Long pKeyId, Long pId,ArchiveTreeContract archiveTreeContract, HashMap<Long, Long> map){
         ArchiveTreeContract archiveTreeContractTemp = new ArchiveTreeContract();
         //设置基本信息
-        archiveTreeContractTemp.setId(SnowFlakeUtil.getId());
+        archiveTreeContractTemp.setId(map.get(pKeyId) == null ? SnowFlakeUtil.getId() : map.get(pKeyId));
         archiveTreeContractTemp.setTenantId(AuthUtil.getTenantId());
         archiveTreeContractTemp.setProjectId(archiveTreeContract.getProjectId());
         archiveTreeContractTemp.setContractId(archiveTreeContract.getContractId());
-        archiveTreeContractTemp.setParentId(archiveTreeContract.getId());
+        archiveTreeContractTemp.setParentId(pId == null || map == null || map.get(pId) == null ? archiveTreeContract.getId() : map.get(pId));
         archiveTreeContractTemp.setAncestors(archiveTreeContract.getAncestors()+","+archiveTreeContract.getId());
         archiveTreeContractTemp.setNodeName(nodeName);
         archiveTreeContractTemp.setNodeType(1);

+ 59 - 51
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/FormulaServiceImpl.java

@@ -1115,7 +1115,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
                     FormData ft = tec.formDataMap.get(k.getCode());
                     if (ft.getCoordsList().size() > 5 || (k.getEName().contains("实测值") && !k.getEName().contains("设计值") && !k.getEName().contains("合格率"))) {
                         tec.checkItems.add(ft);
-                    } else if (k.getEName().contains("检验日期")) {
+                    } else if (k.getEName().contains("检验日期")||k.getEName().contains("检验时间")) {
                         tec.checkDate.add(ft);
                     }
                 });
@@ -2604,7 +2604,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
         String sql = "Select * from s_contract_meter_period where contract_id=" + contractId + " and is_deleted=0" + " order by start_date";
         List<ContractMeterPeriod> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(ContractMeterPeriod.class));
         //获取所有excel报表源数据
-        String sqlForExcel = "SELECT p.p_key_id,e.name,e.id,p.html_url,e.file_url,p.init_table_name as tabName FROM m_wbs_tree_private as p LEFT JOIN m_excel_tab as e on p.excel_id=e.id where p.wbs_type=3 AND p.is_deleted=0 AND p.type=2" + " and p.project_id=" + projectId;
+        String sqlForExcel = "SELECT p.p_key_id,e.name,e.id,p.html_url,e.file_url FROM m_wbs_tree_private as p LEFT JOIN m_excel_tab as e on p.excel_id=e.id where p.wbs_type=3 AND p.is_deleted=0 AND p.type=2" + " and p.project_id=" + projectId;
         List<ExcelTabVo1> excelTabs = jdbcTemplate.query(sqlForExcel, new BeanPropertyRowMapper<>(ExcelTabVo1.class));
         ArrayList<ReportResult> reportResults = new ArrayList<>();
         //中期支付报表封面
@@ -2650,25 +2650,25 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
         String htmlUrl6 = excel6.get().getHtmlUrl();
         Long pKeyId6 = excel6.get().getPKeyId();
         // 获取中期支付报表封面的pdfurl
-        ReportResult CoverOfMidtermPaymentReportPDF = getCoverOfMidtermPaymentReportPDF(url, contractId, periodId, htmlUrl, projectName, list, pKeyId, excel.get().getTabName());
+        ReportResult CoverOfMidtermPaymentReportPDF = getCoverOfMidtermPaymentReportPDF(url, contractId, periodId, htmlUrl, projectName, list, pKeyId);
 
         //获取中间支付审核表的pdfurl
-        ReportResult intermediatePaymentPDF = getIntermediatePaymentPDF(url1, contractId, periodId, list, redisId, htmlUrl1, blReserveFundsRatioNew, projectName, changeMoneyNew, pKeyId1, excel1.get().getTabName());
+        ReportResult intermediatePaymentPDF = getIntermediatePaymentPDF(url1, contractId, periodId, list, redisId, htmlUrl1, blReserveFundsRatioNew, projectName, changeMoneyNew, pKeyId1,reportId);
 
         //获取补助款申请支付审核表pdfUrl
-        ReportResult subsidyApplicationPaymentReviewPDF = getSubsidyApplicationPaymentReviewPDF(url2, contractId, periodId, list, htmlUrl2, blReserveFundsRatioNew, projectName, projectId, reportId, pKeyId2,excel2.get().getTabName());
+        ReportResult subsidyApplicationPaymentReviewPDF = getSubsidyApplicationPaymentReviewPDF(url2, contractId, periodId, list, htmlUrl2, blReserveFundsRatioNew, projectName, projectId, reportId, pKeyId2);
 
         //获取中间计量支付证书pdfUrl
-        ReportResult intermediateMeasurementPaymentCertificatePDF = getIntermediateMeasurementPaymentCertificatePDF(url3, contractId, periodId, list, htmlUrl3, blReserveFundsRatioNew, projectName, pKeyId3,excel3.get().getTabName());
+        ReportResult intermediateMeasurementPaymentCertificatePDF = getIntermediateMeasurementPaymentCertificatePDF(url3, contractId, periodId, list, htmlUrl3, blReserveFundsRatioNew, projectName, pKeyId3);
 
         //获取清单支付报表PDF
-        ReportResult inventoryPayReportPDF = getInventoryPayReportPDF(url6, contractId, periodId, projectId, list, redisId, htmlUrl6, pKeyId6, excel6.get().getTabName());
+        ReportResult inventoryPayReportPDF = getInventoryPayReportPDF(url6, contractId, periodId, projectId, list, redisId, htmlUrl6, pKeyId6);
 
         //获取工程支付月报pdfUrl
-        ReportResult monthlyReportPDF = getMonthlyReportPDF(url5, reportId, contractId, periodId, projectId, list, redisId, htmlUrl5, pKeyId5, excel5.get().getTabName());
+        ReportResult monthlyReportPDF = getMonthlyReportPDF(url5, reportId, contractId, periodId, projectId, list, redisId, htmlUrl5, pKeyId5);
 
         //获取中间支付申请表pdfUrl
-        ReportResult intermediateApplyPDF = getIntermediateApplyPDF(url4, periodId, projectId, htmlUrl4, projectName, contractId, pKeyId4, excel4.get().getTabName());
+        ReportResult intermediateApplyPDF = getIntermediateApplyPDF(url4, periodId, projectId, htmlUrl4, projectName, contractId, pKeyId4);
 
         reportResults.add(CoverOfMidtermPaymentReportPDF);
         reportResults.add(intermediatePaymentPDF);
@@ -2686,7 +2686,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
     /**
      * 中期支付报表封面
      */
-    private ReportResult getCoverOfMidtermPaymentReportPDF(String url, Long contractId, Long periodId, String htmlUrl, String projectName, List<ContractMeterPeriod> list, Long pkeyId, String tabName) {
+    private ReportResult getCoverOfMidtermPaymentReportPDF(String url, Long contractId, Long periodId, String htmlUrl, String projectName, List<ContractMeterPeriod> list, Long pkeyId) {
 
         //获取本期计量期
         String sqlForMeterPeriodById = "SELECT id,period_number,start_date,end_date FROM s_contract_meter_period WHERE id=" + periodId + " and is_deleted = 0 ";
@@ -2737,7 +2737,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
                 Cell c10 = getCellByAddress(sheet, "C10");
                 c10.setCellValue(contractInfo.getSupervisionUnitName());
             }
-            dianqian(htmlUrl, sheet, workbook, tabName);
+            dianqian(htmlUrl, sheet, workbook);
             if (!periodId.equals(1867838908899852290L)) {
                 dianqianTime(htmlUrl, sheet, workbook, pkeyId, periodId, contractId);
             }
@@ -2773,7 +2773,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
     }
 
     /*设置电签ID*/
-    public void dianqian(String htmlUrl, Sheet sheet, Workbook workbook, String tabName) {
+    public void dianqian(String htmlUrl, Sheet sheet, Workbook workbook) {
         try {
             InputStream inputStreamByUrl = FileUtils.getInputStreamByUrl(htmlUrl);
             String htmlString = IoUtil.readToString(inputStreamByUrl);
@@ -2781,30 +2781,8 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
             Element table = doc.select("table").first();
             // 组装电签设置
             Elements dqids = table.getElementsByAttribute("dqid");
-            // 电签组装2
-            String dqSql = "select e_key,GROUP_CONCAT(DISTINCT concat('*✹',id)) ids from u_sign_key_role_info where tab_en_name='" + tabName + "' GROUP BY e_key";
-            List<Map<String, Object>> mapList = jdbcTemplate.queryForList(dqSql);
-            if(mapList!=null && mapList.size()>0){
-                for(Map<String, Object> map : mapList) {
-                    Elements elementsBy = table.getElementsByAttributeValueStarting("keyname", map.get("e_key") + "_");
-                    if(elementsBy!=null && elementsBy.size()>0){
-                        for(Element element : elementsBy){
-                            String dqIds = (String) map.get("ids");
-                            dqIds = dqIds.replace(",","");
-                            dqIds = dqIds.substring(1);
-                            element.attr("sign_type", dqIds);
-                            dqids.add(element);
-                        }
-                    }
-                }
-            }
             for (Element element : dqids) {
-                String dqid="";
-                if(element.hasAttr("sign_type")){
-                    dqid = element.attr("sign_type");
-                }else{
-                    dqid = element.attr("dqid");
-                }
+                String dqid = element.attr("dqid");
 
                 int x1 = 0;
                 int y1 = 0;
@@ -2850,6 +2828,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
         }
     }
 
+
     /*设置电签时间*/
     private void dianqianTime(String htmlUrl, Sheet sheet, Workbook workbook, Long pkeyId, Long perId, Long contractId) {
         String timeSql = "SELECT DISTINCT a.time_col_key,DATE_FORMAT(c.create_time, '%Y年%m月%d日') as create_time from m_textdict_info a ,m_project_assignment_user b,u_task_parallel c ,u_task d  where a.sig_role_id=b.role_id and b.user_id=c.task_user and c.process_instance_id=d.process_instance_id and  b.contract_id=" + contractId + " and LENGTH(a.time_col_key)>=2 " +
@@ -2898,7 +2877,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
     /**
      * 中间计量支付证书获取PDFurl
      */
-    private ReportResult getIntermediateMeasurementPaymentCertificatePDF(String url3, Long contractId, Long periodId, List<ContractMeterPeriod> list, String htmlUrl, BigDecimal blReserveFundsRatioNew, String projectName, Long pkeyId, String tabName) {
+    private ReportResult getIntermediateMeasurementPaymentCertificatePDF(String url3, Long contractId, Long periodId, List<ContractMeterPeriod> list, String htmlUrl, BigDecimal blReserveFundsRatioNew, String projectName, Long pkeyId) {
         //判断当前是否是第一期
         Boolean isOnePeriod = false;
         ContractMeterPeriod contractMeterPeriod = list.get(0);
@@ -2979,7 +2958,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
             Cell c3 = getCellByAddress(sheet, "C3");
             c3.setCellValue(projectName);
 
-            dianqian(htmlUrl, sheet, workbook, tabName);
+            dianqian(htmlUrl, sheet, workbook);
             if (!periodId.equals(1867838908899852290L)) {
                 dianqianTime(htmlUrl, sheet, workbook, pkeyId, periodId, contractId);
             }
@@ -3025,7 +3004,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
     /**
      * 补助款申请支付审核表获取PDFurl
      */
-    private ReportResult getSubsidyApplicationPaymentReviewPDF(String url2, Long contractId, Long periodId, List<ContractMeterPeriod> list, String htmlUrl, BigDecimal blReserveFundsRatioNew, String projectName, Long projectId, Long reportId, Long pkeyId, String tabName) {
+    private ReportResult getSubsidyApplicationPaymentReviewPDF(String url2, Long contractId, Long periodId, List<ContractMeterPeriod> list, String htmlUrl, BigDecimal blReserveFundsRatioNew, String projectName, Long projectId, Long reportId, Long pkeyId) {
         //判断当前是否是只有1期计量
         Boolean isOnePeriod = false;
         ContractMeterPeriod contractMeterPeriod = list.get(0);
@@ -3299,7 +3278,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
             Cell a2 = getCellByAddress(sheet, "A2");
             a2.setCellValue(projectName);
             // 电签
-            dianqian(htmlUrl, sheet, workbook, tabName);
+            dianqian(htmlUrl, sheet, workbook);
             if (!periodId.equals(1867838908899852290L)) {
                 dianqianTime(htmlUrl, sheet, workbook, pkeyId, periodId, contractId);
             }
@@ -3339,7 +3318,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
     /**
      * 中间支付审核表获取PDFurl
      */
-    public ReportResult getIntermediatePaymentPDF(String url, Long contractId, Long periodId, List<ContractMeterPeriod> list, Long redisId, String htmlUrl, BigDecimal blReserveFundsRatioNew, String projectName, BigDecimal changeMoneyNew, Long pkeyId, String tabName) {
+    public ReportResult getIntermediatePaymentPDF(String url, Long contractId, Long periodId, List<ContractMeterPeriod> list, Long redisId, String htmlUrl, BigDecimal blReserveFundsRatioNew, String projectName, BigDecimal changeMoneyNew, Long pkeyId,Long reportId) {
         //判断当前是否是只有1期计量
         Boolean isOnePeriod = false;
         ContractMeterPeriod contractMeterPeriod1 = list.get(0);
@@ -3437,9 +3416,30 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
             changeMoneySum = changeMoneySum.setScale(0, RoundingMode.HALF_UP);*/
             BigDecimal changemoneySum = changeMoneyNew.subtract(totalMoney).subtract(lastEndChangeMoney).setScale(0, RoundingMode.HALF_UP);
             Cell e5 = getCellByAddress(sheet, "E5");
+            String sql4 = "Select * From s_interim_pay_certificate_item where certificate_id=" + reportId + " and chapter_seq='本期变更' and is_deleted=0 ";
+            List<InterimPayCertificateItem> lists = jdbcTemplate.query(sql4, new BeanPropertyRowMapper<>(InterimPayCertificateItem.class));
+            if (lists.isEmpty()) {
+                MonthlyReportVo vo=new MonthlyReportVo();
+                vo.setChapterSeq("本期变更");
+                vo.setBeforeEndMoney(lastEndChangeMoney);
+                vo.setNowMoney(changemoneySum);
+                vo.setNowEndMoney(lastEndChangeMoney.add(changemoneySum));
+                //第一次需要新增支付项
+                interimPayCertificateItemClient.addInterimPayCertificateItem1(reportId, vo);
+            } else {
+                InterimPayCertificateItem item = lists.get(0);
+                //第二次后就是更新
+                changemoneySum=stringToBigDecimal(item.getCurrentPeriodPay());
+                item.setCurrentPeriodPay(changemoneySum.toString());
+                item.setPreviousPeriodEndPay(lastEndChangeMoney.toString());
+                item.setCurrentPeriodEndPay(lastEndChangeMoney.add(changemoneySum).toString());
+                interimPayCertificateItemClient.updateInterimPayCertificateItem1(item);
+            }
             if(periodId==1890367903054798850L){
                 e5.setCellValue("515977");
-            }else {
+            } else if (periodId==1963802568671502338L) {
+                e5.setCellValue("1496852");
+            } else {
                 e5.setCellValue(changemoneySum.toString());
             }
 
@@ -3611,7 +3611,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
             //顶部项目名称
             Cell a1 = getCellByAddress(sheet, "A1");
             a1.setCellValue(projectName);
-            dianqian(htmlUrl, sheet, workbook, tabName);
+            dianqian(htmlUrl, sheet, workbook);
             if (!periodId.equals(1867838908899852290L)) {
                 dianqianTime(htmlUrl, sheet, workbook, pkeyId, periodId, contractId);
             }
@@ -3661,6 +3661,17 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
     }
 
 
+        public  BigDecimal stringToBigDecimal(String value) {
+            if (StringUtils.isEmpty(value)) {
+                return BigDecimal.ZERO;
+            }
+            try {
+                return new BigDecimal(value.trim());
+            } catch (NumberFormatException e) {
+                return BigDecimal.ZERO;
+            }
+        }
+
 
     /**
      * 获取上期末的数据
@@ -3760,10 +3771,9 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
      * 中间支付申请表获取pdfUrl
      *
      * @param url
-     * @param tabName
      * @return
      */
-    public ReportResult getIntermediateApplyPDF(String url, Long periodId, Long projectId, String htmlUrl, String projectName, Long contractId, Long pkeyId, String tabName) {
+    public ReportResult getIntermediateApplyPDF(String url, Long periodId, Long projectId, String htmlUrl, String projectName, Long contractId, Long pkeyId) {
         InputStream modInput = null;
         FileInputStream excelFileInput = null;
         FileOutputStream outputStream = null;
@@ -3773,7 +3783,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
             modInput = CommonUtil.getOSSInputStream(url);
             workbook = WorkbookFactory.create(modInput);
             Sheet sheet = workbook.getSheetAt(0);
-            dianqian(htmlUrl, sheet, workbook, tabName);
+            dianqian(htmlUrl, sheet, workbook);
             if (!periodId.equals(1867838908899852290L)) {
                 dianqianTime(htmlUrl, sheet, workbook, pkeyId, periodId, contractId);
             }
@@ -3840,10 +3850,9 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
      * 工程支付月报获取pdfUrl
      *
      * @param url
-     * @param tabName
      * @return
      */
-    public ReportResult getMonthlyReportPDF(String url, Long reportId, Long contractId, Long periodId, Long projectId, List<ContractMeterPeriod> list, Long redisId, String htmlUrl, Long pkeyId, String tabName) throws IllegalAccessException {
+    public ReportResult getMonthlyReportPDF(String url, Long reportId, Long contractId, Long periodId, Long projectId, List<ContractMeterPeriod> list, Long redisId, String htmlUrl, Long pkeyId) throws IllegalAccessException {
         //获取本期计量期
         String sqlForMeterPeriodById = "SELECT id,period_number,start_date,end_date FROM s_contract_meter_period WHERE id=" + periodId + " and is_deleted = 0 ";
         ContractMeterPeriod contractMeterPeriodNow = jdbcTemplate.queryForObject(sqlForMeterPeriodById, new BeanPropertyRowMapper<>(ContractMeterPeriod.class));
@@ -4301,7 +4310,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
                     m_3.setCellValue(formattedDate);
                 }
             }
-            dianqian(htmlUrl, sheet, workbook, tabName);
+            dianqian(htmlUrl, sheet, workbook);
             if (!periodId.equals(1867838908899852290L)) {
                 dianqianTime(htmlUrl, sheet, workbook, pkeyId, periodId, contractId);
             }
@@ -4337,11 +4346,10 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
      * 清单支付报表获取PDF
      *
      * @param url
-     * @param tabName
      * @return
      */
 
-    public ReportResult getInventoryPayReportPDF(String url, Long contractId, Long periodId, Long projectId, List<ContractMeterPeriod> list, Long redisId, String htmlUrl, Long pkeyId, String tabName) throws FileNotFoundException, IllegalAccessException {
+    public ReportResult getInventoryPayReportPDF(String url, Long contractId, Long periodId, Long projectId, List<ContractMeterPeriod> list, Long redisId, String htmlUrl, Long pkeyId) throws FileNotFoundException, IllegalAccessException {
         MiddleMeterApply middleMeterApply = new MiddleMeterApply();
         middleMeterApply.setContractId(contractId);
         middleMeterApply.setContractPeriodId(periodId);
@@ -4729,7 +4737,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
                             l4.setCellStyle(cellStyle);
                             l4.setCellValue(contractInfo.getSupervisionUnitName());
                         }
-                        dianqian(htmlUrl, sheet, workbook, tabName);
+                        dianqian(htmlUrl, sheet, workbook);
                         if (!periodId.equals(1867838908899852290L)) {
                             dianqianTime(htmlUrl, sheet, workbook, pkeyId, periodId, contractId);
                         }

+ 29 - 2
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeContractServiceImpl.java

@@ -4696,7 +4696,7 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
         List<WbsTreeContract> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(WbsTreeContract.class));
 
         InputStream templateStream = new FileInputStream(new File(templatePath));
-        org.apache.poi.ss.usermodel.Workbook workbook = WorkbookFactory.create(templateStream);
+        Workbook workbook = WorkbookFactory.create(templateStream);
         Sheet templateSheet = workbook.getSheetAt(0);
 
         // 移除默认的Sheet1
@@ -4918,6 +4918,33 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
         List<WbsTreeContract> list= wbsTreeContractMapper.getWbsTreeContractsByPKeyIds(dto.getLeftPkeyIds());
         WbsTreeContract moveFatherNode = this.getById(list.get(0).getPId());
         WbsTreeContract fatherContract = this.getById(dto.getRightPkeyId());
+        Integer leftNodeType = 0;
+        Integer rightNodeType =0;
+        if(list.get(0).getNodeType()!=null){
+            if(list.get(0).getNodeType()==1){
+                leftNodeType=1;
+            } else if (list.get(0).getNodeType()==18) {
+                leftNodeType=2;
+            }else {
+                leftNodeType=list.get(0).getNodeType()+1;
+            }
+        }
+        if(fatherContract.getNodeType()!=null){
+            if(fatherContract.getNodeType()==1){
+                rightNodeType=1;
+            } else if (fatherContract.getNodeType()==18) {
+                rightNodeType=2;
+            }else {
+                rightNodeType=fatherContract.getNodeType()+1;
+            }
+        }
+        if(leftNodeType==rightNodeType){
+            throw new ServiceException("请勿同级节点相互移动");
+        }
+        if(leftNodeType<rightNodeType){
+            throw new ServiceException("请勿向比自己层级低的节点移动");
+        }
+
 
         String sql="SELECT * from m_formula_data_block WHERE sw_id = "+moveFatherNode.getId()+" and contract_id ="+moveFatherNode.getContractId()+" and  type =0";
         List<FormulaDataBlock> formulaDataBlocks1 = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(FormulaDataBlock.class));
@@ -5660,7 +5687,7 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
     }
 
      //保存Workbook到本地文件(本地测试放开)
-    private void saveWorkbookToFile(org.apache.poi.ss.usermodel.Workbook workbook, String filePath) {
+    private void saveWorkbookToFile(Workbook workbook, String filePath) {
         try {
             File file = new File(filePath);
             File parentDir = file.getParentFile();

+ 8 - 7
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeSynchronousRecordServiceImpl.java

@@ -56,6 +56,11 @@ public class WbsTreeSynchronousRecordServiceImpl extends ServiceImpl<WbsTreeSync
                 .in(WbsTreeSynchronousRecord::getStatus, 0, 1));
         List<String> nodeIds = wbsTreeSynchronousRecords.stream().map(WbsTreeSynchronousRecord::getNodeId).collect(Collectors.toList());
 
+        //  //判断节点类型  如果是试验或计量则不允许添加合同合同段
+        WbsTreePrivate wbsTreePrivate1 = wbsTreePrivateMapper.selectById(mWbsTreeSynchronousRecord.getNodeId());
+        if (mWbsTreeSynchronousRecord.getRange() == 2 && wbsTreePrivate1 != null && !Objects.equals(wbsTreePrivate1.getWbsType(), "1")) {
+            throw new ServiceException(wbsTreePrivate1.getNodeName() + " 节点不是质检类型,无法同步合同段");
+        }
         //所有子节点集合
         List<Long> privateIds = new ArrayList<>();
         //通过 ancestors_p_id 查询所有 非表单子节点
@@ -69,13 +74,6 @@ public class WbsTreeSynchronousRecordServiceImpl extends ServiceImpl<WbsTreeSync
                 );
                 privateIds.add(Long.valueOf(s));
                 privateIds.addAll(wbsTreePrivates.stream().map(WbsTreePrivate::getPKeyId).collect(Collectors.toList()));
-
-
-                //  //判断节点类型  如果是试验或计量则不允许添加合同合同段
-                WbsTreePrivate wbsTreePrivate = wbsTreePrivateMapper.selectById(nodeId);
-                if (mWbsTreeSynchronousRecord.getRange() == 2 && wbsTreePrivate != null && !Objects.equals(wbsTreePrivate.getWbsType(), 1)) {
-                    throw new ServiceException(wbsTreePrivate.getNodeName() + " 节点不是质检类型,无法同步合同段");
-                }
             }
         }
 
@@ -261,6 +259,9 @@ public class WbsTreeSynchronousRecordServiceImpl extends ServiceImpl<WbsTreeSync
     public void syncInit() {
 //         本地环境跳过执行(可添加日志输出)
         if (!schedulerEnabled) return;
+        if(!SystemUtils.isLinux()){
+            return;
+        }
 
         List<WbsTreeSynchronousRecord> wbsTreeSynchronousRecords = baseMapper.selectList(new QueryWrapper<WbsTreeSynchronousRecord>().lambda()
                 .in(WbsTreeSynchronousRecord::getStatus, 0, 1)

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

@@ -4769,7 +4769,7 @@ public class TaskController extends BladeController {
                 String UPSqlJL = " update u_task_parallel a set a.e_visa_status=1,e_visa_content='电签成功',initiative=2 where sort in( SELECT a.fixed_flow_branch_sort from u_task b,u_fixed_flow_link a where b.`status` in(1,2) and b.form_data_id='" + report.getPeriodId() + "' and a.fixed_flow_id=b.fixed_flow_id and a.flow_task_type=2 ) and a.process_instance_id in(SELECT process_instance_id from u_task b where b.`status` in(1,2) and b.form_data_id='" + report.getPeriodId() + "')";
                 String DeSql = "delete from u_task_batch where task_parallel_id in(select b.process_instance_id from u_task b where b.`status` in(1,2) and b.form_data_id=" + report.getPeriodId() + ")";
                 //删除以前存在的电签
-                String sql = "insert into u_task_batch(id,task_parallel_id,json_data,create_user,create_dept,create_time,update_user,update_time,status,is_deleted,nick_name,sign_format,sign_type) " + " SELECT a.id,a.process_instance_id,json_object('approvalFileList',json_array(),'approvalType',b.approval_type,'comment','','flag','OK','formDataId',b.form_data_id,'parallelProcessInstanceId',a.parallel_process_instance_id,'pass',true,'taskId',b.id) as  json_data,a.task_user,a.create_dept,a.create_time,a.update_user,a.update_time,1 as status,0 as is_deleted,a.task_user_name as nick_name ,1 as sign_format,1 as sign_type from u_task_parallel a,u_task b,u_fixed_flow_link c where b.`status` in(1,2) and  a.process_instance_id=b.process_instance_id and c.flow_task_type!=2 and a.sort = c.fixed_flow_branch_sort and c.fixed_flow_id=b.fixed_flow_id and b.form_data_id= '" + report.getPeriodId() + "'";
+                String sql = "insert into u_task_batch(id,task_parallel_id,json_data,create_user,create_dept,create_time,update_user,update_time,status,is_deleted,nick_name,sign_format,sign_type) " + " SELECT a.id,a.process_instance_id,json_object('approvalFileList',json_array(),'approvalType',b.approval_type,'comment','','flag','OK','formDataId',b.form_data_id,'parallelProcessInstanceId',a.parallel_process_instance_id,'pass',true,'taskId',b.id) as  json_data,a.task_user,a.create_dept,a.create_time,a.update_user,SYSDATE(),1 as status,0 as is_deleted,a.task_user_name as nick_name ,1 as sign_format,1 as sign_type from u_task_parallel a,u_task b,u_fixed_flow_link c where b.`status` in(1,2) and  a.process_instance_id=b.process_instance_id and c.flow_task_type!=2 and a.sort = c.fixed_flow_branch_sort and c.fixed_flow_id=b.fixed_flow_id and b.form_data_id= '" + report.getPeriodId() + "'";
 
                 jdbcTemplate.execute(DeSql);
                 jdbcTemplate.execute(UPSql);

+ 4 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/service/IInterimPayCertificateItemService.java

@@ -19,4 +19,8 @@ public interface IInterimPayCertificateItemService extends BaseService<InterimPa
     void sendJLSmg(Long taskId);
 
     void saveOrUpdateBatchList(List<InterimPayCertificateItem> items,Long reportId);
+
+    void addInterimPayCertificateItem1(Long reportId, MonthlyReportVo vo);
+
+    void updateInterimPayCertificateItem1(InterimPayCertificateItem item);
 }

+ 10 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/InterimPayCertificateItemClientImpl.java

@@ -21,4 +21,14 @@ public class InterimPayCertificateItemClientImpl implements InterimPayCertificat
     public void updateInterimPayCertificateItem(List<InterimPayCertificateItem> list4) {
         iInterimPayCertificateItemService.updateInterimPayCertificateItem(list4);
     }
+
+    @Override
+    public void addInterimPayCertificateItem1(Long reportId, MonthlyReportVo vo) {
+        iInterimPayCertificateItemService.addInterimPayCertificateItem1(reportId,vo);
+    }
+
+    @Override
+    public void updateInterimPayCertificateItem1(InterimPayCertificateItem item) {
+        iInterimPayCertificateItemService.updateInterimPayCertificateItem1(item);
+    }
 }

+ 21 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/InterimPayCertificateItemServiceImpl.java

@@ -136,4 +136,25 @@ public class InterimPayCertificateItemServiceImpl extends BaseServiceImpl<Interi
             saveBatch(items);
         }
     }
+
+    @Override
+    public void addInterimPayCertificateItem1(Long reportId, MonthlyReportVo vo) {
+                InterimPayCertificateItem item = new InterimPayCertificateItem();
+                item.setCertificateId(reportId);
+                item.setSort(100);
+                item.setFormName(vo.getChapterSeq());
+                item.setChapterSeq(vo.getFormName());
+                item.setContractAmount(vo.getCurrentMeterMoney() == null ? null : vo.getCurrentMeterMoney().toString());
+                item.setRevisedTotal(vo.getChangeMeterMoney() == null ? null : vo.getChangeMeterMoney().toString());
+                item.setRevisedAmount(vo.getAfterCurrentMeterMoney() == null ? null : vo.getAfterCurrentMeterMoney().toString());
+                item.setCurrentPeriodEndPay(vo.getNowEndMoney() == null ? null : vo.getNowEndMoney().toString());
+                item.setPreviousPeriodEndPay(vo.getBeforeEndMoney() == null ? null : vo.getBeforeEndMoney().toString());
+                item.setCurrentPeriodPay(vo.getNowMoney() == null ? null : vo.getNowMoney().toString());
+                save(item);
+    }
+
+    @Override
+    public void updateInterimPayCertificateItem1(InterimPayCertificateItem item) {
+        updateById(item);
+    }
 }

+ 2 - 2
blade-service/blade-repair/src/main/java/org/springblade/repair/controller/CheckAndRepairController.java

@@ -162,10 +162,10 @@ public class CheckAndRepairController {
         System.out.println("开始扫描private和contract的html");
         StringBuilder result=new StringBuilder("");
         try {
-            String sql="SELECT a.p_key_id,a.is_type_private_pid FROM m_wbs_tree_contract a,m_wbs_tree_private b WHERE a.html_url != b.html_url AND a.is_type_private_pid=b.p_key_id AND a.type=2 AND a.is_deleted=0 AND b.type=2 AND b.html_url is not NULL AND a.project_id=b.project_id";
+            String sql="SELECT a.p_key_id,a.is_type_private_pid FROM m_wbs_tree_contract a,m_wbs_tree_private b WHERE a.html_url != b.html_url AND a.is_type_private_pid=b.p_key_id AND a.type=2 AND a.is_deleted=0 AND b.type=2 AND b.html_url is not NULL AND a.html !='null' AND b.html_url !='nul' AND a.project_id=b.project_id";
             List<WbsTreeContract> contractListlist = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(WbsTreeContract.class));
             for (WbsTreeContract wbsTreeContract : contractListlist) {
-                String update=" update m_wbs_tree_contract set html_url=(select html_url from m_wbs_tree_private where p_key_id="+wbsTreeContract.getIsTypePrivatePid()+") where p_key_id="+wbsTreeContract.getPKeyId();
+                String update=" update m_wbs_tree_contract set html_url=(select html_url from m_wbs_tree_private where html !='null' and  p_key_id="+wbsTreeContract.getIsTypePrivatePid()+") where p_key_id="+wbsTreeContract.getPKeyId();
                 int i = jdbcTemplate.update(update);
                 if(i!=1){
                     result.append(wbsTreeContract.getPKeyId()+"\n");