Sfoglia il codice sorgente

Merge remote-tracking branch 'origin/test-merge' into test-merge

LHB 1 settimana fa
parent
commit
e0eae8b6a6
15 ha cambiato i file con 548 aggiunte e 40 eliminazioni
  1. 6 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/entity/ArchiveFile.java
  2. 3 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/TrialSummaryReflectionSaveDTO.java
  3. 2 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/ArchiveTreeContractVO2.java
  4. 76 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ScanFileController.java
  5. 4 1
      blade-service/blade-business/src/main/java/org/springblade/business/mapper/ArchiveFileMapper.xml
  6. 15 0
      blade-service/blade-manager/pom.xml
  7. 2 4
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java
  8. 6 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/TextdictInfoController.java
  9. 35 24
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/TrialSummaryClassificationConfigurationController.java
  10. 360 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/WbsTreeContractController.java
  11. 1 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/impl/SubTable.java
  12. 5 3
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ArchiveTreeContractMapper.xml
  13. 14 3
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/FormulaServiceImpl.java
  14. 14 2
      blade-service/blade-meter/src/main/java/org/springblade/meter/controller/TaskController.java
  15. 5 1
      pom.xml

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

@@ -341,6 +341,12 @@ public class ArchiveFile extends BaseEntity {
     private String remark;
     @ApiModelProperty("是否锁定 1已锁定")
     private Integer isLock;
+    /**
+     *  检测文件完整性,包括:日期、责任者、PDF是否有污渍、PDF分辨率是否大于等于300DPI
+     *
+     */
+    @ApiModelProperty("-1:检测无误,0:未检测,1:无日期,2:无责任者,3:dpi小于300,4:pdf有遮挡或者污渍")
+    private Integer checkStatus;
 
     public void fromExternal(ArchiveFileVo vo) {
         if (vo == null) {

+ 3 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/dto/TrialSummaryReflectionSaveDTO.java

@@ -18,6 +18,9 @@ public class TrialSummaryReflectionSaveDTO implements Serializable {
 
     @Data
     public static class ReflectionBean implements Serializable {
+        @ApiModelProperty(value = "试验表的p_key_id")
+        private Long id;
+
         @ApiModelProperty(value = "试验表的p_key_id")
         private Long trialTabId;
 

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

@@ -237,6 +237,8 @@ public class ArchiveTreeContractVO2 implements INodeEx<ArchiveTreeContractVO2> {
      */
     private Integer classify;
 
+    private Integer checkStatus;
+
     public String toString() {
         return "TreeNode(parentId=" + this.getParentId()
                 + ",ancestors" + this.getAncestors()

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

@@ -1,17 +1,23 @@
 package org.springblade.archive.controller;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.itextpdf.text.pdf.PdfDictionary;
+import com.itextpdf.text.pdf.PdfReader;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiImplicitParams;
 import io.swagger.annotations.ApiOperation;
 import lombok.AllArgsConstructor;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
 import org.springblade.archive.dto.ScanFileMoveDTO;
 import org.springblade.archive.entity.ScanFile;
 import org.springblade.archive.entity.ScanFolder;
 import org.springblade.archive.service.ScanFileService;
 import org.springblade.archive.service.ScanFolderService;
 import org.springblade.archive.vo.ScanFolderVO;
+import org.springblade.business.entity.ArchiveFile;
+import org.springblade.common.utils.CommonUtil;
 import org.springblade.core.mp.support.Query;
 import org.springblade.core.tool.api.R;
 import org.springblade.manager.entity.ContractInfo;
@@ -23,6 +29,9 @@ import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.net.URLEncoder;
+import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ThreadPoolExecutor;
@@ -32,6 +41,8 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 @RestController
 @AllArgsConstructor
@@ -174,5 +185,70 @@ public class ScanFileController {
     }
 
 
+    /**
+     * 每天3点执行一次
+     *  检测文件完整性,包括:日期、责任者、PDF是否有污渍、PDF分辨率是否大于等于300DPI
+     */
+    @Scheduled(cron = "0 0 3 * * ?")
+    public void checkFileInfo(){
+        String sql = "select id,node_id,duty_user,file_time,file_url,pdf_file_url from u_archive_file where is_deleted = 0 and check_status >= 0 and node_id is not null and project_is is not null limit ? offset ?";
+        int size = 10000;
+        int offset = 0;
+        Pattern datePattern = Pattern.compile("[0-9]{4}.?[0-9]{2}.?[0-9]{2}");
+        while (true) {
+            List<ArchiveFile> query = jdbcTemplate.query(String.format(sql, size, offset), new BeanPropertyRowMapper<>(ArchiveFile.class));
+            if (query.isEmpty()) {
+                break;
+            }
+            offset += size;
+            for (ArchiveFile file : query) {
+                String status = "";
+                if (file.getFileTime() == null || !datePattern.matcher(file.getFileTime()).find()) {
+                    status += "1";
+                }
+                if (file.getDutyUser() ==  null || file.getDutyUser().trim().isEmpty() || file.getDutyUser().trim().equals("null")) {
+                    status += "2";
+                }
+                String url = "";
+                if (file.getPdfFileUrl() != null && file.getPdfFileUrl().contains("http")) {
+                    url = file.getFileUrl();
+                } else if (file.getFileUrl() != null && file.getFileUrl().contains("http")) {
+                    url = file.getPdfFileUrl();
+                }
+                if (url == null || url.isEmpty()) {
+                    status += "3";
+                    file.setCheckStatus(Integer.parseInt(status));
+                    continue;
+                }
+                int lastIndexOf = url.lastIndexOf("/");
+                String prefix = url.substring(0, lastIndexOf + 1);
+                String suffix = url.substring(lastIndexOf + 1);
+                try {
+                    url = prefix + URLEncoder.encode(suffix, "UTF-8");
+                    PDDocument document = PDDocument.load(CommonUtil.getOSSInputStream(url));
+                    PDPage page = document.getPage(0);
+                    // 获取pdf 的dpi信息
+                    int dpi = (int) (page.getCropBox().getWidth() / page.getTrimBox().getWidth() * 72);
+                    if (dpi < 300) {
+                        status += "3";
+                    }
+                    // todo 检测pdf是否有遮挡或者污渍
+                    if (status.isEmpty()) {
+                        file.setCheckStatus(-1);
+                    } else {
+                        file.setCheckStatus(Integer.parseInt( status));
+                    }
+                } catch (Exception e) {
+                    status += "3";
+                    file.setCheckStatus(Integer.parseInt( status));
+                }
+            }
+            Date date = new Date();
+            List<Object[]> params = query.stream().map(entry -> new Object[]{entry.getCheckStatus(), date, entry.getId()}).collect(Collectors.toList());
+            jdbcTemplate.batchUpdate("update archive_file set check_status = ?, update_time = ? where id = ?", params);
+        }
+
+
+    }
 
 }

+ 4 - 1
blade-service/blade-business/src/main/java/org/springblade/business/mapper/ArchiveFileMapper.xml

@@ -248,7 +248,10 @@
             end,
             t.tree_sort,
         </if>
-        u.archive_sort,u.sort,u.sort_num,u.create_time
+        <if test="vo.archiveId != null and vo.archiveId != ''">
+            u.archive_sort,
+        </if>
+        u.sort,u.sort_num,u.create_time
         limit #{current}, #{size}
     </select>
 

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

@@ -214,6 +214,21 @@
             <version>2.10.3</version>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>xalan</groupId>
+            <artifactId>xalan</artifactId>
+            <version>2.7.2</version>
+        </dependency>
+        <dependency>
+            <groupId>xerces</groupId>
+            <artifactId>xercesImpl</artifactId>
+            <version>2.12.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jsoup</groupId>
+            <artifactId>jsoup</artifactId>
+            <version>1.16.1</version>
+        </dependency>
     </dependencies>
     <build>
         <plugins>

+ 2 - 4
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java

@@ -687,7 +687,7 @@ public class ExcelTabController extends BladeController {
                 }
             }
         }
-        saveOldHtmlConfig(aPrivate, htmlString);
+        saveOldHtmlConfig(aPrivate, doc);
         File writefile = new File(thmlUrl);
         FileUtil.writeToFile(writefile, doc.html(), Boolean.parseBoolean("UTF-8"));
 
@@ -733,7 +733,7 @@ public class ExcelTabController extends BladeController {
         return R.success("关联成功");
     }
 
-    public void saveOldHtmlConfig(WbsTreePrivate wbsTreePrivate, String htmlString){
+    public void saveOldHtmlConfig(WbsTreePrivate wbsTreePrivate, Document doc){
         // 获取原默认值配置
         List<TextdictInfoVO> defaultConfigs = new ArrayList<>();
         {
@@ -746,8 +746,6 @@ public class ExcelTabController extends BladeController {
                 });
             }
         }
-        // 读取新的html内容
-        Document doc = Jsoup.parse(htmlString);
         // 将原配置信息写入到新的html中,先通过colKey匹配,再通过colName匹配。如果匹配不上,则保存到数据库。后续手动配置、修改
         defaultConfigs.removeIf(config -> {
             String colKey = config.getColKey();

+ 6 - 1
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/TextdictInfoController.java

@@ -709,7 +709,12 @@ public class TextdictInfoController extends BladeController {
             textdictInfo.setProjectId(wbsTreePrivate.getProjectId());
 
             String[] trtd = key.split("__")[1].split("_");
-            Element element = trs.get(Integer.parseInt(trtd[0])).select("td").get(Integer.parseInt(trtd[1]));
+            Element element;
+            try {
+                element = trs.get(Integer.parseInt(trtd[0])).select("td").get(Integer.parseInt(trtd[1]));
+            } catch (Exception e) {
+                throw new ServiceException(String.format("【%s】元素位置与表单不匹配,请重新编辑后保存", textdictInfo.getColName()));
+            }
             String colKey = element.children().get(0).attr("keyname");
             textdictInfo.setColKey(colKey);
 

+ 35 - 24
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/TrialSummaryClassificationConfigurationController.java

@@ -42,6 +42,7 @@ import java.io.*;
 import java.util.*;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 
 @RestController
@@ -256,37 +257,47 @@ public class TrialSummaryClassificationConfigurationController extends BladeCont
                 if (classificationConfiguration != null && ObjectUtil.isNotEmpty(classificationConfiguration.getExcelId())) {
                     List<TrialSummaryReflectionSaveDTO.ReflectionBean> reflectionBeanList = dto.getReflectionBeanList();
                     Set<TrialSummaryReflectionSaveDTO.ReflectionBean> collect = new HashSet<>(reflectionBeanList);
+                    Set<TrialSummaryReflectionSaveDTO.ReflectionBean> updateList = collect.stream().filter(bean -> ObjectUtil.isNotEmpty(bean.getId())).collect(Collectors.toSet());
+                    Set<TrialSummaryReflectionSaveDTO.ReflectionBean> insertList = collect.stream().filter(bean -> ObjectUtil.isEmpty(bean.getId())).collect(Collectors.toSet());
+
                     List<String> keyNameList = collect.stream()
                             .map(bean -> "'" + bean.getHtmlKeyName() + "'")
                             .collect(Collectors.toList());
                     if (keyNameList.size() <= 0) {
                         throw new ServiceException("入参异常,未获取到htmlKeyName");
                     }
-
-//                    String sqlDel = "DELETE FROM m_trial_summary_excel_tab_reflection WHERE excel_id = ? AND class_id = ? AND html_key_name IN (" +
-//                            StringUtils.join(keyNameList, ",") + ")";
-//                    List<Object> paramsDel = new ArrayList<>();
-//                    paramsDel.add(classificationConfiguration.getExcelId());
-//                    paramsDel.add(classificationConfiguration.getId());
-//                    jdbcTemplate.update(sqlDel, paramsDel.toArray());
-
-                    String sqlInsert = "INSERT INTO m_trial_summary_excel_tab_reflection(id,class_id,excel_id,trial_tab_id,element_id,html_key_name,trial_tab_name,element_key) VALUES (?,?,?,?,?,?,?,?)";
-                    List<Object[]> batchArgs = new ArrayList<>();
-                    for (TrialSummaryReflectionSaveDTO.ReflectionBean reflectionBean : collect) {
-                        Object[] paramsInsert = {
-                                SnowFlakeUtil.getId(),
-                                classificationConfiguration.getId(),
-                                classificationConfiguration.getExcelId(),
-                                reflectionBean.getTrialTabId(),
-                                reflectionBean.getElementId(),
-                                reflectionBean.getHtmlKeyName(),
-                                reflectionBean.getTrialTabName(),
-                                reflectionBean.getElementKey()
-                        };
-                        batchArgs.add(paramsInsert);
+                    // 批量更新已有记录
+                    if (!updateList.isEmpty()) {
+                        String sqlUpdate = "UPDATE m_trial_summary_excel_tab_reflection SET trial_tab_id = ?, element_id = ? WHERE id = ?";
+                        List<Object[]> updateBatchArgs = new ArrayList<>();
+                        for (TrialSummaryReflectionSaveDTO.ReflectionBean reflectionBean : updateList) {
+                            Object[] paramsUpdate = {
+                                    reflectionBean.getTrialTabId(),
+                                    reflectionBean.getElementId(),
+                                    reflectionBean.getId()
+                            };
+                            updateBatchArgs.add(paramsUpdate);
+                        }
+                        jdbcTemplate.batchUpdate(sqlUpdate, updateBatchArgs);
+                    }
+                    if (!insertList.isEmpty()){
+                        String sqlInsert = "INSERT INTO m_trial_summary_excel_tab_reflection(id,class_id,excel_id,trial_tab_id,element_id,html_key_name,trial_tab_name,element_key) VALUES (?,?,?,?,?,?,?,?)";
+                        List<Object[]> batchArgs = new ArrayList<>();
+                        for (TrialSummaryReflectionSaveDTO.ReflectionBean reflectionBean : insertList) {
+                            Object[] paramsInsert = {
+                                    SnowFlakeUtil.getId(),
+                                    classificationConfiguration.getId(),
+                                    classificationConfiguration.getExcelId(),
+                                    reflectionBean.getTrialTabId(),
+                                    reflectionBean.getElementId(),
+                                    reflectionBean.getHtmlKeyName(),
+                                    reflectionBean.getTrialTabName(),
+                                    reflectionBean.getElementKey()
+                            };
+                            batchArgs.add(paramsInsert);
+                        }
+                        jdbcTemplate.batchUpdate(sqlInsert, batchArgs);
                     }
-                    jdbcTemplate.batchUpdate(sqlInsert, batchArgs);
-
                     return R.success("操作成功");
                 }
             }

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

@@ -39,6 +39,7 @@ import org.springblade.manager.feign.ContractClient;
 import org.springblade.manager.service.IWbsParamService;
 import org.springblade.manager.service.IWbsTreeContractService;
 import org.springblade.manager.service.impl.WbsTreeContractServiceImpl;
+import org.springblade.manager.util.DataStructureFormatUtils;
 import org.springblade.manager.utils.FileUtils;
 import org.springblade.manager.utils.RandomNumberHolder;
 import org.springblade.manager.vo.*;
@@ -47,6 +48,7 @@ import org.springframework.dao.DataAccessException;
 import org.springframework.http.ResponseEntity;
 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.multipart.MultipartFile;
 
@@ -79,6 +81,7 @@ import static com.aspose.cells.LoadDataFilterOptions.FORMULA;
 import static com.aspose.cells.PropertyType.BOOLEAN;
 import static com.aspose.cells.PropertyType.STRING;
 import static java.sql.Types.NUMERIC;
+import static java.util.stream.Collectors.toMap;
 
 
 @RestController
@@ -682,7 +685,362 @@ public class WbsTreeContractController extends BladeController {
         cellStyle.setBorderRight(BorderStyle.THIN);
         cellStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
     }
+    @PostMapping("/import-node-excel")
+    @ApiOperationSupport(order = 13)
+    @ApiOperation(value = "客户端-导入多sheet excel到对应节点下的表单", notes = "传入节点ID、分类和多sheet excel文件")
+    @Transactional(rollbackFor = Exception.class)
+    public R importNodeExcel(
+            @RequestPart MultipartFile file,
+            @RequestParam Long nodeId,
+            @RequestParam Integer classify) throws Exception {
+
+        // 1. 获取节点下所有表单,并建立 sheet名 -> WbsTreeContract 的映射(处理特殊字符)
+        List<WbsTreeContract> wbsTreeContracts = wbsTreeContractServiceImpl.selectAllPkeyIdByNodeId(nodeId, classify);
+        if (wbsTreeContracts.isEmpty()) {
+            return R.fail("该节点下没有找到对应的表单数据");
+        }
+
+        // 处理表单名称(与下载时的sheet名处理逻辑一致,确保匹配)
+        Map<String, WbsTreeContract> nodeNameToContractMap = new HashMap<>();
+        for (WbsTreeContract contract : wbsTreeContracts) {
+            String processedNodeName = contract.getNodeName().replaceAll("[\\\\/:*?\"<>|]", "_");
+            nodeNameToContractMap.put(processedNodeName, contract);
+        }
+
+        // 2. 加载上传的多sheet Excel文件
+        com.spire.xls.Workbook mainWorkbook = new com.spire.xls.Workbook();
+        try {
+            mainWorkbook.loadFromStream(file.getInputStream());
+        } catch (Exception e) {
+            logger.error("加载Excel文件失败", e);
+            return R.fail("Excel文件解析失败:" + e.getMessage());
+        }
+
+        // 3. 遍历所有sheet,逐个处理
+        int sheetCount = mainWorkbook.getWorksheets().getCount();
+
+        for (int i = 0; i < sheetCount; i++) {
+            com.spire.xls.Worksheet sheet = mainWorkbook.getWorksheets().get(i);
+            String sheetName = sheet.getName();
+            String processedSheetName = sheetName.replaceAll("[\\\\/:*?\"<>|]", "_"); // 处理sheet名特殊字符
+
+            // 匹配对应的表单
+            WbsTreeContract matchedContract = nodeNameToContractMap.get(processedSheetName);
+            if (matchedContract == null) {
+                logger.warn("sheet名[{}]未匹配到任何表单,已跳过", sheetName);
+                continue;
+            }
+
+            // 获取当前表单的pkeyId
+            String pkeyId = matchedContract.getPKeyId() + "";
+            logger.info("开始处理sheet[{}],对应表单pkeyId[{}]", sheetName, pkeyId);
+
+            // 4. 将当前sheet保存为临时Excel文件(模拟单个文件上传)
+            File tempFile = null;
+            InputStream tempInputStream = null;
+            try {
+                // 创建临时文件
+                tempFile = File.createTempFile("sheet_", ".xlsx");
+                // 创建仅包含当前sheet的新工作簿
+                com.spire.xls.Workbook singleSheetWorkbook = new com.spire.xls.Workbook();
+                singleSheetWorkbook.getWorksheets().clear();
+                singleSheetWorkbook.getWorksheets().addCopy(sheet); // 复制当前sheet到新工作簿
+                singleSheetWorkbook.saveToFile(tempFile.getAbsolutePath(), com.spire.xls.FileFormat.Version2016);
+                singleSheetWorkbook.dispose();
+
+                // 读取临时文件作为输入流,调用单表单导入逻辑
+                tempInputStream = new FileInputStream(tempFile);
+                Map<String, Object> sheetResult = processSingleSheetImport(pkeyId, tempInputStream);
+                Map<String, String> dataMap = getDataMap(sheetResult);
+                String delSql = "delete from " + matchedContract.getInitTableName() + " where p_key_id=" + matchedContract.getPKeyId();
+                dataMap.put("p_key_id", matchedContract.getPKeyId()+"");
+                String  sqlInfo = buildMTableInsertSql(matchedContract.getInitTableName(), dataMap, SnowFlakeUtil.getId(), null, null).toString();
+                jdbcTemplate.execute(delSql);
+                jdbcTemplate.execute(sqlInfo);
+            } catch (Exception e) {
+                throw new ServiceException("处理sheet[" + sheetName + "]失败:" + e.getMessage());
+            } finally {
+                // 关闭流并删除临时文件
+                if (tempInputStream != null) {
+                    tempInputStream.close();
+                }
+                if (tempFile != null && !tempFile.delete()) {
+                    logger.warn("临时文件[{}]删除失败", tempFile.getAbsolutePath());
+                }
+            }
+        }
+        mainWorkbook.dispose();
+        return R.success("导入成功");
+    }
+
+    public StringBuilder buildMTableInsertSql(String tabName, Map<String, String> dataMap2, Object id, Object groupId, Object pKeyId) {
+        if (dataMap2 == null || dataMap2.isEmpty() || tabName == null || tabName.isEmpty()) {
+            return new StringBuilder();
+        }
+        //拼接SQL
+        StringBuilder sql = new StringBuilder("INSERT INTO " + tabName),
+                keySql = new StringBuilder(),
+                valSql = new StringBuilder();
+        if (id == null) {
+            if (dataMap2.containsKey("id")) {
+                id = dataMap2.get("id");
+            }
+        }
+        keySql.append("id");
+        valSql.append(id == null ? SnowFlakeUtil.getId() : id);
+        if (groupId ==  null) {
+            groupId = dataMap2.get("group_id");
+        }
+        if (groupId != null) {
+            keySql.append(", group_id");
+            valSql.append(", ").append(groupId);
+        }
+        if (pKeyId == null) {
+            pKeyId = dataMap2.get("p_key_id");
+        }
+        if (pKeyId != null) {
+            keySql.append(", p_key_id");
+            valSql.append(", ").append(pKeyId);
+        }
+        //参数
+        Map<String, String> opsParamMap = new HashMap<>();
+        dataMap2.remove("id");
+        dataMap2.remove("group_id");
+        dataMap2.remove("p_key_id");
+        String key201 = dataMap2.remove("key_201");
+        String fields = dataMap2.keySet().stream().map(key -> "'" + key + "'").collect(Collectors.joining(","));
+        Map<String, Integer> map = new HashMap<>();
+        if (!fields.isEmpty()) {
+            try {
+                fields = fields + ", 'key_201'";
+                List<Map<String, Object>> fieldMap = jdbcTemplate.queryForList("select distinct COLUMN_NAME as fieldName, CHARACTER_MAXIMUM_LENGTH as fieldLength from information_schema.COLUMNS where  TABLE_NAME = '" + tabName +
+                        "' and COLUMN_NAME in (" + fields + ")");
+                map = fieldMap.stream().collect(toMap(k -> k.get("fieldName") + "", v -> {
+                    try {
+                        return Integer.parseInt(v.get("fieldLength") + "");
+                    } catch (Exception e) {
+                        return 0;
+                    }
+                }, Math::min));
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        if (key201 != null) {
+            Map<String, String> map1 = DataStructureFormatUtils.parseDataByKey(key201);
+            if (!map1.isEmpty()) {
+                opsParamMap.putAll(map1);
+            }
+        }
+        for (String key : dataMap2.keySet()) {
+            String[] split = key.split("_");
+            if (split.length > 1 && Integer.parseInt(split[1]) > 80) {
+                // 大于80则保留在扩展字段中
+                opsParamMap.put(key, dataMap2.get(key));
+            } else {
+                String value = dataMap2.get(key);
+                if (value != null) {
+                    Integer i = map.get(key);
+                    // 长度超过数据库长度也保留在扩展字段中
+                    if (i != null &&  value.length() > i) {
+                        opsParamMap.put(key, dataMap2.get(key));
+                        continue;
+                    }
+                }
+                keySql.append(", ").append(key);
+                valSql.append(", '").append(value).append("'");
+            }
+        }
+        if (!opsParamMap.isEmpty()) {
+            keySql.append(", key_201");
+            String data = DataStructureFormatUtils.buildData(opsParamMap);
+            try {
+                if (!map.containsKey( "key_201")) {
+                    jdbcTemplate.execute("alter table " + tabName + " add column key_201 text");
+                } else  {
+                    Integer i = map.get("key_201");
+                    if (data.length() > i) {
+                        if (i < 10000) {
+                            // 65535 byte
+                            jdbcTemplate.execute("alter table " + tabName + " modify column key_201 text");
+                        }else {
+                            // 16777215 byte
+                            jdbcTemplate.execute("alter table " + tabName + " modify column key_201 mediumtext");
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            valSql.append(", '").append(data).append("'");
+        }
+        sql.append("(").append(keySql).append(")").append(" values(").append(valSql).append(")");
+        return sql;
+    }
+
+    public Map<String,String> getDataMap(Map<String, Object> originalMap){
+        // 用于存储合并后的结果
+        Map<String, String> mergedMap = new HashMap<>();
+
+        // 正则表达式:匹配"key_前缀__剩余坐标"的格式
+        Pattern pattern = Pattern.compile("key_(\\w+)__([\\w_]+)");
+
+        for (Map.Entry<String, Object> entry : originalMap.entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue()+"";
+            Matcher matcher = pattern.matcher(key);
+
+            if (matcher.matches()) {
+                String prefix = matcher.group(1); // 提取__前面的前缀(如7、17)
+                String suffix = matcher.group(2); // 提取__后面的剩余坐标(如11_9、12_5)
+
+                // 构建合并后的键(简化为prefix,如7、17)
+                String mergedKey = "key_" + prefix;
+                // 构建合并后的值(值_^_剩余坐标)
+                String mergedValue = value + "_^_" + suffix;
+
+                // 若该键已存在,用☆拼接新值;否则直接存入
+                if (mergedMap.containsKey(mergedKey)) {
+                    mergedMap.put(mergedKey, mergedMap.get(mergedKey) + "☆" + mergedValue);
+                } else {
+                    mergedMap.put(mergedKey, mergedValue);
+                }
+            }
+        }
+        return mergedMap;
+    }
+    /**
+     * 复用importExcel的核心逻辑,处理单个sheet的导入
+     * 抽取自原importExcel方法,参数改为pkeyId和输入流
+     */
+    private Map<String, Object> processSingleSheetImport(String pkeyId, InputStream inputStream) throws Exception {
+        // 获取当前表htmlString(模板)
+        String htmlString_1 = wbsTreeContractServiceImpl.getHtmlString(pkeyId);
+        if (StringUtils.isEmpty(htmlString_1)) {
+            throw new ServiceException("获取表单[" + pkeyId + "]的html模板失败");
+        }
+
+        // 结果集
+        Map<String, Object> resultMap = new HashMap<>();
+
+        // 日期格式正则(复用原逻辑)
+        String doubleSlashRegex_XG = ".*\\/[^\\/]*\\/.*";
+        String dateFormatRegex_yyyyMdd = "\\d{4}/\\d{1,2}/\\d{1,2}";
+        String dateFormatRegex_yyyyMMdd = "\\d{4}/\\d{2}/\\d{2}";
+        String dateFormatRegex_chinese = "(\\d{4}年\\d{1,2}月\\d{1,2}日|\\d{4}年\\d{2}月\\d{2}日)";
+        SimpleDateFormat inputDateFormat = new SimpleDateFormat("yyyy/M/dd");
+        SimpleDateFormat outputDateFormat = new SimpleDateFormat("yyyy年MM月dd日");
+
+        // 临时文件路径(复用原逻辑)
+        Long id = SnowFlakeUtil.getId();
+        String importExcelFilePath = FileUtils.getSysLocalFileUrl();
+        String importExcelTOHtmlPath = importExcelFilePath + "/pdf//" + id + ".html";
+
+        com.spire.xls.Workbook workbook = null;
+        try {
+            // 导入的excel转换为html(复用原逻辑)
+            workbook = new com.spire.xls.Workbook();
+            workbook.loadFromHtml(inputStream); // 加载单个sheet的输入流
+            workbook.saveToFile(importExcelTOHtmlPath, com.spire.xls.FileFormat.HTML);
+            com.spire.xls.Worksheet sheet = workbook.getWorksheets().get(0);
+
+            // 获取转换后的html路径
+            String url_1 = importExcelTOHtmlPath.split("pdf//")[0];
+            String excelToHtmlFileUrl = url_1 + "/pdf/" + id + "_files/" + sheet.getName() + ".html";
+            String htmlString_2 = IoUtil.readToString(new FileInputStream(ResourceUtil.getFile(excelToHtmlFileUrl)));
+
+            // 解析两张html的tr、td(复用原逻辑)
+            Document doc_1 = Jsoup.parse(htmlString_1); // 模板html
+            Document doc_2 = Jsoup.parse(htmlString_2); // 导入的excel转换的html
+            Elements trElements1 = doc_1.select("table tbody tr");
+            Elements trElements2 = doc_2.select("table tbody tr");
+
+            for (int i = 0; i < trElements1.size(); i++) {
+                Element tr1 = trElements1.get(i);
+                Element tr2 = trElements2.size() > i ? trElements2.get(i) : null;
+                if (tr2 == null) break;
+
+                Elements tdElements1 = tr1.select("td");
+                Elements tdElements2 = tr2.select("td");
+
+                for (int j = 0; j < tdElements1.size(); j++) {
+                    Element td1 = tdElements1.get(j);
+                    if (td1.attr("dqid").length() > 0) { // 跳过包含dqid的td
+                        continue;
+                    }
+
+                    Element td2 = tdElements2.size() > j ? tdElements2.get(j) : null;
+                    if (td2 == null) break;
+
+                    String keyName = getKeyNameFromChildElement(td1); // 复用原方法获取key
+                    if (StringUtils.isNotEmpty(keyName)) {
+                        String divValue = td2.text();
+                        if (StringUtils.isNotEmpty(divValue)) {
+                            // 日期范围处理
+                            if (parseDateRange(divValue).size() == 2) {
+                                resultMap.put(keyName, parseDateRange(divValue));
+                                continue;
+                            }
+
+                            // 日期格式转换(复用原逻辑)
+                            Pattern pattern_XG = Pattern.compile(doubleSlashRegex_XG);
+                            Matcher matcher_XG = pattern_XG.matcher(divValue);
+                            if (matcher_XG.matches()) {
+                                Pattern pattern_yyyyMdd = Pattern.compile(dateFormatRegex_yyyyMdd);
+                                Pattern pattern_yyyyMMdd = Pattern.compile(dateFormatRegex_yyyyMMdd);
+                                Matcher matcher_yyyyMdd = pattern_yyyyMdd.matcher(divValue);
+                                Matcher matcher_yyyyMMdd = pattern_yyyyMMdd.matcher(divValue);
 
+                                if (matcher_yyyyMdd.matches() || matcher_yyyyMMdd.matches()) {
+                                    Date date = inputDateFormat.parse(divValue);
+                                    divValue = outputDateFormat.format(date);
+                                }
+                            } else if (divValue.contains("年") && divValue.contains("月") && divValue.contains("日")) {
+                                Pattern pattern_chinese = Pattern.compile(dateFormatRegex_chinese);
+                                Matcher matcher_chinese = pattern_chinese.matcher(divValue);
+                                if (matcher_chinese.matches()) {
+                                    Date date = outputDateFormat.parse(divValue);
+                                    divValue = outputDateFormat.format(date);
+                                }
+                            }
+
+                            resultMap.put(keyName, divValue);
+                        }
+                    }
+                }
+            }
+
+            // 排序结果(复用原逻辑)
+            if (!resultMap.isEmpty()) {
+                List<Map.Entry<String, Object>> entryList = new ArrayList<>(resultMap.entrySet());
+                entryList.sort(new KeyComparator());
+                LinkedHashMap<String, Object> sortedMap = new LinkedHashMap<>();
+                for (Map.Entry<String, Object> entry : entryList) {
+                    sortedMap.put(entry.getKey(), entry.getValue());
+                }
+                resultMap = sortedMap;
+            }
+
+        } finally {
+            if (workbook != null) {
+                workbook.dispose();
+            }
+            // 删除临时文件(复用原逻辑)
+            if (deleteFolder(Paths.get(importExcelTOHtmlPath))) {
+                logger.info("表单[{}]临时文件删除成功", pkeyId);
+            } else {
+                logger.warn("表单[{}]临时文件删除失败", pkeyId);
+            }
+            String url_1 = importExcelTOHtmlPath.split("pdf//")[0];
+            if (deleteFolderAndContents(Paths.get(url_1 + "/pdf/" + id + "_files"))) {
+                logger.info("表单[{}]临时文件夹删除成功", pkeyId);
+            } else {
+                logger.warn("表单[{}]临时文件夹删除失败", pkeyId);
+            }
+        }
+
+        return resultMap.isEmpty() ? Collections.singletonMap("message", "未获取到有效数据") : resultMap;
+    }
     /**
      * 客户端-导入excel数据到对应元素表中
      *
@@ -1145,6 +1503,8 @@ public class WbsTreeContractController extends BladeController {
 
     }
 
+
+
     /**
      * 判断日期范围格式数据,以下12种格式
      * 2023-01-01-2023-01-30 或 2023-01-01~2023-01-30

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

@@ -180,7 +180,7 @@ public class SubTable {
     /*获取最终输出的项目信息*/
     public List<Item> getPutOutList(){
         /*初始化group,保留原先内容,只做同KEY覆盖*/
-        initOriginal();
+        //initOriginal();
         List<Item> itemList = new ArrayList<>(group.values());
         if(this.mainList!=null){
             List<String> itemNameIndex = this.mainList.stream().map(e->FormulaUtils.parseItemName(e.getEName()).trim()).collect(Collectors.toList());

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

@@ -173,6 +173,7 @@
         <result column="specification" property="specification"/>
         <result column="archive_name_suffix" property="archiveNameSuffix"/>
         <result column="tree_number" property="treeNumber"/>
+        <result column="check_status" property="checkStatus"/>
 <!--        <association property="treeNumber" javaType="java.lang.Integer" select="selectFileNumber"-->
 <!--                     column="{id=id,projectId=project_id,code=code,contractId=contract_id,extType=ext_type}"/>-->
     </resultMap>
@@ -273,6 +274,7 @@
         d.archive_name_suffix,
         d.contract_id,
         IFNULL(SUM(file_counts.count), 0) AS tree_number,
+        file_counts.check_status AS check_status,
         (SELECT CASE WHEN count(1) > 0 THEN 1 ELSE 0 END FROM m_archive_tree_contract WHERE parent_id = d.id and is_deleted = 0 and project_id = #{projectId}) AS "has_children"
         FROM
         m_archive_tree_contract d
@@ -281,11 +283,11 @@
             SELECT
                 a.id,
                 a.ancestors,
-                SUM(b.count) AS count
+                SUM(b.count) AS count, if(b.check_status > 0, 1, 0) as check_status
             FROM
                 m_archive_tree_contract a
             INNER JOIN (
-                SELECT node_id AS id, COUNT(*) AS count
+                SELECT node_id AS id, COUNT(*) AS count, count(CASE WHEN check_status > 0 THEN 1 END) as check_status
                 FROM u_archive_file
                 WHERE project_id = #{projectId}
                 AND is_deleted = 0
@@ -294,7 +296,7 @@
                 <if test="extType > 0">
                     UNION ALL
 
-                    SELECT node_ext_id AS id, COUNT(*) AS count
+                    SELECT node_ext_id AS id, COUNT(*) AS count, count(CASE WHEN check_status > 0 THEN 1 END) as check_status
                     FROM u_archive_file
                     WHERE project_id = #{projectId}
                     AND is_deleted = 0

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

@@ -1361,6 +1361,12 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
                             });
                             if (ele.size() < relyList.size()) {
                                 tec.getLog().put(FormulaLog.RELY, fd.getCode() + "@" + fd.getEName() + "@" + fd.getFormula().getFormula().replaceAll("'", ""));
+                                fd.getValues().forEach(e->e.setValue(null));
+                                FormData formData = tec.getFormDataMap().get(fd.getCode());
+                                if(formData!=null){
+                                    formData.getValues().forEach(e->e.setValue(null));
+                                    formData.setUpdate(1);
+                                }
                                 continue;
                             }
                             if (fd.getCoordsList().size() > 1 && f.split("[/+\\-*]").length > 1) {
@@ -1947,10 +1953,15 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
                 headerFooterSub(subTableFds, tec);
                 /*如果识别到手填内容需要在附表写入数据后,更新评定关联数据*/
             }else {
-                List<NodeTable> subTabList = tec.getTableAll().stream().filter(e -> e.getNodeName().contains("质量检验表(附表") && (e.getTableType() == 1 || e.getTableType() == 5)).collect(Collectors.toList());
+                List<NodeTable> subTabList = tec.getTableAll().stream().filter(e -> e.getNodeName().contains("附表") && (e.getTableType() == 1 || e.getTableType() == 5)).collect(Collectors.toList());
                 if(!subTabList.isEmpty()){
-                    List<Long> removeIds=subTabList.stream().map(e->e.getPKeyId()).collect(Collectors.toList());
-                    this.wbsTreeContractMapper.deleteLogicByIds(removeIds);
+                    String queryIds = subTabList.stream().map(e -> e.getPKeyId() + "").collect(Collectors.joining(","));
+                    // 如果不是系统自动生成的附表,则不删除。
+                    List<Long> ids = jdbcTemplate.query("SELECT a.p_key_id from m_wbs_tree_contract a LEFT JOIN m_wbs_tree_private b on b.p_key_id = a.is_type_private_pid " +
+                            "where a.is_deleted = 0 and (b.p_key_id is null or (b.is_deleted = 0 and b.is_link_table = 2 and b.type = 10 )) and a.p_key_id in ( " + queryIds +")", new SingleColumnRowMapper<>(Long.class));
+                    if (!ids.isEmpty()) {
+                        this.wbsTreeContractMapper.deleteLogicByIds(ids);
+                    }
                 }
             }
         } catch (Exception e) {

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

@@ -3006,8 +3006,11 @@ public class TaskController extends BladeController {
             }
 
             /*副任务状态*/
-            updateTaskParallelStatus(task);
-
+            if(task.getApprovalType()==11){ //变更令  走流程不需要电签
+                updateTaskParallelStatusByBGL(task);
+            }else {
+                updateTaskParallelStatus(task);
+            }
             Set<String> aopParamsSet = new HashSet<>();
             for (TaskParallel taskParallel : taskParallels) {
                 String param = taskParallel.getTaskUser() + "," + task.getProjectId() + "," + task.getContractId();
@@ -3865,6 +3868,15 @@ public class TaskController extends BladeController {
         return this;
     }
 
+    private void updateTaskParallelStatusByBGL(Task task) {
+        TaskParallel taskParallelOne = jdbcTemplate.query("SELECT * FROM u_task_parallel WHERE status=1 and process_instance_id = '" + task.getProcessInstanceId() + "' AND task_user = " + SecureUtil.getUserId(), new BeanPropertyRowMapper<>(TaskParallel.class)).stream().findAny().orElse(null);
+        if (taskParallelOne != null && taskParallelOne.getStatus().equals(1)) {
+            String sql = "UPDATE u_task_parallel SET status = ? ,update_time = ? ,e_visa_status=1,e_visa_content='电签成功',initiative=2, create_time = SYSDATE()  WHERE id = ?";
+            Object[] param = {2, new Date(), taskParallelOne.getId()};
+            jdbcTemplate.update(sql, param);
+        }
+    }
+
     private void updateTaskParallelStatus(Task task) {
         TaskParallel taskParallelOne = jdbcTemplate.query("SELECT * FROM u_task_parallel WHERE status=1 and process_instance_id = '" + task.getProcessInstanceId() + "' AND task_user = " + SecureUtil.getUserId(), new BeanPropertyRowMapper<>(TaskParallel.class)).stream().findAny().orElse(null);
         if (taskParallelOne != null && taskParallelOne.getStatus().equals(1)) {

+ 5 - 1
pom.xml

@@ -226,7 +226,11 @@
             <name>Release Repository</name>
             <url>http://nexus.bladex.vip/repository/maven-releases/</url>
         </repository>
-
+        <repository>
+            <id>com.e-iceblue</id>
+            <name>e-iceblue</name>
+            <url>https://repo.e-iceblue.cn/repository/maven-public/</url>
+        </repository>
     </repositories>
 
     <pluginRepositories>