From 42602055de1774251a0f1e506babf9d0492e8e37 Mon Sep 17 00:00:00 2001 From: hjhan Date: Fri, 17 Jul 2026 11:24:11 +0800 Subject: [PATCH] =?UTF-8?q?test(eod):=20=E4=B8=BA=20FR007=20=E5=85=A5?= =?UTF-8?q?=E5=BA=93=20UnderlyingId=20=E5=AE=88=E5=8D=AB=E8=A1=A5=E7=BA=AF?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EodPriceService 抽出纯函数 ResolveUnderlyingIdForCode(行为不变),SyncUnderlyingIdFromCode 调用之,便于无库单测 - 新增 EodPriceUnderlyingIdGuardTest:7 用例全过,覆盖空代码/标的不存在维持原值、已一致不改动、FR007 错行(511160.SH=2173889、159111.SZ=2173890)校正为 2170838、非错配不互相覆盖 - 对应根因:GLMS-20260701 FR007 价格行 UnderlyingId 错挂导致网页查得到/结算查不到 --- .../EodPriceUnderlyingIdGuardTest.cs | 88 +++++++++++++++++++ YLErpDAL/Modules/EodModule/EodPriceService.cs | 44 ++++++++-- 2 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 UnitTestProject/Modules/EodModule/EodPriceUnderlyingIdGuardTest.cs diff --git a/UnitTestProject/Modules/EodModule/EodPriceUnderlyingIdGuardTest.cs b/UnitTestProject/Modules/EodModule/EodPriceUnderlyingIdGuardTest.cs new file mode 100644 index 00000000..4427770b --- /dev/null +++ b/UnitTestProject/Modules/EodModule/EodPriceUnderlyingIdGuardTest.cs @@ -0,0 +1,88 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using YLErp.DBModels; + +namespace YLErp.Modules.EodModule +{ + /// + /// FR007 错行根因的校正决策单测(GLMS-20260701)。 + /// 对应最近提交的 bugfix:eod_commodity_future_price 入库前以 UnderlyingCode(=FutureContractId) 为准 + /// 重派生 UnderlyingId,防止 UnderlyingId 与 FutureContractId 失同步导致"网页能查到、EOD 结算查不到"。 + /// 这里只测纯函数 ResolveUnderlyingIdForCode,不依赖数据库。 + /// + /// 生产事故还原:FR007 价格行的 FutureContractId='FR007',但 UnderlyingId 被错写成 + /// 511160.SH 的 2173889 / 159111.SZ 的 2173890,正确应为 FR007 的 2170838。 + /// 网页端按 UnderlyingId(int) JOIN underlying_manager 把 FR007 行误挂到 511160.SH; + /// 而 EOD 结算按 UnderlyingCode(string) JOIN 查不到,报"结算价格缺失"。 + /// + [TestClass] + public class EodPriceUnderlyingIdGuardTest + { + private const int Fr007CorrectId = 2170838; + private const int Id511160 = 2173889; // 511160.SH 的 id(被错写) + private const int Id159111 = 2173890; // 159111.SZ 的 id(被错写) + + [TestMethod] + public void 空UnderlyingCode_维持原值() + { + Assert.AreEqual(123, EodPriceService.ResolveUnderlyingIdForCode("", 123, null)); + Assert.AreEqual(123, EodPriceService.ResolveUnderlyingIdForCode(null, 123, 999)); + Assert.AreEqual(123, EodPriceService.ResolveUnderlyingIdForCode(" ", 123, 999)); + } + + [TestMethod] + public void 标的不存在_维持原值() + { + // resolvedId=null 表示 underlying_manager 无此代码,无法校正 + Assert.AreEqual(Id511160, + EodPriceService.ResolveUnderlyingIdForCode("FR007", Id511160, null)); + } + + [TestMethod] + public void 已一致_维持原值() + { + Assert.AreEqual(Fr007CorrectId, + EodPriceService.ResolveUnderlyingIdForCode("FR007", Fr007CorrectId, Fr007CorrectId)); + } + + [TestMethod] + public void 不一致_校正为正确id_FR007生产错行_511160() + { + // 生产事故:FR007 行 UnderlyingId=2173889(511160.SH) → 应校正为 2170838 + Assert.AreEqual(Fr007CorrectId, + EodPriceService.ResolveUnderlyingIdForCode("FR007", Id511160, Fr007CorrectId)); + } + + [TestMethod] + public void 不一致_校正为正确id_FR007生产错行_159111() + { + // 生产事故:另两条错行 UnderlyingId=2173890(159111.SZ) → 应校正为 2170838 + Assert.AreEqual(Fr007CorrectId, + EodPriceService.ResolveUnderlyingIdForCode("FR007", Id159111, Fr007CorrectId)); + } + + [TestMethod] + public void 行对象_端到端校正_FR007() + { + var row = new eod_commodity_future_price + { + UnderlyingCode = "FR007", + UnderlyingId = Id511160 + }; + // 模拟 db.underlying_manager 解析到的正确 id + int resolved = Fr007CorrectId; + int? before = row.UnderlyingId; + row.UnderlyingId = EodPriceService.ResolveUnderlyingIdForCode(row.UnderlyingCode, row.UnderlyingId ?? 0, resolved); + + Assert.AreNotEqual(before, row.UnderlyingId); + Assert.AreEqual((int?)Fr007CorrectId, row.UnderlyingId); + } + + [TestMethod] + public void 典型非错配_不同标的_各自正确不互相覆盖() + { + // 511160.SH 自己的行(FutureContractId='511160.SH'),UnderlyingId 已是 2173889 → 不动 + Assert.AreEqual(Id511160, + EodPriceService.ResolveUnderlyingIdForCode("511160.SH", Id511160, Id511160)); + } + } +} diff --git a/YLErpDAL/Modules/EodModule/EodPriceService.cs b/YLErpDAL/Modules/EodModule/EodPriceService.cs index ce2ced60..449a4c75 100644 --- a/YLErpDAL/Modules/EodModule/EodPriceService.cs +++ b/YLErpDAL/Modules/EodModule/EodPriceService.cs @@ -29,6 +29,36 @@ namespace YLErp.Modules.EodModule return (start, end); } + /// + /// 校正 eod_commodity_future_price 行的 UnderlyingId,使其与 UnderlyingCode(=FutureContractId) 一致。 + /// 背景:网页日终价格列表按 UnderlyingId(int) JOIN underlying_manager,而 EOD 结算(EodPriceProvider.Initialize) + /// 按 UnderlyingCode(string) JOIN。两列一旦失同步(典型如 FR007 的价格行 UnderlyingId 被错写成 511160.SH 的 id), + /// 会出现"网页能查到、结算却查不到"的错价缺失,进而 EodCheckSettlePrice 报"结算价格缺失"。 + /// 这里以 UnderlyingCode 为准重新派生 UnderlyingId——该列才是上传/结算使用的自然键(FutureContractId), + /// 在入库前强制两列一致,既阻止产生新的错行,又通过告警日志把失同步暴露给运维追查上游写入来源。 + /// + /// + /// 纯函数:根据 UnderlyingCode 校正决策。给定当前 UnderlyingId 与从 underlying_manager 解析到的正确 id, + /// 返回应使用的 UnderlyingId。UnderlyingCode 为空或库中无对应标的(resolvedId=null)时维持原值, + /// 已一致时也维持原值,仅在不一致时返回正确 id。抽成纯函数便于无数据库单测(覆盖 GLMS-20260701 FR007 错行根因)。 + /// + public static int ResolveUnderlyingIdForCode(string underlyingCode, int currentId, int? resolvedId) + { + if (string.IsNullOrWhiteSpace(underlyingCode)) + { + return currentId; + } + if (resolvedId == null) + { + return currentId; + } + if (resolvedId.Value == currentId) + { + return currentId; + } + return resolvedId.Value; + } + /// /// 校正 eod_commodity_future_price 行的 UnderlyingId,使其与 UnderlyingCode(=FutureContractId) 一致。 /// 背景:网页日终价格列表按 UnderlyingId(int) JOIN underlying_manager,而 EOD 结算(EodPriceProvider.Initialize) @@ -45,18 +75,14 @@ namespace YLErp.Modules.EodModule } var um = db.underlying_manager.FirstOrDefault(u => u.UnderlyingCode == row.UnderlyingCode); - if (um == null) - { - // 标的代码在 underlying_manager 不存在:无法校正,交由既有"标的代码不存在"等校验处理。 - return; - } - - if (row.UnderlyingId != um.id) + var resolvedId = um == null ? (int?)null : um.id; + var before = row.UnderlyingId; + row.UnderlyingId = ResolveUnderlyingIdForCode(row.UnderlyingCode, row.UnderlyingId ?? 0, resolvedId); + if (row.UnderlyingId != before) { LogFactory.GetLogger("EodPrice").Info( $"eod_commodity_future_price.UnderlyingId 与 UnderlyingCode 不一致,已自动校正: " + - $"FutureContractId={row.UnderlyingCode}, 原UnderlyingId={row.UnderlyingId}, 修正为={um.id}"); - row.UnderlyingId = um.id; + $"FutureContractId={row.UnderlyingCode}, 原UnderlyingId={before}, 修正为={row.UnderlyingId}"); } }