diff --git a/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs b/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs index eab45af4..e2315e10 100644 --- a/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs +++ b/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs @@ -4465,12 +4465,20 @@ namespace YLErp.Modules.RiskModule return result; } - public bool QuotaCheck(ref TradeOpenResult res) + /// + /// 交易确认风控检查入口:执行完整风控试算(老风控 + 新风控),并把试算结果转换为交易确认结果。 + /// 返回 true 表示确认流程可以继续;返回 false 表示需要阻断、提示或审批。 + /// + public bool CheckTradeConfirmRisk(ref TradeOpenResult res) { - return QuotaCheck(ref res, false); + return CheckTradeConfirmRisk(ref res, false); } - public bool QuotaCheck(ref TradeOpenResult res, bool ignoreRiskWarning, IEnumerable ignoreRiskRuleIds = null) + /// + /// 交易确认风控检查入口:执行完整风控试算(老风控 + 新风控)。 + /// ignoreRiskWarning/ignoreRiskRuleIds 只用于新风控“需审批”规则的二次确认,不影响老风控结果。 + /// + public bool CheckTradeConfirmRisk(ref TradeOpenResult res, bool ignoreRiskWarning, IEnumerable ignoreRiskRuleIds = null) { if (res == null) { @@ -4483,9 +4491,39 @@ namespace YLErp.Modules.RiskModule } var tradeId = (res.Trade.ParentTradeId == 0 || res.Trade.IsGroup != 0) ? res.Trade.id : res.Trade.ParentTradeId; - var tradeNumber = DbContext.trade.Where(O => O.id == tradeId).Select(O => O.TradeNumber).FirstOrDefault(); var trialService = new QuotaMonitorService(this); - var quotaObj = trialService.QuotaCheck(tradeId, 1); + var quotaObj = trialService.RunQuotaTrial(tradeId, 1); + return ApplyQuotaCheckResult(ref res, quotaObj, ignoreRiskWarning, ignoreRiskRuleIds); + } + + /// + /// 交易确认新风控检查入口:只执行新风控试算。 + /// 用于老风控已被特批放行(ignoreMoneyCheck = true)的场景,避免老风控特批误跳过新风控。 + /// + public bool CheckTradeConfirmNewRisk(ref TradeOpenResult res, bool ignoreRiskWarning, IEnumerable ignoreRiskRuleIds = null) + { + if (res == null) + { + throw new ArgumentNullException(nameof(res)); + } + + if (res.Trade == null) + { + throw new ArgumentNullException(nameof(res.Trade)); + } + + var tradeId = (res.Trade.ParentTradeId == 0 || res.Trade.IsGroup != 0) ? res.Trade.id : res.Trade.ParentTradeId; + var trialService = new QuotaMonitorService(this); + var quotaObj = trialService.RunNewRiskTrial(tradeId, 1); + return ApplyQuotaCheckResult(ref res, quotaObj, ignoreRiskWarning, ignoreRiskRuleIds); + } + + /// + /// 将风控试算结果转换为交易确认结果。 + /// 这里不重新执行风控,只负责设置 RetCode、ErrorMsg、TipMsg、TrialDataId 等返回给确认流程的字段。 + /// + private bool ApplyQuotaCheckResult(ref TradeOpenResult res, QuotaTrial quotaObj, bool ignoreRiskWarning, IEnumerable ignoreRiskRuleIds = null) + { if (quotaObj.TrialStatus == QuotaTrialStatusEnum.Success)//这次计算结果是通过,直接通过 { if (!string.IsNullOrWhiteSpace(quotaObj.AvailableForClient)) // 返回用户资金情况 @@ -5026,12 +5064,13 @@ namespace YLErp.Modules.RiskModule } /// - /// 交易试算 + /// 完整风控试算:先执行老风控资金/限额试算,再叠加新风控试算结果。 + /// 后续老风控下线时,调用方可以保留该入口,内部改为直接调用 RunNewRiskTrial。 /// /// 交易Id,结构化交易请传主交易Id /// 试算来源,0:手动点击试算按钮;1:确认成交时自动调用; /// - public QuotaTrial QuotaCheck(int tradeId, int trialSource) + public QuotaTrial RunQuotaTrial(int tradeId, int trialSource) { _logger.Info($"[限额试算] 开始执行 - tradeId: {tradeId}, trialSource: {trialSource}"); var result = new QuotaTrial(); @@ -5076,8 +5115,20 @@ namespace YLErp.Modules.RiskModule tradeList = tradeList.Where(O => !string.IsNullOrEmpty(O.UnderlyingCode) && O.TradeType != "结构化交易" && O.TradeType != "现金流交易").ToList(); if (tradeList.Count == 0) { + // 老风控没有可试算的标的时,老逻辑认为试算通过;这里仍需继续执行新风控,避免普通确认漏掉新风控检查。 //("当前交易不需要试算"); - return new QuotaTrial() { TrialStatus = QuotaTrialStatusEnum.Success }; + result.TrialStatus = QuotaTrialStatusEnum.Success; + result.TradeId = tradeObj.id; + result.TradeNumber = tradeObj.TradeNumber ?? ""; + result.ClientName = tradeObj.ClientName ?? ""; + result.FundCheckDetails = fundMsg ?? ""; + result.RiskWarningDetails = riskWarningMsg ?? ""; + result.AvailableForClient = availableMsg ?? ""; + result.TrialSource = trialSource; + result.OldRiskTrialStatus = result.TrialStatus; + result = ApplyNewRiskTrial(tradeId, result); + SaveQuotaTrial(result); + return result; } string quotaWarningMsg = "", quotaMsg = ""; bool quotaWarningStatus = true, quotaStatus = true; @@ -5243,8 +5294,42 @@ namespace YLErp.Modules.RiskModule result.AvailableForClient = availableMsg ?? ""; result.TrialSource = trialSource; result.OldRiskTrialStatus = result.TrialStatus; - _logger.Info($"[限额试算] 试算完成 - tradeId: {tradeId}, TrialStatus: {result.TrialStatus}, FundCheck: {fundStatus}, QuotaCheck: {quotaStatus}"); + // 保留老风控原始状态,随后叠加新风控结果;这样二次特批时可以区分是老风控失败还是新风控需审批。 + result = ApplyNewRiskTrial(tradeId, result); + SaveQuotaTrial(result); + return result; + } + /// + /// 新风控独立试算:不执行老风控资金/限额检查,只调用新风控引擎。 + /// 用于老风控特批后的确认流程,也可作为后续老风控下线后的主试算入口。 + /// + public QuotaTrial RunNewRiskTrial(int tradeId, int trialSource) + { + var result = new QuotaTrial + { + TradeId = tradeId, + TrialSource = trialSource, + TrialStatus = QuotaTrialStatusEnum.Success, + OldRiskTrialStatus = QuotaTrialStatusEnum.Success + }; + var tradeObj = DbContext.trade.AsNoTracking().Where(O => O.id == tradeId).FirstOrDefault(); + if (tradeObj != null) + { + result.TradeNumber = tradeObj.TradeNumber ?? ""; + result.ClientName = tradeObj.ClientName ?? ""; + } + result = ApplyNewRiskTrial(tradeId, result); + SaveQuotaTrial(result); + return result; + } + + /// + /// 将新风控引擎结果合并到 QuotaTrial。 + /// 如果传入的是完整试算结果,则是在老风控结果基础上叠加新风控;如果传入的是新建结果,则表示只跑新风控。 + /// + private QuotaTrial ApplyNewRiskTrial(int tradeId, QuotaTrial result) + { // ===== 新风控引擎接入点(设计文档 4.6.1) ===== try { @@ -5318,7 +5403,6 @@ namespace YLErp.Modules.RiskModule } // ===== 新风控引擎接入结束 ===== - SaveQuotaTrial(result); return result; } /// diff --git a/YLErpDAL/Modules/TradeModule/DealModule/TradeConfirmService.cs b/YLErpDAL/Modules/TradeModule/DealModule/TradeConfirmService.cs index 7bca1074..018cac68 100644 --- a/YLErpDAL/Modules/TradeModule/DealModule/TradeConfirmService.cs +++ b/YLErpDAL/Modules/TradeModule/DealModule/TradeConfirmService.cs @@ -203,26 +203,38 @@ namespace YLErp.Modules.TradeModule.DealModule result.ErrorMsg = $"未找到客户信息:{td.ClientName}"; return result; } - if (!ignoreMoneyCheck && !continueQuotaCheck) + if (!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, ignoreRiskRuleIds)) + if (ignoreMoneyCheck) { - return result; + // ignoreMoneyCheck 表示老风控资金/限额检查已被特批放行,只跳过老风控;新风控仍需独立执行。 + if (!trialService.CheckTradeConfirmNewRisk(ref result, ignoreRiskWarning, ignoreRiskRuleIds)) + { + return result; + } + } + else + { + // 正常确认流程:先执行原有交易确认规则和客户评级检查,再执行完整风控(老风控 + 新风控)。 + result = CheckTradeConfirmRule(result, td, isSkipApproval); + + if (!string.IsNullOrWhiteSpace(result.ErrorMsg)) + { + return result; + } + + result = CheckClientRating(result, client, td); + + if (!string.IsNullOrWhiteSpace(result.ErrorMsg)) + { + return result; + } + + if (!trialService.CheckTradeConfirmRisk(ref result, ignoreRiskWarning, ignoreRiskRuleIds)) + { + return result; + } } } var history = "确认交易"; diff --git a/YLErpWeb/Controllers/tradeController.cs b/YLErpWeb/Controllers/tradeController.cs index 0a162c3c..cf12800c 100644 --- a/YLErpWeb/Controllers/tradeController.cs +++ b/YLErpWeb/Controllers/tradeController.cs @@ -8428,7 +8428,7 @@ namespace YLErp.Web.Controllers /// public ActionResult quotaTrial(int id, int trialSource = 0) { - var quotaTrial = new QuotaMonitorService(CurUser).QuotaCheck(id, trialSource); + var quotaTrial = new QuotaMonitorService(CurUser).RunQuotaTrial(id, trialSource); return View(quotaTrial); }