test(swap): SwapFlowEventService seam改造+MergePageEventScenarioTest(6场景全绿)

借鉴 testable 分支,只取 seam(排除 PosiPnl/NetSettmentAmount 公式改动和 MergePageEvent 拆分),
填补当前分支对开平仓合成(MergePageEvent)零覆盖的空白。

SwapFlowEventService 新增11个 protected virtual seam:
- FindTrade/FindTradeExtend/FindPositions/FindAndInvalidateFutureEvents
- GetUnderlying/GetNextBusinessDay/ResolvePositionId
- PersistEvents(替代InitEvent内联Add+SaveChanges)/BeginTransaction/CommitTransaction/RollbackTransaction
MergePageEvent/InitEvent: private→protected virtual,内联DB调用替换为seam。
InitEvent 移除 DbContext.swap_flow_event.Add(转入PersistEvents,纯函数化)。
SwapTradeBaseService.UpdateDbOption: public void→public virtual(测试override用)。

新增测试(借鉴testable分支):
- TestableSwapFlowEventService.cs(集中式共享包装类)
- MergePageEventScenarioTest.cs(6场景全绿):
  Scenario1 单条无持仓开仓 / Scenario2 同向追加 / Scenario3 反向全平
  Scenario4 反向部分平+开 / Scenario5 一开一平 / Scenario6 复杂组合

