Browse Source

feat(应急调度): 模块重构落地与新增能力(18→6 表收尾/SSE 通知/看板/版本快照)

- 18→6 表重构收尾:删除旧拆分 Controller/Domain(67 个),合并为 DisEventController/DisPlanController 统一端点
- 新增调度首页看板接口 /api/dispatch/event/home(统计卡/GIS 点位/处置中事件/类型与等级分布)
- 新增催办系统消息:dispatch_notify 服务与 /notify/my|read|readAll 接口,勾选「系统消息」按处置人落站内通知
- 新增轻量 SSE 实时通知 /sse/connect(URL token 鉴权),催办实时推送;SecurityConfig 放行该端点
- 预案版本快照补全:版本带附件(attach_name/attach_path),正文随保存/发布入快照
- 分级标准新增主键冲突修复:取号锁内显式发号 id=max(id)+1(规避 KES identity 序列未对齐)
- 事件解除佐证:dis_event before_url/after_url 随解除写入并回显
null 5 ngày trước cách đây
mục cha
commit
5bc0c6e455
100 tập tin đã thay đổi với 4528 bổ sung và 2254 xóa
  1. 0 0
      _apicheck.ps1
  2. 0 0
      _beanscan.ps1
  3. 93 0
      pipe-network-service/sql/dispatch_fix_all_identity.sql
  4. 25 0
      pipe-network-service/sql/dispatch_fix_grade_identity.sql
  5. 41 0
      pipe-network-service/sql/dispatch_notify.sql
  6. 36 0
      pipe-network-service/sql/dispatch_plan_library_seed.sql
  7. 16 0
      pipe-network-service/sql/dispatch_plan_version_attach.sql
  8. 562 0
      pipe-network-service/sql/dispatch_refactor.sql
  9. 239 0
      pipe-network-service/sql/dispatch_refactor_drop.sql
  10. 165 0
      pipe-network-service/sql/dispatch_refactor_fix_number.sql
  11. 460 0
      pipe-network-service/sql/dispatch_refactor_migrate.sql
  12. 15 0
      pipe-network-service/sql/dispatch_release_attach.sql
  13. 3 0
      pipe-network-service/sql/early_warning_system.sql
  14. 276 0
      pipe-network-service/sql/migration_dispatch_user_id.sql
  15. 0 0
      pipe-network-service/sql/work_order_split_image_fields.sql
  16. 1 2
      pipe-network-service/zksy-admin/src/main/java/com/zksy/ZksyApplication.java
  17. 156 55
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventController.java
  18. 39 10
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventGradeController.java
  19. 0 65
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventPlanController.java
  20. 0 74
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventRecordController.java
  21. 0 81
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventReportController.java
  22. 0 97
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventTrackController.java
  23. 0 89
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventTransferController.java
  24. 0 89
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventUrgeController.java
  25. 0 73
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventVerifyController.java
  26. 0 105
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisPlanApprovalController.java
  27. 0 72
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisPlanApprovalStepController.java
  28. 0 75
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisPlanCategoryController.java
  29. 140 0
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisPlanController.java
  30. 61 0
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DispatchNotifyController.java
  31. 56 0
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/SseController.java
  32. 4 0
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/GasAlarmController.java
  33. 8 4
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/GasPipeNetworkController.java
  34. 6 1
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/GasRepairRecordController.java
  35. 1 0
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/GisController.java
  36. 2 0
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/PressureAlarmController.java
  37. 2 1
      pipe-network-service/zksy-framework/src/main/java/com/zksy/framework/config/SecurityConfig.java
  38. 7 1
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/domain/PipeNetworkBase.java
  39. 26 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/PipeNetworkBaseService.java
  40. 6 6
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/GasDashboardServiceImpl.java
  41. 56 1
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/PipeNetworkBaseServiceImpl.java
  42. 108 55
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEvent.java
  43. 9 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventAssess.java
  44. 47 11
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventGrade.java
  45. 95 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventLog.java
  46. 0 78
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventPlan.java
  47. 0 66
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventRecord.java
  48. 0 74
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventReport.java
  49. 0 81
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventTrack.java
  50. 0 55
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventTrackLog.java
  51. 0 84
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventTransfer.java
  52. 0 52
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventTransferLog.java
  53. 0 67
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventUrge.java
  54. 0 49
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventUrgeDetail.java
  55. 0 85
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventVerify.java
  56. 126 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisPlan.java
  57. 0 71
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisPlanApproval.java
  58. 0 55
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisPlanApprovalStep.java
  59. 0 50
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisPlanCategory.java
  60. 3 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisPlanLibrary.java
  61. 69 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisPlanLog.java
  62. 3 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisPlanMatchRecord.java
  63. 75 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisPlanVersion.java
  64. 58 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DispatchNotify.java
  65. 36 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisDeptOptionVO.java
  66. 18 8
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisEventGradePageDTO.java
  67. 36 23
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisEventPageDTO.java
  68. 0 34
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisEventPlanPageDTO.java
  69. 0 40
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisEventRecordPageDTO.java
  70. 120 65
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisEventVO.java
  71. 77 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisHomeVO.java
  72. 0 28
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisPlanApprovalPageDTO.java
  73. 0 25
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisPlanApprovalStepPageDTO.java
  74. 0 25
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisPlanCategoryPageDTO.java
  75. 38 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisUserOptionVO.java
  76. 30 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EnumOptionVO.java
  77. 14 16
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventLogPageDTO.java
  78. 38 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventReleaseDTO.java
  79. 0 28
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventReportDTO.java
  80. 0 37
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventReportPageInDTO.java
  81. 65 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventSaveDTO.java
  82. 43 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventTrackDTO.java
  83. 32 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventTransferDTO.java
  84. 0 40
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventTransferPageInDTO.java
  85. 41 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventUrgeDTO.java
  86. 12 11
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventUrgePageDTO.java
  87. 84 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventUrgeRowVO.java
  88. 56 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventVerifyDTO.java
  89. 0 40
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventVerifyPageInDTO.java
  90. 23 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/PlanApproveDTO.java
  91. 49 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/PlanPageDTO.java
  92. 58 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/PlanSaveDTO.java
  93. 57 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/PlanVersionVO.java
  94. 40 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/SseMessageDto.java
  95. 79 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/enums/EventLogOperTypeEnum.java
  96. 149 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/enums/EventStatusEnum.java
  97. 49 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/enums/GradeStatusEnum.java
  98. 50 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/enums/PlanLogOperTypeEnum.java
  99. 76 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/enums/PlanStatusEnum.java
  100. 63 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/enums/PlanVersionStatusEnum.java

+ 0 - 0
_apicheck.ps1


+ 0 - 0
_beanscan.ps1


+ 93 - 0
pipe-network-service/sql/dispatch_fix_all_identity.sql

@@ -0,0 +1,93 @@
+-- =====================================================================
+-- dispatch_fix_all_identity.sql
+-- 一次性对齐 app_user 下所有 dis_* 表的主键自增序列
+-- ---------------------------------------------------------------------
+-- 背景:调度模块各表(dis_event / dis_event_log / dis_plan / dis_plan_version /
+--       dis_event_grade / dis_event_assess / dis_plan_log / dis_plan_library /
+--       dis_plan_match_record 等)由 MySQL 迁移至 Kingbase,迁移数据时带显式 id
+--       插入,GENERATED BY DEFAULT AS IDENTITY 的序列不会自动推进;此后不带
+--       id 的 INSERT 会从序列起点发号(如发 0),撞已存在行,报主键冲突。
+-- 修复:遍历 app_user 下所有 dis_% 表,把 id 列关联的序列 setval 到 max(id)
+--       (setval(seq, max, true) → 下一个发号 = max+1)。幂等可重复执行。
+-- 执行方式:KingbaseES 客户端。逐段执行;某段失败先 ROLLBACK 再继续下一段。
+-- =====================================================================
+
+-- ---------------------------------------------------------------------
+-- 步骤 0(可选):先看各表现状——max(id) 与序列名,便于核对
+-- ---------------------------------------------------------------------
+SELECT c.relname AS 表名,
+       (SELECT COALESCE(max(id), 0) FROM app_user.dis_event)  AS 说明_另查,
+       d.objid::regclass::text AS 关联序列
+  FROM pg_class c
+  JOIN pg_namespace n ON n.oid = c.relnamespace
+  LEFT JOIN pg_depend d
+    ON d.refobjid = c.oid
+   AND d.classid = 'pg_class'::regclass
+   AND d.deptype IN ('a', 'i')
+  LEFT JOIN pg_attribute a
+    ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid AND a.attname = 'id'
+ WHERE n.nspname = 'app_user'
+   AND c.relname LIKE 'dis_%'
+   AND c.relkind = 'r';
+
+-- ---------------------------------------------------------------------
+-- 步骤 1(首选):自动遍历所有 dis_% 表,校准 id 序列
+--   执行成功会逐表 NOTICE 输出「表名 -> 序列名 (next=...)」
+-- ---------------------------------------------------------------------
+DO $$
+DECLARE
+  t          record;
+  seqname    text;
+  maxid      bigint;
+BEGIN
+  FOR t IN
+    SELECT c.relname AS table_name
+      FROM pg_class c
+      JOIN pg_namespace n ON n.oid = c.relnamespace
+     WHERE n.nspname = 'app_user'
+       AND c.relname LIKE 'dis\_%' ESCAPE '\'
+       AND c.relkind = 'r'
+  LOOP
+    EXECUTE format('SELECT COALESCE(max(id), 0) FROM app_user.%I', t.table_name) INTO maxid;
+
+    -- 找 id 列关联的序列(serial=deptype a / identity=deptype i)
+    SELECT d.objid::regclass::text INTO seqname
+      FROM pg_depend d
+      JOIN pg_attribute a ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid
+      JOIN pg_class sc ON sc.oid = d.objid AND sc.relkind = 'S'
+      JOIN pg_namespace sn ON sn.oid = sc.relnamespace AND sn.nspname = 'app_user'
+     WHERE d.refobjid = format('app_user.%I', t.table_name)::regclass
+       AND a.attname = 'id'
+       AND d.classid = 'pg_class'::regclass
+       AND d.deptype IN ('a', 'i')
+     LIMIT 1;
+
+    IF seqname IS NOT NULL THEN
+      EXECUTE format('SELECT setval(%L::regclass, %s, true)', seqname, GREATEST(maxid, 1));
+      RAISE NOTICE '% -> % (next=%)', t.table_name, seqname, maxid + 1;
+    ELSE
+      RAISE NOTICE '% -> 未找到关联序列,请用步骤 2 手工处理', t.table_name;
+    END IF;
+  END LOOP;
+END $$;
+
+-- ---------------------------------------------------------------------
+-- 步骤 2(保底):步骤 1 中提示「未找到关联序列」的表,手工对齐
+--   先查该表 max(id),把下面的 <n> 替换为 max(id)(RESTART WITH 生效值即下一个发号):
+--   ALTER TABLE app_user.表名 ALTER COLUMN id RESTART WITH <max(id)>;
+--
+-- 例(编号仅为示例,以步骤 0 查到的 max(id) 为准):
+--   ALTER TABLE app_user.dis_event        ALTER COLUMN id RESTART WITH 100;
+--   ALTER TABLE app_user.dis_event_log    ALTER COLUMN id RESTART WITH 300;
+--   ALTER TABLE app_user.dis_event_assess ALTER COLUMN id RESTART WITH 10;
+-- ---------------------------------------------------------------------
+
+-- ---------------------------------------------------------------------
+-- 步骤 3:核验——逐表 nextval 应等于 max(id) + 1(多表循环核验可重复步骤 0)
+-- 示例:
+--   SELECT COALESCE(max(id), 0) AS max_id,
+--          nextval(pg_get_serial_sequence('app_user.dis_event', 'id')) AS next_id
+--     FROM app_user.dis_event;
+-- 注意:如 pg_get_serial_sequence 在 KES 上返回空,则改用步骤 0 查到的序列名:
+--   SELECT nextval('app_user.dis_event_id_seq');
+-- ---------------------------------------------------------------------

+ 25 - 0
pipe-network-service/sql/dispatch_fix_grade_identity.sql

@@ -0,0 +1,25 @@
+-- =====================================================================
+-- dispatch_fix_grade_identity.sql
+-- 修复 dis_event_grade 主键序列未对齐导致的新增报错
+-- ---------------------------------------------------------------------
+-- 现象:新增分级标准报「重复键违反唯一约束 dis_event_grade_pkey,
+--       键值 (id)=(0) 已经存在」,INSERT 语句本身未包含 id 列。
+-- 根因:分级标准数据由旧表带显式 id 迁移而来,identity 序列未同步推进
+--       (仍在 0 附近),INSERT 不带 id 时自动生成 0,撞已存在行。
+-- 修复:把 identity 序列校准为表内最大 id(幂等,可重复执行)。
+-- 执行方式:KingbaseES 客户端执行第 1 段即可;第 2 段用于核验。
+-- =====================================================================
+
+-- 1) 校准序列:从 max(id) 之后继续发号
+SELECT setval(
+    pg_get_serial_sequence('app_user.dis_event_grade', 'id'),
+    GREATEST((SELECT COALESCE(max(id), 1) FROM app_user.dis_event_grade), 1)
+);
+
+-- 2) 核验:nextval 应大于 max_id
+SELECT COALESCE(max(id), 0)  AS max_id,
+       nextval(pg_get_serial_sequence('app_user.dis_event_grade', 'id')) AS next_id
+  FROM app_user.dis_event_grade;
+
+-- 附:同批次从旧表迁移过的其他调度表可顺带自检(把表名换掉执行第 1 段):
+--   dis_event / dis_event_log / dis_plan / dis_plan_version / dis_event_assess

+ 41 - 0
pipe-network-service/sql/dispatch_notify.sql

@@ -0,0 +1,41 @@
+-- =====================================================================
+-- dispatch_notify.sql
+-- 应急与指挥调度:催办系统消息通知表
+-- ---------------------------------------------------------------------
+-- 背景:工作催办勾选「系统消息」时,系统向事件的责任处置人推送一条
+-- 站内催办消息(催办内容 + 事件上下文),用户在调度首页铃铛面板查看。
+-- 设计要点:
+--   1. 消息按 user_id 隔离,read_flag 区分未读/已读;
+--   2. event_id / event_no 冗余事件上下文,通知本身不阻塞事件生命周期;
+--   3. 幂等:CREATE TABLE IF NOT EXISTS / CREATE INDEX IF NOT EXISTS,
+--      可重复执行。
+-- 执行方式:KingbaseES 客户端逐段执行(注意 || 不是拼接符的方言红线)。
+-- =====================================================================
+
+CREATE TABLE IF NOT EXISTS app_user.dispatch_notify (
+    id           int8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+    user_id      int8       NOT NULL,
+    event_id     int8,
+    event_no     varchar(64),
+    title        varchar(128) NOT NULL,
+    content      text,
+    urging_user  varchar(64),
+    read_flag    int2       NOT NULL DEFAULT 0,
+    create_time  timestamp  NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    del_flag     char(1)    NOT NULL DEFAULT '0'
+);
+
+COMMENT ON TABLE  app_user.dispatch_notify              IS '催办系统消息通知表:工作催办勾选「系统消息」时按用户落一条站内消息';
+COMMENT ON COLUMN app_user.dispatch_notify.id           IS '主键';
+COMMENT ON COLUMN app_user.dispatch_notify.user_id      IS '接收人用户ID(sys_user.user_id)';
+COMMENT ON COLUMN app_user.dispatch_notify.event_id     IS '关联事件ID(dis_event.id)';
+COMMENT ON COLUMN app_user.dispatch_notify.event_no     IS '关联事件编号(冗余)';
+COMMENT ON COLUMN app_user.dispatch_notify.title        IS '消息标题(如:催办提醒:SJ20261015...)';
+COMMENT ON COLUMN app_user.dispatch_notify.content      IS '消息内容(= 催办内容)';
+COMMENT ON COLUMN app_user.dispatch_notify.urging_user  IS '催办人姓名快照';
+COMMENT ON COLUMN app_user.dispatch_notify.read_flag    IS '已读标记:0未读 1已读';
+COMMENT ON COLUMN app_user.dispatch_notify.create_time  IS '推送时间';
+COMMENT ON COLUMN app_user.dispatch_notify.del_flag     IS '删除标志:0正常 1删除';
+
+CREATE INDEX IF NOT EXISTS idx_dispatch_notify_user_read ON app_user.dispatch_notify(user_id, read_flag);
+CREATE INDEX IF NOT EXISTS idx_dispatch_notify_event     ON app_user.dispatch_notify(event_id);

+ 36 - 0
pipe-network-service/sql/dispatch_plan_library_seed.sql

@@ -0,0 +1,36 @@
+-- =====================================================================
+-- dispatch_plan_library_seed.sql
+-- 预案模板库种子数据(供「新建预案」时选择模板,预填名称与章节正文)
+-- ---------------------------------------------------------------------
+-- 幂等:按 plan_name NOT EXISTS 判断,可重复执行。
+-- =====================================================================
+
+-- TP-001 通用应急预案模板
+INSERT INTO app_user.dis_plan_library
+    (plan_name, event_type, event_level, event_region, responsible_unit, topic, plan_summary, plan_content, create_by, create_time, del_flag)
+SELECT '通用应急预案模板', '综合', 'Ⅲ级', '', '', '综合专题',
+       '适用于各类一般突发事件的通用处置框架。章节:总则 / 组织体系 / 预防预警 / 应急响应。',
+       E'第一章 总则\n1.1 编制目的\n1.2 编制依据\n1.3 适用范围\n1.4 工作原则\n\n第二章 组织体系\n2.1 应急指挥部\n2.2 成员单位职责\n2.3 现场处置组\n\n第三章 预防预警\n3.1 风险辨识\n3.2 预警发布\n\n第四章 应急响应\n4.1 响应分级\n4.2 处置流程\n4.3 信息报告\n\n第五章 后期处置\n5.1 善后处理\n5.2 调查评估',
+       'admin', CURRENT_TIMESTAMP, '0'
+  FROM dual
+ WHERE NOT EXISTS (SELECT 1 FROM app_user.dis_plan_library WHERE plan_name = '通用应急预案模板' AND del_flag = '0');
+
+-- TP-004 燃气专项预案模板
+INSERT INTO app_user.dis_plan_library
+    (plan_name, event_type, event_level, event_region, responsible_unit, topic, plan_summary, plan_content, create_by, create_time, del_flag)
+SELECT '燃气专项预案模板', '燃气', 'Ⅱ级', '', '燃气公司', '燃气专题',
+       '燃气泄漏/爆燃等突发事件的专项处置框架。章节:总则 / 危险源辨识 / 响应分级 / 处置程序。',
+       E'第一章 总则\n1.1 编制目的\n1.2 适用范围\n\n第二章 危险源辨识\n2.1 燃气泄漏\n2.2 管道占压\n2.3 第三方施工破坏\n\n第三章 响应分级\n3.1 Ⅰ级(爆燃)\n3.2 Ⅱ级(大面积泄漏)\n3.3 Ⅲ级(一般泄漏)\n\n第四章 处置程序\n4.1 关阀断气\n4.2 警戒疏散\n4.3 抢修作业\n4.4 恢复供气',
+       'admin', CURRENT_TIMESTAMP, '0'
+  FROM dual
+ WHERE NOT EXISTS (SELECT 1 FROM app_user.dis_plan_library WHERE plan_name = '燃气专项预案模板' AND del_flag = '0');
+
+-- TP-006 供水专项预案模板
+INSERT INTO app_user.dis_plan_library
+    (plan_name, event_type, event_level, event_region, responsible_unit, topic, plan_summary, plan_content, create_by, create_time, del_flag)
+SELECT '供水专项预案模板', '供水', 'Ⅱ级', '', '供水公司', '供水专题',
+       '供水管网爆管/水质污染等突发事件的专项处置框架。章节:总则 / 组织机构 / 响应分级 / 处置程序 / 后期处置。',
+       E'第一章 总则\n1.1 编制目的\n1.2 适用范围\n\n第二章 组织机构\n2.1 应急指挥部\n2.2 抢修队伍\n\n第三章 响应分级\n3.1 Ⅰ级(DN800 以上爆管)\n3.2 Ⅱ级(区域停水)\n3.3 Ⅲ级(一般漏损)\n\n第四章 处置程序\n4.1 关阀降压\n4.2 交通配合\n4.3 抢修换管\n4.4 恢复供水\n\n第五章 后期处置\n5.1 路面恢复\n5.2 水质复检\n5.3 总结评估',
+       'admin', CURRENT_TIMESTAMP, '0'
+  FROM dual
+ WHERE NOT EXISTS (SELECT 1 FROM app_user.dis_plan_library WHERE plan_name = '供水专项预案模板' AND del_flag = '0');

+ 16 - 0
pipe-network-service/sql/dispatch_plan_version_attach.sql

@@ -0,0 +1,16 @@
+-- =====================================================================
+-- dispatch_plan_version_attach.sql
+-- 预案版本快照补充附件字段
+-- ---------------------------------------------------------------------
+-- 背景:版本是预案的快照日志,但快照里只有正文(content)没有附件,
+-- 正文编辑入口此前也未开放,导致版本流程不完整。本次:
+--   1. dis_plan_version 增加 attach_name / attach_path(随版本落快照);
+--   2. 正文由前端编辑弹窗录入,随 savePlan 传入并进入版本快照。
+-- 幂等:ADD COLUMN IF NOT EXISTS,可重复执行。
+-- =====================================================================
+
+ALTER TABLE app_user.dis_plan_version ADD COLUMN IF NOT EXISTS attach_name varchar(255);
+ALTER TABLE app_user.dis_plan_version ADD COLUMN IF NOT EXISTS attach_path  text;
+
+COMMENT ON COLUMN app_user.dis_plan_version.attach_name IS '附件名称快照';
+COMMENT ON COLUMN app_user.dis_plan_version.attach_path IS '附件URL快照(MinIO 直链)';

+ 562 - 0
pipe-network-service/sql/dispatch_refactor.sql

