using YLErp.BLL; using YLErp.BLL.MarginCalculation; using YLErp.DBModels.Consts; 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 BatchUnwindReportGenerateService : YLBaseService { public BatchUnwindReportGenerateService(OptUserInfo userInfo) : base(userInfo) { } public BatchUnwindReportGenerateService(YLBaseService baseService) : base(baseService) { } /// /// 生成结算确认书 /// public IEnumerable Generate(IEnumerable unwindReportInputModels, string docType, int userId, string userName) { if (unwindReportInputModels == null) { throw new ArgumentNullException(nameof(unwindReportInputModels)); } if (docType == null) { throw new ArgumentNullException(nameof(docType)); } //使用Action等于票息并且IsLastAction检索数据,逻辑是存在问题的,敲出平仓+票息是没问题的, 到期+票息应该会取出多条,需要梳理逻辑后调整 var tradeIds = unwindReportInputModels.Select(m => m.TradeId).ToHashSet(); var tradeCashIds = unwindReportInputModels.Select(m => m.TradeCashId).ToHashSet(); //当前交易日 var valueDate = valuedateBLL.ValueDate; var tradeCashStatus = new string[] { ClientCashInCashOut.系统操作_行权费, ClientCashInCashOut.系统操作_平仓费 }; //---------------------------------- // 获取当日行权 平仓 到期的有效交易 //---------------------------------- var service = new TradeDalService(this); var tradeQuery = service.GetTradeOrEodTrade(tradeIds, valueDate).AsEnumerable(); IEnumerable query; var tcPredicate = PredicateBuilder.Create(tc => tc.ValidState != ConsGlobal.InValid && !tc.IsDeleted && tradeCashIds.Contains(tc.id) && (tradeCashStatus.Contains(tc.Action) && tc.ExerciseWay == TradeCashExerciseWayEnum.提前终止行权 || tc.Action == ClientCashInCashOut.系统操作_票息 && tc.IsLastAction)); if (PS.Config.TradeElement.IsSingleExecutionTemplate) { tradeQuery = tradeQuery.Where(O => O.ClientId > 0 && (O.TradeStatus == ConsTrade.已平仓 || O.UnWindNotional > 0)); } else { tradeQuery = tradeQuery.Where(O => O.ClientId > 0 && (O.TradeStatus == ConsTrade.已平仓 || O.UnWindNotional > 0 || (O.TradeStatus == ConsTrade.已执行 && O.ExerciseMode == "American"))); } var tids2 = tradeQuery.Select(n => n.ParentTradeId > 0 ? n.ParentTradeId : n.id).ToHashSet(); var tcrQuery = from tcrConfirm in DbContext.trade_contract_r join contractDoc in DbContext.trade_contract_document.Where(aa => aa.Type == ContractTypeEnum.Clearing) on tcrConfirm.ContractCode equals contractDoc.Code into contractDocs from contractDoc in contractDocs.DefaultIfEmpty() where tids2.Contains(tcrConfirm.TradeId) && tcrConfirm.Type == ContractTypeEnum.Trade && tcrConfirm.IsValid select new { tcrConfirm.TradeId, tcrConfirm, contractDoc }; query = from trade in tradeQuery join tradeCash in DbContext.trade_cash.Where(tcPredicate) on trade.id equals tradeCash.TradeId join tcr in tcrQuery on new { id = trade.ParentTradeId > 0 ? trade.ParentTradeId : trade.id } equals new { id = tcr.TradeId } into tcr_t from tcr in tcr_t.DefaultIfEmpty() select new trade_contract_group { id = tradeCash.id, trade = trade, trade_cash = tradeCash, ConfirmContractR = tcr != null ? tcr.tcrConfirm : null, trade_contract_document = tcr != null ? tcr.contractDoc : null, UnwindVol = tradeCash.Action == ClientCashInCashOut.系统操作_平仓费 ? tradeCash.UnwindVol : null }; if (!query.Any()) { throw new ServiceException("当日没有要结算的记录信息!"); } //---------------------------------- // 生成交易结算书 //---------------------------------- var resultList = new List(); AppManager.TryResolve(out ITradeUnwindReportGenerator generatorV2); var groups = query.ToList(); var tradeList = new List(); var groupSet = groups.Select(n => n.trade.id + n.trade_cash.ExerciseWay).ToHashSet(); foreach (var tradeContractGroup in groups) { if (tradeContractGroup.trade_cash.ExerciseWay == ClientCashInCashOut.系统操作_票息 && groupSet.Contains(tradeContractGroup.trade.id + TradeCashExerciseWayEnum.提前终止行权)) { continue; } if (!GlobalConfig.DotFindBrotherTrade) { if (tradeContractGroup.trade.ParentTradeId != 0) { var structureTrades = service .GetTradeOrEodTradeOfTidAPid(new[] { tradeContractGroup.trade.ParentTradeId }, valueDate); //var structureTrades = (from trade in DbContext.trade // where (trade.id == tradeContractGroup.trade.ParentTradeId // || trade.ParentTradeId == tradeContractGroup.trade.ParentTradeId) // && trade.ValidState != "InValid" // select trade).ToList(); var subTradeDict = structureTrades.Where(O => O.ParentTradeId == tradeContractGroup.trade.ParentTradeId).GroupBy(O => O.TradeStatus).ToDictionary(K => K.Key, V => V.ToList()); if (subTradeDict.Count == 1) { var subTradeIds = subTradeDict.First().Value.Select(O => O.id).ToList(); var tradeCashes = (from cash in DbContext.trade_cash where subTradeIds.Contains(cash.TradeId) && !cash.IsDeleted && cash.Action == tradeContractGroup.trade_cash.Action && cash.ValueDate == tradeContractGroup.trade_cash.ValueDate && cash.ExerciseWay == tradeContractGroup.trade_cash.ExerciseWay && cash.Notional == tradeContractGroup.trade_cash.Notional && cash.UnwindNotional == tradeContractGroup.trade_cash.UnwindNotional select cash) .ToList(); if (tradeCashes.Count == subTradeDict.First().Value.Count) { subTradeDict.First().Value.ForEach(O => O.trade_cash = tradeCashes.First(B => B.TradeId == O.id)); tradeContractGroup.trade = structureTrades.Where(O => !subTradeIds.Contains(O.id)).First(); tradeContractGroup.trade.SubTrades = structureTrades.Where(O => subTradeIds.Contains(O.id)).ToList(); } } } } tradeContractGroup.trade.trade_cash = tradeContractGroup.trade_cash; //if (!YLErp.PS.Config.Is光大光子 && tradeContractGroup.ConfirmContractR == null) //{ // resultList.Add(new TradeDocGenerateResult(tradeContractGroup.trade) { // ErrorMessage = "缺少交易确认书" // }); //} //else if ((tradeContractGroup.trade.TradeType != "结构化交易" && tradeContractGroup.trade_cash == null) || (tradeContractGroup.trade.TradeType == "结构化交易" && tradeContractGroup.trade.IsGroup == 0 && tradeContractGroup.trade.SubTrades.Any(O => O.trade_cash == null))) { resultList.Add(new TradeDocGenerateResult(tradeContractGroup.trade) { ErrorMessage = "缺少现金交割记录" }); } tradeList.Add(tradeContractGroup.trade.Clone()); } if (generatorV2 != null) { var result = GenerateSingleV2(generatorV2, tradeList, tradeCashIds, docType); resultList.Add(result); } else { foreach (var item in groups) { resultList.Add(GenerateSingleV1(item, OptUser.UserId, OptUser.UserName, docType)); } } return resultList; } /// /// /// protected TradeDocGenerateResult GenerateSingleV2(ITradeUnwindReportGenerator generator, List trades, IEnumerable tradeCashIds, string docType = "DOCX", DateTime? startDate = null, DateTime? endDate = null) { try { var context = new UnwindReportGenerateContext(tradeCashIds, trades, ContractTypeEnum.UnWind, OptUser, startDate, endDate); generator.Initialize(docType, context); return generator.GenerateDocument(); } catch (Exception ex) { LogFactory.GetLogger().Error("GenerateSingleV2", ex); return new TradeDocGenerateResult(trades) { Exception = ex, ErrorMessage = "出错:" + ex.Message }; } } /// /// /// protected 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 contractDoc = contractGroup.trade_contract_document; var needAddData = true; #region 如果提前终止确认书已经生成过,则直接返回 var unWindDoc = contractBLL.GetContractDoc(trade.id, ContractTypeEnum.UnWind, tradeCash.id); if (unWindDoc != null) { if (File.Exists(unWindDoc.AbsolutePath)) { if (docType == "PDF") { var pdfPath = unWindDoc.AbsolutePath.Replace(".docx", ".pdf"); if (!File.Exists(pdfPath)) { OfficeFileConverter.ConvertDocxToPDF(unWindDoc.AbsolutePath, pdfPath); } return new TradeDocGenerateResult(trade) { OutputFilePath = $"../App_Docs/GTJA/{unWindDoc.FileName.Replace(".docx", ".pdf")}" }; } return new TradeDocGenerateResult(trade) { OutputFilePath = $"../App_Docs/GTJA/{unWindDoc.FileName}" }; } else { needAddData = false; } } #endregion var isGJBuyer = trade.BuySell == "买入"; 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["{{乙方名称}}"] = client.Name; if (isGJBuyer) { dic["{{甲方方向}}"] = "买方"; dic["{{乙方方向}}"] = "卖方"; } else { dic["{{甲方方向}}"] = "卖方"; dic["{{乙方方向}}"] = "买方"; } var tradeType = trade.TradeType == "香草期权" ? "普通" : (trade.TradeType == "结构化交易" ? trade.StructureType : trade.TradeType.Replace("期权", "")); dic["{{期权类型}}"] = $"{trade.ExerciseModeCn}{tradeType}{trade.OptionType}期权"; var variety = VarietyBLL.GetAllvarietyModel().FirstOrDefault(x => x.id == underlying.UnderlyingTypeId); if (trade.TradeType == "合成价差期权") { dic["{{商品标的名称}}"] = synthetic_underlyingBLL.GetUnderlyingTipsInfo(trade.UnderlyingCode); } else { dic["{{商品标的名称}}"] = trade.UnderlyingCode; } dic["{{交易日}}"] = trade.TradeDate?.ToString("yyyy年M月d日"); dic["{{到期日}}"] = trade.ExerciseDate?.ToString("yyyy年M月d日"); var strUnit = variety.QuoteUnitSingle; var tradeNotional = trade.Notional; var tradeOriginalNotional = (trade.OriginalNotional ?? 0); var unWindCount = trade.TradeStatus == ConsTrade.已平仓 || (trade.UnWindNotional > 0 && tradeCash.UnwindNotional > 0) ? (tradeCash.UnwindNotional).Value : trade.Notional; var unWindCountPercent = (tradeCash.Action == ClientCashInCashOut.系统操作_平仓费 ? (tradeCash.UnwindPercentRate ?? 0) : (trade.Notional / trade.OriginalNotional) ?? 0).OtcFormatNotional(isPercent: true); var unWindPricePercent = (tradeCash.UnwindPricePercentRate ?? 0).OtcFormat(OtcFormatFlag.premiumRateP); dic["{{执行价格}}"] = $"{(trade.ActualStrike ?? 0).OtcFormatUmPrice()}元/{strUnit}"; dic["{{期权价格}}"] = $"{(trade.TradeSinglePrice ?? 0).OtcFormatMoney(grouping: false)}元/{strUnit}"; dic["{{商品标的数量}}"] = $"{tradeOriginalNotional.OtcFormatNotional()}{strUnit}"; dic["{{名义本金}}"] = $"{(trade.OriginalStockEqvNotional ?? 0).OtcFormat(OtcFormatFlag.StockEqvNotional)}"; dic["{{期权费率}}"] = $"{(trade.PremiumRate ?? 0).OtcFormat(OtcFormatFlag.premiumRateP)}"; dic["{{支付时间}}"] = "提前终止/到期时支付"; dic["{{终止类型}}"] = tradeCash.Action == ClientCashInCashOut.系统操作_平仓费 ? "平仓" : "美式期权行权"; dic["{{终止日}}"] = $"{trade.UnWindDate.Value:yyyy年M月d日}"; dic["{{终止数量}}"] = trade.IsUsePremiumRate.HasValue && trade.IsUsePremiumRate.Value ? unWindCountPercent : $"{unWindCount.OtcFormatNotional()}{strUnit}"; dic["{{标的终止日价格}}"] = $"{(tradeCash.FinalPrice ?? 0).OtcFormatUmPrice()}元/{strUnit}"; dic["{{期末价格}}"] = $"{(tradeCash.FinalPrice ?? 0).OtcFormatUmPrice()}元/{strUnit}"; var req = new RunMarginCalculationReq(UserInfo) { tradeList = new List { trade }, settleDate = trade.UnWindDate.Value, PriceProvider = new SinglePriceProvider(trade.UnderlyingCode, tradeCash.FinalPrice ?? 0) }; 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()}元"; } else { dic["{{预付金}}"] = "0元"; } dic["{{期权平仓价格}}"] = tradeCash.Action == "系统操作-行权费" || tradeCash.UnwindType == "部分行权" ? "行权收益" : "期权平仓价格"; dic["{{平仓价格}}"] = trade.IsUsePremiumRate == true && tradeCash.Action == "系统操作-平仓费" ? unWindPricePercent : $"{Math.Abs(tradeCash.UnwindPrice ?? 0).OtcFormatUmPrice()}元/{strUnit}"; dic["{{提前终止款}}"] = $"{tradeCash.Amount.OtcFormatMoney()}元"; dic["{{结算价格}}"] = dic["{{平仓价格}}"]; var premiumFee = trade.OriginalNotional > 0 ? (trade.TradePrice ?? 0) * unWindCount / trade.OriginalNotional.Value : 0; if (isGJBuyer) { var incomes = tradeCash.Amount - premiumFee; dic["{{结算净额}}"] = $"{Math.Abs(incomes).OtcFormatMoney()}元"; dic["{{结算方式}}"] = incomes > 0 ? "卖方向买方支付结算净额" : "买方向卖方支付结算净额"; dic["{{平仓盈亏}}"] = $"{Math.Abs(incomes).OtcFormatMoney(grouping: false)}元"; } else { var incomes = tradeCash.Amount + premiumFee; dic["{{结算净额}}"] = $"{Math.Abs(incomes).OtcFormatMoney()}元"; dic["{{结算方式}}"] = incomes > 0 ? "买方向卖方支付结算净额" : "卖方向买方支付结算净额"; dic["{{平仓盈亏}}"] = $"{Math.Abs(incomes).OtcFormatMoney(grouping: false)}元"; } //合同编号 string contractNo; string outPutFileName; if (unWindDoc != null) { contractNo = unWindDoc.Code; outPutFileName = unWindDoc.FileName; } else { var contractIndex = contractBLL.GetContractNo("GTJAUnWind"); if (PS.Config.CompanyID == "中财资本") { contractNo = $"{trade.TradeNumber} - 01"; } else { contractNo = $"{client.Number}C{contractIndex} - 01"; } outPutFileName = $"{client.Name}交易确认书C{contractIndex}-01.docx"; } dic["{{合同编号}}"] = contractNo; dic["{{确认书编号}}"] = contractDoc == null ? "" : contractDoc.Code; dic["{{确认书时间}}"] = contractDoc == null ? "" : $"{contractDoc.ValueDate.Value:yyyy年M月d日}"; try { var templateDir = OtcAppContext.MapPath("~/App_Docs/GTJA"); var templatePath = OtcAppContext.MapPath("~/App_Docs/导出模板/提前终止确认书模板.docx"); if (trade.IsUsePremiumRate.HasValue && 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; System.IO.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 = 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.UnWind; contractRecord.OptId = userId; contractRecord.OptName = userName; contractRecord.OptDate = nowDate; var contractTradeRelation = new trade_contract_r(); DbContext.trade_contract_r.Add(contractTradeRelation); contractTradeRelation.TradeCashId = tradeCash.id; contractTradeRelation.TradeId = trade.id; contractTradeRelation.TradeNumber = trade.TradeNumber; contractTradeRelation.ContractCode = contractRecord.Code; contractTradeRelation.TradeClearCode = contractRecord.Code; contractTradeRelation.Type = ContractTypeEnum.UnWind; contractTradeRelation.OptId = userId; contractTradeRelation.OptName = userName; contractTradeRelation.OptDate = nowDate; 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("GenerateSingleUnwindReport出错", ex); return new TradeDocGenerateResult(trade) { ErrorMessage = "出错:" + ex.Message }; } } } }