Browse Source

Merge branch 'dev' of http://219.151.181.73:3000/zhuwei/bladex into dev

laibulaizheli 1 month ago
parent
commit
97fb5aa922
27 changed files with 2276 additions and 19 deletions
  1. 6 3
      blade-common/src/main/java/org/springblade/common/utils/CommonUtil.java
  2. 4 1
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/vo/MessageWarningVO.java
  3. 39 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/ServicePlanDTO.java
  4. 34 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/ServicePlanTaskDTO.java
  5. 13 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/ServiceUserDto.java
  6. 86 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/ServicePlan.java
  7. 50 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/ServicePlanTask.java
  8. 34 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/ServicePlanTaskVO.java
  9. 50 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/ServicePlanVO.java
  10. 8 4
      blade-service/blade-business/src/main/java/org/springblade/business/controller/MessageWarningController.java
  11. 10 0
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/WeatherInfoServiceImpl.java
  12. 6 0
      blade-service/blade-manager/pom.xml
  13. 17 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java
  14. 442 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ServicePlanController.java
  15. 129 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ServicePlanTaskController.java
  16. 45 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ServicePlanMapper.java
  17. 53 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ServicePlanMapper.xml
  18. 44 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ServicePlanTaskMapper.java
  19. 22 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ServicePlanTaskMapper.xml
  20. 63 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/IServicePlanService.java
  21. 44 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/IServicePlanTaskService.java
  22. 5 5
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ExcelTabServiceImpl.java
  23. 914 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ServicePlanServiceImpl.java
  24. 50 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ServicePlanTaskServiceImpl.java
  25. 10 6
      blade-service/blade-manager/src/main/java/org/springblade/manager/utils/PdfAddimgUtil.java
  26. 49 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/wrapper/ServicePlanTaskWrapper.java
  27. 49 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/wrapper/ServicePlanWrapper.java

+ 6 - 3
blade-common/src/main/java/org/springblade/common/utils/CommonUtil.java

