Просмотр исходного кода

Merge remote-tracking branch 'origin/master'

林仔 4 дней назад
Родитель
Сommit
12af17fec9
33 измененных файлов с 770 добавлено и 105 удалено
  1. 4 0
      pipe-network-service/CONTEXT.md
  2. 13 7
      pipe-network-service/sql/water_supply_facility_test_data.sql
  3. 1 1
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/WaterSupply/PipeNetworkStatistics/PipeNetworkStatisticsController.java
  4. 7 6
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/WaterSupply/WaterFacilityExportController.java
  5. 4 2
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/WaterSupply/WaterPipeInfo/WaterPipeInfoController.java
  6. 20 1
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/WaterSupply/WaterSupplyGisMap/WaterSupplyGisMapController.java
  7. 9 9
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/WaterSupply/WaterUserInfo/WaterUserInfoController.java
  8. 23 0
      pipe-network-service/zksy-admin/src/test/java/com/zksy/web/controller/WaterSupply/PipeNetworkStatistics/PipeNetworkStatisticsControllerTerminologyTest.java
  9. 32 0
      pipe-network-service/zksy-admin/src/test/java/com/zksy/web/controller/WaterSupply/WaterFacilityExportControllerContractTest.java
  10. 53 0
      pipe-network-service/zksy-admin/src/test/java/com/zksy/web/controller/WaterSupply/WaterSupplyGisMap/WaterSupplyGisMapControllerTest.java
  11. 1 1
      pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/PipeNetworkStatistics/service/impl/PipeNetworkStatisticsExportServiceImpl.java
  12. 3 1
      pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/WaterPipeInfo/service/IWaterPipeInfoService.java
  13. 14 7
      pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/WaterPipeInfo/service/impl/WaterPipeInfoServiceImpl.java
  14. 11 5
      pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/WaterPlantInfo/service/impl/WaterPlantInfoServiceImpl.java
  15. 11 5
      pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/WaterPumpStation/service/impl/WaterPumpStationServiceImpl.java
  16. 11 5
      pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/WaterSourceInfo/service/impl/WaterSourceInfoServiceImpl.java
  17. 52 8
      pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/WaterSupplyGisMap/service/impl/WaterSupplyGisMapServiceImpl.java
  18. 6 6
      pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/WaterUserInfo/domain/WaterUserInfo.java
  19. 11 5
      pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/WaterUserInfo/service/impl/WaterUserInfoServiceImpl.java
  20. 2 1
      pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/export/WaterPipeExportDTO.java
  21. 3 3
      pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/export/WaterUserExportDTO.java
  22. 12 6
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/domain/PipeNetworkBase.java
  23. 1 1
      pipe-network-service/zksy-system/src/main/resources/mapper/WaterSupply/PipeNetworkStatistics/PipeNetworkStatisticsMapper.xml
  24. 8 8
      pipe-network-service/zksy-system/src/main/resources/mapper/WaterSupply/WaterPipeInfo/WaterPipeInfoMapper.xml
  25. 4 4
      pipe-network-service/zksy-system/src/main/resources/mapper/WaterSupply/WaterPlantInfo/WaterPlantInfoMapper.xml
  26. 4 4
      pipe-network-service/zksy-system/src/main/resources/mapper/WaterSupply/WaterPumpStation/WaterPumpStationMapper.xml
  27. 4 4
      pipe-network-service/zksy-system/src/main/resources/mapper/WaterSupply/WaterSourceInfo/WaterSourceInfoMapper.xml
  28. 1 1
      pipe-network-service/zksy-system/src/main/resources/mapper/WaterSupply/WaterSupplyGisMap/WaterSupplyGisMapMapper.xml
  29. 4 4
      pipe-network-service/zksy-system/src/main/resources/mapper/WaterSupply/WaterUserInfo/WaterUserInfoMapper.xml
  30. 27 0
      pipe-network-service/zksy-system/src/test/java/com/zksy/WaterSupply/FacilityCoordinateModelTest.java
  31. 138 0
      pipe-network-service/zksy-system/src/test/java/com/zksy/WaterSupply/WaterFacilityQueryServiceTest.java
  32. 143 0
      pipe-network-service/zksy-system/src/test/java/com/zksy/WaterSupply/WaterSupplyGisMap/WaterSupplyGisMapServiceTest.java
  33. 133 0
      pipe-network-service/zksy-system/src/test/java/com/zksy/WaterSupply/WaterUserTerminologyTest.java

+ 4 - 0
pipe-network-service/CONTEXT.md

@@ -28,6 +28,10 @@ _Avoid_:管网、管道(指单条设施时)
 接受供水服务的居民、工业、商业或行政事业主体。
 _Avoid_:大用水户(除非明确指满足特定用水量或重要性标准的子集)、用户
 
+兼容映射:既有设施类型编码 `user`、接口区块 `waterUser`、Java 类型 `WaterUserInfo`、
+持久化表 `water_user_info` 及其 `user_*` 字段均表示“用水户”,覆盖居民、工业、商业和
+行政事业等全部类型。为保持现有接口与数据兼容,这些技术标识继续保留,不表示“大用水户”子集。
+
 ## 运行监测
 
 **监测设备**:

+ 13 - 7
pipe-network-service/sql/water_supply_facility_test_data.sql

@@ -110,7 +110,7 @@ VALUES
      113.32000000, 23.16000000, NULL, NULL);
 
 -- ------------------------------------------------------------
--- 5. 大用水户:2 个坐标完整 + 1 个缺失坐标
+-- 5. 用水户:居民、工业、商业、行政事业均参与,4 个坐标完整 + 1 个缺失坐标
 -- ------------------------------------------------------------
 INSERT INTO app_user.water_user_info
     (user_code, user_name, user_type, address, contact_person,
@@ -120,12 +120,18 @@ INSERT INTO app_user.water_user_info
 VALUES
     ('GIS-TEST-U-001', 'GIS测试用水户-工业园', '工业', '测试工业园1号', '测试联系人1',
      '13800000005', 'GIS-METER-001', 1000.00, 52000.00, 51000.00, '0',
-     'GIS接口测试-坐标完整-大用水量', '0', 'test', CURRENT_TIMESTAMP, 113.32000000, 23.16000000),
-    ('GIS-TEST-U-002', 'GIS测试用水户-医院', '公共机构', '测试医院2号', '测试联系人2',
+     'GIS接口测试-坐标完整-工业', '0', 'test', CURRENT_TIMESTAMP, 113.32000000, 23.16000000),
+    ('GIS-TEST-U-002', 'GIS测试用水户-医院', '行政事业', '测试医院2号', '测试联系人2',
      '13800000006', 'GIS-METER-002', 500.00, 12500.00, 12000.00, '1',
-     'GIS接口测试-坐标完整-异常', '0', 'test', CURRENT_TIMESTAMP, 113.33000000, 23.17000000),
-    ('GIS-TEST-U-003', 'GIS测试用水户-待测', '商业', '测试商业街3号', '测试联系人3',
-     '13800000007', 'GIS-METER-003', 100.00, 800.00, NULL, NULL,
+     'GIS接口测试-坐标完整-行政事业', '0', 'test', CURRENT_TIMESTAMP, 113.33000000, 23.17000000),
+    ('GIS-TEST-U-003', 'GIS测试用水户-商业街', '商业', '测试商业街3号', '测试联系人3',
+     '13800000007', 'GIS-METER-003', 100.00, 800.00, 700.00, '0',
+     'GIS接口测试-坐标完整-商业', '0', 'test', CURRENT_TIMESTAMP, 113.34000000, 23.18000000),
+    ('GIS-TEST-U-004', 'GIS测试用水户-居民', '居民', '测试小区4号', '测试联系人4',
+     '13800000008', 'GIS-METER-004', 10.00, 120.00, 110.00, '0',
+     'GIS接口测试-坐标完整-居民', '0', 'test', CURRENT_TIMESTAMP, 113.35000000, 23.19000000),
+    ('GIS-TEST-U-005', 'GIS测试用水户-缺失坐标', '商业', '测试商业街5号', '测试联系人5',
+     '13800000009', 'GIS-METER-005', 100.00, 800.00, NULL, NULL,
      'GIS接口测试-缺失坐标-概览计数', '0', 'test', CURRENT_TIMESTAMP, NULL, NULL);
 
 COMMIT;
@@ -133,7 +139,7 @@ COMMIT;
 -- ------------------------------------------------------------
 -- 6. 验证输出
 -- ------------------------------------------------------------
--- 预期:source=3/完整2,plant=2/完整1,pumpStation=2/完整1,pipe=4/完整3,user=3/完整2
+-- 预期:source=3/完整2,plant=2/完整1,pumpStation=2/完整1,pipe=4/完整3,user=5/完整4
 SELECT 'source' facility_type, COUNT(*) total_count,
        COALESCE(SUM(CASE WHEN longitude IS NOT NULL AND latitude IS NOT NULL THEN 1 ELSE 0 END), 0) geo_complete_count
   FROM app_user.water_source_info WHERE source_id LIKE 'GIS-TEST-%'

+ 1 - 1
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/WaterSupply/PipeNetworkStatistics/PipeNetworkStatisticsController.java

@@ -51,7 +51,7 @@ public class PipeNetworkStatisticsController {
 
     @GetMapping("/export")
     @ApiOperation(value = "供水管网-管网数据统计分析-导出统计 Excel",
-            notes = "请求参数与综合统计仪表盘一致。响应为文件流,文件名为管网数据统计分析.xlsx,包含综合概览、管网统计、水厂统计、泵站统计、水源地统计、用水户统计六个工作表。")
+            notes = "请求参数与综合统计仪表盘一致。响应为文件流,文件名为管网数据统计分析.xlsx,包含综合概览、管网统计、水厂统计、泵站统计、水源地统计、用水户统计六个工作表。")
     @Log(title = "管网数据统计分析", businessType = BusinessType.EXPORT)
     @PreAuthorize("@ss.hasPermi('waterSupply:pipeNetwork:statistics:export')")
     public void export(HttpServletResponse response,

+ 7 - 6
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/WaterSupply/WaterFacilityExportController.java

@@ -17,6 +17,7 @@ import com.zksy.common.annotation.Excel;
 import com.zksy.common.enums.BusinessType;
 import com.zksy.common.utils.poi.ExcelUtil;
 import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.format.annotation.DateTimeFormat;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
@@ -39,7 +40,7 @@ public class WaterFacilityExportController {
     @GetMapping("/sourceInfo/export")
     @Log(title = "导出水源地信息", businessType = BusinessType.EXPORT)
     @PreAuthorize("@ss.hasPermi('waterSupply:waterSource:export')")
-    public void sourceExport(HttpServletResponse response, @RequestParam(required = false) String sourceId, @RequestParam(required = false) String sourceName, @RequestParam(required = false) String status, @RequestParam(required = false) String unit, @RequestParam(required = false) LocalDateTime startTime, @RequestParam(required = false) LocalDateTime endTime) {
+    public void sourceExport(HttpServletResponse response, @RequestParam(required = false) String sourceId, @RequestParam(required = false) String sourceName, @RequestParam(required = false) String status, @RequestParam(required = false) String unit, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) {
         var page = sourceService.queryPage(new Page<WaterSourceInfo>(1, Integer.MAX_VALUE), sourceId, sourceName, status, unit, startTime, endTime);
         new ExcelUtil<>(WaterSourceExportDTO.class).exportExcel(response, page.getRecords().stream().map(WaterSourceExportDTO::from).collect(Collectors.toList()), "水源地信息");
     }
@@ -47,7 +48,7 @@ public class WaterFacilityExportController {
     @GetMapping("/plantInfo/export")
     @Log(title = "导出水厂信息", businessType = BusinessType.EXPORT)
     @PreAuthorize("@ss.hasPermi('waterSupply:waterPlant:export')")
-    public void plantExport(HttpServletResponse response, @RequestParam(required = false) String plantCode, @RequestParam(required = false) String plantName, @RequestParam(required = false) String status, @RequestParam(required = false) String principal, @RequestParam(required = false) LocalDateTime startTime, @RequestParam(required = false) LocalDateTime endTime) {
+    public void plantExport(HttpServletResponse response, @RequestParam(required = false) String plantCode, @RequestParam(required = false) String plantName, @RequestParam(required = false) String status, @RequestParam(required = false) String principal, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) {
         var page = plantService.queryPage(new Page<WaterPlantInfo>(1, Integer.MAX_VALUE), plantCode, plantName, status, principal, startTime, endTime);
         new ExcelUtil<>(WaterPlantExportDTO.class).exportExcel(response, page.getRecords().stream().map(WaterPlantExportDTO::from).collect(Collectors.toList()), "水厂信息");
     }
@@ -55,7 +56,7 @@ public class WaterFacilityExportController {
     @GetMapping("/pumpStation/export")
     @Log(title = "导出泵站信息", businessType = BusinessType.EXPORT)
     @PreAuthorize("@ss.hasPermi('waterSupply:waterPumpStation:export')")
-    public void pumpExport(HttpServletResponse response, @RequestParam(required = false) String stationCode, @RequestParam(required = false) String stationName, @RequestParam(required = false) String status, @RequestParam(required = false) String principal, @RequestParam(required = false) LocalDateTime startTime, @RequestParam(required = false) LocalDateTime endTime) {
+    public void pumpExport(HttpServletResponse response, @RequestParam(required = false) String stationCode, @RequestParam(required = false) String stationName, @RequestParam(required = false) String status, @RequestParam(required = false) String principal, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) {
         var page = pumpService.queryPage(new Page<WaterPumpStation>(1, Integer.MAX_VALUE), stationCode, stationName, status, principal, startTime, endTime);
         new ExcelUtil<>(WaterPumpStationExportDTO.class).exportExcel(response, page.getRecords().stream().map(WaterPumpStationExportDTO::from).collect(Collectors.toList()), "泵站信息");
     }
@@ -63,15 +64,15 @@ public class WaterFacilityExportController {
     @GetMapping("/pipe/export")
     @Log(title = "导出管网信息", businessType = BusinessType.EXPORT)
     @PreAuthorize("@ss.hasPermi('waterSupply:waterPipe:export')")
-    public void pipeExport(HttpServletResponse response, @RequestParam(required = false) String pipeCode, @RequestParam(required = false) String pipeName, @RequestParam(required = false) String pipeMaterial, @RequestParam(required = false) String status, @RequestParam(required = false) LocalDateTime startTime, @RequestParam(required = false) LocalDateTime endTime) {
-        var page = pipeService.queryPage(new Page<WaterPipeInfo>(1, Integer.MAX_VALUE), pipeCode, pipeName, pipeMaterial, status, startTime, endTime);
+    public void pipeExport(HttpServletResponse response, @RequestParam(required = false) String pipeCode, @RequestParam(required = false) String pipeName, @RequestParam(required = false) String pipeMaterial, @RequestParam(required = false) String status, @RequestParam(required = false) Integer pipeDiameter, @RequestParam(required = false) Integer layingYear, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) {
+        var page = pipeService.queryPage(new Page<WaterPipeInfo>(1, Integer.MAX_VALUE), pipeCode, pipeName, pipeMaterial, status, pipeDiameter, layingYear, startTime, endTime);
         new ExcelUtil<>(WaterPipeExportDTO.class).exportExcel(response, page.getRecords().stream().map(WaterPipeExportDTO::from).collect(Collectors.toList()), "管网信息");
     }
 
     @GetMapping("/userInfo/export")
     @Log(title = "导出用水户信息", businessType = BusinessType.EXPORT)
     @PreAuthorize("@ss.hasPermi('waterSupply:waterUser:export')")
-    public void userExport(HttpServletResponse response, @RequestParam(required = false) String userCode, @RequestParam(required = false) String userName, @RequestParam(required = false) String status, @RequestParam(required = false) String userType, @RequestParam(required = false) LocalDateTime startTime, @RequestParam(required = false) LocalDateTime endTime) {
+    public void userExport(HttpServletResponse response, @RequestParam(required = false) String userCode, @RequestParam(required = false) String userName, @RequestParam(required = false) String status, @RequestParam(required = false) String userType, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) {
         var page = userService.queryPage(new Page<WaterUserInfo>(1, Integer.MAX_VALUE), userCode, userName, status, userType, startTime, endTime);
         new ExcelUtil<>(WaterUserExportDTO.class).exportExcel(response, page.getRecords().stream().map(WaterUserExportDTO::from).collect(Collectors.toList()), "用水户信息");
     }

+ 4 - 2
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/WaterSupply/WaterPipeInfo/WaterPipeInfoController.java

@@ -48,11 +48,13 @@ public class WaterPipeInfoController {
             @ApiParam(value = "管网名称") @RequestParam(required = false) String pipeName,
             @ApiParam(value = "管网材质") @RequestParam(required = false) String pipeMaterial,
             @ApiParam(value = "状态(0正常 1停用)") @RequestParam(required = false) String status,
+            @ApiParam(value = "管径(毫米)") @RequestParam(required = false) Integer pipeDiameter,
+            @ApiParam(value = "铺设年份") @RequestParam(required = false) Integer layingYear,
             @ApiParam(value = "开始时间(格式:yyyy-MM-dd HH:mm:ss)", example = "2026-01-01 00:00:00") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
             @ApiParam(value = "结束时间(格式:yyyy-MM-dd HH:mm:ss)", example = "2026-12-31 23:59:59") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) {
         try {
             Page<WaterPipeInfo> page = new Page<>(pageNum, pageSize);
-            IPage<WaterPipeInfo> resultPage = waterPipeInfoService.queryPage(page, pipeCode, pipeName, pipeMaterial, status, startTime, endTime);
+            IPage<WaterPipeInfo> resultPage = waterPipeInfoService.queryPage(page, pipeCode, pipeName, pipeMaterial, status, pipeDiameter, layingYear, startTime, endTime);
             return AjaxResult.success("查询成功", resultPage);
         } catch (Exception e) {
             log.error("分页查询管网数据异常", e);
@@ -287,4 +289,4 @@ public class WaterPipeInfoController {
             return AjaxResult.error("彻底删除失败:" + e.getMessage());
         }
     }
-}
+}

+ 20 - 1
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/WaterSupply/WaterSupplyGisMap/WaterSupplyGisMapController.java

@@ -49,9 +49,11 @@ public class WaterSupplyGisMapController {
             @PathVariable Long id) {
         try {
             Object detail = gisMapService.getDetail(facilityType, id);
-            return detail == null ? AjaxResult.error("设施不存在") : AjaxResult.success(detail);
+            return detail == null ? AjaxResult.warn(notFoundMessage(facilityType)) : AjaxResult.success(detail);
         } catch (IllegalArgumentException e) {
             return AjaxResult.error(e.getMessage());
+        } catch (Exception e) {
+            return AjaxResult.error("查询失败:" + e.getMessage());
         }
     }
 
@@ -67,4 +69,21 @@ public class WaterSupplyGisMapController {
         if (text == null || text.trim().isEmpty()) return Collections.emptySet();
         return Arrays.stream(text.split(",")).map(String::trim).filter(s -> !s.isEmpty()).collect(Collectors.toSet());
     }
+
+    private String notFoundMessage(String facilityType) {
+        switch (facilityType) {
+            case "source":
+                return "未找到该水源地信息";
+            case "plant":
+                return "未找到该水厂信息";
+            case "pumpStation":
+                return "未找到该泵站信息";
+            case "pipe":
+                return "未找到该供水管线信息";
+            case "user":
+                return "未找到该用水户信息";
+            default:
+                return "设施不存在";
+        }
+    }
 }

+ 9 - 9
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/WaterSupply/WaterUserInfo/WaterUserInfoController.java

@@ -34,7 +34,7 @@ public class WaterUserInfoController {
 
     // -------------------------- 基础CRUD接口 --------------------------
     @GetMapping("/page")
-    @ApiOperation(value = "分页查询用水户数据", notes = "支持按用户编号、名称、状态、用户类型、时间范围模糊查询")
+    @ApiOperation(value = "分页查询用水户数据", notes = "支持按用户编号、名称、状态、类型、时间范围模糊查询")
     @ApiResponses({
             @ApiResponse(code = 200, message = "查询成功", response = AjaxResult.class),
             @ApiResponse(code = 500, message = "服务器内部错误,查询失败", response = AjaxResult.class)
@@ -42,10 +42,10 @@ public class WaterUserInfoController {
     public AjaxResult queryPage(
             @ApiParam(value = "页码", defaultValue = "1") @RequestParam(defaultValue = "1") Integer pageNum,
             @ApiParam(value = "每页大小", defaultValue = "10") @RequestParam(defaultValue = "10") Integer pageSize,
-            @ApiParam(value = "用户编号") @RequestParam(required = false) String userCode,
-            @ApiParam(value = "用户名称") @RequestParam(required = false) String userName,
-            @ApiParam(value = "用户状态(0正常 1欠费 2停用)") @RequestParam(required = false) String status,
-            @ApiParam(value = "用户类型(居民/工业/商业/行政事业)") @RequestParam(required = false) String userType,
+            @ApiParam(value = "用户编号") @RequestParam(required = false) String userCode,
+            @ApiParam(value = "用户名称") @RequestParam(required = false) String userName,
+            @ApiParam(value = "用户状态(0正常 1欠费 2停用)") @RequestParam(required = false) String status,
+            @ApiParam(value = "用户类型(居民/工业/商业/行政事业)") @RequestParam(required = false) String userType,
             @ApiParam(value = "开始时间(格式:yyyy-MM-dd HH:mm:ss)", example = "2025-01-01 00:00:00") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
             @ApiParam(value = "结束时间(格式:yyyy-MM-dd HH:mm:ss)", example = "2025-12-31 23:59:59") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) {
         try {
@@ -160,9 +160,9 @@ public class WaterUserInfoController {
     public AjaxResult recycleList(
             @ApiParam(value = "页码", defaultValue = "1") @RequestParam(defaultValue = "1") Integer pageNum,
             @ApiParam(value = "每页大小", defaultValue = "10") @RequestParam(defaultValue = "10") Integer pageSize,
-            @ApiParam(value = "用户编号") @RequestParam(required = false) String userCode,
-            @ApiParam(value = "用户名称") @RequestParam(required = false) String userName,
-            @ApiParam(value = "用户状态(0正常 1欠费 2停用)") @RequestParam(required = false) String status,
+            @ApiParam(value = "用户编号") @RequestParam(required = false) String userCode,
+            @ApiParam(value = "用户名称") @RequestParam(required = false) String userName,
+            @ApiParam(value = "用户状态(0正常 1欠费 2停用)") @RequestParam(required = false) String status,
             @ApiParam(value = "开始时间(格式:yyyy-MM-dd HH:mm:ss)", example = "2025-01-01 00:00:00") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
             @ApiParam(value = "结束时间(格式:yyyy-MM-dd HH:mm:ss)", example = "2025-12-31 23:59:59") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) {
         try {
@@ -223,4 +223,4 @@ public class WaterUserInfoController {
             return AjaxResult.error("彻底删除失败:" + e.getMessage());
         }
     }
-}
+}

+ 23 - 0
pipe-network-service/zksy-admin/src/test/java/com/zksy/web/controller/WaterSupply/PipeNetworkStatistics/PipeNetworkStatisticsControllerTerminologyTest.java

@@ -0,0 +1,23 @@
+package com.zksy.web.controller.WaterSupply.PipeNetworkStatistics;
+
+import io.swagger.annotations.ApiOperation;
+import org.junit.jupiter.api.Test;
+
+import javax.servlet.http.HttpServletResponse;
+import java.lang.reflect.Method;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class PipeNetworkStatisticsControllerTerminologyTest {
+
+    @Test
+    void exportSwaggerDescriptionUsesUnifiedWaterUserWorksheetName() throws Exception {
+        Method export = PipeNetworkStatisticsController.class.getMethod(
+                "export", HttpServletResponse.class, String.class, String.class);
+        ApiOperation operation = export.getAnnotation(ApiOperation.class);
+
+        assertTrue(operation.notes().contains("用水户统计"));
+        assertFalse(operation.notes().contains("大用水户统计"));
+    }
+}

+ 32 - 0
pipe-network-service/zksy-admin/src/test/java/com/zksy/web/controller/WaterSupply/WaterFacilityExportControllerContractTest.java

@@ -0,0 +1,32 @@
+package com.zksy.web.controller.WaterSupply;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.lang.reflect.Method;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class WaterFacilityExportControllerContractTest {
+
+    @Test
+    void allFacilityExportsUseTheFrontendDateTimeContract() {
+        for (String methodName : new String[]{"sourceExport", "plantExport", "pumpExport", "pipeExport", "userExport"}) {
+            Method method = Arrays.stream(WaterFacilityExportController.class.getDeclaredMethods())
+                    .filter(candidate -> candidate.getName().equals(methodName))
+                    .findFirst()
+                    .orElseThrow();
+
+            Arrays.stream(method.getParameters())
+                    .filter(parameter -> parameter.getType().equals(LocalDateTime.class))
+                    .forEach(parameter -> {
+                        DateTimeFormat format = parameter.getAnnotation(DateTimeFormat.class);
+                        assertTrue(format != null, methodName + " must declare @DateTimeFormat");
+                        assertEquals("yyyy-MM-dd HH:mm:ss", format.pattern(), methodName);
+                    });
+        }
+    }
+}

+ 53 - 0
pipe-network-service/zksy-admin/src/test/java/com/zksy/web/controller/WaterSupply/WaterSupplyGisMap/WaterSupplyGisMapControllerTest.java

@@ -0,0 +1,53 @@
+package com.zksy.web.controller.WaterSupply.WaterSupplyGisMap;
+
+import com.zksy.WaterSupply.WaterSupplyGisMap.service.WaterSupplyGisMapService;
+import com.zksy.common.core.domain.AjaxResult;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+
+class WaterSupplyGisMapControllerTest {
+
+    @Test
+    void missingFacilityDetailUsesTheExistingFacilitySpecificWarning() {
+        WaterSupplyGisMapService service = mock(WaterSupplyGisMapService.class);
+        WaterSupplyGisMapController controller = controllerWith(service);
+
+        for (Map.Entry<String, String> expected : Map.of(
+                "source", "未找到该水源地信息",
+                "plant", "未找到该水厂信息",
+                "pumpStation", "未找到该泵站信息",
+                "pipe", "未找到该供水管线信息",
+                "user", "未找到该用水户信息").entrySet()) {
+            when(service.getDetail(expected.getKey(), 1L)).thenReturn(null);
+            AjaxResult result = controller.detail(expected.getKey(), 1L);
+
+            assertEquals(601, result.get(AjaxResult.CODE_TAG));
+            assertTrue(result.get(AjaxResult.MSG_TAG).toString().contains(expected.getValue()));
+        }
+    }
+
+    @Test
+    void detailQueryFailureUsesTheExistingExplainableError() {
+        WaterSupplyGisMapService service = mock(WaterSupplyGisMapService.class);
+        when(service.getDetail("plant", 2L)).thenThrow(new IllegalStateException("数据库暂不可用"));
+        WaterSupplyGisMapController controller = controllerWith(service);
+
+        AjaxResult result = controller.detail("plant", 2L);
+
+        assertEquals(500, result.get(AjaxResult.CODE_TAG));
+        assertEquals("查询失败:数据库暂不可用", result.get(AjaxResult.MSG_TAG));
+    }
+
+    private static WaterSupplyGisMapController controllerWith(WaterSupplyGisMapService service) {
+        WaterSupplyGisMapController controller = new WaterSupplyGisMapController();
+        ReflectionTestUtils.setField(controller, "gisMapService", service);
+        return controller;
+    }
+}

+ 1 - 1
pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/PipeNetworkStatistics/service/impl/PipeNetworkStatisticsExportServiceImpl.java

@@ -27,7 +27,7 @@ public class PipeNetworkStatisticsExportServiceImpl implements PipeNetworkStatis
             writeMapSheet(workbook.createSheet("水厂统计"), dashboard.getWaterPlant());
             writeMapSheet(workbook.createSheet("泵站统计"), dashboard.getPumpStation());
             writeMapSheet(workbook.createSheet("水源地统计"), dashboard.getWaterSource());
-            writeMapSheet(workbook.createSheet("用水户统计"), dashboard.getWaterUser());
+            writeMapSheet(workbook.createSheet("用水户统计"), dashboard.getWaterUser());
             workbook.write(outputStream);
         }
     }

+ 3 - 1
pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/WaterPipeInfo/service/IWaterPipeInfoService.java

@@ -22,6 +22,8 @@ public interface IWaterPipeInfoService extends IService<WaterPipeInfo> {
                                    String pipeName,
                                    String pipeMaterial,
                                    String status,
+                                   Integer pipeDiameter,
+                                   Integer layingYear,
                                    LocalDateTime startTime,
                                    LocalDateTime endTime);
 
@@ -64,4 +66,4 @@ public interface IWaterPipeInfoService extends IService<WaterPipeInfo> {
      * 批量彻底物理删除数据
      */
     boolean deleteByIdsPhysically(List<Long> ids);
-}
+}

+ 14 - 7
pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/WaterPipeInfo/service/impl/WaterPipeInfoServiceImpl.java

@@ -8,12 +8,10 @@ import com.zksy.WaterSupply.WaterPipeInfo.domain.WaterPipeInfo;
 import com.zksy.WaterSupply.WaterPipeInfo.mapper.WaterPipeInfoMapper;
 import com.zksy.WaterSupply.WaterPipeInfo.service.IWaterPipeInfoService;
 import org.springframework.stereotype.Service;
-import org.springframework.util.StringUtils;
 
 import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 
 @Service
 public class WaterPipeInfoServiceImpl extends ServiceImpl<WaterPipeInfoMapper, WaterPipeInfo>
@@ -25,12 +23,21 @@ public class WaterPipeInfoServiceImpl extends ServiceImpl<WaterPipeInfoMapper, W
                                           String pipeName,
                                           String pipeMaterial,
                                           String status,
+                                          Integer pipeDiameter,
+                                          Integer layingYear,
                                           LocalDateTime startTime,
                                           LocalDateTime endTime) {
-        List<WaterPipeInfo> list = baseMapper.selectPipeList(pipeCode, pipeName, pipeMaterial, status, startTime, endTime);
-        page.setRecords(list);
-        page.setTotal(list.size());
-        return page;
+        LambdaQueryWrapper<WaterPipeInfo> query = new LambdaQueryWrapper<WaterPipeInfo>()
+                .like(pipeCode != null && !pipeCode.isEmpty(), WaterPipeInfo::getPipeCode, pipeCode)
+                .like(pipeName != null && !pipeName.isEmpty(), WaterPipeInfo::getPipeName, pipeName)
+                .eq(pipeMaterial != null && !pipeMaterial.isEmpty(), WaterPipeInfo::getPipeMaterial, pipeMaterial)
+                .eq(status != null && !status.isEmpty(), WaterPipeInfo::getStatus, status)
+                .eq(pipeDiameter != null, WaterPipeInfo::getPipeDiameter, pipeDiameter)
+                .eq(layingYear != null, WaterPipeInfo::getLayingYear, layingYear)
+                .ge(startTime != null, WaterPipeInfo::getCreateTime, startTime)
+                .le(endTime != null, WaterPipeInfo::getCreateTime, endTime)
+                .orderByDesc(WaterPipeInfo::getCreateTime);
+        return baseMapper.selectPage(page, query);
     }
 
     @Override
@@ -82,4 +89,4 @@ public class WaterPipeInfoServiceImpl extends ServiceImpl<WaterPipeInfoMapper, W
         }
         return baseMapper.deleteByIdsPhysically(ids) > 0;
     }
-}
+}

+ 11 - 5
pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/WaterPlantInfo/service/impl/WaterPlantInfoServiceImpl.java

@@ -1,5 +1,6 @@
 package com.zksy.WaterSupply.WaterPlantInfo.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -23,10 +24,15 @@ public class WaterPlantInfoServiceImpl extends ServiceImpl<WaterPlantInfoMapper,
                                            String principal,
                                            LocalDateTime startTime,
                                            LocalDateTime endTime) {
-        List<WaterPlantInfo> list = baseMapper.selectPlantList(plantCode, plantName, status, principal, startTime, endTime);
-        page.setRecords(list);
-        page.setTotal(list.size());
-        return page;
+        LambdaQueryWrapper<WaterPlantInfo> query = new LambdaQueryWrapper<WaterPlantInfo>()
+                .like(plantCode != null && !plantCode.isEmpty(), WaterPlantInfo::getPlantCode, plantCode)
+                .like(plantName != null && !plantName.isEmpty(), WaterPlantInfo::getPlantName, plantName)
+                .eq(status != null && !status.isEmpty(), WaterPlantInfo::getStatus, status)
+                .like(principal != null && !principal.isEmpty(), WaterPlantInfo::getPrincipal, principal)
+                .ge(startTime != null, WaterPlantInfo::getCreateTime, startTime)
+                .le(endTime != null, WaterPlantInfo::getCreateTime, endTime)
+                .orderByDesc(WaterPlantInfo::getCreateTime);
+        return baseMapper.selectPage(page, query);
     }
 
     @Override
@@ -57,4 +63,4 @@ public class WaterPlantInfoServiceImpl extends ServiceImpl<WaterPlantInfoMapper,
         }
         return baseMapper.deleteByIdsPhysically(ids) > 0;
     }
-}
+}

+ 11 - 5
pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/WaterPumpStation/service/impl/WaterPumpStationServiceImpl.java

@@ -1,5 +1,6 @@
 package com.zksy.WaterSupply.WaterPumpStation.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -23,10 +24,15 @@ public class WaterPumpStationServiceImpl extends ServiceImpl<WaterPumpStationMap
                                              String principal,
                                              LocalDateTime startTime,
                                              LocalDateTime endTime) {
-        List<WaterPumpStation> list = baseMapper.selectStationList(stationCode, stationName, status, principal, startTime, endTime);
-        page.setRecords(list);
-        page.setTotal(list.size());
-        return page;
+        LambdaQueryWrapper<WaterPumpStation> query = new LambdaQueryWrapper<WaterPumpStation>()
+                .like(stationCode != null && !stationCode.isEmpty(), WaterPumpStation::getStationCode, stationCode)
+                .like(stationName != null && !stationName.isEmpty(), WaterPumpStation::getStationName, stationName)
+                .eq(status != null && !status.isEmpty(), WaterPumpStation::getStatus, status)
+                .like(principal != null && !principal.isEmpty(), WaterPumpStation::getPrincipal, principal)
+                .ge(startTime != null, WaterPumpStation::getCreateTime, startTime)
+                .le(endTime != null, WaterPumpStation::getCreateTime, endTime)
+                .orderByDesc(WaterPumpStation::getCreateTime);
+        return baseMapper.selectPage(page, query);
     }
 
     @Override
@@ -57,4 +63,4 @@ public class WaterPumpStationServiceImpl extends ServiceImpl<WaterPumpStationMap
         }
         return baseMapper.deleteByIdsPhysically(ids) > 0;
     }
-}
+}

+ 11 - 5
pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/WaterSourceInfo/service/impl/WaterSourceInfoServiceImpl.java

@@ -1,5 +1,6 @@
 package com.zksy.WaterSupply.WaterSourceInfo.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -23,10 +24,15 @@ public class WaterSourceInfoServiceImpl extends ServiceImpl<WaterSourceInfoMappe
                                             String unit,
                                             LocalDateTime startTime,
                                             LocalDateTime endTime) {
-        List<WaterSourceInfo> list = baseMapper.selectSourceList(sourceId, sourceName, status, unit, startTime, endTime);
-        page.setRecords(list);
-        page.setTotal(list.size());
-        return page;
+        LambdaQueryWrapper<WaterSourceInfo> query = new LambdaQueryWrapper<WaterSourceInfo>()
+                .like(sourceId != null && !sourceId.isEmpty(), WaterSourceInfo::getSourceId, sourceId)
+                .like(sourceName != null && !sourceName.isEmpty(), WaterSourceInfo::getSourceName, sourceName)
+                .eq(status != null && !status.isEmpty(), WaterSourceInfo::getStatus, status)
+                .like(unit != null && !unit.isEmpty(), WaterSourceInfo::getUnit, unit)
+                .ge(startTime != null, WaterSourceInfo::getCreateTime, startTime)
+                .le(endTime != null, WaterSourceInfo::getCreateTime, endTime)
+                .orderByDesc(WaterSourceInfo::getCreateTime);
+        return baseMapper.selectPage(page, query);
     }
 
     @Override
@@ -53,4 +59,4 @@ public class WaterSourceInfoServiceImpl extends ServiceImpl<WaterSourceInfoMappe
         if (ids == null || ids.isEmpty()) return false;
         return baseMapper.deleteByIdsPhysically(ids) > 0;
     }
-}
+}

+ 52 - 8
pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/WaterSupplyGisMap/service/impl/WaterSupplyGisMapServiceImpl.java

@@ -16,7 +16,6 @@ import javax.annotation.Resource;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -24,8 +23,9 @@ import java.util.Set;
 
 @Service
 public class WaterSupplyGisMapServiceImpl implements WaterSupplyGisMapService {
+    private static final List<String> TYPE_ORDER = List.of("source", "plant", "pumpStation", "pipe", "user");
     private static final Set<String> SUPPORTED_TYPES = Set.of("source", "plant", "pumpStation", "pipe", "user");
-    private static final Map<String, String> TYPE_NAMES = Map.of("source", "水源地", "plant", "水厂", "pumpStation", "泵站", "pipe", "供水管网", "user", "用水户");
+    private static final Map<String, String> TYPE_NAMES = Map.of("source", "水源地", "plant", "水厂", "pumpStation", "泵站", "pipe", "供水管网", "user", "用水户");
 
     @Resource private WaterSupplyGisMapMapper mapper;
     @Resource private IWaterSourceInfoService sourceService;
@@ -62,18 +62,44 @@ public class WaterSupplyGisMapServiceImpl implements WaterSupplyGisMapService {
     @Override
     public List<GisOverviewItemDTO> getOverview() {
         List<GisOverviewItemDTO> rows = mapper.selectOverview();
-        if (rows == null || rows.isEmpty()) return Collections.emptyList();
+        Map<String, GisOverviewItemDTO> rowsByType = rows == null
+                ? Collections.emptyMap()
+                : rows.stream().collect(java.util.stream.Collectors.toMap(
+                        GisOverviewItemDTO::getFacilityType,
+                        item -> item,
+                        (left, right) -> left));
+        List<GisOverviewItemDTO> result = new ArrayList<>();
+        for (String type : TYPE_ORDER) {
+            GisOverviewItemDTO item = rowsByType.getOrDefault(type, emptyOverviewItem(type));
+            item.setTotalCount(zeroIfNull(item.getTotalCount()));
+            item.setGeoCompleteCount(zeroIfNull(item.getGeoCompleteCount()));
+            item.setGeoMissingCount(zeroIfNull(item.getGeoMissingCount()));
+            result.add(item);
+        }
         GisOverviewItemDTO total = new GisOverviewItemDTO();
         total.setFacilityType("total");
         total.setFacilityTypeName("合计");
-        total.setTotalCount(rows.stream().mapToLong(item -> item.getTotalCount() == null ? 0 : item.getTotalCount()).sum());
-        total.setGeoCompleteCount(rows.stream().mapToLong(item -> item.getGeoCompleteCount() == null ? 0 : item.getGeoCompleteCount()).sum());
-        total.setGeoMissingCount(rows.stream().mapToLong(item -> item.getGeoMissingCount() == null ? 0 : item.getGeoMissingCount()).sum());
-        List<GisOverviewItemDTO> result = new ArrayList<>(rows);
+        total.setTotalCount(result.stream().mapToLong(GisOverviewItemDTO::getTotalCount).sum());
+        total.setGeoCompleteCount(result.stream().mapToLong(GisOverviewItemDTO::getGeoCompleteCount).sum());
+        total.setGeoMissingCount(result.stream().mapToLong(GisOverviewItemDTO::getGeoMissingCount).sum());
         result.add(total);
         return result;
     }
 
+    private GisOverviewItemDTO emptyOverviewItem(String type) {
+        GisOverviewItemDTO item = new GisOverviewItemDTO();
+        item.setFacilityType(type);
+        item.setFacilityTypeName(TYPE_NAMES.get(type));
+        item.setTotalCount(0L);
+        item.setGeoCompleteCount(0L);
+        item.setGeoMissingCount(0L);
+        return item;
+    }
+
+    private long zeroIfNull(Long value) {
+        return value == null ? 0L : value;
+    }
+
     @Override
     public GisFeatureDTO toPipeFeature(Map<String, Object> row) {
         GisFeatureDTO feature = baseFeature("pipe", row);
@@ -90,6 +116,7 @@ public class WaterSupplyGisMapServiceImpl implements WaterSupplyGisMapService {
     private void addPointFeatures(GisFeatureCollectionDTO collection, String type, List<Map<String, Object>> rows) {
         if (rows == null) return;
         for (Map<String, Object> row : rows) {
+            if (!hasValues(row, "longitude", "latitude")) continue;
             GisFeatureDTO feature = baseFeature(type, row);
             Map<String, Object> geometry = new LinkedHashMap<>();
             geometry.put("type", "Point");
@@ -100,7 +127,15 @@ public class WaterSupplyGisMapServiceImpl implements WaterSupplyGisMapService {
     }
 
     private void addPipeFeatures(GisFeatureCollectionDTO collection, List<Map<String, Object>> rows) {
-        if (rows != null) for (Map<String, Object> row : rows) collection.getFeatures().add(toPipeFeature(row));
+        if (rows != null) {
+            for (Map<String, Object> row : rows) {
+                if (!hasCoordinate(row, "startLongitude", "start_longitude")
+                        || !hasCoordinate(row, "startLatitude", "start_latitude")
+                        || !hasCoordinate(row, "endLongitude", "end_longitude")
+                        || !hasCoordinate(row, "endLatitude", "end_latitude")) continue;
+                collection.getFeatures().add(toPipeFeature(row));
+            }
+        }
     }
 
     private GisFeatureDTO baseFeature(String type, Map<String, Object> row) {
@@ -132,4 +167,13 @@ public class WaterSupplyGisMapServiceImpl implements WaterSupplyGisMapService {
         Object value = value(row, keys);
         return value instanceof BigDecimal ? (BigDecimal) value : new BigDecimal(value.toString());
     }
+
+    private boolean hasValues(Map<String, Object> row, String... keys) {
+        for (String key : keys) if (value(row, key) == null) return false;
+        return true;
+    }
+
+    private boolean hasCoordinate(Map<String, Object> row, String... aliases) {
+        return value(row, aliases) != null;
+    }
 }

+ 6 - 6
pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/WaterUserInfo/domain/WaterUserInfo.java

@@ -34,7 +34,7 @@ public class WaterUserInfo {
     private Long id;
 
     @ApiModelProperty(
-            value = "用户编号",
+            value = "用户编号",
             example = "USER_XX",
             required = true,
             position = 2
@@ -42,15 +42,15 @@ public class WaterUserInfo {
     private String userCode;
 
     @ApiModelProperty(
-            value = "用户名称",
-            example = "XX用户",
+            value = "用户名称",
+            example = "XX用户",
             required = true,
             position = 3
     )
     private String userName;
 
     @ApiModelProperty(
-            value = "用户类型",
+            value = "用户类型",
             example = "居民",
             position = 4,
             notes = "居民/工业/商业/行政事业"
@@ -116,7 +116,7 @@ public class WaterUserInfo {
     private BigDecimal annualWaterConsumption;
 
     @ApiModelProperty(
-            value = "用户状态",
+            value = "用户状态",
             example = "0",
             allowableValues = "0,1,2",
             position = 12,
@@ -126,7 +126,7 @@ public class WaterUserInfo {
 
     @ApiModelProperty(
             value = "备注",
-            example = "XX区域重要用水户",
+            example = "用水户补充说明",
             position = 13
     )
     private String remark;

+ 11 - 5
pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/WaterUserInfo/service/impl/WaterUserInfoServiceImpl.java

@@ -1,5 +1,6 @@
 package com.zksy.WaterSupply.WaterUserInfo.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -22,10 +23,15 @@ public class WaterUserInfoServiceImpl extends ServiceImpl<WaterUserInfoMapper, W
                                           String userType,
                                           LocalDateTime startTime,
                                           LocalDateTime endTime) {
-        List<WaterUserInfo> list = baseMapper.selectUserList(userCode, userName, status, userType, startTime, endTime);
-        page.setRecords(list);
-        page.setTotal(list.size());
-        return page;
+        LambdaQueryWrapper<WaterUserInfo> query = new LambdaQueryWrapper<WaterUserInfo>()
+                .like(userCode != null && !userCode.isEmpty(), WaterUserInfo::getUserCode, userCode)
+                .like(userName != null && !userName.isEmpty(), WaterUserInfo::getUserName, userName)
+                .eq(status != null && !status.isEmpty(), WaterUserInfo::getStatus, status)
+                .eq(userType != null && !userType.isEmpty(), WaterUserInfo::getUserType, userType)
+                .ge(startTime != null, WaterUserInfo::getCreateTime, startTime)
+                .le(endTime != null, WaterUserInfo::getCreateTime, endTime)
+                .orderByDesc(WaterUserInfo::getCreateTime);
+        return baseMapper.selectPage(page, query);
     }
 
     @Override
@@ -52,4 +58,4 @@ public class WaterUserInfoServiceImpl extends ServiceImpl<WaterUserInfoMapper, W
         if (ids == null || ids.isEmpty()) return false;
         return baseMapper.deleteByIdsPhysically(ids) > 0;
     }
-}
+}

+ 2 - 1
pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/export/WaterPipeExportDTO.java

@@ -12,11 +12,12 @@ public class WaterPipeExportDTO {
     @Excel(name = "管网名称") private String pipeName;
     @Excel(name = "材质") private String pipeMaterial;
     @Excel(name = "管径(mm)") private Integer pipeDiameter;
+    @Excel(name = "铺设年份") private Integer layingYear;
     @Excel(name = "长度(m)") private BigDecimal pipeLength;
     @Excel(name = "状态") private String status;
     @Excel(name = "起点经度") private BigDecimal startLongitude;
     @Excel(name = "起点纬度") private BigDecimal startLatitude;
     @Excel(name = "终点经度") private BigDecimal endLongitude;
     @Excel(name = "终点纬度") private BigDecimal endLatitude;
-    public static WaterPipeExportDTO from(WaterPipeInfo value) { WaterPipeExportDTO dto = new WaterPipeExportDTO(); dto.pipeCode=value.getPipeCode(); dto.pipeName=value.getPipeName(); dto.pipeMaterial=value.getPipeMaterial(); dto.pipeDiameter=value.getPipeDiameter(); dto.pipeLength=value.getPipeLength(); dto.status=value.getStatus(); dto.startLongitude=value.getStartLongitude(); dto.startLatitude=value.getStartLatitude(); dto.endLongitude=value.getEndLongitude(); dto.endLatitude=value.getEndLatitude(); return dto; }
+    public static WaterPipeExportDTO from(WaterPipeInfo value) { WaterPipeExportDTO dto = new WaterPipeExportDTO(); dto.pipeCode=value.getPipeCode(); dto.pipeName=value.getPipeName(); dto.pipeMaterial=value.getPipeMaterial(); dto.pipeDiameter=value.getPipeDiameter(); dto.layingYear=value.getLayingYear(); dto.pipeLength=value.getPipeLength(); dto.status=value.getStatus(); dto.startLongitude=value.getStartLongitude(); dto.startLatitude=value.getStartLatitude(); dto.endLongitude=value.getEndLongitude(); dto.endLatitude=value.getEndLatitude(); return dto; }
 }

+ 3 - 3
pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/export/WaterUserExportDTO.java

@@ -8,9 +8,9 @@ import java.math.BigDecimal;
 
 @Data
 public class WaterUserExportDTO {
-    @Excel(name = "用户编号") private String userCode;
-    @Excel(name = "用户名称") private String userName;
-    @Excel(name = "用户类型") private String userType;
+    @Excel(name = "用户编号") private String userCode;
+    @Excel(name = "用户名称") private String userName;
+    @Excel(name = "用户类型") private String userType;
     @Excel(name = "地址") private String address;
     @Excel(name = "年用水量") private BigDecimal annualWaterConsumption;
     @Excel(name = "状态") private String status;

+ 12 - 6
pipe-network-service/zksy-system/src/main/java/com/zksy/base/domain/PipeNetworkBase.java

@@ -45,12 +45,6 @@ public class PipeNetworkBase {
     @ApiModelProperty("位置描述")
     private String location;
 
-    @ApiModelProperty("经度")
-    private BigDecimal longitude;
-
-    @ApiModelProperty("纬度")
-    private BigDecimal latitude;
-
     @ApiModelProperty("长度(米)")
     private BigDecimal length;
 
@@ -87,4 +81,16 @@ public class PipeNetworkBase {
     @TableLogic(value = "0", delval = "2")
     @ApiModelProperty("删除标志(0=正常 2=删除)")
     private String delFlag;
+
+    @ApiModelProperty("管网起点WGS84经度")
+    private BigDecimal start_longitude;
+
+    @ApiModelProperty("管网起点WGS84纬度")
+    private BigDecimal start_latitude;
+
+    @ApiModelProperty("管网终点WGS84经度")
+    private BigDecimal end_longitude;
+
+    @ApiModelProperty("管网终点WGS84纬度")
+    private BigDecimal end_latitude;
 }

+ 1 - 1
pipe-network-service/zksy-system/src/main/resources/mapper/WaterSupply/PipeNetworkStatistics/PipeNetworkStatisticsMapper.xml

@@ -20,7 +20,7 @@
         FROM app_user.water_pipe_info WHERE del_flag = '0'
         <if test="status != null and status != ''">AND status = #{status}</if>
         UNION ALL
-        SELECT 'user', '用水户', COUNT(*), COALESCE(SUM(CASE WHEN status = '0' THEN 1 ELSE 0 END), 0), COALESCE(SUM(CASE WHEN status IS NOT NULL AND status &lt;&gt; '0' THEN 1 ELSE 0 END), 0)
+        SELECT 'user', '用水户', COUNT(*), COALESCE(SUM(CASE WHEN status = '0' THEN 1 ELSE 0 END), 0), COALESCE(SUM(CASE WHEN status IS NOT NULL AND status &lt;&gt; '0' THEN 1 ELSE 0 END), 0)
         FROM app_user.water_user_info WHERE del_flag = '0'
         <if test="status != null and status != ''">AND status = #{status}</if>
     </select>

+ 8 - 8
pipe-network-service/zksy-system/src/main/resources/mapper/WaterSupply/WaterPipeInfo/WaterPipeInfoMapper.xml

@@ -35,7 +35,7 @@
 
     <select id="selectPipeList" resultMap="BaseResultMap">
         SELECT <include refid="Base_Column_List"/>
-        FROM water_pipe_info
+        FROM app_user.water_pipe_info
         WHERE del_flag = '0'
         <if test="pipeCode != null and pipeCode != ''">
             AND pipe_code LIKE CONCAT('%', #{pipeCode}, '%')
@@ -62,7 +62,7 @@
         SELECT
         status,
         COUNT(*) AS pipeCount
-        FROM water_pipe_info
+        FROM app_user.water_pipe_info
         WHERE del_flag = '0'
         GROUP BY status
     </select>
@@ -72,14 +72,14 @@
         pipe_material AS pipeMaterial,
         COUNT(*) AS pipeCount,
         SUM(pipe_length) AS totalLength
-        FROM water_pipe_info
+        FROM app_user.water_pipe_info
         WHERE del_flag = '0'
         GROUP BY pipe_material
     </select>
 
     <select id="getLatestPipe" resultMap="BaseResultMap">
         SELECT <include refid="Base_Column_List"/>
-        FROM water_pipe_info
+        FROM app_user.water_pipe_info
         WHERE del_flag = '0'
         ORDER BY create_time DESC
         LIMIT 1
@@ -91,13 +91,13 @@
         SUM(pipe_length) AS totalLength,
         SUM(CASE WHEN status = '0' THEN 1 ELSE 0 END) AS runningCount,
         SUM(CASE WHEN status = '1' THEN 1 ELSE 0 END) AS stopCount
-        FROM water_pipe_info
+        FROM app_user.water_pipe_info
         WHERE del_flag = '0'
     </select>
 
     <select id="selectDeletedList" resultMap="BaseResultMap">
         SELECT <include refid="Base_Column_List"/>
-        FROM water_pipe_info
+        FROM app_user.water_pipe_info
         WHERE del_flag = '2'
         <if test="pipeCode != null and pipeCode != ''">
             AND pipe_code LIKE CONCAT('%', #{pipeCode}, '%')
@@ -118,14 +118,14 @@
     </select>
 
     <delete id="deleteByIdsPhysically">
-        DELETE FROM water_pipe_info WHERE id IN
+        DELETE FROM app_user.water_pipe_info WHERE id IN
         <foreach collection="ids" item="id" open="(" separator="," close=")">
             #{id}
         </foreach>
     </delete>
 
     <update id="restoreByIds">
-        UPDATE water_pipe_info
+        UPDATE app_user.water_pipe_info
         SET del_flag = '0',
         update_by = #{updateBy},
         update_time = CURRENT_TIMESTAMP

+ 4 - 4
pipe-network-service/zksy-system/src/main/resources/mapper/WaterSupply/WaterPlantInfo/WaterPlantInfoMapper.xml

@@ -36,7 +36,7 @@
     <!-- 条件查询水厂列表 -->
     <select id="selectPlantList" resultMap="BaseResultMap">
         SELECT <include refid="Base_Column_List"/>
-        FROM water_plant_info
+        FROM app_user.water_plant_info
         WHERE del_flag = '0'
         <if test="plantCode != null and plantCode != ''">
             AND plant_code LIKE CONCAT('%', #{plantCode}, '%')
@@ -61,7 +61,7 @@
 
     <select id="selectDeletedList" resultMap="BaseResultMap">
         SELECT <include refid="Base_Column_List"/>
-        FROM water_plant_info
+        FROM app_user.water_plant_info
         WHERE del_flag = '2'
         <if test="plantCode != null and plantCode != ''">
             AND plant_code LIKE CONCAT('%', #{plantCode}, '%')
@@ -82,7 +82,7 @@
     </select>
 
     <update id="restoreByIds">
-        UPDATE water_plant_info
+        UPDATE app_user.water_plant_info
         SET del_flag = '0',
         update_by = #{updateBy},
         update_time = CURRENT_TIMESTAMP
@@ -93,7 +93,7 @@
     </update>
 
     <delete id="deleteByIdsPhysically">
-        DELETE FROM water_plant_info WHERE id IN
+        DELETE FROM app_user.water_plant_info WHERE id IN
         <foreach collection="ids" item="id" open="(" separator="," close=")">
             #{id}
         </foreach>

+ 4 - 4
pipe-network-service/zksy-system/src/main/resources/mapper/WaterSupply/WaterPumpStation/WaterPumpStationMapper.xml

@@ -34,7 +34,7 @@
     <!-- 条件查询泵站列表 -->
     <select id="selectStationList" resultMap="BaseResultMap">
         SELECT <include refid="Base_Column_List"/>
-        FROM water_pump_station
+        FROM app_user.water_pump_station
         WHERE del_flag = '0'
         <if test="stationCode != null and stationCode != ''">
             AND station_code LIKE CONCAT('%', #{stationCode}, '%')
@@ -60,7 +60,7 @@
     <!-- 回收站SQL -->
     <select id="selectDeletedList" resultMap="BaseResultMap">
         SELECT <include refid="Base_Column_List"/>
-        FROM water_pump_station
+        FROM app_user.water_pump_station
         WHERE del_flag = '2'
         <if test="stationCode != null and stationCode != ''">
             AND station_code LIKE CONCAT('%', #{stationCode}, '%')
@@ -81,7 +81,7 @@
     </select>
 
     <update id="restoreByIds">
-        UPDATE water_pump_station
+        UPDATE app_user.water_pump_station
         SET del_flag = '0',
         update_by = #{updateBy},
         update_time = CURRENT_TIMESTAMP
@@ -92,7 +92,7 @@
     </update>
 
     <delete id="deleteByIdsPhysically">
-        DELETE FROM water_pump_station WHERE id IN
+        DELETE FROM app_user.water_pump_station WHERE id IN
         <foreach collection="ids" item="id" open="(" separator="," close=")">
             #{id}
         </foreach>

+ 4 - 4
pipe-network-service/zksy-system/src/main/resources/mapper/WaterSupply/WaterSourceInfo/WaterSourceInfoMapper.xml

@@ -34,7 +34,7 @@
 
     <select id="selectSourceList" resultMap="BaseResultMap">
         SELECT <include refid="Base_Column_List"/>
-        FROM water_source_info
+        FROM app_user.water_source_info
         WHERE del_flag = '0'
         <if test="sourceId != null and sourceId != ''">
             AND source_id LIKE CONCAT('%', #{sourceId}, '%')
@@ -59,7 +59,7 @@
 
     <select id="selectDeletedList" resultMap="BaseResultMap">
         SELECT <include refid="Base_Column_List"/>
-        FROM water_source_info
+        FROM app_user.water_source_info
         WHERE del_flag = '2'
         <if test="sourceId != null and sourceId != ''">
             AND source_id LIKE CONCAT('%', #{sourceId}, '%')
@@ -80,7 +80,7 @@
     </select>
 
     <update id="restoreByIds">
-        UPDATE water_source_info
+        UPDATE app_user.water_source_info
         SET del_flag = '0',
         update_by = #{updateBy},
         update_time = CURRENT_TIMESTAMP
@@ -91,7 +91,7 @@
     </update>
 
     <delete id="deleteByIdsPhysically">
-        DELETE FROM water_source_info WHERE id IN
+        DELETE FROM app_user.water_source_info WHERE id IN
         <foreach collection="ids" item="id" open="(" separator="," close=")">
             #{id}
         </foreach>

+ 1 - 1
pipe-network-service/zksy-system/src/main/resources/mapper/WaterSupply/WaterSupplyGisMap/WaterSupplyGisMapMapper.xml

@@ -39,7 +39,7 @@
         SELECT 'pipe', '供水管网', COUNT(*), COALESCE(SUM(CASE WHEN start_longitude IS NOT NULL AND start_latitude IS NOT NULL AND end_longitude IS NOT NULL AND end_latitude IS NOT NULL THEN 1 ELSE 0 END), 0), COALESCE(SUM(CASE WHEN start_longitude IS NULL OR start_latitude IS NULL OR end_longitude IS NULL OR end_latitude IS NULL THEN 1 ELSE 0 END), 0)
         FROM app_user.water_pipe_info WHERE del_flag = '0'
         UNION ALL
-        SELECT 'user', '用水户', COUNT(*), COALESCE(SUM(CASE WHEN longitude IS NOT NULL AND latitude IS NOT NULL THEN 1 ELSE 0 END), 0), COALESCE(SUM(CASE WHEN longitude IS NULL OR latitude IS NULL THEN 1 ELSE 0 END), 0)
+        SELECT 'user', '用水户', COUNT(*), COALESCE(SUM(CASE WHEN longitude IS NOT NULL AND latitude IS NOT NULL THEN 1 ELSE 0 END), 0), COALESCE(SUM(CASE WHEN longitude IS NULL OR latitude IS NULL THEN 1 ELSE 0 END), 0)
         FROM app_user.water_user_info WHERE del_flag = '0'
     </select>
 </mapper>

+ 4 - 4
pipe-network-service/zksy-system/src/main/resources/mapper/WaterSupply/WaterUserInfo/WaterUserInfoMapper.xml

@@ -34,7 +34,7 @@
 
     <select id="selectUserList" resultMap="BaseResultMap">
         SELECT <include refid="Base_Column_List"/>
-        FROM water_user_info
+        FROM app_user.water_user_info
         WHERE del_flag = '0'
         <if test="userCode != null and userCode != ''">
             AND user_code LIKE CONCAT('%', #{userCode}, '%')
@@ -59,7 +59,7 @@
 
     <select id="selectDeletedList" resultMap="BaseResultMap">
         SELECT <include refid="Base_Column_List"/>
-        FROM water_user_info
+        FROM app_user.water_user_info
         WHERE del_flag = '2'
         <if test="userCode != null and userCode != ''">
             AND user_code LIKE CONCAT('%', #{userCode}, '%')
@@ -80,7 +80,7 @@
     </select>
 
     <update id="restoreByIds">
-        UPDATE water_user_info
+        UPDATE app_user.water_user_info
         SET del_flag = '0',
         update_by = #{updateBy},
         update_time = CURRENT_TIMESTAMP
@@ -91,7 +91,7 @@
     </update>
 
     <delete id="deleteByIdsPhysically">
-        DELETE FROM water_user_info WHERE id IN
+        DELETE FROM app_user.water_user_info WHERE id IN
         <foreach collection="ids" item="id" open="(" separator="," close=")">
             #{id}
         </foreach>

+ 27 - 0
pipe-network-service/zksy-system/src/test/java/com/zksy/WaterSupply/FacilityCoordinateModelTest.java

@@ -7,6 +7,8 @@ import org.junit.jupiter.api.Test;
 import java.io.InputStream;
 import java.math.BigDecimal;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Map;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -114,4 +116,29 @@ class FacilityCoordinateModelTest {
                     "GIS SQL must not resolve water supply tables through search_path");
         }
     }
+
+    @Test
+    void kingbaseMigrationIsRepeatableAndOnlyAddsTheApprovedWgs84Coordinates() throws Exception {
+        Path workingDirectory = Path.of(System.getProperty("user.dir"));
+        Path migration = workingDirectory.resolve(
+                "pipe-network-service/sql/water_supply_facility_gis.sql");
+        if (!Files.exists(migration)) {
+            migration = workingDirectory.resolve("../sql/water_supply_facility_gis.sql").normalize();
+        }
+        assertTrue(Files.exists(migration), migration.toString());
+        String sql = Files.readString(migration, StandardCharsets.UTF_8);
+
+        assertEquals(12, sql.split("ADD COLUMN IF NOT EXISTS", -1).length - 1);
+        assertEquals(5, sql.split("CREATE INDEX IF NOT EXISTS", -1).length - 1);
+        assertTrue(sql.contains("ALTER TABLE app_user.water_source_info"));
+        assertTrue(sql.contains("ALTER TABLE app_user.water_plant_info"));
+        assertTrue(sql.contains("ALTER TABLE app_user.water_pump_station"));
+        assertTrue(sql.contains("ALTER TABLE app_user.water_pipe_info"));
+        assertTrue(sql.contains("ALTER TABLE app_user.water_user_info"));
+        assertTrue(sql.contains("longitude numeric(11,8)"));
+        assertTrue(sql.contains("latitude numeric(10,8)"));
+        assertFalse(sql.toLowerCase().contains("hydrant"));
+        assertFalse(sql.contains("消火栓"));
+        assertFalse(sql.toUpperCase().contains("CREATE TABLE"));
+    }
 }

+ 138 - 0
pipe-network-service/zksy-system/src/test/java/com/zksy/WaterSupply/WaterFacilityQueryServiceTest.java

@@ -0,0 +1,138 @@
+package com.zksy.WaterSupply;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zksy.WaterSupply.WaterPipeInfo.domain.WaterPipeInfo;
+import com.zksy.WaterSupply.WaterPipeInfo.mapper.WaterPipeInfoMapper;
+import com.zksy.WaterSupply.WaterPipeInfo.service.impl.WaterPipeInfoServiceImpl;
+import com.zksy.WaterSupply.WaterPlantInfo.domain.WaterPlantInfo;
+import com.zksy.WaterSupply.WaterPlantInfo.mapper.WaterPlantInfoMapper;
+import com.zksy.WaterSupply.WaterPlantInfo.service.impl.WaterPlantInfoServiceImpl;
+import com.zksy.WaterSupply.WaterPumpStation.domain.WaterPumpStation;
+import com.zksy.WaterSupply.WaterPumpStation.mapper.WaterPumpStationMapper;
+import com.zksy.WaterSupply.WaterPumpStation.service.impl.WaterPumpStationServiceImpl;
+import com.zksy.WaterSupply.WaterSourceInfo.domain.WaterSourceInfo;
+import com.zksy.WaterSupply.WaterSourceInfo.mapper.WaterSourceInfoMapper;
+import com.zksy.WaterSupply.WaterSourceInfo.service.impl.WaterSourceInfoServiceImpl;
+import com.zksy.WaterSupply.WaterUserInfo.domain.WaterUserInfo;
+import com.zksy.WaterSupply.WaterUserInfo.mapper.WaterUserInfoMapper;
+import com.zksy.WaterSupply.WaterUserInfo.service.impl.WaterUserInfoServiceImpl;
+import com.zksy.WaterSupply.export.WaterPipeExportDTO;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatchers;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.List;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.lang.reflect.Field;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class WaterFacilityQueryServiceTest {
+
+    @Test
+    void pipeQueryUsesDatabasePaginationAndFilters() {
+        WaterPipeInfoMapper mapper = mock(WaterPipeInfoMapper.class);
+        Page<WaterPipeInfo> expected = pageOf(new WaterPipeInfo());
+        when(mapper.selectPage(ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(expected);
+        WaterPipeInfoServiceImpl service = new WaterPipeInfoServiceImpl();
+        ReflectionTestUtils.setField(service, "baseMapper", mapper);
+
+        IPage<WaterPipeInfo> actual = service.queryPage(new Page<>(2, 20), "P-1", "主干", "PE", "0", 300, 2020, null, null);
+
+        assertEquals(expected, actual);
+        verify(mapper).selectPage(ArgumentMatchers.any(), ArgumentMatchers.any());
+    }
+
+    @Test
+    void plantQueryUsesDatabasePagination() {
+        WaterPlantInfoMapper mapper = mock(WaterPlantInfoMapper.class);
+        Page<WaterPlantInfo> expected = pageOf(new WaterPlantInfo());
+        when(mapper.selectPage(ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(expected);
+        WaterPlantInfoServiceImpl service = new WaterPlantInfoServiceImpl();
+        ReflectionTestUtils.setField(service, "baseMapper", mapper);
+
+        assertEquals(expected, service.queryPage(new Page<>(3, 5), "P", "厂", "0", "负责人", null, null));
+        verify(mapper).selectPage(ArgumentMatchers.any(), ArgumentMatchers.any());
+    }
+
+    @Test
+    void pumpStationQueryUsesDatabasePagination() {
+        WaterPumpStationMapper mapper = mock(WaterPumpStationMapper.class);
+        Page<WaterPumpStation> expected = pageOf(new WaterPumpStation());
+        when(mapper.selectPage(ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(expected);
+        WaterPumpStationServiceImpl service = new WaterPumpStationServiceImpl();
+        ReflectionTestUtils.setField(service, "baseMapper", mapper);
+
+        assertEquals(expected, service.queryPage(new Page<>(1, 10), "S", "泵站", "0", "负责人", null, null));
+        verify(mapper).selectPage(ArgumentMatchers.any(), ArgumentMatchers.any());
+    }
+
+    @Test
+    void sourceQueryUsesDatabasePagination() {
+        WaterSourceInfoMapper mapper = mock(WaterSourceInfoMapper.class);
+        Page<WaterSourceInfo> expected = pageOf(new WaterSourceInfo());
+        when(mapper.selectPage(ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(expected);
+        WaterSourceInfoServiceImpl service = new WaterSourceInfoServiceImpl();
+        ReflectionTestUtils.setField(service, "baseMapper", mapper);
+
+        assertEquals(expected, service.queryPage(new Page<>(1, 10), "W", "水源", "0", "单位", null, null));
+        verify(mapper).selectPage(ArgumentMatchers.any(), ArgumentMatchers.any());
+    }
+
+    @Test
+    void waterUserQueryUsesDatabasePagination() {
+        WaterUserInfoMapper mapper = mock(WaterUserInfoMapper.class);
+        Page<WaterUserInfo> expected = pageOf(new WaterUserInfo());
+        when(mapper.selectPage(ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(expected);
+        WaterUserInfoServiceImpl service = new WaterUserInfoServiceImpl();
+        ReflectionTestUtils.setField(service, "baseMapper", mapper);
+
+        assertEquals(expected, service.queryPage(new Page<>(4, 10), "U", "居民", "0", "居民", null, null));
+        verify(mapper).selectPage(ArgumentMatchers.any(), ArgumentMatchers.any());
+    }
+
+    @Test
+    void facilityMapperQueriesUseKingbaseAppUserSchema() throws Exception {
+        String[] mapperPaths = {
+                "mapper/WaterSupply/WaterPipeInfo/WaterPipeInfoMapper.xml",
+                "mapper/WaterSupply/WaterPlantInfo/WaterPlantInfoMapper.xml",
+                "mapper/WaterSupply/WaterPumpStation/WaterPumpStationMapper.xml",
+                "mapper/WaterSupply/WaterSourceInfo/WaterSourceInfoMapper.xml",
+                "mapper/WaterSupply/WaterUserInfo/WaterUserInfoMapper.xml"
+        };
+        for (String path : mapperPaths) {
+            try (InputStream input = getClass().getClassLoader().getResourceAsStream(path)) {
+                String xml = new String(input.readAllBytes(), StandardCharsets.UTF_8);
+                org.junit.jupiter.api.Assertions.assertFalse(
+                        xml.matches("(?s).*\\b(?:FROM|UPDATE|DELETE FROM)\\s+water_(?:pipe_info|plant_info|pump_station|source_info|user_info)\\b.*"),
+                        path + " must qualify facilities with app_user schema");
+            }
+        }
+    }
+
+    @Test
+    void pipeExportIncludesLayingYear() {
+        WaterPipeInfo pipe = new WaterPipeInfo();
+        pipe.setLayingYear(1998);
+
+        assertEquals(1998, WaterPipeExportDTO.from(pipe).getLayingYear());
+        try {
+            Field field = WaterPipeExportDTO.class.getDeclaredField("layingYear");
+            assertEquals("铺设年份", field.getAnnotation(com.zksy.common.annotation.Excel.class).name());
+        } catch (ReflectiveOperationException e) {
+            throw new AssertionError("layingYear must be an exported Excel field", e);
+        }
+    }
+
+    private static <T> Page<T> pageOf(T record) {
+        Page<T> page = new Page<>(1, 1);
+        page.setRecords(List.of(record));
+        page.setTotal(1);
+        return page;
+    }
+}

+ 143 - 0
pipe-network-service/zksy-system/src/test/java/com/zksy/WaterSupply/WaterSupplyGisMap/WaterSupplyGisMapServiceTest.java

@@ -0,0 +1,143 @@
+package com.zksy.WaterSupply.WaterSupplyGisMap;
+
+import com.zksy.WaterSupply.WaterPlantInfo.domain.WaterPlantInfo;
+import com.zksy.WaterSupply.WaterPlantInfo.service.IWaterPlantInfoService;
+import com.zksy.WaterSupply.WaterSupplyGisMap.dto.GisOverviewItemDTO;
+import com.zksy.WaterSupply.WaterSupplyGisMap.dto.GisFeatureCollectionDTO;
+import com.zksy.WaterSupply.WaterSupplyGisMap.mapper.WaterSupplyGisMapMapper;
+import com.zksy.WaterSupply.WaterSupplyGisMap.service.impl.WaterSupplyGisMapServiceImpl;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class WaterSupplyGisMapServiceTest {
+
+    @Mock
+    private WaterSupplyGisMapMapper mapper;
+
+    @Mock
+    private IWaterPlantInfoService plantService;
+
+    @InjectMocks
+    private WaterSupplyGisMapServiceImpl service;
+
+    @Test
+    void overviewStillDescribesAllFacilityTypesWhenNoCoordinatesHaveBeenLoaded() {
+        when(mapper.selectOverview()).thenReturn(Collections.emptyList());
+
+        List<GisOverviewItemDTO> overview = service.getOverview();
+
+        assertEquals(List.of("source", "plant", "pumpStation", "pipe", "user", "total"),
+                overview.stream().map(GisOverviewItemDTO::getFacilityType).collect(Collectors.toList()));
+        overview.forEach(item -> {
+            assertEquals(0L, item.getTotalCount());
+            assertEquals(0L, item.getGeoCompleteCount());
+            assertEquals(0L, item.getGeoMissingCount());
+        });
+    }
+
+    @Test
+    void pointLayerOmitsRowsWithoutBothCoordinatesAndKeepsLongitudeFirst() {
+        Map<String, Object> complete = pointRow(1L, "完整水厂");
+        Map<String, Object> missingLatitude = pointRow(2L, "缺纬度水厂");
+        missingLatitude.put("latitude", null);
+        when(mapper.selectPlantFeatures()).thenReturn(List.of(complete, missingLatitude));
+
+        GisFeatureCollectionDTO features = service.getFeatures(Set.of("plant"));
+
+        assertEquals(1, features.getFeatures().size());
+        assertEquals("Point", features.getFeatures().get(0).getGeometry().get("type"));
+        assertEquals(List.of(new BigDecimal("113.12345678"), new BigDecimal("23.12345678")),
+                features.getFeatures().get(0).getGeometry().get("coordinates"));
+    }
+
+    @Test
+    void pipeLayerOmitsRowsWithoutFourCoordinatesAndKeepsEndpointOrder() {
+        Map<String, Object> complete = pipeRow(1L, "完整管线");
+        Map<String, Object> missingEndpoint = pipeRow(2L, "缺终点纬度管线");
+        missingEndpoint.put("end_latitude", null);
+        when(mapper.selectPipeFeatures()).thenReturn(List.of(complete, missingEndpoint));
+
+        GisFeatureCollectionDTO features = service.getFeatures(Set.of("pipe"));
+
+        assertEquals(1, features.getFeatures().size());
+        assertEquals("LineString", features.getFeatures().get(0).getGeometry().get("type"));
+        assertEquals(List.of(
+                        List.of(new BigDecimal("113.10000000"), new BigDecimal("23.10000000")),
+                        List.of(new BigDecimal("113.20000000"), new BigDecimal("23.20000000"))),
+                features.getFeatures().get(0).getGeometry().get("coordinates"));
+    }
+
+    @Test
+    void requestedFacilityTypesDoNotLoadUnselectedLayers() {
+        when(mapper.selectPlantFeatures()).thenReturn(Collections.emptyList());
+
+        service.getFeatures(Set.of("plant"));
+
+        verify(mapper).selectPlantFeatures();
+        verify(mapper, never()).selectSourceFeatures();
+        verify(mapper, never()).selectPumpStationFeatures();
+        verify(mapper, never()).selectPipeFeatures();
+        verify(mapper, never()).selectUserFeatures();
+    }
+
+    @Test
+    void detailReturnsTheExistingFacilityEvenWhenItHasNoCoordinates() {
+        WaterPlantInfo plant = new WaterPlantInfo();
+        plant.setId(9L);
+        plant.setPlantName("待补坐标水厂");
+        when(plantService.getById(9L)).thenReturn(plant);
+
+        Object detail = service.getDetail("plant", 9L);
+
+        assertSame(plant, detail);
+    }
+
+    @Test
+    void unsupportedFacilityTypeIsRejected() {
+        IllegalArgumentException error = assertThrows(IllegalArgumentException.class,
+                () -> service.getDetail("hydrant", 1L));
+
+        assertEquals("不支持的设施类型:hydrant", error.getMessage());
+        verify(plantService, never()).getById(1L);
+    }
+
+    private Map<String, Object> pointRow(Long id, String name) {
+        Map<String, Object> row = new HashMap<>();
+        row.put("id", id);
+        row.put("code", "WP" + id);
+        row.put("name", name);
+        row.put("status", "0");
+        row.put("location", "测试地址");
+        row.put("longitude", new BigDecimal("113.12345678"));
+        row.put("latitude", new BigDecimal("23.12345678"));
+        return row;
+    }
+
+    private Map<String, Object> pipeRow(Long id, String name) {
+        Map<String, Object> row = pointRow(id, name);
+        row.put("start_longitude", new BigDecimal("113.10000000"));
+        row.put("start_latitude", new BigDecimal("23.10000000"));
+        row.put("end_longitude", new BigDecimal("113.20000000"));
+        row.put("end_latitude", new BigDecimal("23.20000000"));
+        return row;
+    }
+}

+ 133 - 0
pipe-network-service/zksy-system/src/test/java/com/zksy/WaterSupply/WaterUserTerminologyTest.java

@@ -0,0 +1,133 @@
+package com.zksy.WaterSupply;
+
+import com.zksy.WaterSupply.PipeNetworkStatistics.dto.PipeNetworkStatisticsDashboardDTO;
+import com.zksy.WaterSupply.PipeNetworkStatistics.mapper.PipeNetworkStatisticsMapper;
+import com.zksy.WaterSupply.PipeNetworkStatistics.service.impl.PipeNetworkStatisticsExportServiceImpl;
+import com.zksy.WaterSupply.WaterSupplyGisMap.dto.GisFeatureCollectionDTO;
+import com.zksy.WaterSupply.WaterSupplyGisMap.mapper.WaterSupplyGisMapMapper;
+import com.zksy.WaterSupply.WaterSupplyGisMap.service.impl.WaterSupplyGisMapServiceImpl;
+import org.apache.ibatis.builder.xml.XMLMapperBuilder;
+import org.apache.ibatis.io.Resources;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.session.Configuration;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class WaterUserTerminologyTest {
+
+    @Mock
+    private WaterSupplyGisMapMapper gisMapper;
+
+    @InjectMocks
+    private WaterSupplyGisMapServiceImpl gisService;
+
+    @Test
+    void gisUserLayerReturnsEveryWaterUserCategoryWithUnifiedName() {
+        when(gisMapper.selectUserFeatures()).thenReturn(List.of(
+                userRow(1L, "RESIDENT", "居民用水户"),
+                userRow(2L, "INDUSTRIAL", "工业用水户"),
+                userRow(3L, "COMMERCIAL", "商业用水户"),
+                userRow(4L, "PUBLIC", "行政事业用水户")
+        ));
+
+        GisFeatureCollectionDTO result = gisService.getFeatures(Set.of("user"));
+
+        assertEquals(4, result.getFeatures().size());
+        assertEquals(Set.of("RESIDENT", "INDUSTRIAL", "COMMERCIAL", "PUBLIC"),
+                result.getFeatures().stream()
+                        .map(feature -> (String) feature.getProperties().get("code"))
+                        .collect(Collectors.toSet()));
+        result.getFeatures().forEach(feature -> {
+            assertEquals("user", feature.getProperties().get("facilityType"));
+            assertEquals("用水户", feature.getProperties().get("facilityTypeName"));
+        });
+    }
+
+    @Test
+    void mapperOverviewKeepsUserCodeAndLabelsAllWaterUsersWithoutCategoryFilter() throws Exception {
+        String gisOverviewSql = mappedSql(
+                "mapper/WaterSupply/WaterSupplyGisMap/WaterSupplyGisMapMapper.xml",
+                WaterSupplyGisMapMapper.class,
+                "selectOverview",
+                null
+        );
+        String gisUsersSql = mappedSql(
+                "mapper/WaterSupply/WaterSupplyGisMap/WaterSupplyGisMapMapper.xml",
+                WaterSupplyGisMapMapper.class,
+                "selectUserFeatures",
+                null
+        );
+        String statisticsOverviewSql = mappedSql(
+                "mapper/WaterSupply/PipeNetworkStatistics/PipeNetworkStatisticsMapper.xml",
+                PipeNetworkStatisticsMapper.class,
+                "selectFacilityOverview",
+                Collections.singletonMap("status", null)
+        );
+
+        assertTrue(gisOverviewSql.contains("SELECT 'user', '用水户'"));
+        assertTrue(statisticsOverviewSql.contains("SELECT 'user', '用水户'"));
+        assertFalse(gisOverviewSql.contains("大用水户"));
+        assertFalse(statisticsOverviewSql.contains("大用水户"));
+        assertTrue(gisUsersSql.contains("FROM app_user.water_user_info"));
+        assertFalse(gisUsersSql.matches("(?is).*\\buser_type\\s*=.*"),
+                "the user GIS layer must not restrict resident, industrial, commercial, or public-service users");
+        assertFalse(statisticsOverviewSql.matches("(?is).*\\buser_type\\s*=.*"),
+                "facility statistics must include every water user category");
+    }
+
+    @Test
+    void statisticsExportCreatesUnifiedWaterUserWorksheet() throws Exception {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        new PipeNetworkStatisticsExportServiceImpl().write(output, new PipeNetworkStatisticsDashboardDTO());
+
+        try (Workbook workbook = new XSSFWorkbook(new ByteArrayInputStream(output.toByteArray()))) {
+            assertNotNull(workbook.getSheet("用水户统计"));
+            assertNull(workbook.getSheet("大用水户统计"));
+        }
+    }
+
+    private Map<String, Object> userRow(Long id, String code, String name) {
+        return Map.of(
+                "id", id,
+                "code", code,
+                "name", name,
+                "status", "0",
+                "location", "测试地址",
+                "longitude", new BigDecimal("113.12345678"),
+                "latitude", new BigDecimal("23.12345678")
+        );
+    }
+
+    private String mappedSql(String resource, Class<?> mapperType, String statementId, Object parameter) throws Exception {
+        Configuration configuration = new Configuration();
+        configuration.addMapper(mapperType);
+        try (InputStream input = Resources.getResourceAsStream(resource)) {
+            new XMLMapperBuilder(input, configuration, resource, configuration.getSqlFragments()).parse();
+        }
+        MappedStatement statement = configuration.getMappedStatement(mapperType.getName() + "." + statementId);
+        return statement.getBoundSql(parameter).getSql().replaceAll("\\s+", " ").trim();
+    }
+}