@@ -0,0 +1,562 @@
+-- =====================================================================
+-- 应急与指挥调度模块(dispatch)改造脚本 —— 建表 / 加列 / 字典 seed
+-- 数据库:KingbaseES(PG 兼容模式),业务 schema = app_user
+--
+-- 目的:
+--   1) 事件域由「按操作拆 9 张表」收敛为 dis_event(主表)+ dis_event_log(日志表)
+--   2) 预案域由 dis_event_plan + dis_plan_approval 两套收敛为
+--      dis_plan + dis_plan_version + dis_plan_log
+--   3) dis_event_grade 字段字典化并补业务编号列
+--   4) 事件来源/渠道/管网类型/类别/等级/区域、预案类型/等级等全部落 sys_dict_data
+--
+-- 设计口径(2026-10-14 与业务确认):
+--   · 事件等级:3 级 —— Ⅰ级(紧急) / Ⅱ级(较大) / Ⅲ级(一般)(原型口径)
+--   · 事件类别:保留两级 —— event_type(管网类型) + event_category(故障类别)
+--   · 事件状态:5 态 —— 0 待核实 / 1 已核实 / 2 处置中 / 3 已解除 / 4 不实事件
+--     存量「待受理」归 0、「已关闭」归 3
+--
+-- 幂等性:本脚本可重复执行。
+--   建表用 CREATE TABLE IF NOT EXISTS;加列用 ADD COLUMN IF NOT EXISTS;
+--   字典先 DELETE 同 dict_type 再 INSERT。
+--
+-- ⚠️ 本脚本只做「新增」,不删除任何旧表/旧列/旧数据。
+--    废弃旧表与旧列的 DROP 语句放在独立的 dispatch_refactor_drop.sql,
+--    确认新结构运行正常后再单独执行。
+-- =====================================================================
+
+BEGIN;
+
+-- =====================================================================
+-- 一、事件域
+-- =====================================================================
+
+-- ---------------------------------------------------------------------
+-- 1.1 dis_event 事件主表(全生命周期当前值)
+--     表若已存在(旧版),下列 ADD COLUMN 只会补齐缺失列;
+--     旧列 source / category / level 的搬迁见第四节「存量数据迁移」。
+-- ---------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS app_user.dis_event (
+    id               int8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+    event_no         varchar(32),
+    description      text,
+    event_source     varchar(20),
+    channel          varchar(20),
+    location         varchar(255),
+    longitude        numeric(12,6),
+    latitude         numeric(12,6),
+    happen_time      timestamp,
+    report_time      timestamp,
+    reporter         varchar(64),
+    reporter_phone   varchar(20),
+    event_type       varchar(20),
+    event_category   varchar(20),
+    event_level      varchar(20),
+    event_region     varchar(20),
+    status           int2 NOT NULL DEFAULT 0,
+    verify_result    int2,
+    verifier         varchar(64),
+    verify_time      timestamp,
+    verify_opinion   varchar(500),
+    handler          varchar(64),
+    handler_phone    varchar(20),
+    department       varchar(128),
+    dispose_time     timestamp,
+    release_time     timestamp,
+    release_reason   varchar(20),
+    release_note     varchar(500),
+    last_visit_time  timestamp,
+    next_visit_time  timestamp,
+    need_upgrade     int2,
+    plan_id          int8,
+    loss_amount      numeric(14,2),
+    create_by        varchar(64),
+    create_time      timestamp DEFAULT CURRENT_TIMESTAMP,
+    update_by        varchar(64),
+    update_time      timestamp DEFAULT CURRENT_TIMESTAMP,
+    del_flag         char(1) DEFAULT '0'
+);
+
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS event_no        varchar(32);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS description     text;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS event_source    varchar(20);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS channel         varchar(20);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS location        varchar(255);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS longitude       numeric(12,6);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS latitude        numeric(12,6);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS happen_time     timestamp;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS report_time     timestamp;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS reporter        varchar(64);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS reporter_phone  varchar(20);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS event_type      varchar(20);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS event_category  varchar(20);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS event_level     varchar(20);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS event_region    varchar(20);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS status          int2 DEFAULT 0;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS verify_result   int2;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS verifier        varchar(64);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS verify_time     timestamp;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS verify_opinion  varchar(500);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS handler         varchar(64);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS handler_phone   varchar(20);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS department      varchar(128);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS dispose_time    timestamp;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS release_time    timestamp;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS release_reason  varchar(20);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS release_note    varchar(500);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS last_visit_time timestamp;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS next_visit_time timestamp;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS need_upgrade    int2;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS plan_id         int8;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS loss_amount     numeric(14,2);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS create_by       varchar(64);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS create_time     timestamp DEFAULT CURRENT_TIMESTAMP;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS update_by       varchar(64);
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS update_time     timestamp DEFAULT CURRENT_TIMESTAMP;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS del_flag        char(1) DEFAULT '0';
+
+COMMENT ON TABLE  app_user.dis_event IS '应急事件主表:一个事件一行,承载全生命周期当前值;过程留痕见 dis_event_log';
+COMMENT ON COLUMN app_user.dis_event.id              IS '主键ID,int8 自增';
+COMMENT ON COLUMN app_user.dis_event.event_no        IS '事件编号(业务序号:SJ+yyyyMMddHHmmss+4位随机,后端保存前生成,普通更新不可修改)';
+COMMENT ON COLUMN app_user.dis_event.description     IS '事件描述';
+COMMENT ON COLUMN app_user.dis_event.event_source    IS '事件来源:字典 dis_event_source';
+COMMENT ON COLUMN app_user.dis_event.channel         IS '来源渠道:字典 dis_event_channel';
+COMMENT ON COLUMN app_user.dis_event.location        IS '发生地点';
+COMMENT ON COLUMN app_user.dis_event.longitude       IS '经度(发生地点地理编码得到)';
+COMMENT ON COLUMN app_user.dis_event.latitude        IS '纬度(发生地点地理编码得到)';
+COMMENT ON COLUMN app_user.dis_event.happen_time     IS '发生时间';
+COMMENT ON COLUMN app_user.dis_event.report_time     IS '接报时间';
+COMMENT ON COLUMN app_user.dis_event.reporter        IS '接报人';
+COMMENT ON COLUMN app_user.dis_event.reporter_phone  IS '上报人联系电话';
+COMMENT ON COLUMN app_user.dis_event.event_type      IS '管网类型:字典 dis_event_type(供水/燃气/排水/其他)';
+COMMENT ON COLUMN app_user.dis_event.event_category  IS '事件类别:字典 dis_event_category(管道泄漏/路面积水/设施损坏/管道堵塞)';
+COMMENT ON COLUMN app_user.dis_event.event_level     IS '事件等级:字典 dis_event_level(1-Ⅰ级紧急 2-Ⅱ级较大 3-Ⅲ级一般)';
+COMMENT ON COLUMN app_user.dis_event.event_region    IS '发生区域:字典 dis_event_region';
+COMMENT ON COLUMN app_user.dis_event.status          IS '事件状态:EventStatusEnum —— 0-待核实 1-已核实 2-处置中 3-已解除 4-不实事件;终态为 3、4';
+COMMENT ON COLUMN app_user.dis_event.verify_result   IS '核实结论:0-真实 1-不实;仅 status in (1,4) 时有值';
+COMMENT ON COLUMN app_user.dis_event.verifier        IS '核验人';
+COMMENT ON COLUMN app_user.dis_event.verify_time     IS '核实时间';
+COMMENT ON COLUMN app_user.dis_event.verify_opinion  IS '核实意见;判定为不实时填核销原因(字典 dis_event_false_reason)';
+COMMENT ON COLUMN app_user.dis_event.handler         IS '责任处置人(后续工作催办的催办对象)';
+COMMENT ON COLUMN app_user.dis_event.handler_phone   IS '责任处置人联系电话';
+COMMENT ON COLUMN app_user.dis_event.department      IS '责任单位 / 转接接收部门';
+COMMENT ON COLUMN app_user.dis_event.dispose_time    IS '开始处置时间(status 1→2 时写入)';
+COMMENT ON COLUMN app_user.dis_event.release_time    IS '解除时间(status 2→3 时写入)';
+COMMENT ON COLUMN app_user.dis_event.release_reason  IS '解除原因:字典 dis_event_release_reason';
+COMMENT ON COLUMN app_user.dis_event.release_note    IS '解除说明 / 处置结果补充';
+COMMENT ON COLUMN app_user.dis_event.last_visit_time IS '最近回访时间(取最近一次处置跟踪的跟踪时间)';
+COMMENT ON COLUMN app_user.dis_event.next_visit_time IS '下次回访时间(取最近一次处置跟踪填写的值)';
+COMMENT ON COLUMN app_user.dis_event.need_upgrade    IS '是否需要升级:字典 dis_yes_no(0-否 1-是),取最近一次处置跟踪填写的值';
+COMMENT ON COLUMN app_user.dis_event.plan_id         IS '应用预案ID(dis_plan.id)';
+COMMENT ON COLUMN app_user.dis_event.loss_amount     IS '直接经济损失(万元)';
+COMMENT ON COLUMN app_user.dis_event.del_flag        IS '删除标志(0=正常 2=删除)';
+
+CREATE UNIQUE INDEX IF NOT EXISTS uk_dis_event_no       ON app_user.dis_event(event_no);
+CREATE INDEX IF NOT EXISTS idx_dis_event_status         ON app_user.dis_event(status);
+CREATE INDEX IF NOT EXISTS idx_dis_event_report_time    ON app_user.dis_event(report_time DESC);
+CREATE INDEX IF NOT EXISTS idx_dis_event_source         ON app_user.dis_event(event_source);
+CREATE INDEX IF NOT EXISTS idx_dis_event_level          ON app_user.dis_event(event_level);
+CREATE INDEX IF NOT EXISTS idx_dis_event_type           ON app_user.dis_event(event_type);
+
+-- ---------------------------------------------------------------------
+-- 1.2 dis_event_log 事件日志表
+--     一张表承载全部环节留痕:接报登记 / 初判核实 / 不实核销 / 电话转接 /
+--     上报报送 / 开始处置 / 处置跟踪 / 事件解除 / 工作催办 / 信息修改。
+--     替代旧 dis_event_transfer_log + dis_event_track_log + dis_event_urge_detail。
+--     日志完全由业务代码写入,不使用触发器/自定义函数。
+-- ---------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS app_user.dis_event_log (
+    id              int8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+    event_id        int8 NOT NULL,
+    event_no        varchar(32),
+    oper_type       int2 NOT NULL CHECK (oper_type BETWEEN 0 AND 9),
+    oper_desc       text,
+    status_from     int2,
+    status_to       int2,
+    targets         varchar(500),
+    receiver_dept   varchar(128),
+    receiver_phone  varchar(20),
+    method          varchar(20),
+    urge_result     varchar(20),
+    trend           varchar(20),
+    need_upgrade    int2,
+    next_visit_time timestamp,
+    attach_url      text,
+    oper_user_id    int8,
+    oper_user_name  varchar(128),
+    oper_time       timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+ALTER TABLE app_user.dis_event_log ADD COLUMN IF NOT EXISTS event_no        varchar(32);
+ALTER TABLE app_user.dis_event_log ADD COLUMN IF NOT EXISTS oper_desc       text;
+ALTER TABLE app_user.dis_event_log ADD COLUMN IF NOT EXISTS status_from     int2;
+ALTER TABLE app_user.dis_event_log ADD COLUMN IF NOT EXISTS status_to       int2;
+ALTER TABLE app_user.dis_event_log ADD COLUMN IF NOT EXISTS targets         varchar(500);
+ALTER TABLE app_user.dis_event_log ADD COLUMN IF NOT EXISTS receiver_dept   varchar(128);
+ALTER TABLE app_user.dis_event_log ADD COLUMN IF NOT EXISTS receiver_phone  varchar(20);
+ALTER TABLE app_user.dis_event_log ADD COLUMN IF NOT EXISTS method          varchar(20);
+ALTER TABLE app_user.dis_event_log ADD COLUMN IF NOT EXISTS urge_result     varchar(20);
+ALTER TABLE app_user.dis_event_log ADD COLUMN IF NOT EXISTS trend           varchar(20);
+ALTER TABLE app_user.dis_event_log ADD COLUMN IF NOT EXISTS need_upgrade    int2;
+ALTER TABLE app_user.dis_event_log ADD COLUMN IF NOT EXISTS next_visit_time timestamp;
+ALTER TABLE app_user.dis_event_log ADD COLUMN IF NOT EXISTS attach_url      text;
+ALTER TABLE app_user.dis_event_log ADD COLUMN IF NOT EXISTS oper_user_id    int8;
+ALTER TABLE app_user.dis_event_log ADD COLUMN IF NOT EXISTS oper_user_name  varchar(128);
+ALTER TABLE app_user.dis_event_log ADD COLUMN IF NOT EXISTS oper_time       timestamp DEFAULT CURRENT_TIMESTAMP;
+
+COMMENT ON TABLE  app_user.dis_event_log IS '应急事件操作日志:一个事件 N 行,记录全部环节动作与状态变更轨迹;仅由业务代码写入';
+COMMENT ON COLUMN app_user.dis_event_log.event_id        IS '关联 dis_event.id';
+COMMENT ON COLUMN app_user.dis_event_log.event_no        IS '事件编号(冗余,便于按编号单独查日志)';
+COMMENT ON COLUMN app_user.dis_event_log.oper_type       IS 'EventLogOperTypeEnum:0-接报登记 1-初判核实 2-不实核销 3-电话转接 4-上报报送 5-开始处置 6-处置跟踪 7-事件解除 8-工作催办 9-信息修改';
+COMMENT ON COLUMN app_user.dis_event_log.oper_desc       IS '操作说明/备注:跟踪内容、催办内容、核实意见、解除说明等';
+COMMENT ON COLUMN app_user.dis_event_log.status_from     IS '变更前事件状态;不改状态的动作为 NULL';
+COMMENT ON COLUMN app_user.dis_event_log.status_to       IS '变更后事件状态;不改状态的动作为 NULL';
+COMMENT ON COLUMN app_user.dis_event_log.targets         IS '操作对象:转接/报送部门名称(多个逗号分隔);工作催办时填目标接收人';
+COMMENT ON COLUMN app_user.dis_event_log.receiver_dept   IS '催办接收部门(oper_type=8)';
+COMMENT ON COLUMN app_user.dis_event_log.receiver_phone  IS '催办接收人电话(oper_type=8)';
+COMMENT ON COLUMN app_user.dis_event_log.method          IS '催办方式:字典 dis_event_urge_method(oper_type=8)';
+COMMENT ON COLUMN app_user.dis_event_log.urge_result     IS '催办结果:字典 dis_event_urge_result(oper_type=8)';
+COMMENT ON COLUMN app_user.dis_event_log.trend           IS '态势评估:字典 dis_event_trend(oper_type=6 处置跟踪)';
+COMMENT ON COLUMN app_user.dis_event_log.need_upgrade    IS '是否需要升级:字典 dis_yes_no(oper_type=6 处置跟踪)';
+COMMENT ON COLUMN app_user.dis_event_log.next_visit_time IS '下次回访时间(oper_type=6 处置跟踪)';
+COMMENT ON COLUMN app_user.dis_event_log.attach_url      IS '附件URL,多个使用逗号分隔';
+COMMENT ON COLUMN app_user.dis_event_log.oper_user_id    IS '操作人ID,系统操作可为空';
+COMMENT ON COLUMN app_user.dis_event_log.oper_user_name  IS '操作人昵称(sys_user.nick_name);自动操作使用系统名称';
+COMMENT ON COLUMN app_user.dis_event_log.oper_time       IS '操作时间';
+
+CREATE INDEX IF NOT EXISTS idx_dis_event_log_event ON app_user.dis_event_log(event_id, oper_time, id);
+CREATE INDEX IF NOT EXISTS idx_dis_event_log_type  ON app_user.dis_event_log(oper_type);
+CREATE INDEX IF NOT EXISTS idx_dis_event_log_no    ON app_user.dis_event_log(event_no);
+
+-- =====================================================================
+-- 二、预案域(编辑与审批合并为同一套数据)
+-- =====================================================================
+
+-- ---------------------------------------------------------------------
+-- 2.1 dis_plan 预案主表
+-- ---------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS app_user.dis_plan (
+    id             int8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+    plan_no        varchar(32),
+    plan_name      varchar(200) NOT NULL,
+    plan_type      varchar(20),
+    plan_level     varchar(20),
+    compile_unit   varchar(128),
+    publish_unit   varchar(128),
+    publish_date   varchar(20),
+    attach_name    varchar(255),
+    attach_path    varchar(1000),
+    is_template    int2 NOT NULL DEFAULT 0,
+    status         int2 NOT NULL DEFAULT 0,
+    version        varchar(20),
+    content        text,
+    approve_result varchar(20),
+    approve_time   timestamp,
+    approve_note   varchar(500),
+    create_by      varchar(64),
+    create_time    timestamp DEFAULT CURRENT_TIMESTAMP,
+    update_by      varchar(64),
+    update_time    timestamp DEFAULT CURRENT_TIMESTAMP,
+    del_flag       char(1) DEFAULT '0'
+);
+
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS plan_no        varchar(32);
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS plan_type      varchar(20);
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS plan_level     varchar(20);
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS compile_unit   varchar(128);
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS publish_unit   varchar(128);
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS publish_date   varchar(20);
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS attach_name    varchar(255);
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS attach_path    varchar(1000);
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS is_template    int2 DEFAULT 0;
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS status         int2 DEFAULT 0;
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS version        varchar(20);
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS content        text;
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS approve_result varchar(20);
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS approve_time   timestamp;
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS approve_note   varchar(500);
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS create_by      varchar(64);
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS create_time    timestamp DEFAULT CURRENT_TIMESTAMP;
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS update_by      varchar(64);
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS update_time    timestamp DEFAULT CURRENT_TIMESTAMP;
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS del_flag       char(1) DEFAULT '0';
+
+COMMENT ON TABLE  app_user.dis_plan IS '应急预案主表:预案编辑与预案审批共用同一套数据,状态流转 草稿→待审核→已通过/已驳回';
+COMMENT ON COLUMN app_user.dis_plan.plan_no        IS '预案编号(业务序号:YA+yyyyMMdd+4位随机)';
+COMMENT ON COLUMN app_user.dis_plan.plan_name      IS '预案名称';
+COMMENT ON COLUMN app_user.dis_plan.plan_type      IS '预案类型:字典 dis_plan_type(综合/专项/现场处置)';
+COMMENT ON COLUMN app_user.dis_plan.plan_level     IS '预案等级:字典 dis_plan_level(Ⅰ级/Ⅱ级/Ⅲ级)';
+COMMENT ON COLUMN app_user.dis_plan.compile_unit   IS '编制单位';
+COMMENT ON COLUMN app_user.dis_plan.publish_unit   IS '发布单位';
+COMMENT ON COLUMN app_user.dis_plan.publish_date   IS '发布日期';
+COMMENT ON COLUMN app_user.dis_plan.attach_name    IS '附件名称';
+COMMENT ON COLUMN app_user.dis_plan.attach_path    IS '附件路径';
+COMMENT ON COLUMN app_user.dis_plan.is_template    IS '是否预案模板:0-否 1-是(新建预案时可套用模板的章节结构)';
+COMMENT ON COLUMN app_user.dis_plan.status         IS 'PlanStatusEnum:0-草稿 1-待审核 2-已通过 3-已驳回';
+COMMENT ON COLUMN app_user.dis_plan.version        IS '当前生效版本号;尚未审批通过时为空';
+COMMENT ON COLUMN app_user.dis_plan.content        IS '当前正文(富文本 HTML);生效正文以版本表快照为准';
+COMMENT ON COLUMN app_user.dis_plan.approve_result IS '审批结果:通过 / 不通过';
+COMMENT ON COLUMN app_user.dis_plan.approve_time   IS '审批完成时间';
+COMMENT ON COLUMN app_user.dis_plan.approve_note   IS '审批意见;不通过时为驳回原因(必填)';
+
+CREATE UNIQUE INDEX IF NOT EXISTS uk_dis_plan_no     ON app_user.dis_plan(plan_no);
+CREATE INDEX IF NOT EXISTS idx_dis_plan_status       ON app_user.dis_plan(status);
+CREATE INDEX IF NOT EXISTS idx_dis_plan_template     ON app_user.dis_plan(is_template);
+
+-- ---------------------------------------------------------------------
+-- 2.2 dis_plan_version 预案版本快照表
+-- ---------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS app_user.dis_plan_version (
+    id          int8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+    plan_id     int8 NOT NULL,
+    plan_no     varchar(32),
+    version     varchar(20) NOT NULL,
+    content     text,
+    change_note varchar(500),
+    editor      varchar(64),
+    edit_time   timestamp,
+    ver_status  int2 NOT NULL DEFAULT 0
+);
+
+ALTER TABLE app_user.dis_plan_version ADD COLUMN IF NOT EXISTS plan_no     varchar(32);
+ALTER TABLE app_user.dis_plan_version ADD COLUMN IF NOT EXISTS content     text;
+ALTER TABLE app_user.dis_plan_version ADD COLUMN IF NOT EXISTS change_note varchar(500);
+ALTER TABLE app_user.dis_plan_version ADD COLUMN IF NOT EXISTS editor      varchar(64);
+ALTER TABLE app_user.dis_plan_version ADD COLUMN IF NOT EXISTS edit_time   timestamp;
+ALTER TABLE app_user.dis_plan_version ADD COLUMN IF NOT EXISTS ver_status  int2 DEFAULT 0;
+
+COMMENT ON TABLE  app_user.dis_plan_version IS '应急预案版本快照:审核通过后的正文落地为版本;修订已通过预案先生成「审核中」候选版本,旧版本仍生效';
+COMMENT ON COLUMN app_user.dis_plan_version.plan_id     IS '关联 dis_plan.id';
+COMMENT ON COLUMN app_user.dis_plan_version.version     IS '版本号,如 v3.1';
+COMMENT ON COLUMN app_user.dis_plan_version.content     IS '该版本正文快照(富文本 HTML)';
+COMMENT ON COLUMN app_user.dis_plan_version.change_note IS '变更说明;修订已通过预案时必填';
+COMMENT ON COLUMN app_user.dis_plan_version.editor      IS '修订人';
+COMMENT ON COLUMN app_user.dis_plan_version.edit_time   IS '修订时间';
+COMMENT ON COLUMN app_user.dis_plan_version.ver_status  IS 'PlanVersionStatusEnum:0-历史 1-当前生效 2-审核中 3-已驳回';
+
+CREATE INDEX IF NOT EXISTS idx_dis_plan_version_plan ON app_user.dis_plan_version(plan_id, ver_status);
+CREATE UNIQUE INDEX IF NOT EXISTS uk_dis_plan_version ON app_user.dis_plan_version(plan_id, version);
+
+-- ---------------------------------------------------------------------
+-- 2.3 dis_plan_log 预案操作日志表
+--     替代旧 dis_plan_approval + dis_plan_approval_step(原型审批为单环节)
+-- ---------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS app_user.dis_plan_log (
+    id             int8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+    plan_id        int8 NOT NULL,
+    plan_no        varchar(32),
+    oper_type      int2 NOT NULL CHECK (oper_type BETWEEN 0 AND 4),
+    oper_desc      text,
+    version        varchar(20),
+    status_from    int2,
+    status_to      int2,
+    oper_user_id   int8,
+    oper_user_name varchar(128),
+    oper_time      timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+ALTER TABLE app_user.dis_plan_log ADD COLUMN IF NOT EXISTS plan_no        varchar(32);
+ALTER TABLE app_user.dis_plan_log ADD COLUMN IF NOT EXISTS oper_desc      text;
+ALTER TABLE app_user.dis_plan_log ADD COLUMN IF NOT EXISTS version        varchar(20);
+ALTER TABLE app_user.dis_plan_log ADD COLUMN IF NOT EXISTS status_from    int2;
+ALTER TABLE app_user.dis_plan_log ADD COLUMN IF NOT EXISTS status_to      int2;
+ALTER TABLE app_user.dis_plan_log ADD COLUMN IF NOT EXISTS oper_user_id   int8;
+ALTER TABLE app_user.dis_plan_log ADD COLUMN IF NOT EXISTS oper_user_name varchar(128);
+ALTER TABLE app_user.dis_plan_log ADD COLUMN IF NOT EXISTS oper_time      timestamp DEFAULT CURRENT_TIMESTAMP;
+
+COMMENT ON TABLE  app_user.dis_plan_log IS '应急预案操作日志:新建/提交审核/审批通过/审批驳回/修订全部留痕';
+COMMENT ON COLUMN app_user.dis_plan_log.oper_type  IS 'PlanLogOperTypeEnum:0-新建 1-提交审核 2-审批通过 3-审批驳回 4-修订';
+COMMENT ON COLUMN app_user.dis_plan_log.oper_desc  IS '操作说明/审批意见';
+COMMENT ON COLUMN app_user.dis_plan_log.version    IS '本次操作涉及的版本号';
+COMMENT ON COLUMN app_user.dis_plan_log.status_from IS '变更前预案状态';
+COMMENT ON COLUMN app_user.dis_plan_log.status_to   IS '变更后预案状态';
+
+CREATE INDEX IF NOT EXISTS idx_dis_plan_log_plan ON app_user.dis_plan_log(plan_id, oper_time, id);
+
+-- =====================================================================
+-- 三、事件分级(保留原表,字段字典化 + 补业务编号)
+-- =====================================================================
+CREATE TABLE IF NOT EXISTS app_user.dis_event_grade (
+    id             int8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+    grade_no       varchar(32),
+    event_category varchar(20),
+    event_type     varchar(20),
+    event_level    varchar(20),
+    sub_category   varchar(50),
+    scenario       varchar(200),
+    criteria       text,
+    response       varchar(500),
+    status         int2 NOT NULL DEFAULT 0,
+    remark         varchar(500),
+    create_by      varchar(64),
+    create_time    timestamp DEFAULT CURRENT_TIMESTAMP,
+    update_by      varchar(64),
+    update_time    timestamp DEFAULT CURRENT_TIMESTAMP,
+    del_flag       char(1) DEFAULT '0'
+);
+
+ALTER TABLE app_user.dis_event_grade ADD COLUMN IF NOT EXISTS grade_no       varchar(32);
+ALTER TABLE app_user.dis_event_grade ADD COLUMN IF NOT EXISTS event_category varchar(20);
+ALTER TABLE app_user.dis_event_grade ADD COLUMN IF NOT EXISTS event_type     varchar(20);
+ALTER TABLE app_user.dis_event_grade ADD COLUMN IF NOT EXISTS event_level    varchar(20);
+ALTER TABLE app_user.dis_event_grade ADD COLUMN IF NOT EXISTS sub_category   varchar(50);
+ALTER TABLE app_user.dis_event_grade ADD COLUMN IF NOT EXISTS scenario       varchar(200);
+ALTER TABLE app_user.dis_event_grade ADD COLUMN IF NOT EXISTS criteria       text;
+ALTER TABLE app_user.dis_event_grade ADD COLUMN IF NOT EXISTS response       varchar(500);
+ALTER TABLE app_user.dis_event_grade ADD COLUMN IF NOT EXISTS status         int2 DEFAULT 0;
+ALTER TABLE app_user.dis_event_grade ADD COLUMN IF NOT EXISTS remark         varchar(500);
+ALTER TABLE app_user.dis_event_grade ADD COLUMN IF NOT EXISTS create_by      varchar(64);
+ALTER TABLE app_user.dis_event_grade ADD COLUMN IF NOT EXISTS create_time    timestamp DEFAULT CURRENT_TIMESTAMP;
+ALTER TABLE app_user.dis_event_grade ADD COLUMN IF NOT EXISTS update_by      varchar(64);
+ALTER TABLE app_user.dis_event_grade ADD COLUMN IF NOT EXISTS update_time    timestamp DEFAULT CURRENT_TIMESTAMP;
+ALTER TABLE app_user.dis_event_grade ADD COLUMN IF NOT EXISTS del_flag       char(1) DEFAULT '0';
+
+COMMENT ON TABLE  app_user.dis_event_grade IS '应急事件分级分类标准,同时作为初判核实的辅助核实依据';
+COMMENT ON COLUMN app_user.dis_event_grade.grade_no       IS '标准编号(业务序号:GJ+yyyyMMdd+3位随机)';
+COMMENT ON COLUMN app_user.dis_event_grade.event_category IS '事件类别:字典 dis_event_category';
+COMMENT ON COLUMN app_user.dis_event_grade.event_type     IS '管网类型:字典 dis_event_type';
+COMMENT ON COLUMN app_user.dis_event_grade.event_level    IS '事件等级:字典 dis_event_level';
+COMMENT ON COLUMN app_user.dis_event_grade.sub_category   IS '类别细分';
+COMMENT ON COLUMN app_user.dis_event_grade.scenario       IS '适用场景';
+COMMENT ON COLUMN app_user.dis_event_grade.criteria       IS '分级分类判断标准';
+COMMENT ON COLUMN app_user.dis_event_grade.response       IS '响应要求,如:30 分钟内启动预案';
+COMMENT ON COLUMN app_user.dis_event_grade.status         IS '标准生效状态:0-启用 1-停用';
+
+CREATE UNIQUE INDEX IF NOT EXISTS uk_dis_event_grade_no ON app_user.dis_event_grade(grade_no);
+CREATE INDEX IF NOT EXISTS idx_dis_event_grade_query    ON app_user.dis_event_grade(event_category, event_level, status);
+
+-- =====================================================================
+-- 四、存量数据迁移 —— 见独立文件 dispatch_refactor_migrate.sql
+--     本脚本只负责建表 / 加列 / 字典,可安全用于全新库与既有库。
+--     存量数据搬迁依赖旧表旧列(source / level / category / dis_event_report 等),
+--     在新库上必然因缺列缺表报错,因此单独拆出,只在有存量数据的环境执行。
+-- =====================================================================
+
+-- =====================================================================
+-- 五、字典(sys_dict_type / sys_dict_data)
+--     编码口径:dict_value 存编码,dict_label 存中文,list_class 配徽标色。
+-- =====================================================================
+
+DELETE FROM app_user.sys_dict_data
+ WHERE dict_type IN ('dis_event_source','dis_event_channel','dis_event_type','dis_event_category',
+                     'dis_event_level','dis_event_region','dis_event_urge_method','dis_event_urge_result',
+                     'dis_event_trend','dis_event_false_reason','dis_event_release_reason',
+                     'dis_transfer_dept','dis_report_target',
+                     'dis_plan_type','dis_plan_level','dis_yes_no');
+
+INSERT INTO app_user.sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
+SELECT v.dict_name, v.dict_type, '0', 'admin', CURRENT_TIMESTAMP, v.remark
+  FROM (VALUES
+        ('应急事件来源',     'dis_event_source',         '应急事件来源:1巡查上报 2呼叫中心 3视频监控 4上级下达'),
+        ('应急事件来源渠道', 'dis_event_channel',        '应急事件来源渠道'),
+        ('应急管网类型',     'dis_event_type',           '应急事件管网类型:1供水管网 2燃气管网 3排水管网 9其他'),
+        ('应急事件类别',     'dis_event_category',       '应急事件类别:故障类别'),
+        ('应急事件等级',     'dis_event_level',          '应急事件等级:1Ⅰ级紧急 2Ⅱ级较大 3Ⅲ级一般'),
+        ('应急事件区域',     'dis_event_region',         '应急事件发生区域'),
+        ('工作催办方式',     'dis_event_urge_method',    '工作催办方式:1电话 2短信 3系统消息'),
+        ('工作催办结果',     'dis_event_urge_result',    '工作催办结果:0未反馈 1已反馈'),
+        ('处置态势评估',     'dis_event_trend',          '处置跟踪的态势评估:1趋于平稳 2仍在发展 3明显恶化'),
+        ('不实事件核销原因', 'dis_event_false_reason',   '初判核实判定不实时的核销原因'),
+        ('事件解除原因',     'dis_event_release_reason', '事件解除原因'),
+        ('转接报送部门',     'dis_transfer_dept',        '电话转接 / 上报报送的接收部门'),
+        ('上报报送层级',     'dis_report_target',        '上报报送的层级对象'),
+        ('应急预案类型',     'dis_plan_type',            '应急预案类型:1综合 2专项 3现场处置'),
+        ('应急预案等级',     'dis_plan_level',           '应急预案等级:1Ⅰ级 2Ⅱ级 3Ⅲ级'),
+        ('通用是否',         'dis_yes_no',               '通用是否:0否 1是')
+       ) AS v(dict_name, dict_type, remark)
+ WHERE NOT EXISTS (SELECT 1 FROM app_user.sys_dict_type t WHERE t.dict_type = v.dict_type);
+
+INSERT INTO app_user.sys_dict_data
+    (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
+VALUES
+-- 事件来源
+(1, '巡查上报',         '1', 'dis_event_source',  '', 'primary', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '应急事件来源:巡查上报'),
+(2, '呼叫中心',         '2', 'dis_event_source',  '', 'info',    'N', '0', 'admin', CURRENT_TIMESTAMP, '应急事件来源:呼叫中心'),
+(3, '视频监控',         '3', 'dis_event_source',  '', 'info',    'N', '0', 'admin', CURRENT_TIMESTAMP, '应急事件来源:视频监控'),
+(4, '上级下达',         '4', 'dis_event_source',  '', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '应急事件来源:上级下达'),
+-- 来源渠道
+(1, '12345市民热线',    '1', 'dis_event_channel', '', '',        'N', '0', 'admin', CURRENT_TIMESTAMP, '来源渠道:12345市民热线'),
+(2, '移动终端巡查系统', '2', 'dis_event_channel', '', '',        'N', '0', 'admin', CURRENT_TIMESTAMP, '来源渠道:移动终端巡查系统'),
+(3, '省应急管理厅平台', '3', 'dis_event_channel', '', '',        'N', '0', 'admin', CURRENT_TIMESTAMP, '来源渠道:省应急管理厅平台'),
+(4, '交警监控共享平台', '4', 'dis_event_channel', '', '',        'N', '0', 'admin', CURRENT_TIMESTAMP, '来源渠道:交警监控共享平台'),
+(5, '其他',             '9', 'dis_event_channel', '', '',        'N', '0', 'admin', CURRENT_TIMESTAMP, '来源渠道:其他'),
+-- 管网类型
+(1, '供水管网',         '1', 'dis_event_type',    '', 'primary', 'N', '0', 'admin', CURRENT_TIMESTAMP, '应急管网类型:供水管网'),
+(2, '燃气管网',         '2', 'dis_event_type',    '', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '应急管网类型:燃气管网'),
+(3, '排水管网',         '3', 'dis_event_type',    '', 'info',    'N', '0', 'admin', CURRENT_TIMESTAMP, '应急管网类型:排水管网'),
+(4, '其他',             '9', 'dis_event_type',    '', '',        'N', '0', 'admin', CURRENT_TIMESTAMP, '应急管网类型:其他'),
+-- 事件类别
+(1, '管道泄漏',         '1', 'dis_event_category', '', 'danger',  'N', '0', 'admin', CURRENT_TIMESTAMP, '事件类别:管道泄漏'),
+(2, '路面积水',         '2', 'dis_event_category', '', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '事件类别:路面积水'),
+(3, '设施损坏',         '3', 'dis_event_category', '', 'info',    'N', '0', 'admin', CURRENT_TIMESTAMP, '事件类别:设施损坏'),
+(4, '管道堵塞',         '4', 'dis_event_category', '', 'info',    'N', '0', 'admin', CURRENT_TIMESTAMP, '事件类别:管道堵塞'),
+-- 事件等级(3 级口径)
+(1, 'Ⅰ级(紧急)',     '1', 'dis_event_level',   '', 'danger',  'N', '0', 'admin', CURRENT_TIMESTAMP, '事件等级:Ⅰ级紧急'),
+(2, 'Ⅱ级(较大)',     '2', 'dis_event_level',   '', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '事件等级:Ⅱ级较大'),
+(3, 'Ⅲ级(一般)',     '3', 'dis_event_level',   '', 'info',    'Y', '0', 'admin', CURRENT_TIMESTAMP, '事件等级:Ⅲ级一般'),
+-- 发生区域(按实际片区维护,以下为占位默认值)
+(1, '城东片区',         '1', 'dis_event_region',  '', '',        'N', '0', 'admin', CURRENT_TIMESTAMP, '发生区域:城东片区(按实际维护)'),
+(2, '城南片区',         '2', 'dis_event_region',  '', '',        'Y', '0', 'admin', CURRENT_TIMESTAMP, '发生区域:城南片区(按实际维护)'),
+(3, '城北片区',         '3', 'dis_event_region',  '', '',        'N', '0', 'admin', CURRENT_TIMESTAMP, '发生区域:城北片区(按实际维护)'),
+(4, '太常片区',         '4', 'dis_event_region',  '', '',        'N', '0', 'admin', CURRENT_TIMESTAMP, '发生区域:太常片区(按实际维护)'),
+-- 催办方式
+(1, '电话',             '1', 'dis_event_urge_method', '', '',    'Y', '0', 'admin', CURRENT_TIMESTAMP, '催办方式:电话'),
+(2, '短信',             '2', 'dis_event_urge_method', '', '',    'N', '0', 'admin', CURRENT_TIMESTAMP, '催办方式:短信'),
+(3, '系统消息',         '3', 'dis_event_urge_method', '', '',    'N', '0', 'admin', CURRENT_TIMESTAMP, '催办方式:系统消息'),
+-- 催办结果
+(1, '未反馈',           '0', 'dis_event_urge_result', '', 'info',   'Y', '0', 'admin', CURRENT_TIMESTAMP, '催办结果:未反馈'),
+(2, '已反馈',           '1', 'dis_event_urge_result', '', 'success','N', '0', 'admin', CURRENT_TIMESTAMP, '催办结果:已反馈'),
+-- 态势评估
+(1, '趋于平稳',         '1', 'dis_event_trend',   '', 'success', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '态势评估:趋于平稳'),
+(2, '仍在发展',         '2', 'dis_event_trend',   '', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '态势评估:仍在发展'),
+(3, '明显恶化',         '3', 'dis_event_trend',   '', 'danger',  'N', '0', 'admin', CURRENT_TIMESTAMP, '态势评估:明显恶化'),
+-- 不实核销原因
+(1, '误报',             '1', 'dis_event_false_reason', '', 'info', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '核销原因:误报'),
+(2, '重复上报',         '2', 'dis_event_false_reason', '', 'info', 'N', '0', 'admin', CURRENT_TIMESTAMP, '核销原因:重复上报'),
+(3, '演练测试',         '3', 'dis_event_false_reason', '', 'info', 'N', '0', 'admin', CURRENT_TIMESTAMP, '核销原因:演练测试'),
+(4, '其他',             '9', 'dis_event_false_reason', '', '',     'N', '0', 'admin', CURRENT_TIMESTAMP, '核销原因:其他'),
+-- 解除原因
+(1, '事件已处置完成',   '1', 'dis_event_release_reason', '', 'success', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '解除原因:事件已处置完成'),
+(2, '事件不存在',       '2', 'dis_event_release_reason', '', 'info',    'N', '0', 'admin', CURRENT_TIMESTAMP, '解除原因:事件不存在'),
+(3, '误报',             '3', 'dis_event_release_reason', '', 'info',    'N', '0', 'admin', CURRENT_TIMESTAMP, '解除原因:误报'),
+(4, '其他原因',         '9', 'dis_event_release_reason', '', '',        'N', '0', 'admin', CURRENT_TIMESTAMP, '解除原因:其他原因'),
+-- 转接报送部门(转接弹窗多选、列表按责任单位筛选共用)
+(1, '市政部门',         '1', 'dis_transfer_dept',  '', '',        'N', '0', 'admin', CURRENT_TIMESTAMP, '转接部门:市政部门'),
+(2, '消防部门',         '2', 'dis_transfer_dept',  '', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '转接部门:消防部门'),
+(3, '排水管理部',       '3', 'dis_transfer_dept',  '', '',        'N', '0', 'admin', CURRENT_TIMESTAMP, '转接部门:排水管理部'),
+(4, '管网维护部',       '4', 'dis_transfer_dept',  '', '',        'N', '0', 'admin', CURRENT_TIMESTAMP, '转接部门:管网维护部'),
+(5, '设备运维部',       '5', 'dis_transfer_dept',  '', '',        'N', '0', 'admin', CURRENT_TIMESTAMP, '转接部门:设备运维部'),
+(6, '安全管理部',       '6', 'dis_transfer_dept',  '', '',        'N', '0', 'admin', CURRENT_TIMESTAMP, '转接部门:安全管理部'),
+-- 上报报送层级(报送弹窗多选)
+(1, '区级应急办',       '1', 'dis_report_target',  '', '',        'N', '0', 'admin', CURRENT_TIMESTAMP, '报送层级:区级应急办'),
+(2, '市级应急办',       '2', 'dis_report_target',  '', 'primary', 'N', '0', 'admin', CURRENT_TIMESTAMP, '报送层级:市级应急办'),
+(3, '省级应急厅',       '3', 'dis_report_target',  '', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '报送层级:省级应急厅'),
+(4, '国务院应急管理部', '4', 'dis_report_target',  '', 'danger',  'N', '0', 'admin', CURRENT_TIMESTAMP, '报送层级:国务院应急管理部'),
+-- 预案类型
+(1, '综合',             '1', 'dis_plan_type',     '', 'primary', 'N', '0', 'admin', CURRENT_TIMESTAMP, '预案类型:综合'),
+(2, '专项',             '2', 'dis_plan_type',     '', 'success', 'N', '0', 'admin', CURRENT_TIMESTAMP, '预案类型:专项'),
+(3, '现场处置',         '3', 'dis_plan_type',     '', 'info',    'N', '0', 'admin', CURRENT_TIMESTAMP, '预案类型:现场处置'),
+-- 预案等级
+(1, 'Ⅰ级',             '1', 'dis_plan_level',    '', 'danger',  'N', '0', 'admin', CURRENT_TIMESTAMP, '预案等级:Ⅰ级'),
+(2, 'Ⅱ级',             '2', 'dis_plan_level',    '', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '预案等级:Ⅱ级'),
+(3, 'Ⅲ级',             '3', 'dis_plan_level',    '', 'info',    'N', '0', 'admin', CURRENT_TIMESTAMP, '预案等级:Ⅲ级'),
+-- 通用是否
+(1, '否',               '0', 'dis_yes_no',        '', 'info',    'Y', '0', 'admin', CURRENT_TIMESTAMP, '通用是否:否'),
+(2, '是',               '1', 'dis_yes_no',        '', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '通用是否:是');
+
+COMMIT;
+
+-- =====================================================================
+-- 六、执行后自检
+-- =====================================================================
+-- 1) 新表是否齐全(用 to_regclass,避免 information_schema.tables 触发
+--    sys_freespace 权限校验;在部分 KingbaseES 实例上普通账号会报权限错误)
+--    SELECT to_regclass('app_user.dis_event'),        to_regclass('app_user.dis_event_log'),
+--           to_regclass('app_user.dis_event_grade'),  to_regclass('app_user.dis_plan'),
+--           to_regclass('app_user.dis_plan_version'), to_regclass('app_user.dis_plan_log');
+-- 2) 字典是否写入
+--    SELECT dict_type, count(*) FROM app_user.sys_dict_data
+--     WHERE dict_type LIKE 'dis_%' GROUP BY dict_type ORDER BY dict_type;
+-- 3) 事件状态分布是否符合预期(只应是 0~4)
+--    SELECT status, count(*) FROM app_user.dis_event GROUP BY status ORDER BY status;
+-- 4) 日志是否迁移到位
+--    SELECT oper_type, count(*) FROM app_user.dis_event_log GROUP BY oper_type ORDER BY oper_type;

+ 239 - 0
pipe-network-service/sql/dispatch_refactor_drop.sql

@@ -0,0 +1,239 @@
+-- =====================================================================
+-- 应急与指挥调度模块改造 · 旧对象下线脚本(确认后执行)
+-- =====================================================================
+-- 文件:dispatch_refactor_drop.sql
+-- 用途:删除改造后不再使用的 13 张旧表,并清理 dis_event / dis_event_grade
+--       上已被新列取代的废弃列。
+--
+-- ⚠️ 执行前置条件(缺一不可):
+--   1) 已执行 dispatch_refactor.sql           (建新表 + 字典 seed)
+--   2) 已执行 dispatch_refactor_migrate.sql   (旧数据搬迁到新结构)
+--   3) 业务验证通过:接报 / 初判核实 / 转接报送 / 跟踪解除 / 工作催办
+--      / 事件分级 / 预案编辑 / 预案审批 八个页面读写正常
+--   4) 已对 app_user schema 做完整备份(pg_dump -n app_user)
+--
+-- ⚠️⚠️ 顺序颠倒的后果(真发生过,必读):
+--     若本脚本早于 migrate 执行,被删掉的旧表 / 旧列正是 migrate 的**唯一数据来源**。
+--     之后 migrate 里所有搬迁块的 `to_regclass(...) IS NOT NULL` 判断都会为假而**静默跳过**,
+--     脚本照常显示"成功",但什么都没搬进来 —— 且**不可重跑补救**:
+--       · 事件日志(dis_event_transfer_log / track_log / urge_detail)内容永久丢失;
+--       · dis_event_grade 的 category / level 值永久丢失(migrate 4.1 再无列可读);
+--       · 预案(dis_event_plan / dis_plan_approval)无法搬入。
+--     唯一挽回途径是执行前的 pg_dump 备份。
+--     执行本脚本前请先跑一遍 dispatch_refactor_migrate.sql 开头的「零、前置体检」,
+--     确认输出是 NOTICE 而不是 WARNING。
+--
+-- 本脚本只做「删除」与「清列」,不建任何对象;
+-- 全部语句幂等,可重复执行;已通过 to_regclass / information_schema.columns 做前置校验,
+-- 新列 / 新表不存在时不会执行对应删除,避免误删未迁移的数据。
+-- (自检段刻意避开 information_schema.tables:部分 KingbaseES 实例上该视图会调
+--   sys_freespace(relid),普通业务账号无该函数权限,会误报权限错误。)
+--
+-- ⚠️ 方言红线(本实例实测):**不要用 `||` 做字符串拼接**。
+--    本实例 KingbaseES 为 MySQL 兼容语义,`||` 是逻辑或(boolean OR),
+--    `'app_user.' || t.table_name` 会得到 boolean 而不是 'app_user.xxx'。
+--    拼接一律用 concat(a, b)。详见 dispatch_refactor_migrate.sql 文件头「方言红线」。
+--
+-- 表对应关系(旧 → 新):
+--   dis_event_report / dis_event_verify / dis_event_transfer /
+--   dis_event_transfer_log / dis_event_track / dis_event_track_log /
+--   dis_event_urge / dis_event_urge_detail / dis_event_record
+--        → 事件主表 dis_event(当前值) + 事件日志 dis_event_log(过程留痕)
+--   dis_event_plan / dis_plan_approval / dis_plan_approval_step
+--        → 预案主表 dis_plan + 版本快照 dis_plan_version + 操作日志 dis_plan_log
+--   dis_plan_category
+--        → 预案分类并入字典 dis_plan_type,不再单独建表
+--
+-- 保留不动:dis_event、dis_event_grade、dis_event_assess、
+--          dis_plan_library、dis_plan_match_record
+--
+-- 作者:zengy    日期:2026-10-14
+-- =====================================================================
+
+BEGIN;
+
+-- ---------------------------------------------------------------------
+-- 一、删除事件域旧表(9 张)
+--     前置校验:dis_event_log 必须已存在
+-- ---------------------------------------------------------------------
+DO $$
+BEGIN
+    IF to_regclass('app_user.dis_event_log') IS NULL THEN
+        RAISE EXCEPTION '未找到 app_user.dis_event_log,请先执行 dispatch_refactor.sql 与 dispatch_refactor_migrate.sql';
+    END IF;
+END $$;
+
+DROP TABLE IF EXISTS app_user.dis_event_report;
+DROP TABLE IF EXISTS app_user.dis_event_verify;
+DROP TABLE IF EXISTS app_user.dis_event_transfer;
+DROP TABLE IF EXISTS app_user.dis_event_transfer_log;
+DROP TABLE IF EXISTS app_user.dis_event_track;
+DROP TABLE IF EXISTS app_user.dis_event_track_log;
+DROP TABLE IF EXISTS app_user.dis_event_urge;
+DROP TABLE IF EXISTS app_user.dis_event_urge_detail;
+DROP TABLE IF EXISTS app_user.dis_event_record;
+
+-- ---------------------------------------------------------------------
+-- 二、删除预案域旧表(4 张)
+--     前置校验:dis_plan 必须已存在
+-- ---------------------------------------------------------------------
+DO $$
+BEGIN
+    IF to_regclass('app_user.dis_plan') IS NULL OR to_regclass('app_user.dis_plan_log') IS NULL THEN
+        RAISE EXCEPTION '未找到 app_user.dis_plan / dis_plan_log,请先执行 dispatch_refactor.sql 与 dispatch_refactor_migrate.sql';
+    END IF;
+END $$;
+
+DROP TABLE IF EXISTS app_user.dis_event_plan;
+DROP TABLE IF EXISTS app_user.dis_plan_approval;
+DROP TABLE IF EXISTS app_user.dis_plan_approval_step;
+DROP TABLE IF EXISTS app_user.dis_plan_category;
+
+-- ---------------------------------------------------------------------
+-- 三、清理 dis_event 上已被新列取代的废弃列
+--     仅当对应新列已存在时才删除,防止未迁移先删列
+-- ---------------------------------------------------------------------
+
+-- 3.1 事件来源:source → event_source
+DO $$
+BEGIN
+    IF EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event' AND column_name = 'event_source')
+       AND EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event' AND column_name = 'source') THEN
+        EXECUTE 'ALTER TABLE app_user.dis_event DROP COLUMN IF EXISTS source';
+    END IF;
+END $$;
+
+-- 3.2 事件类别:category → event_category
+DO $$
+BEGIN
+    IF EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event' AND column_name = 'event_category')
+       AND EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event' AND column_name = 'category') THEN
+        EXECUTE 'ALTER TABLE app_user.dis_event DROP COLUMN IF EXISTS category';
+    END IF;
+END $$;
+
+-- 3.3 事件等级:level → event_level(顺带去掉保留字转义)
+DO $$
+BEGIN
+    IF EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event' AND column_name = 'event_level')
+       AND EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event' AND column_name = 'level') THEN
+        EXECUTE 'ALTER TABLE app_user.dis_event DROP COLUMN IF EXISTS "level"';
+    END IF;
+END $$;
+
+-- 3.4 发生区域:region → event_region
+DO $$
+BEGIN
+    IF EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event' AND column_name = 'event_region')
+       AND EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event' AND column_name = 'region') THEN
+        EXECUTE 'ALTER TABLE app_user.dis_event DROP COLUMN IF EXISTS region';
+    END IF;
+END $$;
+
+-- 3.5 解除说明:close_remark → release_note(名称与语义统一为「解除」)
+DO $$
+BEGIN
+    IF EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event' AND column_name = 'release_note')
+       AND EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event' AND column_name = 'close_remark') THEN
+        EXECUTE 'ALTER TABLE app_user.dis_event DROP COLUMN IF EXISTS close_remark';
+    END IF;
+END $$;
+
+-- 3.6 无替代列的废弃字段:事件名称(改用 description)、初判时间(改用 verify_time)、
+--     应用预案名称(改名走 dis_plan.plan_name 关联)、关闭环节(取消「已关闭」状态)
+ALTER TABLE app_user.dis_event DROP COLUMN IF EXISTS event_name;
+ALTER TABLE app_user.dis_event DROP COLUMN IF EXISTS judge_time;
+ALTER TABLE app_user.dis_event DROP COLUMN IF EXISTS plan_name;
+ALTER TABLE app_user.dis_event DROP COLUMN IF EXISTS close_reason;
+ALTER TABLE app_user.dis_event DROP COLUMN IF EXISTS close_time;
+
+-- ---------------------------------------------------------------------
+-- 四、清理 dis_event_grade 上已被新列取代的废弃列
+-- ---------------------------------------------------------------------
+
+-- 4.1 事件类别:category → event_category
+DO $$
+BEGIN
+    IF EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event_grade' AND column_name = 'event_category')
+       AND EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event_grade' AND column_name = 'category') THEN
+        EXECUTE 'ALTER TABLE app_user.dis_event_grade DROP COLUMN IF EXISTS category';
+    END IF;
+END $$;
+
+-- 4.2 事件等级:level → event_level
+DO $$
+BEGIN
+    IF EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event_grade' AND column_name = 'event_level')
+       AND EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event_grade' AND column_name = 'level') THEN
+        EXECUTE 'ALTER TABLE app_user.dis_event_grade DROP COLUMN IF EXISTS "level"';
+    END IF;
+END $$;
+
+COMMIT;
+
+-- =====================================================================
+-- 五、执行后自检(只读,结果自行核对)
+-- =====================================================================
+
+-- 说明:自检统一用 to_regclass(),不查 information_schema.tables。
+--       部分 KingbaseES 实例的 information_schema.tables 视图会走到
+--       sys_freespace(relid),普通业务账号没有该函数的 EXECUTE 权限,
+--       会报「对函数 sys_freespace 权限不够」,与本次 DDL 结果无关。
+
+-- 5.1 确认 13 张旧表已全部不存在(期望 13 行,still_exists 全为 NULL)
+SELECT t.table_name,
+       to_regclass(concat('app_user.', t.table_name)) AS still_exists
+  FROM (VALUES
+        ('dis_event_report'), ('dis_event_verify'), ('dis_event_transfer'),
+        ('dis_event_transfer_log'), ('dis_event_track'), ('dis_event_track_log'),
+        ('dis_event_urge'), ('dis_event_urge_detail'), ('dis_event_record'),
+        ('dis_event_plan'), ('dis_plan_approval'), ('dis_plan_approval_step'),
+        ('dis_plan_category')
+       ) AS t(table_name);
+
+-- 5.2 确认 4 张新表存在(期望 4 行,exists 全为 true)
+SELECT t.table_name,
+       to_regclass(concat('app_user.', t.table_name)) IS NOT NULL AS exists
+  FROM (VALUES
+        ('dis_event_log'), ('dis_plan'), ('dis_plan_version'), ('dis_plan_log')
+       ) AS t(table_name);
+
+-- 5.3 确认 dis_event 上不再有废弃列(期望返回 0 行)
+SELECT column_name
+  FROM information_schema.columns
+ WHERE table_schema = 'app_user' AND table_name = 'dis_event'
+   AND column_name IN ('source', 'category', 'level', 'region', 'event_name',
+                       'judge_time', 'plan_name', 'close_reason', 'close_remark', 'close_time')
+ ORDER BY column_name;
+
+-- =====================================================================
+-- 六、菜单核对(只读)
+-- =====================================================================
+-- 本次改造只替换前端页面的数据层,8 个页面路径与组件位置均未变动,
+-- 因此 sys_menu 一般无需调整。若历史菜单中残留指向已下线页面的条目,
+-- 可先用下面的查询定位(菜单表实际名称以库内为准):
+--
+--   SELECT menu_id, menu_name, path, component, perms
+--     FROM sys.sys_menu
+--    WHERE component LIKE '%dispatch%'
+--    ORDER BY parent_id, order_num;
+--
+-- 确认某条菜单确实要下线后,再自行执行定向删除(本脚本不代删,避免误伤):
+--
+--   DELETE FROM sys.sys_role_menu WHERE menu_id = <menu_id>;
+--   DELETE FROM sys.sys_menu      WHERE menu_id = <menu_id>;
+-- =====================================================================

+ 165 - 0
pipe-network-service/sql/dispatch_refactor_fix_number.sql

