Sfoglia il codice sorgente

修改生命周期导出逻辑

ywf 4 settimane fa
parent
commit
d15bff03cb

+ 7 - 0
siwei-modules/siwei-apply/src/main/java/com/siwei/apply/domain/vo/ProjectCycleProjectVo.java

@@ -3,6 +3,8 @@ package com.siwei.apply.domain.vo;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
+import java.util.Map;
+
 /**
  * 按生命周期阶段查询项目参数
  */
@@ -18,4 +20,9 @@ public class ProjectCycleProjectVo extends ProjectFilterVo {
      * 前端阶段类型,如 ydysyxz、tdgy
      */
     private String worktype;
+
+    /**
+     * 按当前阶段属性字段查询,key 为前端列字段名
+     */
+    private Map<String, Object> queryParams;
 }

+ 2 - 3
siwei-modules/siwei-apply/src/main/java/com/siwei/apply/mapper/NodeLandMapper.java

@@ -21,9 +21,6 @@ public interface NodeLandMapper {
      */
     Map<String, String> selectGeomByNodeId(String nodeId);
 
-    Map<String, String> selectUnionGeomByNodeId(@Param("nodeId") String nodeId);
-
-
     Integer selectCountGeomByNodeId(@Param("nodeIdList") List<String> nodeIdList);
 
 
@@ -56,6 +53,8 @@ public interface NodeLandMapper {
 
     List<Map<String, String>> selectGeomDbDetailsByNodeId(@Param("nodeId")String nodeId, @Param("geomId")String geomId);
 
+    Boolean existsGeomIntersect(@Param("sourceGeomId") String sourceGeomId, @Param("targetGeomId") String targetGeomId);
+
     Map<String, String> selectGeomContainsNode(@Param("detailsId")String detailsId);
 
     List<Map<String, Object>> selectGeomDetailsByGeomDbId( @Param("geomDbId")String geomDbId);

+ 6 - 1
siwei-modules/siwei-apply/src/main/java/com/siwei/apply/service/cadastre/impl/CadastreManageServiceImpl.java

@@ -1446,6 +1446,11 @@ public class CadastreManageServiceImpl implements CadastreManageService {
 
     @Override
     public List<Map<String,Object>> selectTableRow(String tableName,String geomFlag,String bsm,String ywh) {
+        return selectTableRow(tableName, geomFlag, bsm, ywh, false);
+    }
+
+    @Override
+    public List<Map<String,Object>> selectTableRow(String tableName,String geomFlag,String bsm,String ywh, boolean pageQuery) {
         String validFlag = "0";
         if(Arrays.asList("tf_500", "tf_1000", "tf_2000","zd_shp").contains(tableName)){
             validFlag = "";
@@ -1459,7 +1464,7 @@ public class CadastreManageServiceImpl implements CadastreManageService {
         if (ywh.matches(".*=\\d+")) {
             ywh = ywh.replaceAll("=(\\d+)", "='$1'");
         }
-        List<Map<String,Object>> res = cadastreFileMapper.selectTableDataByCondition(validFlag,tableName,bsm,ywh);
+        List<Map<String,Object>> res = cadastreFileMapper.selectTableDataByCondition(validFlag,tableName,bsm,ywh,!pageQuery);
         if(CollectionUtils.isNotEmpty(res)){ //去掉geom字段和valid_flag字段
             List<Map<String, String>> dbFieldNameAndTypeList = cadastreFileMapper.selectTableCollumAndType(tableName.toLowerCase(Locale.ROOT));
             List<Object> filterCollumList =  dbFieldNameAndTypeList.stream().filter(s->s.get("data_type").equals("bytea")).map(s->s.get("column_name")).collect(Collectors.toList());

+ 739 - 65
siwei-modules/siwei-apply/src/main/java/com/siwei/apply/service/impl/ProjectImpl.java

@@ -12,6 +12,7 @@ import com.siwei.apply.domain.vo.ProjectFilterVo;
 import com.siwei.apply.domain.vo.ProjectUpdateVo;
 import com.siwei.apply.domain.vo.ProjectVo;
 import com.siwei.apply.enums.AloneWorkFlowEnum;
+import com.siwei.apply.enums.ProjectType;
 import com.siwei.apply.mapper.*;
 import com.siwei.apply.service.NodeAttachmentService;
 import com.siwei.apply.service.NodeLandService;
@@ -46,6 +47,7 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.Serializable;
+import java.math.BigDecimal;
 import java.net.URLEncoder;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
@@ -68,6 +70,13 @@ import static com.siwei.apply.common.Common.UserId;
 public class ProjectImpl implements ProjectService {
     private static final Map<String, String> CYCLE_TABLE_WORKTYPE_MAP;
     private static final Map<String, String> CYCLE_RESOURCE_TABLE_MAP;
+    private static final Map<String, String> CYCLE_TABLE_FIELD_PREFIX_MAP;
+    private static final Map<String, String> CYCLE_TABLE_STAGE_NO_MAP;
+    private static final String BP_ALONE_TABLE = "t_ydbp";
+    private static final String BP_BATCH_TABLE = "t_ydbp_data";
+    private static final String GY_TABLE = "t_tdgy";
+    private static final String BP_NAME_FIELD = "BP_name";
+    private static final String BP_PFWH_FIELD = "BP_pfwh";
 
     static {
         Map<String, String> tableWorktypeMap = new HashMap<>();
@@ -94,6 +103,33 @@ public class ProjectImpl implements ProjectService {
         resourceTableMap.put("8", "t_tdhyhs");
         resourceTableMap.put("9", "t_gyjsydjfwscdj");
         CYCLE_RESOURCE_TABLE_MAP = Collections.unmodifiableMap(resourceTableMap);
+
+        Map<String, String> fieldPrefixMap = new HashMap<>();
+        fieldPrefixMap.put("t_ydysyxz", "YS");
+        fieldPrefixMap.put("t_tjyydhx", "GH");
+        fieldPrefixMap.put("t_ydbp", "BP");
+        fieldPrefixMap.put("t_ydbp_data", "BP");
+        fieldPrefixMap.put("t_tdgy", "GY");
+        fieldPrefixMap.put("t_gyjsydscdj", "SD");
+        fieldPrefixMap.put("t_jsydghxk", "YG");
+        fieldPrefixMap.put("t_jsgcghxk", "GG");
+        fieldPrefixMap.put("t_tdhyhs", "HY");
+        fieldPrefixMap.put("t_gyjsydjfwscdj", "FSD");
+        CYCLE_TABLE_FIELD_PREFIX_MAP = Collections.unmodifiableMap(fieldPrefixMap);
+
+        Map<String, String> stageNoMap = new HashMap<>();
+        stageNoMap.put("t_ydysyxz", "01");
+        stageNoMap.put("t_tjyydhx", "02");
+        stageNoMap.put("t_ydbp", "03");
+        stageNoMap.put("t_ydbp_data", "03");
+        stageNoMap.put("t_tdgy", "04");
+        stageNoMap.put("t_gyjsydscdj", "05");
+        stageNoMap.put("t_jsydghxk", "06");
+        stageNoMap.put("t_jsgcghxk", "07");
+        stageNoMap.put("t_tdhyhs", "08");
+        stageNoMap.put("t_gyjsydjfwscdj", "09");
+        CYCLE_TABLE_STAGE_NO_MAP = Collections.unmodifiableMap(stageNoMap);
+
     }
 
     @Autowired
@@ -221,6 +257,7 @@ public class ProjectImpl implements ProjectService {
             }
         }
 
+        rows = filterCycleProjectRows(rows, projectCycleProjectVo.getQueryParams());
         int count = rows.size();
         int fromIndex = Math.min(projectCycleProjectVo.getOffset(), count);
         int toIndex = Math.min(fromIndex + projectCycleProjectVo.getPageSize(), count);
@@ -434,6 +471,8 @@ public class ProjectImpl implements ProjectService {
             }
 
             List<CycleShpRow> rows = new ArrayList<>();
+            List<CycleShpRow> bpIntersectRows = new ArrayList<>();
+            List<CycleFieldAlias> fieldAliases = buildBaseFieldAliases();
             List<String> projectNames = new ArrayList<>();
             for (String projectId : projectIds) {
                 if (StringUtils.isBlank(projectId)) {
@@ -445,15 +484,17 @@ public class ProjectImpl implements ProjectService {
                     continue;
                 }
 
-                List<ProjectCycleExportNode> exportNodes = buildCycleExportNodes(project);
-                if (CollectionUtils.isEmpty(exportNodes)) {
+                CycleExportBuildResult exportResult = buildCycleExportNodes(project);
+                if (CollectionUtils.isEmpty(exportResult.exportNodes)) {
                     log.warn("批量导出项目全生命周期SHP时项目无已上链生命周期数据,projectId={}", projectId);
                     continue;
                 }
 
-                List<CycleShpRow> projectRows = buildCycleShpRows(project, exportNodes);
+                List<CycleShpRow> projectRows = buildCycleShpRows(project, exportResult.exportNodes, fieldAliases,
+                        exportResult.batchBpByGyGeomKey);
                 if (CollectionUtils.isNotEmpty(projectRows)) {
                     rows.addAll(projectRows);
+                    bpIntersectRows.addAll(exportResult.bpIntersectRows);
                     projectNames.add(project.getName());
                 }
             }
@@ -464,21 +505,45 @@ public class ProjectImpl implements ProjectService {
             String shpName = "project_cycle_" + System.currentTimeMillis();
             tempDir = Files.createTempDirectory("project-cycle-shp-").toFile();
             File shpFile = new File(tempDir, shpName + ".shp");
-            writeCycleShapefile(shpFile, rows);
+            List<CycleFieldAlias> orderedFieldAliases = buildOrderedFieldAliases(fieldAliases);
+            List<String> shpFields = buildCycleShpFields(rows, orderedFieldAliases);
+            writeCycleShapefile(shpFile, rows, shpFields);
 
             File cpgFile = new File(tempDir, shpName + ".cpg");
             try (PrintWriter writer = new PrintWriter(cpgFile, "UTF-8")) {
                 writer.print("UTF-8");
             }
+            File aliasFile = new File(tempDir, "字段说明.csv");
+            writeFieldAliasCsv(aliasFile, orderedFieldAliases);
+            File shpXmlFile = new File(tempDir, shpName + ".shp.xml");
+            writeShpXml(shpXmlFile, shpName, orderedFieldAliases, shpFields);
+
+            List<File> zipFiles = new ArrayList<>();
+            zipFiles.add(new File(tempDir, shpName + ".shp"));
+            zipFiles.add(new File(tempDir, shpName + ".shx"));
+            zipFiles.add(new File(tempDir, shpName + ".dbf"));
+            zipFiles.add(new File(tempDir, shpName + ".prj"));
+            zipFiles.add(cpgFile);
+            zipFiles.add(shpXmlFile);
+            zipFiles.add(aliasFile);
+
+            if (CollectionUtils.isNotEmpty(bpIntersectRows)) {
+                String bpShpName = shpName + "_bp_intersect";
+                File bpShpFile = new File(tempDir, bpShpName + ".shp");
+                writeCycleShapefile(bpShpFile, bpIntersectRows);
+                File bpCpgFile = new File(tempDir, bpShpName + ".cpg");
+                try (PrintWriter writer = new PrintWriter(bpCpgFile, "UTF-8")) {
+                    writer.print("UTF-8");
+                }
+                zipFiles.add(new File(tempDir, bpShpName + ".shp"));
+                zipFiles.add(new File(tempDir, bpShpName + ".shx"));
+                zipFiles.add(new File(tempDir, bpShpName + ".dbf"));
+                zipFiles.add(new File(tempDir, bpShpName + ".prj"));
+                zipFiles.add(bpCpgFile);
+            }
 
             File zipFile = new File(tempDir, shpName + ".zip");
-            ServiceFileUtil.zipFiles(Arrays.asList(
-                    new File(tempDir, shpName + ".shp"),
-                    new File(tempDir, shpName + ".shx"),
-                    new File(tempDir, shpName + ".dbf"),
-                    new File(tempDir, shpName + ".prj"),
-                    cpgFile
-            ), zipFile);
+            ServiceFileUtil.zipFiles(zipFiles, zipFile);
 
             String downloadName = projectNames.size() == 1
                     ? safeFileName(projectNames.get(0)) + "_全生命周期.zip"
@@ -618,7 +683,7 @@ public class ProjectImpl implements ProjectService {
         }
     }
 
-    private List<ProjectCycleExportNode> buildCycleExportNodes(Project project) {
+    private CycleExportBuildResult buildCycleExportNodes(Project project) {
         Integer projectType = project.getProjectType();
         List<ProjectCycleRes> workflows = workflowMapper.selectByProjectTypeOrderByIndex(projectType);
         List<String> allowTables = workflows.stream().map(ProjectCycleRes::getTableName).collect(Collectors.toList());
@@ -628,7 +693,9 @@ public class ProjectImpl implements ProjectService {
         }
 
         List<ProjectWorkflowRes> projectWorkflows = projectWorkflowMapper.selectCycleByProjectId(project.getId());
-        List<ProjectCycleExportNode> exportNodes = new ArrayList<>();
+        CycleExportBuildResult result = new CycleExportBuildResult();
+        List<ProjectCycleExportNode> gyNodes = new ArrayList<>();
+        List<ProjectCycleExportNode> batchBpNodes = new ArrayList<>();
         for (ProjectWorkflowRes workflowRes : projectWorkflows) {
             String tableName = workflowRes.getNodeTableName();
             if (!allowTables.contains(tableName)) {
@@ -645,26 +712,126 @@ public class ProjectImpl implements ProjectService {
                 continue;
             }
 
-            Map<String, String> geomInfo = nodeLandMapper.selectUnionGeomByNodeId(workflowRes.getNodeId());
-            if ((geomInfo == null || StringUtils.isBlank(geomInfo.get("geom")))
-                    && shouldUseGyjsydscdjGeom(tableName)) {
-                geomInfo = nodeLandMapper.selectUnionGeomByNodeId(findProjectNodeId(project.getId(), "t_gyjsydscdj"));
+            ProjectWorkflow workflow = new ProjectWorkflow();
+            workflow.setNodeId(workflowRes.getNodeId());
+            workflow.setNodeTableName(tableName);
+            List<Map<String, String>> geomDetails = getCycleProjectGeomDetails(project.getId(), tableName, workflow);
+            if (CollectionUtils.isEmpty(geomDetails)) {
+                continue;
+            }
+
+            int level = levelMap.getOrDefault(workflowRes.getWorkflowId(), result.exportNodes.size() + 1);
+            for (Map<String, String> geomDetail : geomDetails) {
+                String geom = getMapString(geomDetail, "geom");
+                if (StringUtils.isBlank(geom)) {
+                    continue;
+                }
+                ProjectCycleExportNode node = new ProjectCycleExportNode();
+                node.level = level;
+                node.tableName = tableName;
+                node.node = currentNode;
+                node.geom = geom;
+                node.geomId = getMapString(geomDetail, "id");
+                node.geomDbId = getMapString(geomDetail, "uploadId", "upload_id");
+                node.geomKey = buildGeomDetailKey(node.geomDbId, node.geomId, node.geom);
+                if (isBatchProject(project) && isBatchBpTable(tableName)) {
+                    batchBpNodes.add(node);
+                } else {
+                    result.exportNodes.add(node);
+                    if (isGyTable(tableName)) {
+                        gyNodes.add(node);
+                    }
+                }
+            }
+        }
+        applyBatchBpIntersections(project, result, gyNodes, batchBpNodes);
+        result.exportNodes.sort(Comparator.comparingInt(o -> o.level));
+        return result;
+    }
+
+    private void applyBatchBpIntersections(Project project, CycleExportBuildResult result,
+                                           List<ProjectCycleExportNode> gyNodes,
+                                           List<ProjectCycleExportNode> batchBpNodes) {
+        if (!isBatchProject(project) || CollectionUtils.isEmpty(gyNodes) || CollectionUtils.isEmpty(batchBpNodes)) {
+            return;
+        }
+        for (ProjectCycleExportNode bpNode : batchBpNodes) {
+            if (StringUtils.isBlank(bpNode.geomId)) {
+                continue;
+            }
+            String bpName = getObjectString(bpNode.node, "name");
+            String bpPfwh = getObjectString(bpNode.node, "pfwh");
+            for (ProjectCycleExportNode gyNode : gyNodes) {
+                if (StringUtils.isBlank(gyNode.geomId)) {
+                    continue;
+                }
+                Boolean intersects = nodeLandMapper.existsGeomIntersect(bpNode.geomId, gyNode.geomId);
+                if (!Boolean.TRUE.equals(intersects)) {
+                    continue;
+                }
+                BpBatchAggregate aggregate = result.batchBpByGyGeomKey.computeIfAbsent(gyNode.geomKey,
+                        key -> new BpBatchAggregate());
+                if (StringUtils.isNotBlank(bpName)) {
+                    aggregate.names.add(bpName);
+                }
+                if (StringUtils.isNotBlank(bpPfwh)) {
+                    aggregate.pfwhs.add(bpPfwh);
+                }
+                result.bpIntersectRows.add(buildBpIntersectRow(project, bpNode, gyNode, bpName, bpPfwh));
             }
-            if (geomInfo == null || StringUtils.isBlank(geomInfo.get("geom"))) {
+        }
+    }
+
+    private CycleShpRow buildBpIntersectRow(Project project, ProjectCycleExportNode bpNode,
+                                            ProjectCycleExportNode gyNode, String bpName, String bpPfwh) {
+        CycleShpRow row = new CycleShpRow();
+        row.geom = bpNode.geom;
+        row.geomKey = bpNode.geomKey;
+        row.attributes.put("id", project.getId());
+        row.attributes.put("xmmc", project.getName());
+        row.attributes.put("xmbm", project.getCode());
+        row.attributes.put("jsdw", project.getCompany());
+        row.attributes.put("bp_dkid", bpNode.geomId);
+        row.attributes.put("gy_dkid", gyNode.geomId);
+        row.attributes.put("bp_name", StringUtils.defaultString(bpName));
+        row.attributes.put("bp_pfwh", StringUtils.defaultString(bpPfwh));
+        return row;
+    }
+
+    private void applyBatchBpAttributes(List<CycleShpRow> rows, Map<String, BpBatchAggregate> batchBpByGyGeomKey) {
+        if (CollectionUtils.isEmpty(rows) || batchBpByGyGeomKey == null || batchBpByGyGeomKey.isEmpty()) {
+            return;
+        }
+        for (CycleShpRow row : rows) {
+            BpBatchAggregate aggregate = batchBpByGyGeomKey.get(row.geomKey);
+            if (aggregate == null) {
                 continue;
             }
+            row.attributes.put(BP_NAME_FIELD, joinAggregateValues(aggregate.names));
+            row.attributes.put(BP_PFWH_FIELD, joinAggregateValues(aggregate.pfwhs));
+        }
+    }
 
-            int level = levelMap.getOrDefault(workflowRes.getWorkflowId(), exportNodes.size() + 1);
-            ProjectCycleExportNode node = new ProjectCycleExportNode();
-            node.level = level;
-            node.tableName = tableName;
-            node.node = currentNode;
-            node.geom = geomInfo.get("geom");
-            node.geomKey = StringUtils.defaultIfBlank(geomInfo.get("geomDbId"), normalizeGeomKey(node.geom));
-            exportNodes.add(node);
+    private String joinAggregateValues(Set<String> values) {
+        if (CollectionUtils.isEmpty(values)) {
+            return "";
         }
-        exportNodes.sort(Comparator.comparingInt(o -> o.level));
-        return exportNodes;
+        return values.stream()
+                .filter(StringUtils::isNotBlank)
+                .distinct()
+                .collect(Collectors.joining(","));
+    }
+
+    private boolean isBatchProject(Project project) {
+        return project != null && ProjectType.BATCH.getCode().equals(String.valueOf(project.getProjectType()));
+    }
+
+    private boolean isBatchBpTable(String tableName) {
+        return BP_BATCH_TABLE.equalsIgnoreCase(tableName);
+    }
+
+    private boolean isGyTable(String tableName) {
+        return GY_TABLE.equalsIgnoreCase(tableName);
     }
 
     private ProjectWorkflow findExportWorkflow(ProjectCycleCurrentShpVo.Item item, String nodeTableName) {
@@ -726,20 +893,26 @@ public class ProjectImpl implements ProjectService {
         return result;
     }
 
-    private List<CycleShpRow> buildCycleShpRows(Project project, List<ProjectCycleExportNode> exportNodes) {
+    private List<CycleShpRow> buildCycleShpRows(Project project, List<ProjectCycleExportNode> exportNodes,
+                                                List<CycleFieldAlias> fieldAliases,
+                                                Map<String, BpBatchAggregate> batchBpByGyGeomKey) {
         List<CycleShpRow> rows = new ArrayList<>();
         Map<String, CycleShpRow> rowMap = new LinkedHashMap<>();
         Map<String, Map<String, String>> tableColumnMap = new HashMap<>();
-        Map<String, Map<String, String>> cycleFieldMap = buildCycleFieldMap(project.getProjectType(), tableColumnMap);
+        CycleFieldBuildResult fieldBuildResult = buildCycleFieldResult(project.getProjectType(), tableColumnMap);
+        Map<String, Map<String, String>> cycleFieldMap = fieldBuildResult.fieldMap;
+        addFieldAliases(fieldAliases, fieldBuildResult.aliases);
 
         for (ProjectCycleExportNode node : exportNodes) {
             CycleShpRow row = rowMap.computeIfAbsent(node.geomKey, key -> {
                 CycleShpRow newRow = new CycleShpRow();
                 newRow.geom = node.geom;
+                newRow.geomKey = node.geomKey;
                 newRow.attributes.put("id", project.getId());
                 newRow.attributes.put("xmmc", project.getName());
                 newRow.attributes.put("xmbm", project.getCode());
                 newRow.attributes.put("jsdw", project.getCompany());
+                newRow.attributes.put("dkid", node.geomId);
                 for (Map<String, String> fieldMap : cycleFieldMap.values()) {
                     for (String fieldName : fieldMap.values()) {
                         newRow.attributes.put(fieldName, "");
@@ -761,6 +934,7 @@ public class ProjectImpl implements ProjectService {
                 }
             }
         }
+        applyBatchBpAttributes(rows, batchBpByGyGeomKey);
         return rows;
     }
 
@@ -778,31 +952,87 @@ public class ProjectImpl implements ProjectService {
         return fieldMap;
     }
 
-    private Map<String, Map<String, String>> buildCycleFieldMap(Integer projectType, Map<String, Map<String, String>> tableColumnMap) {
+    private CycleFieldBuildResult buildCycleFieldResult(Integer projectType, Map<String, Map<String, String>> tableColumnMap) {
         Map<String, Map<String, String>> result = new LinkedHashMap<>();
+        List<CycleFieldAlias> aliases = new ArrayList<>();
         Set<String> usedFieldNames = new HashSet<>();
-        List<ProjectCycleRes> workflows = workflowMapper.selectByProjectTypeOrderByIndex(projectType);
+        List<ProjectCycleRes> workflows = sortCycleWorkflowsByExportOrder(
+                workflowMapper.selectByProjectTypeOrderByIndex(projectType));
         for (int i = 0; i < workflows.size(); i++) {
             ProjectCycleRes workflow = workflows.get(i);
             String tableName = workflow.getTableName();
+            String fieldPrefix = getCycleFieldPrefix(tableName, i + 1);
+            String stageNo = getCycleStageNo(tableName, i + 1);
+            String stageName = StringUtils.defaultIfBlank(workflow.getName(), tableName);
+            if ("BP".equals(fieldPrefix)) {
+                result.put(tableName, buildBpCycleFieldMap(stageNo, fieldPrefix, stageName, aliases,
+                        usedFieldNames, tableColumnMap));
+                continue;
+            }
             Map<String, String> columnCommentMap = tableColumnMap.computeIfAbsent(tableName, this::getColumnCommentMap);
             Map<String, String> fieldMap = new LinkedHashMap<>();
+            aliases.add(CycleFieldAlias.stage(stageNo, fieldPrefix, stageName, tableName));
             for (Map.Entry<String, String> entry : columnCommentMap.entrySet()) {
                 String columnName = entry.getKey();
                 if (shouldSkipExportColumn(columnName)) {
                     continue;
                 }
-                String fieldName = buildShpFieldName("L" + (i + 1), entry.getValue(), columnName, usedFieldNames);
+                String comment = StringUtils.defaultIfBlank(entry.getValue(), columnName);
+                String fieldName = buildShpFieldName(fieldPrefix, comment, columnName, usedFieldNames);
                 fieldMap.put(columnName, fieldName);
+                aliases.add(CycleFieldAlias.field(stageNo, fieldPrefix, stageName, fieldName,
+                        fieldPrefix + "-" + comment, tableName, columnName));
             }
             result.put(tableName, fieldMap);
         }
-        return result;
+        return new CycleFieldBuildResult(result, aliases);
+    }
+
+    private Map<String, String> buildBpCycleFieldMap(String stageNo, String fieldPrefix, String stageName,
+                                                     List<CycleFieldAlias> aliases, Set<String> usedFieldNames,
+                                                     Map<String, Map<String, String>> tableColumnMap) {
+        Map<String, String> fieldMap = new LinkedHashMap<>();
+        Map<String, String> columnCommentMap = tableColumnMap.computeIfAbsent(BP_ALONE_TABLE, this::getColumnCommentMap);
+        aliases.add(CycleFieldAlias.stage(stageNo, fieldPrefix, stageName, BP_ALONE_TABLE));
+
+        putFixedBpField(fieldMap, aliases, usedFieldNames, stageNo, fieldPrefix, stageName,
+                "name", BP_NAME_FIELD, "报批项目名称", BP_BATCH_TABLE, "name");
+        for (Map.Entry<String, String> entry : columnCommentMap.entrySet()) {
+            String columnName = entry.getKey();
+            if (shouldSkipExportColumn(columnName)) {
+                continue;
+            }
+            String comment = StringUtils.defaultIfBlank(entry.getValue(), columnName);
+            if ("pfwh".equalsIgnoreCase(columnName)) {
+                putFixedBpField(fieldMap, aliases, usedFieldNames, stageNo, fieldPrefix, stageName,
+                        columnName, BP_PFWH_FIELD, comment, BP_ALONE_TABLE, columnName);
+                continue;
+            }
+            String fieldName = buildShpFieldName(fieldPrefix, comment, columnName, usedFieldNames);
+            fieldMap.put(columnName, fieldName);
+            aliases.add(CycleFieldAlias.field(stageNo, fieldPrefix, stageName, fieldName,
+                    fieldPrefix + "-" + comment, BP_ALONE_TABLE, columnName));
+        }
+        if (!fieldMap.containsKey("pfwh")) {
+            putFixedBpField(fieldMap, aliases, usedFieldNames, stageNo, fieldPrefix, stageName,
+                    "pfwh", BP_PFWH_FIELD, "批复文号", BP_ALONE_TABLE, "pfwh");
+        }
+        return fieldMap;
+    }
+
+    private void putFixedBpField(Map<String, String> fieldMap, List<CycleFieldAlias> aliases,
+                                 Set<String> usedFieldNames, String stageNo, String fieldPrefix, String stageName,
+                                 String columnName, String fieldName, String comment,
+                                 String sourceTable, String sourceColumn) {
+        fieldMap.put(columnName, fieldName);
+        usedFieldNames.add(fieldName);
+        aliases.add(CycleFieldAlias.field(stageNo, fieldPrefix, stageName, fieldName,
+                fieldPrefix + "-" + comment, sourceTable, sourceColumn));
     }
 
     private Map<String, String> getColumnCommentMap(String tableName) {
         List<Map<String, String>> columnComments = projectWorkflowMapper.selectTableColumnComments(tableName);
-        Map<String, String> result = new HashMap<>();
+        Map<String, String> result = new LinkedHashMap<>();
         if (CollectionUtils.isNotEmpty(columnComments)) {
             for (Map<String, String> columnComment : columnComments) {
                 result.put(columnComment.get("columnName"), columnComment.get("columnComment"));
@@ -811,18 +1041,78 @@ public class ProjectImpl implements ProjectService {
         return result;
     }
 
+    private List<String> buildCycleShpFields(List<CycleShpRow> rows) {
+        return rows.stream()
+                .flatMap(row -> row.attributes.keySet().stream())
+                .distinct()
+                .collect(Collectors.toList());
+    }
+
+    private List<String> buildCycleShpFields(List<CycleShpRow> rows, List<CycleFieldAlias> fieldAliases) {
+        Set<String> rowFields = rows.stream()
+                .flatMap(row -> row.attributes.keySet().stream())
+                .collect(Collectors.toCollection(LinkedHashSet::new));
+        List<String> fields = new ArrayList<>();
+        for (CycleFieldAlias alias : fieldAliases) {
+            if (StringUtils.isNotBlank(alias.fieldName) && rowFields.contains(alias.fieldName)
+                    && !fields.contains(alias.fieldName)) {
+                fields.add(alias.fieldName);
+            }
+        }
+        for (String rowField : rowFields) {
+            if (!fields.contains(rowField)) {
+                fields.add(rowField);
+            }
+        }
+        return fields;
+    }
+
+    private List<CycleFieldAlias> buildOrderedFieldAliases(List<CycleFieldAlias> fieldAliases) {
+        if (CollectionUtils.isEmpty(fieldAliases)) {
+            return Collections.emptyList();
+        }
+        Map<CycleFieldAlias, Integer> originalIndexMap = new IdentityHashMap<>();
+        for (int i = 0; i < fieldAliases.size(); i++) {
+            originalIndexMap.put(fieldAliases.get(i), i);
+        }
+        List<CycleFieldAlias> orderedAliases = new ArrayList<>(fieldAliases);
+        orderedAliases.sort(Comparator
+                .comparingInt(this::getCycleFieldAliasOrder)
+                .thenComparingInt(alias -> originalIndexMap.getOrDefault(alias, 0)));
+        return orderedAliases;
+    }
+
+    private int getCycleFieldAliasOrder(CycleFieldAlias alias) {
+        int baseOrder = getBaseFieldOrder(alias.fieldName);
+        if (baseOrder >= 0) {
+            return baseOrder;
+        }
+        if (StringUtils.isNotBlank(alias.stageNo)) {
+            try {
+                return 100 + Integer.parseInt(alias.stageNo);
+            } catch (NumberFormatException ignored) {
+                return 1000;
+            }
+        }
+        return 1000;
+    }
+
+    private int getBaseFieldOrder(String fieldName) {
+        List<String> baseFields = Arrays.asList("id", "xmmc", "xmbm", "jsdw", "dkid");
+        return baseFields.indexOf(fieldName);
+    }
+
     private void writeCycleShapefile(File shpFile, List<CycleShpRow> rows) throws Exception {
+        writeCycleShapefile(shpFile, rows, buildCycleShpFields(rows));
+    }
+
+    private void writeCycleShapefile(File shpFile, List<CycleShpRow> rows, List<String> fields) throws Exception {
         SimpleFeatureTypeBuilder typeBuilder = new SimpleFeatureTypeBuilder();
         typeBuilder.setName("project_cycle");
         CoordinateReferenceSystem crs = CRS.decode("EPSG:4490");
         typeBuilder.setCRS(crs);
         typeBuilder.add("the_geom", MultiPolygon.class);
 
-        List<String> fields = rows.stream()
-                .flatMap(row -> row.attributes.keySet().stream())
-                .distinct()
-                .sorted(this::compareCycleField)
-                .collect(Collectors.toList());
         for (String field : fields) {
             typeBuilder.length(254).add(field, String.class);
         }
@@ -879,6 +1169,79 @@ public class ProjectImpl implements ProjectService {
         }
     }
 
+    private void writeFieldAliasCsv(File aliasFile, List<CycleFieldAlias> aliases) throws IOException {
+        try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(aliasFile.toPath(), StandardCharsets.UTF_8))) {
+            writer.print('\ufeff');
+            writer.println("阶段编号,阶段名称,字段名,中文别名,原表名,原字段名");
+            for (CycleFieldAlias alias : aliases) {
+                writer.println(csv(alias.stageNo) + ","
+                        + csv(alias.stageName) + ","
+                        + csv(alias.fieldName) + ","
+                        + csv(alias.aliasName) + ","
+                        + csv(alias.sourceTable) + ","
+                        + csv(alias.sourceColumn));
+            }
+        }
+    }
+
+    private void writeShpXml(File shpXmlFile, String shpName, List<CycleFieldAlias> aliases,
+                             List<String> shpFields) throws IOException {
+        Set<String> fieldSet = new HashSet<>(shpFields);
+        Set<String> writtenFields = new HashSet<>();
+        StringBuilder xml = new StringBuilder();
+        xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+        xml.append("<metadata>\n");
+        xml.append("  <idinfo>\n");
+        xml.append("    <citation><citeinfo><title>")
+                .append(escapeXml(shpName))
+                .append("</title></citeinfo></citation>\n");
+        xml.append("  </idinfo>\n");
+        xml.append("  <eainfo>\n");
+        xml.append("    <detailed>\n");
+        xml.append("      <enttyp><enttypl>project_cycle</enttypl><enttypd>项目全生命周期SHP</enttypd></enttyp>\n");
+        for (CycleFieldAlias alias : aliases) {
+            if (StringUtils.isBlank(alias.fieldName)
+                    || !fieldSet.contains(alias.fieldName)
+                    || !writtenFields.add(alias.fieldName)) {
+                continue;
+            }
+            xml.append("      <attr>\n");
+            xml.append("        <attrlabl>").append(escapeXml(alias.fieldName)).append("</attrlabl>\n");
+            xml.append("        <attalias>").append(escapeXml(alias.aliasName)).append("</attalias>\n");
+            xml.append("        <attrdef>").append(escapeXml(buildAliasDescription(alias))).append("</attrdef>\n");
+            xml.append("      </attr>\n");
+        }
+        xml.append("    </detailed>\n");
+        xml.append("  </eainfo>\n");
+        xml.append("</metadata>\n");
+        Files.write(shpXmlFile.toPath(), xml.toString().getBytes(StandardCharsets.UTF_8));
+    }
+
+    private String buildAliasDescription(CycleFieldAlias alias) {
+        if (StringUtils.isBlank(alias.fieldPrefix)) {
+            return alias.aliasName;
+        }
+        String fieldAlias = alias.aliasName.substring(Math.min(alias.aliasName.length(), alias.fieldPrefix.length() + 1));
+        return alias.fieldPrefix + "-" + alias.stageName + "-" + fieldAlias;
+    }
+
+    private String csv(String value) {
+        String text = StringUtils.defaultString(value);
+        if (text.contains("\"") || text.contains(",") || text.contains("\n") || text.contains("\r")) {
+            return "\"" + text.replace("\"", "\"\"") + "\"";
+        }
+        return text;
+    }
+
+    private String escapeXml(String value) {
+        return StringUtils.defaultString(value)
+                .replace("&", "&amp;")
+                .replace("<", "&lt;")
+                .replace(">", "&gt;")
+                .replace("\"", "&quot;")
+                .replace("'", "&apos;");
+    }
+
     private boolean shouldUseGyjsydscdjGeom(String tableName) {
         return AloneWorkFlowEnum.NODE_4.getTableName().equalsIgnoreCase(tableName)
                 || AloneWorkFlowEnum.NODE_5.getTableName().equalsIgnoreCase(tableName)
@@ -927,41 +1290,78 @@ public class ProjectImpl implements ProjectService {
         return fieldName;
     }
 
-    private int compareCycleField(String field1, String field2) {
-        List<String> baseFields = Arrays.asList("id", "xmmc", "xmbm", "jsdw");
-        int baseIndex1 = baseFields.indexOf(field1);
-        int baseIndex2 = baseFields.indexOf(field2);
-        if (baseIndex1 >= 0 || baseIndex2 >= 0) {
-            if (baseIndex1 < 0) {
-                return 1;
-            }
-            if (baseIndex2 < 0) {
-                return -1;
-            }
-            return Integer.compare(baseIndex1, baseIndex2);
+    private List<CycleFieldAlias> buildBaseFieldAliases() {
+        List<CycleFieldAlias> aliases = new ArrayList<>();
+        aliases.add(CycleFieldAlias.base("id", "项目ID"));
+        aliases.add(CycleFieldAlias.base("xmmc", "项目名称"));
+        aliases.add(CycleFieldAlias.base("xmbm", "项目编码"));
+        aliases.add(CycleFieldAlias.base("jsdw", "建设单位"));
+        aliases.add(CycleFieldAlias.base("dkid", "地块ID"));
+        return aliases;
+    }
+
+    private List<ProjectCycleRes> sortCycleWorkflowsByExportOrder(List<ProjectCycleRes> workflows) {
+        if (CollectionUtils.isEmpty(workflows)) {
+            return Collections.emptyList();
+        }
+        Map<ProjectCycleRes, Integer> originalIndexMap = new IdentityHashMap<>();
+        for (int i = 0; i < workflows.size(); i++) {
+            originalIndexMap.put(workflows.get(i), i);
         }
+        List<ProjectCycleRes> sortedWorkflows = new ArrayList<>(workflows);
+        sortedWorkflows.sort(Comparator
+                .comparingInt((ProjectCycleRes workflow) -> getCycleStageOrder(workflow.getTableName(),
+                        originalIndexMap.getOrDefault(workflow, 0) + 1))
+                .thenComparingInt(workflow -> originalIndexMap.getOrDefault(workflow, 0)));
+        return sortedWorkflows;
+    }
 
-        int level1 = getCycleFieldLevel(field1);
-        int level2 = getCycleFieldLevel(field2);
-        if (level1 != level2) {
-            return Integer.compare(level1, level2);
+    private void addFieldAliases(List<CycleFieldAlias> target, List<CycleFieldAlias> aliases) {
+        Set<String> exists = target.stream()
+                .map(alias -> StringUtils.defaultString(alias.stageNo) + "|"
+                        + StringUtils.defaultString(alias.fieldPrefix) + "|"
+                        + StringUtils.defaultString(alias.fieldName) + "|"
+                        + StringUtils.defaultString(alias.sourceTable) + "|"
+                        + StringUtils.defaultString(alias.sourceColumn))
+                .collect(Collectors.toCollection(LinkedHashSet::new));
+        for (CycleFieldAlias alias : aliases) {
+            String key = StringUtils.defaultString(alias.stageNo) + "|"
+                    + StringUtils.defaultString(alias.fieldPrefix) + "|"
+                    + StringUtils.defaultString(alias.fieldName) + "|"
+                    + StringUtils.defaultString(alias.sourceTable) + "|"
+                    + StringUtils.defaultString(alias.sourceColumn);
+            if (exists.add(key)) {
+                target.add(alias);
+            }
         }
-        return field1.compareToIgnoreCase(field2);
     }
 
-    private int getCycleFieldLevel(String field) {
-        if (StringUtils.isBlank(field) || !field.startsWith("L")) {
-            return Integer.MAX_VALUE;
+    private String getCycleFieldPrefix(String tableName, int index) {
+        String prefix = CYCLE_TABLE_FIELD_PREFIX_MAP.get(tableName);
+        if (StringUtils.isNotBlank(prefix)) {
+            return prefix;
         }
-        int end = field.indexOf('_');
-        if (end <= 1) {
-            return Integer.MAX_VALUE;
+        return "L" + index;
+    }
+
+    private int getCycleStageOrder(String tableName, int index) {
+        String stageNo = CYCLE_TABLE_STAGE_NO_MAP.get(tableName);
+        if (StringUtils.isBlank(stageNo)) {
+            return 1000 + index;
         }
         try {
-            return Integer.parseInt(field.substring(1, end));
+            return Integer.parseInt(stageNo);
         } catch (NumberFormatException e) {
-            return Integer.MAX_VALUE;
+            return 1000 + index;
+        }
+    }
+
+    private String getCycleStageNo(String tableName, int index) {
+        String stageNo = CYCLE_TABLE_STAGE_NO_MAP.get(tableName);
+        if (StringUtils.isNotBlank(stageNo)) {
+            return stageNo;
         }
+        return String.format(Locale.ROOT, "%02d", index);
     }
 
     private String toInitials(String text) {
@@ -1008,6 +1408,37 @@ public class ProjectImpl implements ProjectService {
         return ewkt.replaceAll("\\s+", "").toUpperCase(Locale.ROOT);
     }
 
+    private String buildGeomDetailKey(String geomDbId, String geomId, String geom) {
+        if (StringUtils.isNotBlank(geomDbId) && StringUtils.isNotBlank(geomId)) {
+            return geomDbId + "|" + geomId;
+        }
+        if (StringUtils.isNotBlank(geomId)) {
+            return geomId;
+        }
+        return normalizeGeomKey(geom);
+    }
+
+    private String getMapString(Map<String, String> map, String... keys) {
+        if (map == null || ArrayUtils.isEmpty(keys)) {
+            return null;
+        }
+        for (String key : keys) {
+            String value = map.get(key);
+            if (StringUtils.isNotBlank(value)) {
+                return value;
+            }
+        }
+        return null;
+    }
+
+    private String getObjectString(Map<String, Object> map, String key) {
+        if (map == null || StringUtils.isBlank(key)) {
+            return null;
+        }
+        Object value = map.get(key);
+        return value == null ? null : String.valueOf(value);
+    }
+
     private String trimDbfValue(String value) {
         if (value == null) {
             return "";
@@ -1042,16 +1473,80 @@ public class ProjectImpl implements ProjectService {
         response.getOutputStream().flush();
     }
 
+    private static class CycleFieldBuildResult {
+        private final Map<String, Map<String, String>> fieldMap;
+        private final List<CycleFieldAlias> aliases;
+
+        private CycleFieldBuildResult(Map<String, Map<String, String>> fieldMap, List<CycleFieldAlias> aliases) {
+            this.fieldMap = fieldMap;
+            this.aliases = aliases;
+        }
+    }
+
+    private static class CycleExportBuildResult {
+        private final List<ProjectCycleExportNode> exportNodes = new ArrayList<>();
+        private final Map<String, BpBatchAggregate> batchBpByGyGeomKey = new LinkedHashMap<>();
+        private final List<CycleShpRow> bpIntersectRows = new ArrayList<>();
+    }
+
+    private static class BpBatchAggregate {
+        private final Set<String> names = new LinkedHashSet<>();
+        private final Set<String> pfwhs = new LinkedHashSet<>();
+    }
+
+    private static class CycleFieldAlias {
+        private String stageNo;
+        private String fieldPrefix;
+        private String stageName;
+        private String fieldName;
+        private String aliasName;
+        private String sourceTable;
+        private String sourceColumn;
+
+        private static CycleFieldAlias base(String fieldName, String aliasName) {
+            CycleFieldAlias alias = new CycleFieldAlias();
+            alias.fieldName = fieldName;
+            alias.aliasName = aliasName;
+            return alias;
+        }
+
+        private static CycleFieldAlias stage(String stageNo, String fieldPrefix, String stageName, String sourceTable) {
+            CycleFieldAlias alias = new CycleFieldAlias();
+            alias.stageNo = stageNo;
+            alias.fieldPrefix = fieldPrefix;
+            alias.stageName = stageName;
+            alias.aliasName = stageNo + "-" + stageName + "阶段";
+            alias.sourceTable = sourceTable;
+            return alias;
+        }
+
+        private static CycleFieldAlias field(String stageNo, String fieldPrefix, String stageName, String fieldName,
+                                             String aliasName, String sourceTable, String sourceColumn) {
+            CycleFieldAlias alias = new CycleFieldAlias();
+            alias.stageNo = stageNo;
+            alias.fieldPrefix = fieldPrefix;
+            alias.stageName = stageName;
+            alias.fieldName = fieldName;
+            alias.aliasName = aliasName;
+            alias.sourceTable = sourceTable;
+            alias.sourceColumn = sourceColumn;
+            return alias;
+        }
+    }
+
     private static class ProjectCycleExportNode {
         private int level;
         private String tableName;
         private Map<String, Object> node;
         private String geom;
         private String geomKey;
+        private String geomId;
+        private String geomDbId;
     }
 
     private static class CycleShpRow {
         private String geom;
+        private String geomKey;
         private final Map<String, Object> attributes = new LinkedHashMap<>();
     }
 
@@ -1069,6 +1564,185 @@ public class ProjectImpl implements ProjectService {
         return rows;
     }
 
+    private List<Map<String, Object>> filterCycleProjectRows(List<Map<String, Object>> rows,
+                                                             Map<String, Object> queryParams) {
+        if (CollectionUtils.isEmpty(rows) || queryParams == null || queryParams.isEmpty()) {
+            return rows;
+        }
+
+        Map<String, Object> effectiveQuery = new LinkedHashMap<>();
+        for (Map.Entry<String, Object> entry : queryParams.entrySet()) {
+            if (StringUtils.isNotBlank(entry.getKey()) && !isBlankQueryValue(entry.getValue())) {
+                effectiveQuery.put(entry.getKey(), entry.getValue());
+            }
+        }
+        if (effectiveQuery.isEmpty()) {
+            return rows;
+        }
+
+        return rows.stream()
+                .filter(row -> matchesCycleQuery(row, effectiveQuery))
+                .collect(Collectors.toList());
+    }
+
+    private boolean matchesCycleQuery(Map<String, Object> row, Map<String, Object> queryParams) {
+        for (Map.Entry<String, Object> entry : queryParams.entrySet()) {
+            if (!matchesCycleValue(entry.getKey(), row.get(entry.getKey()), entry.getValue())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean matchesCycleValue(String field, Object rowValue, Object queryValue) {
+        if (isBlankQueryValue(queryValue)) {
+            return true;
+        }
+        if (rowValue == null) {
+            return false;
+        }
+
+        if (queryValue instanceof Collection) {
+            List<Object> values = ((Collection<?>) queryValue).stream()
+                    .filter(value -> !isBlankQueryValue(value))
+                    .collect(Collectors.toList());
+            if (values.isEmpty()) {
+                return true;
+            }
+            if (values.size() == 2 && isDateField(field, rowValue, values.get(0))) {
+                return matchesDateRange(rowValue, values.get(0), values.get(1));
+            }
+            return values.stream().anyMatch(value -> matchesCycleValue(field, rowValue, value));
+        }
+
+        if (queryValue.getClass().isArray()) {
+            int length = java.lang.reflect.Array.getLength(queryValue);
+            List<Object> values = new ArrayList<>();
+            for (int i = 0; i < length; i++) {
+                values.add(java.lang.reflect.Array.get(queryValue, i));
+            }
+            return matchesCycleValue(field, rowValue, values);
+        }
+
+        if (isDateField(field, rowValue, queryValue)) {
+            String rowDate = normalizeDateValue(rowValue);
+            String queryDate = normalizeDateValue(queryValue);
+            return StringUtils.isNotBlank(rowDate) && rowDate.equals(queryDate);
+        }
+
+        BigDecimal rowNumber = toBigDecimal(rowValue);
+        BigDecimal queryNumber = toBigDecimal(queryValue);
+        if (rowNumber != null && queryNumber != null) {
+            return rowNumber.compareTo(queryNumber) == 0;
+        }
+
+        String rowText = String.valueOf(rowValue).trim();
+        String queryText = String.valueOf(queryValue).trim();
+        if (isExactMatchField(field)) {
+            return rowText.equals(queryText);
+        }
+        return rowText.toLowerCase(Locale.ROOT).contains(queryText.toLowerCase(Locale.ROOT));
+    }
+
+    private boolean matchesDateRange(Object rowValue, Object startValue, Object endValue) {
+        String rowDate = normalizeDateValue(rowValue);
+        String startDate = normalizeDateValue(startValue);
+        String endDate = normalizeDateValue(endValue);
+        if (StringUtils.isBlank(rowDate)) {
+            return false;
+        }
+        if (StringUtils.isNotBlank(startDate) && rowDate.compareTo(startDate) < 0) {
+            return false;
+        }
+        return StringUtils.isBlank(endDate) || rowDate.compareTo(endDate) <= 0;
+    }
+
+    private boolean isBlankQueryValue(Object value) {
+        if (value == null) {
+            return true;
+        }
+        if (value instanceof String) {
+            return StringUtils.isBlank((String) value);
+        }
+        if (value instanceof Collection) {
+            return ((Collection<?>) value).stream().allMatch(this::isBlankQueryValue);
+        }
+        if (value.getClass().isArray()) {
+            int length = java.lang.reflect.Array.getLength(value);
+            for (int i = 0; i < length; i++) {
+                if (!isBlankQueryValue(java.lang.reflect.Array.get(value, i))) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isDateField(String field, Object rowValue, Object queryValue) {
+        String lowerField = field == null ? "" : field.toLowerCase(Locale.ROOT);
+        return lowerField.contains("date")
+                || lowerField.contains("time")
+                || lowerField.endsWith("rq")
+                || looksLikeDate(rowValue)
+                || looksLikeDate(queryValue);
+    }
+
+    private boolean isUnitField(String field) {
+        if (StringUtils.isBlank(field)) {
+            return false;
+        }
+        String lowerField = field.toLowerCase(Locale.ROOT);
+        return lowerField.endsWith("unit") || lowerField.equals("unit");
+    }
+
+    private boolean isExactMatchField(String field) {
+        if (StringUtils.isBlank(field)) {
+            return false;
+        }
+        String lowerField = field.toLowerCase(Locale.ROOT);
+        return isUnitField(field)
+                || lowerField.contains("status")
+                || lowerField.endsWith("state")
+                || lowerField.endsWith("type")
+                || lowerField.endsWith("zt");
+    }
+
+    private boolean looksLikeDate(Object value) {
+        if (value == null) {
+            return false;
+        }
+        String text = String.valueOf(value).trim();
+        return text.matches("\\d{4}-\\d{1,2}-\\d{1,2}.*")
+                || text.matches("\\d{4}/\\d{1,2}/\\d{1,2}.*");
+    }
+
+    private String normalizeDateValue(Object value) {
+        if (value == null) {
+            return "";
+        }
+        String text = String.valueOf(value).trim().replace('/', '-');
+        if (text.length() >= 10 && looksLikeDate(text)) {
+            return text.substring(0, 10);
+        }
+        return text;
+    }
+
+    private BigDecimal toBigDecimal(Object value) {
+        if (value == null) {
+            return null;
+        }
+        String text = String.valueOf(value).trim();
+        if (StringUtils.isBlank(text)) {
+            return null;
+        }
+        try {
+            return new BigDecimal(text);
+        } catch (NumberFormatException ex) {
+            return null;
+        }
+    }
+
     private Map<String, Object> buildCycleProjectRow(Project project, ProjectWorkflow workflow,
                                                      Map<String, Object> nodeData, String worktype,
                                                      Map<String, String> geomDetail) {

+ 18 - 13
siwei-modules/siwei-apply/src/main/resources/mapper/NodeLandMapper.xml

@@ -45,18 +45,6 @@
         GROUP BY nl.geom_db_id, tgd.shppath
     </select>
 
-    <select id="selectUnionGeomByNodeId" resultType="map" parameterType="String">
-        SELECT
-            nl.geom_db_id as "geomDbId",
-            public.ST_AsEWKT(public.ST_Union(gd.geom)) as "geom"
-        FROM t_node_land nl
-        LEFT JOIN t_geom_db_details gd ON nl.geom_db_id = gd.upload_id
-        WHERE nl.node_id = #{nodeId}
-          AND gd.geom IS NOT NULL
-        GROUP BY nl.geom_db_id
-        LIMIT 1
-    </select>
-
     <!-- 根据node_id查询地块数量信息 -->
     <select id="selectCountGeomByNodeId" >
         SELECT
@@ -107,7 +95,12 @@
 
     <!--  根据nodeId,查询出当前具体的图斑ID -->
     <select id="selectGeomDbDetailsByNodeId" parameterType="String" resultType="java.util.Map">
-        select  details.id, public.st_asewkt(details.geom) AS  geom, details.upload_id, details.geom_json, details.sort, details.geom_area
+        select  details.id as "id",
+                public.st_asewkt(details.geom) AS "geom",
+                details.upload_id as "uploadId",
+                details.geom_json as "geomJson",
+                details.sort as "sort",
+                details.geom_area as "geomArea"
         from t_geom_db_details  details
                  INNER JOIN   t_node_land node    on  node.geom_db_id=details.upload_id
         <where>
@@ -121,6 +114,18 @@
         </where>
     </select>
 
+    <select id="existsGeomIntersect" resultType="boolean">
+        SELECT EXISTS (
+            SELECT 1
+            FROM t_geom_db_details source
+                     JOIN t_geom_db_details target ON target.id = #{targetGeomId}
+            WHERE source.id = #{sourceGeomId}
+              AND source.geom IS NOT NULL
+              AND target.geom IS NOT NULL
+              AND public.ST_Intersects(source.geom, target.geom)
+        )
+    </select>
+
     <select id="selectGeomContainsNode" parameterType="String" resultType="map">
         SELECT  geom.id , node.node_id   FROM  "t_geom_db_details"  geom
                 LEFT JOIN "public"."t_geom_db"  upload   on geom.upload_id=upload.id