Эх сурвалжийг харах

chore(logs): 记录管网服务启动失败日志

- 在 logs/pipe-network-service/spring.log 中补充开发环境启动过程日志
- 记录 InspectTaskMapper 未注册导致依赖注入失败的异常
- 保留 Spring 上下文取消、数据源关闭及 Tomcat 停止信息,便于排查启动问题
Kazerin 1 сар өмнө
parent
commit
4cd8e2d603

+ 99 - 0
.scratch/cockpit-backend-read-contracts/spec.md

@@ -0,0 +1,99 @@
+# 城市生命线驾驶舱系统:后端只读接口与实时状态契约
+
+Status: ready-for-agent
+
+## Problem Statement
+
+城市生命线驾驶舱系统面向指挥中心领导和值班人员,是一个只读展示大屏。UE 需要在进入程序后加载“一张图”,以监测点为首要地图对象,查看关联智能设备的基础档案、当前监测数据、预警、报警和工单处置状态。现有后端主要提供管理型接口,字段、分页和写入能力与驾驶舱展示边界不一致;预警接口仍处于向统一工单模型迁移的阶段,设备状态 WebSocket 也使用旧的驼峰设备字段。
+
+如果 UE 直接拼接管理接口,客户端将不得不推断监测点综合状态、复制事件关系和承担迁移兼容,导致状态口径不一致、地图分页重复、局部详情失败被空数据掩盖以及只读大屏误用业务写接口。因此后端需要提供稳定、版本化、只读的驾驶舱专用契约,并在内部复用现有表和服务。
+
+## Solution
+
+新增驾驶舱只读聚合服务和 DTO,统一挂载于 `/api/cockpit/v1/`。服务端负责从现有监测点、智能设备、设备类型、预警、报警、`alarm_warning_rel`、工单和指标数据中组合稳定响应;UE 不直接访问管理接口或数据库。
+
+地图摘要使用一个共享 `snapshotAt` 的分页快照,分别返回监测点摘要和未关联智能设备摘要。后端返回已解析的 `pointStatus`、状态原因、统计和坐标校验结果。监测点优先渲染;已关联设备不重复生成独立点位;未关联且坐标有效的设备才作为直接设备点位;无效坐标对象保留在列表和详情中但禁止地图渲染。
+
+设备详情采用基础档案、当前指标、预警、报警、工单、工单时间线和图片等独立只读分区。REST 首次提供权威快照,WebSocket 仅推送状态增量。现有 `/ws/deviceStatus` 增加 `cockpitSubscribe` 范围订阅,规范字段使用 `deviceCode`,迁移期可额外返回旧 `device_code`;UE 以 `serverUpdatedAt` 判断新旧消息,断线后自动重连和恢复订阅。
+
+## User Stories
+
+1. As a 指挥中心领导, I want the cockpit to load the map frame before data completes, so that the display never appears frozen during startup.
+2. As a 值班人员, I want paginated monitoring-point summaries, so that a large city dataset can load progressively.
+3. As a 地图使用者, I want one consistent snapshot across all map pages, so that points are not duplicated or omitted while data changes.
+4. As a 值班人员, I want unassociated intelligent equipment with valid coordinates to appear directly on the map, so that equipment is not lost when an association is absent.
+5. As a 数据负责人, I want invalid or unverified coordinates returned with a reason, so that the cockpit never guesses a spatial transform.
+6. As a 地图使用者, I want the server to return the resolved monitoring-point status, so that UE does not recompute status from a partial page.
+7. As a 值班人员, I want to click a monitoring point and see its summary and backend-ordered equipment cards, so that I can choose the relevant equipment.
+8. As a 值班人员, I want to open equipment details by stable equipment code, so that detail navigation does not depend on database primary IDs.
+9. As a 值班人员, I want equipment archive fields and the latest image, so that I can identify the physical equipment.
+10. As a 值班人员, I want type-specific current metrics, including sewage environmental metrics when supplied, so that the display reflects the equipment schema.
+11. As a 值班人员, I want warning and alarm events in separate sections, so that their distinct event families remain clear.
+12. As a 值班人员, I want current and historical event results with explicit empty and unavailable states, so that missing data is not mistaken for no events.
+13. As a 值班人员, I want every related work order and its complete nine-state timeline, so that handling progress is visible without merging records.
+14. As a 系统集成负责人, I want event and work-order relationships resolved by the backend, so that the cockpit does not infer relationships from names, coordinates or timestamps.
+15. As a 驾驶舱维护者, I want independent detail sections, so that one failed query does not erase successful sibling sections.
+16. As a 值班人员, I want a short-lived cached detail with an explicit stale marker, so that I retain context without being misled about freshness.
+17. As a 值班人员, I want scheduled summary refresh between three and ten minutes and a manual refresh, so that display freshness can match deployment needs.
+18. As a 系统运维人员, I want device-state calibration to start at sixty seconds and be extendable, so that device population does not overload the service.
+19. As a 值班人员, I want incremental WebSocket updates to identify the affected device, so that the map and open detail update promptly.
+20. As a 网络异常场景下的值班人员, I want automatic reconnect and subscription recovery, so that temporary disconnections do not permanently stop updates.
+21. As a 安全负责人, I want fixed display identity supplied outside UE assets, so that credentials are not exposed in packaged content.
+22. As a 安全负责人, I want all cockpit endpoints to be read-only, so that display interactions cannot mutate business records.
+23. As a 后端协作者, I want dedicated JSON fields to use camelCase, so that the contract is consistent with the existing Java stack while keeping database names internal.
+24. As a 后端协作者, I want `deviceCode` to be canonical and `device_code` to be a migration alias, so that old WebSocket clients remain compatible.
+25. As a 统计使用者, I want facility counts and intelligent-equipment counts separated, so that water plants and pump stations are not mixed with sensor equipment.
+26. As a 业务负责人, I want the formal scope limited to gas, supply water and drainage, so that unknown types are not silently counted in a formal industry total.
+27. As a 业务负责人, I want sensor-equipped manholes represented as intelligent equipment, so that manholes are not modeled as a separate industry or facility entity.
+28. As a 统计使用者, I want overview, infrastructure, monitoring and warning-disposal metrics to use one timezone and freshness vocabulary, so that dashboards are comparable.
+29. As a 后端协作者, I want contract handoff material before warning migration replacement, so that the adapter changes from evidence rather than guessed fields.
+30. As a 测试人员, I want the aggregate behavior observable through one public read model seam, so that contract tests verify user-visible behavior without coupling to database joins.
+
+## Implementation Decisions
+
+- Add a versioned read-only cockpit API surface under `/api/cockpit/v1/`; keep management endpoints as internal composition sources only.
+- Preserve the existing response envelope convention, while placing dedicated camelCase DTOs in the data payload.
+- Provide map-summary, monitoring-point-detail and equipment-detail contracts, plus read-only metric, warning, alarm, work-order, timeline and image sections.
+- Map summaries use `page`, `pageSize` and `snapshotAt`; default page size is 500 and maximum is 1000. The response includes total, batch update time and completion metadata.
+- Return monitoring points and unassociated equipment as separate collections. A valid published map coordinate is WGS84; missing, zero, out-of-range or unvalidated coordinates fail the render gate and include a reason.
+- Return `pointStatus` and a status reason from the backend. Fixed codes are `ALARM`, `WARNING`, `WORK_ORDER`, `NORMAL`, `OFFLINE` and `EMPTY`, each with a display label. The backend owns precedence.
+- Keep facility and intelligent-equipment statistics separate. Facilities include water plants, pump stations and pipelines; sensor-equipped manholes belong to intelligent equipment and are not an industry.
+- Use the database `equipment_base.equipment_code` as the preferred shared association key. Dedicated cockpit JSON exposes it as `deviceCode`; legacy data may retain `device_code` or other aliases inside the adapter boundary.
+- Keep warning and alarm event families distinct. Reuse backend authority for `alarm_warning_rel`, event cardinality and work-order uniqueness; the cockpit never creates or deduplicates work orders.
+- Preserve all nine work-order states and each work order's independent timeline. Do not merge timelines across events.
+- Use `Asia/Shanghai` for business date and statistic boundaries. Emit ISO-8601 timestamps with timezone information and return selected range, timezone and update time.
+- Extend `/ws/deviceStatus` with `cockpitSubscribe` and `scope=all`, while retaining existing device-code subscription compatibility. REST provides initial snapshots; WebSocket sends changed-device deltas only.
+- WebSocket payloads use canonical `deviceCode`, may include legacy `device_code`, and include current status, `statusTime` and `serverUpdatedAt`. No sequence field is added; newest `serverUpdatedAt` wins.
+- On reconnect, the client may resubscribe automatically. The service must not treat subscription, refresh or detail queries as business writes.
+- Enforce service identity at the deployment or gateway layer. Do not embed credentials in UE, DTOs or packaged assets. Use standard HTTP errors to distinguish empty data, invalid input, missing resources, authentication failure and server failure.
+- Implement the read model by composing existing tables and services first. Add indexes or a projection only after query and representative-scale measurements demonstrate the need.
+- Do not publish unverified CAD coordinates. Conversion from CGCS2000, Xi'an 80, Beijing 54 and elevation references happens before publication and requires later control-point validation.
+
+## Testing Decisions
+
+- Use one highest-level public seam: a fake read-only cockpit adapter feeds the aggregate presentation contract with deterministic map pages, details and WebSocket updates; assert only public DTO behavior, freshness, selection identity and update results.
+- Add controller/service contract tests for response envelopes, camelCase fields, status dictionaries, HTTP error categories, pagination snapshot consistency and facility/equipment count separation.
+- Add integration tests against representative database fixtures for monitoring-point association, unassociated equipment, invalid coordinates, event-family separation, `alarm_warning_rel` relationships and nine work-order states.
+- Add WebSocket tests for canonical and legacy device-code fields, delta-only pushes, `serverUpdatedAt` ordering, missing point status fallback, scope subscription, reconnect subscription and heartbeat behavior.
+- Add read-only safety tests using an adapter/request spy; all map loading, detail loading, refresh, retry and subscription operations must produce no business mutation request.
+- Add freshness tests for successful empty results, one missed cycle, two missed cycles, stale retention and independent section failure.
+- Add statistics tests for timezone boundaries, selected ranges, formal industry filtering, unknown-type exclusion and separate facility/equipment totals.
+- Reuse existing Spring controller/service test conventions and add dedicated contract fixtures rather than testing private SQL helpers or requiring a particular join implementation.
+
+## Out of Scope
+
+- No UE implementation, UMG changes, Actor implementation, Three.js migration or Pixel Streaming work.
+- No login page, role management, threshold configuration, publishing, upgrading, resolving, dispatching, accepting, processing, verifying, approving or other business write operation in the cockpit API.
+- No replacement of the existing management APIs or removal of their compatibility behavior.
+- No local UE aggregation of monitoring-point state, event relationships, coordinate transforms or work-order uniqueness.
+- No Flowable introduction or redesign of the existing work-order state machine.
+- No judgment of warning truthfulness or false positives.
+- No final GIS accuracy threshold, production-scale performance threshold or Galaxy Kylin certification; these remain later validation gates.
+
+## Further Notes
+
+- The backend working tree contains pre-existing uncommitted changes. This spec does not authorize reverting or mixing those changes into the cockpit implementation.
+- The warning lifecycle is expected to migrate to the same work-order model as alarms. The dedicated adapter must remain stable while the backend absorbs that migration.
+- Before replacing transitional warning mappings, backend handoff must include endpoint paths, request parameters, response examples, field and status dictionaries, relationship/cardinality rules, pagination and freshness semantics, and WebSocket samples.
+- The current repository has `/MonitorPoint`, `/EquipmentBase`, `/api/alarm` and `/ws/deviceStatus` management or transitional surfaces, but no completed `/api/cockpit/v1` contract. This spec defines the target seam without claiming those endpoints already exist.
+- Apply the `ready-for-agent` triage label when publishing this spec to Gogs Issues.