@@ -0,0 +1,165 @@
+/* =====================================================================
+   应急与指挥调度模块改造 · 业务编号 / 分级状态 修复脚本
+   =====================================================================
+   文件:dispatch_refactor_fix_number.sql
+   用途:修补 dispatch_refactor_migrate.sql「三、四」两段因方言问题未生效的部分。
+
+   ---------------------------------------------------------------------
+   背景:本实例的 KingbaseES 处于 MySQL 兼容语义,`||` 被解析为「逻辑或」
+         (boolean OR),不是字符串连接符。实测:
+             SELECT 'GJ' || '20261014' || '001';      -- 返回 boolean  t
+             SELECT concat('GJ','20261014','001');    -- 返回 GJ20261014001
+         于是 migrate 里用 `||` 生成业务编号的语句:
+           · dis_event_grade —— 整列被写成字符串 'true',第 2 行即撞
+             uk_dis_event_grade_no,报「重复键违反唯一约束」并整段回滚;
+           · dis_plan        —— 同理(uk_dis_plan_no),或在只有 1 行时
+             静默写入一行脏值 'true'。
+
+   ---------------------------------------------------------------------
+   为什么不能靠「重跑 migrate」修:
+     migrate 三段的主表搬迁 INSERT 带 `WHERE NOT EXISTS (... plan_name 相同)`,
+     已经插进去的那一行(plan_no = 'true')会被去重跳过,编号永远停错。
+     所以需要本脚本按 id 逐行补号。
+
+   ---------------------------------------------------------------------
+   本脚本做什么(全部幂等,可重复执行;不删除任何数据):
+     1) 清脏编号:plan_no / grade_no 中不符合规范的(如 'true')置为 NULL
+     2) 事件分级状态归一:中文 → 0/1,并把 status 列类型从 varchar 改成 int2
+     3) 分级的口径映射:旧列 category/level(若还在)→ 新列;中文等级/类别 → 数字
+     4) 重新取号:YA+yyyyMMdd+4位序号 / GJ+yyyyMMdd+3位序号(按 id 顺序)
+     5) dis_plan_version.plan_no 与主表对齐
+   注意:4) 已统一改用 concat(),不再使用 `||`。
+
+   执行前提:dispatch_refactor.sql 已执行(新表 + 唯一索引 + 字典已就绪)。
+   执行方式:本脚本只有一个 BEGIN; ... COMMIT;,可整段选中运行。
+   ===================================================================== */
+
+BEGIN;
+
+/* ---------------------------------------------------------------------
+   1) 清脏编号:非规范编号一律置 NULL,交给第 4 步重新生成
+      YA 规范:YA + 8 位日期 + 4 位序号 = YA + 12 位数字
+      GJ 规范:GJ + 8 位日期 + 3 位序号 = GJ + 11 位数字
+   --------------------------------------------------------------------- */
+UPDATE app_user.dis_plan
+   SET plan_no = NULL
+ WHERE plan_no IS NOT NULL
+   AND plan_no !~ '^YA[0-9]{12}$';
+
+UPDATE app_user.dis_event_grade
+   SET grade_no = NULL
+ WHERE grade_no IS NOT NULL
+   AND grade_no !~ '^GJ[0-9]{11}$';
+
+/* ---------------------------------------------------------------------
+   2) 事件分级状态归一:启用/停用 → 0/1,并把列类型改成 int2
+      (migrate 四段整体回滚,所以 status 现在很可能还是 varchar + 中文;
+        后端按 Integer 处理,WHERE status = 2 打在 varchar 列上会直接报类型错)
+   --------------------------------------------------------------------- */
+UPDATE app_user.dis_event_grade SET status = '0' WHERE status::text IN ('启用', '正常');
+UPDATE app_user.dis_event_grade SET status = '1' WHERE status::text IN ('停用', '禁用');
+UPDATE app_user.dis_event_grade SET status = '0' WHERE status::text !~ '^[0-9]+$';
+
+DO $$
+BEGIN
+    IF EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event_grade'
+                  AND column_name = 'status' AND data_type <> 'smallint') THEN
+        EXECUTE 'ALTER TABLE app_user.dis_event_grade ALTER COLUMN status TYPE int2 USING status::int2';
+    END IF;
+END $$;
+
+/* ---------------------------------------------------------------------
+   3) 分级口径映射(旧列若已被 drop 删除,本段自动跳过,是安全的空操作)
+   --------------------------------------------------------------------- */
+DO $$
+BEGIN
+    -- 3.1 旧列 category / level → 新列(必须早于下面的口径映射)
+    IF EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event_grade' AND column_name = 'category') THEN
+        EXECUTE 'UPDATE app_user.dis_event_grade SET event_category = category WHERE event_category IS NULL AND category IS NOT NULL';
+    END IF;
+    IF EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event_grade' AND column_name = 'level') THEN
+        EXECUTE 'UPDATE app_user.dis_event_grade SET event_level = "level" WHERE event_level IS NULL AND "level" IS NOT NULL';
+    END IF;
+END $$;
+
+-- 3.2 等级口径:旧 4 级 → 新 3 级
+UPDATE app_user.dis_event_grade SET event_level = '1'
+ WHERE event_level IN ('一级', 'Ⅰ级', '特别重大', '重大', '紧急');
+UPDATE app_user.dis_event_grade SET event_level = '2'
+ WHERE event_level IN ('二级', 'Ⅱ级', '较大');
+UPDATE app_user.dis_event_grade SET event_level = '3'
+ WHERE event_level IN ('三级', '四级', 'Ⅲ级', 'Ⅳ级', '一般');
+
+-- 3.3 类别口径
+UPDATE app_user.dis_event_grade SET event_category = '1' WHERE event_category IN ('管网泄漏', '管道泄漏');
+UPDATE app_user.dis_event_grade SET event_category = '2' WHERE event_category IN ('路面积水', '积水');
+UPDATE app_user.dis_event_grade SET event_category = '3' WHERE event_category IN ('设施损坏', '塌陷');
+UPDATE app_user.dis_event_grade SET event_category = '4' WHERE event_category IN ('管道堵塞', '堵塞');
+
+/* ---------------------------------------------------------------------
+   4) 重新取号
+      说明:子查询读的是语句开始时的快照,row_number() 按 id 排序;
+            NOT EXISTS 逐个比对,运行期已占用的编号直接跳过(该行保持
+            NULL,由页面人工补号),确保不会撞 uk_dis_plan_no /
+            uk_dis_event_grade_no。
+            CURRENT_TIMESTAMP 在同一事务内是常量,同批日期段一致。
+   --------------------------------------------------------------------- */
+
+-- 4.1 dis_plan:YA + yyyyMMdd + 4 位序号
+UPDATE app_user.dis_plan g
+   SET plan_no = c.new_no
+  FROM (SELECT t.id,
+               concat('YA', to_char(CURRENT_TIMESTAMP, 'YYYYMMDD'),
+                      lpad(CAST(t.rn AS varchar), 4, '0')) AS new_no
+          FROM (SELECT id, row_number() OVER (ORDER BY id) AS rn
+                  FROM app_user.dis_plan
+                 WHERE plan_no IS NULL) t) c
+ WHERE g.id = c.id
+   AND NOT EXISTS (SELECT 1 FROM app_user.dis_plan x WHERE x.plan_no = c.new_no);
+
+-- 4.2 dis_event_grade:GJ + yyyyMMdd + 3 位序号
+UPDATE app_user.dis_event_grade g
+   SET grade_no = c.new_no
+  FROM (SELECT t.id,
+               concat('GJ', to_char(CURRENT_TIMESTAMP, 'YYYYMMDD'),
+                      lpad(CAST(t.rn AS varchar), 3, '0')) AS new_no
+          FROM (SELECT id, row_number() OVER (ORDER BY id) AS rn
+                  FROM app_user.dis_event_grade
+                 WHERE grade_no IS NULL) t) c
+ WHERE g.id = c.id
+   AND NOT EXISTS (SELECT 1 FROM app_user.dis_event_grade x WHERE x.grade_no = c.new_no);
+
+/* ---------------------------------------------------------------------
+   5) 版本快照的编号与主表对齐
+      只处理主表已有编号的版本行;重复执行结果一致(幂等)。
+   --------------------------------------------------------------------- */
+UPDATE app_user.dis_plan_version v
+   SET plan_no = d.plan_no
+  FROM app_user.dis_plan d
+ WHERE v.plan_id = d.id
+   AND d.plan_no IS NOT NULL
+   AND (v.plan_no IS NULL OR v.plan_no <> d.plan_no);
+
+COMMIT;
+
+/* =====================================================================
+   6) 结果自检(只读)—— 请核对:
+      · dis_event_grade:status 只应是 0/1;grade_no 形如 GJ20261014001
+      · dis_plan        :plan_no   形如 YA202610140001
+      · 都不应出现 'true'
+   ===================================================================== */
+SELECT id, grade_no, event_category, event_level, status
+  FROM app_user.dis_event_grade ORDER BY id;
+
+SELECT id, plan_no, plan_name, status
+  FROM app_user.dis_plan ORDER BY id;
+
+/* 若下面这条返回 0 行,说明编号已全部规范 */
+SELECT 'dis_plan' AS tbl, count(*) AS bad FROM app_user.dis_plan
+ WHERE plan_no IS NOT NULL AND plan_no !~ '^YA[0-9]{12}$'
+UNION ALL
+SELECT 'dis_event_grade', count(*) FROM app_user.dis_event_grade
+ WHERE grade_no IS NOT NULL AND grade_no !~ '^GJ[0-9]{11}$';

+ 460 - 0
pipe-network-service/sql/dispatch_refactor_migrate.sql

@@ -0,0 +1,460 @@
+-- =====================================================================
+-- 应急与指挥调度模块(dispatch)改造脚本 —— 存量数据搬迁
+-- 数据库:KingbaseES(PG 兼容模式),业务 schema = app_user
+--
+-- ⚠️ 使用前提:**只在有存量数据的环境执行**。
+--    本脚本依赖旧表 / 旧列(dis_event.source、dis_event."level"、dis_event_report、
+--    dis_event_verify、dis_event_transfer_log、dis_event_track_log、
+--    dis_event_urge(_detail)、dis_event_plan、dis_plan_approval 等)。
+--    全新库请勿执行 —— 所有块都用 to_regclass / information_schema 做了存在性判断,
+--    缺失时自动跳过,但空跑一遍也没有意义。
+--
+-- 执行顺序:先 dispatch_refactor.sql(建表 + 加列 + 字典),再执行本脚本。
+--
+-- 幂等性:所有 INSERT 带 NOT EXISTS 去重;所有 UPDATE 带状态判断,可重复执行。
+-- 破坏性:无。本脚本不删除任何旧表、旧列、旧数据。
+--
+-- ⚠️ 执行注意(KingbaseES 客户端 · 事务挂起):
+--    本脚本按「一 / 二 / 三 / 四」分成 4 个独立事务(每段 END $$; 后即 COMMIT;),
+--    某段失败只回滚该段,前面已成功的段落保持落库。
+--    但失败后当前连接仍处于 aborted 事务中,此后**所有**查询(包括只读 SELECT)
+--    都会报「ERROR: 当前事务被终止, 事务块结束之前的查询被忽略」。
+--    处理办法:先单独执行 ROLLBACK; 再排查失败语句;不要反复重跑。
+--
+-- ⚠️ 执行注意(客户端把注释行与语句拼在一起):
+--    部分客户端在「选中运行」时会把紧邻的 `--` 注释行并入语句并吃掉一个减号,
+--    报 `ERROR: 语法错误 在 "." 或附近的`。逐条执行时请**只选中 SQL 本体**。
+--
+-- ⚠️⚠️ 方言红线(本实例实测,务必遵守):**不要用 `||` 做字符串拼接**。
+--    本实例的 KingbaseES 处于 MySQL 兼容语义,`||` 被解析为**逻辑或(boolean OR)**
+--    而不是字符串连接符。实测:
+--        SELECT 'GJ' || '20261014' || '001';        -- 返回 boolean  t   (错)
+--        SELECT concat('GJ','20261014','001');      -- 返回 GJ20261014001(对)
+--    后果:`SET grade_no = 'GJ' || to_char(...)` 会把 16 行全写成一个值 `'true'`,
+--    第 2 行即撞唯一索引 uk_dis_event_grade_no,报
+--        ERROR: 重复键违反唯一约束 / DETAIL: 键值 "(grade_no)=(true)" 已经存在。
+--    凡拼接一律改用 concat(a, b, c);需要定宽时配 lpad(CAST(x AS varchar), n, '0')。
+-- =====================================================================
+
+-- =====================================================================
+-- 零、前置体检(只读,先跑这里看一眼输出再往下)
+--     作用:把「搬迁源还在不在」显式喊出来。
+--     本脚本内部的搬迁块都用 to_regclass 做存在性判断,旧表/旧列不存在时**静默跳过**,
+--     这是为了幂等;但代价是:若 drop 脚本被提前执行(顺序颠倒),
+--     整个搬迁会一声不响地全部跳过,脚本却显示"成功"。
+--     本段会在那种情况下直接抛 WARNING,避免数据悄悄丢。
+-- =====================================================================
+DO $$
+DECLARE
+    n_old int;
+    src   text;
+BEGIN
+    SELECT count(*), string_agg(t.n, ', ' ORDER BY t.n)
+      INTO n_old, src
+      FROM (VALUES ('dis_event_report'), ('dis_event_verify'), ('dis_event_transfer'),
+                   ('dis_event_transfer_log'), ('dis_event_track'), ('dis_event_track_log'),
+                   ('dis_event_urge'), ('dis_event_urge_detail'), ('dis_event_record'),
+                   ('dis_event_plan'), ('dis_plan_approval'), ('dis_plan_approval_step'),
+                   ('dis_plan_category')
+           ) AS t(n)
+     WHERE to_regclass(concat('app_user.', t.n)) IS NOT NULL;
+
+    IF n_old = 0 THEN
+        RAISE WARNING '搬迁源体检:未检测到任何旧表,本脚本将【不搬迁任何数据】(所有段都会跳过,但脚本仍显示成功)。';
+        RAISE WARNING '可能原因:drop 脚本被提前执行,旧表已被删除 —— 被删的旧表正是本脚本唯一的数据来源。';
+        RAISE WARNING '正确顺序是 建表 -> migrate(本脚本) -> 业务验证 -> drop;顺序颠倒会永久丢失存量数据。';
+    ELSE
+        RAISE NOTICE '搬迁源体检:检测到 % 张旧表(%),将执行搬迁。', n_old, src;
+    END IF;
+
+    IF to_regclass('app_user.dis_event_grade') IS NOT NULL
+       AND EXISTS (SELECT 1 FROM information_schema.columns
+                    WHERE table_schema = 'app_user' AND table_name = 'dis_event_grade'
+                      AND column_name = 'category') THEN
+        RAISE NOTICE 'detected: dis_event_grade 旧列 category / level 尚在,第 4.1 步可正常搬值。';
+    ELSE
+        RAISE NOTICE 'dis_event_grade 旧列 category / level 已不存在(或表不存在),第 4.1 步会跳过,event_category / event_level 将保持为空。';
+    END IF;
+END $$;
+
+BEGIN;
+
+-- =====================================================================
+-- 一、dis_event 状态归一 + 旧列搬迁
+--     旧 status(中文串):待受理/待核实/已核实/处置中/已解除/已关闭/不实事件
+--     新 status(int2)   :0 待核实 / 1 已核实 / 2 处置中 / 3 已解除 / 4 不实事件
+-- =====================================================================
+DO $$
+DECLARE
+    has_source       boolean;
+    has_level        boolean;
+    has_category     boolean;
+    has_close_remark boolean;
+BEGIN
+    SELECT EXISTS (SELECT 1 FROM information_schema.columns
+                    WHERE table_schema = 'app_user' AND table_name = 'dis_event' AND column_name = 'source')
+      INTO has_source;
+    SELECT EXISTS (SELECT 1 FROM information_schema.columns
+                    WHERE table_schema = 'app_user' AND table_name = 'dis_event' AND column_name = 'level')
+      INTO has_level;
+    SELECT EXISTS (SELECT 1 FROM information_schema.columns
+                    WHERE table_schema = 'app_user' AND table_name = 'dis_event' AND column_name = 'category')
+      INTO has_category;
+    SELECT EXISTS (SELECT 1 FROM information_schema.columns
+                    WHERE table_schema = 'app_user' AND table_name = 'dis_event' AND column_name = 'close_remark')
+      INTO has_close_remark;
+
+    -- 1.1 状态归一:只处理仍是中文串的行,重复执行不会二次改写
+    UPDATE app_user.dis_event SET status = '0' WHERE status::text IN ('待受理', '待核实');
+    UPDATE app_user.dis_event SET status = '1' WHERE status::text = '已核实';
+    UPDATE app_user.dis_event SET status = '2' WHERE status::text = '处置中';
+    UPDATE app_user.dis_event SET status = '3' WHERE status::text IN ('已解除', '已关闭');
+    UPDATE app_user.dis_event SET status = '4' WHERE status::text = '不实事件';
+    -- 兜底:任何未识别的历史状态归 0(待核实),避免后续类型转换失败
+    UPDATE app_user.dis_event SET status = '0' WHERE status::text !~ '^[0-9]+$';
+
+    -- 1.1b 列类型收敛:varchar → int2
+    --      必须在状态归一之后执行,否则中文值无法直接转型。
+    --      已是 int2 的库跳过。
+    IF EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event'
+                  AND column_name = 'status' AND data_type <> 'smallint') THEN
+        EXECUTE 'ALTER TABLE app_user.dis_event ALTER COLUMN status TYPE int2 USING status::int2';
+    END IF;
+
+    -- 1.2 旧列 → 新列(仅当新列为空)
+    IF has_source THEN
+        EXECUTE 'UPDATE app_user.dis_event SET event_source = source WHERE event_source IS NULL AND source IS NOT NULL';
+        EXECUTE $q$UPDATE app_user.dis_event SET event_source = '1' WHERE event_source = '巡查上报'$q$;
+        EXECUTE $q$UPDATE app_user.dis_event SET event_source = '2' WHERE event_source = '呼叫中心'$q$;
+        EXECUTE $q$UPDATE app_user.dis_event SET event_source = '3' WHERE event_source = '视频监控'$q$;
+        EXECUTE $q$UPDATE app_user.dis_event SET event_source = '4' WHERE event_source = '上级下达'$q$;
+    END IF;
+
+    IF has_level THEN
+        EXECUTE 'UPDATE app_user.dis_event SET event_level = "level" WHERE event_level IS NULL AND "level" IS NOT NULL';
+        -- 旧 4 级 → 新 3 级口径映射;执行前请按业务确认,以下为默认映射
+        EXECUTE $q$UPDATE app_user.dis_event SET event_level = '1' WHERE event_level IN ('特别重大', '重大', '一级', 'Ⅰ级', '紧急')$q$;
+        EXECUTE $q$UPDATE app_user.dis_event SET event_level = '2' WHERE event_level IN ('较大', '二级', 'Ⅱ级')$q$;
+        EXECUTE $q$UPDATE app_user.dis_event SET event_level = '3' WHERE event_level IN ('一般', '三级', '四级', 'Ⅲ级', 'Ⅳ级')$q$;
+    END IF;
+
+    IF has_category THEN
+        EXECUTE 'UPDATE app_user.dis_event SET event_category = category WHERE event_category IS NULL AND category IS NOT NULL';
+        EXECUTE $q$UPDATE app_user.dis_event SET event_category = '1' WHERE event_category = '管道泄漏'$q$;
+        EXECUTE $q$UPDATE app_user.dis_event SET event_category = '2' WHERE event_category IN ('路面积水', '积水')$q$;
+        EXECUTE $q$UPDATE app_user.dis_event SET event_category = '3' WHERE event_category IN ('设施损坏', '塌陷')$q$;
+        EXECUTE $q$UPDATE app_user.dis_event SET event_category = '4' WHERE event_category IN ('管道堵塞', '堵塞')$q$;
+    END IF;
+
+    -- 1.3 解除说明兜底
+    IF has_close_remark THEN
+        EXECUTE 'UPDATE app_user.dis_event SET release_note = close_remark WHERE release_note IS NULL AND close_remark IS NOT NULL';
+    END IF;
+
+    -- 1.4 状态为终态但缺少时间列的兜底
+    UPDATE app_user.dis_event SET dispose_time = COALESCE(dispose_time, report_time)
+     WHERE status IN (2, 3) AND dispose_time IS NULL;
+    UPDATE app_user.dis_event SET release_time = COALESCE(release_time, update_time, report_time)
+     WHERE status = 3 AND release_time IS NULL;
+END $$;
+
+-- 分段提交:第一段成功即落库;后续段落失败时不会把本段一起回滚。
+COMMIT;
+BEGIN;
+
+-- =====================================================================
+-- 二、各子表 → dis_event_log
+--     旧子表保留不动;待新结构验证通过后由 dispatch_refactor_drop.sql 处理。
+--     对应关系:
+--       dis_event_report        → oper_type 0 接报登记
+--       dis_event_verify        → oper_type 1/2 初判核实 / 不实核销
+--       dis_event_transfer_log  → oper_type 3/4 电话转接 / 上报报送
+--       dis_event_track_log     → oper_type 6 处置跟踪
+--       dis_event_urge_detail   → oper_type 8 工作催办
+--       由状态轨迹推导           → oper_type 5 开始处置 / 7 事件解除
+-- =====================================================================
+DO $$
+BEGIN
+    -- 2.1 接报登记
+    IF to_regclass('app_user.dis_event_report') IS NOT NULL THEN
+        INSERT INTO app_user.dis_event_log
+            (event_id, event_no, oper_type, oper_desc, status_to, oper_time, oper_user_name)
+        SELECT e.id, r.event_no, 0, '接报登记(历史数据迁移)', 0, r.report_time, r.reporter
+          FROM app_user.dis_event_report r
+          JOIN app_user.dis_event e ON e.event_no = r.event_no
+         WHERE r.event_no IS NOT NULL
+           AND NOT EXISTS (SELECT 1 FROM app_user.dis_event_log l
+                            WHERE l.event_no = r.event_no AND l.oper_type = 0);
+    END IF;
+
+    -- 2.2 初判核实 / 不实核销
+    IF to_regclass('app_user.dis_event_verify') IS NOT NULL THEN
+        INSERT INTO app_user.dis_event_log
+            (event_id, event_no, oper_type, oper_desc, status_from, status_to, oper_time, oper_user_name)
+        SELECT e.id, v.event_no,
+               CASE WHEN v.is_real = '不实' THEN 2 ELSE 1 END,
+               COALESCE(v.opinion, '初判核实(历史数据迁移)'),
+               0, CASE WHEN v.is_real = '不实' THEN 4 ELSE 1 END,
+               v.verify_time, v.verifier
+          FROM app_user.dis_event_verify v
+          JOIN app_user.dis_event e ON e.event_no = v.event_no
+         WHERE v.is_real IS NOT NULL
+           AND NOT EXISTS (SELECT 1 FROM app_user.dis_event_log l
+                            WHERE l.event_no = v.event_no AND l.oper_type IN (1, 2));
+    END IF;
+
+    -- 2.3 电话转接 / 上报报送
+    IF to_regclass('app_user.dis_event_transfer_log') IS NOT NULL THEN
+        INSERT INTO app_user.dis_event_log
+            (event_id, event_no, oper_type, oper_desc, targets, oper_time)
+        SELECT e.id, l.event_no,
+               CASE WHEN l.action_type = '报送' THEN 4 ELSE 3 END,
+               COALESCE(l.content, '转接报送(历史数据迁移)'),
+               l.action_target, l.action_time
+          FROM app_user.dis_event_transfer_log l
+          JOIN app_user.dis_event e ON e.event_no = l.event_no
+         WHERE l.event_no IS NOT NULL
+           AND NOT EXISTS (SELECT 1 FROM app_user.dis_event_log x
+                            WHERE x.event_no = l.event_no AND x.oper_type IN (3, 4)
+                              AND COALESCE(x.oper_time, TIMESTAMP '1970-01-01') = l.action_time);
+    END IF;
+
+    -- 2.4 处置跟踪
+    IF to_regclass('app_user.dis_event_track_log') IS NOT NULL THEN
+        INSERT INTO app_user.dis_event_log
+            (event_id, event_no, oper_type, oper_desc, trend, need_upgrade, oper_time)
+        SELECT e.id, l.event_no, 6,
+               COALESCE(l.content, '处置跟踪(历史数据迁移)'),
+               l.assessment,
+               CASE WHEN l.need_upgrade = '是' THEN 1 ELSE 0 END,
+               l.action_time
+          FROM app_user.dis_event_track_log l
+          JOIN app_user.dis_event e ON e.event_no = l.event_no
+         WHERE l.event_no IS NOT NULL
+           AND NOT EXISTS (SELECT 1 FROM app_user.dis_event_log x
+                            WHERE x.event_no = l.event_no AND x.oper_type = 6
+                              AND COALESCE(x.oper_time, TIMESTAMP '1970-01-01') = l.action_time);
+    END IF;
+
+    -- 2.5 工作催办
+    IF to_regclass('app_user.dis_event_urge_detail') IS NOT NULL
+       AND to_regclass('app_user.dis_event_urge') IS NOT NULL THEN
+        INSERT INTO app_user.dis_event_log
+            (event_id, event_no, oper_type, oper_desc, targets, receiver_dept, receiver_phone,
+             method, urge_result, oper_time)
+        SELECT e.id, d.event_no, 8,
+               COALESCE(d.content, '工作催办(历史数据迁移)'),
+               u.receiver, u.receiver_dept, u.receiver_phone,
+               d.method,
+               CASE d.result WHEN '已反馈' THEN '1' WHEN '未反馈' THEN '0' ELSE NULL END,
+               d.urge_time
+          FROM app_user.dis_event_urge_detail d
+          JOIN app_user.dis_event_urge u ON u.id = d.urge_id
+          JOIN app_user.dis_event e ON e.event_no = d.event_no
+         WHERE d.event_no IS NOT NULL
+           AND NOT EXISTS (SELECT 1 FROM app_user.dis_event_log x
+                            WHERE x.event_no = d.event_no AND x.oper_type = 8
+                              AND COALESCE(x.oper_time, TIMESTAMP '1970-01-01') = d.urge_time);
+    END IF;
+
+    -- 2.6 由状态轨迹推导「开始处置」
+    --     旧模型没有「开始处置时间」列,取最早一条处置跟踪日志的时间作代理;
+    --     无跟踪日志时回退到解除时间 / 接报时间。
+    IF to_regclass('app_user.dis_event_track') IS NOT NULL
+       AND to_regclass('app_user.dis_event_track_log') IS NOT NULL THEN
+        INSERT INTO app_user.dis_event_log
+            (event_id, event_no, oper_type, oper_desc, status_from, status_to, oper_time)
+        SELECT e.id, e.event_no, 5, '开始处置(历史数据迁移)', 1, 2,
+               COALESCE(m.first_track, e.release_time, e.report_time)
+          FROM app_user.dis_event e
+          LEFT JOIN (
+                SELECT t.rel_event_id, MIN(l.action_time) AS first_track
+                  FROM app_user.dis_event_track t
+                  JOIN app_user.dis_event_track_log l ON l.track_id = t.id
+                 GROUP BY t.rel_event_id
+          ) m ON m.rel_event_id = e.id
+         WHERE e.status IN (2, 3)
+           AND NOT EXISTS (SELECT 1 FROM app_user.dis_event_log x
+                            WHERE x.event_id = e.id AND x.oper_type = 5);
+    END IF;
+
+    -- 2.7 由状态轨迹推导「事件解除」
+    INSERT INTO app_user.dis_event_log
+        (event_id, event_no, oper_type, oper_desc, status_from, status_to, oper_time)
+    SELECT e.id, e.event_no, 7,
+           COALESCE(e.release_note, '事件解除(历史数据迁移)'), 2, 3, e.release_time
+      FROM app_user.dis_event e
+     WHERE e.status = 3 AND e.release_time IS NOT NULL
+       AND NOT EXISTS (SELECT 1 FROM app_user.dis_event_log x
+                        WHERE x.event_id = e.id AND x.oper_type = 7);
+END $$;
+
+-- 分段提交:第二段成功即落库。
+COMMIT;
+BEGIN;
+
+-- =====================================================================
+-- 三、预案域:dis_event_plan + dis_plan_approval → dis_plan + dis_plan_version
+-- =====================================================================
+DO $$
+BEGIN
+    IF to_regclass('app_user.dis_event_plan') IS NOT NULL THEN
+        -- 3.1 主表搬迁;plan_no 用业务序号格式回填(YA + yyyyMMdd + 4 位补零序列)
+        INSERT INTO app_user.dis_plan
+            (plan_no, plan_name, plan_type, plan_level, compile_unit, publish_unit, publish_date,
+             attach_name, attach_path, is_template, status, version, content, create_by, create_time)
+        -- 拼接必须用 concat():本实例 `||` 是 boolean OR,详见文件头「方言红线」
+        SELECT concat('YA', to_char(CURRENT_TIMESTAMP, 'YYYYMMDD'),
+                      lpad(CAST(t.rn AS varchar), 4, '0')),
+               t.plan_name, t.plan_type, t.plan_level, t.compile_unit, t.publish_unit, t.publish_date,
+               t.attach_name, t.attach_path, 0, 0, t.version, t.plan_content, t.create_by, t.create_time
+          FROM (SELECT p.*, row_number() OVER (ORDER BY p.id) AS rn
+                  FROM app_user.dis_event_plan p
+                 WHERE NOT EXISTS (SELECT 1 FROM app_user.dis_plan d WHERE d.plan_name = p.plan_name)) t;
+
+        -- 3.2 已有正文且带版本号的历史预案,补一条「当前生效」版本快照
+        INSERT INTO app_user.dis_plan_version
+            (plan_id, plan_no, version, content, change_note, editor, edit_time, ver_status)
+        SELECT d.id, d.plan_no, COALESCE(NULLIF(d.version, ''), 'v1.0'), d.content,
+               '历史数据迁移', d.create_by, d.create_time, 1
+          FROM app_user.dis_plan d
+         WHERE d.status = 2
+           AND NOT EXISTS (SELECT 1 FROM app_user.dis_plan_version v WHERE v.plan_id = d.id);
+    END IF;
+
+    -- 3.3 审批表状态合并:编辑中/审批中/终审中/已发布/待修正 → 0/1/1/2/3
+    IF to_regclass('app_user.dis_plan_approval') IS NOT NULL THEN
+        UPDATE app_user.dis_plan d
+           SET status = CASE ap.status
+                          WHEN '已发布' THEN 2
+                          WHEN '审批中' THEN 1
+                          WHEN '终审中' THEN 1
+                          WHEN '待修正' THEN 3
+                          ELSE d.status END,
+               approve_time = ap.approve_time,
+               approve_note = COALESCE(d.approve_note, ap.audit_record),
+               approve_result = CASE ap.status
+                                  WHEN '已发布' THEN '通过'
+                                  WHEN '待修正' THEN '不通过'
+                                  ELSE d.approve_result END
+          FROM app_user.dis_plan_approval ap
+         WHERE d.plan_name = ap.plan_name
+           AND d.status = 0;
+
+        -- 3.4 审批轨迹补预案日志
+        INSERT INTO app_user.dis_plan_log
+            (plan_id, plan_no, oper_type, oper_desc, version, status_to, oper_user_name, oper_time)
+        SELECT d.id, d.plan_no,
+               CASE ap.status WHEN '已发布' THEN 2 WHEN '待修正' THEN 3 ELSE 1 END,
+               COALESCE(ap.audit_record, '历史审批记录迁移'),
+               ap.version,
+               CASE ap.status WHEN '已发布' THEN 2 WHEN '待修正' THEN 3 ELSE 1 END,
+               ap.reviewers, ap.approve_time
+          FROM app_user.dis_plan_approval ap
+          JOIN app_user.dis_plan d ON d.plan_name = ap.plan_name
+         WHERE NOT EXISTS (SELECT 1 FROM app_user.dis_plan_log l WHERE l.plan_id = d.id);
+    END IF;
+
+    -- 3.5 预案等级旧 4 级口径 → 新 3 级口径(执行前请按业务确认)
+    UPDATE app_user.dis_plan SET plan_level = '1'
+     WHERE plan_level IN ('一级', 'Ⅰ级', '特别重大', '重大', '紧急');
+    UPDATE app_user.dis_plan SET plan_level = '2'
+     WHERE plan_level IN ('二级', 'Ⅱ级', '较大');
+    UPDATE app_user.dis_plan SET plan_level = '3'
+     WHERE plan_level IN ('三级', '四级', 'Ⅲ级', 'Ⅳ级', '一般');
+
+    -- 3.6 预案类型中文 → 字典编码
+    UPDATE app_user.dis_plan SET plan_type = '1' WHERE plan_type = '综合';
+    UPDATE app_user.dis_plan SET plan_type = '2' WHERE plan_type = '专项';
+    UPDATE app_user.dis_plan SET plan_type = '3' WHERE plan_type = '现场处置';
+END $$;
+
+-- 分段提交:第三段成功即落库。
+COMMIT;
+BEGIN;
+
+-- =====================================================================
+-- 四、事件分级:状态改数字 + 补业务编号
+-- =====================================================================
+DO $$
+BEGIN
+    -- 4.1 先把旧列 category / level 搬到新列(必须早于下面的口径映射)
+    IF EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event_grade' AND column_name = 'category') THEN
+        EXECUTE 'UPDATE app_user.dis_event_grade SET event_category = category WHERE event_category IS NULL AND category IS NOT NULL';
+    END IF;
+    IF EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event_grade' AND column_name = 'level') THEN
+        EXECUTE 'UPDATE app_user.dis_event_grade SET event_level = "level" WHERE event_level IS NULL AND "level" IS NOT NULL';
+    END IF;
+
+    -- 4.2 状态下沉:启用/停用 → 0/1
+    UPDATE app_user.dis_event_grade SET status = '0' WHERE status::text IN ('启用', '正常');
+    UPDATE app_user.dis_event_grade SET status = '1' WHERE status::text IN ('停用', '禁用');
+    UPDATE app_user.dis_event_grade SET status = '0' WHERE status::text !~ '^[0-9]+$';
+
+    IF EXISTS (SELECT 1 FROM information_schema.columns
+                WHERE table_schema = 'app_user' AND table_name = 'dis_event_grade'
+                  AND column_name = 'status' AND data_type <> 'smallint') THEN
+        EXECUTE 'ALTER TABLE app_user.dis_event_grade ALTER COLUMN status TYPE int2 USING status::int2';
+    END IF;
+
+    -- 4.3 等级口径:旧 4 级 → 新 3 级
+    UPDATE app_user.dis_event_grade SET event_level = '1'
+     WHERE event_level IN ('一级', 'Ⅰ级', '特别重大', '重大', '紧急');
+    UPDATE app_user.dis_event_grade SET event_level = '2'
+     WHERE event_level IN ('二级', 'Ⅱ级', '较大');
+    UPDATE app_user.dis_event_grade SET event_level = '3'
+     WHERE event_level IN ('三级', '四级', 'Ⅲ级', 'Ⅳ级', '一般');
+
+    -- 4.4 类别口径
+    UPDATE app_user.dis_event_grade SET event_category = '1'
+     WHERE event_category IN ('管网泄漏', '管道泄漏');
+    UPDATE app_user.dis_event_grade SET event_category = '2'
+     WHERE event_category IN ('路面积水', '积水');
+    UPDATE app_user.dis_event_grade SET event_category = '3'
+     WHERE event_category IN ('设施损坏', '塌陷');
+    UPDATE app_user.dis_event_grade SET event_category = '4'
+     WHERE event_category IN ('管道堵塞', '堵塞');
+
+    -- 4.5 业务编号回填:GJ + yyyyMMdd + 3 位序号(按 id 顺序)
+    --     防御一:只补 grade_no IS NULL 的行,可重复执行。
+    --     防御二:运行期取号(DispatchNumberSupport.nextGradeNo = GJ+yyyyMMdd+3位随机)
+    --             可能已经占用了同样的编号,这里逐个比对,撞号的候选值直接跳过
+    --             (该行保持 NULL,由页面人工补号),避免 uk_dis_event_grade_no 报重复键。
+    --     注意:CURRENT_TIMESTAMP 在单个事务内是常量,同批回填的日期段一致。
+    --     注意:拼接必须用 concat()。本实例 `||` 被解析为 boolean OR,
+    --           写成 'GJ' || to_char(...) || lpad(...) 会让整列都变成 'true'(详见文件头)。
+    UPDATE app_user.dis_event_grade g
+       SET grade_no = c.new_no
+      FROM (SELECT t.id,
+                   concat('GJ', to_char(CURRENT_TIMESTAMP, 'YYYYMMDD'),
+                          lpad(CAST(t.rn AS varchar), 3, '0')) AS new_no
+              FROM (SELECT id, row_number() OVER (ORDER BY id) AS rn
+                      FROM app_user.dis_event_grade
+                     WHERE grade_no IS NULL) t) c
+     WHERE g.id = c.id
+       AND NOT EXISTS (SELECT 1 FROM app_user.dis_event_grade x
+                        WHERE x.grade_no = c.new_no);
+END $$;
+
+COMMIT;
+
+-- =====================================================================
+-- 五、迁移结果自检
+-- =====================================================================
+-- 1) 事件状态分布:只应是 0~4
+--    SELECT status, count(*) FROM app_user.dis_event GROUP BY status ORDER BY status;
+-- 2) 事件主表编号唯一性
+--    SELECT event_no, count(*) FROM app_user.dis_event GROUP BY event_no HAVING count(*) > 1;
+-- 3) 日志迁移覆盖率:有事件但无日志的异常数据
+--    SELECT e.id, e.event_no FROM app_user.dis_event e
+--     WHERE NOT EXISTS (SELECT 1 FROM app_user.dis_event_log l WHERE l.event_id = e.id);
+-- 4) 各操作类型日志条数
+--    SELECT oper_type, count(*) FROM app_user.dis_event_log GROUP BY oper_type ORDER BY oper_type;
+-- 5) 预案:有主表但无版本快照的已通过预案
+--    SELECT d.id, d.plan_no, d.status FROM app_user.dis_plan d
+--     WHERE d.status = 2 AND NOT EXISTS (SELECT 1 FROM app_user.dis_plan_version v WHERE v.plan_id = d.id);
+-- 6) 分级标准编号回填情况
+--    SELECT count(*) AS total, count(grade_no) AS with_no FROM app_user.dis_event_grade;

+ 15 - 0
pipe-network-service/sql/dispatch_release_attach.sql

@@ -0,0 +1,15 @@
+-- =====================================================================
+-- dispatch_release_attach.sql
+-- 事件解除:处置前 / 处置后 附件(图片、视频)
+-- ---------------------------------------------------------------------
+-- 背景:事件解除且原因为「事件已处置完成」时,要求上传处置前与处置后的
+-- 现场照片/视频作为佐证;多文件 URL 英文逗号分隔存入。
+-- 幂等:ADD COLUMN IF NOT EXISTS,可重复执行。
+-- 执行方式:KingbaseES 客户端逐段执行。
+-- =====================================================================
+
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS before_url text;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS after_url  text;
+
+COMMENT ON COLUMN app_user.dis_event.before_url IS '处置前附件URL(图片/视频,多个英文逗号分隔;解除原因为「事件已处置完成」时上传)';
+COMMENT ON COLUMN app_user.dis_event.after_url  IS '处置后附件URL(图片/视频,多个英文逗号分隔;解除原因为「事件已处置完成」时上传)';

