Ver código fonte

供水子系统-监测报警一张图整合

zengy 3 semanas atrás
pai
commit
52940d1309
21 arquivos alterados com 99 adições e 720 exclusões
  1. 8 5
      docs/alarm-operations-2026-09-24.md
  2. 3 2
      docs/alarm-review-2026-09-23.md
  3. 1 1
      pipe-network-service/CONTEXT.md
  4. 2 2
      pipe-network-service/sql/migration_alarm_operations.sql
  5. 1 1
      pipe-network-service/sql/migration_alarm_review.sql
  6. 1 1
      pipe-network-service/sql/tests/alarm_review.test.mjs
  7. 1 67
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/WaterSupply/MonitorAlarm/WaterSupplyAlarmController.java
  8. 1 1
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/alarm/AlarmController.java
  9. 0 89
      pipe-network-service/zksy-admin/src/test/java/com/zksy/web/controller/WaterSupply/MonitorAlarm/WaterSupplyAlarmLedgerControllerTest.java
  10. 0 54
      pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/MonitorAlarm/dto/out/AlarmMapPointOutDTO.java
  11. 0 44
      pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/MonitorAlarm/service/IWaterSupplyAlarmService.java
  12. 2 203
      pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/MonitorAlarm/service/impl/WaterSupplyAlarmServiceImpl.java
  13. 1 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/alarm/dto/AlarmReviewQuery.java
  14. 7 5
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/alarm/service/AlarmOperationsService.java
  15. 4 3
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/alarm/service/AlarmReviewScopeService.java
  16. 1 2
      pipe-network-service/zksy-system/src/main/resources/mapper/base/AlarmOperationsMapper.xml
  17. 0 234
      pipe-network-service/zksy-system/src/test/java/com/zksy/WaterSupply/MonitorAlarm/WaterSupplyAlarmLedgerServiceTest.java
  18. 0 2
      pipe-network-service/zksy-system/src/test/java/com/zksy/WaterSupply/MonitorAlarm/WaterSupplyAlarmRealtimeContractTest.java
  19. 9 0
      pipe-network-service/zksy-system/src/test/java/com/zksy/base/alarm/mapper/AlarmOperationsMapperTest.java
  20. 43 2
      pipe-network-service/zksy-system/src/test/java/com/zksy/base/alarm/service/AlarmOperationsServiceTest.java
  21. 14 2
      pipe-network-service/zksy-system/src/test/java/com/zksy/base/alarm/service/AlarmReviewScopeServiceTest.java

+ 8 - 5
docs/alarm-operations-2026-09-24.md

@@ -2,15 +2,16 @@
 
 
 ## 交付入口
 ## 交付入口
 
 
-后端统一在 `AlarmController`,查询服务/DTO/枚举/Mapper 位于 `zksy-system/base/alarm`。前端共用 `src/views/subSystem/alarm`。
+后端统一在 `AlarmController`,查询服务/DTO/枚举/Mapper 位于 `zksy-system/base/alarm`。前端公共报警页面及组件位于 `src/views/subSystem/alarm`,排水研判和 GIS 位于 `src/views/subSystem/drainage/alarm`,供水 GIS 位于 `src/views/subSystem/waterSupply/alarm`。
 
 
 | 既有菜单 | path | component | query |
 | 既有菜单 | path | component | query |
 | --- | --- | --- | --- |
 | --- | --- | --- | --- |
 | 2139 | alarmRemind | subSystem/alarm/remind | {"rootTypeId":2} |
 | 2139 | alarmRemind | subSystem/alarm/remind | {"rootTypeId":2} |
 | 2140 | alarmList | subSystem/alarm/list | {"rootTypeId":2} |
 | 2140 | alarmList | subSystem/alarm/list | {"rootTypeId":2} |
-| 2141 | drainageAlarmAnalysis | subSystem/alarm/drainage/analysis | {"rootTypeId":2} |
-| 2143 | drainageAlarmGIS | subSystem/alarm/drainage/alarmGIS | {"rootTypeId":2} |
+| 2141 | drainageAlarmAnalysis | subSystem/drainage/alarm/analysis | {"rootTypeId":2} |
+| 2143 | drainageAlarmGIS | subSystem/drainage/alarm/alarmGIS | {"rootTypeId":2} |
 | 2117 | alarmList | subSystem/alarm/list | {"rootTypeId":1} |
 | 2117 | alarmList | subSystem/alarm/list | {"rootTypeId":1} |
+| 2120 | waterSupplyAlarmGIS | subSystem/waterSupply/alarm/alarmGIS | {"rootTypeId":1} |
 
 
 GIS 按最终要求命名为 `alarmGIS.vue`,没有“周边环境”页签;保留设备信息、报警记录、监测曲线。菜单更新不创建新页面菜单,不调整父级、排序和角色关联。
 GIS 按最终要求命名为 `alarmGIS.vue`,没有“周边环境”页签;保留设备信息、报警记录、监测曲线。菜单更新不创建新页面菜单,不调整父级、排序和角色关联。
 
 
@@ -24,14 +25,16 @@ GIS 按最终要求命名为 `alarmGIS.vue`,没有“周边环境”页签;
 
 
 GIS 监测曲线选项/曲线操作与研判事件下拉只展示 `alarm_data.actual_value` 非空的事件,数值 0 正常保留。GIS 点位和报警记录仍包含无实际值的报警,不按此条件缩减地图。研判下拉请求 `GET /api/alarm?actualValueOnly=true`,数据库在分页之前加 `actual_value IS NOT NULL`,总数同步遵循此条件;默认 false 不影响普通清单。详情进入研判的按钮同样校验实际值,深链接不将无实际值事件补入候选,曲线接口对无实际值返回 unsupported/空采样且不查询采集表。
 GIS 监测曲线选项/曲线操作与研判事件下拉只展示 `alarm_data.actual_value` 非空的事件,数值 0 正常保留。GIS 点位和报警记录仍包含无实际值的报警,不按此条件缩减地图。研判下拉请求 `GET /api/alarm?actualValueOnly=true`,数据库在分页之前加 `actual_value IS NOT NULL`,总数同步遵循此条件;默认 false 不影响普通清单。详情进入研判的按钮同样校验实际值,深链接不将无实际值事件补入候选,曲线接口对无实际值返回 unsupported/空采样且不查询采集表。
 
 
-所有新接口要求 `alarm:operations:list`,同时服务端按用户获授权的菜单校验 `rootTypeId` 和可选 `warningCode`,并查询设备类型根及全部后代。空设备类型范围返回空,不退化为无限制查询。审核授权仍单独校验 `subSystem/alarm/index`,有清单权限不等于有审核权限。GIS/研判只接受排水根类型。
+所有新接口要求 `alarm:operations:list`,同时服务端按用户获授权的菜单校验 `rootTypeId` 和可选 `warningCode`,并查询设备类型根及全部后代。空设备类型范围返回空,不退化为无限制查询。审核授权仍单独校验 `subSystem/alarm/audit`,有清单权限不等于有审核权限。地图和曲线接口接受供水及排水根类型,独立研判页面仍只用于排水。
 
 
-2117 同时保留 `waterSupply:alarm:list`(框架支持逗号分隔的权限),避免仍使用原接口的供水首页和供水报警一张图失去查询权限。
+2117 同时保留 `waterSupply:alarm:list`(框架支持逗号分隔的权限),供供水首页的统计和实时异常接口使用。供水地图及首页报警点位已使用统一地图接口。
 
 
 监测曲线使用采集协议白名单:雷达流量/流速、水位、压力,以及水质悬浮物/COD/氨氮/电导率/pH。水质按 `device_id` 和 `node_id` 关联原采集数据。日/月均值直接聚合有效原始值,字符串无效值不强转、不补零,空日/月保留断点;原始曲线大于区间中位采样间隔 3 倍时断线。未配置固定采样周期,故不虚构覆盖率。状态类等没有数值数据源的报警显示无曲线。原始结果超过 20000 个时间点时提示改看日/月均值。
 监测曲线使用采集协议白名单:雷达流量/流速、水位、压力,以及水质悬浮物/COD/氨氮/电导率/pH。水质按 `device_id` 和 `node_id` 关联原采集数据。日/月均值直接聚合有效原始值,字符串无效值不强转、不补零,空日/月保留断点;原始曲线大于区间中位采样间隔 3 倍时断线。未配置固定采样周期,故不虚构覆盖率。状态类等没有数值数据源的报警显示无曲线。原始结果超过 20000 个时间点时提示改看日/月均值。
 
 
 ## 数据库脚本及部署
 ## 数据库脚本及部署
 
 
+页面目录调整的已部署环境执行 `pipe-network-service/sql/migration_alarm_menu_components.sql`:仅更新审核菜单 2118/2142/2097/2024 和排水研判/GIS 菜单 2141/2143 的 component,不改变 path、query 或权限;同步更新前后端后重新登录。供水 GIS 菜单 2120 单独执行 `migration_water_supply_alarm_gis.sql`。初装脚本已经使用最新组件路径。
+
 本轮脚本:`pipe-network-service/sql/migration_alarm_operations.sql`(KingbaseES PostgreSQL 兼容模式)。首次部署顺序:`migration_alarm_case_work_order_id.sql` → `migration_alarm_review.sql` → `migration_alarm_operations.sql`,先落地 `work_order_id`/`create_time` 字段,再创建日志与菜单。各脚本均不再创建生命周期触发器或自定义函数。
 本轮脚本:`pipe-network-service/sql/migration_alarm_operations.sql`(KingbaseES PostgreSQL 兼容模式)。首次部署顺序:`migration_alarm_case_work_order_id.sql` → `migration_alarm_review.sql` → `migration_alarm_operations.sql`,先落地 `work_order_id`/`create_time` 字段,再创建日志与菜单。各脚本均不再创建生命周期触发器或自定义函数。
 
 
 已部署上述表结构的环境,本次必须重新部署管理端及 `zk-api-service`,日志已经改为业务代码主动写入,不依赖安装日志触发器。`pipe-network-service/sql/migration_alarm_lifecycle_log.sql` 作为增量补丁,更新单号注释/唯一索引、清除旧数据库赋号机制,并删除旧报警生命周期、工单日志镜像触发器及对应自定义函数,可重复执行,不更新菜单、不补录历史日志。部署时停止相关写入,执行清理脚本并同步更新管理端和采集端,再恢复服务。此后直接 SQL 修改业务表不会自动产生操作日志,应通过业务服务入口操作。
 已部署上述表结构的环境,本次必须重新部署管理端及 `zk-api-service`,日志已经改为业务代码主动写入,不依赖安装日志触发器。`pipe-network-service/sql/migration_alarm_lifecycle_log.sql` 作为增量补丁,更新单号注释/唯一索引、清除旧数据库赋号机制,并删除旧报警生命周期、工单日志镜像触发器及对应自定义函数,可重复执行,不更新菜单、不补录历史日志。部署时停止相关写入,执行清理脚本并同步更新管理端和采集端,再恢复服务。此后直接 SQL 修改业务表不会自动产生操作日志,应通过业务服务入口操作。

+ 3 - 2
docs/alarm-review-2026-09-23.md

@@ -2,7 +2,7 @@
 
 
 ## 已确认契约
 ## 已确认契约
 
 
-- 前端 `city-life-line/src/views/subSystem/alarm/index.vue`,后端 `AlarmController`,独立的审核服务位于 `base/alarm`。
+- 前端 `city-life-line/src/views/subSystem/alarm/audit.vue`,后端 `AlarmController`,独立的审核服务位于 `base/alarm`。
 - 仅 `alarm_status=0` 进入审核列表,一条事件一行,不按设备去重;后端分页和时间排序。
 - 仅 `alarm_status=0` 进入审核列表,一条事件一行,不按设备去重;后端分页和时间排序。
 - `audit_status`:0 未审核、1 确认报警、2 误报警。`alarm_level` 继续使用 1~4(一级最严重,红/橙/蓝/绿),不新增或更名为 `case_level`。
 - `audit_status`:0 未审核、1 确认报警、2 误报警。`alarm_level` 继续使用 1~4(一级最严重,红/橙/蓝/绿),不新增或更名为 `case_level`。
 - 确认报警原子更新审核状态、记录审核日志并创建工单;任一步失败整个审核事务回滚。不修改设备状态,也不将报警标为已处理。误报警仅记录结论及意见。
 - 确认报警原子更新审核状态、记录审核日志并创建工单;任一步失败整个审核事务回滚。不修改设备状态,也不将报警标为已处理。误报警仅记录结论及意见。
@@ -20,7 +20,8 @@
 - 管理端与采集端在新增/处理事务中主动插入 `alarm_log`,不依赖日志触发器已部署。新增记录“系统触发报警;报警描述:…”,描述优先取非空备注,否则取报警类型/编码。采集自动恢复记录“监测恢复,报警自动解除”,仅状态真正变为 1 时记录,重复正常上报不重复记日志。部署脚本删除旧生命周期及工单日志镜像触发器/自定义函数,不再由数据库自动写日志。人工处理仍记报警日志;工单关联、工单操作不再单独写入 `alarm_log`,确认日志直接包含“已转工单(实际工单号)”及选填的审核意见。日志写入失败与业务变更一起回滚。最新单号规则为程序生成 `BJyyyyMMddHHmmss + 4位随机数`,见运营交付说明。
 - 管理端与采集端在新增/处理事务中主动插入 `alarm_log`,不依赖日志触发器已部署。新增记录“系统触发报警;报警描述:…”,描述优先取非空备注,否则取报警类型/编码。采集自动恢复记录“监测恢复,报警自动解除”,仅状态真正变为 1 时记录,重复正常上报不重复记日志。部署脚本删除旧生命周期及工单日志镜像触发器/自定义函数,不再由数据库自动写日志。人工处理仍记报警日志;工单关联、工单操作不再单独写入 `alarm_log`,确认日志直接包含“已转工单(实际工单号)”及选填的审核意见。日志写入失败与业务变更一起回滚。最新单号规则为程序生成 `BJyyyyMMddHHmmss + 4位随机数`,见运营交付说明。
 - 人工操作写入 `sys_user.nick_name` 与用户 ID;历史查询按用户 ID 关联昵称,用户不存在时保留原日志名称。按 `oper_time DESC NULLS LAST, log_id DESC` 返回,审核页和详情页共用日期分组历史组件,样式参考工单 `dispose.vue`,图片保留缩略图和大图预览。
 - 人工操作写入 `sys_user.nick_name` 与用户 ID;历史查询按用户 ID 关联昵称,用户不存在时保留原日志名称。按 `oper_time DESC NULLS LAST, log_id DESC` 返回,审核页和详情页共用日期分组历史组件,样式参考工单 `dispose.vue`,图片保留缩略图和大图预览。
 - 日志枚举在 `zksy-system` 与 `zk-api-service` 各定义一份 `AlarmLogOperTypeEnum`,两端值保持一致:0 报警触发、1 确认报警、2 误报警、3 报警处理。不新增共享模块,移除关联工单/工单操作类型,历史旧类型日志保留并显示“历史操作”。已有环境执行增量脚本 `migration_alarm_review_log.sql`,不迁移或删除历史数据。
 - 日志枚举在 `zksy-system` 与 `zk-api-service` 各定义一份 `AlarmLogOperTypeEnum`,两端值保持一致:0 报警触发、1 确认报警、2 误报警、3 报警处理。不新增共享模块,移除关联工单/工单操作类型,历史旧类型日志保留并显示“历史操作”。已有环境执行增量脚本 `migration_alarm_review_log.sql`,不迁移或删除历史数据。
-- 精准更新已有菜单 2118/2142/2097/2024,`path='alarmAudit'`、`component='subSystem/alarm/index'`。设备类型根分别为 1/2/3/4,燃气额外 `warning_code='WARN-GAS-CONCENTRATION'`;保留原父级和角色关联。
+- 精准更新已有菜单 2118/2142/2097/2024,`path='alarmAudit'`、`component='subSystem/alarm/audit'`。设备类型根分别为 1/2/3/4,燃气额外 `warning_code='WARN-GAS-CONCENTRATION'`;保留原父级和角色关联。
+- 已部署环境仅因页面改名需要更新菜单时,执行 `pipe-network-service/sql/migration_alarm_menu_components.sql`,同步更新前后端后重新登录,无需重复执行建表或权限初始化。
 - 页面权限为 `alarm:review:list`;仅在缺失时创建 `alarm:review:audit` 按钮权限,给对应页面的现有角色补充新按钮授权,重复执行不恢复已经撤销的按钮授权。不会重复新增四个页面菜单。
 - 页面权限为 `alarm:review:list`;仅在缺失时创建 `alarm:review:audit` 按钮权限,给对应页面的现有角色补充新按钮授权,重复执行不恢复已经撤销的按钮授权。不会重复新增四个页面菜单。
 - `rootTypeId` 代表 `equipment_type.id` 的根,查询包含所有后代;服务端校验当前用户实际获授权的菜单范围,不能仅修改前端 query 跨系统访问。
 - `rootTypeId` 代表 `equipment_type.id` 的根,查询包含所有后代;服务端校验当前用户实际获授权的菜单范围,不能仅修改前端 query 跨系统访问。
 
 

+ 1 - 1
pipe-network-service/CONTEXT.md

@@ -53,7 +53,7 @@ _Avoid_:把供水子系统等同于单一的报警页面,或把监测设备
 
 
 4.1 **报警管理**:供水报警管理与排水报警清单统一到 `subSystem/alarm/list`,展示报警单号、设备信息、监测/审核/工单状态与责任人,按菜单 `rootTypeId` 校验授权并过滤设备类型树。清单只承载查询与详情,审核、派发和处置仍在相应页面。排水另有 `remind` 提醒、`drainage/analysis` 研判、`drainage/alarmGIS` GIS(无周边环境页签)。新清单替代旧管理页的态势/占比/导出分支;供水原有报警一张图继续保留。不同报警类型可形成不同工单;同一设备同一报警类型同时只能存在一个未结束的同类报警工单(2026-09-24)。
 4.1 **报警管理**:供水报警管理与排水报警清单统一到 `subSystem/alarm/list`,展示报警单号、设备信息、监测/审核/工单状态与责任人,按菜单 `rootTypeId` 校验授权并过滤设备类型树。清单只承载查询与详情,审核、派发和处置仍在相应页面。排水另有 `remind` 提醒、`drainage/analysis` 研判、`drainage/alarmGIS` GIS(无周边环境页签)。新清单替代旧管理页的态势/占比/导出分支;供水原有报警一张图继续保留。不同报警类型可形成不同工单;同一设备同一报警类型同时只能存在一个未结束的同类报警工单(2026-09-24)。
 
 
-4.2 **报警审核**:四个子系统统一使用 `AlarmController` 的审核接口和 `subSystem/alarm/index` 页面,列表仅展示 `alarm_status=0` 的事件。`audit_status` 独立记录未审核、确认报警、误报警;确认报警时创建待派单工单,误报警只记录结论,两种审核都不直接修改设备状态或报警处理状态。完整派单、接单、处理、验收由统一工单页承载。旧供水审核、直接派单和停用的解除接口已删除。报警生成、审核及处理记录在 `alarm_log`,确认记录直接包含“已转工单(实际工单号)”及选填意见;关联工单、工单操作不再单独写入报警日志。人工操作使用 `sys_user.nick_name`,历史按时间和日志 ID 倒序展示,不回填或删除历史数据。运行采集系统在收到有效恢复数据时,可以自动处理同设备、同报警编码的未处理告警;数值回到预警区即可处理,处理记录注明“系统自动”,不自动完成关联工单(见仓库docs/collection-alarm-recovery-2026-09-23.md)。
+4.2 **报警审核**:四个子系统统一使用 `AlarmController` 的审核接口和 `subSystem/alarm/audit` 页面,列表仅展示 `alarm_status=0` 的事件。`audit_status` 独立记录未审核、确认报警、误报警;确认报警时创建待派单工单,误报警只记录结论,两种审核都不直接修改设备状态或报警处理状态。完整派单、接单、处理、验收由统一工单页承载。旧供水审核、直接派单和停用的解除接口已删除。报警生成、审核及处理记录在 `alarm_log`,确认记录直接包含“已转工单(实际工单号)”及选填意见;关联工单、工单操作不再单独写入报警日志。人工操作使用 `sys_user.nick_name`,历史按时间和日志 ID 倒序展示,不回填或删除历史数据。运行采集系统在收到有效恢复数据时,可以自动处理同设备、同报警编码的未处理告警;数值回到预警区即可处理,处理记录注明“系统自动”,不自动完成关联工单(见仓库docs/collection-alarm-recovery-2026-09-23.md)。
 
 
 4.3 **阈值管理**:支持按传感器分类批量设置阈值、设置分时阈值并查询已配置点位的阈值详情。
 4.3 **阈值管理**:支持按传感器分类批量设置阈值、设置分时阈值并查询已配置点位的阈值详情。
 
 

+ 2 - 2
pipe-network-service/sql/migration_alarm_operations.sql

@@ -26,10 +26,10 @@ UPDATE app_user.sys_menu SET
     path = CASE menu_id WHEN 2139 THEN 'alarmRemind' WHEN 2140 THEN 'alarmList'
     path = CASE menu_id WHEN 2139 THEN 'alarmRemind' WHEN 2140 THEN 'alarmList'
         WHEN 2141 THEN 'drainageAlarmAnalysis' WHEN 2143 THEN 'drainageAlarmGIS' WHEN 2117 THEN 'alarmList' END,
         WHEN 2141 THEN 'drainageAlarmAnalysis' WHEN 2143 THEN 'drainageAlarmGIS' WHEN 2117 THEN 'alarmList' END,
     component = CASE menu_id WHEN 2139 THEN 'subSystem/alarm/remind' WHEN 2140 THEN 'subSystem/alarm/list'
     component = CASE menu_id WHEN 2139 THEN 'subSystem/alarm/remind' WHEN 2140 THEN 'subSystem/alarm/list'
-        WHEN 2141 THEN 'subSystem/alarm/drainage/analysis' WHEN 2143 THEN 'subSystem/alarm/drainage/alarmGIS'
+        WHEN 2141 THEN 'subSystem/drainage/alarm/analysis' WHEN 2143 THEN 'subSystem/drainage/alarm/alarmGIS'
         WHEN 2117 THEN 'subSystem/alarm/list' END,
         WHEN 2117 THEN 'subSystem/alarm/list' END,
     query = CASE WHEN menu_id = 2117 THEN '{"rootTypeId":1}' ELSE '{"rootTypeId":2}' END,
     query = CASE WHEN menu_id = 2117 THEN '{"rootTypeId":1}' ELSE '{"rootTypeId":2}' END,
-    -- 框架按逗号拆分权限;供水首页和供水一张图仍使用原查询接口。
+    -- 框架按逗号拆分权限;供水首页的统计和实时异常仍使用原查询接口。
     perms = CASE WHEN menu_id = 2117 THEN 'alarm:operations:list,waterSupply:alarm:list'
     perms = CASE WHEN menu_id = 2117 THEN 'alarm:operations:list,waterSupply:alarm:list'
                  ELSE 'alarm:operations:list' END,
                  ELSE 'alarm:operations:list' END,
     update_time = CURRENT_TIMESTAMP
     update_time = CURRENT_TIMESTAMP

+ 1 - 1
pipe-network-service/sql/migration_alarm_review.sql

@@ -54,7 +54,7 @@ DROP FUNCTION IF EXISTS app_user.record_alarm_lifecycle();
 DROP FUNCTION IF EXISTS app_user.record_alarm_work_order_log();
 DROP FUNCTION IF EXISTS app_user.record_alarm_work_order_log();
 
 
 -- 四条都是已有页面菜单:精准更新 ID,统一 path,保留父级、排序、图标与已有角色关联。
 -- 四条都是已有页面菜单:精准更新 ID,统一 path,保留父级、排序、图标与已有角色关联。
-UPDATE app_user.sys_menu SET path = 'alarmAudit', component = 'subSystem/alarm/index', perms = 'alarm:review:list',
+UPDATE app_user.sys_menu SET path = 'alarmAudit', component = 'subSystem/alarm/audit', perms = 'alarm:review:list',
     query = CASE menu_id
     query = CASE menu_id
         WHEN 2118 THEN '{"rootTypeId":1}'
         WHEN 2118 THEN '{"rootTypeId":1}'
         WHEN 2142 THEN '{"rootTypeId":2}'
         WHEN 2142 THEN '{"rootTypeId":2}'

+ 1 - 1
pipe-network-service/sql/tests/alarm_review.test.mjs

@@ -40,7 +40,7 @@ test('four existing menu IDs get alarmAudit and exact independent scopes, unrela
   const menus = await rows('SELECT * FROM app_user.sys_menu WHERE menu_id IN (2118,2142,2097,2024) ORDER BY menu_id')
   const menus = await rows('SELECT * FROM app_user.sys_menu WHERE menu_id IN (2118,2142,2097,2024) ORDER BY menu_id')
   const scope = { 2118:{rootTypeId:1}, 2142:{rootTypeId:2}, 2097:{rootTypeId:3,warning_code:'WARN-GAS-CONCENTRATION'}, 2024:{rootTypeId:4} }
   const scope = { 2118:{rootTypeId:1}, 2142:{rootTypeId:2}, 2097:{rootTypeId:3,warning_code:'WARN-GAS-CONCENTRATION'}, 2024:{rootTypeId:4} }
   for (const menu of menus) {
   for (const menu of menus) {
-    assert.equal(menu.path, 'alarmAudit'); assert.equal(menu.component, 'subSystem/alarm/index')
+    assert.equal(menu.path, 'alarmAudit'); assert.equal(menu.component, 'subSystem/alarm/audit')
     assert.deepEqual(JSON.parse(menu.query), scope[menu.menu_id]); assert.equal(menu.perms,'alarm:review:list')
     assert.deepEqual(JSON.parse(menu.query), scope[menu.menu_id]); assert.equal(menu.perms,'alarm:review:list')
   }
   }
   assert.equal((await rows('SELECT path FROM app_user.sys_menu WHERE menu_id=3000'))[0].path,'keep')
   assert.equal((await rows('SELECT path FROM app_user.sys_menu WHERE menu_id=3000'))[0].path,'keep')

+ 1 - 67
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/WaterSupply/MonitorAlarm/WaterSupplyAlarmController.java

@@ -1,7 +1,5 @@
 package com.zksy.web.controller.WaterSupply.MonitorAlarm;
 package com.zksy.web.controller.WaterSupply.MonitorAlarm;
 
 
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.zksy.WaterSupply.MonitorAlarm.dto.out.AlarmMapPointOutDTO;
 import com.zksy.WaterSupply.MonitorAlarm.dto.out.AlarmStatsOutDTO;
 import com.zksy.WaterSupply.MonitorAlarm.dto.out.AlarmStatsOutDTO;
 import com.zksy.WaterSupply.MonitorAlarm.dto.out.WaterAlarmOutDTO;
 import com.zksy.WaterSupply.MonitorAlarm.dto.out.WaterAlarmOutDTO;
 import com.zksy.WaterSupply.MonitorAlarm.service.IWaterSupplyAlarmService;
 import com.zksy.WaterSupply.MonitorAlarm.service.IWaterSupplyAlarmService;
@@ -16,16 +14,14 @@ import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import io.swagger.annotations.ApiParam;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.format.annotation.DateTimeFormat;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 
 
-import java.time.LocalDateTime;
 import java.util.List;
 import java.util.List;
 
 
 /**
 /**
  * 供水管网-监测报警接口
  * 供水管网-监测报警接口
- * 保留供水首页统计、实时异常及供水监测报警一张图查询;清单和审核使用统一 AlarmController
+ * 保留供水首页统计、实时异常与清警复核;地图、清单、详情和审核使用统一 AlarmController
  */
  */
 @Slf4j
 @Slf4j
 @RestController
 @RestController
@@ -68,66 +64,4 @@ public class WaterSupplyAlarmController {
         List<WaterAlarmOutDTO> list = waterSupplyAlarmService.getRealtimeAlarmList();
         List<WaterAlarmOutDTO> list = waterSupplyAlarmService.getRealtimeAlarmList();
         return AjaxResult.success(list);
         return AjaxResult.success(list);
     }
     }
-
-    @GetMapping("/history")
-    @ApiOperation("历史异常信息分页查询(支持按时间、设备、预警类型、等级、状态查询)")
-    @PreAuthorize("@ss.hasPermi('waterSupply:alarm:list')")
-    public AjaxResult getHistoryAlarmPage(
-            @ApiParam(value = "页码", defaultValue = "1") @RequestParam(defaultValue = "1") long pageNum,
-            @ApiParam(value = "每页数量", defaultValue = "10") @RequestParam(defaultValue = "10") long pageSize,
-            @ApiParam(value = "设备编码") @RequestParam(required = false) String deviceCode,
-            @ApiParam(value = "预警类型") @RequestParam(required = false) String warningType,
-            @ApiParam(value = "报警等级 1-4") @RequestParam(required = false) Integer alarmLevel,
-            @ApiParam(value = "报警状态 0-未处理 1-已处理") @RequestParam(required = false) Integer alarmStatus,
-            @ApiParam(value = "开始时间(yyyy-MM-dd HH:mm:ss)") @RequestParam(required = false)
-            @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
-            @ApiParam(value = "结束时间(yyyy-MM-dd HH:mm:ss)") @RequestParam(required = false)
-            @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime,
-            @ApiParam(value = "关键字(设备编码/预警类型/预警编码)") @RequestParam(required = false) String keyword) {
-        Page<WaterAlarmOutDTO> page = waterSupplyAlarmService.getHistoryAlarmPage(
-                pageNum, pageSize, deviceCode, warningType, alarmLevel, alarmStatus, startTime, endTime, keyword);
-        return AjaxResult.success(page);
-    }
-
-    @GetMapping("/detail/{alarmId}")
-    @ApiOperation("异常详情(报警数据 + 设备数据 + 关联工单)")
-    @PreAuthorize("@ss.hasPermi('waterSupply:alarm:list')")
-    public AjaxResult getAlarmDetail(@PathVariable String alarmId) {
-        WaterAlarmOutDTO detail = waterSupplyAlarmService.getAlarmDetail(alarmId);
-        return detail != null ? AjaxResult.success(detail) : AjaxResult.error("报警信息不存在");
-    }
-
-    // ------------------------------------------------------------------
-    // 报警管理只读台账(T02)
-    // ------------------------------------------------------------------
-
-    @GetMapping("/ledger/page")
-    @ApiOperation("报警管理只读台账分页(仅供水设备,展示状态后端推导)")
-    @PreAuthorize("@ss.hasPermi('waterSupply:alarm:list')")
-    public AjaxResult getAlarmLedgerPage(
-            @ApiParam(value = "页码", defaultValue = "1") @RequestParam(defaultValue = "1") long pageNum,
-            @ApiParam(value = "每页数量", defaultValue = "10") @RequestParam(defaultValue = "10") long pageSize,
-            @ApiParam("设备编码") @RequestParam(required = false) String deviceCode,
-            @ApiParam("报警类型") @RequestParam(required = false) String warningType,
-            @ApiParam("报警等级 1-4") @RequestParam(required = false) Integer alarmLevel,
-            @ApiParam("报警展示状态 1-待审核 2-已转工单 3-已解除") @RequestParam(required = false) Integer displayStatus,
-            @ApiParam("设备报警状态 0-正常 1-报警") @RequestParam(required = false) Integer deviceAlarmStatus,
-            @ApiParam(value = "开始时间(yyyy-MM-dd HH:mm:ss)") @RequestParam(required = false)
-            @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
-            @ApiParam(value = "结束时间(yyyy-MM-dd HH:mm:ss)") @RequestParam(required = false)
-            @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime,
-            @ApiParam("关键字(设备编码/报警类型/预警编码)") @RequestParam(required = false) String keyword) {
-        Page<WaterAlarmOutDTO> page = waterSupplyAlarmService.getAlarmLedgerPage(
-                pageNum, pageSize, deviceCode, warningType, alarmLevel, displayStatus,
-                deviceAlarmStatus, startTime, endTime, keyword);
-        return AjaxResult.success(page);
-    }
-
-    @GetMapping("/map/points")
-    @ApiOperation("监测报警一张图-报警点位(按报警级别着色)")
-    @PreAuthorize("@ss.hasPermi('waterSupply:alarm:list')")
-    public AjaxResult getAlarmMapPoints() {
-        List<AlarmMapPointOutDTO> points = waterSupplyAlarmService.getAlarmMapPoints();
-        return AjaxResult.success(points);
-    }
 }
 }

+ 1 - 1
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/alarm/AlarmController.java

@@ -74,7 +74,7 @@ public class AlarmController {
     public AjaxResult options(@Valid AlarmQuery query) { return AjaxResult.success(operations.options(query)); }
     public AjaxResult options(@Valid AlarmQuery query) { return AjaxResult.success(operations.options(query)); }
 
 
     @GetMapping("/map")
     @GetMapping("/map")
-    @ApiOperation("排水报警GIS点位")
+    @ApiOperation("供水、排水当前报警GIS点位,按设备大类隔离")
     @PreAuthorize("@ss.hasPermi('alarm:operations:list')")
     @PreAuthorize("@ss.hasPermi('alarm:operations:list')")
     public AjaxResult map(@Valid AlarmQuery query) { return AjaxResult.success(operations.map(query)); }
     public AjaxResult map(@Valid AlarmQuery query) { return AjaxResult.success(operations.map(query)); }
 
 

+ 0 - 89
pipe-network-service/zksy-admin/src/test/java/com/zksy/web/controller/WaterSupply/MonitorAlarm/WaterSupplyAlarmLedgerControllerTest.java

@@ -1,89 +0,0 @@
-package com.zksy.web.controller.WaterSupply.MonitorAlarm;
-
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.zksy.WaterSupply.MonitorAlarm.dto.out.WaterAlarmOutDTO;
-import com.zksy.WaterSupply.MonitorAlarm.service.IWaterSupplyAlarmService;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.test.util.ReflectionTestUtils;
-import org.springframework.test.web.servlet.MockMvc;
-import org.springframework.test.web.servlet.setup.MockMvcBuilders;
-
-import java.util.Collections;
-import java.util.List;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
-/**
- * 供水报警管理只读台账契约测试(T02 / Issue #45)。
- *
- * <p>被测公共 seam:{@code GET /waterSupply/alarm/ledger/page}。
- * 只验证外部可见行为:响应字段与筛选条件透传。</p>
- */
-class WaterSupplyAlarmLedgerControllerTest {
-
-    private IWaterSupplyAlarmService alarmService;
-    private MockMvc mockMvc;
-
-    @BeforeEach
-    void setUp() {
-        alarmService = mock(IWaterSupplyAlarmService.class);
-        WaterSupplyAlarmController controller = new WaterSupplyAlarmController();
-        ReflectionTestUtils.setField(controller, "waterSupplyAlarmService", alarmService);
-        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
-    }
-
-    /**
-     * MockMvc 默认以 ISO-8859-1 解码响应,这里显式按 UTF-8 取体,便于断言中文字段。
-     */
-    private static String body(org.springframework.test.web.servlet.MvcResult result) throws Exception {
-        return new String(result.getResponse().getContentAsByteArray(), java.nio.charset.StandardCharsets.UTF_8);
-    }
-
-    @Test
-    void ledgerPageExposesDerivedDisplayStatusAndDeviceAlarmStatus() throws Exception {
-        WaterAlarmOutDTO row = new WaterAlarmOutDTO();
-        row.setId("alarm-1");
-        row.setDeviceCode("CN-GS-202609020001");
-        row.setWarningType("压力预警");
-        row.setWarningCode("WARN-PRESSURE");
-        row.setAlarmLevel(1);
-        row.setAlarmLevelText("一级报警");
-        row.setDeviceAlarmStatus(1);
-        row.setDeviceAlarmStatusText("报警中");
-        row.setDisplayStatus(2);
-        row.setDisplayStatusText("已转工单");
-        row.setOrderNo("WO202606101703482640");
-        Page<WaterAlarmOutDTO> page = new Page<>(1, 10, 1);
-        page.setRecords(Collections.singletonList(row));
-        when(alarmService.getAlarmLedgerPage(anyLong(), anyLong(), any(), any(), any(), any(), any(), any(), any(), any()))
-                .thenReturn(page);
-
-        mockMvc.perform(get("/waterSupply/alarm/ledger/page")
-                        .param("pageNum", "1")
-                        .param("pageSize", "10"))
-                .andExpect(status().isOk())
-                .andExpect(result -> {
-                    String json = body(result);
-                    assertThat(json, "\"displayStatus\":2");
-                    assertThat(json, "\"displayStatusText\":\"已转工单\"");
-                    assertThat(json, "\"deviceAlarmStatus\":1");
-                    assertThat(json, "\"deviceAlarmStatusText\":\"报警中\"");
-                    assertThat(json, "WO202606101703482640");
-                    assertThat(json, "WARN-PRESSURE");
-                });
-    }
-
-    private static void assertThat(String json, String expected) {
-        if (!json.contains(expected)) {
-            throw new AssertionError("响应缺少期望内容:" + expected + "\n实际:" + json);
-        }
-    }
-}

+ 0 - 54
pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/MonitorAlarm/dto/out/AlarmMapPointOutDTO.java

@@ -1,54 +0,0 @@
-package com.zksy.WaterSupply.MonitorAlarm.dto.out;
-
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import java.io.Serializable;
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-
-/**
- * 监测报警一张图-点位出参
- */
-@Data
-@ApiModel(value = "监测报警一张图-点位出参", description = "报警点位信息,供 GIS 地图按报警级别着色展示")
-public class AlarmMapPointOutDTO implements Serializable {
-
-    private static final long serialVersionUID = 1L;
-
-    @ApiModelProperty(value = "报警ID")
-    private String alarmId;
-
-    @ApiModelProperty(value = "设备编码")
-    private String deviceCode;
-
-    @ApiModelProperty(value = "设备名称")
-    private String deviceName;
-
-    @ApiModelProperty(value = "设备位置")
-    private String equipmentLocation;
-
-    @ApiModelProperty(value = "经度")
-    private String longitude;
-
-    @ApiModelProperty(value = "纬度")
-    private String latitude;
-
-    @ApiModelProperty(value = "预警类型")
-    private String warningType;
-
-    @ApiModelProperty(value = "报警参数值")
-    private BigDecimal actualValue;
-
-    @ApiModelProperty(value = "报警等级:1-4,1级最高(用于地图着色)")
-    private Integer alarmLevel;
-
-    @ApiModelProperty(value = "报警等级文本")
-    private String alarmLevelText;
-
-    @ApiModelProperty(value = "报警触发时间(取记录创建时间)")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime createTime;
-}

+ 0 - 44
pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/MonitorAlarm/service/IWaterSupplyAlarmService.java

@@ -1,11 +1,8 @@
 package com.zksy.WaterSupply.MonitorAlarm.service;
 package com.zksy.WaterSupply.MonitorAlarm.service;
 
 
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.zksy.WaterSupply.MonitorAlarm.dto.out.AlarmMapPointOutDTO;
 import com.zksy.WaterSupply.MonitorAlarm.dto.out.AlarmStatsOutDTO;
 import com.zksy.WaterSupply.MonitorAlarm.dto.out.AlarmStatsOutDTO;
 import com.zksy.WaterSupply.MonitorAlarm.dto.out.WaterAlarmOutDTO;
 import com.zksy.WaterSupply.MonitorAlarm.dto.out.WaterAlarmOutDTO;
 
 
-import java.time.LocalDateTime;
 import java.util.List;
 import java.util.List;
 
 
 /**
 /**
@@ -22,45 +19,4 @@ public interface IWaterSupplyAlarmService {
      * 实时异常报警列表(未处理报警,信息卡片展示)
      * 实时异常报警列表(未处理报警,信息卡片展示)
      */
      */
     List<WaterAlarmOutDTO> getRealtimeAlarmList();
     List<WaterAlarmOutDTO> getRealtimeAlarmList();
-
-    /**
-     * 历史异常报警分页查询
-     */
-    Page<WaterAlarmOutDTO> getHistoryAlarmPage(long pageNum, long pageSize,
-                                               String deviceCode, String warningType,
-                                               Integer alarmLevel, Integer alarmStatus,
-                                               LocalDateTime startTime, LocalDateTime endTime,
-                                               String keyword);
-
-    /**
-     * 报警管理只读台账分页:仅供水设备报警,展示状态由后端推导
-     *
-     * @param displayStatus     报警展示状态:1-待审核 2-已转工单 3-已解除
-     * @param deviceAlarmStatus 设备报警状态:0-正常 1-报警
-     */
-    Page<WaterAlarmOutDTO> getAlarmLedgerPage(long pageNum, long pageSize,
-                                              String deviceCode, String warningType,
-                                              Integer alarmLevel, Integer displayStatus,
-                                              Integer deviceAlarmStatus,
-                                              LocalDateTime startTime, LocalDateTime endTime,
-                                              String keyword);
-
-    /**
-     * 报警管理只读台账导出行(与分页同一推导口径,不分页)
-     */
-    List<WaterAlarmOutDTO> getAlarmLedgerExport(String deviceCode, String warningType,
-                                                Integer alarmLevel, Integer displayStatus,
-                                                Integer deviceAlarmStatus,
-                                                LocalDateTime startTime, LocalDateTime endTime,
-                                                String keyword);
-
-    /**
-     * 报警详情(报警数据 + 设备数据 + 关联工单)
-     */
-    WaterAlarmOutDTO getAlarmDetail(String alarmId);
-
-    /**
-     * 监测报警一张图点位(未处理报警,含经纬度与报警级别)
-     */
-    List<AlarmMapPointOutDTO> getAlarmMapPoints();
 }
 }

+ 2 - 203
pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/MonitorAlarm/service/impl/WaterSupplyAlarmServiceImpl.java

@@ -4,8 +4,6 @@ import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.zksy.WaterSupply.MonitorAlarm.dto.out.AlarmMapPointOutDTO;
 import com.zksy.WaterSupply.MonitorAlarm.dto.out.AlarmStatsOutDTO;
 import com.zksy.WaterSupply.MonitorAlarm.dto.out.AlarmStatsOutDTO;
 import com.zksy.WaterSupply.MonitorAlarm.dto.out.WaterAlarmOutDTO;
 import com.zksy.WaterSupply.MonitorAlarm.dto.out.WaterAlarmOutDTO;
 import com.zksy.WaterSupply.MonitorAlarm.service.IWaterSupplyAlarmService;
 import com.zksy.WaterSupply.MonitorAlarm.service.IWaterSupplyAlarmService;
@@ -22,9 +20,6 @@ import com.zksy.base.domain.EquipmentStatus;
 import com.zksy.base.domain.EquipmentType;
 import com.zksy.base.domain.EquipmentType;
 import com.zksy.base.mapper.EquipmentTypeMapper;
 import com.zksy.base.mapper.EquipmentTypeMapper;
 import com.zksy.ops.domain.WorkOrder;
 import com.zksy.ops.domain.WorkOrder;
-import com.zksy.base.mapper.EquipmentBaseMapper;
-import com.zksy.ops.mapper.WorkOrderMapper;
-import com.zksy.common.exception.ServiceException;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
@@ -34,8 +29,6 @@ import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Collections;
-import java.util.Comparator;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Objects;
@@ -60,14 +53,6 @@ public class WaterSupplyAlarmServiceImpl implements IWaterSupplyAlarmService {
     @Autowired
     @Autowired
     private WaterSupplyQueryService waterSupplyQueryService;
     private WaterSupplyQueryService waterSupplyQueryService;
 
 
-    @Autowired
-    private EquipmentBaseMapper equipmentBaseMapper;
-
-    @Autowired
-    private WorkOrderMapper workOrderMapper;
-
-
-
     @Autowired
     @Autowired
     private EquipmentTypeMapper equipmentTypeMapper;
     private EquipmentTypeMapper equipmentTypeMapper;
 
 
@@ -103,7 +88,7 @@ public class WaterSupplyAlarmServiceImpl implements IWaterSupplyAlarmService {
     }
     }
 
 
     /**
     /**
-     * 报警ID -> 工单映射(通过 alarm_data.order_id 关联)
+     * 报警ID -> 工单映射(通过 alarm_data.work_order_id 关联)
      */
      */
     private Map<String, WorkOrder> loadWorkOrderMapByAlarmIds(List<String> alarmIds) {
     private Map<String, WorkOrder> loadWorkOrderMapByAlarmIds(List<String> alarmIds) {
         return waterSupplyQueryService.latestOrdersByAlarmIds(alarmIds);
         return waterSupplyQueryService.latestOrdersByAlarmIds(alarmIds);
@@ -151,7 +136,7 @@ public class WaterSupplyAlarmServiceImpl implements IWaterSupplyAlarmService {
     }
     }
 
 
     // ------------------------------------------------------------------
     // ------------------------------------------------------------------
-    // 实时 / 历史 / 详情
+    // 首页实时异常
     // ------------------------------------------------------------------
     // ------------------------------------------------------------------
 
 
     @Override
     @Override
@@ -172,99 +157,6 @@ public class WaterSupplyAlarmServiceImpl implements IWaterSupplyAlarmService {
                 .collect(Collectors.toList());
                 .collect(Collectors.toList());
     }
     }
 
 