+ 35 - 0
.scratch/out-of-scope-tests/zksy-system/src/main/java/com/zksy/WaterSupply/MonitorAlarm/dto/out/AlarmDispatchOutDTO.java

@@ -0,0 +1,35 @@
+package com.zksy.WaterSupply.MonitorAlarm.dto.out;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 报警转工单结果。
+ */
+@Data
+@ApiModel(value = "报警转工单结果", description = "报警生成或复用的待派单故障维修工单摘要")
+public class AlarmDispatchOutDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "工单ID")
+    private Long orderId;
+
+    @ApiModelProperty(value = "工单编号")
+    private String orderNo;
+
+    @ApiModelProperty(value = "工单类型,固定为1-故障维修")
+    private Integer orderType;
+
+    @ApiModelProperty(value = "工单状态;新建工单为1-待派单")
+    private Integer orderStatus;
+
+    @ApiModelProperty(value = "关联报警ID")
+    private String alarmId;
+
+    @ApiModelProperty(value = "关联设备编码")
+    private String deviceCode;
+}

+ 92 - 0
T6-water-device-handoff.md

@@ -0,0 +1,92 @@
+# T6 供水设备准入、台账与异常态势 · Handoff
+
+## 目标
+
+继续完成 Gogs Issue #10 / T6“供水设备准入、台账与异常态势”。当前对话已经完成后端与前端的主要实现,剩余工作集中在测试补齐、全量验证、两轴代码审查和提交。
+
+## 仓库与当前状态
+
+- 后端仓库:`E:\pipe-ner-server`
+  - 分支:`master`
+  - 状态:`ahead 1`
+  - T6 相关改动已暂存,另有多处无关脏文件;不要清理或覆盖。
+- 前端仓库:`E:\city-life-line`
+  - 分支:`master`
+  - 状态:`ahead 10`
+  - T6 相关改动仍在工作区,尚未暂存;同样存在无关脏文件,不要清理或覆盖。
+
+## 已完成的核心工作
+
+### 后端
+
+- `WaterSupplyDeviceServiceImpl`
+  - 通过 `equipmentBaseService.findByTopLevelType("供水")` 统一执行供水设备准入。
+  - 设备状态、在线状态、报警状态分别建模,不再互相替代。
+  - 异常原因来自有效报警或维修状态。
+  - 关联工单使用真实 `order_no`,并映射为 `relatedWorkOrderNo`、`relatedWorkOrderStatus`、`relatedWorkOrderPerson`。
+  - 异常页只关联“未结案的故障维修工单”。
+- `WaterSupplyDeviceService` / `WaterSupplyDeviceServiceImplTest`
+  - 补齐供水过滤、空数据、状态分离、异常原因、关联工单、报警等级筛选等测试。
+- 控制器与导出
+  - `DeviceLedgerController`、`DeviceAbnormalController` 已支持分页、筛选、Excel 导出。
+  - 新增 `WaterDeviceLedgerExportDTO`、`WaterDeviceAbnormalExportDTO`。
+  - 对应控制器测试已添加。
+- 测试数据
+  - 新增 `pipe-network-service/sql/water_supply_device_test_data.sql`,覆盖混合供水/非供水设备、正常/报警/维修/离线状态、异常原因和真实工单编号。
+
+### 前端
+
+- `src/views/subSystem/waterSupply/components/WaterDevicePage.vue`
+  - 台账页聚焦资产信息。
+  - 异常页聚焦异常态势、异常原因、处置状态、关联工单。
+  - 展示设备状态、在线状态、报警状态、报警等级、异常原因、关联工单号、工单状态、接单责任人。
+  - 详情使用右侧抽屉。
+  - 导出改为 Blob 下载并处理 JSON 错误。
+- `src/utils/waterSupplyUiModel.js`
+  - 统一设备行归一化,明确区分 `currentStatus`、`onlineStatus`、`alarmStatus`、`alertLevel`、`abnormalReason`、`relatedWorkOrderNo`、`relatedWorkOrderStatus`、`relatedWorkOrderPerson`。
+- `src/api/pipeNetwork/waterSupply.js`
+  - 设备台账和异常设备导出接口改为 `responseType: 'blob'`。
+- 测试
+  - 更新 `tests/waterSupplyUiModel.test.mjs`
+  - 新增 `tests/waterDeviceExport.test.mjs`
+
+## 剩余工作
+
+1. 补一个 `relatedWorkOrderPerson` 的测试,mock `ISysUserService`,确认多个接单人可映射为昵称并用“、”连接。
+2. 后端运行:
+   - `mvn -q -pl zksy-system "-Dtest=WaterSupplyDeviceServiceImplTest" test`
+   - `mvn -q test`
+3. 前端运行:
+   - `node --test tests`
+   - `npm run build:prod`
+4. 按两轴完成代码审查:
+   - Standards:检查是否符合 CONTEXT / ADR / 设计规范。
+   - Spec:检查 T6 验收标准是否完整覆盖。
+5. 只暂存 T6 相关文件,分别提交后端与前端仓库;不要推送、不要合并。
+
+## 参考文档
+
+- 交付规格:`E:\pipe-ner-server\docs\superpowers\specs\2026-08-27-water-supply-subsystem-e2e-delivery.md`
+- 后端上下文:`E:\pipe-ner-server\CONTEXT-MAP.md`
+- 后端领域文档:`E:\pipe-ner-server\pipe-network-service\CONTEXT.md`
+- 前端上下文:`E:\city-life-line\CONTEXT.md`
+- 前端设计规范:`E:\city-life-line\docs\design\2026-08-22-water-supply-work-order-ui.md`
+- ADR:
+  - `E:\pipe-ner-server\pipe-network-service\docs\adr\0003-water-supply-subsystem-product-boundary.md`
+  - `E:\pipe-ner-server\pipe-network-service\docs\adr\0004-cross-system-unified-work-order-lifecycle.md`
+  - `E:\pipe-ner-server\pipe-network-service\docs\adr\0005-independent-frontend-backend-contract-gate.md`
+  - `E:\pipe-ner-server\pipe-network-service\docs\adr\0006-work-order-attachment-storage.md`
+  - `E:\pipe-ner-server\pipe-network-service\docs\adr\0007-shared-pump-station-by-type.md`
+
+## 环境注意事项
+
+- 当前机器的 Windows 沙箱助手曾出现 `helper_unknown_error: setup refresh had errors`。
+- 使用 `require_escalated` 可以绕过该问题并正常执行命令。
+- 若再次遇到沙箱初始化失败,优先对命令使用提权执行,而不是清理工作区。
+
+## Suggested skills
+
+- `implement`
+- `tdd`
+- `code-review`
+- `verification-before-completion`

+ 99 - 0
docs/superpowers/specs/2026-08-26-water-supply-alarm-work-order-acceptance-fixes.md

