fix(codec): CHDT 类字段统一为 SIS 口径 CCLS/CTYP(ACM2-90)

- ChdtXml wire 绑定与 SisWireMapper 映射键改为 CCLS/CTYP(implementation.md「动态运行事件」)
- 新增 V2 迁移将自有 PG FLIGHT_CHUTE 列 chcls/chtyp 更名为 ccls/ctyp,不动已发布的 V1
- FlywayMigrationTest 更新迁移链断言并校验更名后列;补 CHDT 解码→合并往返用例
This commit is contained in:
windyboy
2026-09-21 12:20:50 +08:00
parent 29465e2131
commit 0e09365cf6
6 changed files with 72 additions and 10 deletions
@@ -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/CTYPimplementation.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)
@@ -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) },
@@ -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"),
@@ -0,0 +1,12 @@
-- =====================================================================
-- FLIGHT_CHUTE 类字段更名:CHCLS/CHTYP → CCLS/CTYP
-- ---------------------------------------------------------------------
-- SIS 的 CHDT 类字段固定为 CCLS/CTYPdocs/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;
@@ -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 = """
<MSG><META><SNDR>AODB</SNDR><SEQN>9</SEQN><DTTM>20021010090311</DTTM>
<TYPE>FLOP</TYPE><STYP>CHDT</STYP></META>
<FLOP><FLID>121112312</FLID>
<CHDT CHNO="1"><CHUT>C01</CHUT><CCLS>A</CCLS><CTYP>IN</CTYP></CHDT>
</FLOP></MSG>
""".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"])
}
}
@@ -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/CTYPSIS 口径)。
*
* 没有可用的 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/ctypCHCLS/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<String>()
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'",