From aa87d6999987e58c8411d269fc28219cb9ad5eea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Tue, 25 Aug 2026 17:58:51 +0800 Subject: [PATCH] =?UTF-8?q?fix(swap):=20=E4=BF=AE=E5=A4=8D=E5=85=AC?= =?UTF-8?q?=E5=8F=B8=E8=A1=8C=E4=B8=BA=E4=BA=8B=E4=BB=B6=E7=94=9F=E5=91=BD?= =?UTF-8?q?=E5=91=A8=E6=9C=9F=E7=AE=A1=E7=90=86=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了股权登记日日期格式化显示功能 - 实现了公司行为类型的动态描述逻辑(配股、送股、拆分、分红) - 新增了过滤待生效公司行为事件的操作历史机制 - 修正了操作历史中隐藏未应用公司行为事件的逻辑 - 完善了公司行为事件原因描述的构建方法 - 添加了针对不同公司行为类型的单元测试验证 --- .../CorporateActionEventLifecycleTest.cs | 44 +++++++++++--- .../Modules/SwapModule/SwapEventService.cs | 60 +++++++++++++++++-- 2 files changed, 92 insertions(+), 12 deletions(-) diff --git a/UnitTestProject/Modules/SwapModule/CorporateActionEventLifecycleTest.cs b/UnitTestProject/Modules/SwapModule/CorporateActionEventLifecycleTest.cs index 5df141d4..67570ef6 100644 --- a/UnitTestProject/Modules/SwapModule/CorporateActionEventLifecycleTest.cs +++ b/UnitTestProject/Modules/SwapModule/CorporateActionEventLifecycleTest.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Newtonsoft.Json; using Microsoft.VisualStudio.TestTools.UnitTesting; using YLErp.DBModels; @@ -20,6 +21,7 @@ namespace YLErp.Modules.SwapModule public void RegistrationSnapshot_IsPending_AndKeepsBeforeFields() { var info = CreateAction(77, ConsGlobal.InstrumentType.Stock); + info.GiveShareAmount = 1m; var before = CreateEodPosition(9, info.UnderlyingCode, 1000m, 100m); var snapshot = SwapEodPositionService.BuildCorporateActionEventData( @@ -37,6 +39,7 @@ namespace YLErp.Modules.SwapModule Assert.IsFalse(snapshot.Applied); var reason = SwapEventService.BuildCorporateActionEventReason(snapshot); + StringAssert.Contains(reason, "股权登记日:2026-08-14 发生公司行为(送股)"); StringAssert.Contains(reason, "调整前:名义本金:100000 期初标的价格:100 持仓数量:1000"); StringAssert.Contains(reason, "调整后:名义本金:0 期初标的价格:0 持仓数量:0"); } @@ -65,6 +68,23 @@ namespace YLErp.Modules.SwapModule Assert.IsFalse(SwapEodPositionService.IsCorporateActionInstrument(ConsGlobal.InstrumentType.TBonds)); } + [TestMethod] + public void CorporateActionReason_ShowsOneActionTypeOrActualCashDividend() + { + AssertActionDescription( + new CorporateActionEventData { ExDividendDate = RecordDate, RationedSharesAmount = 1m, GiveShareAmount = 1m, Split = 2m, GiveCashAmount = 10m, CashFlowChange = 1000m }, + "发生公司行为(配股)"); + AssertActionDescription( + new CorporateActionEventData { ExDividendDate = RecordDate, GiveShareAmount = 1m, Split = 2m, GiveCashAmount = 10m, CashFlowChange = 1000m }, + "发生公司行为(送股)"); + AssertActionDescription( + new CorporateActionEventData { ExDividendDate = RecordDate, Split = 2m, GiveCashAmount = 10m, CashFlowChange = 1000m }, + "发生公司行为(拆分)"); + AssertActionDescription( + new CorporateActionEventData { ExDividendDate = RecordDate, GiveCashAmount = 11m, CashFlowChange = 220000m }, + "发生公司行为(产生分红:220000)"); + } + [TestMethod] public void Rerun_DoesNotCreateDuplicateCorporateActionEvent() { @@ -113,7 +133,7 @@ namespace YLErp.Modules.SwapModule } [TestMethod] - public void OperationHistory_PreservesPendingCorporateActionForAudit() + public void OperationHistory_HidesPendingCorporateActionUntilItIsApplied() { var info = CreateAction(80, ConsGlobal.InstrumentType.Stock); var pendingData = SwapEodPositionService.BuildCorporateActionEventData( @@ -133,12 +153,17 @@ namespace YLErp.Modules.SwapModule new swap_event { id = 3, EventType = (int)SwapEventTypeEnum.互换, EventData = "{}" } }; - // 操作历史不再隐藏登记日待生效事件;Applied=false 是事件状态,不是展示过滤条件。 - Assert.AreEqual(3, events.Count); - Assert.IsTrue(SwapEventService.TryDeserializeCorporateActionEventData(events[0], out var pendingSnapshot)); - Assert.IsFalse(pendingSnapshot.Applied); - Assert.IsTrue(SwapEventService.TryDeserializeCorporateActionEventData(events[1], out var appliedSnapshot)); - Assert.IsTrue(appliedSnapshot.Applied); + var filter = typeof(SwapEventService).GetMethod( + "FilterOperationHistory", + BindingFlags.NonPublic | BindingFlags.Static); + Assert.IsNotNull(filter, "操作历史必须过滤登记日创建的待生效公司行为事件。"); + + var visibleEvents = (List)filter.Invoke(null, new object[] { events }); + + Assert.AreEqual(2, visibleEvents.Count); + Assert.IsFalse(visibleEvents.Any(x => x.id == 1)); + Assert.IsTrue(visibleEvents.Any(x => x.id == 2)); + Assert.IsTrue(visibleEvents.Any(x => x.id == 3)); } [TestMethod] @@ -283,6 +308,11 @@ namespace YLErp.Modules.SwapModule }; } + private static void AssertActionDescription(CorporateActionEventData data, string expected) + { + StringAssert.Contains(SwapEventService.BuildCorporateActionEventReason(data), expected); + } + private static eod_swap_position CreateEodPosition(long positionId, string code, decimal quantity, decimal price) { return new eod_swap_position diff --git a/YLErpDAL/Modules/SwapModule/SwapEventService.cs b/YLErpDAL/Modules/SwapModule/SwapEventService.cs index 935bda97..6ecc5433 100644 --- a/YLErpDAL/Modules/SwapModule/SwapEventService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapEventService.cs @@ -207,8 +207,8 @@ namespace YLErp.Modules.SwapModule return events; } /// - /// 获取交易操作历史。登记日创建但尚未到 EffectiveDate 的公司行为事件也保留, - /// 由 EventData.Applied=false 表示“待生效”,保证审计日志完整可追溯。 + /// 获取交易操作历史。登记日创建的待生效公司行为仍保留在审计数据中, + /// 但在 EffectiveDate 将其更新为 Applied=true 前不对操作历史展示。 /// /// 交易id /// @@ -218,7 +218,30 @@ namespace YLErp.Modules.SwapModule .Where(x => x.SwapTradeId == tradeId) .OrderByDescending(o => o.id) .ToList(); - return list; + return FilterOperationHistory(list); + } + + /// + /// 过滤尚未生效的公司行为事件。非公司行为、已生效事件和无法识别的历史事件均保留, + /// 避免过滤条件误伤既有操作记录。 + /// + private static List FilterOperationHistory(IEnumerable events) + { + if (events == null) + { + return new List(); + } + + return events + .Where(x => !IsPendingCorporateActionEvent(x)) + .ToList(); + } + + private static bool IsPendingCorporateActionEvent(swap_event swapEvent) + { + return swapEvent?.EventType == (int)SwapEventTypeEnum.公司行为 + && TryDeserializeCorporateActionEventData(swapEvent, out var data) + && !data.Applied; } /// @@ -257,9 +280,36 @@ namespace YLErp.Modules.SwapModule return "公司行为快照为空"; } - // 使用 InvariantCulture 固定小数格式,说明文本不随服务器区域设置变化。 + // 使用 InvariantCulture 固定小数和日期格式,说明文本不随服务器区域设置变化。 string D(decimal value) => value.ToString(CultureInfo.InvariantCulture); - return $"调整前:名义本金:{D(data.BeforeNotional)} 期初标的价格:{D(data.BeforePrice)} 持仓数量:{D(data.BeforeQuantity)}" + string Date(DateTime? value) => value.HasValue + ? value.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) + : ""; + string ActionDescription() + { + if (data.RationedSharesAmount != 0m) + { + return "配股"; + } + if (data.GiveShareAmount != 0m) + { + return "送股"; + } + if (data.Split.HasValue && data.Split.Value != 1m) + { + return "拆分"; + } + if (data.GiveCashAmount != 0m) + { + // return $"产生分红:{D(data.CashFlowChange)}"; + return $"产生分红"; + } + return "公司行为"; + } + + return $"股权登记日:{Date(data.ExDividendDate)} 发生公司行为({ActionDescription()})" + + Environment.NewLine + + $"调整前:名义本金:{D(data.BeforeNotional)} 期初标的价格:{D(data.BeforePrice)} 持仓数量:{D(data.BeforeQuantity)}" + Environment.NewLine + $"调整后:名义本金:{D(data.AfterNotional)} 期初标的价格:{D(data.AfterPrice)} 持仓数量:{D(data.AfterQuantity)}"; }