using System.Data; using System.Text.RegularExpressions; using YLErp.BLL; using YLErp.BLL.MarginCalculation; using YLErp.DBModels.Consts; using YLErp.DBModels.Converts; using YLErp.Model; using YLErp.Models; using YLErp.Modules.DataProviderModule; using YLErp.Modules.TradeDalModule; using YLErp.MsOffice; using YLErp.Office; using YLErp.Office.Converters; using YLErp.Plugins.TradeDocGenerator.Abstracts; namespace YLErp.Modules.TradeModule.DocGenerateModule { /// /// 生成结算确认书 /// public class SettlementBillGenerateService : YLBaseService { public SettlementBillGenerateService(OptUserInfo userInfo) : base(userInfo) { } public SettlementBillGenerateService(YLBaseService baseService) : base(baseService) { } /// /// 获取交易关联的标的 /// public underlying_manager GetTradeUnderlying(string underlyingcode) { var _underlying = DbContext.underlying_manager.AsNoTracking().FirstOrDefault(x => x.UnderlyingCode == underlyingcode); return _underlying; } /// /// 生成结算确认书 /// public IEnumerable Generate(IEnumerable tradeCashIds, IEnumerable tradeIds, string docType, int userId, string userName) { if (docType == null) { throw new ArgumentNullException(nameof(docType)); } if (tradeCashIds != null && tradeCashIds.Any()) { tradeIds = DbContext.trade_cash.Where(x => tradeCashIds.Contains(x.id) && !x.IsDeleted).Select(x => x.TradeId).Distinct(); } if (tradeIds == null) { throw new ArgumentNullException(nameof(tradeIds)); } //当前交易日 var valueDate = valuedateBLL.ValueDate; var tradeStatus = new string[] { ConsTrade.已到期, ConsTrade.已执行 }; var tradeCashStatus = new string[] { ClientCashInCashOut.系统操作_行权费, ClientCashInCashOut.系统操作_平仓费, ClientCashInCashOut.系统操作_互换, ClientCashInCashOut.系统操作_票息 }; //---------------------------------- // 获取当日行权 平仓 到期的有效交易 //---------------------------------- var service = new TradeDalService(this); var tradeQuery = service .GetTradeOrEodTrade(tradeIds, valueDate) .Where(O => tradeStatus.Contains(O.TradeStatus) && O.UnWindDate <= valueDate && O.ClientId != 0); if (PS.Config.Is浙期) {//主要解决多腿交易,只勾选一腿时,将另一腿查出来一起生成; tradeQuery = service .GetTradeOrEodTradeOfTidASid(tradeIds, valueDate, useTradeDate: false) .Where(O => tradeStatus.Contains(O.TradeStatus) && O.TradeType != "结构化交易" && O.UnWindDate <= valueDate && O.ClientId != 0); } var tradeDic = tradeQuery.ToDictionary(n => n.id, n => new trade_contract_group { trade = n }); if (tradeDic.Count < 1) { throw new ServiceException("当日没有要结算的记录信息!"); } tradeIds = tradeDic.Keys.ToArray(); var parentTradeIds = tradeDic.Values.Where(O => O.trade != null && O.trade.ParentTradeId != 0).ToDictionary(K => K.trade.id, V => V.trade.ParentTradeId); if (PS.Config.Is长江) { parentTradeIds = tradeDic.Values.Where(O => O.trade != null && O.trade.ParentTradeId != 0 && (O.trade.StructureType == "牛市价差" || O.trade.StructureType == "熊市价差")).ToDictionary(K => K.trade.id, V => V.trade.ParentTradeId); } var parentTrades = service .GetStructTradeFromTradeOrEodTrade(parentTradeIds.Values, valueDate) .Where(O => tradeStatus.Contains(O.TradeStatus) && O.UnWindDate <= valueDate && O.ClientId != 0) .ToDictionary(K => K.id, V => V); if (PS.Config.Is长江 && parentTrades.Count > 0) { tradeIds = parentTrades.Select(O => O.Key); } //---------------------------------- // 交易确认书数据 //---------------------------------- var tcrQuery = from aa in DbContext.trade_contract_r where (tradeIds.Contains(aa.TradeId) || parentTradeIds.Values.Contains(aa.TradeId)) && aa.Type == ContractTypeEnum.Trade && aa.IsValid select aa; var tcrDatas = tcrQuery.ToDictionary(K => K.TradeId, V => V); foreach (var data in tradeDic) { var id = data.Key; if (data.Value.trade.ParentTradeId != 0) { id = data.Value.trade.ParentTradeId; } if (tcrDatas.TryGetValue(id, out var tcr)) { data.Value.ConfirmContractR = tcr; } } //---------------------------------- // 交易现金交割记录 //---------------------------------- var cashQuery = from tradeCash in DbContext.trade_cash.AsNoTracking() where tradeIds.Contains(tradeCash.TradeId) && tradeCash.ValidState != ConsGlobal.InValid && !tradeCash.IsDeleted && tradeCash.IsLastAction //20230408:如果参数入值没问题的话,ExerciseWay应该是不需要的 //20230408:东莞的黑箱雪球的tradeCash.ExerciseWay为null,故而把这行注释掉 // tradeCash.ExerciseWay == TradeCashExerciseWayEnum.到期行权 select tradeCash; //当一笔交易存在多条到期行权记录时,取的最后一条资金记录。 --时嬴政 2022年8月2日 var cashDatas = cashQuery.AsEnumerable().GroupBy(O => O.TradeId).ToDictionary(K => K.Key, V => V.LastOrDefault()); foreach (var data in cashDatas) { if (tradeDic.TryGetValue(data.Value.TradeId, out var group)) { if (parentTradeIds.ContainsKey(data.Value.TradeId)) { if (PS.Config.Is光大光子)//这个分支里的逻辑和下面那个长江应该没区别 { var id = parentTradeIds[data.Value.TradeId]; var allTradeList = service.GetTradeOrEodTradeOfTidAPid(new[] { id }, valueDate).GroupBy(O => O.TradeStatus).ToDictionary(K => K.Key, V => V.ToList()); allTradeList.First().Value.RemoveAll(x => x.ParentTradeId != 0 && !tradeIds.Contains(x.id)); var subTrades = allTradeList.First().Value.Where(x => x.ParentTradeId != 0).ToList(); var subTradeIds = subTrades.Select(O => O.id); var cashes = cashQuery.Where(O => subTradeIds.Contains(O.TradeId)).AsEnumerable().GroupBy(O => new { O.ValueDate, O.Action, O.ExerciseWay, O.Notional, O.UnwindNotional }).ToDictionary(K => K.Key, V => V.ToList()); if (subTrades.Count == cashes.First().Value.Count) { subTrades.ForEach(O => O.trade_cash = cashes.First().Value.First(B => B.TradeId == O.id)); group.trade = allTradeList.First().Value.Where(O => O.id == id).First(); group.trade.SubTrades = allTradeList.First().Value.Where(O => O.id != id).ToList(); } } else if (PS.Config.Is长江 || PS.Config.Is浙期) { var id = parentTradeIds[data.Value.TradeId]; //将已到期和已执行视为同一个状态; var allTradeList = service.GetTradeOrEodTradeOfTidAPid(new[] { id }, valueDate).GroupBy(O => (O.TradeStatus == "已执行" ? "已到期" : O.TradeStatus)).ToDictionary(K => K.Key, V => V.ToList()); var subTrades = allTradeList.Values.Aggregate((X, Y) => { X.AddRange(Y); return X; }).Where(x => x.ParentTradeId != 0).ToList(); var subTradeIds = subTrades.Select(O => O.id); var cashes = cashQuery.Where(O => subTradeIds.Contains(O.TradeId)).AsEnumerable().GroupBy(O => new { O.ValueDate, O.Action, O.ExerciseWay, O.Notional, O.UnwindNotional }).ToDictionary(K => K.Key, V => V.ToList()); //忽略主交易状态; if (subTrades.GroupBy(O => (O.TradeStatus == "已执行" ? "已到期" : O.TradeStatus)).Count() == 1 && cashes.Count == 1) { subTrades.ForEach(O => O.trade_cash = cashes.First().Value.First(B => B.TradeId == O.id)); group.trade = allTradeList.First().Value.Where(O => O.id == id).First(); group.trade.SubTrades = subTrades; } } else { var id = parentTradeIds[data.Value.TradeId]; var subTrades = service.GetStructTradeFromTradeOrEodTrade(new[] { id }, valueDate).GroupBy(O => O.TradeStatus).ToDictionary(K => K.Key, V => V.ToList()); tradeIds = subTrades.First().Value.Select(O => O.id).ToArray(); var cashes = cashQuery.AsEnumerable().GroupBy(O => new { O.ValueDate, O.Action, O.ExerciseWay, O.Notional, O.UnwindNotional }).ToDictionary(K => K.Key, V => V.ToList()); if (subTrades.Count == 1 && cashes.Count == 1) { subTrades.First().Value.ForEach(O => O.trade_cash = cashes.First().Value.First(B => B.TradeId == O.id)); group.trade = parentTrades[group.trade.id]; group.trade.SubTrades = subTrades.First().Value; } } } group.trade.trade_cash = data.Value; group.trade_cash = data.Value; if (group.trade.TradeType == "结构化交易") { if (group.trade.IsGroup == 1) { group.trade.SubTrades = DbContext.trade.Where(O => O.ParentTradeId == group.trade.id).ToList(); foreach (var subtc in group.trade.SubTrades) { subtc.trade_cash = DbContext.trade_cash.FirstOrDefault(tc => tc.TradeId == subtc.id && tc.ValidState != ConsGlobal.InValid && !tc.IsDeleted && tc.IsLastAction); } } } } } //---------------------------------- // 生成交易结算书 //---------------------------------- var resultList = new List(); AppManager.TryResolve(out ITradeSettleBillGenerator generatorV2); var checkDuplicateSet = new HashSet(); //客户编号列表 foreach (var tradeContractGroup in tradeDic.Values) { var missingTradeCash = false; if (tradeContractGroup.trade.TradeType != "结构化交易") { missingTradeCash = tradeContractGroup.trade_cash == null; } else if (tradeContractGroup.trade.IsGroup == 0) { missingTradeCash = tradeContractGroup.trade_cash == null || tradeContractGroup.trade.SubTrades.Any(O => O.trade_cash == null); } if (missingTradeCash) { resultList.Add(new TradeDocGenerateResult(tradeContractGroup.trade) { ErrorMessage = "缺少现金交割记录" }); } else if (generatorV2 != null) { var key = $"{tradeContractGroup.trade.id}_{tradeContractGroup.trade_cash.Action}_{tradeContractGroup.trade_cash.Notional}_{tradeContractGroup.trade_cash.UnwindNotional}"; if (!checkDuplicateSet.Add(key)) //判断重复 { continue; } var result = GenerateSingleV2(generatorV2, tradeContractGroup, tradeContractGroup.trade_cash.id, docType); if (PS.Config.Is方顿 && resultList.Any(x => x.OutputFilePath == result.OutputFilePath)) { continue; } resultList.Add(result); } else { var result = GenerateSingleV1(tradeContractGroup, userId, userName, docType); resultList.Add(result); } } return resultList; } /// /// /// private TradeDocGenerateResult GenerateSingleV2(ITradeSettleBillGenerator generator, trade_contract_group tradeContractGroup, int tradeCashId, string docType = "DOCX") { try { var context = new SettlementBillGenerateContext(tradeCashId, tradeContractGroup.trade, ContractTypeEnum.Clearing, OptUser); generator.Initialize(docType, context); return generator.GenerateDocument(); } catch (Exception ex) { LogFactory.GetLogger("GenerateSingleV2").Error(ex); return new TradeDocGenerateResult(tradeContractGroup.trade) { Exception = ex, ErrorMessage = "出错:" + ex.Message }; } } /// /// /// private TradeDocGenerateResult GenerateSingleV1(trade_contract_group contractGroup, int userId, string userName, string docType = "DOCX") { if (contractGroup == null) { throw new ArgumentNullException(nameof(contractGroup)); } var contractBLL = new TradeContractBLL(this); var dic = new Dictionary(); var trade = contractGroup.trade; var tradeCash = contractGroup.trade_cash; var tradeContractR = contractGroup.ConfirmContractR; if (tradeContractR == null) { throw new Exception(contractGroup.trade.TradeNumber + "该笔交易未生成交易确认书"); } var needAddData = true; #region 如果到期结算单已经生成过,则直接返回 var settlementDoc = contractBLL.GetContractDoc(trade.id, ContractTypeEnum.Clearing); if (settlementDoc != null) { if (File.Exists(settlementDoc.AbsolutePath)) { if (docType == "PDF") { var pdfPath = settlementDoc.AbsolutePath.Replace(".docx", ".pdf"); if (!File.Exists(pdfPath)) { OfficeFileConverter.ConvertDocxToPDF(settlementDoc.AbsolutePath, pdfPath); } return new TradeDocGenerateResult(trade) { OutputFilePath = $"../App_Docs/GTJA/{settlementDoc.FileName.Replace(".docx", ".pdf")}" }; } return new TradeDocGenerateResult(trade) { OutputFilePath = $"../App_Docs/GTJA/{settlementDoc.FileName}" }; } else { needAddData = false; } } #endregion var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(trade.UnderlyingCode); if (underlying == null) { return new TradeDocGenerateResult(trade) { ErrorMessage = $"标的资产{trade.UnderlyingCode}信息未找到" }; } var client = DataCacheProvider.GetClientDataSource().GetData(trade.ClientId); if (client == null) { return new TradeDocGenerateResult(trade) { ErrorMessage = $"客户(id:{trade.ClientId})信息未找到" }; } dic["{{客户名称}}"] = $"{trade.ClientName ?? ""}"; dic["{{交易方向}}"] = $"贵司{BuySellConvert.GetClientBuySell(contractGroup.trade.BuySell)}"; var tradeType = trade.TradeType == "香草期权" ? "普通" : (trade.TradeType == "结构化交易" ? trade.StructureType : trade.TradeType.Replace("期权", "")); dic["{{期权类型}}"] = $"{trade.ExerciseModeCn}{tradeType}{trade.OptionType}期权"; dic["{{到期日}}"] = trade.ExerciseDate?.ToString("yyyy年M月d日"); dic["{{交易日}}"] = trade.TradeDate?.ToString("yyyy年M月d日"); dic["{{合约到期日}}"] = trade.MaturityDate?.ToString("yyyy年M月d日"); var variety = VarietyBLL.GetAllvarietyModel().FirstOrDefault(x => x.id == underlying.UnderlyingTypeId); if (trade.TradeType == "合成价差期权") { dic["{{商品标的名称}}"] = synthetic_underlyingBLL.GetUnderlyingTipsInfo(trade.UnderlyingCode); } else { //合约代码和名称需要调整 var UnderlyingCode = underlying.UnderlyingCode; if (Regex.IsMatch(UnderlyingCode, "^[a-zA-Z]+00")) { UnderlyingCode = UnderlyingCode.TrimEnd('0'); } UnderlyingCode += "." + underlying.MarketCode; dic["{{商品标的名称}}"] = PS.Config.Is东兴 ? UnderlyingCode : trade.UnderlyingCode; } var strUnit = variety.QuoteUnitSingle; var tradeNotional = trade.Notional; var tradeOriginalNotional = (trade.OriginalNotional ?? 0); dic["{{商品标的数量}}"] = $"{tradeOriginalNotional.OtcFormatNotional()}{strUnit}"; dic["{{支付时间}}"] = "买方终止 / 到期时支付"; //期末价格 var endPrice = underlying.Price ?? 0; //交割金额 double tradeCashAmount = 0; double notional = 0; if (tradeCash != null) { endPrice = tradeCash.FinalPrice ?? 0; tradeCashAmount = tradeCash.Amount; notional = tradeCash.Action == "系统操作-行权费" ? tradeCash.Notional : tradeCash.UnwindNotional ?? 0; } dic["{{执行价格}}"] = $"{(trade.ActualStrike ?? 0).OtcFormatUmPrice()}元/{strUnit}"; dic["{{期初价格}}"] = $"{(trade.SpotPrice ?? 0).OtcFormatUmPrice()}元/{strUnit}"; dic["{{期权价格}}"] = $"{(trade.TradeSinglePrice ?? 0).OtcFormatMoney(grouping: false)}元/{strUnit}"; dic["{{期权费率}}"] = $"{trade.PremiumRate?.OtcFormat(OtcFormatFlag.premiumRateP)}"; dic["{{期初名义本金}}"] = $"{(trade.OriginalStockEqvNotional ?? 0).OtcFormat(OtcFormatFlag.StockEqvNotional)}元"; dic["{{期末名义本金}}"] = $"{trade.StockEqvNotional.OtcFormat(OtcFormatFlag.StockEqvNotional)}元"; dic["{{期末价格}}"] = $"{endPrice.OtcFormatUmPrice()}元/{strUnit}"; dic["{{期权收益}}"] = $"{Math.Abs(tradeCashAmount / tradeNotional).OtcFormatFlex(2, 4)}元/{strUnit}"; dic["{{期末数量}}"] = $"{tradeNotional.OtcFormatNotional()}{strUnit}"; dic["{{期权费}}"] = $"{(trade.TradePrice ?? 0).OtcFormatMoney(grouping: false)}元"; dic["{{结算价}}"] = $"{Math.Abs(tradeCashAmount).OtcFormatMoney()}元"; dic["{{终止日}}"] = trade.UnWindDate?.ToString("yyyy年M月d日"); var req = new RunMarginCalculationReq(UserInfo) { tradeList = new List { trade }, settleDate = trade.UnWindDate.Value, PriceProvider = new SinglePriceProvider(trade.UnderlyingCode, endPrice) }; if (trade.IsGroup == 1) { req.tradeList = DbContext.trade.Where(x => x.ParentTradeId == trade.id).ToList(); } var tradeSpans = MarginDefault.RunMarginCalculation(req); if (tradeSpans?.Count > 0) { var margin = trade.IsGroup == 1 ? tradeSpans.Sum(x => x.WorstCastClientPayable ?? 0) : (tradeSpans[0].WorstCastClientPayable ?? 0); dic["{{预付金}}"] = $"{Math.Abs(margin).OtcFormatMoney(grouping: false)}元"; } else { dic["{{预付金}}"] = "0元"; } dic["{{结算价格}}"] = dic["{{期权收益}}"]; var isGJBuyer = trade.BuySell == "买入"; double incomes; if (isGJBuyer) { incomes = tradeCashAmount - (notional * trade.TradeSinglePrice) ?? 0; } else { incomes = tradeCashAmount + (notional * trade.TradeSinglePrice) ?? 0; } dic["{{结算净额}}"] = $"{Math.Abs(incomes).OtcFormatMoney(grouping: false)}元"; dic["{{结算方式}}"] = incomes > 0 ? "贵司向我司支付结算净额" : "我司向贵司支付结算净额"; dic["{{到期盈亏}}"] = $"{Math.Abs(incomes).OtcFormatMoney(grouping: false)}元"; //东兴结算净额处理逻辑(卖价差期权) dic["{{客户结算净额}}"] = $"{(Math.Abs(tradeCashAmount) - (notional * trade.TradeSinglePrice))?.OtcFormatMoney(grouping: false)}元"; ; dic["{{机构结算净额}}"] = $"{((notional * trade.TradeSinglePrice) - Math.Abs(tradeCashAmount))?.OtcFormatMoney(grouping: false)}元"; ; //合同编号 string contractNo; string outPutFileName; if (settlementDoc != null) { contractNo = settlementDoc.Code; outPutFileName = settlementDoc.FileName; } else { var contractIndex = contractBLL.GetContractNo("GTJASettlement"); if (PS.Config.CompanyID == "中财资本") { contractNo = $"{trade.TradeNumber}S"; } else { contractNo = $"{client.Number}C{contractIndex}S"; } outPutFileName = $"{client.Name}到期结算单C{contractIndex}S.docx"; } dic["{{合同编号}}"] = contractNo; dic["{{合同时间}}"] = $"{DateTime.Now:yyyy年M月d日}"; dic["{{确认书编号}}"] = tradeContractR.ContractCode; try { var templateDir = OtcAppContext.MapPath("~/App_Docs/GTJA"); var templatePath = OtcAppContext.MapPath("~/App_Docs/导出模板/到期结算单模板.docx"); if (trade.IsUsePremiumRate != null && trade.IsUsePremiumRate.Value) { templatePath = OtcAppContext.MapPath("~/App_Docs/导出模板/到期结算单(名义本金)模板.docx"); } var outPutFilePath = Path.Combine(templateDir, outPutFileName); MsWordTempalteHelper.GenerateFromTemplate(templatePath, dic, outPutFilePath); if (docType == "PDF" && !outPutFilePath.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase)) { MsWordFormatConverter.ConvertToPDF(outPutFilePath, outPutFilePath.Replace(".docx", ".pdf")); } //更新操作时间 var now = DateTime.Now; File.SetLastAccessTime(outPutFilePath, now); #region --- Add trade_contract_document and trade_contract_r records if (needAddData) { var nowDate = DateTime.Now; var contractRecord = new trade_contract_document(); DbContext.trade_contract_document.Add(contractRecord); contractRecord.Code = settlementDoc != null ? settlementDoc.Code : dic["{{合同编号}}"].ToString(); contractRecord.ValueDate = valuedateBLL.ValueDate; contractRecord.BuyerId = isGJBuyer ? 0 : trade.ClientId; contractRecord.BuyerName = isGJBuyer ? PS.Config.CompanyFullName : trade.ClientName; contractRecord.SellerId = isGJBuyer ? trade.ClientId : 0; contractRecord.SellerName = isGJBuyer ? trade.ClientName : PS.Config.CompanyFullName; contractRecord.FileType = FileTypeEnum.Word; contractRecord.Status = ContractStatusEnum.Draft; contractRecord.Paths = $"App_Docs\\GTJA\\{outPutFileName}"; contractRecord.FileName = outPutFileName; contractRecord.Type = ContractTypeEnum.Clearing; contractRecord.OptId = userId; contractRecord.OptName = userName; contractRecord.OptDate = nowDate; var contractTradeRelation = new trade_contract_r(); DbContext.trade_contract_r.Add(contractTradeRelation); contractTradeRelation.TradeId = trade.id; contractTradeRelation.TradeNumber = trade.TradeNumber; contractTradeRelation.ContractCode = contractRecord.Code; contractTradeRelation.TradeClearCode = contractRecord.Code; contractTradeRelation.Type = ContractTypeEnum.Clearing; contractTradeRelation.OptId = userId; contractTradeRelation.OptName = userName; contractTradeRelation.OptDate = nowDate; contractTradeRelation.TradeCashId = tradeCash.id; contractTradeRelation.IsValid = true; DbContext.SaveChanges(); } #endregion --- Add trade_contract_document and trade_contract_r records return new TradeDocGenerateResult(trade) { OutputFilePath = $"/App_Docs/GTJA/{(docType == "PDF" ? outPutFileName.Replace(".docx", ".pdf") : outPutFileName)}" }; } catch (Exception ex) { LogFactory.GetLogger().Error("GenerateSingleSettleBill出错", ex); return new TradeDocGenerateResult(trade) { ErrorMessage = "出错:" + ex.Message }; } } } }