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)}"; }