diff --git a/Framework/YLErp.Core/DBModels/Enums/RiskControlStrategy.cs b/Framework/YLErp.Core/DBModels/Enums/RiskControlStrategy.cs index 1fd37054..c8d8ce8a 100644 --- a/Framework/YLErp.Core/DBModels/Enums/RiskControlStrategy.cs +++ b/Framework/YLErp.Core/DBModels/Enums/RiskControlStrategy.cs @@ -4,6 +4,6 @@ namespace YLErp.DBModels { Block = 1, Approval = 2, - Warning = 3 + ShowTip = 3 } } diff --git a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs b/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs index 4f39bfac..b357f444 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs @@ -294,13 +294,13 @@ namespace YLErp.Modules.RiskEngine }); break; - case RiskControlStrategy.Warning: - result.Warnings.Add($"规则[{rule.RuleName}]触发:提示 - {rule.RuleText}"); + case RiskControlStrategy.ShowTip: + result.ShowTip = true; result.TriggeredRules.Add(new TriggeredRuleInfo { RuleId = ruleId, RuleName = rule.RuleName, - ControlStrategy = RiskControlStrategy.Warning, + ControlStrategy = RiskControlStrategy.ShowTip, RuleText = rule.RuleText, Message = $"规则[{rule.RuleName}]触发:提示" }); diff --git a/YLErpDAL/Modules/RiskEngine/RiskResult.cs b/YLErpDAL/Modules/RiskEngine/RiskResult.cs index 72792f97..69edbd57 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskResult.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskResult.cs @@ -22,25 +22,15 @@ namespace YLErp.Modules.RiskEngine /// public bool NeedApproval { get; set; } - ///// - ///// 是否存在执行异常 - ///// - //public bool HasError { get; set; } - - ///// - ///// 详细错误信息 - ///// - //public string ErrorMessage { get; set; } + /// + /// 是否存在提示 + /// + public bool ShowTip { get; set; } /// /// 触发的规则列表 /// public List TriggeredRules { get; set; } = new List(); - - /// - /// 提示类警告信息 - /// - public List Warnings { get; set; } = new List(); } /// diff --git a/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs b/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs index c49b46f0..27f4a175 100644 --- a/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs +++ b/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs @@ -4332,7 +4332,12 @@ namespace YLErp.Modules.RiskModule public bool QuotaCheck(ref TradeOpenResult res) { - if (res == null && res.Trade == null) + return QuotaCheck(ref res, false); + } + + public bool QuotaCheck(ref TradeOpenResult res, bool ignoreRiskWarning) + { + if (res == null || res.Trade == null) { throw new ArgumentNullException(nameof(res)); } @@ -4349,6 +4354,12 @@ namespace YLErp.Modules.RiskModule res.TrialDataId = quotaObj.id; return false; } + //成功状态下,如果触发TIp检查,提示用户 + if (!string.IsNullOrWhiteSpace(quotaObj.RiskWarningDetails)) + { + res.TrialDataId = quotaObj.id; + res.TipMsg = quotaObj.RiskWarningDetails; + } return true; } var lastTrial = trialService.QueryLastQuotaTrial(tradeId, true); @@ -4357,8 +4368,17 @@ namespace YLErp.Modules.RiskModule new TradeRiskCheckLogService(UserInfo).AddLog(quotaObj); } //否则的情况是上次没算,这次是预警,或上次算了,结果是不通过\通过或预警,这次是预警或不通过,提示用户; - res.RetCode = TradeOpenRetCode.QuotaTrialError; res.TrialDataId = quotaObj.id; + res.ErrorMsg = quotaObj.RiskWarningDetails; + var isRiskApprovalWarning = quotaObj.TrialStatus == QuotaTrialStatusEnum.Warning + && !string.IsNullOrWhiteSpace(quotaObj.RiskWarningDetails); + //需清除ErrorMsg,不然外部调用会认为失败 + if (ignoreRiskWarning && isRiskApprovalWarning) + { + res.ErrorMsg = string.Empty; + return true; + } + res.RetCode = isRiskApprovalWarning ? TradeOpenRetCode.RiskWarning : TradeOpenRetCode.QuotaTrialError; return false; } @@ -4947,7 +4967,7 @@ namespace YLErp.Modules.RiskModule riskContext.DataMap["trade"] = tradeObj; var riskResult = riskEngine.EvaluateRisk(riskContext, "BOOK_CONFIRM"); - _logger.Info($"[风控引擎] 簿记交易确认 - TradeId: {tradeId}, Passed: {riskResult.Passed}, Blocked: {riskResult.Blocked}, NeedApproval: {riskResult.NeedApproval}"); + _logger.Info($"[风控引擎] 簿记交易确认 - TradeId: {tradeId}, Passed: {riskResult.Passed}, Blocked: {riskResult.Blocked}, NeedApproval: {riskResult.NeedApproval}, ShowTip: {riskResult.ShowTip}"); // 按策略映射回 QuotaTrialStatusEnum if (riskResult.Blocked) @@ -4961,16 +4981,27 @@ namespace YLErp.Modules.RiskModule } if (riskResult.NeedApproval) { - result.TrialStatus = QuotaTrialStatusEnum.Warning; + if (result.TrialStatus != QuotaTrialStatusEnum.Error) + { + result.TrialStatus = QuotaTrialStatusEnum.Warning; + } result.RiskWarningDetails += "[风控引擎] 规则触发:需审批\n"; foreach (var triggeredRule in riskResult.TriggeredRules.Where(r => r.ControlStrategy == RiskControlStrategy.Approval)) { result.RiskWarningDetails += $"规则ID:{triggeredRule.RuleId};规则名称:{triggeredRule.RuleName};规则说明:{triggeredRule.RuleText}\n"; } } - if (riskResult.Warnings != null && riskResult.Warnings.Count > 0) + if (riskResult.ShowTip) { - result.RiskWarningDetails += "[风控引擎] 提示:" + string.Join(";", riskResult.Warnings) + "\n"; + var warningMessages = riskResult.TriggeredRules + .Where(r => r.ControlStrategy == RiskControlStrategy.ShowTip) + .Select(r => r.Message) + .Where(r => !string.IsNullOrWhiteSpace(r)) + .ToList(); + if (warningMessages.Count > 0) + { + result.RiskWarningDetails += "[风控引擎] 提示:" + string.Join(";", warningMessages) + "\n"; + } } } catch (Exception ex) diff --git a/YLErpDAL/Modules/RiskModule/TradeRiskCheckLogService.cs b/YLErpDAL/Modules/RiskModule/TradeRiskCheckLogService.cs index 6055e15c..53ac78c3 100644 --- a/YLErpDAL/Modules/RiskModule/TradeRiskCheckLogService.cs +++ b/YLErpDAL/Modules/RiskModule/TradeRiskCheckLogService.cs @@ -1,4 +1,4 @@ -using BaseOUDAL; +using BaseOUDAL; using MoreLinq; using OfficeOpenXml; using Snowflake.Core; @@ -146,6 +146,39 @@ namespace YLErp.Modules.RiskModule DbContext.SaveChanges(); } + public void AddWarningDecisionLog(int quotaTrialId, string decision) + { + var quotaTrial = DbContext.quotaTrial.FirstOrDefault(x => x.id == quotaTrialId); + if (quotaTrial == null) + { + return; + } + var td = DbContext.trade.FirstOrDefault(x => x.id == quotaTrial.TradeId); + trade_risk_check_log log = new trade_risk_check_log(); + if (td != null) + { + var client = DataCacheProvider.GetClientDataSource().GetData(td.ClientId); + log.client_number = client?.Number; + } + var worker = new IdWorker(1, 1); + log.id = worker.NextId(); + log.trial_result = (int)quotaTrial.TrialStatus; + log.client_name = quotaTrial.ClientName; + log.trader = td?.TraderName; + log.trade_number = quotaTrial.TradeNumber; + log.risk_warning = string.IsNullOrWhiteSpace(quotaTrial.RiskWarningDetails) + ? $"[风控预警处理]{decision}" + : quotaTrial.RiskWarningDetails + Environment.NewLine + $"[风控预警处理]{decision}"; + log.limit_warning = quotaTrial.QuotaCheckDetails; + log.remark = string.IsNullOrWhiteSpace(quotaTrial.Remark) + ? $"风控预警处理结果:{decision}" + : quotaTrial.Remark + ";风控预警处理结果:" + decision; + log.create_user = UserId; + log.create_time = DateTime.Now; + DbContext.trade_risk_check_log.Add(log); + DbContext.SaveChanges(); + } + /// /// 回退重置 /// diff --git a/YLErpDAL/Modules/TradeModule/DealModule/TradeConfirmService.cs b/YLErpDAL/Modules/TradeModule/DealModule/TradeConfirmService.cs index f6f74962..4f427cf4 100644 --- a/YLErpDAL/Modules/TradeModule/DealModule/TradeConfirmService.cs +++ b/YLErpDAL/Modules/TradeModule/DealModule/TradeConfirmService.cs @@ -1,4 +1,4 @@ -using BaseOUDAL; +using BaseOUDAL; using DocumentFormat.OpenXml.Office2010.Excel; using OfficeOpenXml.Drawing; using Org.BouncyCastle.Ocsp; @@ -46,7 +46,7 @@ namespace YLErp.Modules.TradeModule.DealModule /// /// 2021-02-04改造:批量确认因为耗时太长,用户多页面(交易列表,今日交易)并发确认导致生成多条期权费资金记录 /// - public TradeConfirmResultModel tradeConfirm(IEnumerable tradeids, bool ignoreMoneyCheck = false, bool isSkipApproval = false) + public TradeConfirmResultModel tradeConfirm(IEnumerable tradeids, bool ignoreMoneyCheck = false, bool isSkipApproval = false, bool ignoreRiskWarning = false) { if (tradeids is null || !tradeids.Any(n => n > 0)) { @@ -76,6 +76,7 @@ namespace YLErp.Modules.TradeModule.DealModule var hasTradeProcess = HasTradeProcess(); var sbError = new StringBuilder(); + var sbTip = new StringBuilder(); var result = new TradeConfirmResultModel { confirmedTradeIds = new List(), @@ -99,7 +100,8 @@ namespace YLErp.Modules.TradeModule.DealModule isSkipApproval: isSkipApproval, confirmedTradeIds: result.confirmedTradeIds, changeConfirmPaths: result.changeConfirmPaths, - continueQuotaCheck: idCache.Contains(pid)); + continueQuotaCheck: idCache.Contains(pid), + ignoreRiskWarning: ignoreRiskWarning); if (pid > 0) { idCache.Add(pid); @@ -114,7 +116,8 @@ namespace YLErp.Modules.TradeModule.DealModule result.LackOfMoney = true; break; } - if (temp.RetCode == TradeOpenRetCode.QuotaTrialError || temp.RetCode == TradeOpenRetCode.FundStatus) + //此处LackOfMoney仅仅代表需要前端二次处理,不代表资金缺乏,延用老逻辑 + if (temp.RetCode == TradeOpenRetCode.QuotaTrialError || temp.RetCode == TradeOpenRetCode.FundStatus || temp.RetCode == TradeOpenRetCode.RiskWarning) { result.LackOfMoney = true; result.TrialDataId = temp.TrialDataId; @@ -128,6 +131,11 @@ namespace YLErp.Modules.TradeModule.DealModule sbError.AppendLine(temp.ErrorMsg); } + if (!string.IsNullOrEmpty(temp.TipMsg)) + { + sbTip.AppendLine(temp.TipMsg); + } + Helpers.LockHelper.Release(lockKeyId); } } @@ -135,6 +143,7 @@ namespace YLErp.Modules.TradeModule.DealModule DbContext.SaveChanges(); result.errorMsg = sbError.ToString(); + result.tipMsg = sbTip.ToString(); return result; } @@ -152,7 +161,7 @@ namespace YLErp.Modules.TradeModule.DealModule /// /// private TradeOpenResult ConfirmTrade(int tid, bool isBatch, bool hasTradeProcess, - bool ignoreMoneyCheck, bool isSkipApproval, List confirmedTradeIds, List changeConfirmPaths, bool continueQuotaCheck = false) + 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()); @@ -207,7 +216,7 @@ namespace YLErp.Modules.TradeModule.DealModule } var trialService = new QuotaMonitorService(this); - if (!trialService.QuotaCheck(ref result)) + if (!trialService.QuotaCheck(ref result, ignoreRiskWarning)) { return result; } @@ -223,6 +232,10 @@ namespace YLErp.Modules.TradeModule.DealModule history = "特权账户批量确认自动" + history; } } + else if (ignoreRiskWarning) + { + history = "风控预警确认-" + history; + } //进入交易审批流程 if (hasTradeProcess && !isSkipApproval) { @@ -609,6 +622,7 @@ namespace YLErp.Modules.TradeModule.DealModule public List confirmedTradeIds; public List changeConfirmPaths; public string errorMsg; + public string tipMsg; public string type; } } diff --git a/YLErpDAL/Modules/TradeModule/DealModule/TradeOpenService.cs b/YLErpDAL/Modules/TradeModule/DealModule/TradeOpenService.cs index 0e354bf0..448967ec 100644 --- a/YLErpDAL/Modules/TradeModule/DealModule/TradeOpenService.cs +++ b/YLErpDAL/Modules/TradeModule/DealModule/TradeOpenService.cs @@ -1,4 +1,4 @@ -using BaseOUDAL; +using BaseOUDAL; using YLErp.BLL; using YLErp.BLL.Eod; using YLErp.Model; @@ -792,6 +792,10 @@ namespace YLErp.Modules.TradeModule.DealModule /// 错误信息 /// public string ErrorMsg; + /// + /// 提示信息 + /// + public string TipMsg; /// /// 变更确认书路径 @@ -831,6 +835,10 @@ namespace YLErp.Modules.TradeModule.DealModule /// FundStatus, /// + /// 风控预警二次确认 + /// + RiskWarning, + /// /// 无审核权限 /// NoRight diff --git a/YLErpDAL/Modules/TradeModule/TradeBLL.cs b/YLErpDAL/Modules/TradeModule/TradeBLL.cs index 5571b14d..993be3a9 100644 --- a/YLErpDAL/Modules/TradeModule/TradeBLL.cs +++ b/YLErpDAL/Modules/TradeModule/TradeBLL.cs @@ -1,4 +1,4 @@ -using BaseOUDAL; +using BaseOUDAL; using Org.BouncyCastle.Ocsp; using System; using System.Drawing; @@ -25,6 +25,8 @@ namespace YLErp.BLL /// public const string LackOfMoney = "LackOfMoney"; + public const string RiskWarningConfirm = "RiskWarningConfirm"; + public const string IgnoreBreak = "IgnoreBreak"; /// diff --git a/YLErpWeb/Controllers/tradeController.cs b/YLErpWeb/Controllers/tradeController.cs index 428a4c66..d51e2550 100644 --- a/YLErpWeb/Controllers/tradeController.cs +++ b/YLErpWeb/Controllers/tradeController.cs @@ -2456,18 +2456,30 @@ namespace YLErp.Web.Controllers } var config = valuedateBLL.SystemDate; var ignoreMoneyCheck = tradeidArr.Count() == 1 && additionalProcessing == tradeBLL.LackOfMoney; + var ignoreRiskWarning = tradeidArr.Count() == 1 && additionalProcessing == tradeBLL.RiskWarningConfirm; var isSkipApproval = false; if (CurUser.交易管理_特批批量确认 && isSkipCheck) { ignoreMoneyCheck = true; isSkipApproval = true; } - var result = new TradeConfirmService(CurUser).tradeConfirm(tradeidArr, ignoreMoneyCheck, isSkipApproval); + var result = new TradeConfirmService(CurUser).tradeConfirm(tradeidArr, ignoreMoneyCheck, isSkipApproval, ignoreRiskWarning); //如果客户缺少资金而操作者有交易特批权限 - if (!ignoreMoneyCheck && result.LackOfMoney) + if (!ignoreMoneyCheck && !ignoreRiskWarning && result.LackOfMoney) { - return JsonSuccessData(new { proccessType = "AdditionalProcessing", type = PS.Config.ErpElement.Company == Configuration.CompanyEnum.天风 || config.SpecialOperateForTrade == 1 ? tradeBLL.LackOfMoney : "", TrialDataId = result.TrialDataId, message = result.errorMsg, typecode = result.type }); + if (result.type == TradeOpenRetCode.RiskWarning.ToString()) + { + return JsonSuccessData(new { proccessType = "AdditionalProcessing", type = tradeBLL.RiskWarningConfirm, TrialDataId = result.TrialDataId, message = result.errorMsg, typecode = result.type }); + } + if (result.type == TradeOpenRetCode.FundStatus.ToString()) + { + return JsonSuccessData(new { proccessType = "AdditionalProcessing", type = "", TrialDataId = result.TrialDataId, message = result.errorMsg, typecode = result.type }); + } + if (result.type == TradeOpenRetCode.LackOfMoney.ToString() || ((PS.Config.ErpElement.Company == Configuration.CompanyEnum.天风 || config.SpecialOperateForTrade == 1) && string.IsNullOrWhiteSpace(result.type))) + { + return JsonSuccessData(new { proccessType = "AdditionalProcessing", type = tradeBLL.LackOfMoney, TrialDataId = result.TrialDataId, message = result.errorMsg, typecode = result.type }); + } } //生成交易确认书 @@ -2475,7 +2487,7 @@ namespace YLErp.Web.Controllers { new TradeContractGenerateService(CurUser).GenerateContractsAsync(result.confirmedTradeIds, "确认书"); } - + //有ErrorMsg则认为失败 if (!string.IsNullOrEmpty(result.errorMsg)) { if (result.changeConfirmPaths?.Count > 0) @@ -2486,11 +2498,12 @@ namespace YLErp.Web.Controllers } else { + var successMsg = string.IsNullOrWhiteSpace(result.tipMsg) ? "操作完成" : result.tipMsg; if (result.changeConfirmPaths?.Count > 0) { - return JsonSuccess("操作完成", new { generateChangeSuccess = true, url = result.changeConfirmPaths }); + return JsonSuccess(successMsg, new { generateChangeSuccess = true, url = result.changeConfirmPaths }); } - return JsonSuccess("操作完成"); + return JsonSuccess(successMsg); } } @@ -8396,6 +8409,20 @@ namespace YLErp.Web.Controllers return JsonSuccess(); } + public JsonResult SaveRiskWarningDecisionLog(int quotaTrialId, string decision) + { + try + { + new TradeRiskCheckLogService(CurUser).AddWarningDecisionLog(quotaTrialId, decision); + } + catch (Exception ex) + { + LogFactory.GetLogger("SaveRiskWarningDecisionLog").Error(ex); + return JsonError("保存风控预警处理结果失败!"); + } + return JsonSuccess(); + } + [MyAuthorize("风险控制-接口审批列表")] public ActionResult InterfaceApproval() { diff --git a/YLErpWeb/wwwroot/Scripts/app/trade/tradeview.js b/YLErpWeb/wwwroot/Scripts/app/trade/tradeview.js index f95b5078..c1943d3b 100644 --- a/YLErpWeb/wwwroot/Scripts/app/trade/tradeview.js +++ b/YLErpWeb/wwwroot/Scripts/app/trade/tradeview.js @@ -1,4 +1,4 @@ -const inputFormatDouble2 = Object.freeze({ precision: 2, append: '' }); +const inputFormatDouble2 = Object.freeze({ precision: 2, append: '' }); function deletetrade(id) { //无效化 main.confirm(page.ConfirmInfo, function () { @@ -85,6 +85,34 @@ var confirmFunc = function (id, additionalProcessing) { } if (data.obj && data.obj.proccessType === "AdditionalProcessing") { if (data.obj.TrialDataId) { + var isRiskWarningConfirm = data.obj.type === "RiskWarningConfirm"; + var additionalProcessingType = isRiskWarningConfirm ? "RiskWarningConfirm" : "LackOfMoney"; + var saveQuotaTrial = function (obj) { + var saveSuccess = true; + if (obj.IsTF && (!obj.Data.Remark || obj.Data.Remark.length <= 0)) { + main.message("必须填写说明内容,才可以录入交易"); + return false; + } + if (obj.Data.Remark && obj.Data.Remark.length > 0) { + main.post("/trade/SaveQuotaTrial", obj.Data, { async: false }).done(function (res) { + if (!res || !res.success) { + saveSuccess = false; + main.message(res.msg); + } + }); + } + return saveSuccess; + }; + var saveRiskWarningDecisionLog = function (quotaTrialId, decision) { + var saveSuccess = true; + main.post("/trade/SaveRiskWarningDecisionLog", { quotaTrialId: quotaTrialId, decision: decision }, { async: false }).done(function (res) { + if (!res || !res.success) { + saveSuccess = false; + main.message(res.msg); + } + }); + return saveSuccess; + }; var layerSetting = { type: 2, title: "提示", @@ -94,31 +122,23 @@ var confirmFunc = function (id, additionalProcessing) { content: "/trade/showQuotaTrial?id=" + data.obj.TrialDataId, yes: function (index) { var obj = window["layui-layer-iframe" + index].page; - if (obj.IsTF) { - if (obj.Data.Remark && obj.Data.Remark.length > 0) { - main.post("/trade/SaveQuotaTrial", obj.Data, { async: false }).done(function (res) { - if (!res || !res.success) { - main.message(res.msg); - } - }); - layer.close(layerIndex); - confirmFunc(id, "LackOfMoney"); + if (!saveQuotaTrial(obj)) { + return; + } + if (isRiskWarningConfirm) { + if (!saveRiskWarningDecisionLog(obj.Data.id, "确认通过")) { return; } - main.message("必须填写说明内容,才可以录入交易"); - } else { - if (obj.Data.Remark && obj.Data.Remark.length > 0) { - main.post("/trade/SaveQuotaTrial", obj.Data, { async: false }).done(function (res) { - if (!res || !res.success) { - main.message(res.msg); - } - }); - } - layer.close(layerIndex); - confirmFunc(id, "LackOfMoney"); } + layer.close(layerIndex); + confirmFunc(id, additionalProcessingType); }, cancel: function () { + var iframeWindow = window["layui-layer-iframe" + this.index]; + var pageObj = iframeWindow && iframeWindow.page; + if (isRiskWarningConfirm && pageObj && pageObj.Data && pageObj.Data.id) { + saveRiskWarningDecisionLog(pageObj.Data.id, "取消不通过"); + } if (window.parent && window.parent.reloadtrade) { window.parent.reloadtrade(); } @@ -128,8 +148,10 @@ var confirmFunc = function (id, additionalProcessing) { if (data.obj.type === "LackOfMoney") { layerSetting.btn = [page.buttonStr, '取消']; } - // 只展示“资金状况”的情况 - if (data.obj.typecode === "FundStatus") { + else if (isRiskWarningConfirm) { + layerSetting.btn = ["交易特批", '取消']; + } + else if (data.obj.typecode === "FundStatus") { layerSetting.btn = ["确认", '取消']; } @@ -167,6 +189,14 @@ var confirmFunc = function (id, additionalProcessing) { }); }); } + if (data.msg && data.msg !== "操作完成") { + main.alert(data.msg, function () { + if (blClose) { + closetradeWindow(); + } + }); + return; + } if (blClose) { closetradeWindow(); }