test(swap): SwapDealService抽7个virtual seam + 手动结算内存测试
攻克SwapIncome/SwapUnwind零单元测试覆盖的盲区(此前仅DBRecording,CI不跑)。 SwapDealService补7个protected virtual seam(参考已验证的SwapEodPositionService模式): - FindTrade/AddClientCash/SaveSwapDeal(原private改protected virtual) - SaveAllChanges/ExecuteInTransaction/CallSaveSwapTradeClientCash/TriggerRealtimeSwapPosition 生产实现行为不变,SwapIncome/SwapUnwind/ApproveSwapTrade重构为调seam。 新增SwapDealSettlementTest.cs(4个内存测试): - SD_001 SwapIncome资金流水金额=-SwapRealizedPnL - SD_002 含预付金返息双流水 - SD_003 SwapUnwind全平仓持仓归零+状态变更 - SD_004 DealFloatPosition含费价重算(后端唯二真做计算处) 红灯验证通过(改错金额符号测试立即失败)。
This commit is contained in:
@@ -20,6 +20,83 @@ namespace YLErp.Modules.SwapModule
|
||||
return EodPriceQueryService.TryGetPrice(valueDate, underlyingCode, out rate);
|
||||
}
|
||||
|
||||
#region 可测试化接缝(Seams)——override 这些虚方法可在测试中替换 DB/外部调用,生产代码行为不变
|
||||
|
||||
/// <summary>查找交易(生产: DbContext.trade.Find;测试: 返回内存对象)</summary>
|
||||
protected virtual trade FindTrade(int tradeId)
|
||||
{
|
||||
return DbContext.trade.Find(tradeId);
|
||||
}
|
||||
|
||||
/// <summary>添加资金记录(生产: AddClientCashInCashOut;测试: 计数并记录金额)</summary>
|
||||
protected virtual int AddClientCash(trade td, double amount, string action, DateTime valueDate)
|
||||
{
|
||||
return AddClientCashInCashOut(td, amount, action, valueDate);
|
||||
}
|
||||
|
||||
/// <summary>保存互换/平仓事件(生产: 落库+建事件;测试: 收集 unwindData 入内存列表)。
|
||||
/// 原 private 改 protected virtual,使测试 stub 可整体 override,规避内部 new SwapEventService 连库。</summary>
|
||||
protected virtual long SaveSwapDeal(UnwindData unwindData, int eventType, int clientCashId, string eventResason = "", bool approve = false)
|
||||
{
|
||||
return SaveSwapDealInternal(unwindData, eventType, clientCashId, eventResason, approve);
|
||||
}
|
||||
|
||||
/// <summary>保存所有变更(生产: DbContext.SaveChanges;测试: 空操作)</summary>
|
||||
protected virtual void SaveAllChanges()
|
||||
{
|
||||
DbContext.SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>在事务中执行(生产: BeginTransaction/Commit/Rollback;测试: 直接执行不包事务)</summary>
|
||||
protected virtual void ExecuteInTransaction(Action action)
|
||||
{
|
||||
var trans = DbContext.Database.BeginTransaction();
|
||||
try
|
||||
{
|
||||
action();
|
||||
trans.Commit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
trans.Rollback();
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
trans.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>保存互换交易资金记录(生产: new ClientCashInCashOutService;测试: 空操作)。
|
||||
/// 仅 SwapUnwind 全平仓且 NeedOpenFee=false 时调用。</summary>
|
||||
protected virtual void CallSaveSwapTradeClientCash(trade td, DateTime valueDate)
|
||||
{
|
||||
td.trade_extend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == td.id);
|
||||
if (td.trade_extend != null && !td.trade_extend.ExtendObj.NeedOpenFee)
|
||||
{
|
||||
new ClientCashInCashOutService(this).SaveSwapTradeClientCash(td, td.TradePrice ?? 0, valueDate, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>触发互换实时持仓计算(生产: Task.Run 异步 RealtimePnlCalc;测试: 空操作)。
|
||||
/// 仅 SwapUnwind 成功后调用。</summary>
|
||||
protected virtual void TriggerRealtimeSwapPosition()
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
RealtimePnlCalc.RealtimeSwapPosition(new OptUserInfo(0, "互换实时持仓服务", OptUserFrom.Service));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogFactory.GetLogger<SwapDealService>().Error("互换实时持仓服务计算失败", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public SwapDealService(OptUserInfo optUser) : base(optUser)
|
||||
{
|
||||
|
||||
@@ -990,24 +1067,23 @@ namespace YLErp.Modules.SwapModule
|
||||
/// <exception cref="ServiceException"></exception>
|
||||
public void SwapUnwind(UnwindData unwindData)
|
||||
{
|
||||
var td = DbContext.trade.Find(unwindData.SwapTradeId);
|
||||
var td = FindTrade(unwindData.SwapTradeId);
|
||||
if (td == null)
|
||||
{
|
||||
throw new ServiceException("未找到交易信息");
|
||||
}
|
||||
//CheckLastEod(unwindData.ValueDate, td.StartDate.Value, unwindData.SwapTradeId); //去掉平仓收盘限制
|
||||
var trans = DbContext.Database.BeginTransaction();
|
||||
bool cofirm = false;
|
||||
try
|
||||
ExecuteInTransaction(() =>
|
||||
{
|
||||
int clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapRealizedPnL), ClientCashInCashOut.系统操作_平仓费, unwindData.ValueDate);
|
||||
int clientCashId = AddClientCash(td, Convert.ToDouble(-unwindData.SwapRealizedPnL), ClientCashInCashOut.系统操作_平仓费, unwindData.ValueDate);
|
||||
if (unwindData.SwapMarginAmount != 0)
|
||||
{
|
||||
AddClientCashInCashOut(td, Convert.ToDouble(unwindData.SwapMarginAmount), ClientCashInCashOut.系统操作_应付预付金, unwindData.ValueDate);
|
||||
AddClientCash(td, Convert.ToDouble(unwindData.SwapMarginAmount), ClientCashInCashOut.系统操作_应付预付金, unwindData.ValueDate);
|
||||
}
|
||||
//if (unwindData.SwapMarginRebatePnl != 0)
|
||||
//{
|
||||
// AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapMarginRebatePnl), ClientCashInCashOut.系统操作_预付金返息, unwindData.ValueDate);
|
||||
// AddClientCash(td, Convert.ToDouble(-unwindData.SwapMarginRebatePnl), ClientCashInCashOut.系统操作_预付金返息, unwindData.ValueDate);
|
||||
//}
|
||||
DealFloatPosition(unwindData);
|
||||
var flowList = new List<swap_flow_event>(unwindData.FlowEvents);
|
||||
@@ -1015,11 +1091,7 @@ namespace YLErp.Modules.SwapModule
|
||||
if (unwindData.CloseMethod == (int)CloseMethodEnum.全部平仓 || unwindData.ClosePercent == 1)
|
||||
{
|
||||
td.TradeStatus = "已平仓";
|
||||
td.trade_extend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == td.id);
|
||||
if (td.trade_extend != null && !td.trade_extend.ExtendObj.NeedOpenFee)
|
||||
{
|
||||
new ClientCashInCashOutService(this).SaveSwapTradeClientCash(td, td.TradePrice ?? 0, unwindData.ValueDate, 0);
|
||||
}
|
||||
CallSaveSwapTradeClientCash(td, unwindData.ValueDate);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1028,32 +1100,12 @@ namespace YLErp.Modules.SwapModule
|
||||
td.UnWindDate = unwindData.UnwindDate;
|
||||
td.StockEqvNotional -= Convert.ToDouble(unwindData.CloseNotionalValue);
|
||||
td.TradeAmount -= Convert.ToDouble(unwindData.CloseQty);
|
||||
DbContext.SaveChanges();
|
||||
trans.Commit();
|
||||
SaveAllChanges();
|
||||
cofirm = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
trans.Rollback();
|
||||
throw new Exception(ex.Message, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
trans.Dispose();
|
||||
}
|
||||
});
|
||||
if (cofirm)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
RealtimePnlCalc.RealtimeSwapPosition(new OptUserInfo(0, "互换实时持仓服务", OptUserFrom.Service));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogFactory.GetLogger<SwapDealService>().Error("互换实时持仓服务计算失败", ex);
|
||||
}
|
||||
});
|
||||
TriggerRealtimeSwapPosition();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
@@ -1490,20 +1542,18 @@ namespace YLErp.Modules.SwapModule
|
||||
/// <exception cref="ServiceException"></exception>
|
||||
public void SwapIncome(UnwindData unwindData)
|
||||
{
|
||||
var td = DbContext.trade.Find(unwindData.SwapTradeId);
|
||||
var td = FindTrade(unwindData.SwapTradeId);
|
||||
if (td == null)
|
||||
{
|
||||
throw new ServiceException("未找到交易信息");
|
||||
}
|
||||
//CheckLastEod(unwindData.ValueDate, td.StartDate.Value, unwindData.SwapTradeId); //去掉平仓收盘限制
|
||||
var trans = DbContext.Database.BeginTransaction();
|
||||
bool confirm = false;
|
||||
try
|
||||
ExecuteInTransaction(() =>
|
||||
{
|
||||
int clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapRealizedPnL), ClientCashInCashOut.系统操作_互换, unwindData.ValueDate);
|
||||
int clientCashId = AddClientCash(td, Convert.ToDouble(-unwindData.SwapRealizedPnL), ClientCashInCashOut.系统操作_互换, unwindData.ValueDate);
|
||||
if (unwindData.SwapMarginRebatePnl != 0)
|
||||
{
|
||||
AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapMarginRebatePnl), ClientCashInCashOut.系统操作_预付金返息, unwindData.ValueDate);
|
||||
AddClientCash(td, Convert.ToDouble(-unwindData.SwapMarginRebatePnl), ClientCashInCashOut.系统操作_预付金返息, unwindData.ValueDate);
|
||||
}
|
||||
foreach (var item in unwindData.FlowEvents)
|
||||
{
|
||||
@@ -1517,19 +1567,8 @@ namespace YLErp.Modules.SwapModule
|
||||
td.TradeStatus = "已到期";
|
||||
}
|
||||
td.UnWindDate = unwindData.UnwindDate;
|
||||
DbContext.SaveChanges();
|
||||
trans.Commit();
|
||||
confirm = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
trans.Rollback();
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
trans.Dispose();
|
||||
}
|
||||
SaveAllChanges();
|
||||
});
|
||||
}
|
||||
/// <summary>
|
||||
/// 互换/平仓审核通过
|
||||
@@ -1548,20 +1587,16 @@ namespace YLErp.Modules.SwapModule
|
||||
swapEvent.unwindData = JsonConvert.DeserializeObject<UnwindData>(swapEvent.EventData);
|
||||
var flowList = DbContext.swap_flow_event.Where(x => x.EventId == swapEvent.id).ToList();
|
||||
string action = eventType == (int)SwapEventTypeEnum.互换 ? ClientCashInCashOut.系统操作_互换 : ClientCashInCashOut.系统操作_平仓费;
|
||||
int clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-swapEvent.unwindData.SwapRealizedPnL), action, swapEvent.unwindData.ValueDate);
|
||||
int clientCashId = AddClientCash(td, Convert.ToDouble(-swapEvent.unwindData.SwapRealizedPnL), action, swapEvent.unwindData.ValueDate);
|
||||
if (swapEvent.unwindData.SwapMarginAmount != 0)
|
||||
{
|
||||
AddClientCashInCashOut(td, Convert.ToDouble(swapEvent.unwindData.SwapMarginAmount), ClientCashInCashOut.系统操作_应付预付金, swapEvent.unwindData.ValueDate);
|
||||
AddClientCash(td, Convert.ToDouble(swapEvent.unwindData.SwapMarginAmount), ClientCashInCashOut.系统操作_应付预付金, swapEvent.unwindData.ValueDate);
|
||||
}
|
||||
swapEvent.ClientCashId = clientCashId;
|
||||
if (swapEvent.unwindData.CloseMethod == (int)CloseMethodEnum.全部平仓)
|
||||
{
|
||||
td.TradeStatus = "已平仓";
|
||||
td.trade_extend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == td.id);
|
||||
if (td.trade_extend != null && !td.trade_extend.ExtendObj.NeedOpenFee)
|
||||
{
|
||||
new ClientCashInCashOutService(this).SaveSwapTradeClientCash(td, td.TradePrice ?? 0, swapEvent.unwindData.ValueDate, 0);
|
||||
}
|
||||
CallSaveSwapTradeClientCash(td, swapEvent.unwindData.ValueDate);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1618,7 +1653,7 @@ namespace YLErp.Modules.SwapModule
|
||||
/// </summary>
|
||||
/// <param name="swap_Deal"></param>
|
||||
/// <param name="eventType"></param>
|
||||
private long SaveSwapDeal(UnwindData unwindData, int eventType, int clientCashId, string eventResason = "", bool approve = false)
|
||||
private long SaveSwapDealInternal(UnwindData unwindData, int eventType, int clientCashId, string eventResason = "", bool approve = false)
|
||||
{
|
||||
var td = DbContext.trade.Find(unwindData.SwapTradeId);
|
||||
if (td == null)
|
||||
|
||||
Reference in New Issue
Block a user