Files
zszq-trs/YLErpDAL/Modules/SwapModule/SwapEventService.cs
T
张名锐 aa3548e77f feat(bond): 支持债券与股票基金公司行为现金流计算的差异化处理 - init2
- 修改 CalcPayment 方法添加 useBondPriceScale 参数区分债券和股票/基金的金额计算口径
- 债券利息按每100元面值票息通过BondPriceConverter转为入库金额,股票基金分红直接计算
- 在BondPaymentService中添加详细的参数说明文档注释
- 更新SwapDealService中分红计算逻辑,根据标的类型自动选择合适的金额转换方式
- 新增CorporateActionEventLifecycleTest单元测试验证公司行为事件生命周期管理
- 添加SplitCorporateActionTddTest测试验证拆合股功能
- 优化FundCorporateActionRollbackAndUnwindTest扩展到股票类型测试
- 更新前端OperationHistory页面表格列宽和显示格式支持更长的说明信息
2026-08-19 17:48:34 +08:00

342 lines
14 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 Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using YLErp.DBModels;
namespace YLErp.Modules.SwapModule
{
public class SwapEventService : SwapTradeBaseService
{
public SwapEventService(OptUserInfo optUser) : base(optUser)
{
}
public SwapEventService(YLBaseService baseService) : base(baseService)
{
}
/// <summary>
/// 生成事件
/// </summary>
/// <param name="tradeDate">日期</param>
/// <param name="tradeId">互换交易码</param>
/// <param name="eventType">事件类型</param>
/// <param name="data">json数据</param>
/// <param name="save">是否保存</param>
/// <param name="clientCashId"></param>
/// <param name="eventRession"></param>
/// <param name="invalid">是否无效</param>
/// <returns></returns>
public swap_event AddSwapEventDate(DateTime tradeDate, int tradeId, int eventType, string data, int clientCashId, bool save = false,string eventRession="",bool invalid=false)
{
var swap_Event = new swap_event()
{
EventType = eventType,
EventData = data,
EventReason= eventRession,
SwapTradeId = tradeId,
ClientCashId = clientCashId,
Invalid = invalid,
ValueDate = tradeDate,
};
UpdateDbOption(swap_Event);
DbContext.swap_event.Add(swap_Event);
if (save)
{
DbContext.SaveChanges();
}
return swap_Event;
}
public swap_event UpdateSwapEvent()
{
var swapEvent= DbContext.swap_event.Where(x=>x.EventType== (int)SwapEventTypeEnum.新增交易 || x.EventType == (int)SwapEventTypeEnum.修改交易).OrderByDescending(o=>o.id).FirstOrDefault();
if (swapEvent!=null)
{
swapEvent.Invalid= false;
DbContext.SaveChanges();
}
return swapEvent;
}
/// <summary>
/// 获取上一日终归档日期
/// </summary>
/// <param name="tradeDate">当前清算日期</param>
/// <returns></returns>
public DateTime GetSwapEventDate(DateTime tradeDate, int tradeId, int? eventType)
{
Expression<Func<swap_event, bool>> expression = x => x.SwapTradeId == tradeId && x.ValueDate < tradeDate && !x.Invalid;
if (eventType.HasValue)
{
expression = expression.And(x => x.EventType == eventType);
}
var swapEvent = DbContext.swap_event.Where(expression).OrderByDescending(o => o.ValueDate).FirstOrDefault();
if (swapEvent != null)
{
return swapEvent.ValueDate;//上一日终归档日期
}
else//不存在归档日期
{
return tradeDate;
}
}
/// <summary>
/// 保存展期信息
/// </summary>
/// <param name="swap_Event"></param>
/// <returns></returns>
/// <exception cref="ServiceException"></exception>
public swap_event SaveExtensionTime(swap_event swap_Event)
{
var obj = DbContext.swap_event.Where(O => O.id == swap_Event.id).FirstOrDefault();
var trade = DbContext.trade.Where(O => O.id == swap_Event.SwapTradeId).FirstOrDefault();
if (trade == null)
{
throw new ServiceException("未找到交易,请刷新页面后再次尝试!");
}
var oldMaturityDate = trade.ExerciseDate.Value;
if (obj != null && !string.IsNullOrEmpty(obj.EventData))
{
oldMaturityDate = JsonConvert.DeserializeObject<ExtenstionData>(obj.EventData).OldMaturityDate;
}
if (swap_Event.extenstionData.NewMaturityDate <= oldMaturityDate)
{
throw new ServiceException("新到期日应晚于原到期日!");
}
if (obj != null && obj.ValueDate >= swap_Event.ValueDate)
{
throw new ServiceException("新展期日应晚于原展期日!");
}
if (swap_Event.extenstionData.NewMaturityDate< trade.StartDate)
{
throw new ServiceException("新到期日不应早于交易开始日期!");
}
swap_Event.extenstionData.OldMaturityDate = oldMaturityDate;
string data = JsonConvert.SerializeObject(swap_Event.extenstionData);
if (obj != null)
{
obj.Invalid = true;
}
swap_Event.EventReason = "交易展期";
trade.ExerciseDate = swap_Event.extenstionData.NewMaturityDate;
//修改互换观察日到期日
new SwapTradeService(this).UpdateObservationDay(trade.id, swap_Event.extenstionData.OldMaturityDate, trade.ExerciseDate.Value);
swap_Event = AddSwapEventDate(swap_Event.ValueDate, swap_Event.SwapTradeId, (int)SwapEventTypeEnum.展期, data, 0,false, swap_Event.EventReason);
DbContext.SaveChanges();
return swap_Event;
}
/// <summary>
/// 删除 展期
/// </summary>
/// <param name="id"></param>
/// <exception cref="ServiceException"></exception>
public void DeleteExtensionTime(long id)
{
var obj = DbContext.swap_event.Where(O => O.id == id).FirstOrDefault();
if (obj==null)
{
throw new ServiceException("未找到展期记录,请刷新页面后再次尝试!");
}
var trade = DbContext.trade.Where(O => O.id == obj.SwapTradeId).FirstOrDefault();
obj.Invalid = true;
obj.EventReason = "交易展期撤销";
if (!string.IsNullOrEmpty(obj.EventData))
{
obj.extenstionData = JsonConvert.DeserializeObject<ExtenstionData>(obj.EventData);
//修改到期日,互换观察日
new SwapTradeService(this).UpdateObservationDay(trade.id, trade.ExerciseDate.Value, obj.extenstionData.OldMaturityDate);
trade.ExerciseDate = obj.extenstionData.OldMaturityDate;
}
DbContext.SaveChanges();
}
/// <summary>
/// 获取当前展期记录
/// </summary>
/// <param name="tradeId"></param>
/// <returns></returns>
public swap_event GetExtensionTime(int tradeId)
{
var trade = DbContext.trade.Where(O => O.id == tradeId).FirstOrDefault();
if (trade == null)
{
throw new ServiceException("未找到交易,请刷新页面后再次尝试!");
}
var extension = DbContext.swap_event.Where(x => !x.Invalid && x.SwapTradeId == tradeId && x.EventType == (int)SwapEventTypeEnum.展期).FirstOrDefault();
if (extension==null)
{
extension = new swap_event();
extension.SwapTradeId = tradeId;
extension.ValueDate = DateTime.Now;
extension.extenstionData = new ExtenstionData()
{
OldMaturityDate = trade.ExerciseDate.Value,
NewMaturityDate=DateTime.Now
};
}
else
{
if (!string.IsNullOrEmpty(extension.EventData))
{
extension.extenstionData = JsonConvert.DeserializeObject<ExtenstionData>(extension.EventData);
}
}
extension.SwapTradeNumber = trade.TradeNumber;
return extension;
}
/// <summary>
/// 获取历史展期信息
/// </summary>
/// <param name="tradeId"></param>
/// <returns></returns>
public List<swap_event> GetExtenstionTimeEvents(int tradeId)
{
var events= DbContext.swap_event.Where(x => x.Invalid && x.SwapTradeId == tradeId&&x.EventType==(int)SwapEventTypeEnum.展期).OrderByDescending(o => o.ValueDate).ToList();
events.ForEach(x =>
{
if (!string.IsNullOrEmpty(x.EventData))
{
x.extenstionData = JsonConvert.DeserializeObject<ExtenstionData>(x.EventData);
}
});
return events;
}
/// <summary>
/// 获取交易操作历史。返回前会过滤掉登记日创建且尚未应用(Applied=false
/// 的公司行为事件,避免交易详情在真实调整前展示一条已完成历史。
/// </summary>
/// <param name="tradeId">交易id</param>
/// <returns></returns>
public List<swap_event> GetOpreationHistorys(int tradeId)
{
List<swap_event> list = DbContext.swap_event
.Where(x => x.SwapTradeId == tradeId)
.OrderByDescending(o => o.id)
.ToList();
return FilterOperationHistory(list);
}
/// <summary>
/// 过滤登记日创建且 Applied=false 的待生效公司行为事件,避免交易详情在真正
/// 调整前展示一条“已完成”历史。其他事件仍保留;无法解析的旧格式公司行为也
/// 保持可见,审计查询不能因为新 JSON 结构而静默丢失历史记录。
/// </summary>
public static List<swap_event> FilterOperationHistory(IEnumerable<swap_event> events)
{
return (events ?? Enumerable.Empty<swap_event>())
.Where(x => !IsPendingCorporateActionEvent(x))
.ToList();
}
/// <summary>
/// 判断指定事件是否为待生效的公司行为事件:EventType 为公司行为(13)
/// 且 EventData 反序列化后 Applied=false(登记日写入、尚未在真实除权日补齐调整后数据)。
/// 非公司行为类型、无法解析的旧格式或已应用的事件均返回 false,保证历史审计记录不被误删。
/// </summary>
public static bool IsPendingCorporateActionEvent(swap_event swapEvent)
{
if (swapEvent == null || swapEvent.EventType != (int)SwapEventTypeEnum.公司行为)
{
return false;
}
if (!TryDeserializeCorporateActionEventData(swapEvent, out var data))
{
// 非快照格式的历史公司行为保持可见,避免误删审计记录。
return false;
}
return !data.Applied;
}
/// <summary>
/// 将 swap_event.EventData 安全反序列化为公司行为快照。事件为空、EventData
/// 为空白或 JSON 格式不匹配时返回 false 并将 data 置 null,调用方据此保留旧格式记录。
/// </summary>
public static bool TryDeserializeCorporateActionEventData(
swap_event swapEvent,
out CorporateActionEventData data)
{
data = null;
if (swapEvent == null || string.IsNullOrWhiteSpace(swapEvent.EventData))
{
return false;
}
try
{
data = JsonConvert.DeserializeObject<CorporateActionEventData>(swapEvent.EventData);
return data != null;
}
catch (JsonException)
{
return false;
}
}
/// <summary>
/// 公司行为说明使用稳定的键值格式,完整保留调整前后名义本金、价格、数量、
/// 待实现分红和现金流变化,操作历史无需重新计算即可核对。
/// </summary>
public static string BuildCorporateActionEventReason(CorporateActionEventData data)
{
if (data == null)
{
return "公司行为快照为空";
}
// 使用 InvariantCulture 固定小数与日期格式,说明文本不随服务器区域设置变化。
string D(decimal value) => value.ToString(CultureInfo.InvariantCulture);
string Date(DateTime? value) => value.HasValue
? value.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)
: "";
return string.Join("; ", new[]
{
$"公司行为[{data.UnderlyingCode}]",
$"ExDividendDate={Date(data.ExDividendDate)}",
$"EffectiveDate={Date(data.EffectiveDate)}",
$"ExDividendInfoId={data.ExDividendInfoId}",
$"PositionId={data.PositionId}",
$"GiveCashAmount={D(data.GiveCashAmount)}",
$"GiveShareAmount={D(data.GiveShareAmount)}",
$"Split={(data.Split.HasValue ? D(data.Split.Value) : "")}",
$"RationedSharesAmount={D(data.RationedSharesAmount)}",
$"RationedSharesPrice={D(data.RationedSharesPrice)}",
"调整前",
$"BeforeNotional={D(data.BeforeNotional)}",
$"BeforePrice={D(data.BeforePrice)}",
$"BeforeQuantity={D(data.BeforeQuantity)}",
$"BeforePendingDividend={D(data.BeforePendingDividend)}",
"调整后",
$"AfterNotional={D(data.AfterNotional)}",
$"AfterPrice={D(data.AfterPrice)}",
$"AfterQuantity={D(data.AfterQuantity)}",
$"AfterPendingDividend={D(data.AfterPendingDividend)}",
$"CashFlowChange={D(data.CashFlowChange)}",
$"Applied={data.Applied}"
});
}
public void DeleteEvent(int tradeId)
{
var events = DbContext.swap_event.Where(x => x.Invalid && x.SwapTradeId == tradeId).ToList();
var flowEvents= DbContext.swap_flow_event.Where(x => x.DataState>0 && x.SwapTradeId == tradeId).ToList();
events.ForEach((x) =>
{
x.Invalid = true;
});
flowEvents.ForEach((x) =>
{
x.DataState = (int)SwapFlowDateStateEnum.废弃;
});
DbContext.SaveChanges();
}
}
}