**未借鉴**:SearchPositionFlowEvent的PosiPnl/NetSettmentAmount公式改动(行为变更)、
MergePageEvent拆分ProcessSingleFlow等(非必需重构)。
SwapModule 169测试全绿(+6),无回归。
This commit is contained in:
hjhan
2026-07-03 09:26:13 +08:00
parent 6e9bae94c5
commit f0ef37b599
4 changed files with 441 additions and 18 deletions
@@ -31,6 +31,53 @@ namespace YLErp.Modules.SwapModule
}
#region Seams refactor-swap-event-testable override DB/
protected virtual trade FindTrade(int swapTradeId)
=> DbContext.trade.Find(swapTradeId);
protected virtual trade_extend FindTradeExtend(int swapTradeId)
=> DbContext.trade_extend.First(x => x.TradeId == swapTradeId);
protected virtual List<swap_position> FindPositions(int swapTradeId)
=> DbContext.swap_position.Where(x => x.SwapTradeId == swapTradeId && !x.IsInitial && !x.Invalid).AsNoTracking().ToList();
protected virtual List<swap_flow_event> FindAndInvalidateFutureEvents(int swapTradeId, DateTime tradeDate)
{
var olds = DbContext.swap_flow_event.Where(x => x.EventDate > tradeDate && x.SwapTradeId == swapTradeId && x.DataState > 0);
olds.ForEach(x => x.DataState = (int)SwapFlowDateStateEnum.);
return olds.ToList();
}
protected virtual underlying_manager GetUnderlying(string underlyingCode)
=> DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode);
protected virtual DateTime GetNextBusinessDay(DateTime date)
=> QdpCalendarHelper.GetNonHoliday(date);
protected virtual long ResolvePositionId(swap_flow_merge merge, DateTime maturityDate, int direction, string tradeNumber)
=> GetMaxPositionId(merge, maturityDate, direction, tradeNumber);
protected virtual void PersistEvents(List<swap_flow_event> events)
{
foreach (var evt in events)
{
DbContext.swap_flow_event.Add(evt);
}
DbContext.SaveChanges();
}
protected virtual IDisposable BeginTransaction()
=> DbContext.Database.BeginTransaction();
protected virtual void CommitTransaction(IDisposable transaction)
=> (transaction as Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction)?.Commit();
protected virtual void RollbackTransaction(IDisposable transaction)
=> (transaction as Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction)?.Rollback();
#endregion
/// <summary>
/// 互换流水开平仓事件
/// </summary>
@@ -70,7 +117,7 @@ namespace YLErp.Modules.SwapModule
/// 分页处理互换流水开平仓事件,暂时只按加权平均处理
/// </summary>
/// <param name="pageSize"></param>
private List<swap_flow_event> MergePageEvent(int swapTradeId, List<swap_flow_merge> flowMergeList, DateTime tradeDate, bool needTrans = true)
protected virtual List<swap_flow_event> MergePageEvent(int swapTradeId, List<swap_flow_merge> flowMergeList, DateTime tradeDate, bool needTrans = true)
{
List<swap_flow_event> flowEvents = new List<swap_flow_event>();
//按照同一互换编码、标的、买卖方向排序,一条买,一条卖//会存在买卖不在同一页
@@ -79,26 +126,22 @@ namespace YLErp.Modules.SwapModule
{
return flowEvents;
}
var trade = DbContext.trade.Find(swapTradeId);
var tradeExtend = DbContext.trade_extend.First(x => x.TradeId == swapTradeId);
var trans = needTrans ? DbContext.Database.BeginTransaction() : null;
var trade = FindTrade(swapTradeId);
var tradeExtend = FindTradeExtend(swapTradeId);
var trans = needTrans ? BeginTransaction() : null;
try
{
var eodPositions = DbContext.swap_position.Where(x => x.SwapTradeId == swapTradeId && !x.IsInitial && !x.Invalid).AsNoTracking().ToList();//上一日终持仓信息
var swapFlowEventOlds = DbContext.swap_flow_event.Where(x => x.EventDate > tradeDate && x.SwapTradeId == swapTradeId && x.DataState > 0);//废弃当前清算日期及之后的开平仓事件
swapFlowEventOlds.ForEach(x =>
{
x.DataState = (int)SwapFlowDateStateEnum.;
});
var eodPositions = FindPositions(swapTradeId);//上一日终持仓信息
FindAndInvalidateFutureEvents(swapTradeId, tradeDate);//废弃当前清算日期及之后的开平仓事件
var mergeUnderlyingGroup = flowquery.GroupBy(g => g.UnderlyingCode);
int direction = tradeExtend.ExtendObj.Direction;
foreach (var underlyingGroup in mergeUnderlyingGroup)
{
var mergeList = underlyingGroup.OrderByDescending(o => o.TradingQty).ToList();//先按数量最大的排序
var flowMerge = mergeList.First();
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(flowMerge.UnderlyingCode);
var underlying = GetUnderlying(flowMerge.UnderlyingCode);
var matuirityDate = trade.ExerciseDate;
var positionId = GetMaxPositionId(flowMerge, matuirityDate.Value, direction, trade.TradeNumber);
var positionId = ResolvePositionId(flowMerge, matuirityDate.Value, direction, trade.TradeNumber);
var payPosition = eodPositions.FirstOrDefault(x => x.PositionId == positionId);//浮动腿 日终持仓信息
bool hasPayPosition = payPosition != null;//是否存在日终持仓
if (mergeList.Count == 1)//只有一条流水
@@ -208,12 +251,12 @@ namespace YLErp.Modules.SwapModule
}
}
DbContext.SaveChanges();
trans?.Commit();
PersistEvents(flowEvents);
if (trans != null) CommitTransaction(trans);
}
catch (Exception ex)
{
trans?.Rollback();
if (trans != null) RollbackTransaction(trans);
throw new Exception(ex.Message, ex);
}
finally
@@ -238,7 +281,7 @@ namespace YLErp.Modules.SwapModule
/// <param name="PayFeeUnwindPnl">平仓浮动费用</param>
/// <param name="allUnwind">是否完全平仓</param>
/// <param name="settleRules">0 T+0 1 T+1</param>
private swap_flow_event InitEvent(
protected virtual swap_flow_event InitEvent(
int eventType,
swap_flow_merge flow_merge,
int direction,
@@ -287,7 +330,6 @@ namespace YLErp.Modules.SwapModule
flow_Event.MarkClosePnl = PayMarkUnwindPnl;
flow_Event.CloseFee = PayFeeUnwindPnl;
flow_Event.DataState = (int)SwapFlowDateStateEnum.;
DbContext.swap_flow_event.Add(flow_Event);
return flow_Event;
}