+ 3 - 0
pipe-network-service/sql/early_warning_system.sql

@@ -10,6 +10,8 @@ CREATE TABLE `early_warning` (
   `warning_type` varchar(64) NOT NULL COMMENT '预警类型',
   `warning_level` varchar(32) NOT NULL COMMENT '预警级别(1-最高 2-次高 3-一般 4-常态;驾驶舱展示色为1红2黄3蓝4绿)',
   `warning_special` varchar(128) DEFAULT NULL COMMENT '预警专项',
+  `device_code` varchar(128) DEFAULT NULL COMMENT '关联设备编码',
+  `device_name` varchar(256) DEFAULT NULL COMMENT '关联设备名称',
   `location` varchar(512) DEFAULT NULL COMMENT '预警位置',
   `ownership_unit` varchar(256) DEFAULT NULL COMMENT '权属单位',
   `publisher` varchar(128) DEFAULT NULL COMMENT '发布人',
@@ -28,6 +30,7 @@ CREATE TABLE `early_warning` (
   UNIQUE KEY `uk_warning_no` (`warning_no`),
   KEY `idx_warning_type` (`warning_type`),
   KEY `idx_warning_level` (`warning_level`),
+  KEY `idx_early_warning_device_code` (`device_code`),
   KEY `idx_status` (`status`),
   KEY `idx_publish_time` (`publish_time`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预警信息表';

+ 276 - 0
pipe-network-service/sql/migration_dispatch_user_id.sql

@@ -0,0 +1,276 @@
+-- =====================================================================
+-- 应急与指挥调度模块(dispatch)—— 人员字段补存「用户ID」迁移脚本
+-- 数据库:KingbaseES(PG 兼容模式),业务 schema = app_user
+--
+-- 背景(问题):
+--   原设计只在业务表里存人员的「名称快照」(接报人 / 核验人 / 责任处置人 / 修订人 /
+--   评估人 / 创建人 / 修改人),没有存 sys_user.user_id,导致:
+--     ① 用户改名后,历史数据无法再关联到本人;
+--     ② 无法按用户维度筛选(如「查我的接报」);
+--     ③ 与日志表的 oper_user_id 口径不一致(日志早就存了 id + 名称两份)。
+--
+-- 本脚本做什么:
+--   1) 补 *_id 列(int8,与 sys_user.user_id 同类型):
+--        dis_event            reporter_id / verifier_id / handler_id / create_user_id / update_user_id
+--        dis_plan             create_user_id / update_user_id
+--        dis_plan_version     editor_id
+--        dis_event_grade      create_user_id / update_user_id
+--        dis_event_assess     assessor_id / create_user_id / update_user_id
+--        dis_plan_library     create_user_id
+--        dis_plan_match_record create_user_id
+--   2) 按名称回填存量数据的 *_id —— **仅当该名称在 sys_user 中唯一命中时才回填**,
+--      命中多条或查不到的一律留空,避免张冠李戴。
+--
+-- 口径说明(重要):
+--   · 名称列(reporter / verifier / handler / editor / assessor / create_by / update_by)
+--     **保留不删**,作为快照冗余:① 兼容存量数据;② 用户离职被删后仍能展示文字。
+--     展示口径:以 *_id 反查 sys_user.nick_name 为准,查不到再退回名称快照。
+--   · create_by / update_by 是 RuoYi 通用字段,存的是**登录名 sys_user.user_name**,
+--     故回填按 user_name 匹配;其余业务人员列存的是**昵称 sys_user.nick_name**,按 nick_name 匹配。
+--   · 不变更任何现存列的类型与取值,纯新增,不破坏既有功能。
+--   · dis_event_log / dis_plan_log 早在建模时就是 oper_user_id + oper_user_name 双字段,本脚本不涉及。
+--
+-- 幂等性:ADD COLUMN IF NOT EXISTS,可重复执行;回填只作用于 *_id IS NULL 的行。
+--
+-- ⚠️ KingbaseES 方言红线(本库实测):
+--   · `||` 在本实例被解析为**布尔 OR**,禁止用于字符串拼接,必须用 concat()。
+--   · information_schema.tables 会触发 sys_freespace() 权限校验,业务账号可能无权限,
+--     判断表是否存在请用 to_regclass()。
+-- ⚠️ 客户端执行提示:
+--   · 语句失败后事务会挂起(25P02),后续查询一律报「当前事务被终止」,先执行 ROLLBACK; 再重试。
+--   · 选中运行时不要连带注释行,客户端会把注释并入语句并吃掉一个减号,导致语法错。
+--   · 本脚本为单事务,请整体执行(BEGIN; … COMMIT;),不要逐段选中执行。
+-- =====================================================================
+
+BEGIN;
+
+-- =====================================================================
+-- 一、补列
+-- =====================================================================
+
+-- 1.1 dis_event 事件主表
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS reporter_id     int8;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS verifier_id     int8;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS handler_id      int8;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS create_user_id  int8;
+ALTER TABLE app_user.dis_event ADD COLUMN IF NOT EXISTS update_user_id  int8;
+
+COMMENT ON COLUMN app_user.dis_event.reporter_id    IS '接报人用户ID(sys_user.user_id);接报登记时固定取当前录入人员,与 reporter 快照同时写入';
+COMMENT ON COLUMN app_user.dis_event.verifier_id    IS '核验人用户ID(sys_user.user_id);初判核实时的当前登录用户';
+COMMENT ON COLUMN app_user.dis_event.handler_id     IS '责任处置人用户ID(sys_user.user_id);与 handler 快照、handler_phone 同时写入';
+COMMENT ON COLUMN app_user.dis_event.create_user_id IS '创建人用户ID(sys_user.user_id);create_by 存登录名,本列存ID';
+COMMENT ON COLUMN app_user.dis_event.update_user_id IS '最后修改人用户ID(sys_user.user_id);update_by 存登录名,本列存ID';
+
+-- 1.2 dis_plan 预案主表
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS create_user_id int8;
+ALTER TABLE app_user.dis_plan ADD COLUMN IF NOT EXISTS update_user_id int8;
+
+COMMENT ON COLUMN app_user.dis_plan.create_user_id IS '创建人用户ID(sys_user.user_id);create_by 存登录名,本列存ID';
+COMMENT ON COLUMN app_user.dis_plan.update_user_id IS '最后修改人用户ID(sys_user.user_id);update_by 存登录名,本列存ID';
+
+-- 1.3 dis_plan_version 预案版本快照表
+ALTER TABLE app_user.dis_plan_version ADD COLUMN IF NOT EXISTS editor_id int8;
+
+COMMENT ON COLUMN app_user.dis_plan_version.editor_id IS '修订人用户ID(sys_user.user_id);与 editor 名称快照同时写入';
+
+-- 1.4 dis_event_grade 事件分级标准表
+ALTER TABLE app_user.dis_event_grade ADD COLUMN IF NOT EXISTS create_user_id int8;
+ALTER TABLE app_user.dis_event_grade ADD COLUMN IF NOT EXISTS update_user_id int8;
+
+COMMENT ON COLUMN app_user.dis_event_grade.create_user_id IS '创建人用户ID(sys_user.user_id);create_by 存登录名,本列存ID';
+COMMENT ON COLUMN app_user.dis_event_grade.update_user_id IS '最后修改人用户ID(sys_user.user_id);update_by 存登录名,本列存ID';
+
+-- 1.5 dis_event_assess 事后评估表
+ALTER TABLE app_user.dis_event_assess ADD COLUMN IF NOT EXISTS assessor_id    int8;
+ALTER TABLE app_user.dis_event_assess ADD COLUMN IF NOT EXISTS create_user_id int8;
+ALTER TABLE app_user.dis_event_assess ADD COLUMN IF NOT EXISTS update_user_id int8;
+
+COMMENT ON COLUMN app_user.dis_event_assess.assessor_id    IS '评估人用户ID(sys_user.user_id);与 assessor 名称快照同时写入';
+COMMENT ON COLUMN app_user.dis_event_assess.create_user_id IS '创建人用户ID(sys_user.user_id);create_by 存登录名,本列存ID';
+COMMENT ON COLUMN app_user.dis_event_assess.update_user_id IS '最后修改人用户ID(sys_user.user_id);update_by 存登录名,本列存ID';
+
+-- 1.6 数字预案库 / 匹配记录(只有创建人,无人工操作列)
+ALTER TABLE app_user.dis_plan_library      ADD COLUMN IF NOT EXISTS create_user_id int8;
+ALTER TABLE app_user.dis_plan_match_record ADD COLUMN IF NOT EXISTS create_user_id int8;
+
+COMMENT ON COLUMN app_user.dis_plan_library.create_user_id      IS '创建人用户ID(sys_user.user_id);create_by 存登录名,本列存ID';
+COMMENT ON COLUMN app_user.dis_plan_match_record.create_user_id IS '创建人用户ID(sys_user.user_id);create_by 存登录名,本列存ID';
+
+-- 1.7 索引:列表可能按「我的接报 / 我核实的」筛选
+CREATE INDEX IF NOT EXISTS idx_dis_event_reporter_id ON app_user.dis_event(reporter_id);
+CREATE INDEX IF NOT EXISTS idx_dis_event_verifier_id ON app_user.dis_event(verifier_id);
+CREATE INDEX IF NOT EXISTS idx_dis_event_handler_id  ON app_user.dis_event(handler_id);
+
+-- =====================================================================
+-- 二、存量数据回填
+--     匹配口径:昵称列 → sys_user.nick_name;create_by/update_by → sys_user.user_name。
+--     只回填「唯一命中」的名称;同名(如两个「张伟」)或查不到的一律留空。
+-- =====================================================================
+
+-- 2.1 dis_event —— 接报人(昵称)
+UPDATE app_user.dis_event e
+   SET reporter_id = m.user_id
+  FROM (SELECT nick_name, min(user_id) AS user_id, count(*) AS cnt
+          FROM app_user.sys_user WHERE del_flag = '0' GROUP BY nick_name) m
+ WHERE e.reporter_id IS NULL
+   AND e.reporter IS NOT NULL
+   AND e.reporter = m.nick_name
+   AND m.cnt = 1;
+
+-- 2.2 dis_event —— 核验人(昵称)
+UPDATE app_user.dis_event e
+   SET verifier_id = m.user_id
+  FROM (SELECT nick_name, min(user_id) AS user_id, count(*) AS cnt
+          FROM app_user.sys_user WHERE del_flag = '0' GROUP BY nick_name) m
+ WHERE e.verifier_id IS NULL
+   AND e.verifier IS NOT NULL
+   AND e.verifier = m.nick_name
+   AND m.cnt = 1;
+
+-- 2.3 dis_event —— 责任处置人(昵称)
+UPDATE app_user.dis_event e
+   SET handler_id = m.user_id
+  FROM (SELECT nick_name, min(user_id) AS user_id, count(*) AS cnt
+          FROM app_user.sys_user WHERE del_flag = '0' GROUP BY nick_name) m
+ WHERE e.handler_id IS NULL
+   AND e.handler IS NOT NULL
+   AND e.handler = m.nick_name
+   AND m.cnt = 1;
+
+-- 2.4 dis_event —— 创建人 / 修改人(登录名)
+UPDATE app_user.dis_event e
+   SET create_user_id = m.user_id
+  FROM (SELECT user_name, min(user_id) AS user_id, count(*) AS cnt
+          FROM app_user.sys_user WHERE del_flag = '0' GROUP BY user_name) m
+ WHERE e.create_user_id IS NULL
+   AND e.create_by IS NOT NULL
+   AND e.create_by = m.user_name
+   AND m.cnt = 1;
+
+UPDATE app_user.dis_event e
+   SET update_user_id = m.user_id
+  FROM (SELECT user_name, min(user_id) AS user_id, count(*) AS cnt
+          FROM app_user.sys_user WHERE del_flag = '0' GROUP BY user_name) m
+ WHERE e.update_user_id IS NULL
+   AND e.update_by IS NOT NULL
+   AND e.update_by = m.user_name
+   AND m.cnt = 1;
+
+-- 2.5 dis_plan —— 创建人 / 修改人(登录名)
+UPDATE app_user.dis_plan p
+   SET create_user_id = m.user_id
+  FROM (SELECT user_name, min(user_id) AS user_id, count(*) AS cnt
+          FROM app_user.sys_user WHERE del_flag = '0' GROUP BY user_name) m
+ WHERE p.create_user_id IS NULL
+   AND p.create_by IS NOT NULL
+   AND p.create_by = m.user_name
+   AND m.cnt = 1;
+
+UPDATE app_user.dis_plan p
+   SET update_user_id = m.user_id
+  FROM (SELECT user_name, min(user_id) AS user_id, count(*) AS cnt
+          FROM app_user.sys_user WHERE del_flag = '0' GROUP BY user_name) m
+ WHERE p.update_user_id IS NULL
+   AND p.update_by IS NOT NULL
+   AND p.update_by = m.user_name
+   AND m.cnt = 1;
+
+-- 2.6 dis_plan_version —— 修订人(昵称)
+UPDATE app_user.dis_plan_version v
+   SET editor_id = m.user_id
+  FROM (SELECT nick_name, min(user_id) AS user_id, count(*) AS cnt
+          FROM app_user.sys_user WHERE del_flag = '0' GROUP BY nick_name) m
+ WHERE v.editor_id IS NULL
+   AND v.editor IS NOT NULL
+   AND v.editor = m.nick_name
+   AND m.cnt = 1;
+
+-- 2.7 dis_event_grade —— 创建人 / 修改人(登录名)
+UPDATE app_user.dis_event_grade g
+   SET create_user_id = m.user_id
+  FROM (SELECT user_name, min(user_id) AS user_id, count(*) AS cnt
+          FROM app_user.sys_user WHERE del_flag = '0' GROUP BY user_name) m
+ WHERE g.create_user_id IS NULL
+   AND g.create_by IS NOT NULL
+   AND g.create_by = m.user_name
+   AND m.cnt = 1;
+
+UPDATE app_user.dis_event_grade g
+   SET update_user_id = m.user_id
+  FROM (SELECT user_name, min(user_id) AS user_id, count(*) AS cnt
+          FROM app_user.sys_user WHERE del_flag = '0' GROUP BY user_name) m
+ WHERE g.update_user_id IS NULL
+   AND g.update_by IS NOT NULL
+   AND g.update_by = m.user_name
+   AND m.cnt = 1;
+
+-- 2.8 dis_event_assess —— 评估人(昵称)
+UPDATE app_user.dis_event_assess a
+   SET assessor_id = m.user_id
+  FROM (SELECT nick_name, min(user_id) AS user_id, count(*) AS cnt
+          FROM app_user.sys_user WHERE del_flag = '0' GROUP BY nick_name) m
+ WHERE a.assessor_id IS NULL
+   AND a.assessor IS NOT NULL
+   AND a.assessor = m.nick_name
+   AND m.cnt = 1;
+
+-- 2.9 dis_event_assess —— 创建人 / 修改人(登录名)
+UPDATE app_user.dis_event_assess a
+   SET create_user_id = m.user_id
+  FROM (SELECT user_name, min(user_id) AS user_id, count(*) AS cnt
+          FROM app_user.sys_user WHERE del_flag = '0' GROUP BY user_name) m
+ WHERE a.create_user_id IS NULL
+   AND a.create_by IS NOT NULL
+   AND a.create_by = m.user_name
+   AND m.cnt = 1;
+
+UPDATE app_user.dis_event_assess a
+   SET update_user_id = m.user_id
+  FROM (SELECT user_name, min(user_id) AS user_id, count(*) AS cnt
+          FROM app_user.sys_user WHERE del_flag = '0' GROUP BY user_name) m
+ WHERE a.update_user_id IS NULL
+   AND a.update_by IS NOT NULL
+   AND a.update_by = m.user_name
+   AND m.cnt = 1;
+
+-- 2.10 dis_plan_library / dis_plan_match_record —— 创建人(登录名)
+UPDATE app_user.dis_plan_library l
+   SET create_user_id = m.user_id
+  FROM (SELECT user_name, min(user_id) AS user_id, count(*) AS cnt
+          FROM app_user.sys_user WHERE del_flag = '0' GROUP BY user_name) m
+ WHERE l.create_user_id IS NULL
+   AND l.create_by IS NOT NULL
+   AND l.create_by = m.user_name
+   AND m.cnt = 1;
+
+UPDATE app_user.dis_plan_match_record r
+   SET create_user_id = m.user_id
+  FROM (SELECT user_name, min(user_id) AS user_id, count(*) AS cnt
+          FROM app_user.sys_user WHERE del_flag = '0' GROUP BY user_name) m
+ WHERE r.create_user_id IS NULL
+   AND r.create_by IS NOT NULL
+   AND r.create_by = m.user_name
+   AND m.cnt = 1;
+
+COMMIT;
+
+-- =====================================================================
+-- 三、执行后自检(另开查询窗口执行,不属于本事务)
+-- =====================================================================
+-- 1) 列是否补齐
+--    SELECT table_name, column_name FROM information_schema.columns
+--     WHERE table_schema = 'app_user' AND column_name LIKE '%_id'
+--       AND table_name IN ('dis_event','dis_plan','dis_plan_version','dis_event_grade',
+--                          'dis_event_assess','dis_plan_library','dis_plan_match_record')
+--     ORDER BY table_name, column_name;
+-- 2) 回填覆盖率(total = 有名称的行数,filled = 已回填用户ID的行数)
+--    SELECT 'dis_event.reporter' AS col, count(reporter) AS total, count(reporter_id) AS filled FROM app_user.dis_event
+--     UNION ALL SELECT 'dis_event.verifier',      count(verifier),   count(verifier_id)   FROM app_user.dis_event
+--     UNION ALL SELECT 'dis_event.handler',       count(handler),    count(handler_id)    FROM app_user.dis_event
+--     UNION ALL SELECT 'dis_event.create_by',     count(create_by),  count(create_user_id) FROM app_user.dis_event
+--     UNION ALL SELECT 'dis_plan.create_by',      count(create_by),  count(create_user_id) FROM app_user.dis_plan
+--     UNION ALL SELECT 'dis_plan_version.editor', count(editor),     count(editor_id)     FROM app_user.dis_plan_version;
+-- 3) 名称与ID是否对得上(应返回 0 行)
+--    SELECT id, reporter, reporter_id FROM app_user.dis_event e
+--     WHERE reporter_id IS NOT NULL
+--       AND NOT EXISTS (SELECT 1 FROM app_user.sys_user u WHERE u.user_id = e.reporter_id AND u.nick_name = e.reporter);

+ 0 - 0
pipe-network-service/sql/work_order_split_image_fields.sql


+ 1 - 2
pipe-network-service/zksy-admin/src/main/java/com/zksy/ZksyApplication.java

@@ -10,8 +10,7 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
  * @author zksy
  */
 @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
