211 lines
11 KiB
C#
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));
|
|
}
|
|
}
|
|
}
|