@@ -0,0 +1,99 @@
+# 供水报警与工单验收问题修复规格
+
+> **2026-09-04 口径注记**:本规格写于旧状态编码期(现网 1-9)。工单状态以七态共识为权威:验收驳回是“待验收回退到处理中”的动作而非终态、新增任意中间态可进入的“已取消”终态、删除延期,见 [ADR-0008](../../../pipe-network-service/docs/adr/0008-work-order-lifecycle-seven-states.md) 与 [work-order-lifecycle.md](../../../pipe-network-service/docs/domain/work-order-lifecycle.md)。本文“申请延期”表述作废。
+
+## Problem Statement
+
+供水系统路由和菜单结构已经通过人工验收,但业务功能验收未通过。设备维修派单、设备异常管理、报警管理、报警审核和报警工单处置在数据准入、报警转工单、工单状态流转、详情可读性和页面职责方面仍不满足已确认的供水领域规则。部分页面无法产生可测试的待派单数据,报警编码或设备报警状态展示不准确,同一设备同一报警类型可重复生成未结束工单,审核驳回状态和处置图片输入也不符合实际闭环。
+
+本规格遵循 `pipe-network-service/docs/adr/0001-unify-device-work-under-work-orders.md` 与 `0002-runtime-owns-device-alarm-state.md`:维修请求和报警转入统一使用故障维修工单(类型 `1`);设备报警状态由运行系统及有效报警原因维护,工单结案只触发清警复核,不能无条件直接清警。
+
+## Solution
+
+修正供水后端 REST 契约和前端页面,使异常设备、报警记录和供水工单都能从真实数据进入可重复验证的闭环:
+
+- 仅展示供水类型监测设备,并明确异常设备的判定来源和筛选规则。
+- 报警记录先确保生成唯一的待派单故障维修工单,再由报警审核或其他允许入口完成责任人派发。
+- 以设备和报警编码限制未结束工单重复创建;报警编码缺失时回退到报警分类。
+- 报警审核提供工单状态数量、责任人派发、接单、开始处理、处理说明及前后置图片、提交验收、验收通过和驳回回处理中。
+- 报警管理只负责态势展示、详情和报警转工单,不承载后续处置。
+- 设备异常管理重做为异常态势与统计入口;设备维修派单聚焦没有适用未结束故障维修工单的异常设备。
+- 设备详情使用右侧抽屉,状态字段采用统一背景框;派单窗口使用左侧部门树和责任人、右侧工单类型/优先级/描述布局。
+- “报警工单处置”页面保留菜单名称兼容性,但页面标题和职责改为“供水工单总览”,承载全部供水工单检索与后续处置。
+
+## User Stories
+
+1. 作为供水运维人员,我想只看到供水类型的监测设备,以免误派其他系统设备。
+2. 作为供水运维人员,我想知道设备为什么进入设备异常清单,以便判断是否可以派发故障维修工单。
+3. 作为派单人员,我想在设备维修派单页看到当前设备状态、设备报警状态、在线状态、报警等级、关联工单编号和维修状态,以便选择正确的设备。
+4. 作为派单人员,我想对没有适用未结束故障维修工单的异常设备发起派单,以便异常设备具备可测试的待派单入口。
+5. 作为派单人员,我想看到真实的工单编号 `order_no`,而不是数据库随机 ID。
+6. 作为派单人员,我想在设备详情右侧抽屉查看列表和详情,以便不丢失上下文。
+7. 作为派单人员,我想用统一样式辨识设备当前状态、报警状态、在线状态、报警等级和维修状态。
+8. 作为派单人员,我想在派单窗口左侧选择部门和单一责任人,右侧填写工单类型、优先级和问题描述,以便快速完成派单。
+9. 作为系统,我想保证设备维修派单生成的工单类型固定为故障维修(`1`)。
+10. 作为异常管理人员,我想在设备异常管理看到异常设备数量、按报警等级/状态的统计和当前报警原因,以便先判断整体态势再进入单设备操作。
+11. 作为报警管理人员,我想看到报警编码、报警类型、报警等级、设备报警状态、报警记录状态和关联工单状态的独立字段。
+12. 作为报警管理人员,我想按红、橙、蓝、绿显示一级、二级、三级、四级报警,并同时显示文字。
+13. 作为报警管理人员,我想从报警记录触发“确保待派单工单存在”,而不是直接绕过待派单状态派发责任人。
+14. 作为系统,我想阻止同一设备同一报警编码同时创建多个未结束故障维修工单。
+15. 作为系统,当报警编码缺失时,我想按报警分类阻止同设备重复生成未结束工单。
+16. 作为系统,我想允许同一设备的不同报警编码分别生成故障维修工单。
+17. 作为报警审核人员,我想在进入页面时看到全部工单、待处理、处理中、待验收、已完成等状态数量。
+18. 作为报警审核人员,我想只在报警审核页处理供水设备故障维修工单。
+19. 作为责任人,我想先接单,再开始处理,并在处理前提交现场图片或说明。
+20. 作为责任人,我想在提交验收时填写简要处理说明并上传处置后图片,而不是填写验收意见。
+21. 作为派单或管理人员,我想在提交验收后看到处理前图片、处置后图片和完整工单信息,以便进行验收审核。
+22. 作为审核人员,我想验收通过后结案,并保留工单日志和图片证据。
+23. 作为审核人员,我想驳回验收后让工单返回处理中,以便责任人重新处置,而不是取消流程。
+24. 作为运行系统,我想在工单结案时触发清警复核,只有不存在其他有效报警原因时才将设备报警状态恢复正常。
+25. 作为运行系统,我想同一设备存在多个有效报警原因时保留设备报警状态,不因单个工单结案而直接清警。
+26. 作为工单管理人员,我想在供水工单总览查看全部供水类型工单,并按工单类型、来源、责任人和状态筛选。
+27. 作为供水责任人,即使巡检或保养工单由其他系统创建,只要工单派给我,我仍想在供水工单总览继续接单、处理和提交验收。
+28. 作为用户,我想刷新五个页面后仍保持正确路由、菜单和数据职责,不出现页面错位或重复菜单。
+29. 作为测试人员,我想通过 REST/真实数据库接缝验证建单去重、状态流转、图片保存、驳回回处理中和设备报警状态复核。
+30. 作为测试人员,我想通过浏览器页面接缝验证五个页面的数据范围、按钮集合、抽屉和派单窗口布局。
+
+## Implementation Decisions
+
+- 后端以设备类型树作为设备和工单查询的系统归属边界;供水页面只返回供水设备,工单仍使用跨系统统一业务流程并按关联设备类型过滤,不为供水单独复制一套生命周期。
+- `/api/work-order` 作为跨系统规范工单 API;供水、排水、燃气、窨井旧接口通过兼容适配接入,不复制平行工单生命周期。
+- 统一工单使用现有数据库状态机和事务服务;新日志采用统一操作语义,历史日志通过兼容映射展示。
+- 工单处理前和处置后图片统一使用现有 MinIO 保存对象、KingbaseES 保存附件元数据;现有 `work_order` 图片 URL 字段保留兼容,新上传附件按阶段记录并由后端提供受控访问地址。
+- 报警转工单接口负责幂等地创建待派单故障维修工单,并返回工单 ID、工单编号、工单类型、状态和关联报警信息。报警管理入口不得直接把责任人写入报警创建接口。
+- 未结束状态包括待派单、待接单、已接单、处理中、待验收以及其他未终止状态;同一设备与报警编码只能有一个。报警编码为空时使用报警分类作为去重键。
+- 维修派单入口只能对没有适用未结束故障维修工单的供水异常设备启用;已有工单时显示真实 `order_no`、责任人和当前状态,并提供查看入口。
+- 报警管理和报警审核分别使用设备报警状态与报警记录状态字段,不能互相替代;设备报警状态来源于设备状态表,报警记录状态仅作为辅助历史信息。
+- 报警审核页面聚焦供水故障维修工单,提供状态统计和完整工单操作;报警管理只提供详情、确保建单和跳转后续处置入口。
+- 工单状态机(本文沿用迁移前现网编码 1-6;目标七态编码 1-7 与独立“已取消”终态见 [ADR-0008](../../../pipe-network-service/docs/adr/0008-work-order-lifecycle-seven-states.md))保持:`1 待派单 -> 2 待接单 -> 3 已接单 -> 4 处理中 -> 5 待验收 -> 6 已结案`;验收驳回从 `5` 返回 `4`,不进入终止状态;任意中间态可由管理员取消进入终态“已取消”(迁移前取消暂落旧值,迁移后为目标编码 7),不存在“已驳回”终态与延期状态。
+- 处理操作支持处理说明和处理前图片;提交验收支持处置说明和处置后图片;图片和说明随工单详情及操作日志返回。
+- 工单结案只执行清警复核;复核检查该设备所有有效报警原因,存在任一原因时保持 `equipment_status.alarm_status=1`,全部恢复后才写入 `0`。
+- 设备异常管理采用异常指标带、筛选、设备异常表和右侧详情抽屉;设备维修派单采用待派单队列和突出派单动作;报警管理采用报警态势表和右侧详情抽屉;报警审核采用左列表右工作台;供水工单总览采用全部供水工单主从工作台。
+- 派单弹窗固定为左侧部门树/责任人、右侧工单类型/优先级/问题描述;只提交选中用户 ID,不允许前端手写权威姓名或电话。
+- 菜单名称“报警工单处置”保持兼容,但页面可见标题和说明使用“供水工单总览”。
+
+## Testing Decisions
+
+- 最高层公共 seam 采用真实登录态浏览器端的供水报警工单端到端业务流程:从五个供水页面进入真实后端 API 和真实数据库,以用户可观察结果验证整个闭环,不把私有 Java 方法或内部 Vue 实现作为独立测试目标。
+- 端到端流程必须覆盖供水设备过滤、报警数据加载、报警记录确保建单幂等、同设备同报警编码去重、不同报警编码可建单、故障维修类型、责任人派单、接单、开始处理、处理说明、处理前图片、提交验收、处置后图片、验收通过、驳回回处理中、再次处理和结案。
+- 端到端流程必须通过页面刷新和关联详情确认三类状态分别正确:设备报警状态来自设备状态表,报警记录状态来自报警记录,工单状态来自工单;同时验证工单日志和图片证据可回看。
+- 端到端流程必须验证同一设备存在多个有效报警原因时,单个工单结案只触发清警复核而不错误清警;所有有效原因恢复后才显示设备正常。
+- 端到端页面验收覆盖 1440px、1024px 和 390px:设备维修派单、设备异常管理、报警管理、报警审核、供水工单总览五页的加载、刷新、职责区分、状态统计、右侧抽屉、派单弹窗和工单操作。
+- 自动化回归作为该 seam 的支撑证据,包括前端已有 Node 测试、`npm run build:prod`、后端 Maven 测试、真实数据库迁移校验、`git diff --check`,以及浏览器控制台无应用级错误;测试断言外部行为和业务契约。
+- 采用现有燃气、窨井工单处置模块作为视觉和处置交互先例。
+- 图片验收除页面展示外,还需确认附件元数据中的工单关联、阶段、上传人和时间,以及历史 URL 兼容读取。
+
+## Out of Scope
+
+- 不在供水系统创建或首次派发日常巡检、设备保养工单。
+- 不修改门户、窨井和供水菜单名称及已通过验收的路由结构,除非修复本规格涉及的兼容跳转。
+- 不实现新的按钮级权限编码;沿用后端现有权限校验,待编码明确后另行配置。
+- 不改变报警等级的后端计算算法,只修正返回、展示和颜色表达。
+- 不将报警记录“已处理”改造成设备报警状态,不以工单结案直接覆盖运行系统的自动清警职责。
+- 不合并或推送执行者 worktree 的代码。
+
+## Further Notes
+
+- 当前人工验收结论为:路由回归通过;设备维修派单、设备异常管理、报警管理、报警审核、报警工单处置和写操作闭环不通过;设备报警闭环通过但需继续验证多报警原因场景。
+- 当前已知真实环境风险是同一设备存在多个报警原因时的清警复核,以及前端报警编码、设备报警状态字段映射不完整。
+- 已确认最高层公共 seam 为真实登录态浏览器端的供水报警工单端到端业务流程。spec 发布后再重新唤醒后端和前端执行者;执行者继续使用 `gpt-5.6-terra`、推理强度 `high`,从现有隔离 worktree 工作,不得合并或推送。