-public class
-ZksyApplication
+public class ZksyApplication
 {
     public static void main(String[] args)
     {

+ 156 - 55
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventController.java

@@ -6,21 +6,47 @@ import com.zksy.common.annotation.Log;
 import com.zksy.common.core.domain.AjaxResult;
 import com.zksy.common.enums.BusinessType;
 import com.zksy.dispatch.domain.DisEvent;
-import com.zksy.dispatch.domain.DisEventTrack;
-import com.zksy.dispatch.domain.DisEventTransfer;
+import com.zksy.dispatch.domain.DisEventLog;
+import com.zksy.dispatch.dto.DisDeptOptionVO;
 import com.zksy.dispatch.dto.DisEventPageDTO;
 import com.zksy.dispatch.dto.DisEventVO;
+import com.zksy.dispatch.dto.EnumOptionVO;
+import com.zksy.dispatch.dto.EventLogPageDTO;
+import com.zksy.dispatch.dto.EventReleaseDTO;
+import com.zksy.dispatch.dto.EventSaveDTO;
 import com.zksy.dispatch.dto.EventStatsVO;
+import com.zksy.dispatch.dto.EventTransferDTO;
+import com.zksy.dispatch.dto.EventTrackDTO;
+import com.zksy.dispatch.dto.EventUrgeDTO;
+import com.zksy.dispatch.dto.EventUrgePageDTO;
+import com.zksy.dispatch.dto.EventUrgeRowVO;
+import com.zksy.dispatch.dto.EventVerifyDTO;
+import com.zksy.dispatch.enums.EventStatusEnum;
+import com.zksy.dispatch.service.DisEventLogService;
 import com.zksy.dispatch.service.DisEventService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * 应急事件统一档案接口(全生命周期闭环)
- * 接报 -> 受理 -> 核实 -> 转接/报送 -> 跟踪/催办 -> 解除 -> 关闭
+ *
+ * <p>接报登记 → 初判核实 → 转接报送 → 开始处置 → 处置跟踪 → 事件解除,
+ * 外加工作催办。全部环节共用一套主表与日志接口,不再按操作拆接口。</p>
  */
 @Slf4j
 @RestController
@@ -31,16 +57,31 @@ public class DisEventController {
     @Autowired
     private DisEventService service;
 
+    @Autowired
+    private DisEventLogService eventLogService;
+
+    // ==================== 查询 ====================
+
     @GetMapping("/findByPage")
-    @ApiOperation("统一事件分页查询(多条件+状态过滤)")
+    @ApiOperation("事件分页查询(多条件 + 状态过滤,接报/核实/转接/跟踪/催办共用)")
     @Anonymous
     public AjaxResult findByPage(DisEventPageDTO dto) {
         Page<DisEventVO> page = service.findByPage(dto);
         return AjaxResult.success(page);
     }
 
+    @GetMapping("/statusOptions")
+    @ApiOperation("事件状态下拉项(EventStatusEnum,供前端筛选使用)")
+    @Anonymous
+    public AjaxResult statusOptions() {
+        List<EnumOptionVO> options = Arrays.stream(EventStatusEnum.values())
+                .map(item -> new EnumOptionVO(item.getCode(), item.getDesc()))
+                .collect(Collectors.toList());
+        return AjaxResult.success(options);
+    }
+
     @GetMapping("/getDetail/{id}")
-    @ApiOperation("事件详情(主表信息+各环节明细)")
+    @ApiOperation("事件详情(主表当前值 + 操作日志时间线)")
     @Anonymous
     public AjaxResult getDetail(@PathVariable Long id) {
         return AjaxResult.success(service.getDetail(id));
@@ -54,83 +95,143 @@ public class DisEventController {
         return AjaxResult.success(stats);
     }
 
+    @GetMapping("/logs/{eventId}")
+    @ApiOperation("事件操作日志时间线(可选按 operType 过滤,如催办记录 operType=8)")
+    @Anonymous
+    public AjaxResult logs(@PathVariable Long eventId,
+                           @RequestParam(required = false) Integer operType) {
+        List<DisEventLog> logs = eventLogService.listByEventId(eventId);
+        if (operType != null) {
+            logs = logs.stream()
+                    .filter(item -> operType.equals(item.getOperType()))
+                    .collect(Collectors.toList());
+        }
+        return AjaxResult.success(logs);
+    }
+
+    @GetMapping("/logs")
+    @ApiOperation("事件操作日志分页查询")
+    @Anonymous
+    public AjaxResult logPage(EventLogPageDTO dto) {
+        return AjaxResult.success(eventLogService.findByPage(dto));
+    }
+
+    @GetMapping("/urge/findByPage")
+    @ApiOperation("工作催办分页查询(事件 + 催办次数 + 最近催办信息)")
+    @Anonymous
+    public AjaxResult findUrgePage(EventUrgePageDTO dto) {
+        Page<EventUrgeRowVO> page = service.findUrgePage(dto);
+        return AjaxResult.success(page);
+    }
+
+    // ==================== 接报登记 ====================
+
     @PostMapping("/saveReport")
-    @ApiOperation("新增接报(自动生成事件编号,创建统一事件主表)")
+    @ApiOperation("新增接报(自动生成事件编号,状态置为待核实)")
     @Log(title = "新增事件接报", businessType = BusinessType.INSERT)
-    public AjaxResult saveReport(@RequestBody DisEvent event) {
-        DisEvent saved = service.saveReport(event);
+    public AjaxResult saveReport(@RequestBody EventSaveDTO dto) {
+        DisEvent saved = service.saveReport(dto);
         return AjaxResult.success("接报成功", saved);
     }
 
-    @PostMapping("/accept")
-    @ApiOperation("受理(待受理 -> 待核实)")
-    @Log(title = "受理事件", businessType = BusinessType.UPDATE)
-    public AjaxResult accept(@RequestBody DisEvent event) {
-        service.accept(event.getId());
-        return AjaxResult.success("受理成功");
+    @PutMapping("/updateById")
+    @ApiOperation("编辑接报信息(仅待核实可改,编号与状态不可改)")
+    @Log(title = "修改事件接报", businessType = BusinessType.UPDATE)
+    public AjaxResult updateById(@RequestBody EventSaveDTO dto) {
+        service.updateEvent(dto);
+        return AjaxResult.success("修改成功");
+    }
+
+    @DeleteMapping("/deleteById")
+    @ApiOperation("删除事件(仅待核实可删)")
+    @Log(title = "删除事件", businessType = BusinessType.DELETE)
+    public AjaxResult deleteById(@RequestParam Long id) {
+        service.deleteEvent(id);
+        return AjaxResult.success("删除成功");
+    }
+
+    // ==================== 初判核实 ====================
+
+    @GetMapping("/userOptions")
+    @ApiOperation("责任处置人下拉项(启用状态的系统用户:昵称 + 联系电话 + 所属部门)")
+    @Anonymous
+    public AjaxResult userOptions() {
+        return AjaxResult.success(service.listUserOptions());
+    }
+
+    @GetMapping("/deptOptions")
+    @ApiOperation("责任单位下拉项(正常状态的系统部门,前端据 parentId 建树)")
+    @Anonymous
+    public AjaxResult deptOptions() {
+        return AjaxResult.success(service.listDeptOptions());
+    }
+
+    @GetMapping("/home")
+    @ApiOperation("调度首页看板(统计卡 + GIS 点位 + 处置中事件 + 类型/等级分布)")
+    @Anonymous
+    public AjaxResult home() {
+        return AjaxResult.success(service.listHome());
     }
 
-    @PostMapping("/verify")
-    @ApiOperation("核实(更新主表初判信息,不实事件为终止态)")
-    @Log(title = "核实事件", businessType = BusinessType.UPDATE)
-    public AjaxResult verify(@RequestBody DisEvent event) {
-        service.verify(event);
+    @PutMapping("/verify/{id}")
+    @ApiOperation("初判核实(真实→已核实、不实→不实事件)")
+    @Log(title = "初判核实事件", businessType = BusinessType.UPDATE)
+    public AjaxResult verify(@PathVariable Long id, @RequestBody EventVerifyDTO dto) {
+        service.verify(id, dto);
         return AjaxResult.success("核实成功");
     }
 
+    // ==================== 转接 / 报送 ====================
+
     @PostMapping("/transfer")
-    @ApiOperation("转接(已核实 -> 处置中,更新责任单位)")
+    @ApiOperation("电话转接(写日志,不改事件状态)")
     @Log(title = "转接事件", businessType = BusinessType.UPDATE)
-    public AjaxResult transfer(@RequestBody DisEventTransfer transfer) {
-        service.transfer(transfer);
+    public AjaxResult transfer(@RequestBody EventTransferDTO dto) {
+        service.transfer(dto);
         return AjaxResult.success("转接成功");
     }
 
     @PostMapping("/report")
-    @ApiOperation("报送(更新报送层级与附件)")
+    @ApiOperation("上报报送(写日志,不改事件状态)")
     @Log(title = "报送事件", businessType = BusinessType.UPDATE)
-    public AjaxResult report(@RequestBody DisEventTransfer transfer) {
-        service.report(transfer);
+    public AjaxResult report(@RequestBody EventTransferDTO dto) {
+        service.report(dto);
         return AjaxResult.success("报送成功");
     }
 
+    // ==================== 处置 ====================
+
+    @PostMapping("/startDispose")
+    @ApiOperation("开始处置(已核实 → 处置中)")
+    @Log(title = "开始处置", businessType = BusinessType.UPDATE)
+    public AjaxResult startDispose(@RequestBody EventTrackDTO dto) {
+        service.startDispose(dto);
+        return AjaxResult.success("已开始处置");
+    }
+
     @PostMapping("/track")
-    @ApiOperation("跟踪(写跟踪单与日志,更新回访时间)")
-    @Log(title = "跟踪事件", businessType = BusinessType.INSERT)
-    public AjaxResult track(@RequestBody DisEventTrack track) {
-        service.track(track);
+    @ApiOperation("处置跟踪(可多次记录,不改事件状态)")
+    @Log(title = "处置跟踪", businessType = BusinessType.INSERT)
+    public AjaxResult track(@RequestBody EventTrackDTO dto) {
+        service.track(dto);
         return AjaxResult.success("跟踪记录保存成功");
     }
 
-    @PostMapping("/release")
-    @ApiOperation("解除(处置中 -> 已解除)")
+    @PostMapping("/release/{id}")
+    @ApiOperation("事件解除(处置中 → 已解除)")
     @Log(title = "解除事件", businessType = BusinessType.UPDATE)
-    public AjaxResult release(@RequestBody DisEvent event) {
-        service.release(event.getId());
+    public AjaxResult release(@PathVariable Long id, @RequestBody EventReleaseDTO dto) {
+        service.release(id, dto);
         return AjaxResult.success("事件已解除");
     }
 
-    @PostMapping("/close")
-    @ApiOperation("关闭(已解除 -> 已关闭,记录关闭原因/损失)")
-    @Log(title = "关闭事件", businessType = BusinessType.UPDATE)
-    public AjaxResult close(@RequestBody DisEvent event) {
-        service.close(event);
-        return AjaxResult.success("事件已关闭");
-    }
+    // ==================== 工作催办 ====================
 
-    @PutMapping("/updateById")
-    @ApiOperation("编辑事件基本信息(编号/状态不可改)")
-    @Log(title = "修改事件", businessType = BusinessType.UPDATE)
-    public AjaxResult updateById(@RequestBody DisEvent entity) {
-        service.updateEvent(entity);
-        return AjaxResult.success("修改成功");
-    }
-
-    @DeleteMapping("/deleteById")
-    @ApiOperation("删除事件")
-    @Log(title = "删除事件", businessType = BusinessType.DELETE)
-    public AjaxResult deleteById(@RequestParam Long id) {
-        boolean deleted = service.removeById(id);
-        return deleted ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
+    @PostMapping("/urge")
+    @ApiOperation("工作催办(写日志,不改事件状态)")
+    @Log(title = "工作催办", businessType = BusinessType.UPDATE)
+    public AjaxResult urge(@RequestBody EventUrgeDTO dto) {
+        service.urge(dto);
+        return AjaxResult.success("催办已发送");
     }
 }

+ 39 - 10
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventGradeController.java

@@ -7,13 +7,33 @@ import com.zksy.common.core.domain.AjaxResult;
 import com.zksy.common.enums.BusinessType;
 import com.zksy.dispatch.domain.DisEventGrade;
 import com.zksy.dispatch.dto.DisEventGradePageDTO;
+import com.zksy.dispatch.dto.EnumOptionVO;
+import com.zksy.dispatch.enums.GradeStatusEnum;
 import com.zksy.dispatch.service.DisEventGradeService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
 
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 应急事件分级分类标准接口
+ *
+ * <p>类别与等级取字典 {@code dis_event_category} / {@code dis_event_level}
+ * 的编码值,不再使用中文常量;状态见 {@code GradeStatusEnum}。</p>
+ */
 @Slf4j
 @RestController
 @RequestMapping("/api/dispatch/grade")
@@ -40,26 +60,35 @@ public class DisEventGradeController {
     }
 
     @GetMapping("/listByCategory")
-    @ApiOperation("根据分类查询分级标准")
+    @ApiOperation("按事件类别查询启用中的分级标准(初判核实辅助匹配)")
+    @Anonymous
+    public AjaxResult listByCategory(@RequestParam(required = false) String eventCategory) {
+        return AjaxResult.success(service.listByCategory(eventCategory));
+    }
+
+    @GetMapping("/statusOptions")
+    @ApiOperation("启用状态下拉项(GradeStatusEnum,供前端筛选使用)")
     @Anonymous
-    public AjaxResult listByCategory(@RequestParam String category) {
-        return AjaxResult.success(service.listByCategory(category));
+    public AjaxResult statusOptions() {
+        List<EnumOptionVO> options = Arrays.stream(GradeStatusEnum.values())
+                .map(item -> new EnumOptionVO(item.getCode(), item.getDesc()))
+                .collect(Collectors.toList());
+        return AjaxResult.success(options);
     }
 
     @PostMapping("/save")
-    @ApiOperation("新增分级标准")
+    @ApiOperation("新增分级标准(自动生成标准编号,默认启用)")
     @Log(title = "新增分级标准", businessType = BusinessType.INSERT)
     public AjaxResult save(@RequestBody DisEventGrade entity) {
-        boolean saved = service.save(entity);
-        return saved ? AjaxResult.success("新增成功", saved) : AjaxResult.error("新增失败");
+        return AjaxResult.success("新增成功", service.saveGrade(entity));
     }
 
     @PutMapping("/updateById")
-    @ApiOperation("修改分级标准")
+    @ApiOperation("修改分级标准(标准编号不可改)")
     @Log(title = "修改分级标准", businessType = BusinessType.UPDATE)
     public AjaxResult updateById(@RequestBody DisEventGrade entity) {
-        boolean updated = service.updateById(entity);
-        return updated ? AjaxResult.success("修改成功", updated) : AjaxResult.error("修改失败");
+        service.updateGrade(entity);
+        return AjaxResult.success("修改成功");
     }
 
     @DeleteMapping("/deleteById")

+ 0 - 65
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventPlanController.java

@@ -1,65 +0,0 @@
-package com.zksy.web.controller.dispatch;
-
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.zksy.common.annotation.Anonymous;
-import com.zksy.common.annotation.Log;
-import com.zksy.common.core.domain.AjaxResult;
-import com.zksy.common.enums.BusinessType;
-import com.zksy.dispatch.domain.DisEventPlan;
-import com.zksy.dispatch.dto.DisEventPlanPageDTO;
-import com.zksy.dispatch.service.DisEventPlanService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-@Slf4j
-@RestController
-@RequestMapping("/api/dispatch/plan")
-@Api(tags = "应急预案")
-public class DisEventPlanController {
-
-    @Autowired
-    private DisEventPlanService service;
-
-    @GetMapping("/findByPage")
-    @ApiOperation("应急预案分页查询")
-    @Anonymous
-    public AjaxResult findByPage(DisEventPlanPageDTO dto) {
-        Page<DisEventPlan> page = service.findByPage(dto);
-        return AjaxResult.success(page);
-    }
-
-    @GetMapping("/getById/{id}")
-    @ApiOperation("根据ID查询应急预案")
-    @Anonymous
-    public AjaxResult getById(@PathVariable Long id) {
-        DisEventPlan entity = service.getById(id);
-        return entity != null ? AjaxResult.success(entity) : AjaxResult.error("应急预案不存在");
-    }
-
-    @PostMapping("/save")
-    @ApiOperation("新增应急预案")
-    @Log(title = "新增应急预案", businessType = BusinessType.INSERT)
-    public AjaxResult save(@RequestBody DisEventPlan entity) {
-        boolean saved = service.save(entity);
-        return saved ? AjaxResult.success("新增成功", saved) : AjaxResult.error("新增失败");
-    }
-
-    @PutMapping("/updateById")
-    @ApiOperation("修改应急预案")
-    @Log(title = "修改应急预案", businessType = BusinessType.UPDATE)
-    public AjaxResult updateById(@RequestBody DisEventPlan entity) {
-        boolean updated = service.updateById(entity);
-        return updated ? AjaxResult.success("修改成功", updated) : AjaxResult.error("修改失败");
-    }
-
-    @DeleteMapping("/deleteById")
-    @ApiOperation("删除应急预案")
-    @Log(title = "删除应急预案", businessType = BusinessType.DELETE)
-    public AjaxResult deleteById(@RequestParam Long id) {
-        boolean deleted = service.removeById(id);
-        return deleted ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
-    }
-}

+ 0 - 74
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventRecordController.java

@@ -1,74 +0,0 @@
-package com.zksy.web.controller.dispatch;
-
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.zksy.common.annotation.Anonymous;
-import com.zksy.common.annotation.Log;
-import com.zksy.common.core.domain.AjaxResult;
-import com.zksy.common.enums.BusinessType;
-import com.zksy.dispatch.domain.DisEventRecord;
-import com.zksy.dispatch.dto.DisEventRecordPageDTO;
-import com.zksy.dispatch.dto.EventStatsVO;
-import com.zksy.dispatch.service.DisEventRecordService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-@Slf4j
-@RestController
-@RequestMapping("/api/dispatch/event-record")
-@Api(tags = "应急事件数据查询与统计")
-public class DisEventRecordController {
-
-    @Autowired
-    private DisEventRecordService service;
-
-    @GetMapping("/findByPage")
-    @ApiOperation("多维条件分页查询")
-    @Anonymous
-    public AjaxResult findByPage(DisEventRecordPageDTO dto) {
-        Page<DisEventRecord> page = service.findByPage(dto);
-        return AjaxResult.success(page);
-    }
-
-    @GetMapping("/stats")
-    @ApiOperation("多维条件统计(事件总数/损失合计/平均损失/重大及以上事件数)")
-    @Anonymous
-    public AjaxResult stats(DisEventRecordPageDTO dto) {
-        EventStatsVO stats = service.stats(dto);
-        return AjaxResult.success(stats);
-    }
-
-    @GetMapping("/getById/{id}")
-    @ApiOperation("根据ID查询事件记录")
-    @Anonymous
-    public AjaxResult getById(@PathVariable Long id) {
-        DisEventRecord entity = service.getById(id);
-        return entity != null ? AjaxResult.success(entity) : AjaxResult.error("事件记录不存在");
-    }
-
-    @PostMapping("/save")
-    @ApiOperation("新增事件记录")
-    @Log(title = "新增事件记录", businessType = BusinessType.INSERT)
-    public AjaxResult save(@RequestBody DisEventRecord entity) {
-        boolean saved = service.save(entity);
-        return saved ? AjaxResult.success("新增成功", saved) : AjaxResult.error("新增失败");
-    }
-
-    @PutMapping("/updateById")
-    @ApiOperation("修改事件记录")
-    @Log(title = "修改事件记录", businessType = BusinessType.UPDATE)
-    public AjaxResult updateById(@RequestBody DisEventRecord entity) {
-        boolean updated = service.updateById(entity);
-        return updated ? AjaxResult.success("修改成功", updated) : AjaxResult.error("修改失败");
-    }
-
-    @DeleteMapping("/deleteById")
-    @ApiOperation("删除事件记录")
-    @Log(title = "删除事件记录", businessType = BusinessType.DELETE)
-    public AjaxResult deleteById(@RequestParam Long id) {
-        boolean deleted = service.removeById(id);
-        return deleted ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
-    }
-}

+ 0 - 81
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventReportController.java

@@ -1,81 +0,0 @@
-package com.zksy.web.controller.dispatch;
-
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.zksy.common.annotation.Anonymous;
-import com.zksy.common.annotation.Log;
-import com.zksy.common.core.domain.AjaxResult;
-import com.zksy.common.enums.BusinessType;
-import com.zksy.dispatch.domain.DisEventReport;
-import com.zksy.dispatch.dto.EventReportPageInDTO;
-import com.zksy.dispatch.service.DisEventReportService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-@Slf4j
-@RestController
-@RequestMapping("/api/dispatch/report")
-@Api(tags = "接报管理")
-public class DisEventReportController {
-
-    @Autowired
-    private DisEventReportService service;
-
-    @GetMapping("/findByPage")
-    @ApiOperation("接报管理分页查询")
-    @Anonymous
-    public AjaxResult findByPage(EventReportPageInDTO dto) {
-        Page<DisEventReport> page = service.findByPage(dto);
-        return AjaxResult.success(page);
-    }
-
-    @GetMapping("/getById/{id}")
-    @ApiOperation("根据ID查询接报记录")
-    @Anonymous
-    public AjaxResult getById(@PathVariable Long id) {
-        DisEventReport entity = service.getById(id);
-        return entity != null ? AjaxResult.success(entity) : AjaxResult.error("接报记录不存在");
-    }
-
-    @PostMapping("/save")
-    @ApiOperation("新增接报记录")
-    @Log(title = "新增接报记录", businessType = BusinessType.INSERT)
-    public AjaxResult save(@RequestBody DisEventReport entity) {
-        boolean saved = service.save(entity);
-        return saved ? AjaxResult.success("新增成功", saved) : AjaxResult.error("新增失败");
-    }
-
-    @PutMapping("/updateById")
-    @ApiOperation("修改接报记录")
-    @Log(title = "修改接报记录", businessType = BusinessType.UPDATE)
-    public AjaxResult updateById(@RequestBody DisEventReport entity) {
-        boolean updated = service.updateById(entity);
-        return updated ? AjaxResult.success("修改成功", updated) : AjaxResult.error("修改失败");
-    }
-
-    @DeleteMapping("/deleteById")
-    @ApiOperation("删除接报记录")
-    @Log(title = "删除接报记录", businessType = BusinessType.DELETE)
-    public AjaxResult deleteById(@RequestParam Long id) {
-        boolean deleted = service.removeById(id);
-        return deleted ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
-    }
-
-    @PutMapping("/accept/{id}")
-    @ApiOperation("受理接报")
-    @Log(title = "受理接报", businessType = BusinessType.UPDATE)
-    public AjaxResult accept(@PathVariable Long id) {
-        boolean accepted = service.accept(id);
-        return accepted ? AjaxResult.success("受理成功") : AjaxResult.error("受理失败");
-    }
-
-    @PutMapping("/reject/{id}")
-    @ApiOperation("驳回接报")
-    @Log(title = "驳回接报", businessType = BusinessType.UPDATE)
-    public AjaxResult reject(@PathVariable Long id, @RequestParam String remark) {
-        boolean rejected = service.reject(id, remark);
-        return rejected ? AjaxResult.success("驳回成功") : AjaxResult.error("驳回失败");
-    }
-}

+ 0 - 97
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventTrackController.java

@@ -1,97 +0,0 @@
-package com.zksy.web.controller.dispatch;
-
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.zksy.common.annotation.Anonymous;
-import com.zksy.common.annotation.Log;
-import com.zksy.common.core.domain.AjaxResult;
-import com.zksy.common.enums.BusinessType;
-import com.zksy.dispatch.domain.DisEventTrack;
-import com.zksy.dispatch.domain.DisEventTrackLog;
-import com.zksy.dispatch.dto.EventTrackPageInDTO;
-import com.zksy.dispatch.service.DisEventTrackService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-@Slf4j
-@RestController
-@RequestMapping("/api/dispatch/track")
-@Api(tags = "跟踪解除")
-public class DisEventTrackController {
-
-    @Autowired
-    private DisEventTrackService service;
-
-    @GetMapping("/findByPage")
-    @ApiOperation("跟踪解除分页查询")
-    @Anonymous
-    public AjaxResult findByPage(EventTrackPageInDTO dto) {
-        Page<DisEventTrack> page = service.findByPage(dto);
-        return AjaxResult.success(page);
-    }
-
-    @GetMapping("/getById/{id}")
-    @ApiOperation("根据ID查询跟踪记录")
-    @Anonymous
-    public AjaxResult getById(@PathVariable Long id) {
-        DisEventTrack entity = service.getById(id);
-        return entity != null ? AjaxResult.success(entity) : AjaxResult.error("跟踪记录不存在");
-    }
-
-    @PostMapping("/save")
-    @ApiOperation("新增跟踪记录")
-    @Log(title = "新增跟踪记录", businessType = BusinessType.INSERT)
-    public AjaxResult save(@RequestBody DisEventTrack entity) {
-        boolean saved = service.save(entity);
-        return saved ? AjaxResult.success("新增成功", saved) : AjaxResult.error("新增失败");
-    }
-
-    @PutMapping("/updateById")
-    @ApiOperation("修改跟踪记录")
-    @Log(title = "修改跟踪记录", businessType = BusinessType.UPDATE)
-    public AjaxResult updateById(@RequestBody DisEventTrack entity) {
-        boolean updated = service.updateById(entity);
-        return updated ? AjaxResult.success("修改成功", updated) : AjaxResult.error("修改失败");
-    }
-
-    @DeleteMapping("/deleteById")
-    @ApiOperation("删除跟踪记录")
-    @Log(title = "删除跟踪记录", businessType = BusinessType.DELETE)
-    public AjaxResult deleteById(@RequestParam Long id) {
-        boolean deleted = service.removeById(id);
-        return deleted ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
-    }
-
-    @PostMapping("/addTrackLog")
-    @ApiOperation("添加跟踪日志")
-    @Log(title = "添加跟踪日志", businessType = BusinessType.INSERT)
-    public AjaxResult addTrackLog(@RequestBody DisEventTrackLog log) {
-        boolean added = service.addTrackLog(log);
-        return added ? AjaxResult.success("添加成功") : AjaxResult.error("添加失败");
-    }
-
-    @PutMapping("/release/{id}")
-    @ApiOperation("解除跟踪")
-    @Log(title = "解除跟踪", businessType = BusinessType.UPDATE)
-    public AjaxResult release(@PathVariable Long id) {
-        boolean released = service.release(id);
-        return released ? AjaxResult.success("解除成功") : AjaxResult.error("解除失败");
-    }
-
-    @PutMapping("/close/{id}")
-    @ApiOperation("关闭跟踪")
-    @Log(title = "关闭跟踪", businessType = BusinessType.UPDATE)
-    public AjaxResult close(@PathVariable Long id) {
-        boolean closed = service.close(id);
-        return closed ? AjaxResult.success("关闭成功") : AjaxResult.error("关闭失败");
-    }
-
-    @GetMapping("/getTrackLogs/{trackId}")
-    @ApiOperation("查询跟踪日志")
-    @Anonymous
-    public AjaxResult getTrackLogs(@PathVariable Long trackId) {
-        return AjaxResult.success(service.getTrackLogs(trackId));
-    }
-}

+ 0 - 89
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventTransferController.java

@@ -1,89 +0,0 @@
-package com.zksy.web.controller.dispatch;
-
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.zksy.common.annotation.Anonymous;
-import com.zksy.common.annotation.Log;
-import com.zksy.common.core.domain.AjaxResult;
-import com.zksy.common.enums.BusinessType;
-import com.zksy.dispatch.domain.DisEventTransfer;
-import com.zksy.dispatch.dto.EventReportDTO;
-import com.zksy.dispatch.dto.EventTransferPageInDTO;
-import com.zksy.dispatch.service.DisEventTransferService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-@Slf4j
-@RestController
-@RequestMapping("/api/dispatch/transfer")
-@Api(tags = "转接报送")
-public class DisEventTransferController {
-
-    @Autowired
-    private DisEventTransferService service;
-
-    @GetMapping("/findByPage")
-    @ApiOperation("转接报送分页查询")
-    @Anonymous
-    public AjaxResult findByPage(EventTransferPageInDTO dto) {
-        Page<DisEventTransfer> page = service.findByPage(dto);
-        return AjaxResult.success(page);
-    }
-
-    @GetMapping("/getById/{id}")
-    @ApiOperation("根据ID查询转接记录")
-    @Anonymous
-    public AjaxResult getById(@PathVariable Long id) {
-        DisEventTransfer entity = service.getById(id);
-        return entity != null ? AjaxResult.success(entity) : AjaxResult.error("转接记录不存在");
-    }
-
-    @PostMapping("/save")
-    @ApiOperation("新增转接记录")
-    @Log(title = "新增转接记录", businessType = BusinessType.INSERT)
-    public AjaxResult save(@RequestBody DisEventTransfer entity) {
-        boolean saved = service.save(entity);
-        return saved ? AjaxResult.success("新增成功", saved) : AjaxResult.error("新增失败");
-    }
-
-    @PutMapping("/updateById")
-    @ApiOperation("修改转接记录")
-    @Log(title = "修改转接记录", businessType = BusinessType.UPDATE)
-    public AjaxResult updateById(@RequestBody DisEventTransfer entity) {
-        boolean updated = service.updateById(entity);
-        return updated ? AjaxResult.success("修改成功", updated) : AjaxResult.error("修改失败");
-    }
-
-    @DeleteMapping("/deleteById")
-    @ApiOperation("删除转接记录")
-    @Log(title = "删除转接记录", businessType = BusinessType.DELETE)
-    public AjaxResult deleteById(@RequestParam Long id) {
-        boolean deleted = service.removeById(id);
-        return deleted ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
-    }
-
-    @PutMapping("/doTransfer")
-    @ApiOperation("执行转接")
-    @Log(title = "执行转接", businessType = BusinessType.UPDATE)
-    public AjaxResult doTransfer(@RequestBody DisEventTransfer entity) {
-        boolean transferred = service.doTransfer(entity);
-        return transferred ? AjaxResult.success("转接成功") : AjaxResult.error("转接失败");
-    }
-
-    @PutMapping("/doReport")
-    @ApiOperation("执行报送")
-    @Log(title = "执行报送", businessType = BusinessType.UPDATE)
-    public AjaxResult doReport(@RequestBody EventReportDTO dto) {
-        boolean reported = service.doReport(dto);
-        return reported ? AjaxResult.success("报送成功") : AjaxResult.error("报送失败");
-    }
-
-    @GetMapping("/getTransferLogs/{transferId}")
-    @ApiOperation("查询转接日志")
-    @Anonymous
-    public AjaxResult getTransferLogs(@PathVariable Long transferId) {
-        return AjaxResult.success(service.getTransferLogs(transferId));
-    }
-}

+ 0 - 89
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventUrgeController.java

@@ -1,89 +0,0 @@
-package com.zksy.web.controller.dispatch;
-
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.zksy.common.annotation.Anonymous;
-import com.zksy.common.annotation.Log;
-import com.zksy.common.core.domain.AjaxResult;
-import com.zksy.common.enums.BusinessType;
-import com.zksy.dispatch.domain.DisEventUrge;
-import com.zksy.dispatch.domain.DisEventUrgeDetail;
-import com.zksy.dispatch.dto.EventUrgePageInDTO;
-import com.zksy.dispatch.service.DisEventUrgeService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-@Slf4j
-@RestController
-@RequestMapping("/api/dispatch/urge")
-@Api(tags = "工作催办")
-public class DisEventUrgeController {
-
-    @Autowired
-    private DisEventUrgeService service;
-
-    @GetMapping("/findByPage")
-    @ApiOperation("工作催办分页查询")
-    @Anonymous
-    public AjaxResult findByPage(EventUrgePageInDTO dto) {
-        Page<DisEventUrge> page = service.findByPage(dto);
-        return AjaxResult.success(page);
-    }
-
-    @GetMapping("/getById/{id}")
-    @ApiOperation("根据ID查询催办记录")
-    @Anonymous
-    public AjaxResult getById(@PathVariable Long id) {
-        DisEventUrge entity = service.getById(id);
-        return entity != null ? AjaxResult.success(entity) : AjaxResult.error("催办记录不存在");
-    }
-
-    @PostMapping("/save")
-    @ApiOperation("新增催办记录")
-    @Log(title = "新增催办记录", businessType = BusinessType.INSERT)
-    public AjaxResult save(@RequestBody DisEventUrge entity) {
-        boolean saved = service.save(entity);
-        return saved ? AjaxResult.success("新增成功", saved) : AjaxResult.error("新增失败");
-    }
-
-    @PutMapping("/updateById")
-    @ApiOperation("修改催办记录")
-    @Log(title = "修改催办记录", businessType = BusinessType.UPDATE)
-    public AjaxResult updateById(@RequestBody DisEventUrge entity) {
-        boolean updated = service.updateById(entity);
-        return updated ? AjaxResult.success("修改成功", updated) : AjaxResult.error("修改失败");
-    }
-
-    @DeleteMapping("/deleteById")
-    @ApiOperation("删除催办记录")
-    @Log(title = "删除催办记录", businessType = BusinessType.DELETE)
-    public AjaxResult deleteById(@RequestParam Long id) {
-        boolean deleted = service.removeById(id);
-        return deleted ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
-    }
-
-    @PostMapping("/doUrge")
-    @ApiOperation("执行催办")
-    @Log(title = "执行催办", businessType = BusinessType.INSERT)
-    public AjaxResult doUrge(@RequestBody DisEventUrgeDetail detail) {
-        boolean urged = service.doUrge(detail);
-        return urged ? AjaxResult.success("催办成功") : AjaxResult.error("催办失败");
-    }
-
-    @PutMapping("/closeUrge/{id}")
-    @ApiOperation("办结催办")
-    @Log(title = "办结催办", businessType = BusinessType.UPDATE)
-    public AjaxResult closeUrge(@PathVariable Long id) {
-        boolean closed = service.closeUrge(id);
-        return closed ? AjaxResult.success("办结成功") : AjaxResult.error("办结失败");
-    }
-
-    @GetMapping("/getUrgeDetails/{urgeId}")
-    @ApiOperation("查询催办明细")
-    @Anonymous
-    public AjaxResult getUrgeDetails(@PathVariable Long urgeId) {
-        return AjaxResult.success(service.getUrgeDetails(urgeId));
-    }
-}

+ 0 - 73
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisEventVerifyController.java

@@ -1,73 +0,0 @@
-package com.zksy.web.controller.dispatch;
-
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.zksy.common.annotation.Anonymous;
-import com.zksy.common.annotation.Log;
-import com.zksy.common.core.domain.AjaxResult;
-import com.zksy.common.enums.BusinessType;
-import com.zksy.dispatch.domain.DisEventVerify;
-import com.zksy.dispatch.dto.EventVerifyPageInDTO;
-import com.zksy.dispatch.service.DisEventVerifyService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-@Slf4j
-@RestController
-@RequestMapping("/api/dispatch/verify")
-@Api(tags = "初判核实")
-public class DisEventVerifyController {
-
-    @Autowired
-    private DisEventVerifyService service;
-
-    @GetMapping("/findByPage")
-    @ApiOperation("初判核实分页查询")
-    @Anonymous
-    public AjaxResult findByPage(EventVerifyPageInDTO dto) {
-        Page<DisEventVerify> page = service.findByPage(dto);
-        return AjaxResult.success(page);
-    }
-
-    @GetMapping("/getById/{id}")
-    @ApiOperation("根据ID查询核实记录")
-    @Anonymous
-    public AjaxResult getById(@PathVariable Long id) {
-        DisEventVerify entity = service.getById(id);
-        return entity != null ? AjaxResult.success(entity) : AjaxResult.error("核实记录不存在");
-    }
-
-    @PostMapping("/save")
-    @ApiOperation("新增核实记录")
-    @Log(title = "新增核实记录", businessType = BusinessType.INSERT)
-    public AjaxResult save(@RequestBody DisEventVerify entity) {
-        boolean saved = service.save(entity);
-        return saved ? AjaxResult.success("新增成功", saved) : AjaxResult.error("新增失败");
-    }
-
-    @PutMapping("/updateById")
-    @ApiOperation("修改核实记录")
-    @Log(title = "修改核实记录", businessType = BusinessType.UPDATE)
-    public AjaxResult updateById(@RequestBody DisEventVerify entity) {
-        boolean updated = service.updateById(entity);
-        return updated ? AjaxResult.success("修改成功", updated) : AjaxResult.error("修改失败");
-    }
-
-    @DeleteMapping("/deleteById")
-    @ApiOperation("删除核实记录")
-    @Log(title = "删除核实记录", businessType = BusinessType.DELETE)
-    public AjaxResult deleteById(@RequestParam Long id) {
-        boolean deleted = service.removeById(id);
-        return deleted ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
-    }
-
-    @PutMapping("/verify")
-    @ApiOperation("核实事件")
-    @Log(title = "核实事件", businessType = BusinessType.UPDATE)
-    public AjaxResult verify(@RequestBody DisEventVerify entity) {
-        boolean verified = service.verify(entity);
-        return verified ? AjaxResult.success("核实成功") : AjaxResult.error("核实失败");
-    }
-}

+ 0 - 105
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisPlanApprovalController.java

@@ -1,105 +0,0 @@
-package com.zksy.web.controller.dispatch;
-
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.zksy.common.annotation.Anonymous;
-import com.zksy.common.annotation.Log;
-import com.zksy.common.core.domain.AjaxResult;
-import com.zksy.common.enums.BusinessType;
-import com.zksy.dispatch.domain.DisPlanApproval;
-import com.zksy.dispatch.dto.DisPlanApprovalPageDTO;
-import com.zksy.dispatch.service.DisPlanApprovalService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-@Slf4j
-@RestController
-@RequestMapping("/api/dispatch/plan-approval")
-@Api(tags = "应急预案审批")
-public class DisPlanApprovalController {
-
-    @Autowired
-    private DisPlanApprovalService service;
-
-    @GetMapping("/findByPage")
-    @ApiOperation("应急预案审批分页查询")
-    @Anonymous
-    public AjaxResult findByPage(DisPlanApprovalPageDTO dto) {
-        Page<DisPlanApproval> page = service.findByPage(dto);
-        return AjaxResult.success(page);
-    }
-
-    @GetMapping("/getById/{id}")
-    @ApiOperation("根据ID查询应急预案审批")
-    @Anonymous
-    public AjaxResult getById(@PathVariable Long id) {
-        DisPlanApproval entity = service.getById(id);
-        return entity != null ? AjaxResult.success(entity) : AjaxResult.error("审批记录不存在");
-    }
-
-    @PostMapping("/save")
-    @ApiOperation("新增应急预案审批")
-    @Log(title = "新增应急预案审批", businessType = BusinessType.INSERT)
-    public AjaxResult save(@RequestBody DisPlanApproval entity) {
-        boolean saved = service.save(entity);
-        return saved ? AjaxResult.success("新增成功", saved) : AjaxResult.error("新增失败");
-    }
-
-    @PutMapping("/updateById")
-    @ApiOperation("修改应急预案审批")
-    @Log(title = "修改应急预案审批", businessType = BusinessType.UPDATE)
-    public AjaxResult updateById(@RequestBody DisPlanApproval entity) {
-        boolean updated = service.updateById(entity);
-        return updated ? AjaxResult.success("修改成功", updated) : AjaxResult.error("修改失败");
-    }
-
-    @DeleteMapping("/deleteById")
-    @ApiOperation("删除应急预案审批")
-    @Log(title = "删除应急预案审批", businessType = BusinessType.DELETE)
-    public AjaxResult deleteById(@RequestParam Long id) {
-        boolean deleted = service.removeById(id);
-        return deleted ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
-    }
-
-    @PutMapping("/submitForApproval/{id}")
-    @ApiOperation("提交审批")
-    @Log(title = "提交审批", businessType = BusinessType.UPDATE)
-    public AjaxResult submitForApproval(@PathVariable Long id) {
-        boolean result = service.submitForApproval(id);
-        return result ? AjaxResult.success("提交审批成功") : AjaxResult.error("提交审批失败,请检查状态");
-    }
-
-    @PutMapping("/approve/{id}")
-    @ApiOperation("初审通过")
-    @Log(title = "初审通过", businessType = BusinessType.UPDATE)
-    public AjaxResult approve(@PathVariable Long id, @RequestParam(required = false) String opinion) {
-        boolean result = service.approve(id, opinion);
-        return result ? AjaxResult.success("初审通过") : AjaxResult.error("审批失败,请检查状态");
-    }
-
-    @PutMapping("/reject/{id}")
-    @ApiOperation("驳回")
-    @Log(title = "驳回审批", businessType = BusinessType.UPDATE)
-    public AjaxResult reject(@PathVariable Long id, @RequestParam(required = false) String opinion) {
-        boolean result = service.reject(id, opinion);
-        return result ? AjaxResult.success("已驳回") : AjaxResult.error("驳回失败,请检查状态");
-    }
-
-    @PutMapping("/publish/{id}")
-    @ApiOperation("终审通过并发布")
-    @Log(title = "终审通过并发布", businessType = BusinessType.UPDATE)
-    public AjaxResult publish(@PathVariable Long id, @RequestParam(required = false) String opinion) {
-        boolean result = service.publish(id, opinion);
-        return result ? AjaxResult.success("已发布") : AjaxResult.error("发布失败,请检查状态");
-    }
-
-    @PostMapping("/submit/{planId}")
-    @ApiOperation("根据预案ID创建审批并提交")
-    @Log(title = "提交审批", businessType = BusinessType.INSERT)
-    public AjaxResult submitFromPlan(@PathVariable Long planId) {
-        boolean result = service.createFromPlan(planId);
-        return result ? AjaxResult.success("提交审批成功") : AjaxResult.error("提交审批失败");
-    }
-}

+ 0 - 72
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisPlanApprovalStepController.java

@@ -1,72 +0,0 @@
-package com.zksy.web.controller.dispatch;
-
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.zksy.common.annotation.Anonymous;
-import com.zksy.common.annotation.Log;
-import com.zksy.common.core.domain.AjaxResult;
-import com.zksy.common.enums.BusinessType;
-import com.zksy.dispatch.domain.DisPlanApprovalStep;
-import com.zksy.dispatch.dto.DisPlanApprovalStepPageDTO;
-import com.zksy.dispatch.service.DisPlanApprovalStepService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-@Slf4j
-@RestController
-@RequestMapping("/api/dispatch/plan-approval-step")
-@Api(tags = "应急预案审批步骤")
-public class DisPlanApprovalStepController {
-
-    @Autowired
-    private DisPlanApprovalStepService service;
-
-    @GetMapping("/findByPage")
-    @ApiOperation("审批步骤分页查询")
-    @Anonymous
-    public AjaxResult findByPage(DisPlanApprovalStepPageDTO dto) {
-        Page<DisPlanApprovalStep> page = service.findByPage(dto);
-        return AjaxResult.success(page);
-    }
-
-    @GetMapping("/getById/{id}")
-    @ApiOperation("根据ID查询审批步骤")
-    @Anonymous
-    public AjaxResult getById(@PathVariable Long id) {
-        DisPlanApprovalStep entity = service.getById(id);
-        return entity != null ? AjaxResult.success(entity) : AjaxResult.error("审批步骤不存在");
-    }
-
-    @GetMapping("/listByApprovalId")
-    @ApiOperation("根据审批主表ID查询步骤列表")
-    @Anonymous
-    public AjaxResult listByApprovalId(@RequestParam Long approvalId) {
-        return AjaxResult.success(service.listByApprovalId(approvalId));
-    }
-
-    @PostMapping("/save")
-    @ApiOperation("新增审批步骤")
-    @Log(title = "新增审批步骤", businessType = BusinessType.INSERT)
-    public AjaxResult save(@RequestBody DisPlanApprovalStep entity) {
-        boolean saved = service.save(entity);
-        return saved ? AjaxResult.success("新增成功", saved) : AjaxResult.error("新增失败");
-    }
-
-    @PutMapping("/updateById")
-    @ApiOperation("修改审批步骤")
-    @Log(title = "修改审批步骤", businessType = BusinessType.UPDATE)
-    public AjaxResult updateById(@RequestBody DisPlanApprovalStep entity) {
-        boolean updated = service.updateById(entity);
-        return updated ? AjaxResult.success("修改成功", updated) : AjaxResult.error("修改失败");
-    }
-
-    @DeleteMapping("/deleteById")
-    @ApiOperation("删除审批步骤")
-    @Log(title = "删除审批步骤", businessType = BusinessType.DELETE)
-    public AjaxResult deleteById(@RequestParam Long id) {
-        boolean deleted = service.removeById(id);
-        return deleted ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
-    }
-}

+ 0 - 75
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisPlanCategoryController.java

@@ -1,75 +0,0 @@
-package com.zksy.web.controller.dispatch;
-
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.zksy.common.annotation.Anonymous;
-import com.zksy.common.annotation.Log;
-import com.zksy.common.core.domain.AjaxResult;
-import com.zksy.common.enums.BusinessType;
-import com.zksy.dispatch.domain.DisPlanCategory;
-import com.zksy.dispatch.dto.DisPlanCategoryPageDTO;
-import com.zksy.dispatch.service.DisPlanCategoryService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-
-@Slf4j
-@RestController
-@RequestMapping("/api/dispatch/plan-category")
-@Api(tags = "预案分类")
-public class DisPlanCategoryController {
-
-    @Autowired
-    private DisPlanCategoryService service;
-
-    @GetMapping("/findByPage")
-    @ApiOperation("预案分类分页查询")
-    @Anonymous
-    public AjaxResult findByPage(DisPlanCategoryPageDTO dto) {
-        Page<DisPlanCategory> page = service.findByPage(dto);
-        return AjaxResult.success(page);
-    }
-
-    @GetMapping("/getById/{id}")
-    @ApiOperation("根据ID查询预案分类")
-    @Anonymous
-    public AjaxResult getById(@PathVariable Long id) {
-        DisPlanCategory entity = service.getById(id);
-        return entity != null ? AjaxResult.success(entity) : AjaxResult.error("预案分类不存在");
-    }
-
-    @GetMapping("/getCategoryTree")
-    @ApiOperation("获取分类树")
-    @Anonymous
-    public AjaxResult getCategoryTree() {
-        List<DisPlanCategory> tree = service.getCategoryTree();
-        return AjaxResult.success(tree);
-    }
-
-    @PostMapping("/save")
-    @ApiOperation("新增预案分类")
-    @Log(title = "新增预案分类", businessType = BusinessType.INSERT)
-    public AjaxResult save(@RequestBody DisPlanCategory entity) {
-        boolean saved = service.save(entity);
-        return saved ? AjaxResult.success("新增成功", saved) : AjaxResult.error("新增失败");
-    }
-
-    @PutMapping("/updateById")
-    @ApiOperation("修改预案分类")
-    @Log(title = "修改预案分类", businessType = BusinessType.UPDATE)
-    public AjaxResult updateById(@RequestBody DisPlanCategory entity) {
-        boolean updated = service.updateById(entity);
-        return updated ? AjaxResult.success("修改成功", updated) : AjaxResult.error("修改失败");
-    }
-
-    @DeleteMapping("/deleteById")
-    @ApiOperation("删除预案分类")
-    @Log(title = "删除预案分类", businessType = BusinessType.DELETE)
-    public AjaxResult deleteById(@RequestParam Long id) {
-        boolean deleted = service.removeById(id);
-        return deleted ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
-    }
-}

+ 140 - 0
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DisPlanController.java

@@ -0,0 +1,140 @@
+package com.zksy.web.controller.dispatch;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zksy.common.annotation.Anonymous;
+import com.zksy.common.annotation.Log;
+import com.zksy.common.core.domain.AjaxResult;
+import com.zksy.common.enums.BusinessType;
+import com.zksy.dispatch.domain.DisPlan;
+import com.zksy.dispatch.dto.EnumOptionVO;
+import com.zksy.dispatch.dto.PlanApproveDTO;
+import com.zksy.dispatch.dto.PlanPageDTO;
+import com.zksy.dispatch.dto.PlanSaveDTO;
+import com.zksy.dispatch.enums.PlanStatusEnum;
+import com.zksy.dispatch.service.DisPlanService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 应急预案接口(编辑 + 审批合并)
+ *
+ * <p>原型「应急预案编辑」与「应急预案审批」是同一个预案的两个视图,共用本组接口:
+ * 编辑页写草稿 / 提交审核,审批页按 status=1 过滤后审批,不再跨表复制数据。</p>
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/dispatch/plan")
+@Api(tags = "应急预案(编辑 + 审批)")
+public class DisPlanController {
+
+    @Autowired
+    private DisPlanService service;
+
+    @GetMapping("/findByPage")
+    @ApiOperation("预案分页查询(审批页传 status=1 只看待审核)")
+    @Anonymous
+    public AjaxResult findByPage(PlanPageDTO dto) {
+        Page<DisPlan> page = service.findByPage(dto);
+        return AjaxResult.success(page);
+    }
+
+    @GetMapping("/getDetail/{id}")
+    @ApiOperation("预案详情")
+    @Anonymous
+    public AjaxResult getDetail(@PathVariable Long id) {
+        return AjaxResult.success(service.getDetail(id));
+    }
+
+    @GetMapping("/statusOptions")
+    @ApiOperation("预案状态下拉项(PlanStatusEnum,供前端筛选使用)")
+    @Anonymous
+    public AjaxResult statusOptions() {
+        List<EnumOptionVO> options = Arrays.stream(PlanStatusEnum.values())
+                .map(item -> new EnumOptionVO(item.getCode(), item.getDesc()))
+                .collect(Collectors.toList());
+        return AjaxResult.success(options);
+    }
+
+    @GetMapping("/templates")
+    @ApiOperation("预案模板列表(新建预案时可选模板套用标准章节结构)")
+    @Anonymous
+    public AjaxResult templates(PlanPageDTO dto) {
+        dto.setIsTemplate(1);
+        return AjaxResult.success(service.findByPage(dto));
+    }
+
+    @GetMapping("/versions/{planId}")
+    @ApiOperation("预案版本列表(当前生效版本在前,不含回滚)")
+    @Anonymous
+    public AjaxResult versions(@PathVariable Long planId) {
+        return AjaxResult.success(service.listVersions(planId));
+    }
+
+    @GetMapping("/version/{versionId}")
+    @ApiOperation("单个版本正文快照")
+    @Anonymous
+    public AjaxResult version(@PathVariable Long versionId) {
+        return AjaxResult.success(service.getVersion(versionId));
+    }
+
+    @GetMapping("/logs/{planId}")
+    @ApiOperation("预案操作日志(新建/提交审核/审批通过/审批驳回/修订)")
+    @Anonymous
+    public AjaxResult logs(@PathVariable Long planId) {
+        return AjaxResult.success(service.listLogs(planId));
+    }
+
+    @PostMapping("/save")
+    @ApiOperation("新增草稿 / 修改预案 / 修订已通过预案(修订会生成审核中的候选版本)")
+    @Log(title = "保存预案", businessType = BusinessType.INSERT)
+    public AjaxResult save(@RequestBody PlanSaveDTO dto) {
+        DisPlan saved = service.savePlan(dto);
+        return AjaxResult.success("保存成功", saved);
+    }
+
+    @DeleteMapping("/deleteById")
+    @ApiOperation("删除预案(仅草稿与已驳回可删)")
+    @Log(title = "删除预案", businessType = BusinessType.DELETE)
+    public AjaxResult deleteById(@RequestParam Long id) {
+        service.deletePlan(id);
+        return AjaxResult.success("删除成功");
+    }
+
+    @PostMapping("/submit/{id}")
+    @ApiOperation("提交审核(草稿 / 已驳回 → 待审核)")
+    @Log(title = "提交预案审核", businessType = BusinessType.UPDATE)
+    public AjaxResult submit(@PathVariable Long id) {
+        service.submit(id);
+        return AjaxResult.success("提交审核成功");
+    }
+
+    @PostMapping("/approve/{id}")
+    @ApiOperation("审批通过(候选版本生效,旧版本降为历史)")
+    @Log(title = "审批通过预案", businessType = BusinessType.UPDATE)
+    public AjaxResult approve(@PathVariable Long id, @RequestBody(required = false) PlanApproveDTO dto) {
+        service.approve(id, dto == null ? new PlanApproveDTO() : dto);
+        return AjaxResult.success("审批通过");
+    }
+
+    @PostMapping("/reject/{id}")
+    @ApiOperation("审批不通过(候选版本驳回,原生效版本仍生效)")
+    @Log(title = "驳回预案", businessType = BusinessType.UPDATE)
+    public AjaxResult reject(@PathVariable Long id, @RequestBody PlanApproveDTO dto) {
+        service.reject(id, dto);
+        return AjaxResult.success("已驳回");
+    }
+}

+ 61 - 0
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/DispatchNotifyController.java

@@ -0,0 +1,61 @@
+package com.zksy.web.controller.dispatch;
+
+import com.zksy.common.core.domain.AjaxResult;
+import com.zksy.dispatch.domain.DispatchNotify;
+import com.zksy.dispatch.service.DispatchNotifyService;
+import com.zksy.dispatch.support.DispatchUserSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 催办系统消息通知
+ *
+ * <p>消息按当前登录用户隔离(与模块其他接口不同,这里**不加** @Anonymous:
+ * 未读数/已读标记都依赖登录身份)。前端在调度首页铃铛面板消费。</p>
+ *
+ * @author zengy
+ */
+@Api(tags = "应急-催办系统消息")
+@RestController
+@RequestMapping("/api/dispatch/notify")
+@RequiredArgsConstructor
+public class DispatchNotifyController {
+
+    private final DispatchNotifyService notifyService;
+
+    @GetMapping("/my")
+    @ApiOperation("我的催办消息(未读在前,最近 limit 条)+ 未读数")
+    public AjaxResult my(@RequestParam(defaultValue = "20") int limit) {
+        Long userId = DispatchUserSupport.currentUserId();
+        List<DispatchNotify> list = notifyService.listMy(userId, limit);
+        Map<String, Object> data = new HashMap<>();
+        data.put("unreadCount", notifyService.countUnread(userId));
+        data.put("list", list);
+        return AjaxResult.success(data);
+    }
+
+    @PostMapping("/read/{id}")
+    @ApiOperation("标记单条已读")
+    public AjaxResult read(@PathVariable Long id) {
+        notifyService.markRead(DispatchUserSupport.currentUserId(), id);
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/readAll")
+    @ApiOperation("全部标记已读")
+    public AjaxResult readAll() {
+        notifyService.markAllRead(DispatchUserSupport.currentUserId());
+        return AjaxResult.success();
+    }
+}

+ 56 - 0
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/dispatch/SseController.java

@@ -0,0 +1,56 @@
+package com.zksy.web.controller.dispatch;
+
+import com.zksy.common.core.domain.model.LoginUser;
+import com.zksy.common.utils.StringUtils;
+import com.zksy.dispatch.support.SseEmitterManager;
+import com.zksy.framework.web.service.TokenService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+/**
+ * SSE 实时消息连接端点(轻量版,参照 RuoYi-Vue-Plus 5.x /resource/sse 设计)
+ *
+ * <p><b>鉴权说明</b>:EventSource 不允许自定义请求头,无法走常规 Authorization 头;
+ * 约定前端在 URL 上携带 {@code ?Authorization=Bearer <token>},本端点手动校验
+ * ({@code TokenService#getLoginUserByToken})。端点已在 SecurityConfig 放行,
+ * 未携带/无效 token 时直接抛 401。</p>
+ *
+ * @author zengy
+ */
+@Api(tags = "应急-SSE实时消息")
+@RestController
+@RequestMapping("/api/dispatch/sse")
+@RequiredArgsConstructor
+public class SseController {
+
+    /** 0L = 永不超时;断连由客户端/网络触发,走回调清理 */
+    private static final Long NO_TIMEOUT = 0L;
+
+    private final SseEmitterManager sseEmitterManager;
+    private final TokenService tokenService;
+
+    @GetMapping(value = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+    @ApiOperation("建立 SSE 连接(URL 携带 token)")
+    public SseEmitter connect(@RequestParam("Authorization") String authorization) {
+        String token = StringUtils.isNotEmpty(authorization) && authorization.startsWith("Bearer ")
+                ? authorization.replace("Bearer ", "") : authorization;
+        LoginUser loginUser = tokenService.getLoginUserByToken(token);
+        if (loginUser == null || loginUser.getUserId() == null) {
+            throw new org.springframework.security.access.AccessDeniedException("SSE 连接凭证无效");
+        }
+        Long userId = loginUser.getUserId();
+        SseEmitter emitter = new SseEmitter(NO_TIMEOUT);
+        sseEmitterManager.connect(userId, emitter);
+        emitter.onCompletion(() -> sseEmitterManager.remove(userId, emitter));
+        emitter.onTimeout(() -> sseEmitterManager.remove(userId, emitter));
+        emitter.onError(e -> sseEmitterManager.remove(userId, emitter));
+        return emitter;
+    }
+}

+ 4 - 0
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/GasAlarmController.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.zksy.base.alarm.domain.AlarmData;
 import com.zksy.base.alarm.service.AlarmDataService;
+import com.zksy.base.constant.GasEquipmentTypeEnum;
 import com.zksy.base.domain.EquipmentBase;
 import com.zksy.base.domain.EquipmentType;
 import com.zksy.base.enums.EquipmentTypeEnum;
@@ -16,10 +17,12 @@ import com.zksy.ops.service.WorkOrderService;
 import com.zksy.base.constant.GasEquipmentTypeEnum;
 import com.zksy.common.annotation.Anonymous;
 import com.zksy.common.core.domain.AjaxResult;
+import com.zksy.common.utils.SecurityUtils;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.*;
@@ -130,6 +133,7 @@ public class GasAlarmController {
             m.put("equipmentName", eq.getEquipmentName());
             m.put("location", eq.getEquipmentLocation());
             m.put("hasAlarm", latest != null && latest.getAlarmStatus() != null && latest.getAlarmStatus() == 0);
+            m.put("latestAlarmId", latest != null ? latest.getId() : null);
             m.put("latestConcentration", latest != null ? latest.getActualValue() : null);
             m.put("latestThreshold", latest != null ? latest.getMaxValue() : null);
             m.put("latestCreateTime", latest != null && latest.getCreateTime() != null ? latest.getCreateTime().format(FMT) : null);

+ 8 - 4
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/GasPipeNetworkController.java

@@ -32,7 +32,8 @@ public class GasPipeNetworkController {
     @ApiOperation("管网分页查询")
     @Anonymous
     public AjaxResult findByPage(PipeNetworkPageInDTO dto) {
-        Page<PipeNetworkBase> page = service.queryByPage(dto);
+        // 燃气模块:仅查询 network_type=0 的燃气设施
+        Page<PipeNetworkBase> page = service.queryGasByPage(dto);
         return AjaxResult.success(page);
     }
 
@@ -40,7 +41,8 @@ public class GasPipeNetworkController {
     @ApiOperation("根据ID查询管网")
     @Anonymous
     public AjaxResult getById(@PathVariable String id) {
-        PipeNetworkBase entity = service.getById(id);
+        // 燃气模块:仅查询 network_type=0 的燃气设施
+        PipeNetworkBase entity = service.getGasById(id);
         return entity != null ? AjaxResult.success(entity) : AjaxResult.error("管网信息不存在");
     }
 
@@ -48,7 +50,8 @@ public class GasPipeNetworkController {
     @ApiOperation("管网下拉选项")
     @Anonymous
     public AjaxResult options() {
-        return AjaxResult.success(service.getOptions());
+        // 燃气模块:仅查询 network_type=0 的燃气设施
+        return AjaxResult.success(service.getGasOptions());
     }
 
     @PostMapping("/save")
@@ -81,7 +84,8 @@ public class GasPipeNetworkController {
     public void exportData(HttpServletResponse response,
                            @RequestParam(required = false) String networkName,
                            @RequestParam(required = false) String status) {
-        List<PipeNetworkExportDTO> list = service.exportData(networkName, status);
+        // 燃气模块:仅查询 network_type=0 的燃气设施
+        List<PipeNetworkExportDTO> list = service.exportGasData(networkName, status);
         ExcelUtil<PipeNetworkExportDTO> util = new ExcelUtil<>(PipeNetworkExportDTO.class);
         util.exportExcel(response, list, "燃气管网数据");
     }

+ 6 - 1
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/GasRepairRecordController.java

@@ -1,6 +1,8 @@
 package com.zksy.web.controller.gasbasic;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zksy.base.domain.PipeNetworkBase;
 import com.zksy.base.domain.PipeRepairRecord;
 import com.zksy.base.dto.RepairRecordPageInDTO;
 import com.zksy.base.service.PipeRepairRecordService;
@@ -57,7 +59,10 @@ public class GasRepairRecordController {
             months[i] = m.getMonthValue() + "月";
         }
         java.util.Map<String, String> levelMap = new java.util.LinkedHashMap<>();
-        pipeNetworkBaseMapper.selectList(null).forEach(n -> levelMap.put(n.getNetworkId(), n.getNetworkLevel()));
+        // 燃气模块:仅查询 network_type=0 的燃气设施
+        pipeNetworkBaseMapper.selectList(new LambdaQueryWrapper<PipeNetworkBase>()
+                        .eq(PipeNetworkBase::getNetworkType, PipeNetworkBase.NETWORK_TYPE_GAS))
+                .forEach(n -> levelMap.put(n.getNetworkId(), n.getNetworkLevel()));
         for (com.zksy.base.domain.PipeRepairRecord r : all) {
             if (r.getRepairDate() == null) continue;
             int mDiff = (today.getYear() - r.getRepairDate().getYear()) * 12 + today.getMonthValue() - r.getRepairDate().getMonthValue();

+ 1 - 0
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/GisController.java

@@ -1,5 +1,6 @@
 package com.zksy.web.controller.gasbasic;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.zksy.base.domain.EquipmentBase;
 import com.zksy.base.domain.ManholeBase;
 import com.zksy.base.domain.PipeNetworkBase;

+ 2 - 0
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/PressureAlarmController.java

@@ -15,10 +15,12 @@ import com.zksy.ops.enums.WorkOrderStatusEnum;
 import com.zksy.ops.mapper.WorkOrderMapper;
 import com.zksy.common.annotation.Anonymous;
 import com.zksy.common.core.domain.AjaxResult;
+import com.zksy.common.utils.SecurityUtils;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
 import java.time.format.DateTimeFormatter;

+ 2 - 1
pipe-network-service/zksy-framework/src/main/java/com/zksy/framework/config/SecurityConfig.java

@@ -125,7 +125,8 @@ public class SecurityConfig
                 permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
                 requests.antMatchers(HttpMethod.GET, "/api/cockpit/v1/**").permitAll()
                     // 对于登录login 注册register 验证码captchaImage 允许匿名访问
-                    .antMatchers("/login", "/register", "/captchaImage","/**/visualization/**","/ws/deviceStatus").permitAll()
+                    // SSE 连接端点放行:EventSource 无法携带 Authorization 头,端点内自行校验 URL 上的 token
+                    .antMatchers("/login", "/register", "/captchaImage","/**/visualization/**","/ws/deviceStatus","/api/dispatch/sse/**").permitAll()
                     // 静态资源,可匿名访问
                     .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
                     .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()

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

@@ -23,6 +23,12 @@ import java.util.Date;
 @TableName(value = "pipe_network_base", schema = "app_user")
 public class PipeNetworkBase {
 
+    /**
+     * 设施类型:燃气设施(network_type = 0)
+     * <p>燃气模块所有查询 pipe_network_base 的接口都必须带上该条件</p>
+     */
+    public static final String NETWORK_TYPE_GAS = "0";
+
     @TableId(value = "network_id", type = IdType.ASSIGN_UUID)
     @ApiModelProperty("管网设施ID")
     private String networkId;
@@ -33,7 +39,7 @@ public class PipeNetworkBase {
     @ApiModelProperty("设施名称")
     private String networkName;
 
-    @ApiModelProperty("设施类型")
+    @ApiModelProperty("设施类型(0=燃气设施)")
     private String networkType;
 
     @ApiModelProperty("等级")

+ 26 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/PipeNetworkBaseService.java

@@ -15,6 +15,32 @@ public interface PipeNetworkBaseService extends IService<PipeNetworkBase> {
                                       String networkCode, String networkName,
                                       String networkType, String status);
 
+    /* ==================== 燃气模块专用查询(强制 network_type=0) ==================== */
+
+    /**
+     * 燃气设施分页查询
+     * <p>仅查询 network_type=0 的燃气设施</p>
+     */
+    Page<PipeNetworkBase> queryGasByPage(PipeNetworkPageInDTO dto);
+
+    /**
+     * 按ID查询燃气设施
+     * <p>仅查询 network_type=0 的燃气设施,非燃气设施返回 null</p>
+     */
+    PipeNetworkBase getGasById(String networkId);
+
+    /**
+     * 燃气设施下拉选项
+     * <p>仅查询 network_type=0 的燃气设施</p>
+     */
+    List<PipeNetworkBase> getGasOptions();
+
+    /**
+     * 燃气设施导出数据
+     * <p>仅查询 network_type=0 的燃气设施</p>
+     */
+    List<PipeNetworkExportDTO> exportGasData(String networkName, String status);
+
     boolean saveWithCheck(PipeNetworkBase entity);
 
     boolean saveBatchWithCheck(List<PipeNetworkBase> entityList);

+ 6 - 6
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/GasDashboardServiceImpl.java

@@ -100,18 +100,18 @@ public class GasDashboardServiceImpl implements GasDashboardService {
         result.put("baseStats", baseStats);
 
         // ---- 管网统计 ----
-        List<PipeNetworkBase> allNetworks = pipeNetworkBaseMapper.
-                selectList(new LambdaQueryWrapper<PipeNetworkBase>().eq(PipeNetworkBase::getNetworkType, "GAS"));
+        // 燃气模块:仅统计 network_type=0 的燃气设施
+        List<PipeNetworkBase> allNetworks = pipeNetworkBaseMapper.selectList(
+                new LambdaQueryWrapper<PipeNetworkBase>()
+                        .eq(PipeNetworkBase::getNetworkType, PipeNetworkBase.NETWORK_TYPE_GAS));
         // 管网总长度(公里)
         double totalLength = allNetworks.stream()
                 .filter(n -> n.getLength() != null)
                 .mapToDouble(n -> n.getLength().doubleValue())
                 .sum();
         double totalLengthKm = Math.round(totalLength / 1000.0 * 100) / 100.0;
-        // 燃气管网数量
-        long gasNetworkCount = allNetworks.stream()
-                .filter(n -> gasTypeIds.contains(n.getNetworkType()) || "GAS".equals(n.getNetworkType()))
-                .count();
+        // 燃气管网数量(上方已按燃气设施过滤)
+        long gasNetworkCount = allNetworks.size();
 
         Map<String, Object> pipeStats = new LinkedHashMap<>();
         pipeStats.put("totalLength", totalLengthKm);        // 管网总长度(km)

+ 56 - 1
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/PipeNetworkBaseServiceImpl.java

@@ -162,8 +162,63 @@ public class PipeNetworkBaseServiceImpl extends ServiceImpl<PipeNetworkBaseMappe
         wrapper.like(networkName != null && !networkName.isEmpty(), PipeNetworkBase::getNetworkName, networkName);
         wrapper.eq(status != null && !status.isEmpty(), PipeNetworkBase::getStatus, status);
         wrapper.orderByDesc(PipeNetworkBase::getCreateTime);
-        List<PipeNetworkBase> list = this.list(wrapper);
+        return buildExportList(this.list(wrapper));
+    }
+
+    /* ==================== 燃气模块专用查询(强制 network_type=0) ==================== */
+
+    @Override
+    public Page<PipeNetworkBase> queryGasByPage(PipeNetworkPageInDTO dto) {
+        Page<PipeNetworkBase> page = new Page<>(dto.getPageNum(), dto.getPageSize());
+        LambdaQueryWrapper<PipeNetworkBase> wrapper = new LambdaQueryWrapper<>();
+        // 燃气模块只允许查询燃气设施
+        wrapper.eq(PipeNetworkBase::getNetworkType, PipeNetworkBase.NETWORK_TYPE_GAS);
+        wrapper.like(dto.getNetworkName() != null && !dto.getNetworkName().isEmpty(), PipeNetworkBase::getNetworkName, dto.getNetworkName());
+        wrapper.like(dto.getManagementUnit() != null && !dto.getManagementUnit().isEmpty(), PipeNetworkBase::getManagementUnit, dto.getManagementUnit());
+        wrapper.eq(dto.getNetworkLevel() != null && !dto.getNetworkLevel().isEmpty(), PipeNetworkBase::getNetworkLevel, dto.getNetworkLevel());
+        wrapper.like(dto.getMaterial() != null && !dto.getMaterial().isEmpty(), PipeNetworkBase::getMaterial, dto.getMaterial());
+        wrapper.eq(dto.getStatus() != null && !dto.getStatus().isEmpty(), PipeNetworkBase::getStatus, dto.getStatus());
+        wrapper.orderByDesc(PipeNetworkBase::getCreateTime);
+        return this.page(page, wrapper);
+    }
+
+    @Override
+    public PipeNetworkBase getGasById(String networkId) {
+        if (networkId == null || networkId.isEmpty()) {
+            return null;
+        }
+        LambdaQueryWrapper<PipeNetworkBase> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(PipeNetworkBase::getNetworkId, networkId);
+        // 燃气模块只允许查询燃气设施
+        wrapper.eq(PipeNetworkBase::getNetworkType, PipeNetworkBase.NETWORK_TYPE_GAS);
+        return this.getOne(wrapper, false);
+    }
+
+    @Override
+    public List<PipeNetworkBase> getGasOptions() {
+        LambdaQueryWrapper<PipeNetworkBase> wrapper = new LambdaQueryWrapper<>();
+        wrapper.select(PipeNetworkBase::getNetworkId, PipeNetworkBase::getNetworkName, PipeNetworkBase::getNetworkCode);
+        // 燃气模块只允许查询燃气设施
+        wrapper.eq(PipeNetworkBase::getNetworkType, PipeNetworkBase.NETWORK_TYPE_GAS);
+        wrapper.orderByAsc(PipeNetworkBase::getNetworkName);
+        return this.list(wrapper);
+    }
+
+    @Override
+    public List<PipeNetworkExportDTO> exportGasData(String networkName, String status) {
+        LambdaQueryWrapper<PipeNetworkBase> wrapper = new LambdaQueryWrapper<>();
+        // 燃气模块只允许查询燃气设施
+        wrapper.eq(PipeNetworkBase::getNetworkType, PipeNetworkBase.NETWORK_TYPE_GAS);
+        wrapper.like(networkName != null && !networkName.isEmpty(), PipeNetworkBase::getNetworkName, networkName);
+        wrapper.eq(status != null && !status.isEmpty(), PipeNetworkBase::getStatus, status);
+        wrapper.orderByDesc(PipeNetworkBase::getCreateTime);
+        return buildExportList(this.list(wrapper));
+    }
 
+    /**
+     * 管网设施列表转导出DTO
+     */
+    private List<PipeNetworkExportDTO> buildExportList(List<PipeNetworkBase> list) {
         List<PipeNetworkExportDTO> result = new ArrayList<>(list.size());
         for (PipeNetworkBase entity : list) {
             PipeNetworkExportDTO dto = new PipeNetworkExportDTO();

+ 108 - 55
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEvent.java

@@ -15,9 +15,18 @@ import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
 /**
- * 应急事件统一档案(全生命周期主表)
- * 状态机:待受理 -> 待核实 -> 已核实 -> 处置中 -> 已解除 -> 已关闭;不实事件为终止态
- * 各环节业务表(接报/核实/转接/催办/跟踪)通过 eventId 挂靠本表,形成业务闭环
+ * 应急事件主表(dis_event)
+ *
+ * <p>一个事件一行,承载全生命周期「当前值」;过程留痕一律写 {@link DisEventLog}。</p>
+ *
+ * <p>状态机:
+ * 0 待核实 → 1 已核实 → 2 处置中 → 3 已解除;
+ * 旁路 4 不实事件(初判核实判定为不实,直接终止)。终态为 3、4。
+ * 转接、报送、处置跟踪、工作催办均不改变状态。</p>
+ *
+ * <p>字段统一加 {@code event_} 前缀,规避 KingbaseES 下 {@code level} 为保留字需转义的问题。</p>
+ *
+ * @author zengy
  */
 @Data
 @AllArgsConstructor
@@ -29,104 +38,140 @@ public class DisEvent {
     @ApiModelProperty("主键ID")
     private Long id;
 
-    @ApiModelProperty("事件编号(SJ+yyyyMMddHHmmss 自动生成,全局唯一)")
+    @ApiModelProperty("事件编号(业务序号:SJ+yyyyMMddHHmmss+4位随机,保存前生成,普通更新不可修改)")
     private String eventNo;
 
-    @ApiModelProperty("事件名称")
-    private String eventName;
-
-    @ApiModelProperty("事件类型:排水/供水/燃气/其他")
-    private String eventType;
+    @ApiModelProperty("事件描述")
+    private String description;
 
-    @ApiModelProperty("事件类别:管道泄漏/塌陷/积水/设备故障")
-    private String category;
+    @ApiModelProperty("事件来源:字典 dis_event_source")
+    private String eventSource;
 
-    @ApiModelProperty("事件等级:一般/较大/重大/特别重大")
-    @TableField("\"level\"")
-    private String level;
+    @ApiModelProperty("来源渠道:字典 dis_event_channel")
+    private String channel;
 
     @ApiModelProperty("发生地点")
     private String location;
 
-    @ApiModelProperty("发生区域:城南/城北/太常")
-    private String region;
+    @ApiModelProperty("经度")
+    private BigDecimal longitude;
 
-    @ApiModelProperty("事件描述")
-    private String description;
+    @ApiModelProperty("纬度")
+    private BigDecimal latitude;
 
-    @ApiModelProperty("事件来源:巡查上报/呼叫中心/视频监控/上级下达")
-    private String source;
+    @ApiModelProperty("发生时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime happenTime;
 
-    @ApiModelProperty("来源渠道")
-    private String channel;
+    @ApiModelProperty("接报时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime reportTime;
 
     @ApiModelProperty("接报人")
     private String reporter;
 
-    @ApiModelProperty("接报时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime reportTime;
+    @ApiModelProperty("接报人用户ID(sys_user.user_id);接报登记时取当前录入人员")
+    private Long reporterId;
 
-    @ApiModelProperty("发生时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime happenTime;
+    @ApiModelProperty("上报人联系电话")
+    private String reporterPhone;
 
-    @ApiModelProperty("初判时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime judgeTime;
+    @ApiModelProperty("管网类型:字典 dis_event_type")
+    private String eventType;
+
+    @ApiModelProperty("事件类别:字典 dis_event_category")
+    private String eventCategory;
+
+    @ApiModelProperty("事件等级:字典 dis_event_level")
+    private String eventLevel;
+
+    @ApiModelProperty("发生区域:字典 dis_event_region")
+    private String eventRegion;
+
+    @ApiModelProperty("事件状态:EventStatusEnum —— 0待核实 1已核实 2处置中 3已解除 4不实事件")
+    private Integer status;
+
+    @ApiModelProperty("核实结论:0-真实 1-不实;仅状态为已核实/不实事件时有值")
+    private Integer verifyResult;
 
-    @ApiModelProperty("核实人")
+    @ApiModelProperty("核验人")
     private String verifier;
 
+    @ApiModelProperty("核验人用户ID(sys_user.user_id);初判核实时的当前登录用户")
+    private Long verifierId;
+
     @ApiModelProperty("核实时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime verifyTime;
 
-    @ApiModelProperty("核实意见")
+    @ApiModelProperty("核实意见;判定为不实时填核销原因")
     private String verifyOpinion;
 
-    @ApiModelProperty("责任单位(转接部门/责任部门)")
+    @ApiModelProperty("责任处置人")
+    private String handler;
+
+    @ApiModelProperty("责任处置人用户ID(sys_user.user_id);初判核实时从系统用户中选出")
+    private Long handlerId;
+
+    @ApiModelProperty("责任处置人电话")
+    private String handlerPhone;
+
+    @ApiModelProperty("责任单位 / 转接接收部门")
     private String department;
 
-    @ApiModelProperty("处置负责人")
-    private String handler;
+    @ApiModelProperty("开始处置时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime disposeTime;
 
-    @ApiModelProperty("应用预案ID(关联 dis_plan_library)")
-    private Long planId;
+    @ApiModelProperty("解除时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime releaseTime;
 
-    @ApiModelProperty("应用预案名称")
-    private String planName;
+    @ApiModelProperty("解除原因:字典 dis_event_release_reason")
+    private String releaseReason;
 
-    @ApiModelProperty("直接经济损失(万元)")
-    private BigDecimal lossAmount;
+    @ApiModelProperty("解除说明 / 处置结果补充")
+    private String releaseNote;
 
-    @ApiModelProperty("事件状态:待受理/待核实/已核实/处置中/已解除/已关闭/不实事件")
-    private String status;
+    @ApiModelProperty("处置前附件URL(图片/视频,多个英文逗号分隔;解除原因为「事件已处置完成」时上传)")
+    private String beforeUrl;
 
-    @ApiModelProperty("关闭原因:事件不存在/处置完成/误报")
-    private String closeReason;
+    @ApiModelProperty("处置后附件URL(图片/视频,多个英文逗号分隔;解除原因为「事件已处置完成」时上传)")
+    private String afterUrl;
 
-    @ApiModelProperty("关闭说明")
-    private String closeRemark;
+    @ApiModelProperty("最近回访时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime lastVisitTime;
 
-    @ApiModelProperty("关闭时间")
+    @ApiModelProperty("下次回访时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime closeTime;
+    private LocalDateTime nextVisitTime;
 
-    @TableField(exist = false)
-    @ApiModelProperty("是否真实(核实入参:真实/不实,不落库)")
-    private String isReal;
+    @ApiModelProperty("是否需要升级:字典 dis_yes_no")
+    private Integer needUpgrade;
+
+    @ApiModelProperty("应用预案ID(dis_plan.id)")
+    private Long planId;
+
+    @ApiModelProperty("直接经济损失(万元)")
+    private BigDecimal lossAmount;
 
     @ApiModelProperty("创建人")
     private String createBy;
 
-    @ApiModelProperty("更新人")
-    private String updateBy;
+    @ApiModelProperty("创建人用户ID(sys_user.user_id);createBy 存登录名,本字段存ID")
+    private Long createUserId;
 
     @ApiModelProperty("创建时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime createTime;
 
+    @ApiModelProperty("更新人")
+    private String updateBy;
+
+    @ApiModelProperty("最后修改人用户ID(sys_user.user_id);updateBy 存登录名,本字段存ID")
+    private Long updateUserId;
+
     @ApiModelProperty("更新时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime updateTime;
@@ -134,4 +179,12 @@ public class DisEvent {
     @TableLogic(value = "0", delval = "2")
     @ApiModelProperty("删除标志(0=正常 2=删除)")
     private String delFlag;
+
+    @TableField(exist = false)
+    @ApiModelProperty("事件状态名称(由 status 推导,用于列表展示)")
+    private String statusName;
+
+    @TableField(exist = false)
+    @ApiModelProperty("是否可编辑/删除(仅待核实为真)")
+    private Boolean editable;
 }

+ 9 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventAssess.java

@@ -68,6 +68,9 @@ public class DisEventAssess {
     @ApiModelProperty("评估人")
     private String assessor;
 
+    @ApiModelProperty("评估人用户ID(sys_user.user_id);与 assessor 名称快照同时写入")
+    private Long assessorId;
+
     @ApiModelProperty("评估时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime assessTime;
@@ -75,9 +78,15 @@ public class DisEventAssess {
     @ApiModelProperty("创建人")
     private String createBy;
 
+    @ApiModelProperty("创建人用户ID(sys_user.user_id);createBy 存登录名,本字段存ID")
+    private Long createUserId;
+
     @ApiModelProperty("更新人")
     private String updateBy;
 
+    @ApiModelProperty("最后修改人用户ID(sys_user.user_id);updateBy 存登录名,本字段存ID")
+    private Long updateUserId;
+
     @ApiModelProperty("创建时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime createTime;

+ 47 - 11
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventGrade.java

@@ -14,7 +14,19 @@ import lombok.NoArgsConstructor;
 import java.time.LocalDateTime;
 
 /**
- * 应急事件分级标准
+ * 应急事件分级分类标准(dis_event_grade)
+ *
+ * <p>改造要点:</p>
+ * <ul>
+ *   <li>补业务编号 {@code gradeNo}(GJ+yyyyMMdd+3位随机),主键仍为 int8 自增</li>
+ *   <li>原 {@code category} / {@code level} 改名 {@code event_category} / {@code event_level},
+ *       复用字典 {@code dis_event_category} / {@code dis_event_level},顺带消掉
+ *       {@code level} 保留字要写 {@code "level"} 转义的坑</li>
+ *   <li>{@code status} 由中文 varchar 改为 int2:见 {@link com.zksy.dispatch.enums.GradeStatusEnum}</li>
+ *   <li>补 {@code response} 响应要求列(原型「应急预案分级」有此列)</li>
+ * </ul>
+ *
+ * @author zengy
  */
 @Data
 @AllArgsConstructor
@@ -26,32 +38,52 @@ public class DisEventGrade {
     @ApiModelProperty("主键ID")
     private Long id;
 
-    @ApiModelProperty("分类")
-    private String category;
+    @ApiModelProperty("标准编号(业务序号:GJ+yyyyMMdd+3位随机,保存前生成,普通更新不可修改)")
+    private String gradeNo;
 
-    @ApiModelProperty("级别")
-    @TableField("\"level\"")
-    private String level;
+    @ApiModelProperty("事件类别:字典 dis_event_category")
+    private String eventCategory;
 
-    @ApiModelProperty("子分类")
+    @ApiModelProperty("管网类型:字典 dis_event_type")
+    private String eventType;
+
+    @ApiModelProperty("事件等级:字典 dis_event_level")
+    private String eventLevel;
+
+    @ApiModelProperty("类别细分")
     private String subCategory;
 
-    @ApiModelProperty("场景")
+    @ApiModelProperty("适用场景")
     private String scenario;
 
-    @ApiModelProperty("判定标准")
+    @ApiModelProperty("分级分类判断标准")
     private String criteria;
 
-    @ApiModelProperty("状态")
-    private String status;
+    @ApiModelProperty("响应要求")
+    private String response;
+
+    @ApiModelProperty("启用状态:GradeStatusEnum —— 0启用 1停用")
+    private Integer status;
 
     @ApiModelProperty("备注")
     private String remark;
 
+    @ApiModelProperty("创建人")
+    private String createBy;
+
+    @ApiModelProperty("创建人用户ID(sys_user.user_id);createBy 存登录名,本字段存ID")
+    private Long createUserId;
+
     @ApiModelProperty("创建时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime createTime;
 
+    @ApiModelProperty("更新人")
+    private String updateBy;
+
+    @ApiModelProperty("最后修改人用户ID(sys_user.user_id);updateBy 存登录名,本字段存ID")
+    private Long updateUserId;
+
     @ApiModelProperty("更新时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime updateTime;
@@ -59,4 +91,8 @@ public class DisEventGrade {
     @TableLogic(value = "0", delval = "2")
     @ApiModelProperty("删除标志(0=正常 2=删除)")
     private String delFlag;
+
+    @TableField(exist = false)
+    @ApiModelProperty("启用状态名称(由 status 推导)")
+    private String statusName;
 }

+ 95 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventLog.java

@@ -0,0 +1,95 @@
+package com.zksy.dispatch.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+/**
+ * 应急事件操作日志(dis_event_log)
+ *
+ * <p>一个事件 N 行,记录全部环节动作与状态变更轨迹。
+ * 替代旧模型的 dis_event_transfer_log / dis_event_track_log / dis_event_urge_detail 三张明细表。</p>
+ *
+ * <p>oper_type 取值见 {@link com.zksy.dispatch.enums.EventLogOperTypeEnum};
+ * 日志完全由业务代码写入,不使用触发器或自定义函数。</p>
+ *
+ * @author zengy
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName(value = "dis_event_log", schema = "app_user")
+public class DisEventLog {
+
+    @TableId(value = "id", type = IdType.AUTO)
+    @ApiModelProperty("主键ID")
+    private Long id;
+
+    @ApiModelProperty("关联事件ID(dis_event.id)")
+    private Long eventId;
+
+    @ApiModelProperty("事件编号(冗余,便于按编号单独查日志)")
+    private String eventNo;
+
+    @ApiModelProperty("操作类型:EventLogOperTypeEnum —— 0接报登记 1初判核实 2不实核销 3电话转接 4上报报送 5开始处置 6处置跟踪 7事件解除 8工作催办 9信息修改")
+    private Integer operType;
+
+    @ApiModelProperty("操作说明/备注:跟踪内容、催办内容、核实意见、解除说明等")
+    private String operDesc;
+
+    @ApiModelProperty("变更前事件状态;不改状态的动作为空")
+    private Integer statusFrom;
+
+    @ApiModelProperty("变更后事件状态;不改状态的动作为空")
+    private Integer statusTo;
+
+    @ApiModelProperty("操作对象:转接/报送部门名称(多个逗号分隔);工作催办时为目标接收人")
+    private String targets;
+
+    @ApiModelProperty("催办接收部门(oper_type=8)")
+    private String receiverDept;
+
+    @ApiModelProperty("催办接收人电话(oper_type=8)")
+    private String receiverPhone;
+
+    @ApiModelProperty("催办方式:字典 dis_event_urge_method(oper_type=8)")
+    private String method;
+
+    @ApiModelProperty("催办结果:字典 dis_event_urge_result(oper_type=8)")
+    private String urgeResult;
+
+    @ApiModelProperty("态势评估:字典 dis_event_trend(oper_type=6)")
+    private String trend;
+
+    @ApiModelProperty("是否需要升级:字典 dis_yes_no(oper_type=6)")
+    private Integer needUpgrade;
+
+    @ApiModelProperty("下次回访时间(oper_type=6)")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime nextVisitTime;
+
+    @ApiModelProperty("附件URL,多个使用逗号分隔")
+    private String attachUrl;
+
+    @ApiModelProperty("操作人ID,系统操作可为空")
+    private Long operUserId;
+
+    @ApiModelProperty("操作人昵称;自动操作使用系统名称")
+    private String operUserName;
+
+    @ApiModelProperty("操作时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime operTime;
+
+    @TableField(exist = false)
+    @ApiModelProperty("操作类型中文描述(由 operType 推导,用于列表展示)")
+    private String operTypeName;
+}

+ 0 - 78
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventPlan.java

@@ -1,78 +0,0 @@
-package com.zksy.dispatch.domain;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableLogic;
-import com.baomidou.mybatisplus.annotation.TableName;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-
-/**
- * 应急预案
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-@TableName(value = "dis_event_plan", schema = "app_user")
-public class DisEventPlan {
-
-    @TableId(value = "id", type = IdType.AUTO)
-    @ApiModelProperty("主键ID")
-    private Long id;
-
-    @ApiModelProperty("分类ID")
-    private Long categoryId;
-
-    @ApiModelProperty("预案名称")
-    private String planName;
-
-    @ApiModelProperty("预案类型")
-    private String planType;
-
-    @ApiModelProperty("预案级别")
-    private String planLevel;
-
-    @ApiModelProperty("版本号")
-    private String version;
-
-    @ApiModelProperty("编制单位")
-    private String compileUnit;
-
-    @ApiModelProperty("发布单位")
-    private String publishUnit;
-
-    @ApiModelProperty("发布日期")
-    private String publishDate;
-
-    @ApiModelProperty("附件名称")
-    private String attachName;
-
-    @ApiModelProperty("附件路径")
-    private String attachPath;
-
-    @ApiModelProperty("预案内容")
-    private String planContent;
-
-    @ApiModelProperty("状态")
-    private String status;
-
-    @ApiModelProperty("创建人")
-    private String createBy;
-
-    @ApiModelProperty("创建时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime createTime;
-
-    @ApiModelProperty("更新时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime updateTime;
-
-    @TableLogic(value = "0", delval = "2")
-    @ApiModelProperty("删除标志(0=正常 2=删除)")
-    private String delFlag;
-}

+ 0 - 66
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventRecord.java

@@ -1,66 +0,0 @@
-package com.zksy.dispatch.domain;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableLogic;
-import com.baomidou.mybatisplus.annotation.TableName;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-
-/**
- * 应急事件数据记录(数据查询与统计)
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-@TableName(value = "dis_event_record", schema = "app_user")
-public class DisEventRecord {
-
-    @TableId(value = "id", type = IdType.AUTO)
-    @ApiModelProperty("主键ID")
-    private Long id;
-
-    @ApiModelProperty("事件名称")
-    private String eventName;
-
-    @ApiModelProperty("事件类型(自然灾害/事故灾难/公共卫生/社会安全/管网泄漏/管道堵塞)")
-    private String eventType;
-
-    @ApiModelProperty("发生时间")
-    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
-    private LocalDate eventTime;
-
-    @ApiModelProperty("事件等级(一般/较大/重大/特别重大)")
-    private String eventLevel;
-
-    @ApiModelProperty("地域(太常/城东/城南/城北片区)")
-    private String region;
-
-    @ApiModelProperty("责任部门(应急管理局/消防救援局/市政管理局/水务局)")
-    private String department;
-
-    @ApiModelProperty("直接经济损失(万元)")
-    private BigDecimal lossAmount;
-
-    @ApiModelProperty("创建人")
-    private String createBy;
-
-    @ApiModelProperty("创建时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime createTime;
-
-    @ApiModelProperty("更新时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime updateTime;
-
-    @TableLogic(value = "0", delval = "2")
-    @ApiModelProperty("删除标志(0=正常 2=删除)")
-    private String delFlag;
-}

+ 0 - 74
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventReport.java

@@ -1,74 +0,0 @@
-package com.zksy.dispatch.domain;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableLogic;
-import com.baomidou.mybatisplus.annotation.TableName;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-
-/**
- * 接报管理表
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-@TableName(value = "dis_event_report", schema = "app_user")
-public class DisEventReport {
-
-    @TableId(value = "id", type = IdType.AUTO)
-    @ApiModelProperty("主键ID")
-    private Long id;
-
-    @ApiModelProperty("关联统一事件主表ID(dis_event.id)")
-    private Long relEventId;
-
-    @ApiModelProperty("事件编号")
-    private String eventNo;
-
-    @ApiModelProperty("来源")
-    private String source;
-
-    @ApiModelProperty("地点")
-    private String location;
-
-    @ApiModelProperty("发生时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime happenTime;
-
-    @ApiModelProperty("描述")
-    private String description;
-
-    @ApiModelProperty("报告人")
-    private String reporter;
-
-    @ApiModelProperty("报告时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime reportTime;
-
-    @ApiModelProperty("渠道")
-    private String channel;
-
-    @ApiModelProperty("状态(待受理/已受理/已驳回)")
-    private String status;
-
-    @ApiModelProperty("备注")
-    private String remark;
-
-    @ApiModelProperty("创建时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime createTime;
-
-    @ApiModelProperty("更新时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime updateTime;
-
-    @TableLogic(value = "0", delval = "2")
-    @ApiModelProperty("删除标志(0=正常 2=删除)")
-    private String delFlag;
-}

+ 0 - 81
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventTrack.java

@@ -1,81 +0,0 @@
-package com.zksy.dispatch.domain;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableLogic;
-import com.baomidou.mybatisplus.annotation.TableName;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-
-/**
- * 跟踪解除表
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-@TableName(value = "dis_event_track", schema = "app_user")
-public class DisEventTrack {
-
-    @TableId(value = "id", type = IdType.AUTO)
-    @ApiModelProperty("主键ID")
-    private Long id;
-
-    @ApiModelProperty("关联统一事件主表ID(dis_event.id)")
-    private Long relEventId;
-
-    @ApiModelProperty("事件编号")
-    private String eventNo;
-
-    @ApiModelProperty("事件类别")
-    private String category;
-
-    @ApiModelProperty("事件级别")
-    @TableField("\"level\"")
-    private String level;
-
-    @ApiModelProperty("地点")
-    private String location;
-
-    @ApiModelProperty("描述")
-    private String description;
-
-    @ApiModelProperty("处理人")
-    private String handler;
-
-    @ApiModelProperty("状态(处理中/已解除/已关闭)")
-    private String status;
-
-    @ApiModelProperty("最近回访时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime lastVisitTime;
-
-    @ApiModelProperty("下次回访时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime nextVisitTime;
-
-    @TableField(exist = false)
-    @ApiModelProperty("态势评估(跟踪入参:趋于平稳/仍在发展/明显恶化,不落库)")
-    private String assessment;
-
-    @TableField(exist = false)
-    @ApiModelProperty("是否需要升级(跟踪入参:是/否,不落库)")
-    private String needUpgrade;
-
-    @ApiModelProperty("创建时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime createTime;
-
-    @ApiModelProperty("更新时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime updateTime;
-
-    @TableLogic(value = "0", delval = "2")
-    @ApiModelProperty("删除标志(0=正常 2=删除)")
-    private String delFlag;
-}

+ 0 - 55
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventTrackLog.java

@@ -1,55 +0,0 @@
-package com.zksy.dispatch.domain;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-
-/**
- * 跟踪日志表
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-@TableName(value = "dis_event_track_log", schema = "app_user")
-public class DisEventTrackLog {
-
-    @TableId(value = "id", type = IdType.AUTO)
-    @ApiModelProperty("主键ID")
-    private Long id;
-
-    @ApiModelProperty("关联跟踪ID")
-    private Long trackId;
-
-    @ApiModelProperty("事件编号")
-    private String eventNo;
-
-    @ApiModelProperty("标题")
-    private String title;
-
-    @ApiModelProperty("内容")
-    private String content;
-
-    @ApiModelProperty("类型:primary/success/warning/danger/info")
-    private String type;
-
-    @ApiModelProperty("态势评估")
-    private String assessment;
-
-    @ApiModelProperty("是否需要升级")
-    private String needUpgrade;
-
-    @ApiModelProperty("操作时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime actionTime;
-
-    @ApiModelProperty("创建时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime createTime;
-}

+ 0 - 84
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventTransfer.java

@@ -1,84 +0,0 @@
-package com.zksy.dispatch.domain;
-
-import com.baomidou.mybatisplus.annotation.*;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-
-/**
- * 转接报送表
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-@TableName(value = "dis_event_transfer", schema = "app_user")
-public class DisEventTransfer {
-
-    @TableId(value = "id", type = IdType.AUTO)
-    @ApiModelProperty("主键ID")
-    private Long id;
-
-    @ApiModelProperty("关联统一事件主表ID(dis_event.id)")
-    private Long relEventId;
-
-    @ApiModelProperty("关联核实ID")
-    private Long verifyId;
-
-    @ApiModelProperty("事件编号")
-    private String eventNo;
-
-    @ApiModelProperty("事件类别")
-    private String category;
-
-    @ApiModelProperty("事件级别")
-    @TableField("\"level\"")
-    private String level;
-
-    @ApiModelProperty("摘要")
-    private String summary;
-
-    @ApiModelProperty("转接部门")
-    private String transferDept;
-
-    @ApiModelProperty("报送级别")
-    private String reportLevel;
-
-    @ApiModelProperty("转接时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime transferTime;
-
-    @ApiModelProperty("转接备注")
-    private String transferRemark;
-
-    @ApiModelProperty("报送对象")
-    private String reportTargets;
-
-    @ApiModelProperty("附件路径(多个用,分隔)")
-    private String attachPaths;
-
-    @TableField(exist = false)
-    @ApiModelProperty("关联接报编号(来自核实记录)")
-    private String reportEventNo;
-
-    @ApiModelProperty("状态(待转接/已转接/已报送)")
-    private String status;
-
-    @ApiModelProperty("操作人")
-    private String operator;
-
-    @ApiModelProperty("创建时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime createTime;
-
-    @ApiModelProperty("更新时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime updateTime;
-
-    @TableLogic(value = "0", delval = "2")
-    @ApiModelProperty("删除标志(0=正常 2=删除)")
-    private String delFlag;
-}

+ 0 - 52
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventTransferLog.java

@@ -1,52 +0,0 @@
-package com.zksy.dispatch.domain;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-
-/**
- * 转接报送日志表
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-@TableName(value = "dis_event_transfer_log", schema = "app_user")
-public class DisEventTransferLog {
-
-    @TableId(value = "id", type = IdType.AUTO)
-    @ApiModelProperty("主键ID")
-    private Long id;
-
-    @ApiModelProperty("关联转接ID")
-    private Long transferId;
-
-    @ApiModelProperty("事件编号")
-    private String eventNo;
-
-    @ApiModelProperty("动作类型:转接/报送")
-    private String actionType;
-
-    @ApiModelProperty("目标部门/层级")
-    private String actionTarget;
-
-    @ApiModelProperty("操作时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime actionTime;
-
-    @ApiModelProperty("操作内容")
-    private String content;
-
-    @ApiModelProperty("类型:primary/success/warning/danger")
-    private String type;
-
-    @ApiModelProperty("创建时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime createTime;
-}

+ 0 - 67
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventUrge.java

@@ -1,67 +0,0 @@
-package com.zksy.dispatch.domain;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableLogic;
-import com.baomidou.mybatisplus.annotation.TableName;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-
-/**
- * 工作催办表
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-@TableName(value = "dis_event_urge", schema = "app_user")
-public class DisEventUrge {
-
-    @TableId(value = "id", type = IdType.AUTO)
-    @ApiModelProperty("主键ID")
-    private Long id;
-
-    @ApiModelProperty("关联统一事件主表ID(dis_event.id)")
-    private Long relEventId;
-
-    @ApiModelProperty("事件编号")
-    private String eventNo;
-
-    @ApiModelProperty("摘要")
-    private String summary;
-
-    @ApiModelProperty("接收人")
-    private String receiver;
-
-    @ApiModelProperty("接收部门")
-    private String receiverDept;
-
-    @ApiModelProperty("接收人电话")
-    private String receiverPhone;
-
-    @ApiModelProperty("催办次数")
-    private Integer urgeCount;
-
-    @ApiModelProperty("最近催办时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime lastUrgeTime;
-
-    @ApiModelProperty("状态(待催办/催办中/已完成)")
-    private String status;
-
-    @ApiModelProperty("创建时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime createTime;
-
-    @ApiModelProperty("更新时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime updateTime;
-
-    @TableLogic(value = "0", delval = "2")
-    @ApiModelProperty("删除标志(0=正常 2=删除)")
-    private String delFlag;
-}

+ 0 - 49
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventUrgeDetail.java

@@ -1,49 +0,0 @@
-package com.zksy.dispatch.domain;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-
-/**
- * 催办明细表
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-@TableName(value = "dis_event_urge_detail", schema = "app_user")
-public class DisEventUrgeDetail {
-
-    @TableId(value = "id", type = IdType.AUTO)
-    @ApiModelProperty("主键ID")
-    private Long id;
-
-    @ApiModelProperty("关联催办ID")
-    private Long urgeId;
-
-    @ApiModelProperty("事件编号")
-    private String eventNo;
-
-    @ApiModelProperty("催办方式:电话/短信/系统消息")
-    private String method;
-
-    @ApiModelProperty("催办内容")
-    private String content;
-
-    @ApiModelProperty("结果:已反馈/未反馈")
-    private String result;
-
-    @ApiModelProperty("催办时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime urgeTime;
-
-    @ApiModelProperty("创建时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime createTime;
-}

+ 0 - 85
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisEventVerify.java

@@ -1,85 +0,0 @@
-package com.zksy.dispatch.domain;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableLogic;
-import com.baomidou.mybatisplus.annotation.TableName;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-
-/**
- * 初判核实表
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-@TableName(value = "dis_event_verify", schema = "app_user")
-public class DisEventVerify {
-
-    @TableId(value = "id", type = IdType.AUTO)
-    @ApiModelProperty("主键ID")
-    private Long id;
-
-    @ApiModelProperty("关联统一事件主表ID(dis_event.id)")
-    private Long relEventId;
-
-    @ApiModelProperty("关联接报ID")
-    private Long reportId;
-
-    @ApiModelProperty("事件编号")
-    private String eventNo;
-
-    @ApiModelProperty("事件类别")
-    private String category;
-
-    @ApiModelProperty("事件级别")
-    @TableField("\"level\"")
-    private String level;
-
-    @ApiModelProperty("地点")
-    private String location;
-
-    @ApiModelProperty("描述")
-    private String description;
-
-    @ApiModelProperty("判定时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime judgeTime;
-
-    @ApiModelProperty("核实人")
-    private String verifier;
-
-    @ApiModelProperty("核实时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime verifyTime;
-
-    @ApiModelProperty("核实意见")
-    private String opinion;
-
-    @ApiModelProperty("是否属实")
-    private String isReal;
-
-    @ApiModelProperty("事件ID")
-    private String eventId;
-
-    @ApiModelProperty("状态(待核实/已核实)")
-    private String status;
-
-    @ApiModelProperty("创建时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime createTime;
-
-    @ApiModelProperty("更新时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime updateTime;
-
-    @TableLogic(value = "0", delval = "2")
-    @ApiModelProperty("删除标志(0=正常 2=删除)")
-    private String delFlag;
-}

+ 126 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisPlan.java

@@ -0,0 +1,126 @@
+package com.zksy.dispatch.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+/**
+ * 应急预案主表(dis_plan)
+ *
+ * <p>原型「预案编辑」与「预案审批」是同一个预案的两个视图,共用本表:
+ * 编辑页写草稿 / 提交审核,审批页按 {@code status=1 待审核} 过滤后审批。</p>
+ *
+ * <p>状态机:0 草稿 → 1 待审核 → 2 已通过 / 3 已驳回。
+ * 已通过的预案再次编辑属于「修订」,会生成一条审核中的候选版本,
+ * 当前生效版本在 {@link DisPlanVersion#getVerStatus()} 为 1 的行上。</p>
+ *
+ * @author zengy
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName(value = "dis_plan", schema = "app_user")
+public class DisPlan {
+
+    @TableId(value = "id", type = IdType.AUTO)
+    @ApiModelProperty("主键ID")
+    private Long id;
+
+    @ApiModelProperty("预案编号(业务序号:YA+yyyyMMdd+4位随机,保存前生成,普通更新不可修改)")
+    private String planNo;
+
+    @ApiModelProperty("预案名称")
+    private String planName;
+
+    @ApiModelProperty("预案类型:字典 dis_plan_type")
+    private String planType;
+
+    @ApiModelProperty("预案等级:字典 dis_plan_level")
+    private String planLevel;
+
+    @ApiModelProperty("编制单位")
+    private String compileUnit;
+
+    @ApiModelProperty("发布单位")
+    private String publishUnit;
+
+    @ApiModelProperty("发布日期")
+    private String publishDate;
+
+    @ApiModelProperty("附件名称")
+    private String attachName;
+
+    @ApiModelProperty("附件路径")
+    private String attachPath;
+
+    @ApiModelProperty("是否预案模板:0-否 1-是")
+    private Integer isTemplate;
+
+    @ApiModelProperty("预案状态:PlanStatusEnum —— 0草稿 1待审核 2已通过 3已驳回")
+    private Integer status;
+
+    @ApiModelProperty("当前生效版本号;尚未审批通过时为空")
+    private String version;
+
+    @ApiModelProperty("预案正文(富文本 HTML)")
+    private String content;
+
+    @ApiModelProperty("最近一次审批结果:通过 / 不通过")
+    private String approveResult;
+
+    @ApiModelProperty("最近一次审批时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime approveTime;
+
+    @ApiModelProperty("最近一次审批意见;不通过时为驳回原因")
+    private String approveNote;
+
+    @ApiModelProperty("创建人")
+    private String createBy;
+
+    @ApiModelProperty("创建人用户ID(sys_user.user_id);createBy 存登录名,本字段存ID")
+    private Long createUserId;
+
+    @ApiModelProperty("创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime createTime;
+
+    @ApiModelProperty("更新人")
+    private String updateBy;
+
+    @ApiModelProperty("最后修改人用户ID(sys_user.user_id);updateBy 存登录名,本字段存ID")
+    private Long updateUserId;
+
+    @ApiModelProperty("更新时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime updateTime;
+
+    @TableLogic(value = "0", delval = "2")
+    @ApiModelProperty("删除标志(0=正常 2=删除)")
+    private String delFlag;
+
+    @TableField(exist = false)
+    @ApiModelProperty("预案状态名称(由 status 推导)")
+    private String statusName;
+
+    @TableField(exist = false)
+    @ApiModelProperty("是否可编辑(草稿 / 已驳回 可直接改,已通过 走修订)")
+    private Boolean editable;
+
+    @TableField(exist = false)
+    @ApiModelProperty("是否可提交审核(草稿 / 已驳回)")
+    private Boolean submittable;
+
+    @TableField(exist = false)
+    @ApiModelProperty("是否有审核中的候选版本")
+    private Boolean hasCandidate;
+}

+ 0 - 71
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisPlanApproval.java

@@ -1,71 +0,0 @@
-package com.zksy.dispatch.domain;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableLogic;
-import com.baomidou.mybatisplus.annotation.TableName;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-
-/**
- * 应急预案审批
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-@TableName(value = "dis_plan_approval", schema = "app_user")
-public class DisPlanApproval {
-
-    @TableId(value = "id", type = IdType.AUTO)
-    @ApiModelProperty("主键ID")
-    private Long id;
-
-    @ApiModelProperty("预案ID")
-    private Long planId;
-
-    @ApiModelProperty("预案名称")
-    private String planName;
-
-    @ApiModelProperty("预案类型")
-    private String planType;
-
-    @ApiModelProperty("预案级别")
-    private String planLevel;
-
-    @ApiModelProperty("版本号")
-    private String version;
-
-    @ApiModelProperty("审批人(多人逗号分隔)")
-    private String reviewers;
-
-    @ApiModelProperty("审批记录")
-    private String auditRecord;
-
-    @ApiModelProperty("状态:编辑中/审批中/终审中/已发布/待修正")
-    private String status;
-
-    @ApiModelProperty("提交时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime submitTime;
-
-    @ApiModelProperty("审批完成时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime approveTime;
-
-    @ApiModelProperty("创建时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime createTime;
-
-    @ApiModelProperty("更新时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime updateTime;
-
-    @TableLogic(value = "0", delval = "2")
-    @ApiModelProperty("删除标志(0=正常 2=删除)")
-    private String delFlag;
-}

+ 0 - 55
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisPlanApprovalStep.java

@@ -1,55 +0,0 @@
-package com.zksy.dispatch.domain;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-
-/**
- * 应急预案审批步骤
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-@TableName(value = "dis_plan_approval_step", schema = "app_user")
-public class DisPlanApprovalStep {
-
-    @TableId(value = "id", type = IdType.AUTO)
-    @ApiModelProperty("主键ID")
-    private Long id;
-
-    @ApiModelProperty("审批主表ID")
-    private Long approvalId;
-
-    @ApiModelProperty("环节序号")
-    private Integer stepOrder;
-
-    @ApiModelProperty("环节名称")
-    private String title;
-
-    @ApiModelProperty("审批人")
-    private String approver;
-
-    @ApiModelProperty("权限描述")
-    private String permission;
-
-    @ApiModelProperty("审批意见")
-    private String opinion;
-
-    @ApiModelProperty("审批结果")
-    private String result;
-
-    @ApiModelProperty("审批时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime approveTime;
-
-    @ApiModelProperty("创建时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime createTime;
-}

+ 0 - 50
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisPlanCategory.java

@@ -1,50 +0,0 @@
-package com.zksy.dispatch.domain;
-
-import com.baomidou.mybatisplus.annotation.*;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-import java.util.List;
-
-/**
- * 预案分类
- */
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-@TableName(value = "dis_plan_category", schema = "app_user")
-public class DisPlanCategory {
-
-    @TableId(value = "id", type = IdType.AUTO)
-    @ApiModelProperty("主键ID")
-    private Long id;
-
-    @ApiModelProperty("父级ID")
-    private Long parentId;
-
-    @ApiModelProperty("分类名称")
-    private String label;
-
-    @ApiModelProperty("排序号")
-    private Integer sortOrder;
-
-    @ApiModelProperty("创建时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime createTime;
-
-    @ApiModelProperty("更新时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime updateTime;
-
-    @TableLogic(value = "0", delval = "2")
-    @ApiModelProperty("删除标志(0=正常 2=删除)")
-    private String delFlag;
-
-    @TableField(exist = false)
-    @ApiModelProperty("子分类列表")
-    private List<DisPlanCategory> children;
-}

+ 3 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisPlanLibrary.java

@@ -52,6 +52,9 @@ public class DisPlanLibrary {
     @ApiModelProperty("创建人")
     private String createBy;
 
+    @ApiModelProperty("创建人用户ID(sys_user.user_id);createBy 存登录名,本字段存ID")
+    private Long createUserId;
+
     @ApiModelProperty("创建时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime createTime;

+ 69 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisPlanLog.java

@@ -0,0 +1,69 @@
+package com.zksy.dispatch.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+/**
+ * 应急预案操作日志(dis_plan_log)
+ *
+ * <p>替代旧模型 dis_plan_approval + dis_plan_approval_step 两张表:
+ * 原型审批是单环节「通过 / 不通过 + 意见」,不需要环节配置表,一次操作一行日志即可。</p>
+ *
+ * <p>日志完全由业务代码写入,不使用触发器或自定义函数。</p>
+ *
+ * @author zengy
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName(value = "dis_plan_log", schema = "app_user")
+public class DisPlanLog {
+
+    @TableId(value = "id", type = IdType.AUTO)
+    @ApiModelProperty("主键ID")
+    private Long id;
+
+    @ApiModelProperty("关联预案ID(dis_plan.id)")
+    private Long planId;
+
+    @ApiModelProperty("预案编号(冗余)")
+    private String planNo;
+
+    @ApiModelProperty("操作类型:PlanLogOperTypeEnum —— 0新建 1提交审核 2审批通过 3审批驳回 4修订")
+    private Integer operType;
+
+    @ApiModelProperty("操作说明 / 审批意见")
+    private String operDesc;
+
+    @ApiModelProperty("本次操作涉及的版本号")
+    private String version;
+
+    @ApiModelProperty("变更前预案状态")
+    private Integer statusFrom;
+
+    @ApiModelProperty("变更后预案状态")
+    private Integer statusTo;
+
+    @ApiModelProperty("操作人ID,系统操作可为空")
+    private Long operUserId;
+
+    @ApiModelProperty("操作人昵称")
+    private String operUserName;
+
+    @ApiModelProperty("操作时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime operTime;
+
+    @TableField(exist = false)
+    @ApiModelProperty("操作类型中文描述(由 operType 推导)")
+    private String operTypeName;
+}

+ 3 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisPlanMatchRecord.java

@@ -55,6 +55,9 @@ public class DisPlanMatchRecord {
     @ApiModelProperty("操作人")
     private String createBy;
 
+    @ApiModelProperty("操作人用户ID(sys_user.user_id);createBy 存登录名,本字段存ID")
+    private Long createUserId;
+
     @ApiModelProperty("操作时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime createTime;

+ 75 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DisPlanVersion.java

@@ -0,0 +1,75 @@
+package com.zksy.dispatch.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+/**
+ * 应急预案版本快照(dis_plan_version)
+ *
+ * <p>版本只在预案正文「审核通过」时生效,因此本表同时承担两种角色:</p>
+ * <ul>
+ *   <li>{@code ver_status=1} 当前生效版本 —— 预案正式发布的内容</li>
+ *   <li>{@code ver_status=2} 审核中候选版本 —— 修订已通过预案后产生,审批通过才转 1</li>
+ * </ul>
+ *
+ * <p>与项目约定一致:版本表不做逻辑删除,删除预案时物理清理本预案的版本行。</p>
+ *
+ * @author zengy
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName(value = "dis_plan_version", schema = "app_user")
+public class DisPlanVersion {
+
+    @TableId(value = "id", type = IdType.AUTO)
+    @ApiModelProperty("主键ID")
+    private Long id;
+
+    @ApiModelProperty("关联预案ID(dis_plan.id)")
+    private Long planId;
+
+    @ApiModelProperty("预案编号(冗余,便于单独按编号查询版本)")
+    private String planNo;
+
+    @ApiModelProperty("版本号,如 v1.0 / v1.1")
+    private String version;
+
+    @ApiModelProperty("该版本正文快照(富文本 HTML)")
+    private String content;
+
+    @ApiModelProperty("附件名称快照")
+    private String attachName;
+
+    @ApiModelProperty("附件URL快照(MinIO 直链)")
+    private String attachPath;
+
+    @ApiModelProperty("变更说明;修订已通过预案时必填")
+    private String changeNote;
+
+    @ApiModelProperty("修订人")
+    private String editor;
+
+    @ApiModelProperty("修订人用户ID(sys_user.user_id);与 editor 名称快照同时写入")
+    private Long editorId;
+
+    @ApiModelProperty("修订时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime editTime;
+
+    @ApiModelProperty("版本状态:PlanVersionStatusEnum —— 0历史 1当前版本 2审核中 3已驳回")
+    private Integer verStatus;
+
+    @TableField(exist = false)
+    @ApiModelProperty("版本状态名称(由 verStatus 推导)")
+    private String verStatusName;
+}

+ 58 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/domain/DispatchNotify.java

@@ -0,0 +1,58 @@
+package com.zksy.dispatch.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+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.time.LocalDateTime;
+
+/**
+ * 催办系统消息通知(工作催办勾选「系统消息」时按用户落一条站内消息)
+ *
+ * @author zengy
+ */
+@Data
+@TableName("dispatch_notify")
+@ApiModel(value = "催办系统消息", description = "应急事件催办站内通知")
+public class DispatchNotify implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(type = IdType.AUTO)
+    @ApiModelProperty("主键")
+    private Long id;
+
+    @ApiModelProperty("接收人用户ID")
+    private Long userId;
+
+    @ApiModelProperty("关联事件ID")
+    private Long eventId;
+
+    @ApiModelProperty("关联事件编号")
+    private String eventNo;
+
+    @ApiModelProperty("消息标题")
+    private String title;
+
+    @ApiModelProperty("消息内容(= 催办内容)")
+    private String content;
+
+    @ApiModelProperty("催办人姓名快照")
+    private String urgingUser;
+
+    @ApiModelProperty("已读标记:0未读 1已读")
+    private Integer readFlag;
+
+    @ApiModelProperty("推送时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime createTime;
+
+    @ApiModelProperty("删除标志:0正常 1删除")
+    private String delFlag;
+}

+ 36 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisDeptOptionVO.java

@@ -0,0 +1,36 @@
+package com.zksy.dispatch.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 责任单位下拉项(系统部门)
+ *
+ * <p>初判核实时「责任单位」不再手填,改为选择系统部门(sys_dept);前端用
+ * {@code deptId} 树形展示并用它过滤责任处置人候选(该部门所属人员),提交时把
+ * {@code deptName} 写入 {@code dis_event.department}(该列存部门中文名,工作催办、
+ * 多条件筛选均按名称口径,保持不变)。</p>
+ *
+ * @author zengy
+ */
+@Data
+@ApiModel(value = "责任单位下拉项", description = "应急事件责任单位可选系统部门")
+public class DisDeptOptionVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("部门ID")
+    private Long deptId;
+
+    @ApiModelProperty("父部门ID;前端据此建树")
+    private Long parentId;
+
+    @ApiModelProperty("部门名称;提交时写入 dis_event.department")
+    private String deptName;
+
+    @ApiModelProperty("显示顺序;同级排序用")
+    private Integer orderNum;
+}

+ 18 - 8
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisEventGradePageDTO.java

@@ -6,23 +6,33 @@ import lombok.Data;
 
 import java.io.Serializable;
 
+/**
+ * 应急事件分级标准分页查询入参
+ */
 @Data
 @ApiModel(value = "应急事件分级标准分页查询入参", description = "应急事件分级标准分页查询入参")
 public class DisEventGradePageDTO implements Serializable {
+
     private static final long serialVersionUID = 1L;
 
-    @ApiModelProperty(value = "分类")
-    private String category;
+    @ApiModelProperty(value = "标准编号(模糊)")
+    private String gradeNo;
+
+    @ApiModelProperty(value = "事件类别:字典 dis_event_category")
+    private String eventCategory;
+
+    @ApiModelProperty(value = "管网类型:字典 dis_event_type")
+    private String eventType;
 
-    @ApiModelProperty(value = "级别")
-    private String level;
+    @ApiModelProperty(value = "事件等级:字典 dis_event_level")
+    private String eventLevel;
 
-    @ApiModelProperty(value = "状态")
-    private String status;
+    @ApiModelProperty(value = "启用状态:GradeStatusEnum —— 0启用 1停用")
+    private Integer status;
 
     @ApiModelProperty(value = "当前记录起始索引", required = true)
-    private Long pageNum;
+    private Long pageNum = 1L;
 
     @ApiModelProperty(value = "每页显示记录数", required = true)
-    private Long pageSize;
+    private Long pageSize = 10L;
 }

+ 36 - 23
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisEventPageDTO.java

@@ -7,53 +7,66 @@ import lombok.Data;
 import java.io.Serializable;
 import java.util.List;
 
+/**
+ * 应急事件分页查询入参
+ *
+ * <p>接报管理 / 初判核实 / 转接报送 / 跟踪解除共用一套筛选条件,
+ * 通过 status / statusList 区分各页面所处的环节。</p>
+ */
 @Data
-@ApiModel(value = "统一事件分页查询入参", description = "应急事件统一档案分页查询入参")
+@ApiModel(value = "应急事件分页查询入参", description = "应急事件分页查询入参")
 public class DisEventPageDTO implements Serializable {
+
     private static final long serialVersionUID = 1L;
 
     @ApiModelProperty(value = "事件编号(模糊)")
     private String eventNo;
 
-    @ApiModelProperty(value = "事件名称(模糊)")
-    private String eventName;
+    @ApiModelProperty(value = "事件描述(模糊)")
+    private String description;
 
-    @ApiModelProperty(value = "事件类型")
+    @ApiModelProperty(value = "管网类型:字典 dis_event_type")
     private String eventType;
 
-    @ApiModelProperty(value = "事件类别")
-    private String category;
-
-    @ApiModelProperty(value = "事件等级")
-    private String level;
+    @ApiModelProperty(value = "事件类别:字典 dis_event_category")
+    private String eventCategory;
 
-    @ApiModelProperty(value = "事件来源")
-    private String source;
+    @ApiModelProperty(value = "事件等级:字典 dis_event_level")
+    private String eventLevel;
 
-    @ApiModelProperty(value = "事件状态(单状态过滤)")
-    private String status;
+    @ApiModelProperty(value = "事件来源:字典 dis_event_source")
+    private String eventSource;
 
-    @ApiModelProperty(value = "事件状态列表(多状态过滤,优先于status)")
-    private List<String> statusList;
+    @ApiModelProperty(value = "发生区域:字典 dis_event_region")
+    private String eventRegion;
 
-    @ApiModelProperty(value = "发生区域")
-    private String region;
+    @ApiModelProperty(value = "发生地点(模糊)")
+    private String location;
 
-    @ApiModelProperty(value = "责任单位")
+    @ApiModelProperty(value = "责任单位(模糊)")
     private String department;
 
-    @ApiModelProperty(value = "开始日期(yyyy-MM-dd,按发生时间)")
+    @ApiModelProperty(value = "责任处置人(模糊)")
+    private String handler;
+
+    @ApiModelProperty(value = "事件状态:EventStatusEnum")
+    private Integer status;
+
+    @ApiModelProperty(value = "事件状态列表(多选筛选)")
+    private List<Integer> statusList;
+
+    @ApiModelProperty(value = "接报开始日期 yyyy-MM-dd")
     private String startDate;
 
-    @ApiModelProperty(value = "结束日期(yyyy-MM-dd,按发生时间)")
+    @ApiModelProperty(value = "接报结束日期 yyyy-MM-dd")
     private String endDate;
 
-    @ApiModelProperty(value = "损失情况(100万以下/100-500万/500-1000万/1000万以上)")
+    @ApiModelProperty(value = "直接经济损失区间:0-1万 1-10万 10-100万 100万以上")
     private String lossRange;
 
     @ApiModelProperty(value = "当前记录起始索引", required = true)
-    private Long pageNum;
+    private Long pageNum = 1L;
 
     @ApiModelProperty(value = "每页显示记录数", required = true)
-    private Long pageSize;
+    private Long pageSize = 10L;
 }

+ 0 - 34
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisEventPlanPageDTO.java

@@ -1,34 +0,0 @@
-package com.zksy.dispatch.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import java.io.Serializable;
-
-@Data
-@ApiModel(value = "应急预案分页查询入参", description = "应急预案分页查询入参")
-public class DisEventPlanPageDTO implements Serializable {
-    private static final long serialVersionUID = 1L;
-
-    @ApiModelProperty(value = "预案名称")
-    private String planName;
-
-    @ApiModelProperty(value = "预案类型")
-    private String planType;
-
-    @ApiModelProperty(value = "预案级别")
-    private String planLevel;
-
-    @ApiModelProperty(value = "状态")
-    private String status;
-
-    @ApiModelProperty(value = "分类ID")
-    private Long categoryId;
-
-    @ApiModelProperty(value = "当前记录起始索引", required = true)
-    private Long pageNum;
-
-    @ApiModelProperty(value = "每页显示记录数", required = true)
-    private Long pageSize;
-}

+ 0 - 40
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisEventRecordPageDTO.java

@@ -1,40 +0,0 @@
-package com.zksy.dispatch.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import java.io.Serializable;
-
-@Data
-@ApiModel(value = "应急事件记录分页查询入参", description = "数据查询与统计分页查询入参")
-public class DisEventRecordPageDTO implements Serializable {
-    private static final long serialVersionUID = 1L;
-
-    @ApiModelProperty(value = "事件名称(模糊)")
-    private String eventName;
-
-    @ApiModelProperty(value = "事件类型")
-    private String eventType;
-
-    @ApiModelProperty(value = "发生时间(yyyy-MM-dd)")
-    private String eventTime;
-
-    @ApiModelProperty(value = "事件等级")
-    private String eventLevel;
-
-    @ApiModelProperty(value = "地域")
-    private String region;
-
-    @ApiModelProperty(value = "责任部门")
-    private String department;
-
-    @ApiModelProperty(value = "损失情况(100万以下/100-500万/500-1000万/1000万以上)")
-    private String lossRange;
-
-    @ApiModelProperty(value = "当前记录起始索引", required = true)
-    private Long pageNum;
-
-    @ApiModelProperty(value = "每页显示记录数", required = true)
-    private Long pageSize;
-}

+ 120 - 65
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisEventVO.java

@@ -1,6 +1,7 @@
 package com.zksy.dispatch.dto;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.zksy.dispatch.domain.DisEventLog;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
@@ -9,15 +10,17 @@ import java.io.Serializable;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.List;
-import java.util.Map;
 
 /**
- * 统一事件详情/列表展示VO
- * 在主表字段基础上,扩展最近环节信息(转接/报送/跟踪),便于各业务页面直接展示
+ * 应急事件展示 VO
+ *
+ * <p>在 dis_event 主表字段基础上,补充由 dis_event_log 聚合出的最近环节信息,
+ * 供接报管理 / 初判核实 / 转接报送 / 工作催办 / 跟踪解除各页面直接展示。</p>
  */
 @Data
-@ApiModel(value = "统一事件展示VO", description = "统一事件详情/列表展示VO")
+@ApiModel(value = "应急事件展示VO", description = "应急事件列表与详情展示VO")
 public class DisEventVO implements Serializable {
+
     private static final long serialVersionUID = 1L;
 
     @ApiModelProperty(value = "主键ID")
@@ -26,63 +29,120 @@ public class DisEventVO implements Serializable {
     @ApiModelProperty(value = "事件编号")
     private String eventNo;
 
-    @ApiModelProperty(value = "事件名称")
-    private String eventName;
-
-    @ApiModelProperty(value = "事件类型:排水/供水/燃气/其他")
-    private String eventType;
+    @ApiModelProperty(value = "事件描述")
+    private String description;
 
-    @ApiModelProperty(value = "事件类别:管道泄漏/塌陷/积水/设备故障")
-    private String category;
+    @ApiModelProperty(value = "事件来源:字典 dis_event_source")
+    private String eventSource;
 
-    @ApiModelProperty(value = "事件等级:一般/较大/重大/特别重大")
-    private String level;
+    @ApiModelProperty(value = "来源渠道:字典 dis_event_channel")
+    private String channel;
 
     @ApiModelProperty(value = "发生地点")
     private String location;
 
-    @ApiModelProperty(value = "发生区域:城南/城北/太常")
-    private String region;
-
-    @ApiModelProperty(value = "事件描述")
-    private String description;
-
-    @ApiModelProperty(value = "事件来源:巡查上报/呼叫中心/视频监控/上级下达")
-    private String source;
+    @ApiModelProperty(value = "经度")
+    private BigDecimal longitude;
 
-    @ApiModelProperty(value = "来源渠道")
-    private String channel;
+    @ApiModelProperty(value = "纬度")
+    private BigDecimal latitude;
 
-    @ApiModelProperty(value = "接报人")
-    private String reporter;
+    @ApiModelProperty(value = "发生时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime happenTime;
 
     @ApiModelProperty(value = "接报时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime reportTime;
 
-    @ApiModelProperty(value = "发生时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime happenTime;
+    @ApiModelProperty(value = "接报人(按 reporterId 反查 sys_user 昵称,查不到退回快照)")
+    private String reporter;
 
-    @ApiModelProperty(value = "初判时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime judgeTime;
+    @ApiModelProperty(value = "接报人用户ID")
+    private Long reporterId;
+
+    @ApiModelProperty(value = "上报人联系电话")
+    private String reporterPhone;
+
+    @ApiModelProperty(value = "管网类型:字典 dis_event_type")
+    private String eventType;
+
+    @ApiModelProperty(value = "事件类别:字典 dis_event_category")
+    private String eventCategory;
 
-    @ApiModelProperty(value = "核实人")
+    @ApiModelProperty(value = "事件等级:字典 dis_event_level")
+    private String eventLevel;
+
+    @ApiModelProperty(value = "发生区域:字典 dis_event_region")
+    private String eventRegion;
+
+    @ApiModelProperty(value = "事件状态:EventStatusEnum")
+    private Integer status;
+
+    @ApiModelProperty(value = "事件状态名称(由 status 推导)")
+    private String statusName;
+
+    @ApiModelProperty(value = "是否可编辑/删除(仅待核实为真)")
+    private Boolean editable;
+
+    @ApiModelProperty(value = "核实结论:0-真实 1-不实")
+    private Integer verifyResult;
+
+    @ApiModelProperty(value = "核验人(按 verifierId 反查 sys_user 昵称,查不到退回快照)")
     private String verifier;
 
+    @ApiModelProperty(value = "核验人用户ID")
+    private Long verifierId;
+
     @ApiModelProperty(value = "核实时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime verifyTime;
 
-    @ApiModelProperty(value = "核实意见")
+    @ApiModelProperty(value = "核实意见 / 核销原因")
     private String verifyOpinion;
 
-    @ApiModelProperty(value = "责任单位(转接部门/责任部门)")
+    @ApiModelProperty(value = "责任处置人(按 handlerId 反查 sys_user 昵称,查不到退回快照)")
+    private String handler;
+
+    @ApiModelProperty(value = "责任处置人用户ID")
+    private Long handlerId;
+
+    @ApiModelProperty(value = "责任处置人电话")
+    private String handlerPhone;
+
+    @ApiModelProperty(value = "责任单位")
     private String department;
 
-    @ApiModelProperty(value = "处置负责人")
-    private String handler;
+    @ApiModelProperty(value = "开始处置时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime disposeTime;
+
+    @ApiModelProperty(value = "解除时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime releaseTime;
+
+    @ApiModelProperty(value = "解除原因:字典 dis_event_release_reason")
+    private String releaseReason;
+
+    @ApiModelProperty(value = "解除说明")
+    private String releaseNote;
+
+    @ApiModelProperty(value = "处置前附件URL(图片/视频,多个英文逗号分隔)")
+    private String beforeUrl;
+
+    @ApiModelProperty(value = "处置后附件URL(图片/视频,多个英文逗号分隔)")
+    private String afterUrl;
+
+    @ApiModelProperty(value = "最近回访时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime lastVisitTime;
+
+    @ApiModelProperty(value = "下次回访时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime nextVisitTime;
+
+    @ApiModelProperty(value = "是否需要升级:字典 dis_yes_no")
+    private Integer needUpgrade;
 
     @ApiModelProperty(value = "应用预案ID")
     private Long planId;
@@ -93,47 +153,42 @@ public class DisEventVO implements Serializable {
     @ApiModelProperty(value = "直接经济损失(万元)")
     private BigDecimal lossAmount;
 
-    @ApiModelProperty(value = "事件状态")
-    private String status;
-
-    @ApiModelProperty(value = "关闭原因")
-    private String closeReason;
-
-    @ApiModelProperty(value = "关闭说明")
-    private String closeRemark;
-
-    @ApiModelProperty(value = "关闭时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime closeTime;
-
     @ApiModelProperty(value = "创建时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime createTime;
 
-    @ApiModelProperty(value = "最近转接部门")
-    private String transferDept;
+    // ==================== 由 dis_event_log 聚合的最近环节信息 ====================
 
-    @ApiModelProperty(value = "最近报送层级")
-    private String reportTargets;
+    @ApiModelProperty(value = "最近一次电话转接的对象部门(多个顿号分隔,operType=3)")
+    private String lastTransferTargets;
 
-    @ApiModelProperty(value = "最近转接时间")
+    @ApiModelProperty(value = "最近一次电话转接时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime transferTime;
+    private LocalDateTime lastTransferTime;
 
-    @ApiModelProperty(value = "关联转接记录ID(最近一条)")
-    private Long transferId;
+    @ApiModelProperty(value = "最近一次上报报送的层级(多个顿号分隔,operType=4)")
+    private String lastReportTargets;
 
-    @ApiModelProperty(value = "最近回访时间")
+    @ApiModelProperty(value = "最近一次上报报送时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime lastVisitTime;
+    private LocalDateTime lastReportTime;
 
-    @ApiModelProperty(value = "下次回访时间")
+    @ApiModelProperty(value = "最近一次报送的附件地址(多个英文逗号分隔,空表示无附件)")
+    private String lastReportAttachUrl;
+
+    @ApiModelProperty(value = "最近一次转接 / 报送操作用户")
+    private String lastTransferUser;
+
+    @ApiModelProperty(value = "工作催办次数(operType=8 的日志条数)")
+    private Long urgeCount;
+
+    @ApiModelProperty(value = "最近催办时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime nextVisitTime;
+    private LocalDateTime lastUrgeTime;
 
-    @ApiModelProperty(value = "关联跟踪记录ID(最近一条)")
-    private Long trackId;
+    @ApiModelProperty(value = "接报后已过时长(如「1天3小时」)")
+    private String elapsed;
 
-    @ApiModelProperty(value = "环节明细(详情用):verifyList/transferList/trackList/urgeList")
-    private Map<String, List<Map<String, Object>>> process;
+    @ApiModelProperty(value = "事件操作日志时间线(详情接口返回)")
+    private List<DisEventLog> logs;
 }

+ 77 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisHomeVO.java

@@ -0,0 +1,77 @@
+package com.zksy.dispatch.dto;
+
+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;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 调度首页看板 VO
+ *
+ * <p>一次请求返回首页全部数据:顶部统计卡、GIS 点位、处置中事件列表、
+ * 近 7 日管网类型分布与事件等级占比。统计口径:</p>
+ * <ul>
+ *   <li>今日接报 / 较昨日:按 reportTime 落在今日 / 昨日的事件数;</li>
+ *   <li>处置中:status=2,其中等级为 Ⅰ级(紧急) 的单独计数;</li>
+ *   <li>已解除:status=3;处置率 = (已解除 + 处置中) / 全部事件 × 100;</li>
+ *   <li>可用预案:dis_plan.status=2(已通过);已发布:存在 verStatus=1(当前版本)的预案版本数;</li>
+ *   <li>类型分布:近 7 日按 event_type 分组(type 为字典编码,前端 useDict 翻译);</li>
+ *   <li>等级占比:已解除单列一段,其余按 event_level(1紧急/2较大/3一般)分组。</li>
+ * </ul>
+ *
+ * @author zengy
+ */
+@Data
+@ApiModel(value = "调度首页看板VO", description = "应急与指挥调度首页统计数据")
+public class DisHomeVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("今日接报事件数")
+    private Long todayCount;
+
+    @ApiModelProperty("昨日接报事件数")
+    private Long yesterdayCount;
+
+    @ApiModelProperty("较昨日增量(今日 - 昨日)")
+    private Long todayDelta;
+
+    @ApiModelProperty("待核实事件数")
+    private Long pendingCount;
+
+    @ApiModelProperty("处置中事件数")
+    private Long disposingCount;
+
+    @ApiModelProperty("处置中且等级为Ⅰ级(紧急)的事件数")
+    private Long urgentDisposingCount;
+
+    @ApiModelProperty("已解除事件数")
+    private Long releasedCount;
+
+    @ApiModelProperty("处置率((已解除+处置中)/全部 ×100,保留整数)")
+    private BigDecimal handledRate;
+
+    @ApiModelProperty("可用预案数(已通过)")
+    private Long planTotal;
+
+    @ApiModelProperty("已发布(存在当前有效版本)的预案数")
+    private Long planPublished;
+
+    @ApiModelProperty("近 7 日管网类型分布:[{type:'1', count:n}],type 为字典 dis_event_type 编码")
+    private List<Map<String, Object>> typeStats;
+
+    @ApiModelProperty("事件等级占比:[{level:'1'|'released', count:n}]")
+    private List<Map<String, Object>> levelStats;
+
+    @ApiModelProperty("处置中事件列表(最新 10 条,status=1/2)")
+    private List<DisEventVO> disposingEvents;
+
+    @ApiModelProperty("GIS 点位:未办结(status=0/1/2)且有坐标的事件精简信息")
+    private List<Map<String, Object>> mapEvents;
+}

+ 0 - 28
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisPlanApprovalPageDTO.java

@@ -1,28 +0,0 @@
-package com.zksy.dispatch.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import java.io.Serializable;
-
-@Data
-@ApiModel(value = "应急预案审批分页查询入参", description = "应急预案审批分页查询入参")
-public class DisPlanApprovalPageDTO implements Serializable {
-    private static final long serialVersionUID = 1L;
-
-    @ApiModelProperty(value = "预案名称")
-    private String planName;
-
-    @ApiModelProperty(value = "预案类型")
-    private String planType;
-
-    @ApiModelProperty(value = "状态")
-    private String status;
-
-    @ApiModelProperty(value = "当前记录起始索引", required = true)
-    private Long pageNum;
-
-    @ApiModelProperty(value = "每页显示记录数", required = true)
-    private Long pageSize;
-}

+ 0 - 25
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisPlanApprovalStepPageDTO.java

@@ -1,25 +0,0 @@
-package com.zksy.dispatch.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import java.io.Serializable;
-
-@Data
-@ApiModel(value = "应急预案审批步骤分页查询入参", description = "应急预案审批步骤分页查询入参")
-public class DisPlanApprovalStepPageDTO implements Serializable {
-    private static final long serialVersionUID = 1L;
-
-    @ApiModelProperty(value = "审批主表ID")
-    private Long approvalId;
-
-    @ApiModelProperty(value = "审批结果")
-    private String result;
-
-    @ApiModelProperty(value = "当前记录起始索引", required = true)
-    private Long pageNum;
-
-    @ApiModelProperty(value = "每页显示记录数", required = true)
-    private Long pageSize;
-}

+ 0 - 25
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisPlanCategoryPageDTO.java

@@ -1,25 +0,0 @@
-package com.zksy.dispatch.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import java.io.Serializable;
-
-@Data
-@ApiModel(value = "预案分类分页查询入参", description = "预案分类分页查询入参")
-public class DisPlanCategoryPageDTO implements Serializable {
-    private static final long serialVersionUID = 1L;
-
-    @ApiModelProperty(value = "分类名称")
-    private String label;
-
-    @ApiModelProperty(value = "父级ID")
-    private Long parentId;
-
-    @ApiModelProperty(value = "当前记录起始索引", required = true)
-    private Long pageNum;
-
-    @ApiModelProperty(value = "每页显示记录数", required = true)
-    private Long pageSize;
-}

+ 38 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/DisUserOptionVO.java

@@ -0,0 +1,38 @@
+package com.zksy.dispatch.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 责任处置人下拉项(系统用户)
+ *
+ * <p>初判核实时「责任处置人」不再手填,改为选择系统用户;选中后前端用
+ * {@code phonenumber} 自动带出「联系电话」,用 {@code nickName} 写入
+ * {@code dis_event.handler}(工作催办即按该字段找催办对象,故必须是可读姓名)。</p>
+ *
+ * @author zengy
+ */
+@Data
+@ApiModel(value = "责任处置人下拉项", description = "应急事件责任处置人可选系统用户")
+public class DisUserOptionVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("用户ID")
+    private Long userId;
+
+    @ApiModelProperty("用户昵称;写入 handler 作为责任处置人")
+    private String nickName;
+
+    @ApiModelProperty("联系电话;选中后前端自动带出")
+    private String phonenumber;
+
+    @ApiModelProperty("所属部门ID;前端据它把处置人候选限定在所选责任单位内")
+    private Long deptId;
+
+    @ApiModelProperty("所属部门名称")
+    private String deptName;
+}

+ 30 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EnumOptionVO.java

@@ -0,0 +1,30 @@
+package com.zksy.dispatch.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 枚举下拉项
+ *
+ * <p>状态类字段取值走 Java 枚举(从 0 起始),前端筛选下拉需要「编码 + 中文」列表。
+ * 由后端把枚举暴露成选项,避免把中文常量复制到 Vue 里造成两处维护。</p>
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@ApiModel(value = "枚举下拉项", description = "枚举下拉项(编码 + 中文)")
+public class EnumOptionVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "枚举编码")
+    private Integer value;
+
+    @ApiModelProperty(value = "中文名称")
+    private String label;
+}

+ 14 - 16
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventTrackPageInDTO.java → pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventLogPageDTO.java

@@ -6,35 +6,33 @@ import lombok.Data;
 
 import java.io.Serializable;
 
+/**
+ * 事件日志分页查询入参
+ */
 @Data
-@ApiModel(value = "跟踪解除分页查询入参", description = "跟踪解除分页查询入参")
-public class EventTrackPageInDTO implements Serializable {
+@ApiModel(value = "事件日志分页查询入参", description = "事件日志分页查询入参")
+public class EventLogPageDTO implements Serializable {
+
     private static final long serialVersionUID = 1L;
 
+    @ApiModelProperty(value = "事件ID")
+    private Long eventId;
+
     @ApiModelProperty(value = "事件编号")
     private String eventNo;
 
-    @ApiModelProperty(value = "事件类别")
-    private String category;
-
-    @ApiModelProperty(value = "事件级别")
-    private String level;
-
-    @ApiModelProperty(value = "状态")
-    private String status;
-
-    @ApiModelProperty(value = "处理人")
-    private String handler;
+    @ApiModelProperty(value = "操作类型:EventLogOperTypeEnum")
+    private Integer operType;
 
     @ApiModelProperty(value = "开始时间")
-    private String beginTime;
+    private String startTime;
 
     @ApiModelProperty(value = "结束时间")
     private String endTime;
 
     @ApiModelProperty(value = "当前记录起始索引", required = true)
-    private Long pageNum;
+    private Long pageNum = 1L;
 
     @ApiModelProperty(value = "每页显示记录数", required = true)
-    private Long pageSize;
+    private Long pageSize = 10L;
 }

+ 38 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventReleaseDTO.java

@@ -0,0 +1,38 @@
+package com.zksy.dispatch.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 事件解除入参
+ *
+ * <p>状态 2 处置中 → 3 已解除。</p>
+ */
+@Data
+@ApiModel(value = "事件解除入参", description = "事件解除入参")
+public class EventReleaseDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "事件ID", required = true)
+    private Long eventId;
+
+    @ApiModelProperty(value = "解除原因:字典 dis_event_release_reason", required = true)
+    private String releaseReason;
+
+    @ApiModelProperty(value = "解除说明 / 处置结果补充")
+    private String releaseNote;
+
+    @ApiModelProperty(value = "直接经济损失(万元);为空时保持原值")
+    private BigDecimal lossAmount;
+
+    @ApiModelProperty(value = "处置前附件URL(图片/视频,多个英文逗号分隔;解除原因为「事件已处置完成」时上传)")
+    private String beforeUrl;
+
+    @ApiModelProperty(value = "处置后附件URL(图片/视频,多个英文逗号分隔;解除原因为「事件已处置完成」时上传)")
+    private String afterUrl;
+}

+ 0 - 28
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventReportDTO.java

@@ -1,28 +0,0 @@
-package com.zksy.dispatch.dto;
-
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import java.io.Serializable;
-import java.util.List;
-
-@Data
-public class EventReportDTO implements Serializable {
-
-    @ApiModelProperty("转接记录ID")
-    private Long id;
-
-    @ApiModelProperty("报送层级列表")
-    private List<String> reportTargets;
-
-    @ApiModelProperty("附件列表")
-    private List<AttachmentDTO> attachments;
-
-    @Data
-    public static class AttachmentDTO implements Serializable {
-        @ApiModelProperty("原始文件名")
-        private String fileName;
-        @ApiModelProperty("文件路径/URL")
-        private String url;
-    }
-}

+ 0 - 37
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventReportPageInDTO.java

@@ -1,37 +0,0 @@
-package com.zksy.dispatch.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import java.io.Serializable;
-
-@Data
-@ApiModel(value = "接报管理分页查询入参", description = "接报管理分页查询入参")
-public class EventReportPageInDTO implements Serializable {
-    private static final long serialVersionUID = 1L;
-
-    @ApiModelProperty(value = "来源")
-    private String source;
-
-    @ApiModelProperty(value = "状态")
-    private String status;
-
-    @ApiModelProperty(value = "地点")
-    private String location;
-
-    @ApiModelProperty(value = "开始时间")
-    private String beginTime;
-
-    @ApiModelProperty(value = "结束时间")
-    private String endTime;
-
-    @ApiModelProperty(value = "关键字(事件编号/描述/报告人)")
-    private String keyword;
-
-    @ApiModelProperty(value = "当前记录起始索引", required = true)
-    private Long pageNum;
-
-    @ApiModelProperty(value = "每页显示记录数", required = true)
-    private Long pageSize;
-}

+ 65 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventSaveDTO.java

@@ -0,0 +1,65 @@
+package com.zksy.dispatch.dto;
+
+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;
+
+/**
+ * 接报登记 新增 / 编辑 入参
+ *
+ * <p>只承载「接报登记」环节可填的业务字段:
+ * 事件编号、事件状态由后端生成,不能由前端传入;
+ * 核实结论、等级、责任单位等属于后续环节,走各自的动作接口。</p>
+ */
+@Data
+@ApiModel(value = "接报登记入参", description = "接报登记新增与编辑入参")
+public class EventSaveDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "事件ID;新增时为空,编辑时必填")
+    private Long id;
+
+    @ApiModelProperty(value = "事件来源:字典 dis_event_source", required = true)
+    private String eventSource;
+
+    @ApiModelProperty(value = "来源渠道:字典 dis_event_channel")
+    private String channel;
+
+    @ApiModelProperty(value = "管网类型:字典 dis_event_type(1供水管网 2燃气管网 3排水管网 9其他)")
+    private String eventType;
+
+    @ApiModelProperty(value = "发生地点", required = true)
+    private String location;
+
+    @ApiModelProperty(value = "经度")
+    private BigDecimal longitude;
+
+    @ApiModelProperty(value = "纬度")
+    private BigDecimal latitude;
+
+    // 前端 el-date-picker 的 value-format 是 "yyyy-MM-dd HH:mm:ss",
+    // 本模块没有全局 Jackson 时间配置,LocalDateTime 必须靠注解指定格式,
+    // 否则 Jackson 会按 ISO(第 10 位要求 'T')解析而抛 JSON parse error。
+    @ApiModelProperty(value = "发生时间", required = true)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime happenTime;
+
+    @ApiModelProperty(value = "接报时间;为空时取当前时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime reportTime;
+
+    @ApiModelProperty(value = "接报人;新增时忽略传入值,后端固定取当前登录用户;编辑时也不可改")
+    private String reporter;
+
+    @ApiModelProperty(value = "上报人联系电话")
+    private String reporterPhone;
+
+    @ApiModelProperty(value = "事件描述", required = true)
+    private String description;
+}

+ 43 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventTrackDTO.java

@@ -0,0 +1,43 @@
+package com.zksy.dispatch.dto;
+
+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.time.LocalDateTime;
+
+/**
+ * 开始处置 / 处置跟踪入参
+ *
+ * <p>两者都是写 dis_event_log:开始处置额外把状态 1 已核实 → 2 处置中;
+ * 处置跟踪可多次记录,<b>不改变状态</b>,但会把回访时间与是否需要升级回填到主表当前值。</p>
+ */
+@Data
+@ApiModel(value = "处置跟踪入参", description = "开始处置与处置跟踪入参")
+public class EventTrackDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "事件ID", required = true)
+    private Long eventId;
+
+    @ApiModelProperty(value = "处置备注 / 跟踪说明")
+    private String operDesc;
+
+    @ApiModelProperty(value = "态势评估:字典 dis_event_trend(处置跟踪填写)")
+    private String trend;
+
+    @ApiModelProperty(value = "是否需要升级:0-否 1-是(处置跟踪填写)")
+    private Integer needUpgrade;
+
+    // 本模块无全局 Jackson 时间配置,LocalDateTime 入参必须显式指定格式,
+    // 否则前端传 "yyyy-MM-dd HH:mm:ss" 会被按 ISO 解析而抛 JSON parse error。
+    @ApiModelProperty(value = "下次回访时间(处置跟踪填写)")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime nextVisitTime;
+
+    @ApiModelProperty(value = "附件URL,多个逗号分隔")
+    private String attachUrl;
+}

+ 32 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventTransferDTO.java

@@ -0,0 +1,32 @@
+package com.zksy.dispatch.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 转接 / 报送入参
+ *
+ * <p>转接与报送均为「已核实」之后的操作记录,<b>只写 dis_event_log,不改变事件状态</b>。</p>
+ */
+@Data
+@ApiModel(value = "转接报送入参", description = "转接报送入参")
+public class EventTransferDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "事件ID", required = true)
+    private Long eventId;
+
+    @ApiModelProperty(value = "目标部门 / 报送层级名称列表(电话转接填接收部门,上报报送填报送层级)", required = true)
+    private List<String> depts;
+
+    @ApiModelProperty(value = "操作备注")
+    private String operDesc;
+
+    @ApiModelProperty(value = "附件URL,多个逗号分隔(上报报送常用)")
+    private String attachUrl;
+}

+ 0 - 40
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventTransferPageInDTO.java

@@ -1,40 +0,0 @@
-package com.zksy.dispatch.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import java.io.Serializable;
-
-@Data
-@ApiModel(value = "转接报送分页查询入参", description = "转接报送分页查询入参")
-public class EventTransferPageInDTO implements Serializable {
-    private static final long serialVersionUID = 1L;
-
-    @ApiModelProperty(value = "事件编号")
-    private String eventNo;
-
-    @ApiModelProperty(value = "事件类别")
-    private String category;
-
-    @ApiModelProperty(value = "事件级别")
-    private String level;
-
-    @ApiModelProperty(value = "状态")
-    private String status;
-
-    @ApiModelProperty(value = "转接部门")
-    private String transferDept;
-
-    @ApiModelProperty(value = "开始时间")
-    private String beginTime;
-
-    @ApiModelProperty(value = "结束时间")
-    private String endTime;
-
-    @ApiModelProperty(value = "当前记录起始索引", required = true)
-    private Long pageNum;
-
-    @ApiModelProperty(value = "每页显示记录数", required = true)
-    private Long pageSize;
-}

+ 41 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventUrgeDTO.java

@@ -0,0 +1,41 @@
+package com.zksy.dispatch.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 工作催办入参
+ *
+ * <p>催办不建独立表:一次催办就是 dis_event_log 里 operType=8 的一条记录,
+ * 列表页的「催办次数」「最近催办时间」由日志聚合得出,状态取事件当前状态。</p>
+ */
+@Data
+@ApiModel(value = "工作催办入参", description = "工作催办入参")
+public class EventUrgeDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "事件ID", required = true)
+    private Long eventId;
+
+    @ApiModelProperty(value = "目标接收人姓名", required = true)
+    private String receiver;
+
+    @ApiModelProperty(value = "接收部门")
+    private String receiverDept;
+
+    @ApiModelProperty(value = "接收人电话")
+    private String receiverPhone;
+
+    @ApiModelProperty(value = "催办方式:字典 dis_event_urge_method", required = true)
+    private String method;
+
+    @ApiModelProperty(value = "催办内容")
+    private String operDesc;
+
+    @ApiModelProperty(value = "催办结果:字典 dis_event_urge_result")
+    private String urgeResult;
+}

+ 12 - 11
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventUrgePageInDTO.java → pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventUrgePageDTO.java

@@ -6,32 +6,33 @@ import lombok.Data;
 
 import java.io.Serializable;
 
+/**
+ * 工作催办分页查询入参
+ */
 @Data
 @ApiModel(value = "工作催办分页查询入参", description = "工作催办分页查询入参")
-public class EventUrgePageInDTO implements Serializable {
+public class EventUrgePageDTO implements Serializable {
+
     private static final long serialVersionUID = 1L;
 
     @ApiModelProperty(value = "事件编号")
     private String eventNo;
 
-    @ApiModelProperty(value = "接收人")
-    private String receiver;
-
-    @ApiModelProperty(value = "接收部门")
-    private String receiverDept;
+    @ApiModelProperty(value = "事件状态(EventStatusEnum)")
+    private Integer status;
 
-    @ApiModelProperty(value = "状态")
-    private String status;
+    @ApiModelProperty(value = "责任人")
+    private String handler;
 
     @ApiModelProperty(value = "开始时间")
-    private String beginTime;
+    private String startTime;
 
     @ApiModelProperty(value = "结束时间")
     private String endTime;
 
     @ApiModelProperty(value = "当前记录起始索引", required = true)
-    private Long pageNum;
+    private Long pageNum = 1L;
 
     @ApiModelProperty(value = "每页显示记录数", required = true)
-    private Long pageSize;
+    private Long pageSize = 10L;
 }

+ 84 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventUrgeRowVO.java

@@ -0,0 +1,84 @@
+package com.zksy.dispatch.dto;
+
+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.time.LocalDateTime;
+
+/**
+ * 工作催办列表行
+ *
+ * <p>按原型口径:一行 = 一个事件 + 最近一次催办信息 + 催办次数,
+ * 「催办事项」即事件描述,「状态」即事件当前状态。</p>
+ */
+@Data
+@ApiModel(value = "工作催办列表行", description = "工作催办列表行")
+public class EventUrgeRowVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "事件ID")
+    private Long eventId;
+
+    @ApiModelProperty(value = "事件编号")
+    private String eventNo;
+
+    @ApiModelProperty(value = "管网类型:字典 dis_event_type")
+    private String eventType;
+
+    @ApiModelProperty(value = "催办事项(= 事件描述)")
+    private String summary;
+
+    @ApiModelProperty(value = "事件状态(EventStatusEnum)")
+    private Integer status;
+
+    @ApiModelProperty(value = "事件状态名称")
+    private String statusName;
+
+    @ApiModelProperty(value = "接报时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime reportTime;
+
+    @ApiModelProperty(value = "接报后已过时长(如「1天3小时」)")
+    private String elapsed;
+
+    @ApiModelProperty(value = "责任人(按 handlerId 反查 sys_user 昵称,查不到退回快照)")
+    private String handler;
+
+    @ApiModelProperty(value = "责任人用户ID")
+    private Long handlerId;
+
+    @ApiModelProperty(value = "责任人电话(初判核实录入的 handler_phone;催办弹窗展示用)")
+    private String handlerPhone;
+
+    @ApiModelProperty(value = "责任部门")
+    private String department;
+
+    @ApiModelProperty(value = "催办次数")
+    private Long urgeCount;
+
+    @ApiModelProperty(value = "最近催办时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime lastUrgeTime;
+
+    @ApiModelProperty(value = "最近催办方式:字典 dis_event_urge_method")
+    private String lastMethod;
+
+    @ApiModelProperty(value = "最近催办内容")
+    private String lastContent;
+
+    @ApiModelProperty(value = "最近催办结果:字典 dis_event_urge_result")
+    private String lastResult;
+
+    @ApiModelProperty(value = "目标接收人")
+    private String receiver;
+
+    @ApiModelProperty(value = "接收部门")
+    private String receiverDept;
+
+    @ApiModelProperty(value = "接收人电话")
+    private String receiverPhone;
+}

+ 56 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventVerifyDTO.java

@@ -0,0 +1,56 @@
+package com.zksy.dispatch.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 初判核实入参
+ *
+ * <p>真实性判定为「真实」时补齐管网类型 / 事件类别 / 事件等级 / 责任处置人;
+ * 判定为「不实」时只需填写核销原因,事件直接结束。</p>
+ */
+@Data
+@ApiModel(value = "初判核实入参", description = "初判核实入参")
+public class EventVerifyDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "事件ID", required = true)
+    private Long id;
+
+    @ApiModelProperty(value = "核实结论:0-真实 1-不实", required = true)
+    private Integer verifyResult;
+
+    @ApiModelProperty(value = "管网类型:字典 dis_event_type")
+    private String eventType;
+
+    @ApiModelProperty(value = "事件类别:字典 dis_event_category")
+    private String eventCategory;
+
+    @ApiModelProperty(value = "事件等级:字典 dis_event_level")
+    private String eventLevel;
+
+    @ApiModelProperty(value = "发生区域:字典 dis_event_region")
+    private String eventRegion;
+
+    @ApiModelProperty(value = "责任处置人")
+    private String handler;
+
+    @ApiModelProperty(value = "责任处置人用户ID(sys_user.user_id);前端从系统用户下拉选中后回传")
+    private Long handlerId;
+
+    @ApiModelProperty(value = "责任处置人电话")
+    private String handlerPhone;
+
+    @ApiModelProperty(value = "责任单位")
+    private String department;
+
+    @ApiModelProperty(value = "核验人;为空时取当前登录用户")
+    private String verifier;
+
+    @ApiModelProperty(value = "核实意见;判定为不实时填核销原因")
+    private String verifyOpinion;
+}

+ 0 - 40
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/EventVerifyPageInDTO.java

@@ -1,40 +0,0 @@
-package com.zksy.dispatch.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import java.io.Serializable;
-
-@Data
-@ApiModel(value = "初判核实分页查询入参", description = "初判核实分页查询入参")
-public class EventVerifyPageInDTO implements Serializable {
-    private static final long serialVersionUID = 1L;
-
-    @ApiModelProperty(value = "事件编号")
-    private String eventNo;
-
-    @ApiModelProperty(value = "事件类别")
-    private String category;
-
-    @ApiModelProperty(value = "事件级别")
-    private String level;
-
-    @ApiModelProperty(value = "状态")
-    private String status;
-
-    @ApiModelProperty(value = "是否属实")
-    private String isReal;
-
-    @ApiModelProperty(value = "开始时间")
-    private String beginTime;
-
-    @ApiModelProperty(value = "结束时间")
-    private String endTime;
-
-    @ApiModelProperty(value = "当前记录起始索引", required = true)
-    private Long pageNum;
-
-    @ApiModelProperty(value = "每页显示记录数", required = true)
-    private Long pageSize;
-}

+ 23 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/PlanApproveDTO.java

@@ -0,0 +1,23 @@
+package com.zksy.dispatch.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 预案审批入参
+ *
+ * <p>审批为单环节:通过 / 不通过 + 意见,通过与否由调用的接口决定
+ * ({@code /approve/{id}} 或 {@code /reject/{id}})。</p>
+ */
+@Data
+@ApiModel(value = "预案审批入参", description = "预案审批入参")
+public class PlanApproveDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "审批意见;不通过时为驳回原因(必填)")
+    private String approveNote;
+}

+ 49 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/PlanPageDTO.java

@@ -0,0 +1,49 @@
+package com.zksy.dispatch.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 预案分页查询入参
+ *
+ * <p>预案编辑与预案审批共用:审批页传 {@code status=1} 即只看待审核。</p>
+ */
+@Data
+@ApiModel(value = "预案分页查询入参", description = "预案分页查询入参")
+public class PlanPageDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "预案编号(模糊)")
+    private String planNo;
+
+    @ApiModelProperty(value = "预案名称(模糊)")
+    private String planName;
+
+    @ApiModelProperty(value = "预案类型:字典 dis_plan_type")
+    private String planType;
+
+    @ApiModelProperty(value = "预案等级:字典 dis_plan_level")
+    private String planLevel;
+
+    @ApiModelProperty(value = "预案状态:PlanStatusEnum —— 0草稿 1待审核 2已通过 3已驳回")
+    private Integer status;
+
+    @ApiModelProperty(value = "是否模板:0-否 1-是")
+    private Integer isTemplate;
+
+    @ApiModelProperty(value = "开始日期 yyyy-MM-dd(按创建时间)")
+    private String startDate;
+
+    @ApiModelProperty(value = "结束日期 yyyy-MM-dd(按创建时间)")
+    private String endDate;
+
+    @ApiModelProperty(value = "当前记录起始索引", required = true)
+    private Long pageNum = 1L;
+
+    @ApiModelProperty(value = "每页显示记录数", required = true)
+    private Long pageSize = 10L;
+}

+ 58 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/PlanSaveDTO.java

@@ -0,0 +1,58 @@
+package com.zksy.dispatch.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 预案新增 / 编辑入参
+ *
+ * <p>「草稿 / 已驳回」保存是直接改本预案当前值;
+ * 「已通过」保存视为<b>修订</b>,会生成一条审核中的候选版本,审批通过才替换生效版本。</p>
+ *
+ * <p>预案编号与当前生效版本号均由后端管理,不由前端传入。</p>
+ */
+@Data
+@ApiModel(value = "预案保存入参", description = "预案新增与编辑入参")
+public class PlanSaveDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "预案ID;新增时为空,编辑时必填")
+    private Long id;
+
+    @ApiModelProperty(value = "预案名称", required = true)
+    private String planName;
+
+    @ApiModelProperty(value = "预案类型:字典 dis_plan_type", required = true)
+    private String planType;
+
+    @ApiModelProperty(value = "预案等级:字典 dis_plan_level")
+    private String planLevel;
+
+    @ApiModelProperty(value = "编制单位")
+    private String compileUnit;
+
+    @ApiModelProperty(value = "发布单位")
+    private String publishUnit;
+
+    @ApiModelProperty(value = "发布日期 yyyy-MM-dd")
+    private String publishDate;
+
+    @ApiModelProperty(value = "附件名称")
+    private String attachName;
+
+    @ApiModelProperty(value = "附件路径")
+    private String attachPath;
+
+    @ApiModelProperty(value = "是否设为预案模板:0-否 1-是")
+    private Integer isTemplate;
+
+    @ApiModelProperty(value = "预案正文(富文本 HTML)")
+    private String content;
+
+    @ApiModelProperty(value = "变更说明;修订已通过预案时必填")
+    private String changeNote;
+}

+ 57 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/PlanVersionVO.java

@@ -0,0 +1,57 @@
+package com.zksy.dispatch.dto;
+
+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.time.LocalDateTime;
+
+/**
+ * 预案版本快照展示
+ */
+@Data
+@ApiModel(value = "预案版本VO", description = "预案版本快照展示")
+public class PlanVersionVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "版本ID")
+    private Long id;
+
+    @ApiModelProperty(value = "预案ID")
+    private Long planId;
+
+    @ApiModelProperty(value = "预案编号")
+    private String planNo;
+
+    @ApiModelProperty(value = "版本号")
+    private String version;
+
+    @ApiModelProperty(value = "版本状态:PlanVersionStatusEnum —— 0历史 1当前版本 2审核中 3已驳回")
+    private Integer verStatus;
+
+    @ApiModelProperty(value = "版本状态名称")
+    private String verStatusName;
+
+    @ApiModelProperty(value = "变更说明")
+    private String changeNote;
+
+    @ApiModelProperty(value = "修订人")
+    private String editor;
+
+    @ApiModelProperty(value = "修订人用户ID")
+    private Long editorId;
+
+    @ApiModelProperty(value = "修订时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime editTime;
+
+    @ApiModelProperty(value = "版本正文快照")
+    private String content;
+
+    private String attachName;
+
+    private String attachPath;
+}

+ 40 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/dto/SseMessageDto.java

@@ -0,0 +1,40 @@
+package com.zksy.dispatch.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * SSE 消息体(轻量版,参照 RuoYi-Vue-Plus 5.x SseMessageDto 设计)
+ *
+ * <p>序列化后作为 EventSource message 事件负载下发,前端按 type 分发。</p>
+ *
+ * @author zengy
+ */
+@Data
+@ApiModel(value = "SSE消息体", description = "服务端实时推送消息")
+public class SseMessageDto implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 消息类型:催办提醒 */
+    public static final String TYPE_URGING = "URGING";
+
+    @ApiModelProperty("消息类型:URGING-催办提醒")
+    private String type;
+
+    @ApiModelProperty("消息标题")
+    private String title;
+
+    @ApiModelProperty("消息内容")
+    private String content;
+
+    @ApiModelProperty("催办人姓名(前端来源标注用)")
+    private String urgingUser;
+
+    @ApiModelProperty("接收人用户ID列表(服务端内部使用,前端无需关心)")
+    private List<Long> userIds;
+}

+ 79 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/enums/EventLogOperTypeEnum.java

@@ -0,0 +1,79 @@
+package com.zksy.dispatch.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Objects;
+
+/**
+ * 应急事件操作日志类型枚举(dis_event_log.oper_type)
+ *
+ * <p>与事件生命周期各环节一一对应,从 0 开始编号。</p>
+ * <p>其中 3 电话转接 / 4 上报报送 / 6 处置跟踪 / 8 工作催办 / 9 信息修改
+ * <b>不改变事件状态</b>,写入日志时 status_from 与 status_to 相同或为 NULL。</p>
+ *
+ * @author zengy
+ */
+@Getter
+@AllArgsConstructor
+public enum EventLogOperTypeEnum {
+
+    /** 接报登记,事件创建 */
+    CREATE(0, "接报登记"),
+    /** 初判核实判定为真实事件 */
+    VERIFY(1, "初判核实"),
+    /** 初判核实判定为不实事件,核销 */
+    VERIFY_FALSE(2, "不实核销"),
+    /** 电话转接 */
+    TRANSFER(3, "电话转接"),
+    /** 上报报送 */
+    REPORT(4, "上报报送"),
+    /** 开始处置 */
+    START_DISPOSE(5, "开始处置"),
+    /** 处置跟踪,可多次记录 */
+    TRACK(6, "处置跟踪"),
+    /** 事件解除 */
+    RELEASE(7, "事件解除"),
+    /** 工作催办 */
+    URGE(8, "工作催办"),
+    /** 事件信息修改 */
+    UPDATE(9, "信息修改");
+
+    private final Integer code;
+
+    private final String desc;
+
+    /**
+     * 按 code 获取枚举
+     */
+    public static EventLogOperTypeEnum getByCode(Integer code) {
+        if (code == null) {
+            return null;
+        }
+        for (EventLogOperTypeEnum item : values()) {
+            if (Objects.equals(item.code, code)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 按 code 获取操作描述,未知返回 "操作"
+     */
+    public static String getDescByCode(Integer code) {
+        EventLogOperTypeEnum item = getByCode(code);
+        return item == null ? "操作" : item.getDesc();
+    }
+
+    /**
+     * 该操作是否改变事件状态
+     */
+    public static boolean changesStatus(Integer code) {
+        return Objects.equals(code, CREATE.code)
+                || Objects.equals(code, VERIFY.code)
+                || Objects.equals(code, VERIFY_FALSE.code)
+                || Objects.equals(code, START_DISPOSE.code)
+                || Objects.equals(code, RELEASE.code);
+    }
+}

+ 149 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/enums/EventStatusEnum.java

@@ -0,0 +1,149 @@
+package com.zksy.dispatch.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * 应急事件状态枚举(dis_event.status)
+ *
+ * <p>主流程:0 待核实 → 1 已核实 → 2 处置中 → 3 已解除</p>
+ * <p>旁路:4 不实事件(初判核实判定为不实,事件直接结束)</p>
+ *
+ * <p>转接、报送、处置跟踪、工作催办只写 dis_event_log,<b>不改变事件状态</b>。</p>
+ * <p>旧库中文状态映射:待受理/待核实→0,已核实→1,处置中→2,已解除/已关闭→3,不实事件→4。</p>
+ *
+ * @author zengy
+ */
+@Getter
+@AllArgsConstructor
+public enum EventStatusEnum {
+
+    /** 接报登记后待初判核实 */
+    PENDING_VERIFY(0, "待核实"),
+    /** 初判核实判定为真实,待处置 */
+    VERIFIED(1, "已核实"),
+    /** 已开始处置 */
+    DISPOSING(2, "处置中"),
+    /** 处置完成并解除,终态 */
+    RELEASED(3, "已解除"),
+    /** 初判核实判定为不实,终态 */
+    FALSE_EVENT(4, "不实事件");
+
+    private final Integer code;
+
+    private final String desc;
+
+    /** 终态:已解除、不实事件 */
+    private static final List<Integer> FINISHED_CODES = Collections.unmodifiableList(
+            Arrays.asList(RELEASED.code, FALSE_EVENT.code));
+
+    /** 流转中(未办结)状态 */
+    private static final List<Integer> ACTIVE_CODES = Collections.unmodifiableList(
+            Arrays.asList(PENDING_VERIFY.code, VERIFIED.code, DISPOSING.code));
+
+    /** 允许编辑 / 删除的状态:待核实 */
+    private static final List<Integer> EDITABLE_CODES = Collections.unmodifiableList(
+            Collections.singletonList(PENDING_VERIFY.code));
+
+    /** 允许工作催办的状态:待核实(卡在核实)、已核实(未处置)、处置中(超时未办结) */
+    private static final List<Integer> URGEABLE_CODES = Collections.unmodifiableList(
+            Arrays.asList(PENDING_VERIFY.code, VERIFIED.code, DISPOSING.code));
+
+    /**
+     * 按 code 获取枚举
+     */
+    public static EventStatusEnum getByCode(Integer code) {
+        if (code == null) {
+            return null;
+        }
+        for (EventStatusEnum item : values()) {
+            if (Objects.equals(item.code, code)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 按 code 获取状态描述,未知返回 "未知"
+     */
+    public static String getDescByCode(Integer code) {
+        EventStatusEnum item = getByCode(code);
+        return item == null ? "未知" : item.getDesc();
+    }
+
+    /**
+     * 是否为合法状态值
+     */
+    public static boolean isValid(Integer code) {
+        return getByCode(code) != null;
+    }
+
+    /**
+     * 全部状态 code
+     */
+    public static List<Integer> codes() {
+        return Arrays.stream(values()).map(EventStatusEnum::getCode).collect(Collectors.toList());
+    }
+
+    /** 已办结(已解除 / 不实事件) */
+    public static boolean isFinished(Integer code) {
+        return code != null && FINISHED_CODES.contains(code);
+    }
+
+    /** 未办结 */
+    public static boolean isActive(Integer code) {
+        return code != null && ACTIVE_CODES.contains(code);
+    }
+
+    /** 可初判核实:仅待核实 */
+    public static boolean canVerify(Integer code) {
+        return Objects.equals(code, PENDING_VERIFY.code);
+    }
+
+    /** 可开始处置:仅已核实 */
+    public static boolean canStartDispose(Integer code) {
+        return Objects.equals(code, VERIFIED.code);
+    }
+
+    /** 可处置跟踪:仅处置中 */
+    public static boolean canTrack(Integer code) {
+        return Objects.equals(code, DISPOSING.code);
+    }
+
+    /** 可解除:仅处置中 */
+    public static boolean canRelease(Integer code) {
+        return Objects.equals(code, DISPOSING.code);
+    }
+
+    /** 可转接 / 报送:已核实之后(含处置中) */
+    public static boolean canTransfer(Integer code) {
+        return code != null && (Objects.equals(code, VERIFIED.code) || Objects.equals(code, DISPOSING.code));
+    }
+
+    /** 可编辑 / 删除:仅待核实 */
+    public static boolean canEdit(Integer code) {
+        return code != null && EDITABLE_CODES.contains(code);
+    }
+
+    /** 可工作催办 */
+    public static boolean canUrge(Integer code) {
+        return code != null && URGEABLE_CODES.contains(code);
+    }
+
+    /** 未办结状态列表 */
+    public static List<Integer> activeCodes() {
+        return ACTIVE_CODES;
+    }
+
+    /** 终态状态列表 */
+    public static List<Integer> finishedCodes() {
+        return FINISHED_CODES;
+    }
+}

+ 49 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/enums/GradeStatusEnum.java

@@ -0,0 +1,49 @@
+package com.zksy.dispatch.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Objects;
+
+/**
+ * 应急事件分级标准启用状态枚举(dis_event_grade.status)
+ *
+ * <p>取值从 0 起始,与其它枚举保持一致;等级标准本身不做停用删除,
+ * 只通过本状态控制是否参与初判核实的辅助匹配。</p>
+ *
+ * @author zengy
+ */
+@Getter
+@AllArgsConstructor
+public enum GradeStatusEnum {
+
+    /** 启用 */
+    ENABLED(0, "启用"),
+    /** 停用 */
+    DISABLED(1, "停用");
+
+    private final Integer code;
+
+    private final String desc;
+
+    public static GradeStatusEnum getByCode(Integer code) {
+        if (code == null) {
+            return null;
+        }
+        for (GradeStatusEnum item : values()) {
+            if (Objects.equals(item.code, code)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    public static String getDescByCode(Integer code) {
+        GradeStatusEnum item = getByCode(code);
+        return item == null ? "未知" : item.getDesc();
+    }
+
+    public static boolean isValid(Integer code) {
+        return getByCode(code) != null;
+    }
+}

+ 50 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/enums/PlanLogOperTypeEnum.java

@@ -0,0 +1,50 @@
+package com.zksy.dispatch.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Objects;
+
+/**
+ * 应急预案操作日志类型枚举(dis_plan_log.oper_type)
+ *
+ * <p>从 0 开始编号。</p>
+ *
+ * @author zengy
+ */
+@Getter
+@AllArgsConstructor
+public enum PlanLogOperTypeEnum {
+
+    /** 新建预案 */
+    CREATE(0, "新建"),
+    /** 提交审核 */
+    SUBMIT(1, "提交审核"),
+    /** 审批通过 */
+    APPROVE(2, "审批通过"),
+    /** 审批驳回 */
+    REJECT(3, "审批驳回"),
+    /** 修订已通过预案,生成候选版本 */
+    REVISE(4, "修订");
+
+    private final Integer code;
+
+    private final String desc;
+
+    public static PlanLogOperTypeEnum getByCode(Integer code) {
+        if (code == null) {
+            return null;
+        }
+        for (PlanLogOperTypeEnum item : values()) {
+            if (Objects.equals(item.code, code)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    public static String getDescByCode(Integer code) {
+        PlanLogOperTypeEnum item = getByCode(code);
+        return item == null ? "操作" : item.getDesc();
+    }
+}

+ 76 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/enums/PlanStatusEnum.java

@@ -0,0 +1,76 @@
+package com.zksy.dispatch.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Objects;
+
+/**
+ * 应急预案状态枚举(dis_plan.status)
+ *
+ * <p>流程:0 草稿 → 1 待审核 → 2 已通过 / 3 已驳回</p>
+ * <p>「已驳回」可修改后重新提交,回到 1 待审核。</p>
+ * <p>旧库状态映射:编辑中→0,审批中/终审中→1,已发布→2,待修正→3。</p>
+ *
+ * @author zengy
+ */
+@Getter
+@AllArgsConstructor
+public enum PlanStatusEnum {
+
+    /** 编辑中,可自由修改 */
+    DRAFT(0, "草稿"),
+    /** 已提交审核,等待审批 */
+    PENDING(1, "待审核"),
+    /** 审批通过,版本生效 */
+    APPROVED(2, "已通过"),
+    /** 审批不通过,需修改后重新提交 */
+    REJECTED(3, "已驳回");
+
+    private final Integer code;
+
+    private final String desc;
+
+    public static PlanStatusEnum getByCode(Integer code) {
+        if (code == null) {
+            return null;
+        }
+        for (PlanStatusEnum item : values()) {
+            if (Objects.equals(item.code, code)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    public static String getDescByCode(Integer code) {
+        PlanStatusEnum item = getByCode(code);
+        return item == null ? "未知" : item.getDesc();
+    }
+
+    public static boolean isValid(Integer code) {
+        return getByCode(code) != null;
+    }
+
+    /** 可提交审核:草稿、已驳回 */
+    public static boolean canSubmit(Integer code) {
+        return Objects.equals(code, DRAFT.code) || Objects.equals(code, REJECTED.code);
+    }
+
+    /** 可审批:仅待审核 */
+    public static boolean canApprove(Integer code) {
+        return Objects.equals(code, PENDING.code);
+    }
+
+    /** 可编辑正文:草稿、已驳回 */
+    public static boolean canEdit(Integer code) {
+        return Objects.equals(code, DRAFT.code) || Objects.equals(code, REJECTED.code);
+    }
+
+    /**
+     * 可修订(生成候选版本):仅已通过
+     */
+    public static boolean canRevise(Integer code) {
+        return Objects.equals(code, APPROVED.code);
+    }
+}

+ 63 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/dispatch/enums/PlanVersionStatusEnum.java

@@ -0,0 +1,63 @@
+package com.zksy.dispatch.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Objects;
+
+/**
+ * 应急预案版本状态枚举(dis_plan_version.ver_status)
+ *
+ * <p>版本是「审核通过」后落地的快照:</p>
+ * <ul>
+ *   <li>修订已通过预案并保存 → 生成一条 2 审核中候选版本,旧版本仍为 1 当前生效</li>
+ *   <li>审核通过 → 候选版本转 1 当前生效,旧版本降 0 历史</li>
+ *   <li>审核不通过 → 候选版本转 3 已驳回,旧版本保持 1 当前生效</li>
+ * </ul>
+ *
+ * @author zengy
+ */
+@Getter
+@AllArgsConstructor
+public enum PlanVersionStatusEnum {
+
+    /** 历史版本 */
+    HISTORY(0, "历史"),
+    /** 当前生效版本 */
+    EFFECTIVE(1, "当前版本"),
+    /** 已提交审核的候选版本 */
+    PENDING(2, "审核中"),
+    /** 审核不通过被驳回的候选版本 */
+    REJECTED(3, "已驳回");
+
+    private final Integer code;
+
+    private final String desc;
+
+    public static PlanVersionStatusEnum getByCode(Integer code) {
+        if (code == null) {
+            return null;
+        }
+        for (PlanVersionStatusEnum item : values()) {
+            if (Objects.equals(item.code, code)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    public static String getDescByCode(Integer code) {
+        PlanVersionStatusEnum item = getByCode(code);
+        return item == null ? "未知" : item.getDesc();
+    }
+
+    /** 是否为当前生效版本 */
+    public static boolean isEffective(Integer code) {
+        return Objects.equals(code, EFFECTIVE.code);
+    }
+
+    /** 是否为审核中的候选版本 */
+    public static boolean isPending(Integer code) {
+        return Objects.equals(code, PENDING.code);
+    }
+}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác