diff --git a/UnitTestProject/Modules/EodModule/EodPriceDtoTest.cs b/UnitTestProject/Modules/EodModule/EodPriceDtoTest.cs index bbab1029..6f64b3f5 100644 --- a/UnitTestProject/Modules/EodModule/EodPriceDtoTest.cs +++ b/UnitTestProject/Modules/EodModule/EodPriceDtoTest.cs @@ -202,5 +202,53 @@ namespace YLErp.Modules.EodModule } #endregion + + #region 日期窗口解析("页面始终5条"根因锁定,纯单测不连库) + + [TestMethod] + [Description("前端未传日期(年份<=2000) → 回退到 [今天-1年, 今天+1年)")] + public void 未传日期_回退最近一年到明年() + { + var (start, end) = EodPriceService.ResolveValueDateWindow(DateTime.MinValue, DateTime.MinValue); + Assert.AreEqual(DateTime.Today.AddYears(-1).Date, start.Date, "起始应回退到今天-1年"); + Assert.AreEqual(DateTime.Today.AddYears(1).Date, end.Date, "结束应回退到今天+1年"); + Assert.IsTrue(end > start, "窗口应正向"); + } + + [TestMethod] + [Description("列表页默认起止都填今天 → 窗口=[今天, 今天+1天),仅返回当天记录(即'5条'现象成因)")] + public void 起止都填今天_窗口仅今天() + { + var today = DateTime.Today; + var (start, end) = EodPriceService.ResolveValueDateWindow(today, today); + Assert.AreEqual(today.Date, start.Date, "起始应为今天"); + Assert.AreEqual(today.AddDays(1).Date, end.Date, "结束应为今天+1天(半开区间含今天)"); + Assert.IsTrue(today >= start && today < end, "今天的记录应落入窗口"); + Assert.IsFalse(today.AddDays(-1) >= start && today.AddDays(-1) < end, "昨天的记录不应落入仅今天窗口"); + Assert.IsFalse(today.AddDays(1) >= start && today.AddDays(1) < end, "明天的记录不应落入仅今天窗口"); + } + + [TestMethod] + [Description("显式传区间(如近30天) → 原样生效,不被回退覆盖")] + public void 显式区间_原样生效() + { + var start0 = DateTime.Today.AddDays(-30); + var end0 = DateTime.Today; + var (start, end) = EodPriceService.ResolveValueDateWindow(start0, end0); + Assert.AreEqual(start0.Date, start.Date, "起始应等于传入"); + Assert.AreEqual(end0.AddDays(1).Date, end.Date, "结束应等于传入+1天"); + } + + [TestMethod] + [Description("结束日=今天 → 半开区间上界=今天+1天,今天当天记录可命中")] + public void 结束日今天_上界为明天_当天可命中() + { + var (start, end) = EodPriceService.ResolveValueDateWindow(DateTime.Today.AddDays(-365), DateTime.Today); + var today = DateTime.Today; + Assert.IsTrue(today >= start && today < end, "今天记录应命中"); + Assert.IsFalse(today.AddDays(1) >= start && today.AddDays(1) < end, "明天记录不应命中"); + } + + #endregion } } diff --git a/UnitTestProject/Modules/EodModule/EodPriceGoldenReplayTest.cs b/UnitTestProject/Modules/EodModule/EodPriceGoldenReplayTest.cs index cb2b19ee..bf5a0633 100644 --- a/UnitTestProject/Modules/EodModule/EodPriceGoldenReplayTest.cs +++ b/UnitTestProject/Modules/EodModule/EodPriceGoldenReplayTest.cs @@ -187,5 +187,78 @@ namespace YLErp.Modules.EodModule } #endregion + + #region 回归:新增日终价格可见性(连真实库,标 [Ignore] 手动跑) + + /// + /// 回归"新增日终价格后是否查得出",直接跑生产查询 SearchUnderlyingList。 + /// 守护点(与之前"新增后查不出"的修复一一对应): + /// (a) 今天 + 已上市(LaunchState=1) 标的 → 查得出; + /// (b) 估值日期=0001(未填) → 落在列表默认"仅今天"窗口外 → 查不出; + /// (c) 标的未上市(LaunchState!=1) → 被 inner join(underlying_manager.LaunchState=="1") 过滤 → 查不出。 + /// 复用库中已有标的(不新建 underlying_manager,避免触碰该表约束),只插入/清理临时债券估值行。 + /// + [TestMethod] + [Ignore] + [TestCategory("EodVisibility")] + [Description("新增日终价格可见性:(a)今天+已上市可查 (b)日期0001查不出 (c)未上市查不出")] + public void Record_NewRecordVisibility() + { + using (var db = DbContextFactory.GetYLDbContext()) + { + var svc = new EodPriceService(OptUserInfo.SystemUser); + var today = DateTime.Today; + var req = new EodCommodityFuturePriceReq { ValueDateStart = today, ValueDateEnd = today }; + + // 取一个已上市的债券类标的(正向用例);退而求其次取任意已上市标的 + var listedBond = db.underlying_manager + .FirstOrDefault(x => x.LaunchState == "1" && x.UnderlyingInstrumentType == ConsGlobal.InstrumentType.CreditBonds) + ?? db.underlying_manager.FirstOrDefault(x => x.LaunchState == "1"); + Assert.IsNotNull(listedBond, "需存在一个 LaunchState=1 的标的用于正向回归"); + + // 取一个未上市的标的(负向用例) + var unlisted = db.underlying_manager.FirstOrDefault(x => x.LaunchState != "1"); + Assert.IsNotNull(unlisted, "需存在一个 LaunchState!=1 的标的用于负向回归"); + + var insertedIds = new List(); + try + { + // (a) 今天 + 已上市 → 查得出 + var a = new ChinaBondValuation { bond_id = listedBond.UnderlyingCode, valuation_date = today, dirty_price_close = 100, net_price = 100, yield = 3 }; + db.china_bond_valuation.Add(a); + db.SaveChanges(); + insertedIds.Add(a.id); + var rA = svc.SearchUnderlyingList(req); + Assert.IsTrue(rA.rows.Any(x => x.id == a.id), "(a) 今天+已上市债券应查得出"); + + // (b) 日期=0001(未填) → 落在仅今天窗口外,查不出 + var b = new ChinaBondValuation { bond_id = listedBond.UnderlyingCode, valuation_date = DateTime.MinValue, dirty_price_close = 100, net_price = 100, yield = 3 }; + db.china_bond_valuation.Add(b); + db.SaveChanges(); + insertedIds.Add(b.id); + var rB = svc.SearchUnderlyingList(req); + Assert.IsFalse(rB.rows.Any(x => x.id == b.id), "(b) 日期0001 应查不出"); + + // (c) 未上市标的 → 被 inner join 过滤,查不出 + var c = new ChinaBondValuation { bond_id = unlisted.UnderlyingCode, valuation_date = today, dirty_price_close = 100, net_price = 100, yield = 3 }; + db.china_bond_valuation.Add(c); + db.SaveChanges(); + insertedIds.Add(c.id); + var rC = svc.SearchUnderlyingList(req); + Assert.IsFalse(rC.rows.Any(x => x.id == c.id), "(c) 未上市标的应查不出"); + } + finally + { + foreach (var id in insertedIds) + { + var e = db.china_bond_valuation.Find(id); + if (e != null) db.china_bond_valuation.Remove(e); + } + db.SaveChanges(); + } + } + } + + #endregion } } diff --git a/YLErpDAL/Modules/EodModule/EodPriceService.cs b/YLErpDAL/Modules/EodModule/EodPriceService.cs index 104847dc..7e2146dc 100644 --- a/YLErpDAL/Modules/EodModule/EodPriceService.cs +++ b/YLErpDAL/Modules/EodModule/EodPriceService.cs @@ -17,10 +17,24 @@ namespace YLErp.Modules.EodModule } + /// + /// 解析日终价格列表的"估值日期"查询窗口。抽成 static 以便纯单测锁定行为(避免改坏)。 + /// 规则: + /// - 起始日期年份 > 2000(前端传了有效日期)→ 用传入值;否则回退到 今天-1年。 + /// - 结束日期年份 > 2000 → 用传入值+1天(闭区间转半开);否则回退到 今天+1年。 + /// 注意:列表页默认把起止都设成"今天",于是窗口=[今天, 今天+1天)=仅今天 → 仅返回当天的记录 + /// (即"页面始终5条"现象的真正成因,非分页/查询 bug)。要看历史须把起始日期调早。 + /// + public static (DateTime start, DateTime end) ResolveValueDateWindow(DateTime reqStart, DateTime reqEnd) + { + var start = reqStart.Year > 2000 ? reqStart : DateTime.Today.AddYears(-1); + var end = reqEnd.Year > 2000 ? reqEnd.AddDays(1) : DateTime.Today.AddYears(1); + return (start, end); + } + public SearchListResult SearchUnderlyingList(EodCommodityFuturePriceReq req) { - var valueDtStart = req.ValueDateStart.Year > 2000 ? req.ValueDateStart : DateTime.Today.AddYears(-1); - var valueDtEnd = req.ValueDateEnd.Year > 2000 ? req.ValueDateEnd.AddDays(1) : DateTime.Today.AddYears(1); + var (valueDtStart, valueDtEnd) = ResolveValueDateWindow(req.ValueDateStart, req.ValueDateEnd); var predicatUn = PredicateBuilder.Create(d => d.LaunchState == "1"); var predicatEoc = PredicateBuilder.Create(source => source.ValueDate >= valueDtStart && source.ValueDate < valueDtEnd);