@@ -857,11 +857,15 @@ public class CommonUtil {
     public static byte[] compressImage3(String url) throws IOException, ImageProcessingException, MetadataException {
         try(InputStream file = CommonUtil.getOSSInputStream(url)) {
             byte[] imageData = InputStreamToBytes(file);
-            return compressImage3(imageData);
+            String extension = url.substring(url.lastIndexOf(".") + 1).toUpperCase();
+            if ("JPG".equals(extension) || "JPEG".equals(extension)) {
+                extension = "JPEG";
+            }
+            return compressImage3(imageData, extension);
         }
     }
 
-    public static byte[] compressImage3(byte[] imageData) throws ImageProcessingException, IOException, MetadataException {
+    public static byte[] compressImage3(byte[] imageData, String formatName) throws ImageProcessingException, IOException, MetadataException {
         // 读取原始图像(处理旋转问题)
         int orientation = 1;
         Metadata metadata = ImageMetadataReader.readMetadata(new ByteArrayInputStream(imageData));
@@ -873,7 +877,6 @@ public class CommonUtil {
             }
         }
         // 缩放图像
-        String formatName = "JPEG";
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         long sizeLimit = 1024*1024*5; //5M 1920 ×1080
         int width = 1080;

+ 4 - 1
blade-service-api/blade-business-api/src/main/java/org/springblade/business/vo/MessageWarningVO.java

@@ -45,7 +45,10 @@ public class MessageWarningVO extends MessageWarning {
     @ApiModelProperty("结束时间")
     private String endTime;
 
-    @ApiModelProperty("类型")
+    @ApiModelProperty("1废除, 2驳回")
+    private String repealType;
+
+    @ApiModelProperty("1废除, 2驳回")
     private String typeValue;
 
     @ApiModelProperty("任务催办未读数量")

+ 39 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/ServicePlanDTO.java

@@ -0,0 +1,39 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.dto;
+
+import org.springblade.manager.entity.ServicePlan;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 数据传输对象实体类
+ *
+ * @author BladeX
+ * @since 2025-06-25
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ServicePlanDTO extends ServicePlan {
+	private static final long serialVersionUID = 1L;
+
+    private String userId;
+
+    private String planStartTime1;
+
+    private String planEndTime1;
+}

+ 34 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/ServicePlanTaskDTO.java

@@ -0,0 +1,34 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.dto;
+
+import org.springblade.manager.entity.ServicePlanTask;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 数据传输对象实体类
+ *
+ * @author BladeX
+ * @since 2025-06-27
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ServicePlanTaskDTO extends ServicePlanTask {
+	private static final long serialVersionUID = 1L;
+
+}

+ 13 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/ServiceUserDto.java

@@ -0,0 +1,13 @@
+package org.springblade.manager.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ServiceUserDto {
+    private String userId;
+    private String userName;
+}

+ 86 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/ServicePlan.java

@@ -0,0 +1,86 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.time.LocalDate;
+
+import io.swagger.annotations.ApiModelProperty;
+import org.springblade.core.mp.base.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 实体类
+ *
+ * @author BladeX
+ * @since 2025-06-25
+ */
+@Data
+@TableName("m_service_plan")
+@EqualsAndHashCode(callSuper = true)
+public class ServicePlan extends BaseEntity {
+
+	private static final long serialVersionUID = 1L;
+
+	/**
+	* 填写类型 1月度服务计划 2服务完成确认单
+	*/
+    @ApiModelProperty(value = "填写类型 1月度服务计划 2服务完成确认单")
+		private Integer fileInType;
+    /**
+     * 项目Id
+     */
+    @ApiModelProperty(value = "项目Id")
+        private Long projectId;
+    /**
+     * 合同段Id
+     */
+    @ApiModelProperty(value = "合同段Id")
+        private Long contractId;
+	/**
+	* 计划开始时间
+	*/
+    @ApiModelProperty(value = "计划开始时间")
+		private LocalDate planStartTime;
+	/**
+	* 计划结束时间
+	*/
+    @ApiModelProperty(value = "计划结束时间")
+		private LocalDate planEndTime;
+    /**
+     * 编写人
+     */
+    @ApiModelProperty(value = "编写人ID 逗号拼接")
+        private String writeUser;
+	/**
+	* 发送人员
+	*/
+    @ApiModelProperty(value = "发送人员ID 逗号拼接")
+		private String sendUser;
+    /**
+     * pdf路径
+      */
+    @ApiModelProperty(value = "pdf路径")
+        private String pdfUrl;
+
+
+
+
+
+}

+ 50 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/ServicePlanTask.java

@@ -0,0 +1,50 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import org.springblade.core.mp.base.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 实体类
+ *
+ * @author BladeX
+ * @since 2025-06-27
+ */
+@Data
+@TableName("m_service_plan_task")
+@EqualsAndHashCode(callSuper = true)
+public class ServicePlanTask extends BaseEntity {
+
+	private static final long serialVersionUID = 1L;
+
+	/**
+	* 服务计划主键ID
+	*/
+		private Long servicePlanId;
+	/**
+	* 发送人ID
+	*/
+		private Long userId;
+
+		private Integer isCancel;
+
+
+}

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

@@ -0,0 +1,34 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.vo;
+
+import org.springblade.manager.entity.ServicePlanTask;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 视图实体类
+ *
+ * @author BladeX
+ * @since 2025-06-27
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ServicePlanTaskVO extends ServicePlanTask {
+	private static final long serialVersionUID = 1L;
+
+}

+ 50 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/ServicePlanVO.java

@@ -0,0 +1,50 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.vo;
+
+import org.springblade.manager.entity.ServicePlan;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 视图实体类
+ *
+ * @author BladeX
+ * @since 2025-06-25
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ServicePlanVO extends ServicePlan {
+	private static final long serialVersionUID = 1L;
+   //1计划中  2协同中-甲方  3协同中-系统  4已计划
+    private String statusValue;
+    //是否可编辑
+   private Boolean isEdit;
+    //计划开始时间
+   private String startTime;
+   //计划结束时间
+   private String endTime;
+   //发送人
+   private String sendUserName;
+
+   private String writeUserName;
+
+   private String planTime;
+
+
+
+}

+ 8 - 4
blade-service/blade-business/src/main/java/org/springblade/business/controller/MessageWarningController.java

@@ -155,7 +155,8 @@ public class MessageWarningController extends BladeController {
     @ApiOperationSupport(order = 4)
     @ApiOperation(value = "分页", notes = "传入operationWarning")
     public R<IPage<MessageWarningVO>> list(MessageWarningVO vo, Query query) {
-
+        String content = vo.getContent();
+        vo.setContent(null);
         QueryWrapper<MessageWarning> wrapper = Condition.getQueryWrapper(vo);
         //获取当前人的数据
         wrapper.lambda().eq(MessageWarning::getPushUser, AuthUtil.getUserId().toString());
@@ -166,13 +167,16 @@ public class MessageWarningController extends BladeController {
         if (vo.getSmsType() != null && vo.getSmsType() > -1) {
             wrapper.lambda().eq(MessageWarning::getIsRead, new Integer("2").equals(vo.getSmsType()) ? 0 : 1);
         }
-        if (vo.getTypeValue() != null && !vo.getTypeValue().isEmpty()) {
-            if (vo.getTypeValue().equals("1")) {
+        if (vo.getRepealType() != null && !vo.getRepealType().isEmpty()) {
+            if (vo.getRepealType().equals("1")) {
                 wrapper.lambda().like(MessageWarning::getContent, "废除了");
             } else {
                 wrapper.lambda().like(MessageWarning::getContent, "驳回了");
             }
         }
+        if (content != null && !content.isEmpty()) {
+            wrapper.lambda().like(MessageWarning::getContent, content);
+        }
         //设置合同段ID
         wrapper.lambda().eq(MessageWarning::getProjectId, vo.getProjectId()).eq(MessageWarning::getContractId, vo.getContractId());
 
@@ -197,7 +201,7 @@ public class MessageWarningController extends BladeController {
                 if (dictBizs != null && dictBizs.size() > 0) {
                     for (DictBiz biz : dictBizs) {
                         if (biz.getDictKey().equals(reVO.getType().toString())) {
-                            reVO.setTypeValue(biz.getDictValue());
+                            reVO.setRepealType(biz.getDictValue());
                             break;
                         }
                     }

+ 10 - 0
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/WeatherInfoServiceImpl.java

@@ -16,6 +16,7 @@ import org.springblade.business.entity.WeatherInfo;
 import org.springblade.business.mapper.WeatherInfoMapper;
 import org.springblade.business.service.WeatherInfoService;
 import org.springblade.common.utils.BaiduApiUtil;
+import org.springblade.common.utils.SystemUtils;
 import org.springblade.common.utils.YiKeYunApiUtils;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.tool.utils.DateUtil;
@@ -121,6 +122,9 @@ public class WeatherInfoServiceImpl extends ServiceImpl<WeatherInfoMapper, Weath
      */
     @Scheduled(cron = "0 18 10 * * ?")
     public void syncWeatherInfo() {
+        if (!SystemUtils.isLinux()) {
+            return;
+        }
         //获取所有合同段的定位信息
         List<ProjectContractArea> areaList = this.projectContractAreaClient.queryAllContractArea();
         Map<String, Map<String, String>> cachedWeatherMap = new HashMap<>();
@@ -176,6 +180,9 @@ public class WeatherInfoServiceImpl extends ServiceImpl<WeatherInfoMapper, Weath
      */
     @Scheduled(cron = "0 0 8 * * ?")
     public void syncHistoryWeatherInfo() {
+        if (!SystemUtils.isLinux()) {
+            return;
+        }
         //获取所有合同段的定位信息
         List<ProjectContractArea> areaList = this.projectContractAreaClient.queryAllContractArea();
 
@@ -290,6 +297,9 @@ public class WeatherInfoServiceImpl extends ServiceImpl<WeatherInfoMapper, Weath
     //去除重复天气,并填充每个合同段缺失的天气
     @Scheduled(cron = "0 0 9 * * ?")
     public void scanMissWeather(){
+        if (!SystemUtils.isLinux()) {
+            return;
+        }
         //获取所有项目的合同段
         List<ContractInfoVO> contractInfos = baseMapper.getAllContract();
         for (ContractInfoVO contract : contractInfos) {

+ 6 - 0
blade-service/blade-manager/pom.xml

@@ -208,6 +208,12 @@
             <version>7.2.4</version>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+            <version>2.10.3</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
     <build>
         <plugins>

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

@@ -790,6 +790,23 @@ public class ExcelTabController extends BladeController {
                 this.excelTabService.gsColor(pkeyId, process.getPKeyId().toString(), wbsTreeContract.getProjectId(), doc);
             }
             doc.select("Col").remove();
+            org.springblade.manager.entity.TableInfo tableInfo = this.tableInfoService.getOne(Wrappers.<org.springblade.manager.entity.TableInfo>lambdaQuery().select(org.springblade.manager.entity.TableInfo::getId)
+                    .eq(org.springblade.manager.entity.TableInfo::getTabEnName, wbsTreeContract.getInitTableName()).eq(org.springblade.manager.entity.TableInfo::getIsDeleted, 0).last("limit 1"));
+            if (tableInfo != null ) {
+                List<WbsFormElement> list = this.wbsFormElementService.list(Wrappers.<WbsFormElement>lambdaQuery().select(WbsFormElement::getEKey, WbsFormElement::getELength)
+                        .eq(WbsFormElement::getFId, tableInfo.getId()).eq(WbsFormElement::getIsDeleted, 0));
+                if (list != null && !list.isEmpty()) {
+                    Map<String, Integer> keyNameMap = list.stream().filter(wbsFormElement -> wbsFormElement.getELength() != null).collect(Collectors.toMap(WbsFormElement::getEKey, WbsFormElement::getELength, (v1, v2) -> v1 > v2 ? v1 : v2));
+                    Elements keyNames = table.getElementsByAttribute("keyname");
+                    keyNames.forEach(element -> {
+                        String key = element.attr("keyname");
+                        Integer length = keyNameMap.get(key.split("__")[0]);
+                        if (length != null) {
+                            element.attr("maxlength", length.toString());
+                        }
+                    });
+                }
+            }
             fileInputStream.close();
             return R.data(table + "");
         } catch (Exception e) {

+ 442 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ServicePlanController.java

@@ -0,0 +1,442 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.controller;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import io.swagger.annotations.*;
+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.apache.commons.lang3.ObjectUtils;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.util.IOUtils;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+import org.springblade.business.entity.InformationQuery;
+import org.springblade.common.constant.CommonConstant;
+import org.springblade.common.utils.CommonUtil;
+import org.springblade.common.vo.DataVO;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.oss.model.BladeFile;
+import org.springblade.core.secure.BladeUser;
+import org.springblade.core.secure.utils.SecureUtil;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.*;
+import org.springblade.manager.bean.TableInfo;
+import org.springblade.manager.dto.ServicePlanDTO;
+import org.springblade.manager.dto.ServiceUserDto;
+import org.springblade.manager.entity.*;
+import org.springblade.manager.service.IServicePlanTaskService;
+import org.springblade.manager.utils.FileUtils;
+import org.springblade.system.cache.ParamCache;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.RequestParam;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.manager.vo.ServicePlanVO;
+import org.springblade.manager.wrapper.ServicePlanWrapper;
+import org.springblade.manager.service.IServicePlanService;
+import org.springblade.core.boot.ctrl.BladeController;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ *  控制器
+ *
+ * @author BladeX
+ * @since 2025-06-25
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/serviceplan")
+@Api(value = "服务计划", tags = "服务计划接口")
+public class ServicePlanController extends BladeController {
+
+	private final IServicePlanService servicePlanService;
+
+    private final IServicePlanTaskService servicePlanTaskService;
+
+    private final JdbcTemplate jdbcTemplate;
+
+	/**
+	 * 详情
+	 */
+	@GetMapping("/detail")
+	@ApiOperationSupport(order = 1)
+	@ApiOperation(value = "详情", notes = "传入servicePlan")
+    @ApiImplicitParams(value = {
+        @ApiImplicitParam(name = "id", value = "id", required = true)
+    })
+	public R<ServicePlanVO> detail(Long id) {
+        ServicePlanVO detail = servicePlanService.getdetail(id);
+		return R.data(detail);
+	}
+
+	/**
+	 * 分页
+	 */
+//	@GetMapping("/list")
+//	@ApiOperationSupport(order = 2)
+//	@ApiOperation(value = "分页", notes = "传入servicePlan")
+//	public R<IPage<ServicePlanVO>> list(ServicePlan servicePlan, Query query) {
+//		IPage<ServicePlan> pages = servicePlanService.page(Condition.getPage(query), Condition.getQueryWrapper(servicePlan));
+//		return R.data(ServicePlanWrapper.build().pageVO(pages));
+//	}
+
+
+	/**
+	 * 自定义分页
+	 */
+	@GetMapping("/page")
+	@ApiOperationSupport(order = 3)
+	@ApiOperation(value = "分页", notes = "传入servicePlan")
+    @ApiImplicitParams(value = {
+        @ApiImplicitParam(name = "projectId", value = "项目Id", required = true),
+        @ApiImplicitParam(name = "contractId", value = "合同Id", required = true)
+    })
+	public R<IPage<ServicePlanVO>> page(ServicePlanDTO servicePlan, Query query) {
+		IPage<ServicePlanVO> pages = servicePlanService.selectServicePlanPage(Condition.getPage(query), servicePlan);
+		return R.data(pages);
+	}
+    @GetMapping("/getSendUserAndWriteUser")
+    @ApiOperationSupport(order = 35)
+    @ApiOperation(value = "获取发送人及填写人", notes = "获取发送人及填写人")
+    @ApiImplicitParams(value = {
+        @ApiImplicitParam(name = "projectId", value = "项目Id", required = true),
+        @ApiImplicitParam(name = "contractId", value = "合同Id", required = true)
+    })
+    public R<Map<String,List<ServiceUserDto>>> getSendUserAndWriteUser(Long projectId, Long contractId){
+        return R.data(servicePlanService.getSendUserAndWriteUser(projectId,contractId));
+    }
+
+	/**
+	 * 新增
+	 */
+//	@PostMapping("/save")
+//	@ApiOperationSupport(order = 4)
+//	@ApiOperation(value = "新增", notes = "传入servicePlan")
+//    @ApiImplicitParams(value = {
+//        @ApiImplicitParam(name = "projectId", value = "项目Id", required = true),
+//        @ApiImplicitParam(name = "contractId", value = "合同Id", required = true),
+//        @ApiImplicitParam(name = "type", value = "1月度服务计划 2服务完成确认单", required = true),
+//        @ApiImplicitParam(name = "planStartTime", value = "计划开始时间", required = true),
+//        @ApiImplicitParam(name = "planEndTime", value = "计划结束时间", required = true),
+//    })
+//	public R save(@Valid @RequestBody ServicePlan servicePlan) {
+//        servicePlan.setWriteUser(SecureUtil.getUserId()+"");
+//        return R.status(servicePlanService.save(servicePlan));
+//	}
+
+	/**
+	 * 修改
+	 */
+//	@PostMapping("/update")
+//	@ApiOperationSupport(order = 5)
+//	@ApiOperation(value = "修改", notes = "传入servicePlan")
+//	public R update(@Valid @RequestBody ServicePlan servicePlan) {
+//        if(servicePlan.getWriteUser()!=null){
+//            if(!servicePlan.getWriteUser().contains(SecureUtil.getUserId()+"")){
+//                servicePlan.setWriteUser(servicePlan.getWriteUser()+","+SecureUtil.getUserId()+"");
+//            }
+//        }
+//		return R.status(servicePlanService.updateById(servicePlan));
+//	}
+    @GetMapping("/sendServicePlan")
+    @ApiOperationSupport(order = 34)
+    @ApiOperation(value = "发送计划", notes = "修改服务计划流程")
+    @ApiImplicitParams(value = {
+        @ApiImplicitParam(name = "id", value = "计划id", required = true),
+        @ApiImplicitParam(name = "sendUser", value = "发送人ID 逗号拼接", required = true),
+    })
+    public R sendServicePlan(Long id,String sendUser){
+        ServicePlan plan = servicePlanService.getById(id);
+        plan.setStatus(2);
+        String[] split = sendUser.split(",");
+        for (String sid : split) {
+            ServicePlanTask task = servicePlanTaskService.getOne(new LambdaQueryWrapper<>(ServicePlanTask.class).eq(ServicePlanTask::getServicePlanId, id).eq(ServicePlanTask::getUserId, sid));
+            if(task==null){
+                task=new ServicePlanTask();
+            }
+            task.setServicePlanId(id);
+            task.setUserId(Long.valueOf(sid));
+            task.setStatus(2);
+            servicePlanTaskService.saveOrUpdate(task);
+        }
+        plan.setSendUser(sendUser);
+        servicePlanService.updateById(plan);
+        return R.status(true);
+    }
+
+    @GetMapping("/cancelServicePlan")
+    @ApiOperationSupport(order = 34)
+    @ApiOperation( value = "计划回退")
+    public R cancelServicePlan(Long id){
+        ServicePlan plan = servicePlanService.getById(id);
+        plan.setStatus(3);
+        String sql="update m_service_plan_task set status=3 where service_plan_id="+id+" and is_deleted=0";
+        jdbcTemplate.update(sql);
+        String sql2="update m_service_plan_task set is_cancel=1 where service_plan_id="+id+" and is_deleted=0 and user_id="+SecureUtil.getUserId();
+        jdbcTemplate.update(sql2);
+         return R.status(servicePlanService.updateById(plan));
+    }
+
+    @GetMapping("/confirmServicePlan")
+    @ApiOperationSupport(order = 34)
+    @ApiOperation( value = "确认计划")
+    public R confirmServicePlan(Long id){
+        ServicePlan plan = servicePlanService.getById(id);
+        String sql="update m_service_plan_task set status=4 where service_plan_id="+id+" and user_id="+SecureUtil.getUserId();
+        jdbcTemplate.update(sql);
+        List<ServicePlanTask> tasks = servicePlanTaskService.getTaskByServicePlanId(id);
+        if(!tasks.isEmpty()){
+            List<ServicePlanTask> collect = tasks.stream().filter(o -> o.getStatus() != 4).collect(Collectors.toList());
+            if(collect.isEmpty()){
+                plan.setStatus(4);
+             return R.status(servicePlanService.updateById(plan));
+            }
+        }
+        return R.status(true);
+    }
+
+	/**
+	 * 新增或修改
+	 */
+//	@PostMapping("/submit")
+//	@ApiOperationSupport(order = 6)
+//	@ApiOperation(value = "新增或修改", notes = "传入servicePlan")
+//	public R submit(@Valid @RequestBody ServicePlan servicePlan) {
+//		return R.status(servicePlanService.saveOrUpdate(servicePlan));
+//	}
+
+
+	/**
+	 * 删除
+	 */
+	@PostMapping("/remove")
+	@ApiOperationSupport(order = 7)
+	@ApiOperation(value = "逻辑删除", notes = "传入ids")
+	public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
+		return R.status(servicePlanService.deleteLogic(Func.toLongList(ids)));
+	}
+
+    @GetMapping("/getServiceHtml")
+    @ApiOperationSupport(order = 8)
+    @ApiOperation(value = "获取服务计划html", notes = "projectId,contractId,type 1月度服务计划 2服务完成确认单")
+    @ApiImplicitParams(value = {
+        @ApiImplicitParam(name = "projectId", value = "项目ID", required = true),
+        @ApiImplicitParam(name = "contractId", value = "合同段ID", required = false),
+        @ApiImplicitParam(name = "pkeyId", value = "1月度服务计划 2服务完成确认单", required = true),
+    })
+    public R<String> getServiceHtml(Long projectId, Long contractId,Long pkeyId){
+        return R.data(servicePlanService.getServiceHtml(projectId,contractId,pkeyId));
+    }
+
+    @GetMapping("/get-html-buss-cols")
+    @ApiOperationSupport(order = 36)
+    @ApiOperation(value = "获取坐标位置", notes = "获取坐标位置")
+    @ApiImplicitParams(value = {
+        @ApiImplicitParam(name = "pkeyId", value = "1月度服务计划 2服务完成确认单", required = true)
+    })
+    public R getHtmlBussCols(Long pkeyId) {
+        String sql="select * from m_wbs_tree_contract where p_key_id="+pkeyId;
+        WbsTreeContract wbsTreeContract = jdbcTemplate.queryForObject( sql, new BeanPropertyRowMapper<>(WbsTreeContract.class));
+        if (wbsTreeContract == null) {
+            throw new ServiceException("暂无表单");
+        }
+        if (wbsTreeContract.getHtmlUrl() == null) {
+            throw new ServiceException("暂无表单");
+        }
+        String file_path = ParamCache.getValue(CommonConstant.SYS_LOCAL_URL);
+        String sys_file_net_url = ParamCache.getValue(CommonConstant.SYS_FILE_NET_URL);
+        try {
+            String fileUrl = wbsTreeContract.getHtmlUrl();
+            File file1 = ResourceUtil.getFile(fileUrl);
+            InputStream fileInputStream = null;
+            if (file1.exists()) {
+                fileInputStream = new FileInputStream(file1);
+            } else {
+                String path = sys_file_net_url + fileUrl.replaceAll("//", "/").replaceAll(file_path, "");
+                fileInputStream = CommonUtil.getOSSInputStream(path);
+            }
+
+
+            String htmlString = IoUtil.readToString(fileInputStream);
+            // 解析 style
+            Document doc = Jsoup.parse(htmlString);
+            Element table = doc.select("table").first();
+            Elements trs = table.select("tr");
+
+            List<List<String>> redata = new ArrayList<>();
+            for (int i = 0; i < trs.size(); i++) {
+                Element tr = trs.get(i);
+                Elements tds = tr.select("td");
+                List<String> tdList = new ArrayList<>();
+                for (int j = 0; j < tds.size(); j++) {
+                    Element element = tds.get(j);
+                    if (element.html().indexOf("el-tooltip") >= 0) {
+                        element = element.children().get(0);
+                    }
+                    if (element.children().size() >= 1) {
+                        String keyname = element.children().get(0).attr("keyname");
+                        if (StringUtils.isNotEmpty(keyname)) {
+                            tdList.add(keyname);
+                        }
+                    }
+                }
+                if (tdList != null && tdList.size() >= 1) {
+                    redata.add(tdList);
+                }
+            }
+
+            String[][] res = new String[redata.size()][]; // 存放转换结果的 二维数组
+            for (int i = 0; i < res.length; i++) { // 转换方法
+                res[i] = redata.get(i).toArray(new String[redata.get(i).size()]);
+            }
+            return R.data(res);
+        } catch (Exception e) {
+            return R.fail("暂无表单!");
+        }
+    }
+
+    @PostMapping("/saveServiceData")
+    @ApiOperationSupport(order = 9)
+    @ApiOperation(value = "保存服务计划数据", notes = "传入dataInfo")
+    @Transactional
+    public R<Long> saveServiceData(@Valid @RequestBody JSONObject dataInfo) throws Exception {
+        JSONArray dataArray = new JSONArray();
+        Long pkeyId = Long.valueOf(dataInfo.getString("pkeyId"));
+        Long projectId=Long.valueOf(dataInfo.getString("projectId"));
+        Long contractId=Long.valueOf(dataInfo.getString("contractId"));
+        Long groupId1 = null;
+        if(dataInfo.containsKey("group_id")){
+            if(StringUtils.isNotEmpty(dataInfo.getString("group_id"))){
+                groupId1=Long.valueOf(dataInfo.getString("group_id"));
+            }
+        }
+        dataArray.add(dataInfo);
+        List<TableInfo> tableInfoList=getServiceData(dataArray);
+        Long groupId = servicePlanService.saveServicePlan(pkeyId,projectId,contractId,dataInfo,groupId1);
+        servicePlanService.saveServiceData(tableInfoList,groupId,pkeyId);
+        servicePlanService.saveServicePlanPdf(projectId,pkeyId,Func.toLong(groupId));
+        return R.data(groupId);
+    }
+
+
+    private List<TableInfo> getServiceData(JSONArray dataInfo) {
+        if (dataInfo != null && !dataInfo.isEmpty()) {
+            List<TableInfo> result = new ArrayList<>();
+            for (int m = 0; m < dataInfo.size(); m++) {
+                TableInfo tableInfo = new TableInfo();
+                JSONObject dataInfo2 = dataInfo.getJSONObject(m);
+                tableInfo.setToBeUpdated(true);
+                tableInfo.setContractId(dataInfo2.getString("contractId"));
+                dataInfo2.fluentRemove("contractId")
+                    .fluentRemove("pkeyId")
+                    .fluentRemove("p_key_id")
+                    .fluentRemove("projectId")
+                    .fluentRemove("classify")
+                    .fluentRemove("pickerKey")
+                    .fluentRemove("id")
+                    .fluentRemove("isFirst")
+                    .fluentRemove("firstNodeId")
+                    .fluentRemove("isTheLog")
+                    .fluentRemove("theLogId")
+                    .fluentRemove("linkTabIds")
+                    .fluentRemove("recordTime")
+                    .fluentRemove("businessId")
+                    .fluentRemove("sourceUrl")
+                    .fluentRemove("pdfUrl")
+                    .fluentRemove("firstFileName")
+                    .fluentRemove("");
+                //计算数据
+                LinkedHashMap<String, List<String>> dataMap = dataInfo2.keySet().stream().filter(e -> e.contains("__")).collect(Collectors.groupingBy(e -> e.split("__")[0], LinkedHashMap<String, List<String>>::new, Collectors.toList()));
+                LinkedHashMap<String, String> dataMap2 = new LinkedHashMap<>();
+
+                //字段组合
+                for (String k : dataMap.keySet()) {
+                    if (dataMap.get(k).size() > 1 && !dataMap.get(k).contains("000Z")) {
+                        String[] ziduan = dataMap.get(k).toArray(new String[]{});
+                        String temp = "";
+                        for (int i = 0; i < ziduan.length - 1; i++) {
+                            for (int j = 0; j < ziduan.length - i - 1; j++) {
+                                int tr = Integer.parseInt((ziduan[j].split("__")[1]).split("_")[0]);
+                                int td = Integer.parseInt(ziduan[j].split("__")[1].split("_")[1]);
+                                int tr_1 = Integer.parseInt(ziduan[j + 1].split("__")[1].split("_")[0]);
+                                int td_1 = Integer.parseInt(ziduan[j + 1].split("__")[1].split("_")[1]);
+                                if (tr > tr_1 && td == td_1) { //纵向排序
+                                    temp = ziduan[j];
+                                    ziduan[j] = ziduan[j + 1];
+                                    ziduan[j + 1] = temp;
+                                }
+                            }
+                        }
+                        StringBuilder lastStr = new StringBuilder(dataInfo2.getString(ziduan[0]) + "_^_" + ziduan[0].split("__")[1]);
+                        for (int i = 1; i < ziduan.length; i++) {
+                            String keyData = dataInfo2.getString(ziduan[i]);
+                            if (keyData!=null && Func.isNotEmpty(keyData)) {
+                                lastStr.append("☆").append(dataInfo2.getString(ziduan[i])).append("_^_").append(ziduan[i].split("__")[1]);
+                            }
+
+                        }
+                        dataMap2.put(k, lastStr.toString());
+                    } else {
+                        String dataVal = dataInfo2.getString(dataMap.get(k).get(0));
+                        if(StringUtils.isNotEmpty(dataVal)){
+                            dataMap2.put(k, dataVal + "_^_" + dataMap.get(k).get(0).split("__")[1]);
+                        }
+                    }
+                }
+                tableInfo.setDataMap(dataMap2);
+                result.add(tableInfo);
+            }
+            return result;
+        }
+        return null;
+    }
+
+    @GetMapping("/getServiceBussData")
+    @ApiOperationSupport(order = 36)
+    @ApiOperation(value = "获取保存数据", notes = "获取保存数据")
+    @ApiImplicitParams(value = {
+        @ApiImplicitParam(name = "id", value = "id", required = true),
+        @ApiImplicitParam(name = "pkeyId", value = "1月度服务计划 2服务完成确认单", required = true)
+    })
+    public R<Map<String, Object>> getServiceBussData(Long id,Long pkeyId){
+        return R.data(servicePlanService.getServiceBussData(id,pkeyId));
+    }
+
+}

+ 129 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ServicePlanTaskController.java

@@ -0,0 +1,129 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import lombok.AllArgsConstructor;
+import javax.validation.Valid;
+
+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.Func;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.RequestParam;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.manager.entity.ServicePlanTask;
+import org.springblade.manager.vo.ServicePlanTaskVO;
+import org.springblade.manager.wrapper.ServicePlanTaskWrapper;
+import org.springblade.manager.service.IServicePlanTaskService;
+import org.springblade.core.boot.ctrl.BladeController;
+
+/**
+ *  控制器
+ *
+ * @author BladeX
+ * @since 2025-06-27
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/serviceplantask")
+@Api(value = "", tags = "接口")
+public class ServicePlanTaskController extends BladeController {
+
+	private final IServicePlanTaskService servicePlanTaskService;
+
+	/**
+	 * 详情
+	 */
+	@GetMapping("/detail")
+	@ApiOperationSupport(order = 1)
+	@ApiOperation(value = "详情", notes = "传入servicePlanTask")
+	public R<ServicePlanTaskVO> detail(ServicePlanTask servicePlanTask) {
+		ServicePlanTask detail = servicePlanTaskService.getOne(Condition.getQueryWrapper(servicePlanTask));
+		return R.data(ServicePlanTaskWrapper.build().entityVO(detail));
+	}
+
+	/**
+	 * 分页 
+	 */
+	@GetMapping("/list")
+	@ApiOperationSupport(order = 2)
+	@ApiOperation(value = "分页", notes = "传入servicePlanTask")
+	public R<IPage<ServicePlanTaskVO>> list(ServicePlanTask servicePlanTask, Query query) {
+		IPage<ServicePlanTask> pages = servicePlanTaskService.page(Condition.getPage(query), Condition.getQueryWrapper(servicePlanTask));
+		return R.data(ServicePlanTaskWrapper.build().pageVO(pages));
+	}
+
+
+	/**
+	 * 自定义分页 
+	 */
+	@GetMapping("/page")
+	@ApiOperationSupport(order = 3)
+	@ApiOperation(value = "分页", notes = "传入servicePlanTask")
+	public R<IPage<ServicePlanTaskVO>> page(ServicePlanTaskVO servicePlanTask, Query query) {
+		IPage<ServicePlanTaskVO> pages = servicePlanTaskService.selectServicePlanTaskPage(Condition.getPage(query), servicePlanTask);
+		return R.data(pages);
+	}
+
+	/**
+	 * 新增 
+	 */
+	@PostMapping("/save")
+	@ApiOperationSupport(order = 4)
+	@ApiOperation(value = "新增", notes = "传入servicePlanTask")
+	public R save(@Valid @RequestBody ServicePlanTask servicePlanTask) {
+		return R.status(servicePlanTaskService.save(servicePlanTask));
+	}
+
+	/**
+	 * 修改 
+	 */
+	@PostMapping("/update")
+	@ApiOperationSupport(order = 5)
+	@ApiOperation(value = "修改", notes = "传入servicePlanTask")
+	public R update(@Valid @RequestBody ServicePlanTask servicePlanTask) {
+		return R.status(servicePlanTaskService.updateById(servicePlanTask));
+	}
+
+	/**
+	 * 新增或修改 
+	 */
+	@PostMapping("/submit")
+	@ApiOperationSupport(order = 6)
+	@ApiOperation(value = "新增或修改", notes = "传入servicePlanTask")
+	public R submit(@Valid @RequestBody ServicePlanTask servicePlanTask) {
+		return R.status(servicePlanTaskService.saveOrUpdate(servicePlanTask));
+	}
+
+	
+	/**
+	 * 删除 
+	 */
+	@PostMapping("/remove")
+	@ApiOperationSupport(order = 7)
+	@ApiOperation(value = "逻辑删除", notes = "传入ids")
+	public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
+		return R.status(servicePlanTaskService.deleteLogic(Func.toLongList(ids)));
+	}
+
+	
+}

+ 45 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ServicePlanMapper.java

@@ -0,0 +1,45 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.mapper;
+
+import org.apache.ibatis.annotations.Param;
+import org.springblade.manager.dto.ServicePlanDTO;
+import org.springblade.manager.entity.ServicePlan;
+import org.springblade.manager.vo.ServicePlanVO;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import java.util.List;
+
+/**
+ *  Mapper 接口
+ *
+ * @author BladeX
+ * @since 2025-06-25
+ */
+public interface ServicePlanMapper extends BaseMapper<ServicePlan> {
+
+	/**
+	 * 自定义分页
+	 *
+	 * @param page
+	 * @param servicePlan
+	 * @return
+	 */
+	List<ServicePlan> selectServicePlanPage(IPage page, @Param("servicePlan")ServicePlanDTO servicePlan);
+
+    List<String> getUserID(@Param("projectId") Long projectId, @Param("contractId") Long contractId, @Param("type") int type);
+}

+ 53 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ServicePlanMapper.xml

@@ -0,0 +1,53 @@
+<?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.manager.mapper.ServicePlanMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="servicePlanResultMap" type="org.springblade.manager.entity.ServicePlan">
+        <result column="id" property="id"/>
+        <result column="status" property="status"/>
+        <result column="create_user" property="createUser"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_user" property="updateUser"/>
+        <result column="update_time" property="updateTime"/>
+        <result column="file_in_type" property="fileInType"/>
+        <result column="plan_start_time" property="planStartTime"/>
+        <result column="plan_end_time" property="planEndTime"/>
+        <result column="send_user" property="sendUser"/>
+    </resultMap>
+
+
+    <select id="selectServicePlanPage" resultMap="servicePlanResultMap">
+        select * from m_service_plan where is_deleted = 0 and project_id = #{servicePlan.projectId} and contract_id = #{servicePlan.contractId}
+                                       and (FIND_IN_SET(#{servicePlan.userId},write_user) or FIND_IN_SET(#{servicePlan.userId},send_user))
+        <if test="servicePlan.fileInType != null">
+            and file_in_type = #{servicePlan.fileInType}
+        </if>
+        <if test="servicePlan.status!=null">
+            and status = #{servicePlan.status}
+        </if>
+        <if test="servicePlan.writeUser!=null">
+            and FIND_IN_SET(write_user,#{servicePlan.writeUser})
+        </if>
+        <if test="servicePlan.sendUser!=null">
+            and FIND_IN_SET(send_user,#{servicePlan.sendUser})
+        </if>
+        <if test="servicePlan.planStartTime1 != null and servicePlan.planEndTime1 != null">
+            and (
+            (#{servicePlan.planStartTime1} between plan_start_time and plan_end_time)
+            or (#{servicePlan.planEndTime1} between plan_start_time and plan_end_time)
+            or (plan_start_time between #{servicePlan.planStartTime1} and #{servicePlan.planEndTime1})
+            or (plan_end_time between #{servicePlan.planStartTime1} and #{servicePlan.planEndTime1})
+            )
+        </if>
+    </select>
+    <select id="getUserID" resultType="java.lang.String">
+        <if test="type == 1">
+            select send_user from m_service_plan where is_deleted = 0 and project_id = #{projectId} and contract_id = #{contractId}
+        </if>
+        <if test="type == 2">
+            select write_user from m_service_plan where is_deleted = 0 and project_id = #{projectId} and contract_id = #{contractId}
+        </if>
+    </select>
+
+</mapper>

+ 44 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ServicePlanTaskMapper.java

@@ -0,0 +1,44 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.mapper;
+
+import io.lettuce.core.dynamic.annotation.Param;
+import org.springblade.manager.entity.ServicePlanTask;
+import org.springblade.manager.vo.ServicePlanTaskVO;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import java.util.List;
+
+/**
+ *  Mapper 接口
+ *
+ * @author BladeX
+ * @since 2025-06-27
+ */
+public interface ServicePlanTaskMapper extends BaseMapper<ServicePlanTask> {
+
+	/**
+	 * 自定义分页
+	 *
+	 * @param page
+	 * @param servicePlanTask
+	 * @return
+	 */
+	List<ServicePlanTaskVO> selectServicePlanTaskPage(IPage page, ServicePlanTaskVO servicePlanTask);
+
+    List<ServicePlanTask> getTaskByServicePlanId(@Param("id") Long id);
+}

+ 22 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ServicePlanTaskMapper.xml

@@ -0,0 +1,22 @@
+<?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.manager.mapper.ServicePlanTaskMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="servicePlanTaskResultMap" type="org.springblade.manager.entity.ServicePlanTask">
+        <result column="id" property="id"/>
+        <result column="status" property="status"/>
+        <result column="is_deleted" property="isDeleted"/>
+        <result column="service_plan_id" property="servicePlanId"/>
+        <result column="user_id" property="userId"/>
+    </resultMap>
+
+
+    <select id="selectServicePlanTaskPage" resultMap="servicePlanTaskResultMap">
+        select * from m_service_plan_task where is_deleted = 0
+    </select>
+    <select id="getTaskByServicePlanId" resultType="org.springblade.manager.entity.ServicePlanTask">
+         select * from m_service_plan_task where service_plan_id=#{id} and is_deleted=0;
+    </select>
+
+</mapper>

+ 63 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/IServicePlanService.java

@@ -0,0 +1,63 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.service;
+
+import com.alibaba.fastjson.JSONObject;
+import org.springblade.manager.bean.TableInfo;
+import org.springblade.manager.dto.ServicePlanDTO;
+import org.springblade.manager.dto.ServiceUserDto;
+import org.springblade.manager.entity.ServicePlan;
+import org.springblade.manager.vo.ServicePlanVO;
+import org.springblade.core.mp.base.BaseService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *  服务类
+ *
+ * @author BladeX
+ * @since 2025-06-25
+ */
+public interface IServicePlanService extends BaseService<ServicePlan> {
+
+	/**
+	 * 自定义分页
+	 *
+	 * @param page
+	 * @param servicePlan
+	 * @return
+	 */
+	IPage<ServicePlanVO> selectServicePlanPage(IPage<ServicePlanVO> page, ServicePlanDTO servicePlan);
+
+    String getServiceHtml(Long projectId,Long contractId,Long pkeyId);
+
+    boolean saveServiceData(List<TableInfo> tableInfoList,Long groupId,Long pkeyId);
+
+    void saveServicePlanPdf(Long projectId,Long pkeyId, Long id) throws Exception;
+
+    Map<String, Object>getServiceBussData(Long id, Long pkeyId);
+
+    Map<String,List<ServiceUserDto>> getSendUserAndWriteUser(Long projectId, Long contractId);
+
+    Long saveServicePlan(Long pkeyId,Long projectId,Long contractId,@Valid JSONObject dataInfo,Long groupId1);
+
+    ServicePlanVO getdetail(Long id);
+}

+ 44 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/IServicePlanTaskService.java

@@ -0,0 +1,44 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.service;
+
+import org.springblade.manager.entity.ServicePlanTask;
+import org.springblade.manager.vo.ServicePlanTaskVO;
+import org.springblade.core.mp.base.BaseService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+
+import java.util.List;
+
+/**
+ *  服务类
+ *
+ * @author BladeX
+ * @since 2025-06-27
+ */
+public interface IServicePlanTaskService extends BaseService<ServicePlanTask> {
+
+	/**
+	 * 自定义分页
+	 *
+	 * @param page
+	 * @param servicePlanTask
+	 * @return
+	 */
+	IPage<ServicePlanTaskVO> selectServicePlanTaskPage(IPage<ServicePlanTaskVO> page, ServicePlanTaskVO servicePlanTask);
+
+    List<ServicePlanTask> getTaskByServicePlanId(Long id);
+}

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

@@ -2060,7 +2060,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
         }
         for (int i = 0; i < xle; i++) {
             CellRangeAddress mergedCell = sheet.getMergedRegion(i);
-            int xx = mergedCell.getNumberOfCells();
+            int xx = mergedCell.getLastColumn();
             if (xx >= all) {
                 all = xx;
             }
@@ -2068,7 +2068,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
 
         for (int i = 0; i < xle; i++) {
             CellRangeAddress mergedCell = sheet.getMergedRegion(i);
-            int xx = mergedCell.getNumberOfCells() + 2;
+            int xx = mergedCell.getLastColumn() + 2;
             if (xx >= all) {
                 int fisRow = mergedCell.getFirstRow();
                 int firsrCol = mergedCell.getFirstColumn();
@@ -2086,7 +2086,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
                 newStyle.cloneStyleFrom(cell.getCellStyle());
 
                 short fontHeightInPoints = redFont.getFontHeightInPoints();
-                if (fontHeightInPoints >= isWater && StringUtils.isEmpty(cell.getStringCellValue()) && fisRow <= 8) {
+                if (fontHeightInPoints >= isWater && StringUtils.isBlank(cell.getStringCellValue()) && fisRow <= 8) {
 
                     String title = projectInfo.getProjectName();
                     if (title.length() >= 30) {
@@ -3210,7 +3210,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
         int mergedCellCnt = sheet.getNumMergedRegions();
         for (int i = 0; i < mergedCellCnt - 1; i++) {
             CellRangeAddress mergedCell = sheet.getMergedRegion(i);
-            int xx = mergedCell.getNumberOfCells();
+            int xx = mergedCell.getLastColumn();
             if (xx <= all) {
                 int fisRow = mergedCell.getFirstRow();
                 int firstCol = mergedCell.getFirstColumn();
@@ -4309,7 +4309,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
                 int i = 0;
                 i < mergedCellCnt - 1; i++) {
             CellRangeAddress mergedCell = sheet.getMergedRegion(i);
-            int xx = mergedCell.getNumberOfCells();
+            int xx = mergedCell.getLastColumn();
             if (xx <= all) {
                 int fisRow = mergedCell.getFirstRow();
                 int firstCol = mergedCell.getFirstColumn();

+ 914 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ServicePlanServiceImpl.java

@@ -0,0 +1,914 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.mixsmart.utils.ListUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.util.IOUtils;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+import org.springblade.business.dto.TrialSelfInspectionRecordDTO;
+import org.springblade.business.entity.EntrustInfo;
+import org.springblade.business.entity.TrialMaterialMobilization;
+import org.springblade.business.entity.TrialSampleInfo;
+import org.springblade.business.entity.TrialSelfInspectionRecord;
+import org.springblade.common.constant.CommonConstant;
+import org.springblade.common.utils.CommonUtil;
+import org.springblade.common.utils.SnowFlakeUtil;
+import org.springblade.common.vo.DataVO;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.oss.model.BladeFile;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.secure.utils.SecureUtil;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.*;
+import org.springblade.manager.bean.TableInfo;
+import org.springblade.manager.dto.ServicePlanDTO;
+import org.springblade.manager.dto.ServiceUserDto;
+import org.springblade.manager.entity.*;
+import org.springblade.manager.utils.FileUtils;
+import org.springblade.manager.vo.ServicePlanVO;
+import org.springblade.manager.mapper.ServicePlanMapper;
+import org.springblade.manager.service.IServicePlanService;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.resource.feign.NewIOSSClient;
+import org.springblade.system.cache.ParamCache;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.amqp.RabbitTemplateConfigurer;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.stereotype.Service;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.DefaultTransactionDefinition;
+
+import javax.annotation.Resource;
+import java.io.*;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ *  服务实现类
+ *
+ * @author BladeX
+ * @since 2025-06-25
+ */
+@Service
+public class ServicePlanServiceImpl extends BaseServiceImpl<ServicePlanMapper, ServicePlan> implements IServicePlanService {
+    @Resource
+    private  JdbcTemplate jdbcTemplate;
+    @Autowired
+    private DataSourceTransactionManager transactionManager1;
+    @Autowired
+    private  NewIOSSClient newIOSSClient;
+
+	@Override
+	public IPage<ServicePlanVO> selectServicePlanPage(IPage<ServicePlanVO> page, ServicePlanDTO servicePlan) {
+        servicePlan.setUserId(SecureUtil.getUserId()+"");
+        List<ServicePlan> vos = baseMapper.selectServicePlanPage(page, servicePlan);
+        List<ServicePlanVO>voList = new ArrayList<>();
+        for (ServicePlan sp : vos) {
+            ServicePlanVO vo = new ServicePlanVO();
+            BeanUtil.copyProperties(sp, vo);
+           vo.setStatusValue(vo.getStatus()==1?"计划中":vo.getStatus()==2?"确认中-甲方":vo.getStatus()==3?"反馈中-系统":"已计划");
+           vo.setIsEdit(checkIsEdit(vo));
+           if(vo.getPlanStartTime()!=null){
+               vo.setStartTime(vo.getPlanStartTime().format(DateTimeFormatter.ofPattern("yyyy年M月d日")));
+           }
+           if(vo.getPlanEndTime()!=null){
+               vo.setEndTime(vo.getPlanEndTime().format(DateTimeFormatter.ofPattern("yyyy年M月d日")));
+           }
+           if(StringUtils.isNotEmpty(vo.getStartTime())&&StringUtils.isNotEmpty(vo.getEndTime())){
+               vo.setPlanTime(vo.getStartTime()+"至"+vo.getEndTime());
+           }
+            vo.setWriteUserName(slectUserName(vo.getWriteUser()));
+            vo.setSendUserName(slectUserName(vo.getSendUser()));
+            voList.add(vo);
+        }
+        return page.setRecords(voList);
+	}
+
+
+
+    private String slectUserName(String sendUser) {
+        if(StringUtils.isNotEmpty(sendUser)){
+            String sql="select real_name from blade_user where id in("+sendUser+")";
+            List<String> list = jdbcTemplate.queryForList(sql, String.class);
+            if(!list.isEmpty()){
+                return list.stream().collect(Collectors.joining(","));
+            }
+        }
+        return "";
+    }
+
+    private Boolean checkIsEdit(ServicePlanVO vo) {
+        if(vo.getStatus()==1){
+            return true;
+        }else if(vo.getStatus()==2){
+            if(StringUtils.isNotEmpty(vo.getSendUser())){
+                return vo.getSendUser().contains(SecureUtil.getUserId()+"");
+            }
+        }else if(vo.getStatus()==2){
+            if(StringUtils.isNotEmpty(vo.getSendUser())){
+                return vo.getWriteUser().contains(SecureUtil.getUserId()+"");
+            }
+        }else {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String getServiceHtml(Long projectId, Long contractId,Long pkeyId) {
+        String sql="select * from m_wbs_tree_contract where p_key_id="+pkeyId;
+        WbsTreeContract wbsTreeContract = jdbcTemplate.queryForObject( sql, new BeanPropertyRowMapper<>(WbsTreeContract.class));
+        if (wbsTreeContract == null) {
+            throw new ServiceException("暂无表单");
+        }
+        if (wbsTreeContract.getHtmlUrl() == null) {
+            throw new ServiceException("暂无表单");
+        }
+        try {
+            String fileUrl = wbsTreeContract.getHtmlUrl();
+            InputStream fileInputStream = FileUtils.getInputStreamByUrl(fileUrl);
+            String htmlString = IoUtil.readToString(fileInputStream);
+            htmlString = htmlString.replaceAll("placeholder", "placeholderxx");
+            htmlString = htmlString.replaceAll("title", "titlexx");
+
+            // 远程搜索配置
+            Document doc = Jsoup.parse(htmlString);
+            Element table = doc.select("table").first();
+            // 标题解决
+            String sql1="select * from m_project_info where id="+projectId;
+            ProjectInfo projectInfo = jdbcTemplate.queryForObject(sql1,new BeanPropertyRowMapper<>(ProjectInfo.class));
+            //判断是否是水利水电表,水利水电项目名14,表名12 。   其他表都是18
+            Boolean isWater = false;
+            // 添加标题显示
+            Elements trs = table.select("tr");
+            for (int i = 1; i < 6; i++) {
+                Element tr = trs.get(i);
+                Elements tds = tr.select("td");
+                for (int j = 0; j < tds.size(); j++) {
+                    Element data = tds.get(j);
+                    String style = data.attr("style");
+                    if (style.indexOf("font-size") >= 0) {
+                        int fontsize = Integer.parseInt(style.substring(style.indexOf("font-size:") + 10, style.indexOf(".0pt")));
+                        Element element = null;
+                        if (isWater) {
+                            if (StringUtils.isNotEmpty(data.text()) && fontsize >= 12) {
+                                element = trs.get(i - 1).select("td").get(0);
+
+                            }
+                        } else {
+                            if (StringUtils.isNotEmpty(data.text()) && fontsize >= 14) {
+                                element = trs.get(i - 1).select("td").get(0);
+                            }
+                        }
+                        if (element != null && Func.isNotEmpty(element)) {
+                            //
+                            Elements allElements = element.children();
+                            if (allElements.size() >= 1) {
+                                String defText = allElements.get(0).attr("deftext");
+                                if (defText != null && Func.isNotEmpty(defText)) {
+                                    element.text(defText);
+                                } else {
+                                    element.text(projectInfo.getProjectName());
+                                }
+                            } else {
+                                element.text(projectInfo.getProjectName());
+                            }
+                        }
+                    }
+                }
+            }
+            fileInputStream.close();
+            return table + "";
+        } catch (Exception e) {
+            throw new ServiceException("暂无表单");
+        }
+    }
+
+    @Override
+    public boolean saveServiceData(List<TableInfo> tableInfoList,Long groupId,Long  pkeyId) {
+        if (ListUtils.isNotEmpty(tableInfoList)) {
+            for (TableInfo tableInfo : tableInfoList) {
+                String sql="select * from m_wbs_tree_contract where p_key_id="+pkeyId;
+                WbsTreeContract wbsTreeContract = jdbcTemplate.queryForObject( sql, new BeanPropertyRowMapper<>(WbsTreeContract.class));
+                if (wbsTreeContract == null || StringUtils.isEmpty(wbsTreeContract.getInitTableName())) {
+                    continue;
+                }
+                //删除SQL
+                String delSql = "delete from " + wbsTreeContract.getInitTableName() + " where p_key_id='" + pkeyId + "' and group_id = '" + groupId + "'";
+
+                //新增SQ
+                String sqlInfo = "";
+                LinkedHashMap<String, String> dataMap2 = tableInfo.getDataMap();
+                sqlInfo = "INSERT INTO " + wbsTreeContract.getInitTableName() + " ( ";
+                String keyStr = "id,p_key_id,group_id,";
+                String valStr = SnowFlakeUtil.getId() + "," + pkeyId + ","+groupId+",";
+                if(dataMap2.containsKey("p_key_id")){
+                    dataMap2.remove("p_key_id");
+                }
+                for (String keys : dataMap2.keySet()) {
+                    keyStr += keys + ",";
+                    valStr += "'" + dataMap2.get(keys) + "',";
+                }
+                keyStr = keyStr.substring(0, keyStr.lastIndexOf(","));
+                valStr = valStr.substring(0, valStr.lastIndexOf(","));
+                sqlInfo = sqlInfo + keyStr + ") VALUES (" + valStr + ")";
+                TransactionStatus transactionStatus = this.beginTransaction(transactionManager1);
+                try {
+                    //删除
+                    jdbcTemplate.execute(delSql);
+                    //新增
+                    jdbcTemplate.execute(sqlInfo);
+                    //提交事务
+                    transactionManager1.commit(transactionStatus);
+                } catch (Exception e) {
+                    //回滚
+                    transactionManager1.rollback(transactionStatus);
+                    throw new RuntimeException("500"+e.getCause().getMessage()+"  字段过长,新增失败");
+                }
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void saveServicePlanPdf(Long projectId,Long pkeyId, Long id) throws Exception {
+        String file_path = FileUtils.getSysLocalFileUrl();//ParamCache.getValue(CommonConstant.SYS_LOCAL_URL);
+        String sys_file_net_url = ParamCache.getValue(CommonConstant.SYS_FILE_NET_URL);
+        String sql="select * from m_wbs_tree_contract where p_key_id="+pkeyId;
+        WbsTreeContract wbsTreeContract = jdbcTemplate.queryForObject( sql, new BeanPropertyRowMapper<>(WbsTreeContract.class));
+        if (wbsTreeContract == null) {
+            throw new ServiceException("该数据下无此节点!");
+        }
+        if (wbsTreeContract.getHtmlUrl() == null) {
+            throw new ServiceException("请关联清表!");
+        }
+        String pdfPath = file_path + "/pdf//" + pkeyId + ".pdf";
+        String excelPath = file_path + "/pdf//" + pkeyId + ".xlsx";
+        File tabPdf = ResourceUtil.getFile(pdfPath);
+        if (tabPdf.exists()) {
+            tabPdf.delete();
+        }
+        String sql1="select * from m_excel_tab where id="+wbsTreeContract.getExcelId();
+        //获取清表信息
+        ExcelTab excelTab =jdbcTemplate.queryForObject( sql1, new BeanPropertyRowMapper<>(ExcelTab.class));
+
+        if (excelTab == null) {
+            throw new ServiceException("操作失败!");
+        }
+
+        //获取数据信息info
+        List<Map<String, Object>> bussDataInfoTrial = this.getBussDataInfoTrial(pkeyId,id);
+        Map<String, Object> DataInfo = new HashMap<>();
+        if (bussDataInfoTrial.size() > 0) {
+            DataInfo.putAll(bussDataInfoTrial.stream().findAny().orElse(null));
+        }
+
+        //获取清表excel文件
+        org.apache.poi.ss.usermodel.Workbook workbook = WorkbookFactory.create(Objects.requireNonNull(CommonUtil.getOSSInputStreamTow(excelTab.getFileUrl())));
+        Sheet sheet = workbook.getSheetAt(0);
+        sheet.setForceFormulaRecalculation(true);
+
+       String sql2="select * from m_project_info where id="+projectId;
+       ProjectInfo projectInfo = jdbcTemplate.queryForObject(sql2, new BeanPropertyRowMapper<>(ProjectInfo.class));
+        int all = sheet.getRow(0).getLastCellNum();
+        int mergedCellCnt = sheet.getNumMergedRegions();
+        for (
+            int i = 0;
+            i < mergedCellCnt - 1; i++) {
+            CellRangeAddress mergedCell = sheet.getMergedRegion(i);
+            int xx = mergedCell.getNumberOfCells();
+            if (xx <= all) {
+                int fisRow = mergedCell.getFirstRow();
+                int firstCol = mergedCell.getFirstColumn();
+                Cell cell = sheet.getRow(fisRow).getCell(firstCol);
+                short fontIndex = cell.getCellStyle().getFontIndex();
+                Font oldFontAt = workbook.getFontAt(fontIndex);
+                Font redFont = workbook.createFont();
+                redFont.setFontHeightInPoints(oldFontAt.getFontHeightInPoints());//设置字体大小
+                redFont.setFontName(oldFontAt.getFontName());//设置字体
+                CellStyle newStyle = workbook.createCellStyle();//创建单元格样式
+                newStyle.cloneStyleFrom(cell.getCellStyle());
+                short fontHeightInPoints = redFont.getFontHeightInPoints();
+                if (fontHeightInPoints >= 14 && StringUtils.isEmpty(cell.getStringCellValue()) && fisRow <= 8) {
+                    String title = projectInfo.getProjectName();
+                    if (title.length() >= 30) {
+                        sheet.getRow(fisRow).setHeight((short) 900);
+                        newStyle.setWrapText(true);
+                    }
+                    redFont.setBold(true);
+                    newStyle.setFont(redFont);
+                    cell.setCellStyle(newStyle);
+                    cell.setCellValue(title);
+                    break;
+                }
+            }
+        }
+
+        //数据不为空,构造数据
+        String fileUrl = wbsTreeContract.getHtmlUrl();
+        InputStream fileInputStream = FileUtils.getInputStreamByUrl(fileUrl);
+
+        String htmlString = IoUtil.readToString(fileInputStream);
+        htmlString = htmlString.replaceAll("placeholder", "placeholderxx");
+        htmlString = htmlString.replaceAll("title", "titlexx");
+
+        Document doc = Jsoup.parse(htmlString);
+        Element table = doc.select("table").first();
+        Elements trs = table.select("tr");
+
+        if (ObjectUtil.isNotEmpty(DataInfo)) {
+            for (String val : Objects.requireNonNull(DataInfo).keySet()) {
+                if (val.contains("__")) {
+                    String[] DataVal = val.split("__");
+                    String[] xy = DataVal[1].split("_");
+                    if (Integer.parseInt(xy[0]) < trs.size()) {
+                        Element ytzData = trs.get(Integer.parseInt(xy[0]));
+                        if (ytzData != null) {
+                            Elements tdsx = ytzData.select("td");
+                            if (Integer.parseInt(xy[1]) < tdsx.size()) {
+                                Element data = ytzData.select("td").get(Integer.parseInt(xy[1]));
+                                if (data != null) {
+                                    if (data.html().contains("x1") && data.html().contains("y1")) {
+                                        int x1 = 0;
+                                        int x2 = 0;
+                                        int y1 = 0;
+                                        int y2 = 0;
+                                        if (data.html().contains("el-tooltip")) {
+                                            x1 = Integer.parseInt(data.children().get(0).children().get(0).attr("x1"));
+                                            x2 = Integer.parseInt(data.children().get(0).children().get(0).attr("x2"));
+                                            y1 = Integer.parseInt(data.children().get(0).children().get(0).attr("y1"));
+                                        } else {
+                                            x1 = Integer.parseInt(data.children().get(0).attr("x1"));
+                                            y1 = Integer.parseInt(data.children().get(0).attr("y1"));
+                                        }
+                                        if (x1 == 0) {
+                                            x1 = 1;
+                                        }
+                                        String myData = DataInfo.get(val) + "";
+                                        if (myData.contains("T") && myData.contains("-") && myData.contains(":")) {
+                                            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+                                            sdf.setTimeZone(TimeZone.getTimeZone("GTM+8"));
+                                            SimpleDateFormat formatStr = new SimpleDateFormat("yyyy年MM月dd日");
+                                            if (myData.contains(",") && myData.contains("]")) {
+
+                                                myData = myData.replace("[", "").replace("]", "").replaceAll("'", "");
+                                                String[] dataVal = myData.split(",");
+
+                                                Date Start_dataStr = sdf.parse(dataVal[0]);
+                                                Date end_dataStr = sdf.parse(dataVal[1]);
+                                                String StartDate = formatStr.format(Start_dataStr);
+                                                String endDate = formatStr.format(end_dataStr);
+                                                if (StartDate.equals(endDate)) {
+                                                    myData = StartDate;
+                                                } else {
+                                                    myData = StartDate + "-" + endDate;
+                                                }
+                                            } else {
+                                                String[] dataStr = myData.split("T")[0].split("-");
+                                                myData = StringUtil.format("{}年{}月{}日", dataStr[0], dataStr[1], Integer.parseInt(dataStr[2]));
+                                            }
+                                        }
+
+                                        if (myData.contains("lang.String")) {
+                                            Object obj = DataInfo.get(val);
+                                            if (obj instanceof String[]) {
+                                                String[] dataDate = (String[]) obj;
+                                                myData = dataDate[0].trim() + "至" + dataDate[1].trim();
+                                                if (dataDate[0].trim().equals(dataDate[1].trim())) {
+                                                    myData = dataDate[0];
+                                                }
+                                            }
+                                        }
+
+                                        if (myData.contains("http") && myData.contains("aliyuncs")) {
+                                            InputStream imageIn = CommonUtil.getOSSInputStream(myData);
+                                            byte[] byteNew = new byte[0];
+                                            if (imageIn != null) {
+                                                byteNew = IOUtils.toByteArray(imageIn);
+                                            }
+
+                                            byte[] bytes = CommonUtil.compressImage(byteNew);
+
+                                            CreationHelper helper = workbook.getCreationHelper();
+                                            ClientAnchor anchor = helper.createClientAnchor();
+                                            anchor.setCol1(x1); // param1是列号
+                                            anchor.setCol2(x2);
+                                            anchor.setRow1(y1); // param2是行号
+                                            anchor.setRow2(y2); // param2是行号
+
+                                            Drawing<?> drawing = sheet.createDrawingPatriarch();
+                                            anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE);
+                                            // 插入图片
+                                            Picture picture = drawing.createPicture(anchor, workbook.addPicture(bytes, Workbook.PICTURE_TYPE_PNG));
+                                            picture.resize(1, 1);
+                                            FileUtils.imageOrientation(sheet, anchor, new DataVO(x1 - 1, y1 - 1));
+
+                                        } else if (myData.equals("1") && data.html().contains("hc-form-checkbox-group")) {
+                                            Row row = sheet.getRow(y1 - 1);
+                                            if (row != null) {
+                                                Cell cell = row.getCell(x1 - 1);
+                                                if (cell != null) {
+                                                    String exceVal = cell.getStringCellValue().replaceAll(" ", "");
+                                                    short fontIndex = cell.getCellStyle().getFontIndex();
+                                                    Font fontAt = workbook.getFontAt(fontIndex);
+                                                    fontAt.setFontName("EUDC");
+                                                    cell.setCellValue(exceVal.replace("□", "\u2611"));
+                                                } else {
+                                                    ObjectUtils.isNotEmpty(cell);
+                                                }
+                                            }
+                                        } else {
+                                            Row row = sheet.getRow(y1 - 1);
+                                            if (row != null) {
+                                                Cell cell = row.getCell(x1 - 1);
+                                                if (cell != null) {
+                                                    if(myData.contains("[")&&myData.contains("]")){
+                                                        myData=myData.replace("[", "").replace("]", "");
+                                                        if (myData.contains(",")){
+                                                            myData=  myData.replace(", ","至");
+                                                        }
+                                                        if (myData.contains(",")){
+                                                            myData=  myData.replace(", ","至");
+                                                        }
+                                                    }
+                                                    cell.setCellValue(myData);
+                                                } else {
+                                                    ObjectUtils.isNotEmpty(cell);
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        //输出流
+        FileOutputStream outputStream = new FileOutputStream(excelPath);
+        workbook.write(outputStream);
+        FileUtils.setExcelScaleToPdf(excelPath, pdfPath);
+        BladeFile bladeFile = newIOSSClient.uploadFile(id + ".pdf", pdfPath);
+        if (bladeFile != null) {
+            String sql3="update m_service_plan set pdf_url=' "+bladeFile.getLink()+" ' where id="+id;
+            jdbcTemplate.update(sql3);
+        }
+
+    }
+
+    @Override
+    public Map<String, Object> getServiceBussData(Long id, Long pkeyId) {
+        Map<String, Object> reData = new HashMap<>();
+        String sqll="select * from m_wbs_tree_contract where p_key_id="+pkeyId;
+        WbsTreeContract wbsTreeContract = jdbcTemplate.queryForObject(sqll, new BeanPropertyRowMapper<>(WbsTreeContract.class));
+        if (wbsTreeContract == null) {
+            throw new ServiceException("该数据下无此节点!");
+        }
+        if (wbsTreeContract.getHtmlUrl() == null) {
+            throw new ServiceException("请关联清表!");
+        }
+        //表单是否存储在
+        String tabName = wbsTreeContract.getInitTableName();
+        String isExitSql = "select * from information_schema.TABLES where TABLE_NAME='" + tabName + "'";
+        List<Map<String, Object>> tabList = jdbcTemplate.queryForList(isExitSql);
+        if (tabList.size() <= 0) {
+            throw new ServiceException("请关联清表!");
+        }
+        //实体数据
+        String querySql = "select * from " + wbsTreeContract.getInitTableName() + " where p_key_id=" + pkeyId + " and group_id = " + id;
+        List<Map<String, Object>> dataIn = jdbcTemplate.queryForList(querySql);
+        //匹配关联
+        try {
+            if (dataIn.size() >= 1) {
+                Map<String, Object> mysqlData = dataIn.get(0);
+                for (String key : mysqlData.keySet()) {
+                    String tabVal = mysqlData.get(key) + "";
+                    // 时间段处理
+                    if (StringUtils.isNotEmpty(tabVal) && !tabVal.equals("null")) {
+                        if (tabVal.contains("T") && tabVal.contains(".000Z]")) {
+                            String[] tabData = tabVal.split("_\\^_");
+
+                            if (reData.containsKey("pickerKey")) {
+                                String pickerKey = reData.get("pickerKey") + "," + key + "__" + tabData[1];
+                                reData.put("pickerKey", pickerKey);
+                            } else {
+                                reData.put("pickerKey", key + "__" + tabData[1]);
+                            }
+
+                            String sql = tabData[0];
+                            sql = sql.replaceAll("\\[", "['");
+                            sql = sql.replaceAll("]", "']");
+                            sql = sql.replaceAll("000Z,", "000Z',");
+                            sql = sql.replaceAll(", 20", ", '20");
+                            //   sql = sql.replaceAll("'", "");
+                            if (StringUtils.isNotEmpty(tabData[0])) {
+                                reData.put(key + "__" + tabData[1], sql);
+                            }
+                        } else if (tabVal.indexOf("T") >= 0 && tabVal.indexOf(".000Z") >= 0) {//时间
+                            // 时间和字符串合作
+                            if (tabVal.indexOf("☆") >= 0) {
+                                String[] mysql = tabVal.split("☆");
+                                for (String data : mysql) {
+                                    String[] tabData = data.split("_\\^_");
+                                    if (StringUtils.isNotEmpty(tabData[0])) {
+                                        reData.put(key + "__" + tabData[1], tabData[0]);
+                                    }
+                                }
+                            } else {
+                                String[] tabData = tabVal.split("_\\^_");
+                                if (StringUtils.isNotEmpty(tabData[0])) {
+                                    reData.put(key + "__" + tabData[1], tabData[0]);
+                                }
+                            }
+                        } else if (tabVal.indexOf("☆") >= 0) {
+                            String[] mysql = tabVal.split("☆");
+                            for (String data : mysql) {
+                                String[] tabData = data.split("_\\^_");
+                                if (tabVal.contains("[")) {
+                                    String[] strings = StringUtils.strip(tabData[0], "[]").split(",");
+                                    reData.put(key + "__" + tabData[1], strings);
+                                } else {
+                                    reData.put(key + "__" + tabData[1], tabData[0]);
+                                }
+                            }
+                        } else if (tabVal.indexOf("_^_") >= 0) {
+                            String[] tabData = tabVal.split("_\\^_");
+                            if (StringUtils.isNotEmpty(tabData[0])) {
+                                if (tabVal.contains("[")) {
+                                    String[] strings = StringUtils.strip(tabData[0], "[]").split(",");
+                                    reData.put(key + "__" + tabData[1], strings);
+                                } else {
+                                    reData.put(key + "__" + tabData[1], tabData[0]);
+                                }
+                            }
+                        } else {
+                            reData.put(key, tabVal);
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return reData;
+    }
+
+    @Override
+    public Map<String,List<ServiceUserDto>> getSendUserAndWriteUser(Long projectId, Long contractId) {
+        HashMap<String, List<ServiceUserDto>> map = new HashMap<>();
+        List<String> sendList=baseMapper.getUserID(projectId, contractId,1);
+        List<String> writeList=baseMapper.getUserID(projectId, contractId,2);
+        if (!sendList.isEmpty()) {
+            List<String> sendResult = sendList.stream()
+                .filter(Objects::nonNull)
+                .filter(str -> !str.isEmpty())
+                .flatMap(str -> Arrays.stream(str.split(",")))
+                .distinct()
+                .sorted()
+                .collect(Collectors.toList());
+            map.put("sendUser", getUserNamesByIds(sendResult));
+        }
+
+        if (!writeList.isEmpty()) {
+            List<String> writedResult = writeList.stream()
+                .filter(Objects::nonNull)
+                .filter(str -> !str.isEmpty())
+                .flatMap(str -> Arrays.stream(str.split(",")))
+                .distinct()
+                .sorted()
+                .collect(Collectors.toList());
+            map.put("writeUser", getUserNamesByIds(writedResult));
+        }
+        return map;
+    }
+
+    @Override
+    public Long saveServicePlan(Long pkeyId,Long projectId,Long contractId,JSONObject dataInfo,Long groupId1) {
+        ServicePlan plan = new ServicePlan();
+        if(dataInfo.containsKey("key_15__3_4")){
+            Object o = dataInfo.get("key_15__3_4");
+            String startDate = null;
+            String endDate = null;
+            if (o instanceof List<?>) {
+                List<?> list = (List<?>) o;
+                if (list.size() >= 2 && list.get(0) instanceof String && list.get(1) instanceof String) {
+                    startDate = formatDate((String) list.get(0));
+                    endDate = formatDate((String) list.get(1));
+                }
+            } else if (o != null && o.getClass().isArray()) {
+                Object[] array = (Object[]) o;
+                if (array.length >= 2 && array[0] instanceof String && array[1] instanceof String) {
+                    startDate = formatDate((String) array[0]);
+                    endDate = formatDate((String) array[1]);
+                }
+            }
+            if (startDate != null) {
+                plan.setPlanStartTime(LocalDate.parse(startDate));
+            }
+            if (endDate != null) {
+                plan.setPlanEndTime(LocalDate.parse(endDate));
+            }
+        }
+        if(dataInfo.containsKey("key_42__3_4")){
+            Object o = dataInfo.get("key_42__3_4");
+            String startDate = null;
+            String endDate = null;
+            if (o instanceof List<?>) {
+                List<?> list = (List<?>) o;
+                if (list.size() >= 2 && list.get(0) instanceof String && list.get(1) instanceof String) {
+                    startDate = formatDate((String) list.get(0));
+                    endDate = formatDate((String) list.get(1));
+                }
+            } else if (o != null && o.getClass().isArray()) {
+                Object[] array = (Object[]) o;
+                if (array.length >= 2 && array[0] instanceof String && array[1] instanceof String) {
+                    startDate = formatDate((String) array[0]);
+                    endDate = formatDate((String) array[1]);
+                }
+            }
+            if (startDate != null) {
+                plan.setPlanStartTime(LocalDate.parse(startDate));
+            }
+            if (endDate != null) {
+                plan.setPlanEndTime(LocalDate.parse(endDate));
+            }
+        }
+        if(groupId1 != null){
+            plan.setId(groupId1);
+            ServicePlan plan1 = this.getById(plan.getId());
+            if(plan1.getWriteUser()!=null){
+                if(plan1.getStatus()!=2&&!plan1.getWriteUser().contains(SecureUtil.getUserId()+"")){
+                    plan.setWriteUser(plan1.getWriteUser()+","+SecureUtil.getUserId());
+                }
+            }
+            if(plan1.getStatus()!=null){
+                plan.setStatus(plan1.getStatus());
+            }
+        }else {
+            plan.setProjectId(projectId);
+            plan.setContractId(contractId);
+            plan.setStatus(1);
+            plan.setWriteUser(SecureUtil.getUserId()+"");
+            if(pkeyId==1937773223861026820L){
+                plan.setFileInType(1);
+            }else {
+                plan.setFileInType(2);
+            }
+        }
+        this.saveOrUpdate(plan);
+        return plan.getId();
+    }
+
+    @Override
+    public ServicePlanVO getdetail(Long id) {
+        ServicePlan plan = this.getById(id);
+        ServicePlanVO vo = new ServicePlanVO();
+        BeanUtil.copyProperties(plan, vo);
+        vo.setStatusValue(vo.getStatus()==1?"计划中":vo.getStatus()==2?"协同中-甲方":vo.getStatus()==3?"协同中-系统":"已计划");
+        vo.setIsEdit(checkIsEdit(vo));
+        if(vo.getPlanStartTime()!=null){
+            vo.setStartTime(vo.getPlanStartTime().format(DateTimeFormatter.ofPattern("yyyy年M月d日")));
+        }
+        if(vo.getPlanEndTime()!=null){
+            vo.setEndTime(vo.getPlanEndTime().format(DateTimeFormatter.ofPattern("yyyy年M月d日")));
+        }
+        if(StringUtils.isNotEmpty(vo.getStartTime())&&StringUtils.isNotEmpty(vo.getEndTime())){
+            vo.setPlanTime(vo.getStartTime()+"至"+vo.getEndTime());
+        }
+        vo.setWriteUserName(slectUserName(vo.getWriteUser()));
+        vo.setSendUserName(slectUserName(vo.getSendUser()));
+        return vo;
+    }
+
+    private static String formatDate(String dateStr) {
+        if (dateStr == null || dateStr.trim().isEmpty()) return null;
+        try {
+            // 替换掉“年”、“月”、“日”字符,然后解析为LocalDate
+            return dateStr.replaceAll("年", "-")
+                .replaceAll("月", "-")
+                .replaceAll("日", "")
+                .trim();
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public List<ServiceUserDto> getUserNamesByIds(List<String> sendResult) {
+        if (sendResult == null || sendResult.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        // 构建 SQL 查询语句
+        String inSql = sendResult.stream()
+            .map(id -> "?")
+            .collect(Collectors.joining(", "));
+
+        String sql = "SELECT id, real_name FROM blade_user WHERE id IN (" + inSql + ")";
+
+        try {
+            List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, sendResult.toArray());
+
+            return rows.stream()
+                .map(row -> new ServiceUserDto(
+                    row.get("id").toString(),
+                    row.get("real_name").toString()))
+                .collect(Collectors.toList());
+        } catch (DataAccessException e) {
+            // 日志记录异常信息
+            log.error("查询用户信息失败", e);
+            return Collections.emptyList();
+        }
+    }
+
+    private List<Map<String, Object>> getBussDataInfoTrial(Long pkeyId ,Long id) {
+        String file_path = ParamCache.getValue(CommonConstant.SYS_LOCAL_URL);
+        String sys_file_net_url = ParamCache.getValue(CommonConstant.SYS_FILE_NET_URL);
+        List<Map<String, Object>> list = new ArrayList<>();
+        Map<String, Object> reData = new HashMap<>();
+        String sqll="select * from m_wbs_tree_contract where p_key_id="+pkeyId;
+        WbsTreeContract wbsTreeContract = jdbcTemplate.queryForObject(sqll, new BeanPropertyRowMapper<>(WbsTreeContract.class));
+        if (wbsTreeContract == null) {
+            return list;
+        }
+        if (wbsTreeContract.getHtmlUrl() == null) {
+            return list;
+        }
+        //表单是否存储在
+        String tabName = wbsTreeContract.getInitTableName();
+        String isExitSql = "select * from information_schema.TABLES where TABLE_NAME='" + tabName + "'";
+        List<Map<String, Object>> tabList = jdbcTemplate.queryForList(isExitSql);
+        if (tabList.size() <= 0) {
+            return list;
+        }
+
+        //实体数据
+        String querySql = "select * from " + wbsTreeContract.getInitTableName() + " where p_key_id=" + pkeyId + " and group_id = " + id;
+        List<Map<String, Object>> dataIn = jdbcTemplate.queryForList(querySql);
+        String keyNames="";
+        //匹配关联
+        try {
+            String fileUrl = wbsTreeContract.getHtmlUrl();
+            File file1 = ResourceUtil.getFile(fileUrl);
+            InputStream fileInputStream;
+            if (file1.exists()) {
+                fileInputStream = new FileInputStream(file1);
+            } else {
+                String path = sys_file_net_url + fileUrl.replaceAll("//", "/").replaceAll(file_path, "");
+                fileInputStream = CommonUtil.getOSSInputStream(path);
+            }
+            String htmlString = IoUtil.readToString(fileInputStream);
+            htmlString = htmlString.replaceAll("placeholder", "placeholderxx");
+            htmlString = htmlString.replaceAll("title", "titlexx");
+            Document doc = Jsoup.parse(htmlString);
+            keyNames= getKeyNameList(doc);
+                if (dataIn.size() >= 1) {
+                    Map<String, Object> mysqlData = dataIn.get(0);
+                    for (String key : mysqlData.keySet()) {
+                        String tabVal = mysqlData.get(key) + "";
+                        // 时间段处理
+                        if (StringUtils.isNotEmpty(tabVal) && !tabVal.equals("null")) {
+                            if (tabVal.contains("T") && tabVal.contains(".000Z]")) {
+                                String[] tabData = tabVal.split("_\\^_");
+
+                                if (reData.containsKey("pickerKey")) {
+                                    String pickerKey = reData.get("pickerKey") + "," + key + "__" + tabData[1];
+                                    reData.put("pickerKey", pickerKey);
+                                } else {
+                                    reData.put("pickerKey", key + "__" + tabData[1]);
+                                }
+
+                                String sql = tabData[0];
+                                sql = sql.replaceAll("\\[", "['");
+                                sql = sql.replaceAll("]", "']");
+                                sql = sql.replaceAll("000Z,", "000Z',");
+                                sql = sql.replaceAll(", 20", ", '20");
+                                //   sql = sql.replaceAll("'", "");
+                                if (StringUtils.isNotEmpty(tabData[0])) {
+                                    reData.put(key + "__" + tabData[1], sql);
+                                }
+                            } else if (tabVal.indexOf("T") >= 0 && tabVal.indexOf(".000Z") >= 0) {//时间
+                                // 时间和字符串合作
+                                if (tabVal.indexOf("☆") >= 0) {
+                                    String[] mysql = tabVal.split("☆");
+                                    for (String data : mysql) {
+                                        String[] tabData = data.split("_\\^_");
+                                        if (StringUtils.isNotEmpty(tabData[0])) {
+                                            reData.put(key + "__" + tabData[1], tabData[0]);
+                                        }
+                                    }
+                                } else {
+                                    String[] tabData = tabVal.split("_\\^_");
+                                    if (StringUtils.isNotEmpty(tabData[0])) {
+                                        reData.put(key + "__" + tabData[1], tabData[0]);
+                                    }
+                                }
+                            } else if (tabVal.indexOf("☆") >= 0) {
+                                String[] mysql = tabVal.split("☆");
+                                for (String data : mysql) {
+                                    String[] tabData = data.split("_\\^_");
+                                    if (StringUtils.isNotEmpty(tabData[0])) {
+                                        reData.put(key + "__" + tabData[1], tabData[0]);
+                                    }
+                                }
+                            } else if (tabVal.indexOf("_^_") >= 0) {
+                                String[] tabData = tabVal.split("_\\^_");
+                                if (StringUtils.isNotEmpty(tabData[0])) {
+                                    if (tabVal.contains("[") && tabVal.contains("年")) {
+                                        String[] strings = StringUtils.strip(tabData[0], "[]").split(",");
+                                        reData.put(key + "__" + tabData[1], strings);
+                                    }else if (tabVal.contains("[") && tabVal.contains("]") ) {
+                                        String[] strings = StringUtils.strip(tabData[0], "[]").split(",");
+                                        reData.put(key + "__" + tabData[1], strings);
+                                    } else {
+                                        reData.put(key + "__" + tabData[1], tabData[0]);
+                                    }
+                                }
+                            } else {
+                                reData.put(key, tabVal);
+                            }
+                        }
+                    }
+                }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        // 移除Id 和 p_key_id
+        reData.remove("id");
+        reData.remove("p_key_id");
+        reData.remove("classify");
+        reData.remove("contractId");
+        reData.remove("pkeyId");
+        reData.remove("projectId");
+        if(reData.size()>0){
+            //处理key重复导致pdf数据错位
+            for (Iterator<Map.Entry<String, Object>> iterator = reData.entrySet().iterator(); iterator.hasNext(); ) {
+                Map.Entry<String, Object> entry = iterator.next();
+                if (!keyNames.equals("") && keyNames.indexOf(entry.getKey()) <0) {
+                    iterator.remove();
+                }
+            }
+        }
+        list.add(reData);
+        return list;
+    }
+    public String getKeyNameList(Document doc){
+        StringBuilder allKeyName=new StringBuilder();
+        Elements select = doc.getElementsByAttributeValueContaining("keyname","key_");
+        if(!select.isEmpty()){
+            for (Element element : select) {
+                String keyName = element.attr("keyname");
+                allKeyName.append(keyName+",");
+            }
+        }
+        String allKeyNames = allKeyName.toString();
+        if(allKeyNames.endsWith(",")){
+            allKeyNames = allKeyNames.substring(0, allKeyNames.length() - 1);
+        }
+        return allKeyNames;
+    }
+
+    public TransactionStatus beginTransaction(DataSourceTransactionManager transactionManager) {
+        DefaultTransactionDefinition def = new DefaultTransactionDefinition();//事务定义类
+        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
+        return transactionManager.getTransaction(def);
+    }
+
+
+
+}

+ 50 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ServicePlanTaskServiceImpl.java

@@ -0,0 +1,50 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.service.impl;
+
+import org.springblade.manager.entity.ServicePlanTask;
+import org.springblade.manager.vo.ServicePlanTaskVO;
+import org.springblade.manager.mapper.ServicePlanTaskMapper;
+import org.springblade.manager.service.IServicePlanTaskService;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springframework.stereotype.Service;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ *  服务实现类
+ *
+ * @author BladeX
+ * @since 2025-06-27
+ */
+@Service
+public class ServicePlanTaskServiceImpl extends BaseServiceImpl<ServicePlanTaskMapper, ServicePlanTask> implements IServicePlanTaskService {
+
+	@Override
+	public IPage<ServicePlanTaskVO> selectServicePlanTaskPage(IPage<ServicePlanTaskVO> page, ServicePlanTaskVO servicePlanTask) {
+		return page.setRecords(baseMapper.selectServicePlanTaskPage(page, servicePlanTask));
+	}
+
+	@Override
+	public List<ServicePlanTask> getTaskByServicePlanId(Long id) {
+
+		return baseMapper.getTaskByServicePlanId(id);
+	}
+
+}

+ 10 - 6
blade-service/blade-manager/src/main/java/org/springblade/manager/utils/PdfAddimgUtil.java

@@ -136,9 +136,11 @@ public class PdfAddimgUtil {
         // 添加图片
 
         // 设置图片的新宽度和高度
-        float newWidth = 75f; // 新的宽度
-        float newHeight = image.getScaledHeight() * (newWidth / image.getScaledWidth()); // 根据宽度计算高度
-        image.scaleAbsolute(newWidth, newHeight); // 设置图片的新尺寸
+        if (!type.equals("6")) {
+            float newWidth = 75f; // 新的宽度
+            float newHeight = image.getScaledHeight() * (newWidth / image.getScaledWidth()); // 根据宽度计算高度
+            image.scaleAbsolute(newWidth, newHeight); // 设置图片的新尺寸
+        }
         //调整图片尺寸
         image.setAbsolutePosition(x, y);
         under.addImage(image);
@@ -216,9 +218,11 @@ public class PdfAddimgUtil {
         // 添加图片
 
         // 设置图片的新宽度和高度
-        int newWidth = cmToPx(wide);
-        int newHeight = cmToPx(hign);
-        image.scaleAbsolute(newWidth, newHeight); // 设置图片的新尺寸
+        if (!type.equals("6")) {
+            int newWidth = cmToPx(wide);
+            int newHeight = cmToPx(hign);
+            image.scaleAbsolute(newWidth, newHeight); // 设置图片的新尺寸
+        }
         //调整图片尺寸
        // image.scaleAbsolute(newWidth, newHeight);
         image.setAbsolutePosition(x, y);

+ 49 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/wrapper/ServicePlanTaskWrapper.java

@@ -0,0 +1,49 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.wrapper;
+
+import org.springblade.core.mp.support.BaseEntityWrapper;
+import org.springblade.core.tool.utils.BeanUtil;
+import org.springblade.manager.entity.ServicePlanTask;
+import org.springblade.manager.vo.ServicePlanTaskVO;
+import java.util.Objects;
+
+/**
+ * 包装类,返回视图层所需的字段
+ *
+ * @author BladeX
+ * @since 2025-06-27
+ */
+public class ServicePlanTaskWrapper extends BaseEntityWrapper<ServicePlanTask, ServicePlanTaskVO>  {
+
+	public static ServicePlanTaskWrapper build() {
+		return new ServicePlanTaskWrapper();
+ 	}
+
+	@Override
+	public ServicePlanTaskVO entityVO(ServicePlanTask servicePlanTask) {
+		ServicePlanTaskVO servicePlanTaskVO = Objects.requireNonNull(BeanUtil.copy(servicePlanTask, ServicePlanTaskVO.class));
+
+		//User createUser = UserCache.getUser(servicePlanTask.getCreateUser());
+		//User updateUser = UserCache.getUser(servicePlanTask.getUpdateUser());
+		//servicePlanTaskVO.setCreateUserName(createUser.getName());
+		//servicePlanTaskVO.setUpdateUserName(updateUser.getName());
+
+		return servicePlanTaskVO;
+	}
+
+}

+ 49 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/wrapper/ServicePlanWrapper.java

@@ -0,0 +1,49 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.wrapper;
+
+import org.springblade.core.mp.support.BaseEntityWrapper;
+import org.springblade.core.tool.utils.BeanUtil;
+import org.springblade.manager.entity.ServicePlan;
+import org.springblade.manager.vo.ServicePlanVO;
+import java.util.Objects;
+
+/**
+ * 包装类,返回视图层所需的字段
+ *
+ * @author BladeX
+ * @since 2025-06-25
+ */
+public class ServicePlanWrapper extends BaseEntityWrapper<ServicePlan, ServicePlanVO>  {
+
+	public static ServicePlanWrapper build() {
+		return new ServicePlanWrapper();
+ 	}
+
+	@Override
+	public ServicePlanVO entityVO(ServicePlan servicePlan) {
+		ServicePlanVO servicePlanVO = Objects.requireNonNull(BeanUtil.copy(servicePlan, ServicePlanVO.class));
+
+		//User createUser = UserCache.getUser(servicePlan.getCreateUser());
+		//User updateUser = UserCache.getUser(servicePlan.getUpdateUser());
+		//servicePlanVO.setCreateUserName(createUser.getName());
+		//servicePlanVO.setUpdateUserName(updateUser.getName());
+
+		return servicePlanVO;
+	}
+
+}