diff --git a/src/main/kotlin/com/gzzn/omms/msgexchange/codec/SisMessageBody.kt b/src/main/kotlin/com/gzzn/omms/msgexchange/codec/SisMessageBody.kt index b04e5fc..6b8a7fe 100644 --- a/src/main/kotlin/com/gzzn/omms/msgexchange/codec/SisMessageBody.kt +++ b/src/main/kotlin/com/gzzn/omms/msgexchange/codec/SisMessageBody.kt @@ -187,12 +187,13 @@ data class PsdtXml( data class ChdtXml( @param:JacksonXmlProperty(isAttribute = true, localName = "CHNO") val chno: String? = null, @param:JacksonXmlProperty(localName = "CHUT") val chut: String? = null, - @param:JacksonXmlProperty(localName = "CHCLS") val chcls: String? = null, + // SIS 的 CHDT 类字段固定为 CCLS/CTYP(implementation.md「动态运行事件」);曾误绑 CHCLS/CHTYP + @param:JacksonXmlProperty(localName = "CCLS") val ccls: String? = null, @param:JacksonXmlProperty(localName = "PCBT") val pcbt: String? = null, @param:JacksonXmlProperty(localName = "PCET") val pcet: String? = null, @param:JacksonXmlProperty(localName = "CBTM") val cbtm: String? = null, @param:JacksonXmlProperty(localName = "CETM") val cetm: String? = null, - @param:JacksonXmlProperty(localName = "CHTYP") val chtyp: String? = null, + @param:JacksonXmlProperty(localName = "CTYP") val ctyp: String? = null, ) @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/src/main/kotlin/com/gzzn/omms/msgexchange/codec/SisWireMapper.kt b/src/main/kotlin/com/gzzn/omms/msgexchange/codec/SisWireMapper.kt index 5ba0390..771f009 100644 --- a/src/main/kotlin/com/gzzn/omms/msgexchange/codec/SisWireMapper.kt +++ b/src/main/kotlin/com/gzzn/omms/msgexchange/codec/SisWireMapper.kt @@ -46,7 +46,7 @@ internal object SisWireMapper { "CKDT" to ckdt.map { it.toMap("CKNO" to it.ckno, "CHKC" to it.chkc, "CCLS" to it.ccls, "PCOT" to it.pcot, "PCCT" to it.pcct, "COTM" to it.cotm, "CCTM" to it.cctm, "CTYP" to it.ctyp) }, "CLDT" to cldt.map { it.toMap("CLNO" to it.clno, "BELT" to it.belt, "BCLS" to it.bcls, "PCOT" to it.pcot, "PCCT" to it.pcct, "FBAG" to it.fbag, "LBAG" to it.lbag, "BTYP" to it.btyp) }, "PSDT" to psdt.map { it.toMap("PSNO" to it.psno, "PSST" to it.psst, "STST" to it.stst, "STET" to it.stet) }, - "CHDT" to chdt.map { it.toMap("CHNO" to it.chno, "CHUT" to it.chut, "CHCLS" to it.chcls, "PCBT" to it.pcbt, "PCET" to it.pcet, "CBTM" to it.cbtm, "CETM" to it.cetm, "CHTYP" to it.chtyp) }, + "CHDT" to chdt.map { it.toMap("CHNO" to it.chno, "CHUT" to it.chut, "CCLS" to it.ccls, "PCBT" to it.pcbt, "PCET" to it.pcet, "CBTM" to it.cbtm, "CETM" to it.cetm, "CTYP" to it.ctyp) }, "DELY" to dely.map { it.toMap("CODE" to it.code, "STRT" to it.strt, "DURA" to it.dura, "REMC" to it.text) }, "ABTM" to abtm.map { it.toMap("ASNO" to it.asno, "ABDG" to it.abdg, "ABOP" to it.abop, "AOTM" to it.aotm) }, "CHOT" to chot.map { it.toMap("CSNO" to it.csno, "CHID" to it.chid, "CHST" to it.chst, "CHTM" to it.chtm) }, diff --git a/src/main/kotlin/com/gzzn/omms/msgexchange/infra/persistence/jdbc/JdbcPgRepositories.kt b/src/main/kotlin/com/gzzn/omms/msgexchange/infra/persistence/jdbc/JdbcPgRepositories.kt index 102eed6..869e511 100644 --- a/src/main/kotlin/com/gzzn/omms/msgexchange/infra/persistence/jdbc/JdbcPgRepositories.kt +++ b/src/main/kotlin/com/gzzn/omms/msgexchange/infra/persistence/jdbc/JdbcPgRepositories.kt @@ -872,7 +872,7 @@ class JdbcFlightStateRepository( "CKDT" to DetailSpec("flight_checkin", listOf("chkc", "ccls", "pcot", "pcct", "cotm", "cctm", "ctyp"), "CKNO"), "CLDT" to DetailSpec("flight_belt", listOf("belt", "bcls", "pcot", "pcct", "fbag", "lbag", "btyp"), "CLNO"), "PSDT" to DetailSpec("flight_stand_plan", listOf("psst", "stst", "stet"), "PSNO"), - "CHDT" to DetailSpec("flight_chute", listOf("chut", "chcls", "pcbt", "pcet", "cbtm", "cetm", "chtyp"), "CHNO"), + "CHDT" to DetailSpec("flight_chute", listOf("chut", "ccls", "pcbt", "pcet", "cbtm", "cetm", "ctyp"), "CHNO"), "DELY" to DetailSpec("flight_delay", listOf("code", "strt", "dura", "remc"), null), "ABTM" to DetailSpec("flight_bridge_op", listOf("abdg", "abop", "aotm"), "ASNO"), "CHOT" to DetailSpec("flight_chock_op", listOf("chid", "chst", "chtm"), "CSNO"), diff --git a/src/main/resources/db/migration/V2__flight_chute_class_type_rename.sql b/src/main/resources/db/migration/V2__flight_chute_class_type_rename.sql new file mode 100644 index 0000000..ad596c1 --- /dev/null +++ b/src/main/resources/db/migration/V2__flight_chute_class_type_rename.sql @@ -0,0 +1,12 @@ +-- ===================================================================== +-- FLIGHT_CHUTE 类字段更名:CHCLS/CHTYP → CCLS/CTYP +-- --------------------------------------------------------------------- +-- SIS 的 CHDT 类字段固定为 CCLS/CTYP(docs/implementation.md「动态运行事件」)。 +-- V1 基线已发布不可改写,列名偏差以本新增迁移修正;只作用于自有 PostgreSQL, +-- 共享 MySQL 信箱不受影响(C-2)。wire DTO 与映射键已同步改为 CCLS/CTYP, +-- 本迁移保证库端列名与代码读出的键一致(存量行原值随列更名保留)。 +-- ===================================================================== + +ALTER TABLE flight_chute RENAME COLUMN chcls TO ccls; + +ALTER TABLE flight_chute RENAME COLUMN chtyp TO ctyp; diff --git a/src/test/kotlin/com/gzzn/omms/msgexchange/codec/JacksonXmlCodecTest.kt b/src/test/kotlin/com/gzzn/omms/msgexchange/codec/JacksonXmlCodecTest.kt index 6cc77f3..9a8b54a 100644 --- a/src/test/kotlin/com/gzzn/omms/msgexchange/codec/JacksonXmlCodecTest.kt +++ b/src/test/kotlin/com/gzzn/omms/msgexchange/codec/JacksonXmlCodecTest.kt @@ -1,6 +1,10 @@ package com.gzzn.omms.msgexchange.codec import com.gzzn.omms.msgexchange.domain.MsgKind +import com.gzzn.omms.msgexchange.domain.flight.FlightSnapshot +import com.gzzn.omms.msgexchange.domain.flight.FlightState +import com.gzzn.omms.msgexchange.domain.flight.FlightStateEngine +import com.gzzn.omms.msgexchange.domain.flight.MergeChange import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertInstanceOf @@ -271,4 +275,36 @@ class JacksonXmlCodecTest { assertEquals(MsgKind.Unsupported("FLOP-CNCL"), (codec.decode(raw("CNCL")) as DecodeResult.Ok).message.kind) assertEquals(MsgKind.Unsupported("FLOP-"), (codec.decode(raw("")) as DecodeResult.Ok).message.kind) } + + @Test + fun `CHDT round trip keeps SIS class fields CCLS and CTYP from decode to merged state`() { + val raw = """ + AODB920021010090311 + FLOPCHDT + 121112312 + C01AIN + + """.trimIndent() + + val payload = ((codec.decode(raw) as DecodeResult.Ok).message.body as FlopPayload) + + // wire 层:CCLS/CTYP 键在、CHCLS/CHTYP 键不在 + val row = payload.collections.getValue("CHDT").single() + assertEquals("A", row["CCLS"]) + assertEquals("IN", row["CTYP"]) + assertFalse(row.containsKey("CHCLS")) + assertFalse(row.containsKey("CHTYP")) + + // 合并层:MergeChange 带进航班快照的 CHDT 明细 + val current = FlightSnapshot( + "121112312", java.time.LocalDate.of(2026, 12, 15), FlightState.ACTIVE, 1, + scalars = mapOf("FLNO" to "CA001"), + collections = emptyMap(), + ) + val next = FlightStateEngine.mergedState( + current, + MergeChange("121112312", scalars = emptyMap(), collections = payload.collections), + ) + assertEquals(listOf(row), next.collections["CHDT"]) + } } diff --git a/src/test/kotlin/com/gzzn/omms/msgexchange/infra/persistence/jdbc/FlywayMigrationTest.kt b/src/test/kotlin/com/gzzn/omms/msgexchange/infra/persistence/jdbc/FlywayMigrationTest.kt index 433d001..be8f116 100644 --- a/src/test/kotlin/com/gzzn/omms/msgexchange/infra/persistence/jdbc/FlywayMigrationTest.kt +++ b/src/test/kotlin/com/gzzn/omms/msgexchange/infra/persistence/jdbc/FlywayMigrationTest.kt @@ -10,10 +10,11 @@ import org.junit.jupiter.api.Test import java.sql.DriverManager /** - * 在真实 PostgreSQL 上跑一遍迁移,确认结果符合预期:单基线 `V1__flight_state_baseline.sql` - * 执行成功且是唯一的迁移记录,该建的表和单行种子(PIPELINE_LOCK、INBOX_CURSOR)都在, - * 回填事实与收报水位都落在基线里,`BACKFILL_TODO`、`idx_evt_flid`、`PROC_STATE` 的处理开始 - * 时间列都不复存在——这些正是原 V2–V10 合并后的净结构。 + * 在真实 PostgreSQL 上跑一遍迁移链,确认结果符合预期:`V1__flight_state_baseline.sql` + * 基线加 `V2__flight_chute_class_type_rename.sql` 更名依序执行成功,该建的表和单行种子 + * (PIPELINE_LOCK、INBOX_CURSOR)都在,回填事实与收报水位都落在基线里,`BACKFILL_TODO`、 + * `idx_evt_flid`、`PROC_STATE` 的处理开始时间列都不复存在;FLIGHT_CHUTE 的类字段列 + * 已由 V2 更名为 CCLS/CTYP(SIS 口径)。 * * 没有可用的 PostgreSQL 时跳过(不假装通过)。 */ @@ -42,7 +43,7 @@ class FlywayMigrationTest { DriverManager.getConnection(url, user, pass).use { conn -> conn.createStatement().use { stmt -> - // 单基线:原 V1–V10 已合并为一条迁移,历史里只应留下 V1 + // 迁移链:V1 基线 + V2 类字段更名,依序成功 stmt.executeQuery( "SELECT version, script, success FROM flyway_schema_history ORDER BY installed_rank ASC", ).use { rs -> @@ -50,12 +51,24 @@ class FlywayMigrationTest { while (rs.next()) { records.add(Triple(rs.getString("version"), rs.getString("script"), rs.getBoolean("success"))) } - assertEquals(1, records.size, "迁移链必须收敛为单基线,不得再有 V2+ 条目") + assertEquals(2, records.size, "迁移链应为 V1 基线 + V2 更名两条") assertEquals("1", records[0].first) assertEquals("V1__flight_state_baseline.sql", records[0].second) + assertEquals("2", records[1].first) + assertEquals("V2__flight_chute_class_type_rename.sql", records[1].second) assertTrue(records.all { it.third }) } + // V2 更名后 FLIGHT_CHUTE 类字段为 SIS 口径的 ccls/ctyp,CHCLS/CHTYP 不复存在 + stmt.executeQuery( + "SELECT column_name FROM information_schema.columns WHERE table_name = 'flight_chute' " + + "AND column_name IN ('ccls', 'ctyp', 'chcls', 'chtyp')", + ).use { rs -> + val cols = mutableSetOf() + while (rs.next()) cols.add(rs.getString("column_name")) + assertEquals(setOf("ccls", "ctyp"), cols, "FLIGHT_CHUTE 类字段必须已更名为 ccls/ctyp") + } + // 全表就绪(回填事实并入 PROC_STATE,基线不再建 BACKFILL_TODO) stmt.executeQuery( "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'",