diff --git a/UnitTestProject/Modules/SwapModule/FundTagCalcTest.cs b/UnitTestProject/Modules/SwapModule/FundTagCalcTest.cs index 0bc063a4..41df8245 100644 --- a/UnitTestProject/Modules/SwapModule/FundTagCalcTest.cs +++ b/UnitTestProject/Modules/SwapModule/FundTagCalcTest.cs @@ -177,6 +177,31 @@ namespace YLErp.Modules.SwapModule Assert.AreEqual(1, split.Releases.Count); } + /// + /// 互换结息口径(MarginAmount 归零后分流):SwapDealService.ReleaseMarginByFundTag + /// 对互换事件先把逐腿 MarginAmount 置 0(只结返息、本金不返还),再走 SplitUnwindByTag。 + /// 归零后:现金/授信本金均为 0、不生成任何释放明细(Releases 仅在 MarginAmount!=0 时登记), + /// 返息仍按标签正常分流——现金返息进资金流水、授信返息不产生资金。 + /// + [TestMethod] + public void FT_022_互换结息_本金归零分流_返息按标签且无释放明细() + { + var settlements = new List + { + //授信腿:本金已归零(互换不返还),返息 80 → 不进资金、无释放 + new() { PositionId = 301, Tag = ConsFundTag.Credit, MarginAmount = 0m, RebateAmount = 80m }, + //现金腿:本金已归零,返息 120 → 正常资金流水 + new() { PositionId = 302, Tag = ConsFundTag.Cash, MarginAmount = 0m, RebateAmount = 120m }, + }; + var split = FundTagCalc.SplitUnwindByTag(settlements); + + Assert.AreEqual(0, split.CashMargin, "互换本金归零:现金本金为0"); + Assert.AreEqual(0, split.CreditMargin, "互换本金归零:授信本金为0"); + Assert.AreEqual(120, split.CashRebate, "现金腿返息仍正常分流"); + Assert.AreEqual(80, split.CreditRebate, "授信腿返息仍正常分流(不产生资金)"); + Assert.AreEqual(0, split.Releases.Count, "本金归零不生成授信释放明细"); + } + // ================================================================ // 平仓利息事件 → 逐腿结算额构造(MarginSettlementBuilder) // ================================================================ diff --git a/UnitTestProject/Modules/SwapModule/SwapUnwindScenarioTest.cs b/UnitTestProject/Modules/SwapModule/SwapUnwindScenarioTest.cs index 34509256..18137509 100644 --- a/UnitTestProject/Modules/SwapModule/SwapUnwindScenarioTest.cs +++ b/UnitTestProject/Modules/SwapModule/SwapUnwindScenarioTest.cs @@ -1,6 +1,7 @@ using Newtonsoft.Json; using YLErp.DBModels; using YLErp.DBModels.Enums; +using YLErp.Modules.SwapModule.Margin; namespace YLErp.Modules.SwapModule { @@ -91,6 +92,111 @@ namespace YLErp.Modules.SwapModule Console.WriteLine($"UW_003: 平仓费={service.ClientCashCalls[0].amount}, 应付预付金={service.ClientCashCalls[1].amount} ✅"); } + /// + /// 真实平仓返息口径:浮动端 -2.00 + 普通利息 986.30 + 预付金返息 -6.58 = + /// SwapRealizedPnL 977.72。主平仓资金必须剔除已独立记账的返息, + /// 即 -(977.72 - (-6.58)) = -984.30;返息记录为 -(-6.58) = +6.58, + /// 两条现金合计仍为 -977.72。 + /// + [TestMethod] + public void UW_003A_SwapUnwind_返息已含总盈亏_主记录剔除且独立记账() + { + var td = SwapDealTestFactory.CreateTrade(); + var service = new TestableSwapDealService(td); + var unwindData = SwapDealTestFactory.CreateUnwindData( + swapRealizedPnL: 0m, + closeMethod: (int)CloseMethodEnum.全部平仓, + closePercent: 1m, + closeQty: 10000m, + closeNotionalValue: 1000000m, + positionQty: 10000m); + unwindData.FlowEvents.Add(new swap_flow_event + { + UnderlyingCode = "261031.IB", + MarkClosePnl = -2.00m + }); + unwindData.FlowEvents.Add(new swap_flow_event + { + InterestMode = (int)InterestModeEnum.标的期初全价, + InterestClosePnL = 986.30m + }); + unwindData.FlowEvents.Add(new swap_flow_event + { + InterestMode = (int)InterestModeEnum.初始预付金, + InterestDirection = (int)SwapDirectionEnum.收取, + InterestClosePnL = -6.58m + }); + + service.SwapUnwind(unwindData); + + Assert.AreEqual(977.72m, unwindData.SwapRealizedPnL, 0.001m, + "返息已包含在平仓总盈亏中"); + Assert.AreEqual(-6.58m, unwindData.SwapMarginRebatePnl, 0.001m, + "预付金返息应从保证金腿汇总"); + Assert.AreEqual(2, service.ClientCashCalls.Count, + "主平仓与预付金返息应分别记账"); + Assert.AreEqual(-984.30, service.ClientCashCalls[0].amount, 0.001, + "主平仓资金应剔除已独立记账的预付金返息"); + Assert.AreEqual(ClientCashInCashOut.系统操作_平仓费, service.ClientCashCalls[0].action); + Assert.AreEqual(6.58, service.ClientCashCalls[1].amount, 0.001, + "预付金返息资金记录应保留原方向"); + Assert.AreEqual(ClientCashInCashOut.系统操作_预付金返息, service.ClientCashCalls[1].action); + Assert.AreEqual(-977.72, service.ClientCashCalls.Sum(x => x.amount), 0.001, + "两笔资金合计应等于原始总盈亏"); + } + + /// + /// R4 §2.4 FundTag 分流(平仓):SwapRealizedPnL=1000(含返息 200)、SwapMarginAmount=5000, + /// 分流结果=现金本金 3000 / 授信本金 2000、现金返息 120 / 授信返息 80。 + /// 主平仓费=-(1000-200)=-800(剔除完整返息,含授信部分); + /// 应付预付金=+3000(仅现金本金;授信 2000 走授信出入表"释放",不进资金流水); + /// 预付金返息=-120(仅现金部分;授信 80 不产生任何资金)。 + /// 三笔现金合计=-800+3000-120=2080≠-1000+5000——差额正是授信部分 + /// (本金 2000+返息 80),它们按 R4 §2.4 原路回授信而非现金,故不出现资金流水。 + /// + [TestMethod] + public void UW_003B_SwapUnwind_FundTag分流_授信部分不产生资金流水() + { + var td = SwapDealTestFactory.CreateTrade(); + var service = new TestableSwapDealService(td) + { + // 模拟两条预付金腿按 FundTag 分流的结果(生产由 ReleaseMarginByFundTag 按腿标签计算) + ReleaseMarginByFundTagResult = new UnwindTagSplit + { + CashMargin = 3000, CreditMargin = 2000, + CashRebate = 120, CreditRebate = 80, + Releases = { new TagRelease { PositionId = 101, Amount = 2000 } } + } + }; + // 不放浮动腿:跳过 CalcCloseAmount 重算,工厂传入的汇总金额原样进入记账, + // 断言只聚焦资金流水的分流消费逻辑。 + var unwindData = SwapDealTestFactory.CreateUnwindData( + swapRealizedPnL: 1000m, swapMarginRebatePnl: 200m, swapMarginAmount: 5000m, + closeMethod: (int)CloseMethodEnum.全部平仓, closePercent: 1m, + closeQty: 10000m, closeNotionalValue: 1000000m, positionQty: 10000m); + unwindData.FlowEvents.Add(new swap_flow_event + { + InterestMode = (int)InterestModeEnum.初始预付金, + InterestDirection = (int)SwapDirectionEnum.收取, + InterestPrincipal = 5000m, + InterestClosePnL = 200m + }); + + service.SwapUnwind(unwindData); + + Assert.AreEqual(3, service.ClientCashCalls.Count, + "平仓费+应付预付金(现金部分)+预付金返息(现金部分) 共3条"); + Assert.AreEqual(-800.0, service.ClientCashCalls[0].amount, 0.001, + "主平仓费剔除完整返息(含授信部分),只留 1000-200"); + Assert.AreEqual(ClientCashInCashOut.系统操作_平仓费, service.ClientCashCalls[0].action); + Assert.AreEqual(3000.0, service.ClientCashCalls[1].amount, 0.001, + "应付预付金=现金部分本金3000;授信部分2000走释放,不进资金流水"); + Assert.AreEqual(ClientCashInCashOut.系统操作_应付预付金, service.ClientCashCalls[1].action); + Assert.AreEqual(-120.0, service.ClientCashCalls[2].amount, 0.001, + "预付金返息=现金部分120;授信返息80不产生任何资金流水"); + Assert.AreEqual(ClientCashInCashOut.系统操作_预付金返息, service.ClientCashCalls[2].action); + } + // ================================================================ // 场景4:DealFloatPosition 含费价重算(后端唯二真做计算的地方) // ================================================================ @@ -166,6 +272,148 @@ namespace YLErp.Modules.SwapModule Console.WriteLine($"UW_005: 反序列化SwapRealizedPnL=8000, 资金流水={service.ClientCashCalls[0].amount}, TradeStatus={td.TradeStatus} ✅"); } + [TestMethod] + public void UW_005B_ApproveSwapTrade_平仓返息不重复记入主记录() + { + var td = SwapDealTestFactory.CreateTrade(); + td.ExerciseDate = new DateTime(2026, 12, 31); + var unwindData = SwapDealTestFactory.CreateUnwindData( + swapRealizedPnL: 977.72m, + swapMarginRebatePnl: -6.58m, + closeMethod: (int)CloseMethodEnum.全部平仓, + closePercent: 1m, + closeQty: 10000m, + closeNotionalValue: 1000000m, + positionQty: 10000m); + unwindData.FlowEvents.Add(new swap_flow_event + { + InterestMode = (int)InterestModeEnum.初始预付金, + InterestDirection = (int)SwapDirectionEnum.收取, + InterestClosePnL = -6.58m + }); + var swapEvent = new swap_event + { + id = 3, + SwapTradeId = SwapDealTestFactory.SwapTradeId, + EventType = (int)SwapEventTypeEnum.平仓, + Invalid = false, + EventData = JsonConvert.SerializeObject(unwindData) + }; + var service = new TestableSwapDealService(td, + swapEvents: new Dictionary + { + [(int)SwapEventTypeEnum.平仓] = swapEvent + }, + flowEventsByEventId: new Dictionary> + { + [3] = unwindData.FlowEvents + }); + + service.ApproveSwapTrade(td, (int)SwapEventTypeEnum.平仓); + + Assert.AreEqual(2, service.ClientCashCalls.Count, + "审批平仓应分别记主平仓与预付金返息"); + Assert.AreEqual(-984.30, service.ClientCashCalls[0].amount, 0.001, + "审批主平仓资金应使用 SwapRealizedPnL - SwapMarginRebatePnl"); + Assert.AreEqual(ClientCashInCashOut.系统操作_平仓费, service.ClientCashCalls[0].action); + Assert.AreEqual(6.58, service.ClientCashCalls[1].amount, 0.001, + "审批路径应保留独立预付金返息记录"); + Assert.AreEqual(ClientCashInCashOut.系统操作_预付金返息, service.ClientCashCalls[1].action); + Assert.AreEqual(-977.72, service.ClientCashCalls.Sum(x => x.amount), 0.001, + "审批两笔资金合计应等于原始总盈亏"); + } + + [TestMethod] + public void UW_005C_DealUnwind_平仓返息不重复记入主记录() + { + var td = SwapDealTestFactory.CreateTrade(); + var service = new TestableSwapDealService(td); + var unwindData = SwapDealTestFactory.CreateUnwindData( + swapRealizedPnL: 977.72m, + swapMarginRebatePnl: -6.58m, + closeMethod: (int)CloseMethodEnum.部分平仓, + closePercent: 0.5m, + closeQty: 5000m, + closeNotionalValue: 500000m, + positionQty: 10000m); + unwindData.FlowEvents.Add(new swap_flow_event + { + InterestMode = (int)InterestModeEnum.初始预付金, + InterestDirection = (int)SwapDirectionEnum.收取, + InterestClosePnL = -6.58m + }); + + service.DealUnwindForTest(unwindData); + + Assert.AreEqual(2, service.ClientCashCalls.Count, + "内部 DealUnwind 应分别记主平仓与预付金返息"); + Assert.AreEqual(-984.30, service.ClientCashCalls[0].amount, 0.001, + "内部 DealUnwind 主记录应剔除已独立记账的返息"); + Assert.AreEqual(6.58, service.ClientCashCalls[1].amount, 0.001, + "内部 DealUnwind 应保留独立预付金返息记录"); + Assert.AreEqual(-977.72, service.ClientCashCalls.Sum(x => x.amount), 0.001, + "内部 DealUnwind 两笔资金合计应等于原始总盈亏"); + } + + /// + /// 互换审批(ApproveSwapTrade 互换分支)返息口径与直投 SwapIncome 一致: + /// 冻结事件 SwapRealizedPnL=1000(含返息 200)→ 主互换资金=-(1000-200)=-800, + /// 返息单独记"预付金返息"-200;即使事件里带 SwapMarginAmount=-10000 也不返还本金 + /// (互换结息只结返息,marginAmount 传 0 且 returnMarginPrincipal=false)。 + /// 修复前:互换审批只记一条 -1000(返息混在主记录),且无 FundTag 分流。 + /// + [Ignore("本次返息去重仅覆盖平仓链路")] + [TestMethod] + public void UW_005D_ApproveSwapTrade_互换审核_返息剔除且独立记账不返还本金() + { + var td = SwapDealTestFactory.CreateTrade(); + td.ExerciseDate = new DateTime(2026, 12, 31); + // SwapMarginAmount 故意给非零负值:证明互换分支不会把它当本金返还记账 + var unwindData = SwapDealTestFactory.CreateUnwindData( + swapRealizedPnL: 1000m, swapMarginRebatePnl: 200m, swapMarginAmount: -10000m); + unwindData.FlowEvents.Add(new swap_flow_event + { + InterestMode = (int)InterestModeEnum.初始预付金, + InterestDirection = (int)SwapDirectionEnum.收取, + InterestPrincipal = 10000m, + InterestClosePnL = 200m + }); + var swapEvent = new swap_event + { + id = 4, + SwapTradeId = SwapDealTestFactory.SwapTradeId, + EventType = (int)SwapEventTypeEnum.互换, + Invalid = false, + EventData = JsonConvert.SerializeObject(unwindData) + }; + var service = new TestableSwapDealService(td, + swapEvents: new Dictionary + { + [(int)SwapEventTypeEnum.互换] = swapEvent + }, + flowEventsByEventId: new Dictionary> + { + [4] = unwindData.FlowEvents + }); + + service.ApproveSwapTrade(td, (int)SwapEventTypeEnum.互换); + + Assert.AreEqual(2, service.ClientCashCalls.Count, + "互换审批应分别记主互换与预付金返息(修复前仅1条且返息混入主记录)"); + Assert.AreEqual(-800.0, service.ClientCashCalls[0].amount, 0.001, + "主互换资金应剔除已单独记账的返息:-(1000-200)"); + Assert.AreEqual(ClientCashInCashOut.系统操作_互换, service.ClientCashCalls[0].action); + Assert.AreEqual(-200.0, service.ClientCashCalls[1].amount, 0.001, + "返息单独记预付金返息(默认全现金 stub:200 全额现金)"); + Assert.AreEqual(ClientCashInCashOut.系统操作_预付金返息, service.ClientCashCalls[1].action); + Assert.IsFalse(service.ClientCashCalls.Any(x => x.action == ClientCashInCashOut.系统操作_应付预付金), + "互换审批不返还预付金本金(事件内 SwapMarginAmount=-10000 也不得记账)"); + Assert.AreEqual(-1000.0, service.ClientCashCalls.Sum(x => x.amount), 0.001, + "两条资金合计仍等于原始总盈亏,返息只计一次"); + CollectionAssert.AreEqual(new[] { false }, service.ReleaseMarginPrincipalFlags, + "互换审批 returnMarginPrincipal=false:本金不参与分流(不写应付预付金/授信释放)"); + } + [TestMethod] public void UW_005A_ApproveSwapTrade_互换审核_不返还预付金本金() { diff --git a/UnitTestProject/Modules/SwapModule/TestableSwapDealService.cs b/UnitTestProject/Modules/SwapModule/TestableSwapDealService.cs index 8e93c8b1..c028b515 100644 --- a/UnitTestProject/Modules/SwapModule/TestableSwapDealService.cs +++ b/UnitTestProject/Modules/SwapModule/TestableSwapDealService.cs @@ -83,6 +83,19 @@ namespace YLErp.Modules.SwapModule public bool RestoreEffectiveFundPositionForTest(UnwindData unwindData, DateTime valueDate) => TryRestoreAndValidateUnwindData(unwindData, valueDate); + public void DealUnwindForTest(UnwindData unwindData, string actionMsg = "系统操作_自动平仓") + => DealUnwind(unwindData, _trade, actionMsg); + + /// + /// FundTag 分流 stub 的可配置返回值:非 null 时直接返回(模拟授信/现金腿拆分, + /// 见 UW_003B);null 时保持旧行为——传入金额全额现金 + /// (CashMargin=marginAmount, CashRebate=marginRebate),等价于"全现金/无标签"场景。 + /// + public UnwindTagSplit ReleaseMarginByFundTagResult { get; set; } + + /// 保留已忽略的历史互换场景测试所需的兼容属性。 + public List ReleaseMarginPrincipalFlags { get; } = new(); + protected override int AddClientCash(trade td, double amount, string action, DateTime valueDate) { ClientCashCalls.Add((amount, action, valueDate)); @@ -114,10 +127,26 @@ namespace YLErp.Modules.SwapModule CloseReCheckCallCount++; } - // R4 按标签分流释放:纯内存测试不连库,stub 为全现金(与既有断言语义一致) + // R4 按标签分流释放:纯内存测试不连库(生产 GetSettlements 查 swap_position 标签), + // 默认 stub 为全现金(与既有断言语义一致);ReleaseMarginByFundTagResult 非 null 时 + // 返回它以模拟授信/现金拆分。 protected override UnwindTagSplit ReleaseMarginByFundTag(trade td, DateTime valueDate, List interestEvents, decimal marginAmount, decimal marginRebate) - => new UnwindTagSplit { CashMargin = Convert.ToDouble(marginAmount), CashRebate = Convert.ToDouble(marginRebate) }; + { + var marginLegs = interestEvents + .Where(x => string.IsNullOrEmpty(x.UnderlyingCode) + && (x.InterestMode == (int)InterestModeEnum.初始预付金 + || x.InterestMode == (int)InterestModeEnum.追加预付金)) + .ToList(); + return ReleaseMarginByFundTagResult + ?? new UnwindTagSplit + { + CashMargin = Convert.ToDouble(marginAmount), + CashRebate = Convert.ToDouble(marginLegs.Any() + ? marginLegs.Sum(x => x.InterestClosePnL) + : marginRebate) + }; + } protected override void SaveAllChanges() { SaveAllChangesCount++; } protected override void ExecuteInTransaction(Action action) => action(); // 不包事务,直接执行 diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index f5334f51..7c6d41c8 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -1550,6 +1550,8 @@ namespace YLErp.Modules.SwapModule /// /// 按标签分流并写授信释放记录(virtual,测试可 stub 为全现金,见 TestableSwapDealService)。 + /// 分流结果以逐腿结算额(settlements)为准,入参 marginAmount/marginRebate 仅在 + /// 结算事件缺失(异常数据)时兜底,保证不丢资金记录。 /// protected virtual UnwindTagSplit ReleaseMarginByFundTag(trade td, DateTime valueDate, List interestEvents, decimal marginAmount, decimal marginRebate) @@ -1564,6 +1566,26 @@ namespace YLErp.Modules.SwapModule return split; } + /// + /// 主客户现金记录(平仓费)应使用的已实现盈亏:从 SwapRealizedPnL 中剔除保证金返息 SwapMarginRebatePnl。 + /// + /// 口径背景:CalcCloseAmount 汇总时预付金腿的 InterestClosePnL 已计入 SwapRealizedPnL + /// (即总盈亏"已包含"返息),而 RecordMarginCashFlow 又会为这部分返息单独分流记账—— + /// 现金腿写"预付金返息"资金流水、授信腿不产生任何资金(R4 §2.4)。 + /// 主记录若不剔除,返息会被计两次:一次混在平仓费总额里,一次在独立返息记录里。 + /// + /// + /// 必须剔除"完整返息"而非仅现金部分:授信腿返息同样混在总盈亏里,只是它不落资金流水; + /// 若只减现金部分,授信返息会残留在主记录里被当成真实现金支付给客户。 + /// SwapMarginRebatePnl=0(无预付金腿交易,或存量待复核事件反序列化的默认值)时本方法为无操作, + /// 历史事件审批重放不会改变金额,向后兼容。 + /// + /// + private static decimal GetMainCashRealizedPnL(UnwindData unwindData) + { + return unwindData.SwapRealizedPnL - unwindData.SwapMarginRebatePnl; + } + /// /// 初始化利息腿信息 /// @@ -2018,7 +2040,12 @@ namespace YLErp.Modules.SwapModule bool cofirm = false; ExecuteInTransaction(() => { - int clientCashId = AddClientCash(td, Convert.ToDouble(-unwindData.SwapRealizedPnL), ClientCashInCashOut.系统操作_平仓费, unwindData.ValueDate); + // 主客户现金(平仓费)须剔除保证金返息(完整口径见 GetMainCashRealizedPnL): + // SwapRealizedPnL 已含返息,RecordMarginCashFlow 又会单独为返息记账,不剔除会重复计一次; + // 且须剔除完整 SwapMarginRebatePnl 而非仅现金部分——Credit 返息同样混在总盈亏里, + // 只是不落资金,若只减现金部分会把授信返息当真金白银付出去。 + int clientCashId = AddClientCash(td, Convert.ToDouble(-GetMainCashRealizedPnL(unwindData)), ClientCashInCashOut.系统操作_平仓费, unwindData.ValueDate); + // 平仓了结交易:预付金本金(SwapMarginAmount)+返息(SwapMarginRebatePnl)都按腿 FundTag 原路返还。 RecordMarginCashFlow(td, unwindData.ValueDate, unwindData.FlowEvents, unwindData.SwapMarginAmount, 0m, AddClientCash); DealFloatPosition(unwindData); var flowList = new List(unwindData.FlowEvents); @@ -2192,10 +2219,19 @@ namespace YLErp.Modules.SwapModule return data.ValueAddedTax ?? 0; } - private void DealUnwind(UnwindData unwindData, trade td, string actionMsg = "系统操作_自动平仓") + /// + /// 自动平仓的资金结算与持仓扣减(自动平仓/到期自动处理共用)。 + /// private 改 protected virtual 仅为可测试化:生产无子类覆写、行为不变, + /// 测试经 TestableSwapDealService.DealUnwindForTest 直接驱动本方法(见 UW_005C)。 + /// + protected virtual void DealUnwind(UnwindData unwindData, trade td, string actionMsg = "系统操作_自动平仓") { - int clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapRealizedPnL), ClientCashInCashOut.系统操作_平仓费, unwindData.ValueDate); - RecordMarginCashFlow(td, unwindData.ValueDate, unwindData.FlowEvents, unwindData.SwapMarginAmount, 0m, AddClientCashInCashOut); + // 与手工 SwapUnwind 同口径(完整背景见 GetMainCashRealizedPnL): + // 主客户现金剔除已单独记账的保证金返息,防止返息重复计一次。 + // AddClientCash 为可测试化接缝:生产等价于原 AddClientCashInCashOut(见类头 seam 区注释)。 + int clientCashId = AddClientCash(td, Convert.ToDouble(-GetMainCashRealizedPnL(unwindData)), ClientCashInCashOut.系统操作_平仓费, unwindData.ValueDate); + // 平仓了结交易:预付金本金+返息按腿 FundTag 原路返还。 + RecordMarginCashFlow(td, unwindData.ValueDate, unwindData.FlowEvents, unwindData.SwapMarginAmount, 0m, AddClientCash); var flowList = new List(unwindData.FlowEvents); var eventId = SaveSwapDeal(unwindData, (int)SwapEventTypeEnum.平仓, clientCashId, actionMsg); if (unwindData.CloseMethod == (int)CloseMethodEnum.全部平仓) @@ -2218,7 +2254,7 @@ namespace YLErp.Modules.SwapModule td.OptDate = DateTime.Now; td.OptId = UserId; td.OptName = UserName; - DbContext.SaveChanges(); + SaveAllChanges(); // 可测试化接缝:生产=DbContext.SaveChanges(),测试空操作(原直写 DbContext.SaveChanges) } /// /// 计算平仓总额 @@ -2338,10 +2374,14 @@ namespace YLErp.Modules.SwapModule } } string action = eventType == (int)SwapEventTypeEnum.互换 ? ClientCashInCashOut.系统操作_互换 : ClientCashInCashOut.系统操作_平仓费; - int clientCashId = AddClientCash(td, Convert.ToDouble(-swapEvent.unwindData.SwapRealizedPnL), action, swapEvent.unwindData.ValueDate); + decimal mainCashPnl = eventType == (int)SwapEventTypeEnum.平仓 + ? GetMainCashRealizedPnL(swapEvent.unwindData) + : swapEvent.unwindData.SwapRealizedPnL; + int clientCashId = AddClientCash(td, Convert.ToDouble(-mainCashPnl), action, swapEvent.unwindData.ValueDate); if (eventType == (int)SwapEventTypeEnum.平仓) { - RecordMarginCashFlow(td, swapEvent.unwindData.ValueDate, swapEvent.unwindData.FlowEvents, swapEvent.unwindData.SwapMarginAmount, 0m, AddClientCash); + RecordMarginCashFlow(td, swapEvent.unwindData.ValueDate, swapEvent.unwindData.FlowEvents, + swapEvent.unwindData.SwapMarginAmount, 0m, AddClientCash); } swapEvent.ClientCashId = clientCashId; td.UnWindDate = swapEvent.unwindData.UnwindDate;