Files
zszq-trs/UnitTestProject/Modules/SwapModule/TestableSwapEodPositionService.cs
T
张名锐 b2f4e16782 feat(bond): 支持股票和基金现金分红纳入债券支付计算 - 收紧过度开发行为
- 实现股票/基金现金分红数据从 ex_dividend_info 同步到 BondPayment
- 新增公司行为去重机制,避免镜像任务完成后重复计息
- 统一现金分红存储口径为"每 10 份派现金额",保持与同步任务一致性
- 修改 CalcPayment 方法,股票/基金分红需除以 10 转换实际现金金额
- 添加单元测试验证债券票息和股票/基金分红的不同计算方式
- 更新文档注释说明"每 10 份派现金额"存储规范
- 修复公司行为生效日处理逻辑,确保正确应用除权系数
- 扩展测试覆盖股票类证券的公司行为处理场景
2026-08-20 13:30:36 +08:00

116 lines
5.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using YLErp.DBModels;
using YLErp.DBModels.Enums;
using YLErp.Model;
namespace YLErp.Modules.SwapModule
{
/// <summary>
/// SwapEodPositionService 的可测试化基类(纯内存,不连库)。
/// ============================================================================
/// 收敛各 ScenarioTest 中 Stub 子类的重复 override
/// - PersistEodSwapPosition:收集到列表,不写库
/// - SaveAllChangesno-op
/// - GetCurrencyRate:返回 1.0(本币)
/// - 统一构造函数(注入 OptUserInfo,标记 UnitTest 来源)
///
/// 暴露 PersistedPositions / SaveChangesCount / ClientCashCalls 等输出捕获属性,
/// 供断言使用。各测试子类按需再 override 业务 seamFindTrade/GetUnderlyingPrice 等)。
///
/// 设计原则:
/// - 只收敛 8/8 Stub 都重复的高频 override,不预设业务数据注入方式
/// ComposePage 用属性字典、SwapPositionCompose 用构造函数 List,差异留给子类)
/// - 不提供 Execute* 包装器(签名各异且大多只出现 1-2 次,留在各子类避免基类膨胀)
/// ============================================================================
/// </summary>
public class TestableSwapEodPositionService : SwapEodPositionService
{
/// <summary>捕获所有持久化的 eod 持仓(按调用顺序)</summary>
public List<eod_swap_position> PersistedPositions { get; } = new();
/// <summary>
/// 捕获所有持久化的互换流水事件(按调用顺序)。
/// 生产 PersistFlowEvent 写 DbContext.swap_flow_eventGetConsumedInterest 也读同一张表。
/// 测试不连库,这里把真实收盘产生的 swap_flow_event 收集起来,
/// 使 GetConsumedInterest 能像生产一样从"真实累积的流水"里算已结利息,
/// 而无需硬编码、无需连库。这是消除"return 0m"伪绿的关键。
/// </summary>
public List<swap_flow_event> FlowEvents { get; } = new();
/// <summary>SaveAllChanges 调用次数</summary>
public int SaveChangesCount { get; private set; }
/// <summary>AddClientCash 调用记录(金额, 操作)</summary>
public List<(double amount, string action)> ClientCashCalls { get; } = new();
/// <summary>SwapPositionCompose 使用的公司行为内存数据;默认空,避免测试访问数据库。</summary>
public List<ex_dividend_info> ExDividendInfos { get; } = new();
/// <summary>捕获公司行为生命周期事件,避免事件测试访问真实 swap_event 表。</summary>
public List<swap_event> CorporateActionEvents { get; } = new();
/// <summary>自增 id 模拟器(新增 eod 时分配 id</summary>
private int _nextId = 1;
protected TestableSwapEodPositionService(string testName)
: base(new OptUserInfo(0, testName ?? nameof(TestableSwapEodPositionService), OptUserFrom.UnitTest))
{
}
// ===== 高频 seam override8/8 Stub 都重复,收敛到基类)=====
protected override void PersistEodSwapPosition(eod_swap_position position)
{
if (position.id == 0) position.id = _nextId++;
PersistedPositions.Add(position);
}
/// <summary>
/// 捕获真实收盘产生的 swap_flow_event(生产写 DbContext.swap_flow_event)。
/// 与 PersistEodSwapPosition 同理,这里只收集不写库,供 GetConsumedInterest 真实计算。
/// </summary>
protected void PersistFlowEvent(swap_flow_event flowEvent)
{
if (flowEvent.id == 0) flowEvent.id = _nextId++;
FlowEvents.Add(flowEvent);
}
protected override void SaveAllChanges()
{
SaveChangesCount++;
}
protected override double GetCurrencyRate(string quoteCurrency, string settlementCurrency, DateTime valueDate, bool seekPreday, CurrencyRateType currencyRateType)
{
return 1.0; // 本币,汇率=1
}
protected override List<ex_dividend_info> FindCorporateActionInfos(DateTime settleDate)
{
return ExDividendInfos
.Where(x => x.ValidStatus
&& (x.ExDividendDate?.Date == settleDate.Date
|| x.EffectiveDate?.Date == settleDate.Date))
.ToList();
}
protected override List<swap_event> FindCorporateActionEvents(int swapTradeId)
{
return CorporateActionEvents
.Where(x => x.SwapTradeId == swapTradeId && !x.Invalid
&& x.EventType == (int)SwapEventTypeEnum.公司行为)
.ToList();
}
protected override decimal GetFundCorporateActionClosePrice(
ex_dividend_info dividendInfo,
decimal fallbackPrice)
=> fallbackPrice;
protected override int AddClientCash(trade td, double amount, string action, DateTime valueDate)
{
ClientCashCalls.Add((amount, action));
return _nextId++;
}
}
}