Ver Fonte

feat(waterSupply): 管网状态字典与呈现对齐三值域 (#55)

- 新增幂等字典脚本 migration_pipe_network_status_dict.sql:pipe_network_status = 0正常/1维护中/2异常,只重写该字典定义,不改表结构、不改业务数据
- 统计口径回归测试固化:管网 0 计入正常、1 计入预警侧(维护中)、2 计入异常(异常不再落到未知)
- 本地 KingbaseES 单事务执行脚本成功并回滚,库内未留下任何数据
- 后端全量测试:mvn -o test 全绿(zksy-system 133、zksy-admin 172)
Kazerin há 1 mês atrás
pai
commit
1344d9e86e

+ 31 - 0
pipe-network-service/sql/migration_pipe_network_status_dict.sql

@@ -0,0 +1,31 @@
+-- 管网状态字典对齐(KingbaseES)
+-- 目的:管网状态以当前数据库口径为准 —— 0-正常 / 1-维护中 / 2-异常(三值域,不新增第四个取值)。
+-- 说明:旧字典只有“0正常 / 1异常”两项,导致“维护中(1)”在页面上被当成异常(红色)。
+--      本脚本只重写 pipe_network_status 字典定义,不改动表结构、也不改动业务数据;
+--      脚本幂等,可重复执行。
+BEGIN;
+
+-- 1. 字典类型(幂等登记)
+INSERT INTO app_user.sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
+SELECT '管网状态', 'pipe_network_status', '0', 'admin', CURRENT_TIMESTAMP, '管网状态:0正常 1维护中 2异常'
+ WHERE NOT EXISTS (SELECT 1 FROM app_user.sys_dict_type WHERE dict_type = 'pipe_network_status');
+
+UPDATE app_user.sys_dict_type
+   SET dict_name = '管网状态',
+       status = '0',
+       update_by = 'admin',
+       update_time = CURRENT_TIMESTAMP,
+       remark = '管网状态:0正常 1维护中 2异常'
+ WHERE dict_type = 'pipe_network_status';
+
+-- 2. 字典数据(幂等重写为三值域)
+DELETE FROM app_user.sys_dict_data WHERE dict_type = 'pipe_network_status';
+
+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, '正常',   '0', 'pipe_network_status', '', 'success', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '管网状态:正常'),
+    (2, '维护中', '1', 'pipe_network_status', '', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '管网状态:维护中'),
+    (3, '异常',   '2', 'pipe_network_status', '', 'danger',  'N', '0', 'admin', CURRENT_TIMESTAMP, '管网状态:异常');
+
+COMMIT;

+ 90 - 0
pipe-network-service/zksy-system/src/test/java/com/zksy/WaterSupply/PipeNetworkStatusDictionaryContractTest.java

@@ -0,0 +1,90 @@
+package com.zksy.WaterSupply;
+
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Gogs #55:管网状态三值域(0-正常 / 1-维护中 / 2-异常)的字典与统计口径。
+ *
+ * <p>字典以后端仓库内的幂等脚本为准,部署时执行生效;脚本不改表结构、不改业务数据。</p>
+ */
+class PipeNetworkStatusDictionaryContractTest {
+
+    private static final String DICT_MIGRATION_NAME = "migration_pipe_network_status_dict.sql";
+
+    @Test
+    void dictionaryScriptDefinesTheThreeAuthoritativeValues() throws Exception {
+        String sql = dictMigration();
+
+        assertTrue(sql.contains("'正常'") && sql.contains("'维护中'") && sql.contains("'异常'"),
+                "字典必须给出 正常 / 维护中 / 异常 三项文案");
+        assertTrue(sql.contains("'0', 'pipe_network_status'")
+                        && sql.contains("'1', 'pipe_network_status'")
+                        && sql.contains("'2', 'pipe_network_status'"),
+                "字典必须覆盖 0/1/2 三个取值");
+        assertTrue(sql.contains("pipe_network_status"),
+                "字典类型必须保持 pipe_network_status(不改菜单名与字典类型)");
+    }
+
+    @Test
+    void dictionaryScriptIsRepeatableAndDoesNotTouchSchemaOrBusinessData() throws Exception {
+        String sql = dictMigration();
+
+        assertTrue(sql.contains("DELETE FROM app_user.sys_dict_data WHERE dict_type = 'pipe_network_status'"),
+                "重写字典取值需先按 dict_type 清空该项字典数据,保证可重复执行");
+        assertTrue(sql.contains("WHERE NOT EXISTS (SELECT 1 FROM app_user.sys_dict_type WHERE dict_type = 'pipe_network_status')"),
+                "字典类型登记必须幂等");
+        assertFalse(sql.contains("dict_code"),
+                "字典数据不得写死 dict_code,否则重复执行会撞主键");
+        String upper = sql.toUpperCase();
+        assertFalse(upper.contains("ALTER TABLE"), "本票不改动数据库表结构");
+        assertFalse(upper.contains("CREATE TABLE"), "本票不改动数据库表结构");
+        assertFalse(upper.contains("DROP "), "本票不做破坏性结构变更");
+        assertFalse(upper.contains("UPDATE APP_USER.PIPE_NETWORK_BASE"),
+                "本票不直接改动业务数据");
+    }
+
+    /** 模块目录与仓库根目录两种工作目录下都要能找到 SQL。 */
+    private static String dictMigration() throws Exception {
+        Path cwd = Path.of(System.getProperty("user.dir")).toAbsolutePath();
+        for (Path candidate : new Path[]{
+                cwd.resolve("sql").resolve(DICT_MIGRATION_NAME),
+                cwd.resolve("..").resolve("sql").resolve(DICT_MIGRATION_NAME)}) {
+            Path normalized = candidate.normalize();
+            if (Files.exists(normalized)) {
+                return Files.readString(normalized, StandardCharsets.UTF_8);
+            }
+        }
+        throw new IllegalStateException("未找到字典迁移脚本:" + DICT_MIGRATION_NAME);
+    }
+
+    @Test
+    void statisticsTreatPipeStatusAsNormalMaintenanceAndAbnormal() throws Exception {
+        String mapper = Files.readString(Path.of(
+                "src/main/resources/mapper/WaterSupply/PipeNetworkStatistics/PipeNetworkStatisticsMapper.xml"),
+                StandardCharsets.UTF_8);
+
+        int start = mapper.indexOf("SELECT 'pipe'");
+        assertTrue(start > 0, "统计查询必须包含管网设施分组");
+        int end = mapper.indexOf("FROM app_user.pipe_network_base", start);
+        assertTrue(end > start, "管网统计必须读统一管网表");
+        String pipeBlock = mapper.substring(start, end);
+
+        int normal = pipeBlock.indexOf("status = '0'");
+        int abnormal = pipeBlock.indexOf("status = '2'");
+        int maintenance = pipeBlock.indexOf("status = '1'");
+        assertTrue(normal > 0, "0-正常 必须计入正常");
+        assertTrue(abnormal > normal, "2-异常 必须计入异常,且排在正常之后(DTO 列序:正常/异常/预警/未知)");
+        assertTrue(maintenance > abnormal, "1-维护中 必须计入预警侧,不得与异常混淆");
+        assertFalse(pipeBlock.contains("status NOT IN ('0', '1')"),
+                "管网已是三值域,2 不得再被记为未知");
+        assertTrue(pipeBlock.contains("status IS NULL OR status NOT IN ('0', '1', '2')"),
+                "未知只吸收三值域之外的空值与脏值");
+    }
+}