-    @Override
-    public Page<WaterAlarmOutDTO> getHistoryAlarmPage(long pageNum, long pageSize,
-                                                      String deviceCode, String warningType,
-                                                      Integer alarmLevel, Integer alarmStatus,
-                                                      LocalDateTime startTime, LocalDateTime endTime,
-                                                      String keyword) {
-        Page<AlarmData> page = new Page<>(pageNum, pageSize);
-        Set<String> waterCodes = resolveWaterDeviceCodes();
-        LambdaQueryWrapper<AlarmData> wrapper = new LambdaQueryWrapper<>();
-        if (!waterCodes.isEmpty()) {
-            wrapper.in(AlarmData::getDeviceCode, waterCodes);
-        } else {
-            wrapper.eq(AlarmData::getId, "-1");
-        }
-        wrapper.eq(StrUtil.isNotBlank(deviceCode), AlarmData::getDeviceCode, deviceCode)
-                .like(StrUtil.isNotBlank(warningType), AlarmData::getWarningType, warningType)
-                .eq(alarmLevel != null, AlarmData::getAlarmLevel, alarmLevel)
-                .eq(alarmStatus != null, AlarmData::getAlarmStatus, alarmStatus)
-                .ge(startTime != null, AlarmData::getCreateTime, startTime)
-                .le(endTime != null, AlarmData::getCreateTime, endTime)
-                .and(StrUtil.isNotBlank(keyword), w -> w.like(AlarmData::getDeviceCode, keyword)
-                        .or().like(AlarmData::getWarningType, keyword)
-                        .or().like(AlarmData::getWarningCode, keyword))
-                .orderByDesc(AlarmData::getCreateTime);
-        Page<AlarmData> result = alarmDataService.page(page, wrapper);
-        Page<WaterAlarmOutDTO> outPage = new Page<>(pageNum, pageSize, result.getTotal());
-        outPage.setRecords(convertToVoList(result.getRecords()));
-        return outPage;
-    }
-
-    @Override
-    public WaterAlarmOutDTO getAlarmDetail(String alarmId) {
-        AlarmData alarm = alarmDataService.getById(alarmId);
-        if (alarm == null) {
-            throw new ServiceException("报警信息不存在");
-        }
-        List<WaterAlarmOutDTO> vos = convertToVoList(Collections.singletonList(alarm));
-        return vos.isEmpty() ? null : vos.get(0);
-    }
-
-    // ------------------------------------------------------------------
-    // 报警管理只读台账(供水设备 + 后端推导展示状态)
-    // ------------------------------------------------------------------
-
-    @Override
-    public Page<WaterAlarmOutDTO> getAlarmLedgerPage(long pageNum, long pageSize,
-                                                     String deviceCode, String warningType,
-                                                     Integer alarmLevel, Integer displayStatus,
-                                                     Integer deviceAlarmStatus,
-                                                     LocalDateTime startTime, LocalDateTime endTime,
-                                                     String keyword) {
-        List<WaterAlarmOutDTO> rows = getAlarmLedgerExport(deviceCode, warningType, alarmLevel,
-                displayStatus, deviceAlarmStatus, startTime, endTime, keyword);
-        Page<WaterAlarmOutDTO> page = new Page<>(pageNum, pageSize, rows.size());
-        if (pageNum < 1 || pageSize < 1) {
-            page.setRecords(new ArrayList<>());
-            return page;
-        }
-        int from = (int) Math.min((pageNum - 1) * pageSize, rows.size());
-        int to = (int) Math.min(from + pageSize, rows.size());
-        page.setRecords(new ArrayList<>(rows.subList(from, to)));
-        return page;
-    }
-
-    @Override
-    public List<WaterAlarmOutDTO> getAlarmLedgerExport(String deviceCode, String warningType,
-                                                       Integer alarmLevel, Integer displayStatus,
-                                                       Integer deviceAlarmStatus,
-                                                       LocalDateTime startTime, LocalDateTime endTime,
-                                                       String keyword) {
-        Set<String> waterCodes = resolveWaterDeviceCodes();
-        if (waterCodes.isEmpty()) {
-            return new ArrayList<>();
-        }
-        LambdaQueryWrapper<AlarmData> wrapper = new LambdaQueryWrapper<AlarmData>()
-                .in(AlarmData::getDeviceCode, waterCodes)
-                .eq(StrUtil.isNotBlank(deviceCode), AlarmData::getDeviceCode, deviceCode)
-                .like(StrUtil.isNotBlank(warningType), AlarmData::getWarningType, warningType)
-                .eq(alarmLevel != null, AlarmData::getAlarmLevel, alarmLevel)
-                .ge(startTime != null, AlarmData::getCreateTime, startTime)
-                .le(endTime != null, AlarmData::getCreateTime, endTime)
-                .and(StrUtil.isNotBlank(keyword), w -> w.like(AlarmData::getDeviceCode, keyword)
-                        .or().like(AlarmData::getWarningType, keyword)
-                        .or().like(AlarmData::getWarningCode, keyword))
-                .orderByDesc(AlarmData::getCreateTime);
-        List<AlarmData> alarms = alarmDataService.list(wrapper);
-        List<WaterAlarmOutDTO> rows = buildLedgerVoList(alarms);
-        return rows.stream()
-                .filter(row -> displayStatus == null || displayStatus.equals(row.getDisplayStatus()))
-                .filter(row -> deviceAlarmStatus == null || deviceAlarmStatus.equals(row.getDeviceAlarmStatus()))
-                .collect(Collectors.toList());
-    }
-
     /**
     /**
      * 报警记录 -> 台账出参:补充设备报警状态与后端推导的报警展示状态。
      * 报警记录 -> 台账出参:补充设备报警状态与后端推导的报警展示状态。
      *
      *
@@ -364,101 +256,8 @@ public class WaterSupplyAlarmServiceImpl implements IWaterSupplyAlarmService {
         return waterSupplyQueryService.statusesByDeviceIds(equipmentIds);
         return waterSupplyQueryService.statusesByDeviceIds(equipmentIds);
     }
     }
 
 
-    /**
-     * 报警记录 -> 出参(填充设备名称/位置/经纬度及关联工单)
-     */
-    private List<WaterAlarmOutDTO> convertToVoList(List<AlarmData> alarms) {
-        if (CollUtil.isEmpty(alarms)) {
-            return new ArrayList<>();
-        }
-        Map<String, EquipmentBase> eqMap = loadEquipmentCodeMap(alarms.stream()
-                .map(AlarmData::getDeviceCode).collect(Collectors.toSet()));
-        Map<Integer, String> typeNames = loadDeviceTypeNames(eqMap.values());
-        List<String> alarmIds = alarms.stream().map(AlarmData::getId).collect(Collectors.toList());
-        Map<String, WorkOrder> orderMap = loadWorkOrderMapByAlarmIds(alarmIds);
-        List<WaterAlarmOutDTO> vos = new ArrayList<>();
-        for (AlarmData alarm : alarms) {
-            WaterAlarmOutDTO vo = BeanUtil.copyProperties(alarm, WaterAlarmOutDTO.class);
-            EquipmentBase eq = alarm.getDeviceCode() == null ? null : eqMap.get(alarm.getDeviceCode());
-            if (eq != null) {
-                vo.setEquipmentId(eq.getEquipmentId());
-                vo.setDeviceType(typeNames.get(eq.getEquipmentTypeId()));
-                if (StrUtil.isBlank(vo.getDeviceName())) {
-                    vo.setDeviceName(eq.getEquipmentName());
-                }
-                vo.setEquipmentLocation(eq.getEquipmentLocation());
-                if (eq.getLongitude() != null) {
-                    vo.setLongitude(eq.getLongitude().toPlainString());
-                }
-                if (eq.getLatitude() != null) {
-                    vo.setLatitude(eq.getLatitude().toPlainString());
-                }
-            }
-            vo.setAlarmLevelText(formatAlarmLevel(alarm.getAlarmLevel()));
-            vo.setAlarmStatusText(alarm.getAlarmStatus() != null && alarm.getAlarmStatus() == 0 ? "待处理" : "已处理");
-            WorkOrder order = alarm.getId() == null ? null : orderMap.get(alarm.getId());
-            if (order != null) {
-                vo.setOrderNo(order.getOrderNo());
-                vo.setOrderStatus(order.getOrderStatus());
-            }
-            vos.add(vo);
-        }
-        return vos;
-    }
-
     private String formatAlarmLevel(Integer alarmLevel) {
     private String formatAlarmLevel(Integer alarmLevel) {
         AlarmLevelEnum level = AlarmLevelEnum.fromCode(alarmLevel);
         AlarmLevelEnum level = AlarmLevelEnum.fromCode(alarmLevel);
         return level == null ? "未知等级" : level.getLabel();
         return level == null ? "未知等级" : level.getLabel();
     }
     }
-
-    // ------------------------------------------------------------------
-    // 监测报警一张图
-    // ------------------------------------------------------------------
-
-    @Override
-    public List<AlarmMapPointOutDTO> getAlarmMapPoints() {
-        Set<String> waterCodes = resolveWaterDeviceCodes();
-        if (waterCodes.isEmpty()) {
-            return new ArrayList<>();
-        }
-        List<AlarmData> pendingAlarms = alarmDataService.list(new LambdaQueryWrapper<AlarmData>()
-                .in(AlarmData::getDeviceCode, waterCodes)
-                .eq(AlarmData::getAlarmStatus, 0)
-                .orderByDesc(AlarmData::getCreateTime));
-        Map<String, EquipmentBase> eqMap = loadEquipmentCodeMap(pendingAlarms.stream()
-                .map(AlarmData::getDeviceCode).collect(Collectors.toSet()));
-        // 同一设备只展示最新一条未处理报警
-        Map<String, AlarmData> latestPerDevice = new LinkedHashMap<>();
-        for (AlarmData alarm : pendingAlarms) {
-            latestPerDevice.putIfAbsent(alarm.getDeviceCode(), alarm);
-        }
-        List<AlarmMapPointOutDTO> points = new ArrayList<>();
-        for (AlarmData alarm : latestPerDevice.values()) {
-            EquipmentBase eq = alarm.getDeviceCode() == null ? null : eqMap.get(alarm.getDeviceCode());
-            if (eq == null || eq.getLongitude() == null || eq.getLatitude() == null) {
-                continue;
-            }
-            AlarmMapPointOutDTO point = new AlarmMapPointOutDTO();
-            point.setAlarmId(alarm.getId());
-            point.setDeviceCode(alarm.getDeviceCode());
-            point.setDeviceName(eq.getEquipmentName());
-            point.setEquipmentLocation(eq.getEquipmentLocation());
-            point.setLongitude(eq.getLongitude().toPlainString());
-            point.setLatitude(eq.getLatitude().toPlainString());
-            point.setWarningType(alarm.getWarningType());
-            point.setActualValue(alarm.getActualValue());
-            point.setAlarmLevel(alarm.getAlarmLevel());
-            point.setAlarmLevelText(formatAlarmLevel(alarm.getAlarmLevel()));
-            point.setCreateTime(alarm.getCreateTime());
-            points.add(point);
-        }
-        points.sort(Comparator.comparing(AlarmMapPointOutDTO::getAlarmLevel, Comparator.nullsLast(Comparator.naturalOrder())));
-        return points;
-    }
-
-    // ------------------------------------------------------------------
-    // 当前用户
-    // ------------------------------------------------------------------
-
-
 }
 }