+ 115 - 0
docs/superpowers/specs/2026-08-27-water-supply-subsystem-e2e-delivery.md

@@ -0,0 +1,115 @@
+# 供水管网安全运行监测系统前后端整体交付规格
+
+> **2026-09-04 口径注记**:本规格写于旧状态编码期(现网 1-9)。工单状态以七态共识为权威:目标编码 1-7、验收驳回是“待验收回退到处理中”的动作而非终态、新增任意中间态可进入的“已取消”终态、删除延期,见 [ADR-0008](../../../pipe-network-service/docs/adr/0008-work-order-lifecycle-seven-states.md) 与 [work-order-lifecycle.md](../../../pipe-network-service/docs/domain/work-order-lifecycle.md)。
+
+## Problem Statement
+
+“城市地下管网及设施建设改造智能化系统项目”的软件平台由“地下管网管控平台”及其业务子系统组成。本规格针对其中的“供水管网安全运行监测系统”,要求 `pipe-ner-server` 后端与 `city-life-line` 前端形成可独立构建、可通过版本化 REST 契约集成验收的完整业务体验。
+
+当前系统的路由和菜单已经完成多轮人工修正,但供水业务仍需要以一个统一的外部行为标准验收:用户能从门户进入供水系统,查看供水设施、供水监测设备和报警态势,并将真实报警推进到统一工单生命周期。设备维修派单、设备异常管理、报警管理、报警审核和供水工单总览必须职责清晰、数据来源可追溯、页面刷新后状态一致。
+
+现有问题集中在跨前后端边界:供水设备过滤和异常判定不稳定,报警记录与设备报警状态容易混淆,报警转工单和责任人派发阶段混淆,同一设备同一报警类型可能重复建单,工单处置证据和状态流转不完整,且部分页面仍可能依赖模拟数据或返回无法解释的错误。测试必须以真实登录态、真实 REST API 和 KingbaseES 数据为依据,不能以 PostgreSQL、随机模拟数据或私有实现细节代替生产真相。
+
+## Solution
+
+交付一个完整的供水管网安全运行监测系统版本,前后端共同遵守已确认的领域模型、统一工单生命周期和 KingbaseES 数据契约:
+
+- 以门户到供水系统的真实浏览器流程作为最高层公共验收接缝。
+- 供水设施管理覆盖管网信息查询、水厂信息查询、泵站信息查询、水源地信息查询、用水户信息查询、管网数据统计分析和供水设施 GIS 一张图。
+- 监测设备管理覆盖供水设备台账、设备异常管理、设备维修派单、设备维修记录和监测设备 GIS 一张图;只展示设备类型树归属为供水的设备。
+- 运行监测覆盖流量监测、压力监测和漏失监测,展示真实监测数据及其查询、导出能力。
+- 监测报警覆盖报警管理、报警审核、阈值管理和监测报警一张图;报警管理偏重态势与报警转工单,报警审核偏重供水故障维修工单的完整处置。
+- 报警记录进入处置流程时幂等生成不指定责任人的 `1-故障维修` 待派单工单;后续由统一工单流程指定唯一接单责任人。
+- 统一工单视图作为“供水工单总览”使用,集中检索和处置全部供水类型工单,包括故障维修、日常巡检和设备保养;菜单兼容名称“报警工单处置”不擅自修改。
+- 工单结案只触发清警复核;运行系统确认设备不存在任何有效报警原因后,才将设备报警状态恢复为正常。
+
+## User Stories
+
+1. As a 项目用户, I want to 从门户进入供水管网安全运行监测系统首页, so that 我能在正确的业务系统上下文中工作。
+2. As a 项目用户, I want to 看到稳定的供水菜单层级和菜单图标, so that 我能按设施、设备、监测和报警职责定位功能。
+3. As a 供水设施管理人员, I want to 查询供水管线和管点, so that 我能按管材、管径、建设年代、所属区域和关键字定位资料。
+4. As a 供水设施管理人员, I want to 查询水源地、水厂、泵站和用水户, so that 我能维护完整的供水设施业务视图。
+5. As a 供水设施管理人员, I want to 将用水户按居民、工业、商业和行政事业等类型查询, so that 所有供水服务对象使用统一术语并可分类管理。
+6. As a 供水设施管理人员, I want to 批量导入和导出五类供水设施及其 WGS84 坐标, so that 我能批量初始化资料并进行线下分析、业务留档。
+7. As a 供水设施管理人员, I want to 在供水设施 GIS 一张图查看水源地、水厂、泵站、供水管线和用水户, so that 我能在统一空间视图中搜索、定位和查看详情。
+8. As a 供水设施管理人员, I want to 在列表页面与 GIS 一张图之间保持职责区分, so that 查询页面不会重复实现空间定位逻辑。
+9. As a 设备管理人员, I want to 只看到设备类型树归属为供水的监测设备, so that 其他系统设备不会混入供水台账或工单。
+10. As a 设备管理人员, I want to 查看设备分类统计、设备状态、在线状态和报警状态, so that 我能判断设备是否需要人工关注。
+11. As a 设备管理人员, I want to 知道设备进入异常清单的报警原因或维修原因, so that 我能选择正确的后续处置动作。
+12. As a 设备管理人员, I want to 在设备异常管理中查看异常态势、处置状态和关联工单, so that 该页面承担异常管理而不是重复设备维修派单页面。
+13. As a 派单人员, I want to 在设备维修派单中只看到已判定运行故障且没有适用未结束故障维修工单的设备, so that 我能创建可执行的维修工单。
+14. As a 派单人员, I want to 在已有关联工单的设备上看到真实工单编号 `order_no`, so that 我能追踪业务工单而不是辨认随机数据库 ID。
+15. As a 派单人员, I want to 在设备详情侧边栏查看设备当前状态、设备报警状态、在线状态、报警等级和维修状态, so that 详情不会遮挡列表且字段具有一致的辨识度。
+16. As a 派单人员, I want to 在派单窗口左侧选择部门树和唯一责任人、右侧填写工单类型、优先级和问题描述, so that 我能按统一布局完成派单。
+17. As a 派单人员, I want to 使设备维修派单生成的工单类型固定为 `1-故障维修`, so that 维修流程不会进入巡检或保养生命周期。
+18. As a 运行监测人员, I want to 查看流量、压力和漏失监测点位及查询结果, so that 我能发现供水运行趋势和异常。
+19. As a 报警管理人员, I want to 看到报警编码、报警类型、报警等级、设备报警状态、报警记录状态和关联工单状态的独立字段, so that 不同状态来源不会相互替代。
+20. As a 报警管理人员, I want to 看到一级红色、二级橙色、三级蓝色、四级绿色并同时显示文字, so that 报警等级具有明确且可访问的表达。
+21. As a 报警管理人员, I want to 从报警记录确保生成待派单故障维修工单, so that 报警事实先进入统一工单流程。
+22. As a 系统, I want to 对报警转工单执行幂等处理, so that 同一报警记录重复操作不会生成重复工单。
+23. As a 系统, I want to 限制同一设备同一报警编码同时只能存在一个未结束故障维修工单, so that 并发操作不会产生重复处置。
+24. As a 系统, I want to 在报警编码缺失时按报警分类执行同类工单去重, so that 缺少编码的数据仍具有稳定的幂等规则。
+25. As a 系统, I want to 允许同一设备的不同报警类型分别生成故障维修工单, so that 不同有效报警原因可以分别追踪。
+26. As a 报警审核人员, I want to 在页面上看到全部工单、待处理、处理中、待验收和已完成数量, so that 我能判断处置积压和审核工作量。
+27. As a 报警审核人员, I want to 只处理供水设备关联的故障维修工单, so that 报警审核不会承担其他系统或非故障类型工单。
+28. As a 接单责任人, I want to 接收待接单工单并开始处理, so that 工单状态能反映真实处置阶段。
+29. As a 接单责任人, I want to 在处理前提交简要说明和处理前图片, so that 验收人员能比较处置前现场状态。
+30. As a 接单责任人, I want to 在提交验收时提交处置说明和处置后图片, so that 工单具备可审计的完成证据。
+31. As a 派单或审核人员, I want to 验收通过后结案, so that 已完成工单保留完整日志、责任人和图片证据。
+32. As a 派单或审核人员, I want to 驳回验收后让工单回到处理中, so that 责任人可以继续修复而不是丢失闭环。
+33. As a 供水工单管理人员, I want to 在供水工单总览查看全部供水类型工单, so that 故障维修、日常巡检和设备保养均有统一查询入口。
+34. As a 供水责任人, I want to 继续处置其他系统派发给我的巡检或保养工单, so that 供水总览不会因工单类型而阻断授权处置。
+35. As a 运行系统, I want to 在工单结案后复核设备全部有效报警原因, so that 单个工单结案不会错误清除仍存在的其他报警。
+36. As a 运行系统, I want to 在所有有效报警原因恢复后自动清警, so that 设备状态最终反映真实运行情况。
+37. As a 业务用户, I want to 刷新供水页面后保持正确路由、菜单、筛选、状态和详情关系, so that 页面不会发生错位、重复菜单或状态丢失。
+38. As a 验收人员, I want to 在桌面和移动宽度下检查列表、抽屉、弹窗和工单工作台, so that 页面可读且不会发生溢出、重叠或空白渲染。
+39. As a 验收人员, I want to 通过真实登录态端到端流程核对页面、接口和数据库结果, so that 通过标准代表可交付版本而不是局部模拟成功。
+
+## Implementation Decisions
+
+- 前端和后端保持独立仓库、独立构建,通过版本化 REST 契约和真实登录态端到端流程集成;只有契约、页面和数据库验收同时通过,才视为同一系统版本。
+- KingbaseES 是生产和最终验收唯一数据库真相源。PostgreSQL 不作为本项目生产验收或主要集成测试数据库;只有在无法避免时才可用于非权威兼容性检查。
+- 供水系统只展示设备类型树稳定编码判定为供水的设备、报警和工单。工单跨供水、排水、燃气和窨井共用统一生命周期,系统归属由关联设备类型判定,不由页面名称或报警记录状态推断。
+- 既有统一工单 API 和数据库状态机继续作为工单契约;不引入新的平行维修任务、报警工单生命周期或 Flowable 工作流。
+- 工单类型固定为:`1-故障维修`、`2-日常巡检`、`3-设备保养`。报警转入和设备故障派单均使用类型 `1`;供水系统不负责首次创建或首次派发巡检、保养工单,但被派给供水责任人的巡检或保养工单可在供水工单总览继续处置。
+- 报警转工单是“生成待派单工单”,工单派发是“为待派单工单指定唯一接单责任人”,两者必须在契约、页面文案和状态上分开。报警创建接口不能接收责任人并越过待派单阶段。
+- 同一设备与同一报警编码只能有一个未结束同类故障维修工单;报警编码缺失时使用报警分类作为去重键;不同报警类型允许分别建单。并发重复请求必须返回既有工单或明确的业务冲突结果。
+- 设备报警状态来自设备状态表,由运行系统根据全部有效报警原因维护;报警记录状态只表示单条历史记录的人工处置辅助状态,工单状态只表示处置生命周期,三者不得互相替代。
+- 工单状态保持现有数据库状态机:待派单、待接单、已接单、处理中、待验收、已结案,另设任意中间态可进入的终态“已取消”;验收驳回从待验收回到处理中(非终态)。目标七态编码 1-7 与新旧编码对照见 [ADR-0008](../../../pipe-network-service/docs/adr/0008-work-order-lifecycle-seven-states.md),不存在“已驳回”终态,也不存在延期状态。工单结案只触发清警复核,不直接写正常状态。
+- 同一设备存在多个有效报警原因时,一项维修可以产生“随同修复”结果,但其他工单仍保留记录并完成各自验收;只有清警复核确认没有有效报警原因时,设备才恢复正常。
+- 处理前和处置后图片及说明是工单处置证据。附件使用既有 MinIO 与 KingbaseES 元数据方案,现有工单图片 URL 字段保留兼容,读取时不得丢失历史图片。
+- 页面职责固定:设备异常管理展示异常态势和处置跟踪;设备维修派单聚焦可派单故障设备;报警管理展示报警态势、详情和报警转工单/派发入口;报警审核承载供水故障维修工单完整流程;“报警工单处置”菜单兼容保留,页面职责为供水工单总览。
+- GIS 联动统一集中到供水设施 GIS 一张图、监测设备 GIS 一张图和监测报警一张图;供水设施 GIS 范围包括水源地、水厂、泵站、供水管线和用水户,不包含消火栓。
+- 报警等级由运行监测规则计算,前端只展示一级至四级及其红、橙、蓝、绿颜色,不在前端重新计算等级。
+- 页面采用已确认的蓝白运行指挥台视觉规范:状态文字与颜色同时表达,详情使用右侧抽屉,派单窗口采用部门/责任人与工单表单左右分栏,菜单项使用语义图标并提供缺失图标兜底。
+- 后端优先复用已有接口和服务,在不破坏旧入口的前提下通过兼容适配接入统一契约;新能力不得制造与现有统一工单流程相冲突的替代实现。
+
+## Testing Decisions
+
+- 最高层公共 seam 是真实登录态浏览器端的供水报警工单端到端业务流程:从门户进入供水系统,经真实前端页面、REST API 和 KingbaseES 数据完成可观察闭环。不得把私有 Java 方法、单个 Mapper、Vue 内部状态或随机模拟数据当成独立交付标准。
+- 端到端流程至少覆盖:门户路由、供水菜单、供水设备过滤、设施与监测页面加载、报警数据加载、报警转工单幂等、同设备同报警类型去重、不同报警类型建单、故障维修类型、唯一责任人派发、接单、开始处理、处理说明、处理前图片、提交验收、处置后图片、验收通过、驳回回处理中、再次处理、验收结案和清警复核。
+- 每个关键页面刷新后重新读取真实数据,确认路由不错位、不产生重复菜单、不回退随机数据;接口失败必须显示可解释的错误和重试状态。
+- 端到端断言必须区分三类状态来源:设备报警状态来自设备状态表,报警记录状态来自报警记录,工单处置状态来自工单;同时确认 `order_no`、责任人、日志和处置证据可回看。
+- 必须构造同一设备存在两个有效报警原因的场景:结案一个工单后设备仍报警,全部原因恢复后才显示正常;不得以单个工单结案直接覆盖设备状态。
+- 浏览器验收覆盖 1440px、1024px 和 390px,检查设备维修派单、设备异常管理、报警管理、报警审核和供水工单总览五页的职责区分、状态统计、右侧抽屉、派单弹窗、图片证据和工单工作台。
+- 自动化测试作为该公共 seam 的支撑证据:前端生产构建和已有 Node 测试、后端 Maven 编译/测试、KingbaseES SQL 迁移校验、接口冒烟测试、浏览器控制台无应用级错误、`git diff --check`。测试断言外部行为、REST 契约和数据库业务结果。
+- 现有燃气和窨井工单处置模块作为交互先例;图片验证除视觉展示外,还要核对工单、阶段、对象键/URL、上传人和时间等元数据关联。
+- 测试数据必须可重复初始化且与业务真实字段一致;生产验收不得依赖 PostgreSQL 或只在内存数据库中通过。
+
+## Out of Scope
+
+- 不改变已确认的门户、窨井和供水菜单名称及已通过验收的路由结构,除非为本规格的兼容跳转修复必要错误。
+- 不在供水系统首次创建或首次派发日常巡检、设备保养工单;不复制其他系统的工单生命周期。
+- 不改变报警等级计算算法、设备类型树编码体系或统一工单数据库状态机。
+- 不把报警记录状态改造成设备报警状态,不以工单结案无条件清警。
+- 不新增消火栓实体、消火栓 GIS 图层或消火栓专用业务流程。
+- 不新增未经确认的按钮级权限编码;权限沿用现有后端校验,待编码明确后另行配置。
+- 不将前后端合并为单仓库,不要求执行者合并、推送或发布代码。
+- 不把 UI 视觉重构扩展到本规格未覆盖的其他业务系统页面。
+
+## Further Notes
+
+- 当前已确认的最高层 seam 是真实登录态浏览器端供水报警工单端到端流程,前后端局部测试只提供证据,不替代该接缝验收。
+- 当前重点风险是供水设备准入判定、报警编码和关联工单映射、同类工单并发去重、状态字段来源、报警审核驳回回处理中,以及多报警原因下的清警复核。
+- 生产数据库变更和集成验收必须在 KingbaseES 环境完成;无法连接目标数据库时,应记录为环境阻塞,不得用其他数据库的通过结果替代。
+- 执行者从隔离 worktree 开始工作,默认使用 `gpt-5.6-terra`、推理强度 `high`,不得合并或推送;当前对话负责项目管理、代码审查和最终验收。

