Przeglądaj źródła

Merge remote-tracking branch 'origin/master'

赵越越 3 tygodni temu
rodzic
commit
69efd17be3
19 zmienionych plików z 820 dodań i 287 usunięć
  1. 1 0
      assistMg/src/main/java/com/hotent/baseInfo/manager/impl/CostCatalogUnitManagerImpl.java
  2. 62 3
      assistMg/src/main/java/com/hotent/enterpriseDeclare/controller/material/CostProjectTaskMaterialSummaryController.java
  3. 228 206
      assistMg/src/main/java/com/hotent/enterpriseDeclare/controller/material/CostProjectTaskSurveyGenericController.java
  4. 3 1
      assistMg/src/main/java/com/hotent/enterpriseDeclare/req/CostTaskPageReq.java
  5. 2 2
      assistMg/src/main/java/com/hotent/project/dao/CostProjectTaskMaterialSummaryDetailDao.java
  6. 3 3
      assistMg/src/main/java/com/hotent/project/manager/CostProjectTaskMaterialSummaryDetailManager.java
  7. 8 1
      assistMg/src/main/java/com/hotent/project/manager/impl/CostProjectApprovalManagerImpl.java
  8. 1 0
      assistMg/src/main/java/com/hotent/project/manager/impl/CostProjectMaterialManagerImpl.java
  9. 85 14
      assistMg/src/main/java/com/hotent/project/manager/impl/CostProjectTaskManagerImpl.java
  10. 7 6
      assistMg/src/main/java/com/hotent/project/manager/impl/CostProjectTaskMaterialSummaryDetailManagerImpl.java
  11. 31 31
      assistMg/src/main/java/com/hotent/project/manager/impl/CostProjectTaskMaterialSummaryManagerImpl.java
  12. 1 1
      assistMg/src/main/java/com/hotent/project/model/CostProjectTaskMaterial.java
  13. 1 1
      assistMg/src/main/java/com/hotent/project/model/CostProjectTaskMaterialSummary.java
  14. 5 6
      assistMg/src/main/java/com/hotent/project/model/CostProjectTaskMaterialSummaryDetail.java
  15. 335 0
      assistMg/src/main/java/com/hotent/project/service/AsyncMaterialSummaryService.java
  16. 16 11
      assistMg/src/main/java/com/hotent/surveyinfo/controller/CostSurveyTemplateController.java
  17. 16 0
      assistMg/src/main/java/com/hotent/surveyinfo/controller/CostVerifyTemplateController.java
  18. 14 0
      assistMg/src/main/java/com/hotent/surveyinfo/model/CostSurveyTemplateUpload.java
  19. 1 1
      assistMg/src/main/resources/mapper/CostSurveyTemplateItemsMapper.xml

+ 1 - 0
assistMg/src/main/java/com/hotent/baseInfo/manager/impl/CostCatalogUnitManagerImpl.java

@@ -59,6 +59,7 @@ public class CostCatalogUnitManagerImpl extends BaseManagerImpl<CostCatalogUnitD
 
         LambdaQueryWrapper<CostCatalogUnit> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.eq(CostCatalogUnit::getCatalogId, req.getCatalogId());
+        queryWrapper.orderByAsc(CostCatalogUnit::getOrderNum);
         IPage<CostCatalogUnit> page = new Page<>(req.getPageNum(), req.getPageSize());
         IPage<CostCatalogUnit> pageList = this.page(page, queryWrapper);
         if (ObjectUtil.isEmpty(pageList.getRecords())) {

+ 62 - 3
assistMg/src/main/java/com/hotent/enterpriseDeclare/controller/material/CostProjectTaskMaterialSummaryController.java

@@ -5,7 +5,7 @@ import com.hotent.base.constants.ApiGroupConsts;
 import com.hotent.base.controller.BaseController;
 import com.hotent.base.model.CommonResult;
 import com.hotent.base.util.StringUtil;
-import com.hotent.project.manager.CostProjectTaskMaterialDetailManager;
+import com.hotent.project.manager.CostProjectTaskMaterialSummaryDetailManager;
 import com.hotent.project.manager.CostProjectTaskMaterialSummaryManager;
 import com.hotent.project.model.CostProjectTaskMaterialSummary;
 import com.hotent.project.req.MaterialSummarySortReq;
@@ -31,8 +31,6 @@ import java.util.List;
 @ApiGroup(group = {ApiGroupConsts.GROUP_COST})
 public class CostProjectTaskMaterialSummaryController extends BaseController<CostProjectTaskMaterialSummaryManager, CostProjectTaskMaterialSummary> {
 
-    @Autowired
-    private CostProjectTaskMaterialDetailManager costProjectTaskMaterialDetailManager;
 
     /**
      * 根据taskId获取资料归纳列表
@@ -124,5 +122,66 @@ public class CostProjectTaskMaterialSummaryController extends BaseController<Cos
         baseService.sort(req.getId(), req.getDirection());
         return CommonResult.<String>ok().message("排序成功");
     }
+
+    @Autowired
+    private com.hotent.project.service.AsyncMaterialSummaryService asyncMaterialSummaryService;
+
+    /**
+     * 档案校对通过,触发异步生成Word卷宗
+     * @param taskId 任务ID
+     * @return
+     */
+    @PostMapping(value = "/approveArchive")
+    @ApiOperation(value = "档案校对通过", httpMethod = "POST", notes = "档案校对通过后,异步生成Word卷宗")
+    public CommonResult<String> approveArchive(
+            @ApiParam(name = "taskId", value = "任务ID", required = true)
+            @RequestParam(required = true) String taskId) {
+
+        // 校验任务是否存在资料归纳数据
+        List<CostProjectTaskMaterialSummary> summaryList = baseService.listByTaskId(taskId);
+        if (summaryList == null || summaryList.isEmpty()) {
+            return CommonResult.<String>error().message("该任务没有资料归纳数据,无法校对");
+        }
+
+        // 校验所有资料是否都已上传附件
+        boolean allHasAttachment = true;
+        for (CostProjectTaskMaterialSummary summary : summaryList) {
+            if (summary.getDetailList() != null) {
+                for (com.hotent.project.model.CostProjectTaskMaterialSummaryDetail detail : summary.getDetailList()) {
+                    if (StringUtil.isEmpty(detail.getAttachmentUrl())) {
+                        allHasAttachment = false;
+                        break;
+                    }
+                }
+            }
+            if (!allHasAttachment) {
+                break;
+            }
+        }
+
+        if (!allHasAttachment) {
+            return CommonResult.<String>error().message("部分资料未上传附件,请完善后再进行校对");
+        }
+
+        // 异步生成Word卷宗
+        asyncMaterialSummaryService.generateWordArchiveAsync(taskId);
+
+        return CommonResult.<String>ok().message("档案校对通过,正在后台生成Word卷宗,请稍后查看");
+    }
+
+    /**
+     * 查询Word卷宗生成状态
+     * @param taskId 任务ID
+     * @return
+     */
+    @GetMapping(value = "/getArchiveStatus")
+    @ApiOperation(value = "查询Word卷宗生成状态", httpMethod = "GET", notes = "查询Word卷宗生成状态")
+    public CommonResult<String> getArchiveStatus(
+            @ApiParam(name = "taskId", value = "任务ID", required = true)
+            @RequestParam(required = true) String taskId) {
+        // TODO: 实现查询卷宗生成状态的逻辑
+        // 可以在数据库中添加一个状态字段来记录生成进度
+        return CommonResult.<String>ok().value("生成中").message("查询成功");
+    }
 }
 

+ 228 - 206
assistMg/src/main/java/com/hotent/enterpriseDeclare/controller/material/CostProjectTaskSurveyGenericController.java

@@ -172,13 +172,22 @@ public class CostProjectTaskSurveyGenericController {
                         costSurveyTemplateUploadManager.save(upload);
                     }
                 }
-                return CommonResult.ok().value(costSurveyTemplateUploadManager.listByTaskId(taskId));
+                List<CostSurveyTemplateUpload> costSurveyTemplateUploads = costSurveyTemplateUploadManager.listByTaskId(taskId);
+                for (CostSurveyTemplateUpload costSurveyTemplateUpload : costSurveyTemplateUploads) {
+                    CostProjectTask task = costProjectTaskManager.getById(taskId);
+                    costSurveyTemplateUpload.setAuditedUnitId(task.getAuditedUnitId());
+                }
+                return CommonResult.ok().value(costSurveyTemplateUploads);
             }
             case "2": {
                 // 财务数据表逻辑
                 QueryWrapper<CostProjectTaskMaterial> wrapper = new QueryWrapper<>();
                 wrapper.eq("task_id", taskId);
                 List<CostProjectTaskMaterial> materialList = costProjectTaskMaterialManager.list(wrapper);
+                for (CostProjectTaskMaterial costProjectTaskMaterial : materialList) {
+                    CostProjectTask task = costProjectTaskManager.getById(taskId);
+                    costProjectTaskMaterial.setAuditedUnitId(task.getAuditedUnitId()); ;
+                }
                 return CommonResult.ok().value(materialList);
             }
             default:
@@ -358,7 +367,6 @@ public class CostProjectTaskSurveyGenericController {
             wrapper.eq("period_record_id", queryData.getPeriodRecordId());
         }
 