+ 1 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/base/alarm/dto/AlarmReviewQuery.java

@@ -14,6 +14,7 @@ public class AlarmReviewQuery {
     @ApiModelProperty("报警编码,精确匹配;燃气菜单限定 WARN-GAS-CONCENTRATION")
     @ApiModelProperty("报警编码,精确匹配;燃气菜单限定 WARN-GAS-CONCENTRATION")
     @Size(max = 100)
     @Size(max = 100)
     private String warningCode;
     private String warningCode;
+    @ApiModelProperty("设备编码/名称,模糊匹配")
     @Size(max = 100)
     @Size(max = 100)
     private String keyword;
     private String keyword;
     private Integer alarmLevel;
     private Integer alarmLevel;

+ 7 - 5
pipe-network-service/zksy-system/src/main/java/com/zksy/base/alarm/service/AlarmOperationsService.java

@@ -88,7 +88,8 @@ public class AlarmOperationsService {
         return result;
         return result;
     }
     }
     public List<AlarmVO> map(AlarmQuery query) {
     public List<AlarmVO> map(AlarmQuery query) {
-        requireDrainage(query);
+        requireWaterSubsystem(query);
+        query.setAlarmStatus(AlarmRecordStatusEnum.UNHANDLED.getCode());
         validate(query);
         validate(query);
         List<AlarmVO> result = mapper.selectMap(query, scope.resolveOperations(query));
         List<AlarmVO> result = mapper.selectMap(query, scope.resolveOperations(query));
         if (result.size() > 10000) throw new ServiceException("报警数量超过地图展示上限,请缩小查询范围");
         if (result.size() > 10000) throw new ServiceException("报警数量超过地图展示上限,请缩小查询范围");
@@ -96,7 +97,7 @@ public class AlarmOperationsService {
         return result;
         return result;
     }
     }
     public Map<String, Object> analysis(String id, AlarmQuery query, AlarmSeriesPeriod period) {
     public Map<String, Object> analysis(String id, AlarmQuery query, AlarmSeriesPeriod period) {
-        requireDrainage(query);
+        requireWaterSubsystem(query);
         AlarmVO alarm = detail(id, query);
         AlarmVO alarm = detail(id, query);
         AlarmMetricSource source = AlarmMetricSource.fromCode(alarm.getWarningCode());
         AlarmMetricSource source = AlarmMetricSource.fromCode(alarm.getWarningCode());
         Map<String, Object> result = new LinkedHashMap<>();
         Map<String, Object> result = new LinkedHashMap<>();
@@ -151,9 +152,10 @@ public class AlarmOperationsService {
     private Map<String, Object> option(int value, String label) {
     private Map<String, Object> option(int value, String label) {
         Map<String,Object> result = new LinkedHashMap<>(); result.put("value",value); result.put("label",label); return result;
         Map<String,Object> result = new LinkedHashMap<>(); result.put("value",value); result.put("label",label); return result;
     }
     }
-    private void requireDrainage(AlarmQuery query) {
-        if (!EquipmentTypeEnum.ROOT_DRAINAGE_DEVICE.getId().equals(query.getRootTypeId()))
-            throw new ServiceException("该功能仅适用于排水子系统");
+    private void requireWaterSubsystem(AlarmQuery query) {
+        if (!EquipmentTypeEnum.ROOT_WATER_SUPPLY_DEVICE.getId().equals(query.getRootTypeId())
+                && !EquipmentTypeEnum.ROOT_DRAINAGE_DEVICE.getId().equals(query.getRootTypeId()))
+            throw new ServiceException("该功能仅适用于供水或排水子系统");
     }
     }
     private void validate(AlarmQuery query) {
     private void validate(AlarmQuery query) {
         if (query.getPageNum() < 1 || query.getPageSize() < 1 || query.getPageSize() > 100) throw new ServiceException("分页参数无效");
         if (query.getPageNum() < 1 || query.getPageSize() < 1 || query.getPageSize() > 100) throw new ServiceException("分页参数无效");

+ 4 - 3
pipe-network-service/zksy-system/src/main/java/com/zksy/base/alarm/service/AlarmReviewScopeService.java

@@ -23,13 +23,14 @@ public class AlarmReviewScopeService {
     private final ISysMenuService menuService;
     private final ISysMenuService menuService;
 
 
     public List<Integer> resolve(AlarmReviewQuery query) {
     public List<Integer> resolve(AlarmReviewQuery query) {
-        return resolve(query, Collections.singleton("subSystem/alarm/index"));
+        return resolve(query, Collections.singleton("subSystem/alarm/audit"));
     }
     }
 
 
     /** 读取与审核授权独立,列表菜单不能授予跨系统审核能力。 */
     /** 读取与审核授权独立,列表菜单不能授予跨系统审核能力。 */
     public List<Integer> resolveOperations(AlarmReviewQuery query) {
     public List<Integer> resolveOperations(AlarmReviewQuery query) {
         return resolve(query, new HashSet<>(Arrays.asList("subSystem/alarm/remind", "subSystem/alarm/list",
         return resolve(query, new HashSet<>(Arrays.asList("subSystem/alarm/remind", "subSystem/alarm/list",
-                "subSystem/alarm/drainage/analysis", "subSystem/alarm/drainage/alarmGIS")));
+                "subSystem/drainage/alarm/analysis", "subSystem/drainage/alarm/alarmGIS",
+                "subSystem/waterSupply/alarm/alarmGIS")));
     }
     }
 
 
     private List<Integer> resolve(AlarmReviewQuery query, Set<String> components) {
     private List<Integer> resolve(AlarmReviewQuery query, Set<String> components) {
@@ -55,7 +56,7 @@ public class AlarmReviewScopeService {
     }
     }
 
 
     boolean matches(SysMenu menu, AlarmReviewQuery query) {
     boolean matches(SysMenu menu, AlarmReviewQuery query) {
-        return matches(menu, query, Collections.singleton("subSystem/alarm/index"));
+        return matches(menu, query, Collections.singleton("subSystem/alarm/audit"));
     }
     }
 
 
     private boolean matches(SysMenu menu, AlarmReviewQuery query, Set<String> components) {
     private boolean matches(SysMenu menu, AlarmReviewQuery query, Set<String> components) {

+ 1 - 2
pipe-network-service/zksy-system/src/main/resources/mapper/base/AlarmOperationsMapper.xml

@@ -23,8 +23,7 @@
         <include refid="com.zksy.base.alarm.mapper.AlarmReviewMapper.scope"/>
         <include refid="com.zksy.base.alarm.mapper.AlarmReviewMapper.scope"/>
         <if test="query.keyword != null and query.keyword != ''">
         <if test="query.keyword != null and query.keyword != ''">
             AND (a.device_code ILIKE CONCAT('%',#{query.keyword},'%')
             AND (a.device_code ILIKE CONCAT('%',#{query.keyword},'%')
-                 OR e.equipment_name ILIKE CONCAT('%',#{query.keyword},'%')
-                 OR e.equipment_location ILIKE CONCAT('%',#{query.keyword},'%'))
+                 OR e.equipment_name ILIKE CONCAT('%',#{query.keyword},'%'))
         </if>
         </if>
         <if test="query.alarmNo != null and query.alarmNo != ''">AND a.alarm_no ILIKE CONCAT('%',#{query.alarmNo},'%')</if>
         <if test="query.alarmNo != null and query.alarmNo != ''">AND a.alarm_no ILIKE CONCAT('%',#{query.alarmNo},'%')</if>
         <if test="query.deviceCode != null and query.deviceCode != ''">AND a.device_code = #{query.deviceCode}</if>
         <if test="query.deviceCode != null and query.deviceCode != ''">AND a.device_code = #{query.deviceCode}</if>

+ 0 - 234
pipe-network-service/zksy-system/src/test/java/com/zksy/WaterSupply/MonitorAlarm/WaterSupplyAlarmLedgerServiceTest.java

@@ -1,234 +0,0 @@
-package com.zksy.WaterSupply.MonitorAlarm;
-
-import com.baomidou.mybatisplus.core.MybatisConfiguration;
-import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
-import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.zksy.WaterSupply.MonitorAlarm.dto.out.WaterAlarmOutDTO;
-import com.zksy.WaterSupply.MonitorAlarm.service.impl.WaterSupplyAlarmServiceImpl;
-import com.zksy.base.service.impl.WaterSupplyQueryService;
-import com.zksy.base.alarm.domain.AlarmData;
-import com.zksy.base.alarm.mapper.AlarmDataMapper;
-import com.zksy.base.alarm.service.AlarmDataService;
-import com.zksy.base.domain.EquipmentBase;
-import com.zksy.base.domain.EquipmentStatus;
-import com.zksy.base.mapper.EquipmentBaseMapper;
-import com.zksy.base.mapper.EquipmentStatusMapper;
-import com.zksy.base.mapper.EquipmentTypeMapper;
-import com.zksy.base.service.EquipmentBaseService;
-import com.zksy.ops.domain.WorkOrder;
-import com.zksy.ops.mapper.WorkOrderMapper;
-import org.apache.ibatis.builder.MapperBuilderAssistant;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.mockito.ArgumentCaptor;
-import org.springframework.test.util.ReflectionTestUtils;
-
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-/**
- * 报警管理只读台账推导口径测试(T02 / Issue #45)。
- *
- * <p>被测公共 seam:{@link com.zksy.WaterSupply.MonitorAlarm.service.IWaterSupplyAlarmService#getAlarmLedgerPage}。
- * 验证报警展示状态、设备报警状态与供水数据边界三项外部可见行为。</p>
- */
-class WaterSupplyAlarmLedgerServiceTest {
-
-    @BeforeAll
-    static void initLambdaCache() {
-        MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
-        TableInfoHelper.initTableInfo(assistant, AlarmData.class);
-        TableInfoHelper.initTableInfo(assistant, EquipmentBase.class);
-        TableInfoHelper.initTableInfo(assistant, EquipmentStatus.class);
-        TableInfoHelper.initTableInfo(assistant, WorkOrder.class);
-    }
-
-    private WaterSupplyAlarmServiceImpl service;
-    private AlarmDataService alarmDataService;
-    private AlarmDataMapper alarmDataMapper;
-    private EquipmentBaseService equipmentBaseService;
-    private EquipmentBaseMapper equipmentBaseMapper;
-    private EquipmentStatusMapper equipmentStatusMapper;
-    private EquipmentTypeMapper equipmentTypeMapper;
-    private WorkOrderMapper workOrderMapper;
-
-    private EquipmentBase pressureDevice;
-    private EquipmentBase flowDevice;
-
-    @BeforeEach
-    void setUp() {
-        service = new WaterSupplyAlarmServiceImpl();
-        alarmDataService = mock(AlarmDataService.class);
-        alarmDataMapper = mock(AlarmDataMapper.class);
-        equipmentBaseService = mock(EquipmentBaseService.class);
-        equipmentBaseMapper = mock(EquipmentBaseMapper.class);
-        equipmentStatusMapper = mock(EquipmentStatusMapper.class);
-        equipmentTypeMapper = mock(EquipmentTypeMapper.class);
-        workOrderMapper = mock(WorkOrderMapper.class);
-        ReflectionTestUtils.setField(service, "alarmDataService", alarmDataService);
-        ReflectionTestUtils.setField(service, "waterSupplyQueryService",
-                new WaterSupplyQueryService(equipmentBaseService, equipmentBaseMapper,
-                        equipmentStatusMapper, equipmentTypeMapper, workOrderMapper,
-                        alarmDataMapper));
-        ReflectionTestUtils.setField(service, "equipmentBaseMapper", equipmentBaseMapper);
-        ReflectionTestUtils.setField(service, "workOrderMapper", workOrderMapper);
-
-        pressureDevice = device("EQ-P-1", "CN-GS-202609020001", "城南压力监测点");
-        flowDevice = device("EQ-F-1", "CN-GS-202609020002", "城北流量监测点");
-        when(equipmentBaseService.findByTopLevelType("供水")).thenReturn(Arrays.asList(pressureDevice, flowDevice));
-        when(equipmentBaseMapper.selectList(any())).thenReturn(Arrays.asList(pressureDevice, flowDevice));
-    }
-
-    private static EquipmentBase device(String id, String code, String name) {
-        EquipmentBase device = new EquipmentBase();
-        device.setEquipmentId(id);
-        device.setEquipmentCode(code);
-        device.setEquipmentName(name);
-        device.setEquipmentLocation("测试大道1号");
-        device.setLongitude(new BigDecimal("113.28"));
-        device.setLatitude(new BigDecimal("23.12"));
-        return device;
-    }
-
-    private static AlarmData alarm(String id, String deviceCode, String warningType, Integer alarmStatus,
-                                   Integer alarmLevel) {
-        AlarmData alarm = new AlarmData();
-        alarm.setId(id);
-        alarm.setDeviceCode(deviceCode);
-        alarm.setWarningType(warningType);
-        alarm.setWarningCode("WARN-" + warningType);
-        alarm.setAlarmStatus(alarmStatus);
-        alarm.setAlarmLevel(alarmLevel);
-        return alarm;
-    }
-
-    private void stubLedger(AlarmData... alarms) {
-        when(alarmDataService.list(any(AbstractWrapper.class))).thenReturn(Arrays.asList(alarms));
-    }
-
-    @SuppressWarnings("unchecked")
-    private Page<WaterAlarmOutDTO> ledgerPage() {
-        return service.getAlarmLedgerPage(1, 10, null, null, null, null, null, null, null, null);
-    }
-
-    @Test
-    void alarmDetailLoadsItsDeviceWithoutAnUnboundedEquipmentRead() {
-        AlarmData linkedAlarm = alarm("alarm-1", "CN-GS-202609020001", "压力预警", 1, 1);
-        linkedAlarm.setWorkOrderId(501L);
-        when(alarmDataMapper.selectList(any())).thenReturn(Collections.singletonList(linkedAlarm));
-        when(alarmDataService.getById("alarm-1")).thenReturn(linkedAlarm);
-        // 数据访问边界拒绝无条件设备扫描,详情应只读取本条报警涉及的设备。
-        when(equipmentBaseMapper.selectList(isNull()))
-                .thenThrow(new IllegalStateException("禁止无条件扫描设备表"));
-        WorkOrder closed = new WorkOrder();
-        closed.setOrderId(501L);
-        closed.setOrderNo("WO-CLOSED");
-        closed.setOrderStatus(5);
-        when(workOrderMapper.selectBatchIds(any())).thenReturn(Collections.singletonList(closed));
-
-        WaterAlarmOutDTO row = service.getAlarmDetail("alarm-1");
-
-        assertEquals("EQ-P-1", row.getEquipmentId());
-        assertEquals("城南压力监测点", row.getDeviceName());
-        assertEquals("WO-CLOSED", row.getOrderNo());
-        assertEquals(5, row.getOrderStatus());
-    }
-
-    @Test
-    void pendingAlarmWithoutAuditOrOrderShowsPendingAudit() {
-        stubLedger(alarm("alarm-1", "CN-GS-202609020001", "压力预警", 0, 1));
-        when(workOrderMapper.selectBatchIds(any())).thenReturn(Collections.emptyList());
-        when(equipmentStatusMapper.selectList(any())).thenReturn(Collections.singletonList(
-                status("EQ-P-1", 1, 1, 0)));
-
-        List<WaterAlarmOutDTO> rows = ledgerPage().getRecords();
-
-        assertEquals(1, rows.size());
-        WaterAlarmOutDTO row = rows.get(0);
-        assertEquals(1, row.getDisplayStatus());
-        assertEquals("待审核", row.getDisplayStatusText());
-        assertEquals(1, row.getDeviceAlarmStatus());
-        assertEquals("报警中", row.getDeviceAlarmStatusText());
-    }
-
-    @Test
-    void alarmWithLiveWorkOrderShowsTransferred() {
-        stubLedger(alarm("alarm-1", "CN-GS-202609020001", "压力预警", 1, 1));
-        AlarmData linkedAlarm = alarm("alarm-1", "CN-GS-202609020001", "压力过低", 0, 1);
-        linkedAlarm.setWorkOrderId(201L);
-        when(alarmDataMapper.selectList(any())).thenReturn(Collections.singletonList(linkedAlarm));
-        when(alarmDataService.list(any(AbstractWrapper.class))).thenReturn(Collections.singletonList(linkedAlarm));
-        WorkOrder order = new WorkOrder();
-        order.setOrderId(201L);
-        order.setOrderNo("WO202606101703482640");
-        order.setOrderStatus(3);
-        when(workOrderMapper.selectBatchIds(any())).thenReturn(Collections.singletonList(order));
-        when(equipmentStatusMapper.selectList(any())).thenReturn(Collections.singletonList(
-                status("EQ-P-1", 1, 1, 0)));
-
-        WaterAlarmOutDTO row = ledgerPage().getRecords().get(0);
-
-        assertEquals(2, row.getDisplayStatus());
-        assertEquals("已转工单", row.getDisplayStatusText());
-        assertEquals("WO202606101703482640", row.getOrderNo());
-    }
-
-    @Test
-    void falseAlarmAuditWithoutOrderDoesNotImplyRecovery() {
-        AlarmData falseAlarm = alarm("alarm-1", "CN-GS-202609020002", "流量预警", 0, 2);
-        falseAlarm.setAuditStatus(com.zksy.base.alarm.enums.AlarmAuditStatusEnum.FALSE_ALARM.getCode());
-        stubLedger(falseAlarm);
-        when(workOrderMapper.selectBatchIds(any())).thenReturn(Collections.emptyList());
-        when(equipmentStatusMapper.selectList(any())).thenReturn(Collections.singletonList(
-                status("EQ-F-1", 0, 1, 1)));
-
-        WaterAlarmOutDTO row = ledgerPage().getRecords().get(0);
-
-        assertEquals(4, row.getDisplayStatus());
-        assertEquals("误报警", row.getDisplayStatusText());
-        // 报警记录自身仍未处理,但设备已不在报警中:展示状态与设备报警状态各自独立
-        assertEquals(0, row.getDeviceAlarmStatus());
-        assertEquals("正常", row.getDeviceAlarmStatusText());
-    }
-
-    @Test
-    void ledgerQueryIsScopedToWaterDevices() {
-        stubLedger();
-        when(workOrderMapper.selectBatchIds(any())).thenReturn(Collections.emptyList());
-        when(equipmentStatusMapper.selectList(any())).thenReturn(Collections.emptyList());
-
-        ledgerPage();
-
-        ArgumentCaptor<AbstractWrapper<AlarmData, ?, ?>> captor = ArgumentCaptor.forClass(AbstractWrapper.class);
-        verify(alarmDataService).list(captor.capture());
-        String sql = captor.getValue().getSqlSegment();
-        List<Object> values = Arrays.asList(captor.getValue().getParamNameValuePairs().values().toArray());
-        // 只允许供水设备编码进入 IN 条件,不得混入排水/燃气/窨井设备
-        assertEquals(true, sql.contains("device_code"));
-        assertEquals(true, values.contains("CN-GS-202609020001"));
-        assertEquals(true, values.contains("CN-GS-202609020002"));
-        assertEquals(false, values.contains("YJ-EE-20260702"));
-    }
-
-    private static EquipmentStatus status(String equipmentId, Integer alarmStatus, Integer currentStatus,
-                                          Integer onlineStatus) {
-        EquipmentStatus status = new EquipmentStatus();
-        status.setEquipmentId(equipmentId);
-        status.setAlarmStatus(alarmStatus);
-        status.setCurrentStatus(currentStatus);
-        status.setOnlineStatus(onlineStatus);
-        return status;
-    }
-}

+ 0 - 2
pipe-network-service/zksy-system/src/test/java/com/zksy/WaterSupply/MonitorAlarm/WaterSupplyAlarmRealtimeContractTest.java

@@ -78,8 +78,6 @@ class WaterSupplyAlarmRealtimeContractTest {
                 new WaterSupplyQueryService(equipmentBaseService, equipmentBaseMapper,
                 new WaterSupplyQueryService(equipmentBaseService, equipmentBaseMapper,
                         equipmentStatusMapper, equipmentTypeMapper, workOrderMapper,
                         equipmentStatusMapper, equipmentTypeMapper, workOrderMapper,
                         alarmDataMapper));
                         alarmDataMapper));
-        ReflectionTestUtils.setField(service, "equipmentBaseMapper", equipmentBaseMapper);
-        ReflectionTestUtils.setField(service, "workOrderMapper", workOrderMapper);
 
 
         EquipmentBase device = device("EQ-P-1", "CN-GS-202609020001", "城南压力监测点");
         EquipmentBase device = device("EQ-P-1", "CN-GS-202609020001", "城南压力监测点");
         when(equipmentBaseService.findByTopLevelType("供水")).thenReturn(Collections.singletonList(device));
         when(equipmentBaseService.findByTopLevelType("供水")).thenReturn(Collections.singletonList(device));

+ 9 - 0
pipe-network-service/zksy-system/src/test/java/com/zksy/base/alarm/mapper/AlarmOperationsMapperTest.java

@@ -30,6 +30,15 @@ class AlarmOperationsMapperTest {
             assertFalse(sql.contains("a.actual_value IS NOT NULL"));
             assertFalse(sql.contains("a.actual_value IS NOT NULL"));
             assertTrue(sql.contains("w.order_id = a.work_order_id")); rendered.put(name,sql);
             assertTrue(sql.contains("w.order_id = a.work_order_id")); rendered.put(name,sql);
         }
         }
+        query.setKeyword("设备搜索");
+        for (String name : Arrays.asList("selectPage", "selectReminders", "countByLevel", "selectMap")) {
+            String keywordSql = sql(config,name,params);
+            assertTrue(keywordSql.contains("a.device_code ILIKE CONCAT('%',?,'%')"));
+            assertTrue(keywordSql.contains("OR e.equipment_name ILIKE CONCAT('%',?,'%')"));
+            assertFalse(keywordSql.contains("e.equipment_location ILIKE"));
+            assertTrue(keywordSql.contains("e.equipment_type_id IN"));
+        }
+        query.setKeyword(null);
         query.setActualValueOnly(true);
         query.setActualValueOnly(true);
         String numeric = sql(config,"selectPage",params);
         String numeric = sql(config,"selectPage",params);
         assertTrue(numeric.contains("a.actual_value IS NOT NULL"));
         assertTrue(numeric.contains("a.actual_value IS NOT NULL"));

+ 43 - 2
pipe-network-service/zksy-system/src/test/java/com/zksy/base/alarm/service/AlarmOperationsServiceTest.java

@@ -73,9 +73,9 @@ class AlarmOperationsServiceTest {
         assertEquals(Collections.emptyList(),service.logs("a1",query));
         assertEquals(Collections.emptyList(),service.logs("a1",query));
         verify(logs).selectHistory("a1");
         verify(logs).selectHistory("a1");
     }
     }
-    @Test void invalidFiltersAndNonDrainageAnalysisAreRejected() {
+    @Test void invalidFiltersAndNonWaterSubsystemAnalysisAreRejected() {
         query.setAlarmStatus(5); assertThrows(ServiceException.class, () -> service.page(query));
         query.setAlarmStatus(5); assertThrows(ServiceException.class, () -> service.page(query));
-        query.setRootTypeId(1); assertThrows(ServiceException.class, () -> service.map(query));
+        query.setRootTypeId(3); assertThrows(ServiceException.class, () -> service.map(query));
         assertThrows(ServiceException.class, () -> service.analysis("a1",query,AlarmSeriesPeriod.CURVE));
         assertThrows(ServiceException.class, () -> service.analysis("a1",query,AlarmSeriesPeriod.CURVE));
         verifyNoInteractions(mapper);
         verifyNoInteractions(mapper);
     }
     }
@@ -104,10 +104,51 @@ class AlarmOperationsServiceTest {
         assertEquals(true,result.get("supported"));
         assertEquals(true,result.get("supported"));
         verify(mapper).selectSamples(AlarmMetricSource.INSTANT_FLOW,"D1",LocalDateTime.of(2026,1,10,0,0),LocalDateTime.of(2026,1,11,0,0),"DAY");
         verify(mapper).selectSamples(AlarmMetricSource.INSTANT_FLOW,"D1",LocalDateTime.of(2026,1,10,0,0),LocalDateTime.of(2026,1,11,0,0),"DAY");
     }
     }
+    @Test void mapForcesUnhandledStatusWithOrWithoutRequestedStatus() {
+        when(mapper.selectMap(eq(query),anyList())).thenAnswer(call -> {
+            AlarmQuery filter = call.getArgument(0);
+            assertEquals(AlarmRecordStatusEnum.UNHANDLED.getCode(), filter.getAlarmStatus());
+            assertEquals(Arrays.asList(2,21), call.getArgument(1));
+            return Collections.emptyList();
+        });
+        for (Integer status : Arrays.asList(null, 0, 1)) {
+            query.setAlarmStatus(status);
+            assertTrue(service.map(query).isEmpty());
+        }
+        verify(mapper,times(3)).selectMap(eq(query),anyList());
+    }
     @Test void mapNeverSilentlyTruncatesLargeResult() {
     @Test void mapNeverSilentlyTruncatesLargeResult() {
         when(mapper.selectMap(eq(query),anyList())).thenReturn(Collections.nCopies(10001,alarm));
         when(mapper.selectMap(eq(query),anyList())).thenReturn(Collections.nCopies(10001,alarm));
         assertThrows(ServiceException.class, () -> service.map(query));
         assertThrows(ServiceException.class, () -> service.map(query));
     }
     }
+    @Test void waterSupplyMapUsesAuthorizedTypeTreeAndForcesCurrentAlarms() {
+        query.setRootTypeId(1); query.setAlarmStatus(AlarmRecordStatusEnum.HANDLED.getCode());
+        List<Integer> ids = Arrays.asList(1,11,111);
+        when(scope.resolveOperations(query)).thenReturn(ids);
+        when(mapper.selectMap(query,ids)).thenReturn(Collections.singletonList(alarm));
+        assertEquals(Collections.singletonList(alarm),service.map(query));
+        assertEquals(AlarmRecordStatusEnum.UNHANDLED.getCode(),query.getAlarmStatus());
+        verify(mapper).selectMap(query,ids);
+    }
+    @Test void waterSupplyPressureCurveUsesRealSamplesWithinAuthorizedScope() {
+        query.setRootTypeId(1); alarm.setWarningCode("WARN-PRESSURE");
+        List<Integer> ids = Arrays.asList(1,11);
+        when(scope.resolveOperations(query)).thenReturn(ids);
+        when(mapper.selectSamples(eq(AlarmMetricSource.PRESSURE),eq("D1"),any(),any(),eq("CURVE")))
+                .thenReturn(Collections.emptyList());
+        Map<String,Object> result = service.analysis("a1",query,AlarmSeriesPeriod.CURVE);
+        assertEquals(true,result.get("supported"));
+        verify(mapper).selectDetail("a1",query,ids);
+        verify(mapper).selectSamples(AlarmMetricSource.PRESSURE,"D1",
+                LocalDateTime.of(2026,1,10,11,0),LocalDateTime.of(2026,1,10,13,0),"CURVE");
+    }
+    @Test void unauthorizedWaterSupplyScopeCannotReadMapOrCurveSamples() {
+        query.setRootTypeId(1);
+        when(scope.resolveOperations(query)).thenThrow(new org.springframework.security.access.AccessDeniedException("denied"));
+        assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> service.map(query));
+        assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> service.analysis("a1",query,AlarmSeriesPeriod.CURVE));
+        verifyNoInteractions(mapper);
+    }
     @Test void dashboardKeepsTotalsAndGroupsFourLevelsOnConsecutiveBeijingDates() {
     @Test void dashboardKeepsTotalsAndGroupsFourLevelsOnConsecutiveBeijingDates() {
         query.setRootTypeId(1);
         query.setRootTypeId(1);
         List<Integer> ids = Arrays.asList(1, 11);
         List<Integer> ids = Arrays.asList(1, 11);

+ 14 - 2
pipe-network-service/zksy-system/src/test/java/com/zksy/base/alarm/service/AlarmReviewScopeServiceTest.java

@@ -23,7 +23,7 @@ class AlarmReviewScopeServiceTest {
     @BeforeEach void login() {
     @BeforeEach void login() {
         SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
         SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
                 new LoginUser(9L, 10L, new SysUser(), Collections.emptySet()), null));
                 new LoginUser(9L, 10L, new SysUser(), Collections.emptySet()), null));
-        SysMenu menu = new SysMenu(); menu.setComponent("subSystem/alarm/index"); menu.setStatus("0");
+        SysMenu menu = new SysMenu(); menu.setComponent("subSystem/alarm/audit"); menu.setStatus("0");
         menu.setQuery("{\"rootTypeId\":3,\"warning_code\":\"WARN-GAS-CONCENTRATION\"}");
         menu.setQuery("{\"rootTypeId\":3,\"warning_code\":\"WARN-GAS-CONCENTRATION\"}");
         when(menus.selectMenuList(9L)).thenReturn(Collections.singletonList(menu));
         when(menus.selectMenuList(9L)).thenReturn(Collections.singletonList(menu));
         query.setRootTypeId(3); query.setWarningCode("WARN-GAS-CONCENTRATION");
         query.setRootTypeId(3); query.setWarningCode("WARN-GAS-CONCENTRATION");
@@ -48,7 +48,7 @@ class AlarmReviewScopeServiceTest {
         assertThrows(AccessDeniedException.class, () -> service.resolveOperations(query));
         assertThrows(AccessDeniedException.class, () -> service.resolveOperations(query));
     }
     }
     @Test void gisComponentUsesUpdatedNameAndDisabledMenuIsDenied() {
     @Test void gisComponentUsesUpdatedNameAndDisabledMenuIsDenied() {
-        SysMenu menu = new SysMenu(); menu.setStatus("0"); menu.setComponent("subSystem/alarm/drainage/alarmGIS");
+        SysMenu menu = new SysMenu(); menu.setStatus("0"); menu.setComponent("subSystem/drainage/alarm/alarmGIS");
         menu.setQuery("{\"rootTypeId\":2}"); when(menus.selectMenuList(9L)).thenReturn(Collections.singletonList(menu));
         menu.setQuery("{\"rootTypeId\":2}"); when(menus.selectMenuList(9L)).thenReturn(Collections.singletonList(menu));
         when(types.selectList(null)).thenReturn(Arrays.asList(type(2,0),type(21,2),type(22,21),type(12,1)));
         when(types.selectList(null)).thenReturn(Arrays.asList(type(2,0),type(21,2),type(22,21),type(12,1)));
         query.setRootTypeId(2); query.setWarningCode(null);
         query.setRootTypeId(2); query.setWarningCode(null);
@@ -58,4 +58,16 @@ class AlarmReviewScopeServiceTest {
     @Test void reviewMenuDoesNotGrantOperationsAccess() {
     @Test void reviewMenuDoesNotGrantOperationsAccess() {
         assertThrows(AccessDeniedException.class, () -> service.resolveOperations(query));
         assertThrows(AccessDeniedException.class, () -> service.resolveOperations(query));
     }
     }
+    @Test void waterSupplyGisMenuOnlyGrantsWaterOperationsAndNotAudit() {
+        SysMenu menu = new SysMenu(); menu.setStatus("0"); menu.setComponent("subSystem/waterSupply/alarm/alarmGIS");
+        menu.setQuery("{\"rootTypeId\":1}"); when(menus.selectMenuList(9L)).thenReturn(Collections.singletonList(menu));
+        when(types.selectList(null)).thenReturn(Arrays.asList(type(1,0),type(11,1),type(111,11),type(2,0),type(21,2)));
+        query.setRootTypeId(1); query.setWarningCode(null);
+        assertEquals(Arrays.asList(1,11,111),service.resolveOperations(query));
+        assertThrows(AccessDeniedException.class, () -> service.resolve(query));
+        query.setRootTypeId(2);
+        assertThrows(AccessDeniedException.class, () -> service.resolveOperations(query));
+        query.setRootTypeId(1); menu.setStatus("1");
+        assertThrows(AccessDeniedException.class, () -> service.resolveOperations(query));
+    }
 }
 }