+ 62 - 0
logs/pipe-network-service/spring.log

@@ -5501,3 +5501,65 @@ com.zksy.common.exception.ServiceException: 设备不存在:dfbb32ef87a74fc353
 17:32:25:222  INFO 19692 --- [SpringApplicationShutdownHook] sys-user                                 : ====关闭后台任务任务线程池====
 17:32:25:236  INFO 19692 --- [SpringApplicationShutdownHook] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} closing ...
 17:32:25:246  INFO 19692 --- [SpringApplicationShutdownHook] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} closed
+17:11:36:343  INFO 41920 --- [restartedMain] com.zksy.ZksyApplication                 : The following 1 profile is active: "dev"
+17:11:37:093  INFO 41920 --- [restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
+17:11:37:094  INFO 41920 --- [restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode.
+17:11:37:148  INFO 41920 --- [restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 44 ms. Found 0 Redis repository interfaces.
+17:11:37:366  INFO 41920 --- [restartedMain] o.s.cloud.context.scope.GenericScope     : BeanFactory id=0e5a4171-518d-3030-a3b9-b47de6434c0a
+17:11:38:071  INFO 41920 --- [restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8301 (http)
+17:11:38:083  INFO 41920 --- [restartedMain] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
+17:11:38:084  INFO 41920 --- [restartedMain] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.106]
+17:11:38:166  INFO 41920 --- [restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
+17:11:38:167  INFO 41920 --- [restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1817 ms
+17:11:38:503 DEBUG 41920 --- [restartedMain] c.z.f.s.f.JwtAuthenticationTokenFilter   : Filter 'jwtAuthenticationTokenFilter' configured for use
+17:11:38:894  INFO 41920 --- [restartedMain] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
+17:11:41:536  INFO 41920 --- [restartedMain] c.z.s.impl.MinioFileStorageServiceImpl   : 初始化MinIO成功
+17:11:43:269  INFO 41920 --- [restartedMain] c.a.c.sentinel.SentinelWebMvcConfigurer  : [Sentinel Starter] register SentinelWebInterceptor with urlPatterns: [/**].
+17:11:43:511  WARN 41920 --- [restartedMain] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'inspectTaskServiceImpl': Unsatisfied dependency expressed through field 'baseMapper'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.zksy.ops.mapper.InspectTaskMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
+17:11:43:512  INFO 41920 --- [restartedMain] sys-user                                 : ====关闭后台任务任务线程池====
+17:11:43:512  WARN 41920 --- [restartedMain] .s.c.a.CommonAnnotationBeanPostProcessor : Destroy method on bean with name 'shutdownManager' threw an exception: java.lang.ExceptionInInitializerError
+17:11:43:517  INFO 41920 --- [restartedMain] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} closing ...
+17:11:43:518  INFO 41920 --- [restartedMain] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} closed
+17:11:43:657  INFO 41920 --- [restartedMain] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
+17:11:43:683  INFO 41920 --- [restartedMain] ConditionEvaluationReportLoggingListener : 
+
+Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
+17:11:43:719 ERROR 41920 --- [restartedMain] o.s.boot.SpringApplication               : Application run failed
+
+org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'inspectTaskServiceImpl': Unsatisfied dependency expressed through field 'baseMapper'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.zksy.ops.mapper.InspectTaskMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
+	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:713) ~[spring-beans-5.3.39.jar:5.3.39]
+	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:693) ~[spring-beans-5.3.39.jar:5.3.39]
+	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119) ~[spring-beans-5.3.39.jar:5.3.39]
+	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:408) ~[spring-beans-5.3.39.jar:5.3.39]
+	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1431) ~[spring-beans-5.3.39.jar:5.3.39]
+	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:619) ~[spring-beans-5.3.39.jar:5.3.39]
+	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.39.jar:5.3.39]
+	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:336) ~[spring-beans-5.3.39.jar:5.3.39]
+	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.39.jar:5.3.39]
+	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334) ~[spring-beans-5.3.39.jar:5.3.39]
+	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:209) ~[spring-beans-5.3.39.jar:5.3.39]
+	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955) ~[spring-beans-5.3.39.jar:5.3.39]
+	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:932) ~[spring-context-5.3.39.jar:5.3.39]
+	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:591) ~[spring-context-5.3.39.jar:5.3.39]
+	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.12.jar:2.7.12]
+	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731) ~[spring-boot-2.7.12.jar:2.7.12]
+	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[spring-boot-2.7.12.jar:2.7.12]
+	at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) ~[spring-boot-2.7.12.jar:2.7.12]
+	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) ~[spring-boot-2.7.12.jar:2.7.12]
+	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) ~[spring-boot-2.7.12.jar:2.7.12]
+	at com.zksy.ZksyApplication.main(ZksyApplication.java:19) ~[classes/:na]
+	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
+	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
+	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
+	at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
+	at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) ~[spring-boot-devtools-2.7.12.jar:2.7.12]
+Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.zksy.ops.mapper.InspectTaskMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
+	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1801) ~[spring-beans-5.3.39.jar:5.3.39]
+	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1357) ~[spring-beans-5.3.39.jar:5.3.39]
+	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1311) ~[spring-beans-5.3.39.jar:5.3.39]
+	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:710) ~[spring-beans-5.3.39.jar:5.3.39]
+	... 25 common frames omitted
+
+17:11:44:645  WARN 41920 --- [Thread-8] c.a.nacos.common.notify.NotifyCenter     : [NotifyCenter] Start destroying Publisher
+17:11:44:646  WARN 41920 --- [Thread-8] c.a.nacos.common.notify.NotifyCenter     : [NotifyCenter] Destruction of the end
+17:11:44:645  WARN 41920 --- [Thread-4] c.a.n.common.http.HttpClientBeanHolder   : [HttpClientBeanHolder] Start destroying common HttpClient

+ 9 - 0
pipe-network-service/docs/adr/0003-water-supply-subsystem-product-boundary.md

@@ -0,0 +1,9 @@
+---
+status: accepted
+---
+
+# Water supply subsystem product boundary
+
+本项目软件平台的正式产品定位是“城市地下管网及设施建设改造智能化系统项目”中的“地下管网管控平台”;供水领域的交付单元是其“供水管网安全运行监测系统”,由 `pipe-network-service` 管理端后端和 `city-life-line` 前端共同组成完整业务体验。供水设施、监测设备、运行监测、监测报警和工单处置属于同一供水子系统,不能把当前交付缩减为单独的报警页面或单一工单功能。
+
+供水基础设施中的居民、工业、商业、行政事业等所有服务对象统一称为“用水户”;既有 `user`、`waterUser`、`WaterUserInfo` 和 `water_user_info` 等技术标识继续保留,仅作为兼容映射,不表示“大用水户”子集。

+ 9 - 0
pipe-network-service/docs/adr/0004-cross-system-unified-work-order-lifecycle.md

@@ -0,0 +1,9 @@
+---
+status: accepted
+---
+
+# Cross-system unified work-order lifecycle
+
+供水、排水、燃气、窨井等业务系统共用统一的工单业务流程和生命周期;工单所属系统由其关联设备类型树中的稳定业务编码确定,而不是为每个系统建立一套互不兼容的工单服务。现有 `/api/work-order` 作为规范 API,旧系统入口和接口保留兼容适配;统一工单继续采用数据库状态机和事务服务,不在本轮引入 Flowable 等工作流引擎。新工单使用统一的创建、派单、接单、开始处理、提交验收、验收通过、驳回、延期日志语义,历史日志通过兼容映射展示。
+
+> **2026-09-04 更新(部分被 ADR-0008 取代)**:上句“驳回、延期日志语义”及隐含的九态口径已被 [ADR-0008](0008-work-order-lifecycle-seven-states.md) 收敛为**七态**:驳回是“待验收→处理中”的回退动作而非独立终态;新增独立终态“已取消”;删除“延期审核/已延期”。跨系统共用统一工单、以设备类型树确定系统归属、数据库状态机、旧入口兼容等其余决策继续有效。权威流程见 [docs/domain/work-order-lifecycle.md](../domain/work-order-lifecycle.md)。

+ 7 - 0
pipe-network-service/docs/adr/0005-independent-frontend-backend-contract-gate.md

@@ -0,0 +1,7 @@
+---
+status: accepted
+---
+
+# Independent frontend and backend contract gate
+
+`city-life-line` 前端与 `pipe-ner-server` 后端保持独立构建和独立仓库边界,通过版本化 REST 契约、真实登录态端到端流程和 KingbaseES 集成验收作为集成门禁;不强制合并为单仓库或同步发布。前后端只有在接口契约和端到端验收均通过时,才视为同一供水管网安全运行监测系统版本。

+ 7 - 0
pipe-network-service/docs/adr/0006-work-order-attachment-storage.md

@@ -0,0 +1,7 @@
+---
+status: accepted
+---
+
+# Work-order attachment storage
+
+工单处理前和处置后的图片、后续扩展附件统一使用现有 MinIO 保存对象,KingbaseES 保存附件元数据(工单、阶段、对象键、类型、大小、校验值、上传人和时间),由后端提供受控访问地址。现有 `work_order` 表中的图片 URL 字段保留作为兼容字段,不删除或破坏历史数据;新上传附件以阶段化元数据为规范来源,读取时兼容既有 URL。这样既保留当前接口兼容性,又避免工单主表和数据库存储二进制文件持续膨胀。

+ 16 - 0
pipe-network-service/docs/adr/0007-shared-pump-station-by-type.md

@@ -0,0 +1,16 @@
+---
+status: accepted
+---
+
+# Shared pump station table discriminated by station_type
+
+泵站同时服务于供水与排水两个业务系统,历史上只有供水专用表 `app_user.water_pump_station`,而排水侧接口(`/api/drainage/pumpStation`)直接复用该表且没有归属过滤,两侧看到同一批数据。
+
+决策:将该表改名为公用表 `app_user.pump_station`,新增 `station_type varchar(16) NOT NULL DEFAULT 'water_supply'` 作为业务归属判别列,取值 `water_supply`(供水泵站)/`drainage`(排水泵站),并以 CHECK 约束限定取值域;存量数据全部回填为 `water_supply`。
+
+- 不建立第二张平行泵站表,不复制一套排水泵站实体;Java 类名(`WaterPumpStation`)、包路径与既有 REST 路径(`/api/water/pumpStation`、`/api/drainage/pumpStation`)为兼容前端全部保留,只改表映射,不做大规模重命名。
+- 每个入口必须显式限定本系统类型:供水侧的分页、回收站、GIS 一张图、统计分析、导入导出固定 `station_type='water_supply'`;排水侧固定 `station_type='drainage'`。新增记录的类型由后端按入口强制写入,更新接口不允许修改归属列。
+- 类型编码采用稳定英文业务编码(与设施类型 `source/plant/pumpStation` 风格一致),不使用易变的中文名,也不使用语义不自解释的数字。
+- 迁移与回滚脚本分别为 `sql/migration_pump_station_shared.sql`、`sql/rollback_pump_station_shared.sql`;回滚前若已存在排水记录必须先人工处置,回滚脚本内置护栏中止回滚。
+
+这样在不破坏现有接口和数据的前提下,让同一张物理表支撑两个业务系统,并从 SQL 与后端入口两层保证数据不串系统。

+ 42 - 0
pipe-network-service/docs/adr/0008-work-order-lifecycle-seven-states.md

@@ -0,0 +1,42 @@
+---
+status: accepted
+---
+
+# ADR-0008 工单生命周期收敛为七态
+
+日期:2026-09-04(状态编码 2026-09-05 调整为从 1 开始)
+取代(部分):[ADR-0004 跨系统统一工单生命周期](0004-cross-system-unified-work-order-lifecycle.md) 中“驳回、延期日志语义”及隐含的九态口径。
+领域共识全文:[docs/domain/work-order-lifecycle.md](../domain/work-order-lifecycle.md)
+
+## 背景(Context)
+
+统一工单此前在代码与文档中使用九态:`1 待派单 / 2 待接单 / 3 已接单 / 4 处理中 / 5 待验收 / 6 已结案 / 7 已驳回 / 8 延期审核 / 9 已延期`,并存在两处与实际业务闭环不符的问题:
+
+1. **“驳回”被建成独立终态,且只允许在待派单/待接单驳回**;但真实业务里,管理员是在“待验收”阶段对处置结果不满意,需要把工单**退回“处理中”让同一责任人继续整改**,而不是终止工单。旧实现缺失“待验收→处理中”这条回退边。
+2. **“取消”没有独立终态**:旧 `cancel` 与 `reject` 都写入状态 7,数据上无法区分“取消”和“驳回”;同时“延期审核/已延期”使状态机膨胀,而当前交付流程并不包含延期环节。
+
+前端 `CONTEXT.md`、UE 驾驶舱 `CONTEXT.md` 与 issue#10、供应商需求文档又分别写成“含已驳回的阶段”“完整保留九态”,三套口径互相不一致。
+
+## 决策(Decision)
+
+以新的**七态模型**作为跨三仓(后端、前端、UE)唯一领域权威,状态编码从 1 开始:
+
+- 状态:`1 待派单 / 2 待接单 / 3 已接单 / 4 处理中 / 5 待验收 / 6 已结案 / 7 已取消`,终态仅“已结案 / 已取消”。
+- **驳回是动作不是状态**:验收驳回 = `待验收(5) → 处理中(4)`,必须带驳回原因,交同一责任人继续处置。
+- **取消进入独立终态“已取消(7)”**:任意中间态(1–5)可由管理员取消,已结案不可取消;取消后需要继续处置的,另建新工单派给新责任人。
+- **删除延期**:移除“延期审核/已延期”两态及主流程中的申请/审核延期环节。
+- 巡检、维修等工单允许创建即下派,直接进入“待接单(2)”。
+- 继续使用数据库状态机与事务服务,不引入工作流引擎;附件仍按 ADR-0006 分阶段存储。
+
+## 编码与迁移
+
+- 领域文档以**状态名**为权威,目标编码为 1–7;现网 `work_order.order_status` 仍是旧 1–9。目标编码从 1 开始后,前六态(1–6)与现网旧编码**同值**,仅第 7 态语义不同。
+- **本次只确立领域共识与文档,不重排数据库、不改后端代码**;代码改造与存量数据编码迁移另开任务,按生命周期文档第 8 节对照表执行。
+- 迁移任务内容包括:把旧 7(“已驳回”/取消混用)重定义为独立“已取消(7)”、实现 5→4 验收驳回回退、下线延期状态(旧 8/9)/字段语义、统一工单操作日志类型,并同步前端与 UE 的状态字典;前六态编码与现网一致,无需整体重排。
+
+## 后果(Consequences)
+
+- 三仓对工单状态的表述统一为七态,消除“已驳回是状态还是动作”“是否保留九态/延期”的长期分歧。
+- UE 驾驶舱按七态**只读展示**工单当前状态与状态时间线,不提供任何处置写入口;原 issue#10“保留九态”按本决策改为七态。
+- 在后端迁移完成前存在“文档目标编码 1–7 / 现网编码 1–9(前六态同值,第 7 态语义不同)”的临时双轨期,所有接口、测试与 UI 以状态名和对照表换算,不得再新增依赖“已驳回终态/延期”的功能。
+- ADR-0004 中“跨系统共用统一工单、以设备类型树确定系统归属、数据库状态机、兼容旧入口”等仍然有效;仅其“驳回、延期日志语义”被本 ADR 取代。

+ 17 - 0
pipe-network-service/docs/adr/0009-retain-parameter-names-and-explicit-mvc-binding-names.md

@@ -0,0 +1,17 @@
+---
+status: accepted
+---
+
+# Retain parameter names at compile time and name MVC binding annotations explicitly
+
+供水 GIS 图层 `/waterSupply/gis/map/features`、流量监测分页 `/waterSupply/monitoring/flow/page` 等接口运行期返回 500:`Name for argument of type [java.lang.String/long] not specified, and parameter name information not found in class file either.`。
+
+根因(经 spring-webmvc 5.3.39 MockMvc 最小复现验证,与是否被 CGLIB 代理无关):Spring MVC 对未显式命名的 `@RequestParam/@PathVariable`,先靠 `-parameters` 保留的反射形参名,再靠调试局部变量表(`-g:vars`,Maven 默认开、裸 javac 默认不开)兜底;两条路都拿不到名字时,参数绑定直接抛异常。本工程两个 pom 都未开 `-parameters`,一旦运行时类未带局部变量表即必现。
+
+决策(A+B 双保险):
+
+- A(构建兜底):根 `pom.xml`、`pipe-network-service/pom.xml`、`zksy-system/pom.xml` 的 maven-compiler-plugin 统一开启 `<parameters>true</parameters>`;`pipe-network-service` 的编译器插件由不支持该选项的 3.1 升到与根一致的 3.8.1。实测开启后即使 `-g:none` 也能正常绑定。
+- B(代码规范):新增/在维护的控制器显式写 `@RequestParam(value="x")`/`@PathVariable("x")`,不依赖编译产物。本轮先覆盖供水(`web/controller/WaterSupply`,17 个)与排水(`web/controller/drainage`,8 个)共 25 个控制器、397 处;其余历史控制器由 `-parameters` 兜底,显式命名另行排期,避免跨团队大 diff。
+- 禁止 `@RequestParam Map/ MultiValueMap` 形式补名(Spring 对 Map 形参走整表注入,写名反而报错),脚本已内置跳过。
+
+新增控制器的评审检查项:绑定注解必须带显式名;CI 构建不得关闭 `-parameters`。

+ 124 - 0
pipe-network-service/docs/domain/work-order-lifecycle.md

@@ -0,0 +1,124 @@
+# 统一工单处置生命周期(七态共识)
+
+状态:accepted(跨后端 `pipe-ner-server`、前端 `city-life-line`、UE 驾驶舱 `Pipelines` 的统一领域共识)
+确立日期:2026-09-04(状态编码 2026-09-05 调整为从 1 开始)
+决策依据:[ADR-0008 工单生命周期收敛为七态](../adr/0008-work-order-lifecycle-seven-states.md)
+取代口径:旧“九态模型”(含“已驳回”终态、“延期审核/已延期”),以及 ADR-0004 中“驳回、延期日志语义”的旧表述。
+
+> 本文只定义**领域流程共识**(状态、角色、迁移动作、证据与边界),不规定具体类、接口或 SQL。后端代码与存量数据当前仍是旧编码,**编码迁移另开任务**,在迁移完成前以本文状态名为唯一业务权威,编码以对照表换算。
+
+## 1. 角色
+
+- **管理员(派单/验收者)**:派单下发、验收(审核通过 / 验收驳回 / 取消工单)。可在任意中间态取消工单。
+- **接单责任人**:接单确认、开始处置、多次追加处理流水、提交审核。一次派发指定唯一接单责任人。
+- **运行系统/设备**:产生设备预警、报警,是工单的来源之一,但不参与工单状态决策。
+
+## 2. 进入流程的两条入口
+
+1. **报警转工单**:由设备预警/报警创建工单,进入“待派单”,创建时不指定责任人。
+2. **直接下派**:巡检工单、维修工单等可在创建时直接派给责任人,跳过“待派单”,直接进入“待接单”。
+
+## 3. 七个状态(目标权威模型)
+
+状态编码从 1 开始:
+
+| 目标编码 | 状态 | 含义 | 是否终态 |
+| --- | --- | --- | --- |
+| 1 | 待派单 | 已创建、尚未指定接单责任人,等待管理员派单 | 否 |
+| 2 | 待接单 | 已派给责任人,等待责任人接单确认 | 否 |
+| 3 | 已接单 | 责任人已接单确认,尚未开始现场处置 | 否 |
+| 4 | 处理中 | 责任人已开始处置;可多次追加处理流水 | 否 |
+| 5 | 待验收 | 责任人已提交审核,等待管理员验收 | 否 |
+| 6 | 已结案 | 管理员审核通过,处置闭环 | 是 |
+| 7 | 已取消 | 管理员取消,强制终止;用于改派新责任人(另建新单) | 是 |
+
+终态只有“已结案(6)”和“已取消(7)”。除两个终态外,其余均为中间态(1–5)。
+
+## 4. 状态迁移(动作即事件)
+
+| 动作 | 角色 | 前置态 | 目标态 | 必填信息 |
+| --- | --- | --- | --- | --- |
+| 创建(报警转工单) | 系统/管理员 | — | 1 待派单 | 工单描述、来源报警/设备 |
+| 直接下派(巡检/维修等) | 管理员 | — | 2 待接单 | 唯一责任人 |
+| 派单/下发 | 管理员 | 1 | 2 待接单 | 唯一责任人 |
+| 接单确认 | 责任人 | 2 | 3 已接单 | — |
+| 开始处置 | 责任人 | 3 | 4 处理中 | **处理前照片 + 现场简述** |
+| 追加处理流水 | 责任人 | 4 | 4 处理中(自循环,可多次) | 处理描述,可含图片 |
+| 提交审核 | 责任人 | 4 | 5 待验收 | **处理后照片 + 处理描述** |
+| 审核通过 | 管理员 | 5 | 6 已结案(终态) | 审核评价/建议 |
+| 验收驳回 | 管理员 | 5 | 4 处理中(**回退**) | 驳回原因 |
+| 取消工单 | 管理员 | 1/2/3/4/5 任一中间态 | 7 已取消(终态) | 建议填取消原因 |
+
+约束:
+
+- “已结案(6)”不可再取消、驳回或回退。
+- “已取消(7)”是终态,不能复活;需要换责任人时,**另建一张新工单**重新派发。
+- “验收驳回”只发生在“待验收(5)”,回到“处理中(4)”交**同一责任人**继续处理,可再次走“提交审核→待验收”。
+
+### 状态机视图
+
+```mermaid
+stateDiagram-v2
+    [*] --> 待派单: 报警转工单创建
+    [*] --> 待接单: 巡检/维修直接下派
+    待派单 --> 待接单: 管理员派单
+    待接单 --> 已接单: 责任人接单确认
+    已接单 --> 处理中: 开始处置(处理前照片+简述)
+    处理中 --> 处理中: 追加处理流水(可多次)
+    处理中 --> 待验收: 提交审核(处理后照片+描述)
+    待验收 --> 处理中: 验收驳回(驳回原因)
+    待验收 --> 已结案: 审核通过(评价/建议)
+    待派单 --> 已取消: 管理员取消
+    待接单 --> 已取消: 管理员取消
+    已接单 --> 已取消: 管理员取消
+    处理中 --> 已取消: 管理员取消
+    待验收 --> 已取消: 管理员取消
+    已结案 --> [*]
+    已取消 --> [*]
+```
+
+## 5. “验收驳回”与“取消工单”的区别(重点,消除旧歧义)
+
+- **验收驳回**是一个**动作而非状态**:管理员对处置结果不满意时,把“待验收”工单退回“处理中”,由原责任人继续整改;工单不终止、不换单。
+- **取消工单**是一个**迁到终态“已取消”的动作**:用于不再继续本单(例如对该责任人处置不满、误报、停止处置)。取消后若仍需处置,应创建新工单并派给新责任人。
+- 旧模型中“已驳回”是独立终态、且只允许在待派单/待接单驳回;**该终态在七态模型中废止**:未开始即终止的情形统一走“取消→已取消”,验收不通过统一走“驳回→回处理中”。
+
+## 6. 处理流水与处置证据
+
+处置证据按阶段区分,遵循 [ADR-0006 工单附件存储](../adr/0006-work-order-attachment-storage.md)(MinIO 对象 + KingbaseES 阶段化元数据,旧 URL 字段兼容):
+
+- **处理前证据**:开始处置(3→4)时上传处理前照片与现场简述。
+- **处理流水**:处理中(4)可多次追加,每条可含图片与处理描述,并逐条写入工单操作日志,不改变状态。
+- **处理后证据**:提交审核(4→5)时上传处理后照片与处理描述。
+- **验收意见**:审核通过时记录评价/建议;验收驳回时记录驳回原因;取消时记录取消原因。
+
+## 7. 与设备预警/报警的关系
+
+- 工单来源于设备预警/报警,但**工单状态独立于设备报警状态**:工单结案/取消不直接清除报警;设备是否恢复由运行系统依据“有效报警原因”自动维护(见 ADR-0002)。
+- 同一设备同一报警类型,同一时间只能存在一张未结束(非 6/7 终态)的同类工单;不同报警类型可分别建单。
+- 一次维修同时消除其他异常属于“随同修复”,其他工单仍各自保留并完成验收,不静默结案。
+
+## 8. 目标编码与现网编码对照(迁移期)
+
+现网 `work_order.order_status` 仍是旧 1–9 编码;**对齐到目标 1–7 的代码与存量数据迁移另开任务**,迁移完成前按下表换算,业务讨论一律使用状态名。目标编码从 1 开始后,前六态与现网旧编码 1–6 **同值**,仅第 7 态语义不同。
+
+| 状态名 | 目标编码(权威) | 现网旧编码(迁移前) |
+| --- | --- | --- |
+| 待派单 | 1 | 1(同值) |
+| 待接单 | 2 | 2(同值) |
+| 已接单 | 3 | 3(同值) |
+| 处理中 | 4 | 4(同值) |
+| 待验收 | 5 | 5(同值) |
+| 已结案 | 6 | 6(同值) |
+| 已取消 | 7 | 旧实现无独立值(`cancel`/`reject` 都落到旧 7) |
+| ~~已驳回(废止)~~ | — | 7(旧终态,取消与驳回混用) |
+| ~~延期审核(废止)~~ | — | 8 |
+| ~~已延期(废止)~~ | — | 9 |
+
+迁移任务至少需要:把旧 7(“已驳回”/取消混用)重定义为独立“已取消(7)”、把“验收驳回”实现为 5→4 回退、下线延期状态(旧 8/9)与字段语义、同步工单操作日志类型与各前端/UE 状态字典;前六态编码与现网一致,无需整体重排。
+
+## 9. 本版明确删除/不纳入的内容
+
+- **删除“已驳回”终态**:驳回仅指“待验收→处理中”的回退动作。
+- **删除延期相关状态**:不再有“延期审核(旧 8)”“已延期(旧 9)”,工单主流程不含申请/审核延期环节;旧字段的下线随迁移任务处理。
+- 不引入 Flowable 等外部工作流引擎,继续使用数据库状态机 + 事务服务(沿用 ADR-0004 中仍有效的部分)。

+ 54 - 0
pipe-network-service/sql/migration_pump_station_shared.sql

@@ -0,0 +1,54 @@
+-- 泵站信息表公用化迁移(KingbaseES / PostgreSQL 兼容)
+-- 目标:
+--   1. app_user.water_pump_station 改名为 app_user.pump_station(公用泵站表)
+--   2. 新增 station_type 类型字段:water_supply-供水泵站,drainage-排水泵站
+--   3. 存量数据全部回填为供水泵站,并强制非空与取值域约束
+-- 可重复执行;不删除任何业务数据。
+-- 对应回滚脚本:rollback_pump_station_shared.sql
+
+-- 1. 表改名(重复执行时旧表已不存在,IF EXISTS 自动跳过;数据、主键、外键保持不变)
+ALTER TABLE IF EXISTS app_user.water_pump_station RENAME TO pump_station;
+
+-- 2. 新增泵站类型字段
+ALTER TABLE app_user.pump_station ADD COLUMN IF NOT EXISTS station_type varchar(16);
+
+-- 3. 存量数据回填为供水泵站(历史数据均来自供水子系统)
+UPDATE app_user.pump_station SET station_type = 'water_supply' WHERE station_type IS NULL;
+
+-- 4. 默认值与非空约束
+ALTER TABLE app_user.pump_station ALTER COLUMN station_type SET DEFAULT 'water_supply';
+ALTER TABLE app_user.pump_station ALTER COLUMN station_type SET NOT NULL;
+COMMENT ON COLUMN app_user.pump_station.station_type IS '泵站类型:water_supply-供水泵站,drainage-排水泵站';
+
+-- 5. 取值域 CHECK 约束(幂等)
+DO $$
+BEGIN
+    IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'ck_pump_station_type') THEN
+        ALTER TABLE app_user.pump_station
+            ADD CONSTRAINT ck_pump_station_type CHECK (station_type IN ('water_supply', 'drainage'));
+    END IF;
+END $$;
+
+-- 6. 坐标索引随表改名,并新增类型过滤索引(均幂等)
+ALTER INDEX IF EXISTS app_user.idx_water_pump_station_lon_lat RENAME TO idx_pump_station_lon_lat;
+CREATE INDEX IF NOT EXISTS idx_pump_station_type ON app_user.pump_station (station_type);
+
+-- 7. 主键序列改名(若沿用表名前缀默认命名则存在,幂等处理)
+DO $$
+BEGIN
+    IF EXISTS (SELECT 1
+                 FROM pg_class c
+                 JOIN pg_namespace n ON n.oid = c.relnamespace
+                WHERE n.nspname = 'app_user'
+                  AND c.relname = 'water_pump_station_id_seq') THEN
+        ALTER SEQUENCE app_user.water_pump_station_id_seq RENAME TO pump_station_id_seq;
+    END IF;
+END $$;
+
+-- 8. 表注释更新为公用泵站表
+COMMENT ON TABLE app_user.pump_station IS '公用泵站信息表,按 station_type 区分供水泵站(water_supply)与排水泵站(drainage)';
+
+-- 执行后校验(手动执行确认):
+-- SELECT station_type, COUNT(*) FROM app_user.pump_station GROUP BY station_type;
+-- SELECT column_name, data_type, is_nullable, column_default
+--   FROM information_schema.columns WHERE table_schema='app_user' AND table_name='pump_station' AND column_name='station_type';

+ 56 - 0
pipe-network-service/sql/rollback_pump_station_shared.sql

@@ -0,0 +1,56 @@
+-- 泵站信息表公用化迁移的回滚脚本(KingbaseES / PostgreSQL 兼容)
+-- 对应正向迁移:migration_pump_station_shared.sql
+--
+-- 回滚前置条件:
+--   回滚后表恢复为“供水专用”,不能携带排水泵站数据。
+--   默认安全策略:若存在 station_type='drainage' 的记录则中止回滚,
+--   由人工先导出/迁移这些数据;确认丢弃时再手动执行下方注释中的删除语句。
+--
+-- 警告:本脚本会删除 station_type 列并改回表名,请先完整备份后执行。
+
+-- 0. 安全护栏:存在排水泵站记录时中止回滚
+DO $$
+DECLARE
+    drainage_count integer;
+BEGIN
+    SELECT COUNT(*) INTO drainage_count
+      FROM app_user.pump_station WHERE station_type = 'drainage';
+    IF drainage_count > 0 THEN
+        RAISE EXCEPTION '回滚中止:pump_station 中存在 % 条排水泵站(drainage)记录,请先导出或确认删除后再回滚', drainage_count;
+    END IF;
+END $$;
+
+-- 如确认丢弃排水泵站数据,先手动执行(默认不执行):
+-- DELETE FROM app_user.pump_station WHERE station_type = 'drainage';
+
+-- 1. 删除取值域约束与类型索引
+ALTER TABLE app_user.pump_station DROP CONSTRAINT IF EXISTS ck_pump_station_type;
+DROP INDEX IF EXISTS app_user.idx_pump_station_type;
+
+-- 2. 删除类型字段(先移除默认值与非空,再删列)
+ALTER TABLE IF EXISTS app_user.pump_station ALTER COLUMN station_type DROP DEFAULT;
+ALTER TABLE IF EXISTS app_user.pump_station DROP COLUMN IF EXISTS station_type;
+
+-- 3. 坐标索引名改回
+ALTER INDEX IF EXISTS app_user.idx_pump_station_lon_lat RENAME TO idx_water_pump_station_lon_lat;
+
+-- 4. 主键序列名改回(幂等)
+DO $$
+BEGIN
+    IF EXISTS (SELECT 1
+                 FROM pg_class c
+                 JOIN pg_namespace n ON n.oid = c.relnamespace
+                WHERE n.nspname = 'app_user'
+                  AND c.relname = 'pump_station_id_seq') THEN
+        ALTER SEQUENCE app_user.pump_station_id_seq RENAME TO water_pump_station_id_seq;
+    END IF;
+END $$;
+
+-- 5. 表名改回
+ALTER TABLE IF EXISTS app_user.pump_station RENAME TO water_pump_station;
+COMMENT ON TABLE app_user.water_pump_station IS '供水泵站信息表';
+
+-- 回滚后校验:
+-- SELECT to_regclass('app_user.water_pump_station') AS old_table_back,
+--        to_regclass('app_user.pump_station')        AS new_table_gone;
+-- 代码层需同步 git 回退到本次迁移前的版本(实体 @TableName、Mapper XML、过滤逻辑等)。