-        wrapper.orderByAsc("rowid", "rkey");
         List<CostSurveyTemplateUploadData> dataList = costSurveyTemplateUploadDataManager.list(wrapper);
         for (CostSurveyTemplateUploadData costSurveyTemplateUploadData : dataList) {
             costSurveyTemplateUploadData.setUploadId(costSurveyTemplateUploadData.getRefId());
@@ -600,8 +608,7 @@ public class CostProjectTaskSurveyGenericController {
                         surveyTemplateId, currentVersion.getId());
 
                 Workbook workbook = new XSSFWorkbook();
-                String sheetName = currentVersion.getSurveyTemplateName() != null ?
-                        currentVersion.getSurveyTemplateName() : "成本调查表";
+                String sheetName = System.currentTimeMillis() + "_成本调查表";
                 Sheet sheet = workbook.createSheet(sheetName);
 
                 Row headerRow = sheet.createRow(0);
@@ -656,12 +663,19 @@ public class CostProjectTaskSurveyGenericController {
 
                 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
                 response.setCharacterEncoding("utf-8");
-                String fileName = sheetName + "_" + System.currentTimeMillis() + ".xlsx";
+                String fileName = "成本调查表_" + System.currentTimeMillis() + ".xlsx";
                 response.setHeader("Content-Disposition", "attachment; filename=" +
                         URLEncoder.encode(fileName, "UTF-8"));
 
-                workbook.write(response.getOutputStream());
-                workbook.close();
+                try {
+                    workbook.write(response.getOutputStream());
+                } catch (Exception e) {
+                    System.err.println("成本调查表导出失败: " + e.getMessage());
+                    e.printStackTrace();
+                    throw e;
+                } finally {
+                    workbook.close();
+                }
                 break;
             }
             // 财务数据表逻辑
@@ -715,8 +729,7 @@ public class CostProjectTaskSurveyGenericController {
 
                 // 5.返回excel
                 Workbook workbook = new XSSFWorkbook();
-                String sheetName = currentVersion.getSurveyTemplateName() != null ?
-                        currentVersion.getSurveyTemplateName() : "财务数据表";
+                String sheetName = System.currentTimeMillis() + "_财务数据表";
                 Sheet sheet = workbook.createSheet(sheetName);
 
                 Row headerRow = sheet.createRow(0);
@@ -771,12 +784,19 @@ public class CostProjectTaskSurveyGenericController {
 
                 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
                 response.setCharacterEncoding("utf-8");
-                String fileName = sheetName + "_" + System.currentTimeMillis() + ".xlsx";
+                String fileName = "财务数据表_" + System.currentTimeMillis() + ".xlsx";
                 response.setHeader("Content-Disposition", "attachment; filename=" +
                         URLEncoder.encode(fileName, "UTF-8"));
 
-                workbook.write(response.getOutputStream());
-                workbook.close();
+                try {
+                    workbook.write(response.getOutputStream());
+                } catch (Exception e) {
+                    System.err.println("财务数据表导出失败: " + e.getMessage());
+                    e.printStackTrace();
+                    throw e;
+                } finally {
+                    workbook.close();
+                }
                 break;
             }
             // 核定表逻辑
@@ -786,6 +806,7 @@ public class CostProjectTaskSurveyGenericController {
                     response.sendError(HttpServletResponse.SC_BAD_REQUEST, "未找到指定的核定表模板");
                     return;
                 }
+
                 List<CostVerifyTemplateHeaders> headersList =
                         costVerifyTemplateHeadersDao.selectList(
                                         new QueryWrapper<CostVerifyTemplateHeaders>()
@@ -796,12 +817,12 @@ public class CostProjectTaskSurveyGenericController {
                                 .sorted(Comparator.comparing(h -> {
                                     String orderNum = h.getOrderNum();
                                     if (StringUtil.isEmpty(orderNum)) {
-                                        return Integer.MAX_VALUE; // 空值排到最后
+                                        return Integer.MAX_VALUE;
                                     }
                                     try {
                                         return Integer.parseInt(orderNum.trim());
                                     } catch (NumberFormatException e) {
-                                        return Integer.MAX_VALUE; // 无效数字排到最后
+                                        return Integer.MAX_VALUE;
                                     }
                                 }))
                                 .collect(Collectors.toList());
@@ -811,124 +832,56 @@ public class CostProjectTaskSurveyGenericController {
                     return;
                 }
 
-                // 查询核定表数据项
                 List<CostVerifyTemplateItems> itemsList =
                         costVerifyTemplateItemsDao.selectByVerifyTemplateId(surveyTemplateId, null);
 
-                // 添加数据验证日志
-                System.out.println("核定表导出 - 模板ID: " + surveyTemplateId + ", 表头数量: " + headersList.size() + ", 数据项数量: " + (itemsList != null ? itemsList.size() : 0));
-
-                // 在创建 Excel 之前,先补充缺失的表头
-                if (itemsList != null && !itemsList.isEmpty()) {
-                    // 收集所有数据项中的 headersId
-                    Set<String> allHeadersIds = itemsList.stream()
-                            .map(CostVerifyTemplateItems::getHeadersId)
-                            .filter(StringUtil::isNotEmpty)
-                            .collect(Collectors.toSet());
-
-                    System.out.println("数据项中的唯一 headersId 数量: " + allHeadersIds.size());
-
-                    // 检查是否有 headersId 在表头中找不到
-                    Set<String> existingHeaderIds = headersList.stream()
-                            .map(CostVerifyTemplateHeaders::getId)
-                            .collect(Collectors.toSet());
-
-                    Set<String> missingHeaderIds = new HashSet<>(allHeadersIds);
-                    missingHeaderIds.removeAll(existingHeaderIds);
-
-                    if (!missingHeaderIds.isEmpty()) {
-                        System.err.println("警告:发现 " + missingHeaderIds.size() + " 个数据项的 headersId 在表头中不存在:");
-                        for (String missingId : missingHeaderIds) {
-                            System.err.println("  - " + missingId);
-                            // 查询这个缺失的表头信息
-                            CostVerifyTemplateHeaders missingHeader = costVerifyTemplateHeadersDao.selectById(missingId);
-                            if (missingHeader != null) {
-                                System.err.println("    找到表头: " + missingHeader.getFieldName() + ", showVisible: " + missingHeader.getShowVisible());
-                                // 将缺失的表头添加到列表中
-                                headersList.add(missingHeader);
-                            } else {
-                                System.err.println("    表头不存在于数据库中");
-                            }
-                        }
-                        System.out.println("已补充缺失的表头,新的表头总数: " + headersList.size());
-                    }
-                }
-
-                // 打印表头统计
-                System.out.println("最终表头列表: 共 " + headersList.size() + " 列");
-
                 Workbook workbook = new XSSFWorkbook();
-                String sheetName = template.getSurveyTemplateName() != null ? template.getSurveyTemplateName() : "核定表";
-
-                // Excel sheet 名称不能包含以下字符: : \ / ? * [ ]
-                // 并且长度不能超过 31 个字符
-                sheetName = sheetName.replaceAll("[:\\\\/*?\\[\\]]", "_");
-                if (sheetName.length() > 31) {
-                    sheetName = sheetName.substring(0, 31);
-                }
-                System.out.println("Sheet 名称: " + sheetName);
-
+                String sheetName = System.currentTimeMillis() + "_核定表";
                 Sheet sheet = workbook.createSheet(sheetName);
 
-                // 创建表头行
                 Row headerRow = sheet.createRow(0);
                 int colIndex = 0;
 
-                // 添加原有表头
                 for (int i = 0; i < headersList.size(); i++) {
                     headerRow.createCell(colIndex++).setCellValue(headersList.get(i).getFieldName());
                 }
 
-                // 添加行ID列和父行ID列(固定表需要)
                 int rowIdColIndex = colIndex;
                 headerRow.createCell(colIndex++).setCellValue("行ID");
                 int parentIdColIndex = colIndex;
                 headerRow.createCell(colIndex++).setCellValue("父行ID");
 
-                // 填充数据
                 if (itemsList != null && !itemsList.isEmpty()) {
-
                     Map<String, Integer> headerIndexMap = new HashMap<>();
                     for (int i = 0; i < headersList.size(); i++) {
                         headerIndexMap.put(headersList.get(i).getId(), i);
-                        System.out.println("表头映射 - ID: " + headersList.get(i).getId() + ", 列索引: " + i + ", 字段名: " + headersList.get(i).getFieldName());
                     }
-
-                    // 打印 items 的 headersId 信息
-                    System.out.println("数据项的 headersId 分布:");
-                    Map<String, Long> headersIdCount = itemsList.stream()
-                            .collect(Collectors.groupingBy(
-                                    item -> item.getHeadersId() != null ? item.getHeadersId() : "null",
-                                    Collectors.counting()
-                            ));
-                    headersIdCount.forEach((headersId, count) ->
-                            System.out.println("  headersId: " + headersId + ", 数量: " + count)
-                    );
-
                     fillExcelDataVerify(sheet, itemsList, headerIndexMap, rowIdColIndex, parentIdColIndex);
                 }
 
-                // 列宽处理
                 for (int i = 0; i < headersList.size(); i++) {
                     sheet.autoSizeColumn(i);
                     sheet.setColumnWidth(i, Math.max(sheet.getColumnWidth(i), 3000));
                 }
 
-                // 隐藏行ID和父行ID列
                 sheet.setColumnHidden(rowIdColIndex, true);
                 sheet.setColumnHidden(parentIdColIndex, true);
 
-                // 输出
                 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
                 response.setCharacterEncoding("utf-8");
-                String fileName = sheetName + "_" + System.currentTimeMillis() + ".xlsx";
+                String fileName = "核定表_" + System.currentTimeMillis() + ".xlsx";
                 response.setHeader("Content-Disposition", "attachment; filename=" +
                         URLEncoder.encode(fileName, "UTF-8"));
 
-                System.out.println("开始写入 Excel 文件到输出流...");
-                workbook.write(response.getOutputStream());
-                workbook.close();
-                System.out.println("Excel 文件写入完成");
+                try {
+                    workbook.write(response.getOutputStream());
+                } catch (Exception e) {
+                    System.err.println("核定表导出失败: " + e.getMessage());
+                    e.printStackTrace();
+                    throw e;
+                } finally {
+                    workbook.close();
+                }
                 break;
             }
             // 其它不支持
@@ -978,6 +931,12 @@ public class CostProjectTaskSurveyGenericController {
         if (refId == null) {
             refId = materialId;
         }
+        if (periodRecordId !=null){
+            CostAuditPeriodRecord periodRecord = costAuditPeriodRecordManager.get(periodRecordId);
+            if (periodRecord != null){
+                templateType = periodRecord.getType();
+            }
+        }
         switch (type) {
             // 成本调查表逻辑
             case "1": {
@@ -1173,7 +1132,9 @@ public class CostProjectTaskSurveyGenericController {
 
                     List<String> errors = verifyImportData(dataList, type, surveyTemplateId, rowIdToExcelRowMap);
                     if (!errors.isEmpty()) {
-                        return CommonResult.<String>error().message("导入失败,发现以下问题:\n" + String.join("\n", errors));
+                        CommonResult<String> result = CommonResult.<String>error().message("导入失败,发现以下问题:<br>" + String.join("<br>", errors));
+                        result.setCode(250);
+                        return result;
                     }
                     costSurveyTemplateUploadDataManager.saveData(dataList);
 
@@ -1379,12 +1340,16 @@ public class CostProjectTaskSurveyGenericController {
                     }
                     List<String> errors = verifyImportData(dataList, type, surveyTemplateId, rowIdToExcelRowMap);
                     if (!errors.isEmpty()) {
-                        return CommonResult.<String>error().message("导入失败,发现以下问题:\n" + String.join("\n", errors));
+                        CommonResult<String> result = CommonResult.<String>error().message("导入失败,发现以下问题:<br>" + String.join("<br>", errors));
+                        result.setCode(250);
+                        return result;
                     }
                     costSurveyTemplateUploadDataManager.saveData(dataList);
-                    CostProjectTaskMaterial material = costProjectTaskMaterialManager.getById(materialId);
-                    material.setIsUpload("1");
-                    costProjectTaskMaterialManager.updateById(material);
+                    if (materialId != null) {
+                        CostProjectTaskMaterial material = costProjectTaskMaterialManager.getById(materialId);
+                        material.setIsUpload("1");
+                        costProjectTaskMaterialManager.updateById(material);
+                    }
 
                     return CommonResult.<String>ok().message("导入成功,共导入 " + dataRowCount + " 行数据");
                 } catch (Exception e) {
@@ -1519,13 +1484,9 @@ public class CostProjectTaskSurveyGenericController {
                     if (dataList.isEmpty()) {
                         return CommonResult.<String>error().message("Excel文件没有有效数据");
                     }
-                    List<String> errors = verifyImportData(dataList, type, surveyTemplateId, rowIdToExcelRowMap);
-                    if (!errors.isEmpty()) {
-                        return CommonResult.<String>error().message("导入失败,发现以下问题:\n" + String.join("\n", errors));
-                    }
-                    // 保存
-                    costSurveyTemplateUploadDataManager.saveData(dataList);
 
+                    // 保存数据(核定表不需要复杂的字段校验)
+                    costSurveyTemplateUploadDataManager.saveData(dataList);
 
                     return CommonResult.<String>ok().message("导入成功,共导入 " + importRowCount + " 行数据");
 
@@ -1818,12 +1779,13 @@ public class CostProjectTaskSurveyGenericController {
             String fieldType = getHeaderFieldType(headerObj);
             Integer fieldTypelen = getHeaderFieldTypelen(headerObj);
             Integer fieldTypenointlen = getHeaderFieldTypenointlen(headerObj);
+            String format = getHeaderFormat(headerObj);
             String isRequired = getHeaderIsRequired(headerObj);
             String isDict = getHeaderIsDict(headerObj);
             String dictCode = getHeaderDictCode(headerObj);
 
-            // 获取用户输入的值
-            String value = rowData.get(fieldEname);
+            // 获取用户输入的值(注意:rowData的key是fieldName中文字段名)
+            String value = rowData.get(fieldName);
 
             System.out.println("  校验字段: " + fieldEname + "(" + fieldName + "), 值: " + value +
                              ", 必填: " + isRequired + ", 类型: " + fieldType + ", 字典: " + isDict);
@@ -1842,25 +1804,16 @@ public class CostProjectTaskSurveyGenericController {
             // 2. 字典校验(支持 "1" 和 "true" 两种格式)
             if (("1".equals(isDict) || "true".equalsIgnoreCase(isDict)) && StringUtil.isNotEmpty(dictCode)) {
                 try {
-                    validateDictValue(rowid, fieldEname, fieldName, value, dictCode);
+                    validateDictValue(rowDisplay, fieldEname, fieldName, value, dictCode);
                 } catch (IllegalArgumentException e) {
                     errors.add(e.getMessage());
                 }
             }
 
-            // 3. 字段类型校验
+            // 3. 字段类型和长度校验(已合并)
             if (StringUtil.isNotEmpty(fieldType)) {
                 try {
-                    validateFieldType(rowDisplay, fieldEname, fieldName, value, fieldType);
-                } catch (IllegalArgumentException e) {
-                    errors.add(e.getMessage());
-                }
-            }
-
-            // 4. 字段长度校验
-            if (fieldTypelen != null && fieldTypelen > 0) {
-                try {
-                    validateFieldLength(rowid, fieldEname, fieldName, value, fieldType, fieldTypelen, fieldTypenointlen);
+                    validateFieldType(rowDisplay, fieldEname, fieldName, value, fieldType, fieldTypelen, fieldTypenointlen, format);
                 } catch (IllegalArgumentException e) {
                     errors.add(e.getMessage());
                 }
@@ -1873,7 +1826,7 @@ public class CostProjectTaskSurveyGenericController {
     /**
      * 字典校验
      */
-    private void validateDictValue(String rowid, String fieldEname, String fieldName,
+    private void validateDictValue(String rowDisplay, String fieldEname, String fieldName,
                                    String value, String dictCode) {
         if (StringUtil.isEmpty(value) || StringUtil.isEmpty(dictCode)) {
             return;
@@ -1890,14 +1843,14 @@ public class CostProjectTaskSurveyGenericController {
             List<DataDict> dictDataList = dataDictManager.list(wrapper);
 
             if (dictDataList == null || dictDataList.isEmpty()) {
-                throw new IllegalArgumentException(String.format("行[%s] [%s]的选项配置异常",
-                        rowid, fieldName));
+                throw new IllegalArgumentException(String.format("%s [%s]的选项配置异常",
+                        rowDisplay, fieldName));
             }
 
             // 检查值是否在字典允许的范围内(支持多选,用逗号分隔)
             String[] values = value.split(",");
             Set<String> validValues = dictDataList.stream()
-                    .map(DataDict::getKey)
+                    .map(DataDict::getName)
                     .filter(StringUtil::isNotEmpty)
                     .collect(Collectors.toSet());
 
@@ -1910,56 +1863,151 @@ public class CostProjectTaskSurveyGenericController {
             }
 
             if (!invalidValues.isEmpty()) {
-                throw new IllegalArgumentException(String.format("行[%s] [%s]的值[%s]不在允许的选项范围内",
-                        rowid, fieldName, String.join(", ", invalidValues)));
+                throw new IllegalArgumentException(String.format("%s [%s]的值[%s]不在允许的选项范围内",
+                        rowDisplay, fieldName, String.join(", ", invalidValues)));
             }
         } catch (IllegalArgumentException e) {
             throw e;
         } catch (Exception e) {
-            throw new IllegalArgumentException(String.format("行[%s] [%s]选项校验异常:%s",
-                    rowid, fieldName, e.getMessage()));
+            throw new IllegalArgumentException(String.format("%s [%s]选项校验异常:%s",
+                    rowDisplay, fieldName, e.getMessage()));
         }
     }
 
     /**
-     * 字段类型校验
+     * 根据日期格式字符串生成正则表达式
+     */
+    private String getDateRegexByFormat(String format) {
+        if (StringUtil.isEmpty(format)) {
+            return null;
+        }
+
+        switch (format) {
+            case "yyyy-MM-dd":
+                return "\\d{4}-\\d{2}-\\d{2}";
+            case "yyyy-MM-dd HH:mm:ss":
+                return "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}";
+            case "yyyy/MM/dd":
+                return "\\d{4}/\\d{2}/\\d{2}";
+            case "yyyy/MM/dd HH:mm:ss":
+                return "\\d{4}/\\d{2}/\\d{2} \\d{2}:\\d{2}:\\d{2}";
+            case "yyyy年MM月dd日":
+                return "\\d{4}年\\d{2}月\\d{2}日";
+            case "yyyyMMdd":
+                return "\\d{8}";
+            default:
+                // 对于其他格式,尝试通用转换
+                return format
+                    .replace("yyyy", "\\\\d{4}")
+                    .replace("MM", "\\\\d{2}")
+                    .replace("dd", "\\\\d{2}")
+                    .replace("HH", "\\\\d{2}")
+                    .replace("mm", "\\\\d{2}")
+                    .replace("ss", "\\\\d{2}");
+        }
+    }
+
+    /**
+     * 字段类型和长度校验(合并)
      */
     private void validateFieldType(String rowDisplay, String fieldEname, String fieldName,
-                                   String value, String fieldType) {
+                                   String value, String fieldType, Integer fieldTypelen, Integer fieldTypenointlen, String format) {
         try {
             switch (fieldType.toLowerCase()) {
                 case "int":
                 case "integer":
-                case "bigint":
                     try {
                         Long.parseLong(value);
+
+                        // 校验整数位数(必须精确匹配)
+                        if (fieldTypelen != null && fieldTypelen > 0) {
+                            String absValue = value.replace("-", "").replace("+", ""); // 去除正负号
+                            if (absValue.length() != fieldTypelen) {
+                                throw new IllegalArgumentException(
+                                    String.format("%s [%s]必须是%d位整数,实际值:%s",
+                                        rowDisplay, fieldName, fieldTypelen, value));
+                            }
+                        }
                     } catch (NumberFormatException e) {
                         throw new IllegalArgumentException(String.format("%s [%s]应为整数,实际值:%s", rowDisplay, fieldName, value));
                     }
                     break;
 
-                case "decimal":
                 case "double":
-                case "zi":
-                case "number":
                     try {
                         Double.parseDouble(value);
+
+                        // 数字精度校验
+                        String[] parts = value.split("\\.");
+
+                        // 整数部分长度
+                        int intPartLen = parts[0].replace("-", "").replace("+", "").length();
+                        if (fieldTypenointlen != null && fieldTypelen != null && intPartLen > (fieldTypelen - fieldTypenointlen)) {
+                            throw new IllegalArgumentException(String.format("%s [%s]整数部分长度超限,最大:%d,实际:%d",
+                                    rowDisplay, fieldName, (fieldTypelen - fieldTypenointlen), intPartLen));
+                        }
+
+                        // 小数部分长度(必须精确匹配)
+                        if (fieldTypenointlen != null && fieldTypenointlen > 0) {
+                            if (parts.length == 1) {
+                                // 没有小数部分,但要求有小数位
+                                throw new IllegalArgumentException(
+                                    String.format("%s [%s]必须包含%d位小数,实际值:%s",
+                                        rowDisplay, fieldName, fieldTypenointlen, value));
+                            } else {
+                                int decimalPartLen = parts[1].length();
+                                // 改为精确匹配,而不是"不超过"
+                                if (decimalPartLen != fieldTypenointlen) {
+                                    throw new IllegalArgumentException(
+                                        String.format("%s [%s]必须是%d位小数,实际:%d位",
+                                            rowDisplay, fieldName, fieldTypenointlen, decimalPartLen));
+                                }
+                            }
+                        }
                     } catch (NumberFormatException e) {
                         throw new IllegalArgumentException(String.format("%s [%s]应为数字,实际值:%s", rowDisplay, fieldName, value));
                     }
                     break;
 
-                case "date":
-                    // 日期格式校验(可以根据实际需求调整)
-                    if (!value.matches("\\d{4}-\\d{2}-\\d{2}")) {
-                        throw new IllegalArgumentException(String.format("%s [%s]日期格式应为yyyy-MM-dd,实际值:%s", rowDisplay, fieldName, value));
+                case "varchar":
+                case "string":
+                case "text":
+                    // 字符串长度校验(使用 format 字段)
+                    if (StringUtil.isNotEmpty(format)) {
+                        try {
+                            int maxLength = Integer.parseInt(format);
+                            if (value.length() > maxLength) {
+                                throw new IllegalArgumentException(String.format("%s [%s]长度超限,最大长度:%d,实际长度:%d",
+                                        rowDisplay, fieldName, maxLength, value.length()));
+                            }
+                        } catch (NumberFormatException e) {
+                            // format 不是数字,跳过长度校验
+                        }
                     }
                     break;
 
+                case "date":
                 case "datetime":
-                    // 日期时间格式校验 - 支持两种格式
-                    if (!value.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}") && !value.matches("\\d{4}-\\d{2}-\\d{2}")) {
-                        throw new IllegalArgumentException(String.format("%s [%s]日期格式应为yyyy-MM-dd或yyyy-MM-dd HH:mm:ss,实际值:%s", rowDisplay, fieldName, value));
+                    // 日期格式校验(使用 format 字段)
+                    if (StringUtil.isNotEmpty(format)) {
+                        String regex = getDateRegexByFormat(format);
+                        if (StringUtil.isNotEmpty(regex) && !value.matches(regex)) {
+                            throw new IllegalArgumentException(String.format("%s [%s]日期格式应为%s,实际值:%s",
+                                rowDisplay, fieldName, format, value));
+                        }
+                    } else {
+                        // 如果没有 format,使用默认校验
+                        if ("date".equals(fieldType.toLowerCase())) {
+                            if (!value.matches("\\d{4}-\\d{2}-\\d{2}")) {
+                                throw new IllegalArgumentException(String.format("%s [%s]日期格式应为yyyy-MM-dd,实际值:%s",
+                                    rowDisplay, fieldName, value));
+                            }
+                        } else {
+                            if (!value.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}") && !value.matches("\\d{4}-\\d{2}-\\d{2}")) {
+                                throw new IllegalArgumentException(String.format("%s [%s]日期格式应为yyyy-MM-dd或yyyy-MM-dd HH:mm:ss,实际值:%s",
+                                    rowDisplay, fieldName, value));
+                            }
+                        }
                     }
                     break;
 
@@ -1975,11 +2023,6 @@ public class CostProjectTaskSurveyGenericController {
                     }
                     break;
 
-                case "varchar":
-                case "string":
-                case "text":
-                    // 字符串类型,无需特殊校验
-                    break;
                 default:
                     // 未知类型,不校验
                     break;
@@ -1991,61 +2034,6 @@ public class CostProjectTaskSurveyGenericController {
         }
     }
 
-    /**
-     * 字段长度校验
-     */
-    private void validateFieldLength(String rowid, String fieldEname, String fieldName, String value,
-                                     String fieldType, Integer fieldTypelen, Integer fieldTypenointlen) {
-        try {
-            switch (fieldType.toLowerCase()) {
-                case "varchar":
-                case "string":
-                case "text":
-                    // 字符串长度校验
-                    if (value.length() > fieldTypelen) {
-                        throw new IllegalArgumentException(String.format("行[%s] [%s]长度超限,最大长度:%d,实际长度:%d",
-                                rowid, fieldName, fieldTypelen, value.length()));
-                    }
-                    break;
-
-                case "decimal":
-                case "double":
-                case "float":
-                case "number":
-                    // 数字精度校验
-                    try {
-                        double numValue = Double.parseDouble(value);
-                        String[] parts = value.split("\\.");
-
-                        // 整数部分长度
-                        int intPartLen = parts[0].replace("-", "").length();
-                        if (fieldTypenointlen != null && intPartLen > (fieldTypelen - fieldTypenointlen)) {
-                            throw new IllegalArgumentException(String.format("行[%s] 字段[%s(%s)]整数部分长度超限,最大:%d,实际:%d",
-                                    rowid, fieldEname, fieldName, (fieldTypelen - fieldTypenointlen), intPartLen));
-                        }
-
-                        // 小数部分长度
-                        if (parts.length > 1 && fieldTypenointlen != null) {
-                            int decimalPartLen = parts[1].length();
-                            if (decimalPartLen > fieldTypenointlen) {
-                                throw new IllegalArgumentException(String.format("行[%s] 字段[%s(%s)]小数位数超限,最大:%d,实际:%d",
-                                        rowid, fieldEname, fieldName, fieldTypenointlen, decimalPartLen));
-                            }
-                        }
-                    } catch (NumberFormatException e) {
-                        // 类型校验已经处理过了,这里不重复报错
-                    }
-                    break;
-
-                default:
-                    break;
-            }
-        } catch (IllegalArgumentException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new IllegalArgumentException(String.format("行[%s] 字段[%s(%s)]长度校验异常:%s", rowid, fieldEname, fieldName, e.getMessage()));
-        }
-    }
 
     /**
      * 获取表头的字段英文名
@@ -2160,6 +2148,20 @@ public class CostProjectTaskSurveyGenericController {
     }
 
     /**
+     * 获取表头的格式
+     */
+    private String getHeaderFormat(Object header) {
+        if (header instanceof CostSurveyTemplateHeaders) {
+            return ((CostSurveyTemplateHeaders) header).getFormat();
+        } else if (header instanceof CostSurveyFdTemplateHeaders) {
+            return ((CostSurveyFdTemplateHeaders) header).getFormat();
+        } else if (header instanceof CostVerifyTemplateHeaders) {
+            return ((CostVerifyTemplateHeaders) header).getFormat();
+        }
+        return null;
+    }
+
+    /**
      * 根据类型获取模板项
      */
     private List<? extends Object> getTemplateItems(String type, String surveyTemplateId) {
@@ -2803,19 +2805,29 @@ public class CostProjectTaskSurveyGenericController {
             }
         }
 
-        // 找出所有根节点(parentid为空或不存在的)
+        // 找出所有根节点(parentid为空或不存在的),按 orderNum 排序
         Set<String> allRowIds = new HashSet<>(itemsByRowId.keySet());
         List<String> rootRowIds = allRowIds.stream()
                 .filter(rowId -> {
                     String parentId = rowIdToParentId.get(rowId);
                     return StringUtil.isEmpty(parentId) || !allRowIds.contains(parentId);
                 })
-                .sorted()
+                .sorted((id1, id2) -> {
+                    List<CostVerifyTemplateItems> items1 = itemsByRowId.get(id1);
+                    List<CostVerifyTemplateItems> items2 = itemsByRowId.get(id2);
+                    if (items1 == null || items1.isEmpty()) return 1;
+                    if (items2 == null || items2.isEmpty()) return -1;
+                    Integer order1 = items1.get(0).getOrderNum();
+                    Integer order2 = items2.get(0).getOrderNum();
+                    if (order1 == null) return 1;
+                    if (order2 == null) return -1;
+                    return order1.compareTo(order2);
+                })
                 .collect(Collectors.toList());
 
         // 递归添加节点及其子节点
         for (String rootRowId : rootRowIds) {
-            addRowIdWithChildrenVerify(rootRowId, rowIdToParentId, allRowIds, result);
+            addRowIdWithChildrenVerify(rootRowId, rowIdToParentId, allRowIds, result, itemsByRowId);
         }
 
         return result;
@@ -2825,18 +2837,28 @@ public class CostProjectTaskSurveyGenericController {
      * 递归添加rowId及其所有子节点(核定表)
      */
     private void addRowIdWithChildrenVerify(String rowId, Map<String, String> rowIdToParentId,
-                                           Set<String> allRowIds, List<String> result) {
+                                           Set<String> allRowIds, List<String> result, Map<String, List<CostVerifyTemplateItems>> itemsByRowId) {
         result.add(rowId);
 
-        // 找出所有子节点
+        // 找出所有子节点,按 orderNum 排序
         List<String> children = allRowIds.stream()
                 .filter(id -> rowId.equals(rowIdToParentId.get(id)))
-                .sorted()
+                .sorted((id1, id2) -> {
+                    List<CostVerifyTemplateItems> items1 = itemsByRowId.get(id1);
+                    List<CostVerifyTemplateItems> items2 = itemsByRowId.get(id2);
+                    if (items1 == null || items1.isEmpty()) return 1;
+                    if (items2 == null || items2.isEmpty()) return -1;
+                    Integer order1 = items1.get(0).getOrderNum();
+                    Integer order2 = items2.get(0).getOrderNum();
+                    if (order1 == null) return 1;
+                    if (order2 == null) return -1;
+                    return order1.compareTo(order2);
+                })
                 .collect(Collectors.toList());
 
         // 递归处理子节点
         for (String childRowId : children) {
-            addRowIdWithChildrenVerify(childRowId, rowIdToParentId, allRowIds, result);
+            addRowIdWithChildrenVerify(childRowId, rowIdToParentId, allRowIds, result, itemsByRowId);
         }
     }
 

+ 3 - 1
assistMg/src/main/java/com/hotent/enterpriseDeclare/req/CostTaskPageReq.java

@@ -13,11 +13,13 @@ public class CostTaskPageReq {
 
     private String processNodeKey;
 
-    @ApiModelProperty(value = "按钮操作key:1-补充材料、2-中止、3-退回上一步、4-退回、5-归档、6-退回上一步、7-扭转下一步")
+    @ApiModelProperty(value = "按钮操作key:1-补充材料、2-中止、3-退回上一步、4-退回、5-催办、6-退回上一步、7-扭转下一步,10-催报,8-复核")
     private String key;
 
     private String userIds;
+
     private String auditedUnitIds;
+
     private String userIdNames;
 
     @ApiModelProperty(value = "发送方式 1站内消息 2短信通知 “,”分割")

+ 2 - 2
assistMg/src/main/java/com/hotent/project/dao/CostProjectTaskMaterialDetailDao.java → assistMg/src/main/java/com/hotent/project/dao/CostProjectTaskMaterialSummaryDetailDao.java

@@ -1,7 +1,7 @@
 package com.hotent.project.dao;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.hotent.project.model.CostProjectTaskMaterialDetail;
+import com.hotent.project.model.CostProjectTaskMaterialSummaryDetail;
 import org.apache.ibatis.annotations.Mapper;
 
 /**
@@ -12,7 +12,7 @@ import org.apache.ibatis.annotations.Mapper;
  * @since 2025-01-27
  */
 @Mapper
-public interface CostProjectTaskMaterialDetailDao extends BaseMapper<CostProjectTaskMaterialDetail> {
+public interface CostProjectTaskMaterialSummaryDetailDao extends BaseMapper<CostProjectTaskMaterialSummaryDetail> {
 
 }
 

+ 3 - 3
assistMg/src/main/java/com/hotent/project/manager/CostProjectTaskMaterialDetailManager.java → assistMg/src/main/java/com/hotent/project/manager/CostProjectTaskMaterialSummaryDetailManager.java

@@ -1,6 +1,6 @@
 package com.hotent.project.manager;
 
-import com.hotent.project.model.CostProjectTaskMaterialDetail;
+import com.hotent.project.model.CostProjectTaskMaterialSummaryDetail;
 import com.hotent.base.manager.BaseManager;
 
 /**
@@ -10,14 +10,14 @@ import com.hotent.base.manager.BaseManager;
  * @author 超级管理员
  * @since 2025-01-27
  */
-public interface CostProjectTaskMaterialDetailManager extends BaseManager<CostProjectTaskMaterialDetail> {
+public interface CostProjectTaskMaterialSummaryDetailManager extends BaseManager<CostProjectTaskMaterialSummaryDetail> {
 
     /**
      * 根据id查询明细详情
      * @param id 明细ID
      * @return 明细详情
      */
-    CostProjectTaskMaterialDetail getDetail(String id);
+    CostProjectTaskMaterialSummaryDetail getDetail(String id);
 
     /**
      * 删除明细(逻辑删除)

+ 8 - 1
assistMg/src/main/java/com/hotent/project/manager/impl/CostProjectApprovalManagerImpl.java

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.hotent.base.exception.BaseException;
 import com.hotent.base.query.PageList;
+import com.hotent.base.util.AuthenticationUtil;
 import com.hotent.base.util.StringUtil;
 import com.hotent.baseInfo.manager.*;
 import com.hotent.baseInfo.model.*;
@@ -26,6 +27,7 @@ import com.hotent.surveyinfo.model.*;
 import com.hotent.uc.api.model.IUser;
 import com.hotent.uc.manager.OrgManager;
 import com.hotent.uc.manager.UserManager;
+import com.hotent.uc.model.Org;
 import com.hotent.uc.model.User;
 import com.hotent.uc.util.ContextUtil;
 import com.hotent.util.AreaCodeUtil;
@@ -39,6 +41,8 @@ import java.time.LocalDateTime;
 import java.util.*;
 import java.util.stream.Collectors;
 
+import static com.hotent.base.util.AuthenticationUtil.getCurrentUserMainOrgId;
+
 
 /**
  * 成本监审立项主表 服务实现类
@@ -486,6 +490,9 @@ public class CostProjectApprovalManagerImpl extends BaseManagerImpl<CostProjectA
             if (StringUtil.isNotEmpty(req.getContent())) {
                 content += "," + req.getContent();
             }
+            String orgId = getCurrentUserMainOrgId();
+            Org org = orgManager.getById(orgId);
+            String noticeSource = (org != null ? org.getName() : "系统") + " " + AuthenticationUtil.getCurrentUserFullname();
             // 站内通知(绑定子任务ID)
             this.costNoticeManager.sendNotice(
                     req.getProjectId(),
@@ -494,7 +501,7 @@ public class CostProjectApprovalManagerImpl extends BaseManagerImpl<CostProjectA
                     title,
                     content,
                     childTask.getAuditedUnitId(),
-                    "系统",
+                    noticeSource,
                     sendTarget
             );
 

+ 1 - 0
assistMg/src/main/java/com/hotent/project/manager/impl/CostProjectMaterialManagerImpl.java

@@ -88,6 +88,7 @@ public class CostProjectMaterialManagerImpl extends BaseManagerImpl<CostProjectM
     public IPage<CostProjectMaterial> pageList(CostProjectBasePageReq req) {
         LambdaQueryWrapper<CostProjectMaterial> qw = new LambdaQueryWrapper<>();
         qw.eq(CostProjectMaterial::getProjectId, req.getProjectId());
+        qw.orderByAsc(CostProjectMaterial::getOrderNum);
         IPage<CostProjectMaterial> page = new Page<>(req.getPageNum(), req.getPageSize());
         IPage<CostProjectMaterial> pageList = this.page(page, qw);
 

+ 85 - 14
assistMg/src/main/java/com/hotent/project/manager/impl/CostProjectTaskManagerImpl.java

@@ -32,6 +32,7 @@ import com.hotent.uc.model.Org;
 import com.hotent.uc.model.User;
 import com.hotent.uc.util.ContextUtil;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -97,6 +98,12 @@ public class CostProjectTaskManagerImpl extends BaseManagerImpl<CostProjectTaskD
     @Autowired
     private AuditedUnitManager auditedUnitManager;
 
+    @Autowired
+    private CostProjectTaskMaterialSummaryManager costProjectTaskMaterialSummaryManager;
+
+    @Autowired
+    private com.hotent.project.service.AsyncMaterialSummaryService asyncMaterialSummaryService;
+
 
     @Override
     public List<CostProjectTask> getTaskList(CostTaskSearchReq req) throws Exception {
@@ -436,14 +443,19 @@ public class CostProjectTaskManagerImpl extends BaseManagerImpl<CostProjectTaskD
                 // 扭转下一步
                 resultMessage = toNextSubmit(task, req);
                 break;
+            case "8":
+                // 复核
+                resultMessage = reviewTask(task, req);
+                break;
             case "10":
                 // 催报
                 resultMessage = remindUnitTask(task, req);
                 break;
-            case "8":
-                // 复核
-                resultMessage = reviewTask(task, req);
+            case "9":
+                // 归档
+                resultMessage = archiveTask(task, req);
                 break;
+
             default:
                 return "未知的操作类型";
         }
@@ -578,24 +590,25 @@ public class CostProjectTaskManagerImpl extends BaseManagerImpl<CostProjectTaskD
         String orgId = getCurrentUserMainOrgId();
         Org org = orgManager.getById(orgId);
         String noticeSource = (org.getName()) + " " + AuthenticationUtil.getCurrentUserFullname();
-        if ("0".equals(task.getPid())) {
-            return "仅支持子任务进行此操作!";
-        }
         switch (currentNode) {
             // 集体审议节点:支持指定多个子任务补充材料
             case "jtsy":
-                if (StringUtil.isNotEmpty(req.getChildTaskId())) {
-                    String[] childTaskIdArray = req.getChildTaskId().split(",");
+                if (StringUtil.isNotEmpty(req.getAuditedUnitIds())) {
+                    String[] childUnitIds = req.getAuditedUnitIds().split(",");
                     List<String> unitNames = new java.util.ArrayList<>();
 
-                    for (String childTaskId : childTaskIdArray) {
-                        CostProjectTask childTask = costProjectTaskManager.getById(childTaskId);
+                    for (String auditedUnitId : childUnitIds) {
+                        CostProjectTask childTask = costProjectTaskManager.getOne(
+                                new LambdaQueryWrapper<CostProjectTask>()
+                                        .eq(CostProjectTask::getPid, task.getId())
+                                        .eq(CostProjectTask::getAuditedUnitId, auditedUnitId)
+                        );
                         if (childTask == null) {
-                            throw new RuntimeException("子任务不存在:" + childTaskId);
+                            throw new RuntimeException("未知的子任务");
                         }
-                        // 验证子任务是否属于当前主任务
-                        if (!childTask.getPid().equals(task.getId())) {
-                            throw new RuntimeException("子任务不属于当前主任务:" + childTaskId);
+                        // 检查子任务是否已中止
+                        if (TaskStatusConstant.SUSPENDED.getStatusCode().equals(childTask.getStatus())) {
+                            throw new RuntimeException("子任务已中止,无法补充材料:" + childTask.getAuditedUnitName());
                         }
                         // 更新子任务状态为补充材料
                         childTask.setStatus(TaskStatusConstant.BCCL.getStatusCode());
@@ -615,6 +628,10 @@ public class CostProjectTaskManagerImpl extends BaseManagerImpl<CostProjectTaskD
                     return title + "(单位:" + unitNamesStr + ")";
                 }
             default:
+                // 检查任务是否已中止
+                if (TaskStatusConstant.SUSPENDED.getStatusCode().equals(task.getStatus())) {
+                    throw new RuntimeException("任务已中止,无法补充材料");
+                }
                 task.setStatus(TaskStatusConstant.BCCL.getStatusCode());
                 costProjectTaskManager.updateById(task);
                 String content = "[" + NodeConstant.getNodeValueByKey(task.getCurrentNode()) + "]" + AuthenticationUtil.getCurrentUserFullname() + "要求" + task.getAuditedUnitName() + "补充材料,项目(" + task.getProjectName() + ")";
@@ -1345,4 +1362,58 @@ public class CostProjectTaskManagerImpl extends BaseManagerImpl<CostProjectTaskD
         return title;
     }
 
+    /**
+     * 归档任务,修改任务归档状态并异步生成资料归纳主表
+     *
+     * @param task 任务对象
+     * @param req  请求参数
+     * @return 归档结果消息
+     */
+    private String archiveTask(CostProjectTask task, CostTaskPageReq req) {
+        CostProjectTask nTask = costProjectTaskManager.getById(task.getId());
+        if (!"0".equals(task.getPid())) {
+            return "仅支持主任务进行归档操作";
+        }
+
+        // 检查任务是否已办结
+        if (!TaskStatusConstant.COMPLETED.getStatusCode().equals(nTask.getStatus())) {
+            throw new RuntimeException("任务未办结,无法归档");
+        }
+
+        // 检查所有子任务是否都已办结
+        List<CostProjectTask> children = costProjectTaskManager.list(
+                new LambdaQueryWrapper<CostProjectTask>()
+                        .eq(CostProjectTask::getPid, nTask.getId())
+                        .eq(CostProjectTask::getIsDeleted, "0")
+        );
+        boolean allChildrenCompleted = children.stream()
+                .filter(child -> !TaskStatusConstant.SUSPENDED.getStatusCode().equals(child.getStatus()))
+                .allMatch(child -> TaskStatusConstant.COMPLETED.getStatusCode().equals(child.getStatus()));
+        if (!allChildrenCompleted) {
+            throw new RuntimeException("子任务未全部办结,主任务无法归档");
+        }
+
+        // 更新主任务归档状态
+        nTask.setIsGd("1");
+        costProjectTaskManager.updateById(nTask);
+
+        // 异步生成资料归纳主表
+        asyncMaterialSummaryService.generateMaterialSummaryAsync(nTask, children);
+
+        // 发送通知
+        String title = "任务已归档";
+        String content = "[" + NodeConstant.getNodeValueByKey(task.getCurrentNode()) + "]" + AuthenticationUtil.getCurrentUserFullname() + "已归档" + task.getProjectName();
+        if (StringUtil.isNotEmpty(req.getContent())) {
+            content += ",备注:" + req.getContent();
+        }
+        String enterpriseId = task.getAuditedUnitId() == null ? "" : task.getAuditedUnitId();
+        String orgId = getCurrentUserMainOrgId();
+        Org org = orgManager.getById(orgId);
+        String noticeSource = (org.getName()) + " " + AuthenticationUtil.getCurrentUserFullname();
+        String sendTarget = task.getCreateBy() == null ? "" : task.getCreateBy();
+        costNoticeManager.sendNotice(task.getProjectId(), task.getId(), "1", title, content, enterpriseId, noticeSource, sendTarget);
+
+        return title;
+    }
+
 }

+ 7 - 6
assistMg/src/main/java/com/hotent/project/manager/impl/CostProjectTaskMaterialDetailManagerImpl.java → assistMg/src/main/java/com/hotent/project/manager/impl/CostProjectTaskMaterialSummaryDetailManagerImpl.java

@@ -2,9 +2,9 @@ package com.hotent.project.manager.impl;
 
 import com.hotent.base.manager.impl.BaseManagerImpl;
 import com.hotent.base.util.StringUtil;
-import com.hotent.project.dao.CostProjectTaskMaterialDetailDao;
-import com.hotent.project.manager.CostProjectTaskMaterialDetailManager;
-import com.hotent.project.model.CostProjectTaskMaterialDetail;
+import com.hotent.project.dao.CostProjectTaskMaterialSummaryDetailDao;
+import com.hotent.project.manager.CostProjectTaskMaterialSummaryDetailManager;
+import com.hotent.project.model.CostProjectTaskMaterialSummaryDetail;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -16,15 +16,16 @@ import org.springframework.transaction.annotation.Transactional;
  * @since 2025-01-27
  */
 @Service
-public class CostProjectTaskMaterialDetailManagerImpl extends BaseManagerImpl<CostProjectTaskMaterialDetailDao, CostProjectTaskMaterialDetail> implements CostProjectTaskMaterialDetailManager {
+public class CostProjectTaskMaterialSummaryDetailManagerImpl
+        extends BaseManagerImpl<CostProjectTaskMaterialSummaryDetailDao, CostProjectTaskMaterialSummaryDetail> implements CostProjectTaskMaterialSummaryDetailManager {
 
     @Override
-    public CostProjectTaskMaterialDetail getDetail(String id) {
+    public CostProjectTaskMaterialSummaryDetail getDetail(String id) {
         if (StringUtil.isEmpty(id)) {
             return null;
         }
 
-        CostProjectTaskMaterialDetail detail = this.getById(id);
+        CostProjectTaskMaterialSummaryDetail detail = this.getById(id);
         if (detail == null) {
             return null;
         }

+ 31 - 31
assistMg/src/main/java/com/hotent/project/manager/impl/CostProjectTaskMaterialSummaryManagerImpl.java

@@ -4,9 +4,9 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.hotent.base.manager.impl.BaseManagerImpl;
 import com.hotent.base.util.StringUtil;
 import com.hotent.project.dao.CostProjectTaskMaterialSummaryDao;
-import com.hotent.project.manager.CostProjectTaskMaterialDetailManager;
+import com.hotent.project.manager.CostProjectTaskMaterialSummaryDetailManager;
 import com.hotent.project.manager.CostProjectTaskMaterialSummaryManager;
-import com.hotent.project.model.CostProjectTaskMaterialDetail;
+import com.hotent.project.model.CostProjectTaskMaterialSummaryDetail;
 import com.hotent.project.model.CostProjectTaskMaterialSummary;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -26,7 +26,7 @@ import java.util.stream.Collectors;
 public class CostProjectTaskMaterialSummaryManagerImpl extends BaseManagerImpl<CostProjectTaskMaterialSummaryDao, CostProjectTaskMaterialSummary> implements CostProjectTaskMaterialSummaryManager {
 
     @Autowired
-    private CostProjectTaskMaterialDetailManager costProjectTaskMaterialDetailManager;
+    private CostProjectTaskMaterialSummaryDetailManager costProjectTaskMaterialSummaryDetailManager;
 
     @Override
     public List<CostProjectTaskMaterialSummary> listByTaskId(String taskId) {
@@ -77,19 +77,19 @@ public class CostProjectTaskMaterialSummaryManagerImpl extends BaseManagerImpl<C
                 .collect(Collectors.toList());
 
         // 批量查询明细
-        LambdaQueryWrapper<CostProjectTaskMaterialDetail> detailQueryWrapper = new LambdaQueryWrapper<>();
-        detailQueryWrapper.in(CostProjectTaskMaterialDetail::getMasterId, masterIds)
-                .orderByAsc(CostProjectTaskMaterialDetail::getOrderNum);
+        LambdaQueryWrapper<CostProjectTaskMaterialSummaryDetail> detailQueryWrapper = new LambdaQueryWrapper<>();
+        detailQueryWrapper.in(CostProjectTaskMaterialSummaryDetail::getMasterId, masterIds)
+                .orderByAsc(CostProjectTaskMaterialSummaryDetail::getOrderNum);
 
-        List<CostProjectTaskMaterialDetail> allDetails = costProjectTaskMaterialDetailManager.list(detailQueryWrapper);
+        List<CostProjectTaskMaterialSummaryDetail> allDetails = costProjectTaskMaterialSummaryDetailManager.list(detailQueryWrapper);
 
         // 按masterId分组
-        java.util.Map<String, List<CostProjectTaskMaterialDetail>> detailMap = allDetails.stream()
-                .collect(Collectors.groupingBy(CostProjectTaskMaterialDetail::getMasterId));
+        java.util.Map<String, List<CostProjectTaskMaterialSummaryDetail>> detailMap = allDetails.stream()
+                .collect(Collectors.groupingBy(CostProjectTaskMaterialSummaryDetail::getMasterId));
 
         // 设置到对应的主表
         for (CostProjectTaskMaterialSummary summary : summaryList) {
-            List<CostProjectTaskMaterialDetail> detailList = detailMap.getOrDefault(summary.getId(), java.util.Collections.emptyList());
+            List<CostProjectTaskMaterialSummaryDetail> detailList = detailMap.getOrDefault(summary.getId(), java.util.Collections.emptyList());
             summary.setDetailList(detailList);
         }
     }
@@ -127,43 +127,43 @@ public class CostProjectTaskMaterialSummaryManagerImpl extends BaseManagerImpl<C
 
         // 查询数据库中已存在的明细
         if (!isNew && !incomingDetailIds.isEmpty()) {
-            LambdaQueryWrapper<CostProjectTaskMaterialDetail> existingQueryWrapper = new LambdaQueryWrapper<>();
-            existingQueryWrapper.eq(CostProjectTaskMaterialDetail::getMasterId, masterId);
-            List<CostProjectTaskMaterialDetail> existingDetails = costProjectTaskMaterialDetailManager.list(existingQueryWrapper);
+            LambdaQueryWrapper<CostProjectTaskMaterialSummaryDetail> existingQueryWrapper = new LambdaQueryWrapper<>();
+            existingQueryWrapper.eq(CostProjectTaskMaterialSummaryDetail::getMasterId, masterId);
+            List<CostProjectTaskMaterialSummaryDetail> existingDetails = costProjectTaskMaterialSummaryDetailManager.list(existingQueryWrapper);
 
             // 物理删除不在传入列表中的明细
-            for (CostProjectTaskMaterialDetail existing : existingDetails) {
+            for (CostProjectTaskMaterialSummaryDetail existing : existingDetails) {
                 if (!incomingDetailIds.contains(existing.getId())) {
-                    costProjectTaskMaterialDetailManager.removeById(existing.getId());
+                    costProjectTaskMaterialSummaryDetailManager.removeById(existing.getId());
                 }
             }
         } else if (!isNew && (summary.getDetailList() == null || summary.getDetailList().isEmpty())) {
             // 如果传入的明细列表为空,物理删除所有明细
-            LambdaQueryWrapper<CostProjectTaskMaterialDetail> deleteQueryWrapper = new LambdaQueryWrapper<>();
-            deleteQueryWrapper.eq(CostProjectTaskMaterialDetail::getMasterId, masterId);
-            List<CostProjectTaskMaterialDetail> allDetails = costProjectTaskMaterialDetailManager.list(deleteQueryWrapper);
+            LambdaQueryWrapper<CostProjectTaskMaterialSummaryDetail> deleteQueryWrapper = new LambdaQueryWrapper<>();
+            deleteQueryWrapper.eq(CostProjectTaskMaterialSummaryDetail::getMasterId, masterId);
+            List<CostProjectTaskMaterialSummaryDetail> allDetails = costProjectTaskMaterialSummaryDetailManager.list(deleteQueryWrapper);
             List<String> detailIds = allDetails.stream()
-                    .map(CostProjectTaskMaterialDetail::getId)
+                    .map(CostProjectTaskMaterialSummaryDetail::getId)
                     .collect(Collectors.toList());
             if (!detailIds.isEmpty()) {
-                costProjectTaskMaterialDetailManager.removeByIds(detailIds);
+                costProjectTaskMaterialSummaryDetailManager.removeByIds(detailIds);
             }
         }
 
         // 3. 保存或更新明细列表
         if (summary.getDetailList() != null && !summary.getDetailList().isEmpty()) {
-            for (CostProjectTaskMaterialDetail detail : summary.getDetailList()) {
+            for (CostProjectTaskMaterialSummaryDetail detail : summary.getDetailList()) {
                 detail.setMasterId(masterId);
                 detail.setTaskId(summary.getTaskId());
 
                 // 如果明细没有id且没有orderNum,设置orderNum(如果是新增)
                 if (StringUtil.isEmpty(detail.getId()) && detail.getOrderNum() == null) {
                     // 查询当前主表下最大的orderNum
-                    LambdaQueryWrapper<CostProjectTaskMaterialDetail> queryWrapper = new LambdaQueryWrapper<>();
-                    queryWrapper.eq(CostProjectTaskMaterialDetail::getMasterId, masterId)
-                            .orderByDesc(CostProjectTaskMaterialDetail::getOrderNum)
+                    LambdaQueryWrapper<CostProjectTaskMaterialSummaryDetail> queryWrapper = new LambdaQueryWrapper<>();
+                    queryWrapper.eq(CostProjectTaskMaterialSummaryDetail::getMasterId, masterId)
+                            .orderByDesc(CostProjectTaskMaterialSummaryDetail::getOrderNum)
                             .last("LIMIT 1");
-                    CostProjectTaskMaterialDetail lastDetail = costProjectTaskMaterialDetailManager.getOne(queryWrapper);
+                    CostProjectTaskMaterialSummaryDetail lastDetail = costProjectTaskMaterialSummaryDetailManager.getOne(queryWrapper);
                     int nextOrderNum = lastDetail != null && lastDetail.getOrderNum() != null ? lastDetail.getOrderNum() + 1 : 1;
                     detail.setOrderNum(nextOrderNum);
                 } else if (detail.getOrderNum() == null) {
@@ -171,7 +171,7 @@ public class CostProjectTaskMaterialSummaryManagerImpl extends BaseManagerImpl<C
                     detail.setOrderNum(1);
                 }
 
-                costProjectTaskMaterialDetailManager.saveOrUpdate(detail);
+                costProjectTaskMaterialSummaryDetailManager.saveOrUpdate(detail);
             }
         }
     }
@@ -184,14 +184,14 @@ public class CostProjectTaskMaterialSummaryManagerImpl extends BaseManagerImpl<C
         }
 
         // 1. 物理删除所有明细
-        LambdaQueryWrapper<CostProjectTaskMaterialDetail> detailQueryWrapper = new LambdaQueryWrapper<>();
-        detailQueryWrapper.eq(CostProjectTaskMaterialDetail::getMasterId, id);
-        List<CostProjectTaskMaterialDetail> detailList = costProjectTaskMaterialDetailManager.list(detailQueryWrapper);
+        LambdaQueryWrapper<CostProjectTaskMaterialSummaryDetail> detailQueryWrapper = new LambdaQueryWrapper<>();
+        detailQueryWrapper.eq(CostProjectTaskMaterialSummaryDetail::getMasterId, id);
+        List<CostProjectTaskMaterialSummaryDetail> detailList = costProjectTaskMaterialSummaryDetailManager.list(detailQueryWrapper);
         List<String> detailIds = detailList.stream()
-                .map(CostProjectTaskMaterialDetail::getId)
+                .map(CostProjectTaskMaterialSummaryDetail::getId)
                 .collect(Collectors.toList());
         if (!detailIds.isEmpty()) {
-            costProjectTaskMaterialDetailManager.removeByIds(detailIds);
+            costProjectTaskMaterialSummaryDetailManager.removeByIds(detailIds);
         }
 
         // 2. 物理删除主表

+ 1 - 1
assistMg/src/main/java/com/hotent/project/model/CostProjectTaskMaterial.java

@@ -123,7 +123,7 @@ public class CostProjectTaskMaterial extends BaseModel<CostProjectTaskMaterial>
     @TableField("file_url")
     private String fileUrl;
 
-    @ApiModelProperty(value = "文件地址")
+    @ApiModelProperty(value = "企业单位")
     @JsonProperty("auditedUnitId")
     @TableField("audited_unit_id")
     private String auditedUnitId;

+ 1 - 1
assistMg/src/main/java/com/hotent/project/model/CostProjectTaskMaterialSummary.java

@@ -74,7 +74,7 @@ public class CostProjectTaskMaterialSummary extends BaseModel<CostProjectTaskMat
     @ApiModelProperty(value = "明细列表")
     @TableField(exist = false)
     @JsonProperty("detailList")
-    private List<CostProjectTaskMaterialDetail> detailList;
+    private List<CostProjectTaskMaterialSummaryDetail> detailList;
 
 }
 

+ 5 - 6
assistMg/src/main/java/com/hotent/project/model/CostProjectTaskMaterialDetail.java → assistMg/src/main/java/com/hotent/project/model/CostProjectTaskMaterialSummaryDetail.java

@@ -11,7 +11,6 @@ import io.swagger.annotations.ApiModelProperty;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Data;
 
-import java.io.Serializable;
 import java.time.LocalDateTime;
 
 /**
@@ -23,7 +22,7 @@ import java.time.LocalDateTime;
  */
 @ApiModel(value="CostProjectTaskMaterialDetail对象", description="资料归纳明细表")
 @Data
-public class CostProjectTaskMaterialDetail extends BaseModel<CostProjectTaskMaterialDetail> {
+public class CostProjectTaskMaterialSummaryDetail extends BaseModel<CostProjectTaskMaterialSummaryDetail> {
 
     private static final long serialVersionUID = 1L;
 
@@ -72,10 +71,10 @@ public class CostProjectTaskMaterialDetail extends BaseModel<CostProjectTaskMate
     @JsonProperty("pageCount")
     private Integer pageCount;
 
-    @ApiModelProperty(value = "附件ID")
-    @TableField("attachment_id")
-    @JsonProperty("attachmentId")
-    private String attachmentId;
+    @ApiModelProperty(value = "附件地址")
+    @TableField("attachment_url")
+    @JsonProperty("attachmentUrl")
+    private String attachmentUrl;
 
     @ApiModelProperty(value = "序号")
     @TableField("order_num")

+ 335 - 0
assistMg/src/main/java/com/hotent/project/service/AsyncMaterialSummaryService.java

@@ -0,0 +1,335 @@
+package com.hotent.project.service;
+
+import com.hotent.base.util.StringUtil;
+import com.hotent.project.manager.CostProjectTaskMaterialSummaryDetailManager;
+import com.hotent.project.manager.CostProjectTaskMaterialSummaryManager;
+import com.hotent.project.model.CostProjectTask;
+import com.hotent.project.model.CostProjectTaskMaterialSummaryDetail;
+import com.hotent.project.model.CostProjectTaskMaterialSummary;
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
+import org.apache.poi.xwpf.usermodel.XWPFParagraph;
+import org.apache.poi.xwpf.usermodel.XWPFRun;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.io.FileOutputStream;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * 异步资料归纳服务
+ *
+ * @company 山西清众科技股份有限公司
+ * @author 超级管理员
+ * @since 2025-12-02
+ */
+@Service
+public class AsyncMaterialSummaryService {
+
+    @Autowired
+    private CostProjectTaskMaterialSummaryManager costProjectTaskMaterialSummaryManager;
+
+    @Autowired
+    private CostProjectTaskMaterialSummaryDetailManager costProjectTaskMaterialSummaryDetailManager;
+
+    /**
+     * 生成资料归纳主表(13个基础资料类别)
+     *
+     * @param mainTask 主任务
+     * @param childTasks 子任务列表
+     */
+    @Async
+    public void generateMaterialSummaryAsync(CostProjectTask mainTask, List<CostProjectTask> childTasks) {
+        try {
+            // 定义13个基础资料类别
+            String[] materialNames = {
+                    "成本监审报告(含成本监审报告签发稿、送达回证)",
+                    "被监审单位申请定(调)价报告(复印件)",
+                    "成本监审通知书(含送达回证)",
+                    "经营者需提供成本资料清单",
+                    "《政府定价成本监审调查表》",
+                    "成本监审补充资料通知书(含送达回证)",
+                    "成本审核初步意见告知书(含送达回证)",
+                    "经营者书面反馈的材料",
+                    "成本审核初步意见表(集体审议用)",
+                    "成本监审集体审议记录",
+                    "成本监审工作底稿",
+                    "成本监审提取资料登记表",
+                    "提取的成本资料和会计凭证等复印件"
+            };
+
+            // 为主任务生成13个资料归纳主表记录
+            for (int i = 0; i < materialNames.length; i++) {
+                CostProjectTaskMaterialSummary summary = new CostProjectTaskMaterialSummary();
+                summary.setTaskId(mainTask.getId());
+                summary.setMaterialName(materialNames[i]);
+                summary.setMaterialOrderNum(i + 1);
+                summary.setIsDeleted("0");
+                costProjectTaskMaterialSummaryManager.createOrUpdate(summary);
+
+                // 根据不同的资料类别生成对应的明细记录
+                generateDetailsByMaterialType(summary, mainTask, childTasks, i + 1);
+            }
+        } catch (Exception e) {
+            // 记录异常日志,但不影响主流程
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 根据资料类别生成对应的明细记录
+     *
+     * @param summary 资料归纳主表
+     * @param mainTask 主任务
+     * @param childTasks 子任务列表
+     * @param materialType 资料类别序号(1-13)
+     */
+    private void generateDetailsByMaterialType(CostProjectTaskMaterialSummary summary,
+                                               CostProjectTask mainTask,
+                                               List<CostProjectTask> childTasks,
+                                               int materialType) {
+        switch (materialType) {
+            case 1:
+                // 成本监审报告(含成本监审报告签发稿、送达回证)
+                generateDetail(summary, mainTask, "成本监审报告", 1);
+                generateDetail(summary, mainTask, "成本监审报告签发稿", 2);
+                generateDetail(summary, mainTask, "送达回证", 3);
+                break;
+            case 2:
+                // 被监审单位申请定(调)价报告(复印件)
+                for (int i = 0; i < childTasks.size(); i++) {
+                    CostProjectTask child = childTasks.get(i);
+                    generateDetail(summary, mainTask, child.getAuditedUnitName() + "申请定(调)价报告(复印件)", i + 1);
+                }
+                break;
+            case 3:
+                // 成本监审通知书(含送达回证)
+                for (int i = 0; i < childTasks.size(); i++) {
+                    CostProjectTask child = childTasks.get(i);
+                    generateDetail(summary, mainTask, child.getAuditedUnitName() + "成本监审通知书", i * 2 + 1);
+                    generateDetail(summary, mainTask, child.getAuditedUnitName() + "送达回证", i * 2 + 2);
+                }
+                break;
+            case 4:
+                // 经营者需提供成本资料清单
+                for (int i = 0; i < childTasks.size(); i++) {
+                    CostProjectTask child = childTasks.get(i);
+                    generateDetail(summary, mainTask, child.getAuditedUnitName() + "成本资料清单", i + 1);
+                }
+                break;
+            case 5:
+                // 《政府定价成本监审调查表》
+                for (int i = 0; i < childTasks.size(); i++) {
+                    CostProjectTask child = childTasks.get(i);
+                    generateDetail(summary, mainTask, child.getAuditedUnitName() + "《政府定价成本监审调查表》", i + 1);
+                }
+                break;
+            case 6:
+                // 成本监审补充资料通知书(含送达回证)
+                for (int i = 0; i < childTasks.size(); i++) {
+                    CostProjectTask child = childTasks.get(i);
+                    generateDetail(summary, mainTask, child.getAuditedUnitName() + "成本监审补充资料通知书", i * 2 + 1);
+                    generateDetail(summary, mainTask, child.getAuditedUnitName() + "送达回证", i * 2 + 2);
+                }
+                break;
+            case 7:
+                // 成本审核初步意见告知书(含送达回证)
+                for (int i = 0; i < childTasks.size(); i++) {
+                    CostProjectTask child = childTasks.get(i);
+                    generateDetail(summary, mainTask, child.getAuditedUnitName() + "成本审核初步意见告知书", i * 2 + 1);
+                    generateDetail(summary, mainTask, child.getAuditedUnitName() + "送达回证", i * 2 + 2);
+                }
+                break;
+            case 8:
+                // 经营者书面反馈的材料
+                for (int i = 0; i < childTasks.size(); i++) {
+                    CostProjectTask child = childTasks.get(i);
+                    generateDetail(summary, mainTask, child.getAuditedUnitName() + "书面反馈材料", i + 1);
+                }
+                break;
+            case 9:
+                // 成本审核初步意见表(集体审议用)
+                for (int i = 0; i < childTasks.size(); i++) {
+                    CostProjectTask child = childTasks.get(i);
+                    generateDetail(summary, mainTask, child.getAuditedUnitName() + "成本审核初步意见表", i + 1);
+                }
+                break;
+            case 10:
+                // 成本监审集体审议记录
+                generateDetail(summary, mainTask, "成本监审集体审议记录", 1);
+                break;
+            case 11:
+                // 成本监审工作底稿
+                for (int i = 0; i < childTasks.size(); i++) {
+                    CostProjectTask child = childTasks.get(i);
+                    generateDetail(summary, mainTask, child.getAuditedUnitName() + "成本监审工作底稿", i + 1);
+                }
+                break;
+            case 12:
+                // 成本监审提取资料登记表
+                for (int i = 0; i < childTasks.size(); i++) {
+                    CostProjectTask child = childTasks.get(i);
+                    generateDetail(summary, mainTask, child.getAuditedUnitName() + "提取资料登记表", i + 1);
+                }
+                break;
+            case 13:
+                // 提取的成本资料和会计凭证等复印件
+                for (int i = 0; i < childTasks.size(); i++) {
+                    CostProjectTask child = childTasks.get(i);
+                    generateDetail(summary, mainTask, child.getAuditedUnitName() + "成本资料和会计凭证复印件", i + 1);
+                }
+                break;
+            default:
+                generateDetail(summary, mainTask, "待补充", 1);
+                break;
+        }
+    }
+
+    /**
+     * 生成单个明细记录
+     *
+     * @param summary 资料归纳主表
+     * @param mainTask 主任务
+     * @param documentName 文书名称
+     * @param orderNum 序号
+     */
+    private void generateDetail(CostProjectTaskMaterialSummary summary,
+                                CostProjectTask mainTask,
+                                String documentName,
+                                int orderNum) {
+        CostProjectTaskMaterialSummaryDetail detail = new CostProjectTaskMaterialSummaryDetail();
+        detail.setMasterId(summary.getId());
+        detail.setTaskId(mainTask.getId());
+        detail.setDocumentName(documentName);
+        detail.setDocumentNumber("");
+        detail.setAuditedUnitName("");
+        detail.setFileSource("系统生成");
+        detail.setPageCount(0);
+        detail.setAttachmentUrl(""); // 初始为空,等待用户上传
+        detail.setOrderNum(orderNum);
+        detail.setIsDeleted("0");
+        costProjectTaskMaterialSummaryDetailManager.save(detail);
+    }
+
+    /**
+     * 档案校对通过后,异步生成Word卷宗
+     *
+     * @param taskId 任务ID
+     */
+    @Async
+    public void generateWordArchiveAsync(String taskId) {
+        try {
+            // 1. 查询任务的所有资料归纳主表(按序号排序)
+            // 2. 遍历每个主表,获取其明细列表(按序号排序)
+            // 3. 按顺序读取每个明细对应的文件
+            // 4. 将所有文件合并为一个完整的Word文档
+            // 5. 保存生成的Word卷宗文件
+
+            System.out.println("开始生成Word卷宗,任务ID:" + taskId);
+
+            // 示例代码框架(需要根据实际情况实现)
+             List<CostProjectTaskMaterialSummary> summaryList =
+                 costProjectTaskMaterialSummaryManager.listByTaskId(taskId);
+
+            // 按序号排序
+             summaryList.sort(Comparator.comparing(CostProjectTaskMaterialSummary::getMaterialOrderNum));
+
+            // 创建Word文档
+             XWPFDocument document = new XWPFDocument();
+
+             for (CostProjectTaskMaterialSummary summary : summaryList) {
+                 // 获取明细列表
+                 List<CostProjectTaskMaterialSummaryDetail> detailList = summary.getDetailList();
+                 if (detailList != null) {
+                     detailList.sort(Comparator.comparing(CostProjectTaskMaterialSummaryDetail::getOrderNum));
+
+                     for (CostProjectTaskMaterialSummaryDetail detail : detailList) {
+                         // 读取文件内容并添加到Word文档
+                         if (StringUtil.isNotEmpty(detail.getAttachmentUrl())) {
+                             try {
+
+                                 // 读取Word文件
+                                 java.io.FileInputStream fis = new java.io.FileInputStream(detail.getAttachmentUrl());
+                                 XWPFDocument sourceDoc = new XWPFDocument(fis);
+
+                                 // 合并段落
+                                 for (XWPFParagraph srcPara : sourceDoc.getParagraphs()) {
+                                     XWPFParagraph newPara = document.createParagraph();
+                                     copyParagraph(srcPara, newPara);
+                                 }
+
+                                 // 添加分页符(每个文档之间分页)
+                                 XWPFParagraph pageBreakPara = document.createParagraph();
+                                 XWPFRun pageBreakRun = pageBreakPara.createRun();
+                                 pageBreakRun.addBreak(org.apache.poi.xwpf.usermodel.BreakType.PAGE);
+
+                                 sourceDoc.close();
+                                 fis.close();
+                             } catch (Exception e) {
+                                 System.err.println("合并文件失败:" + detail.getDocumentName() + ",错误:" + e.getMessage());
+                                 e.printStackTrace();
+                             }
+                         }
+                     }
+                 }
+             }
+
+             //保存Word文档
+             String outputPath = "卷宗_" + taskId + ".docx";
+             FileOutputStream out = new FileOutputStream(outputPath);
+             document.write(out);
+             out.close();
+             document.close();
+
+            System.out.println("Word卷宗生成完成,任务ID:" + taskId);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            System.err.println("生成Word卷宗失败,任务ID:" + taskId + ",错误:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 复制段落内容
+     *
+     * @param srcPara 源段落
+     * @param destPara 目标段落
+     */
+    private void copyParagraph(XWPFParagraph srcPara, XWPFParagraph destPara) {
+        // 复制段落样式
+        destPara.setAlignment(srcPara.getAlignment());
+        destPara.setIndentationLeft(srcPara.getIndentationLeft());
+        destPara.setIndentationRight(srcPara.getIndentationRight());
+        destPara.setSpacingBefore(srcPara.getSpacingBefore());
+        destPara.setSpacingAfter(srcPara.getSpacingAfter());
+
+        // 复制段落中的所有Run
+        for (XWPFRun srcRun : srcPara.getRuns()) {
+            XWPFRun destRun = destPara.createRun();
+            copyRun(srcRun, destRun);
+        }
+    }
+
+    /**
+     * 复制Run内容
+     *
+     * @param srcRun 源Run
+     * @param destRun 目标Run
+     */
+    private void copyRun(XWPFRun srcRun, XWPFRun destRun) {
+        destRun.setText(srcRun.getText(0));
+        destRun.setBold(srcRun.isBold());
+        destRun.setItalic(srcRun.isItalic());
+        destRun.setUnderline(srcRun.getUnderline());
+        destRun.setStrikeThrough(srcRun.isStrikeThrough());
+        destRun.setFontSize(srcRun.getFontSize());
+        if (srcRun.getFontFamily() != null) {
+            destRun.setFontFamily(srcRun.getFontFamily());
+        }
+        if (srcRun.getColor() != null) {
+            destRun.setColor(srcRun.getColor());
+        }
+    }
+
+ }

+ 16 - 11
assistMg/src/main/java/com/hotent/surveyinfo/controller/CostSurveyTemplateController.java

@@ -284,7 +284,7 @@ public class CostSurveyTemplateController extends BaseController<CostSurveyTempl
 
             // 1. 开始将模板数据写入 CostVerifyTemplate 表中
 
-// 复制源模板的基本属性
+            // 复制源模板的基本属性
             ttemplateId = UUID.randomUUID().toString();
             costVerifyTemplate.setSurveyTemplateId(ttemplateId);
             costVerifyTemplate.setSurveyTemplateName(sourceTemplate.getSurveyTemplateName());
@@ -301,17 +301,18 @@ public class CostSurveyTemplateController extends BaseController<CostSurveyTempl
             costVerifyTemplate.setIsDelete("0");
             costVerifyTemplate.setTaskId(taskId);
 
-//			costVerifyTemplate.setCatalogId(sourceTemplate.getCatalogId());
+            //			costVerifyTemplate.setCatalogId(sourceTemplate.getCatalogId());
 
-// 设置与源模板的关联关系
+            // 设置与源模板的关联关系
 
             costVerifyTemplate.setVersionNo(costVerifyTemplateManager.generateVersionNumber());
 
-// 保存成本核定模板
+            // 保存成本核定模板
             costVerifyTemplateManager.createOrUpdate(costVerifyTemplate);
 
-// 2. 将headersList数据写入 CostVerifyTemplateHeaders 表中
+            // 2. 将headersList数据写入 CostVerifyTemplateHeaders 表中(批量保存)
             if (headersList != null && !headersList.isEmpty()) {
+                List<CostVerifyTemplateHeaders> verifyHeadersList = new ArrayList<>();
                 for (CostSurveyTemplateHeaders header : headersList) {
                     CostVerifyTemplateHeaders verifyHeader = new CostVerifyTemplateHeaders();
                     // 复制表头属性
@@ -360,13 +361,16 @@ public class CostSurveyTemplateController extends BaseController<CostSurveyTempl
 
                         }
                     }
-                    // 保存成本核定表头
-                    costVerifyTemplateHeadersManager.createOrUpdate(verifyHeader);
+                    // 添加到批量保存列表
+                    verifyHeadersList.add(verifyHeader);
                 }
+                // 批量保存成本核定表头
+                costVerifyTemplateHeadersManager.batchCreate(verifyHeadersList);
             }
 
-// 3. 将itemsList数据写入 CostVerifyTemplateItems 表中
+            // 3. 将itemsList数据写入 CostVerifyTemplateItems 表中(批量保存)
             if (updateites != null && !updateites.isEmpty()) {
+                List<CostVerifyTemplateItems> verifyItemsList = new ArrayList<>();
                 for (CostSurveyTemplateItems item : updateites) {
                     CostVerifyTemplateItems verifyItem = new CostVerifyTemplateItems();
                     // 复制数据项属性
@@ -389,10 +393,11 @@ public class CostSurveyTemplateController extends BaseController<CostSurveyTempl
                     verifyItem.setVersionId(currentVersion.getId());
                     verifyItem.setHeadersId(item.getHeadersId());
 
-                    // 保存成本核定数据项
-                    costVerifyTemplateItemsManager.createOrUpdate(verifyItem);
+                    // 添加到批量保存列表
+                    verifyItemsList.add(verifyItem);
                 }
-
+                // 批量保存成本核定数据项
+                costVerifyTemplateItemsManager.saveBatch(verifyItemsList);
             }
         }
 

+ 16 - 0
assistMg/src/main/java/com/hotent/surveyinfo/controller/CostVerifyTemplateController.java

@@ -1,6 +1,7 @@
 package com.hotent.surveyinfo.controller;
 
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.hotent.base.annotation.ApiGroup;
 import com.hotent.base.constants.ApiGroupConsts;
 import com.hotent.base.controller.BaseController;
@@ -189,6 +190,21 @@ public class CostVerifyTemplateController extends BaseController<CostVerifyTempl
 	public CommonResult<CostVerifyTemplate> getDetail(@ApiParam(name="id",value="业务对象主键", required = true)@RequestParam(required=true) String id) throws Exception{
 		return CommonResult.<CostVerifyTemplate>ok().value(baseService.getDetail(id));
 	}
+
+	/**
+	 * 根据id获取成本核定表模板表数据详情
+	 * @param id
+	 * @return
+	 * @throws Exception
+	 * ModelAndView
+	 */
+	@GetMapping(value="/getDetailByTaskId")
+	@ApiOperation(value="根据id获取成本核定表模板表数据详情",httpMethod = "GET",notes = "根据id获取成本核定表模板表数据详情")
+	public CommonResult<CostVerifyTemplate> getDetailByTaskId(@ApiParam(name="id",value="业务对象主键", required = true)@RequestParam(required=true) String id) throws Exception{
+		return CommonResult.<CostVerifyTemplate>ok().value(baseService.getOne(
+				new QueryWrapper<CostVerifyTemplate>().eq("task_id", id).orderByDesc("create_time").last("limit 1")
+		));
+	}
     /**
 	 * 新增,更新成本核定表模板表
 	 * @param costSurveyTemplate

+ 14 - 0
assistMg/src/main/java/com/hotent/surveyinfo/model/CostSurveyTemplateUpload.java

@@ -124,6 +124,20 @@ public class CostSurveyTemplateUpload extends BaseModel<CostSurveyTemplateUpload
     @JsonProperty("auditedUserId")
     private String auditedUserId;
 
+
+    @ApiModelProperty(value = "企业单位")
+    @JsonProperty("auditedUnitId")
+    @TableField("audited_unit_id")
+    private String auditedUnitId;
+
+    public String getAuditedUnitId() {
+        return auditedUnitId;
+    }
+
+    public void setAuditedUnitId(String auditedUnitId) {
+        this.auditedUnitId = auditedUnitId;
+    }
+
     public String getType() {
         return type;
     }

+ 1 - 1
assistMg/src/main/resources/mapper/CostSurveyTemplateItemsMapper.xml

@@ -76,7 +76,7 @@
 	</select>
 	<select id="selectBySurveyTemplateIdAndVersion" resultType="com.hotent.surveyinfo.model.CostSurveyTemplateItems">
 		SELECT * FROM cost_survey_template_items
-		WHERE survey_template_id = #{surveyTemplateId} AND version_id = #{version}
+		WHERE survey_template_id = #{surveyTemplateId} AND version_id = #{version} order by order_num
 	</select>
 	<select id="selectBySurveyTemplateIdAndVersionAndHeadersId" resultType="com.hotent.surveyinfo.model.CostSurveyTemplateItems">
 		SELECT * FROM cost_survey_template_items