using BaseOUDAL; using DocumentFormat.OpenXml.Office2010.Excel; using OfficeOpenXml.Drawing; using Org.BouncyCastle.Ocsp; using System.Text; using YieldChain.Helpers; using YLErp.BLL; using YLErp.BLL.Eod; using YLErp.DBModels; using YLErp.DBModels.Enums; using YLErp.Model.Enum; using YLErp.Modules.RiskModule; using YLErp.Modules.SalesModule; using YLErp.Modules.SwapModule; using YLErp.Modules.SystemModule; using YLErp.Modules.UnderlyingModule; namespace YLErp.Modules.TradeModule.DealModule { /// /// 交易确认服务 /// public class TradeConfirmService : TradeServiceBase { readonly DateTime confirmDate; readonly ClientDBContext clientDB; public TradeConfirmService(YLBaseService baseService) : base(baseService) { confirmDate = SystemValueDate; clientDB = DbContextFactory.GetClientDbContext(OptUser); } public TradeConfirmService(OptUserInfo userInfo) : base(userInfo) { confirmDate = SystemValueDate; clientDB = DbContextFactory.GetClientDbContext(OptUser); } public TradeConfirmService(OptUserInfo userInfo, YLContext dbContext) : base(userInfo, dbContext) { confirmDate = SystemValueDate; clientDB = DbContextFactory.GetClientDbContext(OptUser); } /// /// 2021-02-04改造:批量确认因为耗时太长,用户多页面(交易列表,今日交易)并发确认导致生成多条期权费资金记录 /// public TradeConfirmResultModel tradeConfirm(IEnumerable tradeids, bool ignoreMoneyCheck = false, bool isSkipApproval = false, bool ignoreRiskWarning = false) { if (tradeids is null || !tradeids.Any(n => n > 0)) { throw new ArgumentException("tradeids 参数无效", nameof(tradeids)); } //将交易ID集合转换为Set结构 var idSet = tradeids.ToHashSet(); var isBatch = idSet.Count > 1; //首先查找是否有父交易ID存在的情况,找出父交易下面的所有子交易ID集合 var subTds = DbContext.trade.Where(n => n.ParentTradeId > 0 && n.ValidState != "InValid" && idSet.Contains(n.ParentTradeId) && (n.IsGroup == 0 || n.IsGroup == 2 && n.TradeType == "收益互换") && n.TradeStatus != "确认成交") .Select(n => new { n.id, n.ParentTradeId, n.IsGroup }).ToArray(); if (subTds.Any()) { foreach (var std in subTds) { idSet.Add(std.id); if (std.IsGroup == 0) { idSet.Remove(std.ParentTradeId); } } } var hasTradeProcess = HasTradeProcess(); var sbError = new StringBuilder(); var sbTip = new StringBuilder(); var result = new TradeConfirmResultModel { confirmedTradeIds = new List(), changeConfirmPaths = new List() }; HashSet idCache = new HashSet(); foreach (var tid in idSet) { var lockKeyId = "tradeConfirm_" + tid; //对每个交易单独处理基本上可以解决并发问题 //为了更加保险增加多线程并发锁定 lock (Helpers.LockHelper.Acquire(lockKeyId, 300)) { var pid = subTds.Where(O => O.id == tid).FirstOrDefault()?.ParentTradeId ?? 0; var temp = ConfirmTrade(tid: tid, isBatch: isBatch, hasTradeProcess: hasTradeProcess, ignoreMoneyCheck: ignoreMoneyCheck, isSkipApproval: isSkipApproval, confirmedTradeIds: result.confirmedTradeIds, changeConfirmPaths: result.changeConfirmPaths, continueQuotaCheck: idCache.Contains(pid), ignoreRiskWarning: ignoreRiskWarning); if (pid > 0) { idCache.Add(pid); } result.type = temp.RetCode.ToString(); if (temp.RetCode == TradeOpenRetCode.LackOfMoney) { sbError.AppendLine(temp.ErrorMsg); result.TradeId = temp.Trade.id; result.LackOfMoney = true; break; } //此处LackOfMoney仅仅代表需要前端二次处理,不代表资金缺乏,延用老逻辑 if (temp.RetCode == TradeOpenRetCode.QuotaTrialError || temp.RetCode == TradeOpenRetCode.FundStatus || temp.RetCode == TradeOpenRetCode.RiskWarning) { result.LackOfMoney = true; result.TrialDataId = temp.TrialDataId; result.TradeId = temp.Trade.id; sbError.AppendLine(temp.ErrorMsg); break; } if (!string.IsNullOrEmpty(temp.ErrorMsg)) { sbError.AppendLine(temp.ErrorMsg); } if (!string.IsNullOrEmpty(temp.TipMsg)) { sbTip.AppendLine(temp.TipMsg); } Helpers.LockHelper.Release(lockKeyId); } } DbContext.SaveChanges(); result.errorMsg = sbError.ToString(); result.tipMsg = sbTip.ToString(); return result; } /// /// 确认交易 /// /// 交易Id /// /// /// 忽略交易限额/资金和检查 /// /// /// /// /// private TradeOpenResult ConfirmTrade(int tid, bool isBatch, bool hasTradeProcess, bool ignoreMoneyCheck, bool isSkipApproval, List confirmedTradeIds, List changeConfirmPaths, bool continueQuotaCheck = false, bool ignoreRiskWarning = false) { var td = DbContext.trade.FirstOrDefault(t => t.id == tid); var result = new TradeOpenResult(td ?? new trade()); if (td == null) { result.ErrorMsg = $"系统编号:{tid},找不到交易数据,不能被确认"; return result; } if (td.TradeType == "结构化交易" && td.IsGroup == 0) { result.ErrorMsg = $"系统内部错误,不应出现结构化交易类型的确认处理,交易编号:{td.TradeNumber}"; return result; } //判断如果为待确认的则修改状态为确认状态 if (!ConsTrade.UnConfirmTradeStatusList.Contains(td.TradeStatus) && ((td.TradeStatus == "审批中" && !isSkipApproval) || td.TradeStatus == "确认成交")) { //result.ErrorMsg = $"客户:{td.ClientName},交易编号:{td.TradeNumber},状态为:{td.TradeStatus},不能被确认"; result.RetCode = TradeOpenRetCode.Success; return result; } if (ConsTrade.TradeStatusAfterAndWithOutConfirmed.Contains(td.TradeStatus)) { result.ErrorMsg = $"客户:{td.ClientName},交易编号:{td.TradeNumber},状态为:{td.TradeStatus},不能被确认"; return result; } //判断交易对手方信息是否允许确认 var client = DataCacheProvider.GetClientDataSource().GetData(td.ClientId); if (client == null) { result.ErrorMsg = $"未找到客户信息:{td.ClientName}"; return result; } if (!ignoreMoneyCheck && !continueQuotaCheck) { result = CheckTradeConfirmRule(result, td, isSkipApproval); if (!string.IsNullOrWhiteSpace(result.ErrorMsg)) { return result; } result = CheckClientRating(result, client, td); if (!string.IsNullOrWhiteSpace(result.ErrorMsg)) { return result; } var trialService = new QuotaMonitorService(this); if (!trialService.QuotaCheck(ref result, ignoreRiskWarning)) { return result; } } var history = "确认交易"; if (ignoreMoneyCheck) { history = "交易特批-" + history; if (isBatch) { history = "特权账户批量确认自动" + history; } } else if (ignoreRiskWarning) { history = "风控预警确认-" + history; } //进入交易审批流程 if (hasTradeProcess && !isSkipApproval) { td.TradeOldStatus = td.TradeStatus; td.OptId = UserId; td.OptName = UserName; td.OptDate = OptDate; int passStatus = new ApprovalProcessService(OptUser).CheckTradeApprovalStep(td, OptUser.UserId); if (passStatus == 0) { td.TradeStatus = ConsTrade.确认成交; //删掉trade_check相关的复核数据 new TradeCheckService(this).DeleteTradeCheck(td.id); var p = new ProcessTradeLog() { TradeId = td.id, ProcessType = "TradeProcess", ProcessStatus = ProcessTradeStatus.通过审批.ToString(), Log = "", OptId = UserId, CreateTime = DateTime.Now }; DbContext.processtradelog.Add(p); new SalesCommissionDataService(OptUser).SetCommissionVaild(td.id); EditReportStatus(td.id); SwapTradeConfirm(td, history,false,optlog:"手工操作"); } else if (passStatus == -3) { result.ErrorMsg = $"当前存在分支交易审批流程,检测到申请人不满足任意一个分支进入条件,无法提交审批,请更换申请人或调整审批组"; return result; } } else { td.TradeOldStatus = td.TradeStatus; td.OptId = UserId; td.OptName = UserName; td.OptDate = OptDate; td.TradeStatus = ConsTrade.确认成交; //删掉trade_check相关的复核数据 new TradeCheckService(this).DeleteTradeCheck(td.id); td.ProcessOrderId = ProcessTradeLog.审批通过; td.CheckTradeUpdate = Convert.ToInt32(TradeCheckEnum.StatusOfNew); td.ProcessOptDate = DateTime.Now; td.ProcessStatus = ProcessTradeStatus.通过审批.ToString(); var p = new ProcessTradeLog() { TradeId = td.id, ProcessType = "TradeProcess", ProcessStatus = ProcessTradeStatus.通过审批.ToString(), Log = "", OptId = UserId, CreateTime = DateTime.Now }; DbContext.processtradelog.Add(p); new SalesCommissionDataService(OptUser).SetCommissionVaild(td.id); EditReportStatus(td.id); SwapTradeConfirm(td, history, false, optlog: "手工操作"); } new ClientCashInCashOutService(this).SaveClientCashInCashOut(td); DbContext.SaveChanges(); //生成变更确认书(如果走审批流程应该在审批流程走完以后生成才对!!!) if (PS.Config.TradeElement.IsGenerateAmendDoc) { try { var filepath = new ChangeConfirmBookGenerator(this).GenerateChangeConfirmBook(td.id, out _); if (!string.IsNullOrEmpty(filepath)) { changeConfirmPaths.Add(filepath); } } catch (Exception ex) { LogFactory.GetLogger("生成变更确认书").Error(ex); } } if (td.TradeStatus == ConsTrade.确认成交) { confirmedTradeIds.Add(td.id); } AddTradeOperationHistoryAndSetParentTradeInfo(false, td, history, td.TradeOldStatus); result.RetCode = TradeOpenRetCode.Success; return result; } /// /// 新版互换确认 /// /// public void SwapTradeConfirm(trade td,string history,bool addlog,DateTime? openDate=null,string optlog="") { if (td.TradeType != "收益互换") { return; } openDate = openDate == null ? td.TradeDate : openDate; td.trade_extend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == td.id); var marginModes = new int[]{ (int)InterestModeEnum.追加预付金, (int)InterestModeEnum.初始预付金 }; var positions= DbContext.swap_position.Where(x => x.SwapTradeId == td.id && x.IsInitial && !x.Invalid ).ToList(); td.swap_positions = positions.Where(x =>marginModes.Contains(x.InterestMode)).ToList(); var posiPositions= positions.Where(x => x.PosiDirection>0).ToList(); SwapTradeService swapTradeService = new SwapTradeService(this); var swapEventService = new SwapEventService(this); swapTradeService.InitialPosition(td); swapTradeService.AddPositionEvent(td, optlog); //new SwapEodPositionService(this).InitSaveEodSwapPosition(td); var happenDate = td.TradeDate.Value; if (td.trade_extend != null && !td.trade_extend.ExtendObj.NeedOpenFee) { happenDate = td.ExerciseDate.Value; } var cashService = new ClientCashInCashOutService(this); cashService.SaveSwapTradeClientCash(td, td.TradePrice ?? 0, happenDate,0); foreach (var marginPositions in td.swap_positions.GroupBy(g=>g.HappenDate)) { var marginHappenDate = marginPositions.Key; foreach (var marginPosition in marginPositions) { marginPosition.Obervation = DbContext.trade_obervation.FirstOrDefault(x => x.PositionId == marginPosition.id); bool generateMargin = true; if (marginPosition.Obervation != null) { if (!marginPosition.Obervation.IsDeductPrincipal) { generateMargin = false; } } if (generateMargin) { var marginAmount = marginPosition.InterestPrincipalFix * (marginPosition.InterestDirection == 1 ? -1 : 1); cashService.SaveSwapTradeClientCash(td,Convert.ToDouble( marginAmount), marginHappenDate.HasValue ? marginHappenDate.Value : td.TradeDate.Value, marginPosition.id, ClientCashInCashOut.系统操作_应付预付金); } } } var swap_event= swapEventService.UpdateSwapEvent(); swapEventService.AddSwapEventDate(openDate.Value, td.id, (int)SwapEventTypeEnum.确认交易, "", 0, true, history); if (addlog) { var swapInsertEnum = SwapPushDataEnum.收益互换交易新增; var posiInsertEnum = SwapPushDataEnum.互换资产交易新增; var marginInsertEnum = SwapPushDataEnum.预付金交易新增; } else { Task.Run(() => { RealtimePnlCalc.RealtimeSwapPosition(new OptUserInfo(0, "互换实时持仓服务", OptUserFrom.Service)); }); } var interestDirection = (int)SwapDirectionEnum.收取; if (td.swap_positions.Any()) { interestDirection = td.swap_positions.FirstOrDefault().InterestDirection; } InitTradeReport(td, interestDirection); } /// /// 初始化报送字段填写 /// /// private void InitTradeReport(trade td,int marginDiretion) { var marinRate= UnderlyingHelper.GetApplicableMarginRate(td.ClientId, td.UnderlyingCode, td.TradeDate.Value); if (marinRate == null) { marinRate = new client_margin_detail() { init_rate = 1, maintain_rate = 1 }; } var trade_Report = DbContext.trade_Report.Where(x => x.TradeId == td.id).FirstOrDefault(); if (trade_Report == null) { trade_Report = new trade_report(); trade_Report.TradeId = td.id; DbContext.trade_Report.Add(trade_Report); } trade_Report.FillingPartyRole = td.OpponentRole == "乙方" ? "甲方" : "乙方"; trade_Report.PerformanceGuarantee = "部分担保"; trade_Report.PerformanceCollProvider = marginDiretion==(int)SwapDirectionEnum.收取? "乙方" : "甲方"; trade_Report.IsUsePerformanceColl = "否"; trade_Report.PerformanceExplain = "详见交易确认书。"; trade_Report.IsPerformanceCollInterest = "否"; trade_Report.PerformanceCollType = "0"; trade_Report.PerformanceCollRange = "0"; trade_Report.InitialPerformanceColl = (decimal)marinRate.init_rate*100; trade_Report.RecoveryPerformanceColl = (decimal)marinRate.maintain_rate * 100; trade_Report.UnwindPerformanceColl = trade_Report.RecoveryPerformanceColl; trade_Report.EodPerformanceColl = trade_Report.RecoveryPerformanceColl; trade_Report.PerformanceRemark = "详见交易确认书及定义性文件"; trade_Report.SetOpt(OptUser); var tradeauditing = new TradeAuditLog { TradeId = td.id, Changes = "", DataType = "C02", OptId = UserId, OptName = UserName, OptDate = OptDate, OptType = "修改报送相关字段", AuditFlag = TradeAuditFlag.operation }; //记录审核日志 DbContext.TradeAuditLog.Add(tradeauditing); DbContext.SaveChanges(); } private TradeOpenResult CheckClient(Client client, trade td, bool ignoreMoneyCheck, bool isSkipApproval) { var result = new TradeOpenResult(td); //if (client.PendingMarginCallPayment == 1) //{ // return $"交易'{td.TradeNumber}'确认失败,客户需要追保!"; //} result = CheckIsMoneyLack(result, client, td, ignoreMoneyCheck); if (result.RetCode == TradeOpenRetCode.LackOfMoney) { return result; } result.RetCode = TradeOpenRetCode.Success; return result; } private TradeOpenResult CheckIsMoneyLack(TradeOpenResult result, Client client, trade td, bool ignoreMoneyCheck) { //增加实时资金查询 查看资金资金是否足够支付期权费 tradeBLL.SetFieldsByTradeType(td); //!!!这个逻辑有问题,结构化交易应该是检查所有总和才对 if (!(ignoreMoneyCheck || td.IsGroup == 1) && !RealtimePnlCalc.TradeCanBeConfirm(client.id, td, out var errorMsg)) { result.RetCode = TradeOpenRetCode.LackOfMoney; result.ErrorMsg = $"客户:{client.Name},交易编号:{td.TradeNumber},{errorMsg}"; return result; } return result; } private TradeOpenResult CheckTradeConfirmRule(TradeOpenResult result, trade td, bool isSkipApproval) { if (td.TradeDate == SystemValueDate) { var invalidTradeConfirmRuleName = new TradeConfirmRuleService(this).checkTradeConfirmRule(td, UserId); if (!string.IsNullOrEmpty(invalidTradeConfirmRuleName) && !isSkipApproval) { result.ErrorMsg = $"该交易'{td.TradeNumber}'不能满足交易确认规则{invalidTradeConfirmRuleName}"; return result; } } return result; } private TradeOpenResult CheckClientRating(TradeOpenResult result, Client client, trade td) { var clientRatingQuery = clientDB.Client_Rating.Where(x => x.ClientId == client.id && !x.IsDeleted); //jira OTC-11345 //只有资信评级在有效期内且资信评级禁止交易时才阻止交易 if (clientRatingQuery.Any()) { //if (!clientRatingQuery.Any(cr => cr.ProcessStatus == "已审批")) //{ // return $"客户:{td.ClientName},资信等级未审批!"; //} var query2 = from clientR in clientRatingQuery join creditR in clientDB.credit_rating on clientR.CreditRatingId equals creditR.id into creditR_t from creditR in creditR_t.DefaultIfEmpty() where clientR.RatingDeadLine >= confirmDate && clientR.RatingStartDate <= confirmDate && clientR.ProcessStatus == "已审批" orderby clientR.RatingDeadLine descending, clientR.ProcessOptDate descending select new { clientR.id, credit = creditR == null ? null : new { creditR.IsSuspend, creditR.ValidState } }; var rating = query2.FirstOrDefault(); if (rating == null) { //return $"客户:{td.ClientName},资信评级不存在!"; } else if (rating.credit == null) { //return $"客户:{td.ClientName},资信等级不存在!"; } else if (rating.credit.ValidState == "InValid") { //return $"客户:{td.ClientName},资信等级无效,请重新设置该客户资信等级!"; } else if (rating.credit.IsSuspend) { result.ErrorMsg = $"客户:{td.ClientName},资信等级为暂停交易! "; return result; } } else { //没有的话,资信评级不做限制。 //return $"客户:{td.ClientName},{confirmDate:yyyy-MM-dd}的资信评级未找到!请设置! "; } result.RetCode = TradeOpenRetCode.Success; return result; } public void EditReportStatus(int id, bool editConfirmBook = false) { var tcList = DbContext.trade_cash.Where(O => O.TradeId == id && O.Action == "系统操作-期权费").ToList(); foreach (var item in tcList) { item.OptDate = DateTime.Now; } var contractCodeList = DbContext.trade_contract_r.Where(O => O.TradeId == id && O.Type == "交易确认书").Select(O => O.ContractCode).ToList(); foreach (var code in contractCodeList) { //如果查不到,说明是新交易,不用修改报送状态; if (string.IsNullOrWhiteSpace(code)) { continue; } var infoTag = $"_{code.Replace("_", "-")}_成交_"; if (editConfirmBook) { infoTag += "确认书"; } var notes = DbContext.sac_report_notes.Where(O => O.InfoTag.Contains(infoTag)).ToArray(); foreach (var item in notes) { item.changeStatus = true; } } } /// /// from tradecontroller.TerminationBackToConfirmed /// public void TerminationBackToConfirmed(int tradeId) { var td = DbContext.trade.Find(tradeId); if (td == null || td.ValidState == ConsGlobal.InValid) { throw new ServiceException("系统中没有相关的交易"); } if (td.TradeStatus != ConsTrade.提前终止拒绝) { throw new ServiceException("只有提前终止拒绝状态的交易可以执行此操作"); } td.TradeStatus = ConsTrade.确认成交; td.OptId = UserId; td.OptName = UserName; td.OptDate = DateTime.Now; AddTradeOperationHistoryAndSetParentTradeInfo(false, td, "提前终止放弃"); DbContext.SaveChanges(); } } /// /// 交易确认结果 /// public class TradeConfirmResultModel { public bool LackOfMoney; public int TrialDataId; public int TradeId; public List confirmedTradeIds; public List changeConfirmPaths; public string errorMsg; public string tipMsg; public string type; } }