Files
zszq-trs/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeSettleBillGenerator.cs
T
2026-08-28 11:28:41 +08:00

211 lines
11 KiB
C#

using Newtonsoft.Json.Linq;
using YLErp.Core.Helpers;
using YLErp.DBModels;
using YLErp.DBModels.Consts;
using YLErp.DBModels.Enums;
using YLErp.Models;
using YLErp.Modules.TradeModule.DocGenerateModule;
using YLErp.Plugins.TradeDocGenerator;
using YLErp.Plugins.TradeDocGenerator.Abstracts;
using YLErp.QdpModule;
namespace YLErp.Plugins.GuoLian.DocumentGenerator
{
/// <summary>
/// 国联证券结算单生成器
/// </summary>
internal class TradeSettleBillGenerator : BaseSettleBillGenerator, ITradeSettleBillGenerator
{
protected override string GetContractNo(out string contractIndex)
{
var contractNo = Context.GenerateContractNo(out contractIndex);
return contractNo;
}
protected override void PrepareViewData(Dictionary<string, object> dic)
{
// 普通 XLSX 结算单的公共上下文一次性加载,后续按平仓流水在内存中关联。
var client = Context.GetClient();
var flowEvents = Context.GetFlowEvents();
var allFlowEvents = Context.GetAllFlowEvents();
var swapPositions = Context.GetSwapPositions();
var trades = Context.Trades;
var tradeIds = trades.Select(t => t.id).ToList();
var tradeExtends = Context.GetTrade_Extends(tradeIds);
List<ExcelReportModel> table = new List<ExcelReportModel>();
SwapEndConfirmModel outPut = new SwapEndConfirmModel();
foreach (var flowEventGroup in flowEvents)
{
// 为每条平仓流水组装交易、初始持仓腿及同 EventId 的结算流水。
var tradeId = flowEventGroup.SwapTradeId;
var positions = swapPositions.Where(x => x.SwapTradeId == tradeId && x.IsInitial).ToList();
var posi = positions.FirstOrDefault(f => f.id == flowEventGroup.PositionId)
?? throw new ServiceException($"平仓事件{flowEventGroup.id}未找到对应初始持仓");
var unwindFlowEvents = allFlowEvents.Where(x => x.EventId == flowEventGroup.EventId).ToList();
var trade = trades.OfType<trade>().FirstOrDefault(x => x.id == tradeId);
if (trade == null)
{
throw new ServiceException($"平仓事件{flowEventGroup.id}未找到对应交易");
}
var tradeExtend = tradeExtends.FirstOrDefault(x => x.TradeId == tradeId);
var confirmNo = Context.Gettrade_contract_r(tradeId, ContractTypeEnum.Trade);
if (string.IsNullOrEmpty(confirmNo))
{
// 定位要点:带上事件id便于与 SwapSettlementBillGenerateService 的"生成范围扩张日志"对齐——
// 报错交易常是扩张拉入的同客户同日平仓,并非用户勾选的那笔。
throw new ServiceException($"{trade.TradeNumber}未生成交易确认书(平仓事件id={flowEventGroup.id}, tradeId={tradeId}, 客户={client.Name}, 平仓日={flowEventGroup.UnwindDate?.ToString("yyyy-MM-dd")});请先为该笔交易生成交易确认书后重试");
}
// 历史流水可能未落 PayDate,按交易结算规则补算后再进入统一构造器。
flowEventGroup.PayDate = ResolvePayDate(flowEventGroup, tradeExtend);
// UnwindData 提供平仓名义本金及浮动腿期末收益率,均属于事件快照数据。
UnwindData? unwindData = null;
if (flowEventGroup.EventId.HasValue)
{
var swapEvent = Context.GetEvent(flowEventGroup.EventId.Value);
if (swapEvent != null)
{
unwindData = JsonHelper.Deserialize<UnwindData>(swapEvent.EventData);
}
}
var underlying = Context.GetTradeUnderlying(flowEventGroup.UnderlyingCode);
var closeNotionalValue = unwindData?.CloseNotionalValue
?? flowEventGroup.Quantity * flowEventGroup.ContractSize * posi.PosiGrossPrice;
// 行构造器统一处理客户视角、结算公式、品种差异和模板展示精度。
var row = SwapSettlementBillRowBuilder.Build(new SwapSettlementBillRowInput
{
ConfirmNo = confirmNo,
ClientName = client.Name,
Trade = trade,
CloseFlow = flowEventGroup,
EventFlows = unwindFlowEvents,
Positions = positions,
UnderlyingInstrumentType = underlying?.UnderlyingInstrumentType,
CloseNotionalValue = closeNotionalValue,
// 与提前终止详情页保持同一来源:读取 swap_flow_event 中的平仓浮动腿记录。
ExitYtm = flowEventGroup.ExitYtm,
IncludePeriodPaymentInNetting = (tradeExtend?.ExtendObj?.DividendPayDate ?? 1) == 0
});
table.Add(row);
}
outPut.table = table;
dic["TRS结算单"] = outPut;
}
protected override void PrepareViewData(Dictionary<string, JToken> dic)
{
// DMA/定义文件型结算确认书沿用原 DOCX 数据结构,与普通 XLSX 模板相互独立。
var client = Context.GetClient();
if (string.IsNullOrEmpty(client.SettleFileNumber))
{
throw new ServiceException($"{client.Name}未设置定义文件编号");
}
var flowEvents = Context.GetFlowEvents();
var allFlowEvents = Context.GetAllFlowEvents();
var swapPositions = Context.GetSwapPositions();
var trades = Context.Trades;
var eventDate = flowEvents.First().UnwindDate;
var td = trades.First();
var realPositions = swapPositions.Where(x => x.PosiQuantity > 0 && !x.IsInitial).ToList();
var posiNationalValue = realPositions.Sum(x => x.PosiNotionalValue);
var metaDic = Context.GetTradeMetas(td.id);
dic["主协议编号"] = metaDic[ConsTradeMetaKey.MainProtocolCode];
dic["文件编号"] = client.SettleFileNumber;
dic["乙方"] = client.Name;
dic["管理人名称"] = client.Manager;
var confirmNo = Context.Gettrade_contract_r(td.id, ContractTypeEnum.Trade);
if (string.IsNullOrEmpty(confirmNo))
{
throw new ServiceException($"{td.TradeNumber}未生成交易确认书");
}
FormatToDict("平仓日期", eventDate, dic);
JArray table = new JArray();
JArray table2 = new JArray();
foreach (var flowEvent in flowEvents)
{
var tradeId = flowEvent.SwapTradeId;
var positions = swapPositions.Where(x => x.SwapTradeId == tradeId).ToList();
var posi = positions.FirstOrDefault(f => f.PositionId == flowEvent.PositionId);
JObject row = new JObject();
row["交易确认书编号"] = confirmNo;
var ratio = flowEvent.PositionType == (int)PositionTypeFlag.Long ? 1 : -1;
row["多空方向"] = flowEvent.PositionType == (int)PositionTypeFlag.Long ? "多" : "空";
FormatToDict("平仓日期", eventDate, row);
underlying_manager underlying = Context.GetTradeUnderlying(flowEvent.UnderlyingCode);
row["标的代码"] = flowEvent.UnderlyingCode;
row["标的名称"] = underlying?.UnderlyingName;
var preQty = flowEvent.Quantity + flowEvent.PositionQty ?? 0;
FormatToDict("平仓前数量", Convert.ToDouble(preQty) * ratio, row);
FormatToDict("平仓数量", Convert.ToDouble(flowEvent.Quantity), row);
FormatToDict("平仓后数量", Convert.ToDouble(flowEvent.PositionQty ?? 0) * ratio, row);
FormatToDict("平仓成交金额", Convert.ToDouble(flowEvent.Quantity * posi.PosiNetPrice * flowEvent.ContractSize), row);
table.Add(row);
}
JObject row2 = new JObject();
row2["交易确认书编号"] = confirmNo;
FormatToDict("平仓日期", eventDate, row2);
FormatToDict("平仓后名义本金", Convert.ToDouble(posiNationalValue), row2);
table2.Add(row2);
dic["table"] = table;
dic["table2"] = table2;
}
protected override string GetTemplateFilePath()
{
var trades = Context.Trades;
var templatePath = string.Empty;
// 多空组合及定义文件型债券使用 DMA DOCX,其余普通结算单使用 27 列 XLSX。
if (trades.Any(s => s.StructureType == "多空组合" || s.StructureType == "定义文件型债券收益互换"))
{
templatePath = MosPathHelper.Combine(GlobalConfig.PluginFolder, "App_Docs/settlement_template/dma_01.docx");
return templatePath;
}
templatePath = MosPathHelper.Combine(GlobalConfig.PluginFolder, "App_Docs/settlement_template/nodma_01.xlsx");
return templatePath;
}
protected override string GetOutputFileName(string contractNo, string contractIndex)
{
var trades = Context.Trades;
var client = Context.GetClient();
var flowEvents = Context.GetFlowEvents();
var eventDate = flowEvents.FirstOrDefault().UnwindDate.Value;
var valueDate = eventDate.ToString("yyyy-MM-dd");
var count = Context.GetDMASelttementCount(client.id, eventDate);
var countStr = count == 0 ? "" : "-" + (count + 1).ToString();
if (trades.Any(s => s.StructureType == "多空组合" || s.StructureType == "定义文件型债券收益互换"))
{
var FullName = $"{client.Name}-结算确认书-{valueDate}-交易平仓{countStr}";
return $"{FullName}.docx";
}
// 普通结算单以支付日命名;同一附件中的支付日必须唯一。
var tradeExtends = Context.GetTrade_Extends(trades.Select(x => x.id).ToList());
var payDate = SwapSettlementBillRowBuilder.GetSinglePayDate(
flowEvents.Select(flow => (DateTime?)ResolvePayDate(
flow,
tradeExtends.FirstOrDefault(x => x.TradeId == flow.SwapTradeId))));
return SwapSettlementBillRowBuilder.BuildOutputFileName(client.Name, payDate, count);
}
/// <summary>
/// 优先使用平仓流水已保存的支付日;历史数据缺失时按事件日和交易结算规则补算。
/// </summary>
private static DateTime ResolvePayDate(swap_flow_event flowEvent, trade_extend? tradeExtend)
{
if (flowEvent.PayDate.HasValue)
{
return flowEvent.PayDate.Value;
}
var settlementRules = tradeExtend?.ExtendObj?.SettlementRules ?? 0;
return QdpCalendarHelper.GetNonHoliday(flowEvent.EventDate.AddDays(settlementRules));
}
}
}