using Qdp.Foundation.Utilities; using YLErp.DBModels; using YLErp.DBModels.Consts; using YLErp.DBModels.Enums; using YLErp.Model; using YLErp.Modules.ApiModule; using YLErp.Modules.TradeDalModule; using YLErp.Modules.UnderlyingModule; using static YLErp.ConsGlobal; namespace YLErp.Modules.TradeModule { /// /// 交易OA服务 /// public class TradeOAService : YLBaseService { private static readonly IYcLogger logger = LogFactory.GetLogger(); public TradeOAService(OptUserInfo userInfo) : base(userInfo) { } public TradeOAService(YLBaseService baseService) : base(baseService) { } /// /// 处理单个交易的OA提交 /// /// 交易ID /// 处理结果 public async Task ProcessSingleOAAsync(int tradeId) { try { // 获取交易信息 var trade = DbContext.trade.FirstOrDefault(x=>x.id==tradeId); if (trade == null) { return new TradeOAResult {TradeId=tradeId, Success = false, Message = "未找到交易信息" }; } // 获取交易确认书信息 var tcr = DbContext.trade_contract_r.Where(t => t.Type == ContractTypeEnum.Trade && t.IsValid&&t.TradeId==tradeId) .FirstOrDefault(); trade_contract_document tdoc = null; if (tcr!=null) { tdoc = DbContext.trade_contract_document.Where(t => t.Code == tcr.ContractCode&&t.Type==tcr.Type).FirstOrDefault(); } // 将之前的OA记录设置为无效 var existingOAResults = DbContext.tradeContractOaResult.Where(x => x.trade_id == tradeId && x.is_valid == true).ToList(); foreach (var existingResult in existingOAResults) { existingResult.is_valid = false; existingResult.SetOpt(UserInfo); } // 创建OA结果记录 var oaResult = new trade_contract_oa_result { trade_id = tradeId, contract_code = tcr?.ContractCode, status = "提交中", is_valid = true, }; oaResult.SetOpt(UserInfo); DbContext.tradeContractOaResult.Add(oaResult); await DbContext.SaveChangesAsync(); // 调用OA接口 var oaResp = await CallOAInterfaceAsync(trade, tcr?.ContractCode, tdoc, oaResult.id); if (oaResp != null&& oaResp.success) { oaResult.status = "提交成功"; oaResult.oa_fileid = oaResp.data?.fileid; oaResult.oa_msg = oaResp.data?.url; } else { oaResult.status = "提交失败"; oaResult.oa_msg = oaResp?.message ?? "OA接口调用失败"; } await DbContext.SaveChangesAsync(); return new TradeOAResult { TradeId = tradeId, Success = oaResult.status== "提交成功", Message = oaResult.oa_msg }; } catch (Exception ex) { logger.Error($"处理交易ID {tradeId} 的OA时发生错误: {ex.Message}", ex); return new TradeOAResult { TradeId = tradeId, Success = false, Message = $"提交OA失败: {ex.Message}" }; } } /// /// 调用OA接口 /// /// 交易信息 /// 合同编号 /// 交易确认书文档 /// OA结果记录ID /// 是否成功 private async Task CallOAInterfaceAsync(trade trade, string contractCode, trade_contract_document tdoc, int oaResultId) { try { // 获取OA配置 var oaConfigSection = AppManager.GetConfiguration().GetSection("oa_confg"); var baseUrl = oaConfigSection["BaseUrl"]; var loginName = UserInfo.OaAccount; var systemKey = oaConfigSection["SystemKey"]; var objectClass = oaConfigSection["ObjectClass"]; var urgent = int.Parse(oaConfigSection["Urgent"] ?? "1"); var issend = int.Parse(oaConfigSection["Issend"] ?? "0"); if (string.IsNullOrEmpty(loginName)) { logger.Error("OA账号未配置"); return new OAResponse() { data = null, success = false, message = "OA账号未配置" }; } if (string.IsNullOrEmpty(baseUrl)) { logger.Error("OA接口配置中BaseUrl为空"); return new OAResponse() { data = null, success = false, message = "OA接口配置中地址为空" }; } // 处理附件 var attachments = new List(); if (tdoc != null && !string.IsNullOrEmpty(tdoc.AbsolutePath)) { try { if (File.Exists(tdoc.AbsolutePath)) { var fileBytes = await File.ReadAllBytesAsync(tdoc.AbsolutePath); var base64Content = Convert.ToBase64String(fileBytes); var fileName = Path.GetFileName(tdoc.AbsolutePath); attachments.Add(new { nrtitle = fileName, nrtype= "1", content = base64Content }); logger.Info($"成功读取附件文件:{tdoc.AbsolutePath},文件大小:{fileBytes.Length} 字节"); } else { logger.Error($"附件文件不存在:{tdoc.AbsolutePath}"); return new OAResponse() { data = null, success = false, message = $"附件文件不存在" }; } } catch (Exception ex) { logger.Error($"读取附件文件失败:{tdoc.AbsolutePath},错误:{ex.Message}", ex); return new OAResponse() { data = null, success = false, message = $"读取附件文件失败" }; } } // 计算同一客户、同一交易日期、同一多空方向名义本金汇总 // 1. 先获取当前交易的多空方向 var currentPosition = DbContext.swap_position.AsNoTracking() .Where(sp => sp.SwapTradeId == trade.id && sp.IsInitial && sp.PositionType > 0 && !sp.Invalid) .FirstOrDefault(); var currentPositionType = currentPosition.PositionType; // 2. 查询同一客户、同一交易日期的所有有效交易 var sameClientTrades = DbContext.trade.AsNoTracking() .Where(t => t.ClientId == trade.ClientId && t.TradeDate == trade.TradeDate && t.ValidState != "InValid"); var exerciseDate = sameClientTrades.Select(x => x.ExerciseDate).OrderBy(o => o).FirstOrDefault(); // 3. 关联持仓表,筛选相同多空方向的持仓 var matchingPositions = from t in sameClientTrades join sp in DbContext.swap_position.AsNoTracking() on t.id equals sp.SwapTradeId where sp.PositionType == currentPositionType && sp.IsInitial && sp.PositionType > 0 && !sp.Invalid select t; // 4. 计算名义本金汇总和加权平均保证金率 var totalNotionalPrincipal = matchingPositions.Sum(x => x.OriginalStockEqvNotional); // 计算加权平均保证金率 decimal marginRate = 0; if (totalNotionalPrincipal > 0) { decimal weightedMarginSum = 0; decimal totalCount = 0; foreach (var t in matchingPositions) { var marginDetail = UnderlyingHelper.GetApplicableMarginRate(t.ClientId, t.UnderlyingCode, t.TradeDate.Value); var rate = marginDetail?.init_rate ?? 0; weightedMarginSum += rate*Convert.ToDecimal(t.OriginalStockEqvNotional); totalCount+= Convert.ToDecimal(t.OriginalStockEqvNotional); } marginRate = weightedMarginSum / totalCount; marginRate=Math.Round(marginRate, 4); // 保留四位小数 } var posiTypeStr = currentPositionType == (int)PositionTypeFlag.Long ? "买入" : "卖出"; // 构建OA请求参数 var oaRequest = new { loginname = loginName, systemkey = systemKey, objectclass = objectClass, title = $"交易确认书OA申请-{trade.ClientName}-{contractCode}", urgent = urgent, issend = issend, copyattfileid = "", je = (double)totalNotionalPrincipal, filerela = new object[0], groupno = "", extinfo = new { busdata = new { BM1 = "", BM2 = "", BGRQ = trade.TradeDate.Value.ToString("yyyy-MM-dd"), NGR1 = "", NGR2 = "", CKDX1 = "", CKDX2 = "", LB = "", QSBGNR = $"各位领导:\n\t\t经友好协商,我司拟与交易对手开展以下场外利率收益互换交易:\n浮动收益交付方\t存款收益接收方\t标的\t方向\t起始日\t到期日\t合约名义本金\t保证金支付方\n浙商证券\t{trade.ClientName}\t{trade.UnderlyingInstrumentTypeCn}\t{posiTypeStr}\t{trade.StartDate?.ToString("yyyy/M/d")}\t{exerciseDate?.ToString("yyyy/M/d")}\t{totalNotionalPrincipal:N0}元\t{trade.ClientName}\n交易标的满足浙商证券标的池管理要求,交易对手方为非交易商,提供【{(marginRate*100).ToString("0.##")}%】名义本金的履约担保品作为初始保证金。保证金预警线与盯市追保符合内外规要求。", MX = new object[0], SQMX = new object[0] }, attachments = attachments.ToArray() } }; logger.Info($"OA接口参数:{JsonHelper.Serialize(oaRequest)}"); // 调用OA接口 var httpClient = new HttpClientWrap(baseUrl); var response = httpClient.PostJson("/fileDraft", oaRequest, null); logger.Info($"OA接口返回:{response}"); // 解析响应 var responseObj = JsonHelper.Deserialize(response); return responseObj; } catch (Exception ex) { logger.Error($"调用OA接口时发生异常,交易ID:{trade.id},异常信息:{ex.Message}", ex); return new OAResponse() { data = null, success = false, message = $"调用OA接口时发生异常" }; } } } }