diff --git a/Framework/YLErp.Core/DBModels/QuotaTrial.cs b/Framework/YLErp.Core/DBModels/QuotaTrial.cs
index 233f0d3a..c4e95df1 100644
--- a/Framework/YLErp.Core/DBModels/QuotaTrial.cs
+++ b/Framework/YLErp.Core/DBModels/QuotaTrial.cs
@@ -1,4 +1,5 @@
-using System.ComponentModel.DataAnnotations.Schema;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
using YLErp.Enums;
namespace YLErp.DBModels
@@ -62,5 +63,8 @@ namespace YLErp.DBModels
/// 1:确认成交时系统调用;
///
public int TrialSource { get; set; }
+
+ [NotMapped]
+ public List ApprovalRuleIds { get; set; } = new List();
}
}
diff --git a/Framework/YLErp.Core/Enums/QuotaMonitorEnums.cs b/Framework/YLErp.Core/Enums/QuotaMonitorEnums.cs
index 18980071..2a3cd093 100644
--- a/Framework/YLErp.Core/Enums/QuotaMonitorEnums.cs
+++ b/Framework/YLErp.Core/Enums/QuotaMonitorEnums.cs
@@ -1,4 +1,4 @@
-using YLErp.Models;
+using YLErp.Models;
namespace YLErp.Enums
{
@@ -143,6 +143,11 @@ namespace YLErp.Enums
[Description("预警")]
Warning,
///
+ /// 风控需审批
+ ///
+ [Description("风控需审批")]
+ RiskWarning,
+ ///
/// 不通过
///
[Description("不通过")]
diff --git a/YLErpDAL/Helpers/StringHelper.cs b/YLErpDAL/Helpers/StringHelper.cs
index 411c0c11..62140735 100644
--- a/YLErpDAL/Helpers/StringHelper.cs
+++ b/YLErpDAL/Helpers/StringHelper.cs
@@ -1,4 +1,4 @@
-namespace YLErp.Helpers
+namespace YLErp.Helpers
{
public static class StringHelper
{
@@ -49,5 +49,24 @@
var decExp = Math.Pow(10, decimalSize + 1);
return (Math.Floor(value.Value * decExp) / decExp).ToString("f" + decimalSize);
}
+
+ ///
+ /// 将逗号分隔的字符串转换为字符串数组,并去除空白项与重复项。
+ ///
+ /// 逗号分隔的字符串。
+ /// 去除空白项与重复项后的字符串数组。
+ public static string[] ConvertCommaValuesToStringArray(string commaValues)
+ {
+ if (string.IsNullOrWhiteSpace(commaValues))
+ {
+ return new string[0];
+ }
+
+ return commaValues.Split(',')
+ .Select(o => o?.Trim())
+ .Where(o => !string.IsNullOrWhiteSpace(o))
+ .Distinct()
+ .ToArray();
+ }
}
}
diff --git a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs b/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs
index 30296d9d..fea278d0 100644
--- a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs
+++ b/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs
@@ -161,18 +161,18 @@ namespace YLErp.Modules.RiskEngine
foreach (var rule in rules)
{
var ruleId = rule.Id.ToString();
- //已存在编译缓存的规则直接跳过,避免重复编译
- if (RuleCompiledCache.TryGet(ruleId, out _))
- {
- _logger.Info($"[风控引擎] 规则已存在编译缓存,跳过预编译 - RuleId: {rule.Id}");
- continue;
- }
//非活跃的rule不编译
if (rule.Status != RiskRuleStatus.Active)
{
_logger.Info($"[风控引擎] 规则非活跃,跳过预编译 - RuleId: {rule.Id}, Status: {rule.Status}");
continue;
}
+ //已存在编译缓存的规则直接跳过,避免重复编译
+ if (RuleCompiledCache.TryGet(ruleId, out _))
+ {
+ _logger.Info($"[风控引擎] 规则已存在编译缓存,跳过预编译 - RuleId: {rule.Id}");
+ continue;
+ }
var compileResult = RuleCompiler.ValidateAndCompileRule(rule);
if (!compileResult.Success)
diff --git a/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs b/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs
index 2d7a1215..189382a8 100644
--- a/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs
+++ b/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs
@@ -4335,7 +4335,7 @@ namespace YLErp.Modules.RiskModule
return QuotaCheck(ref res, false);
}
- public bool QuotaCheck(ref TradeOpenResult res, bool ignoreRiskWarning)
+ public bool QuotaCheck(ref TradeOpenResult res, bool ignoreRiskWarning, IEnumerable ignoreRiskRuleIds = null)
{
if (res == null || res.Trade == null)
{
@@ -4381,14 +4381,27 @@ namespace YLErp.Modules.RiskModule
detailParts.Add($"限额预警:{quotaObj.QuotaWarningDetails}");
res.ErrorMsg = string.Join("\n", detailParts);
//确认本次为需审批后,二次特批。因为只靠点击“交易特批”的ignoreRiskWarning,不能保证本次校验通过。
- var isRiskApprovalWarning = quotaObj.TrialStatus == QuotaTrialStatusEnum.Warning
- && !string.IsNullOrWhiteSpace(quotaObj.RiskWarningDetails);
- //需清除ErrorMsg,不然外部调用会认为失败
- //触发需审批,交易特批逻辑
- if (ignoreRiskWarning && isRiskApprovalWarning)
+ var isRiskApprovalWarning = quotaObj.TrialStatus == QuotaTrialStatusEnum.RiskWarning
+ && !string.IsNullOrWhiteSpace(quotaObj.RiskWarningDetails)
+ && (quotaObj.ApprovalRuleIds?.Any() ?? false);
+ //当前为老风控特批,不是新风控需审批
+ res.OldRiskNeedSpecialApproval = quotaObj.TrialStatus == QuotaTrialStatusEnum.Warning;
+ if (isRiskApprovalWarning)
{
- res.ErrorMsg = string.Empty;
- return true;
+ var currentApprovalRuleIds = quotaObj.ApprovalRuleIds ?? new List();
+ res.IgnoredRiskRuleIds = currentApprovalRuleIds;
+ //需清除ErrorMsg,不然外部调用会认为失败
+ //触发需审批,交易特批逻辑
+ if (ignoreRiskWarning)
+ {
+ var ignoredRuleIdSet = new HashSet((ignoreRiskRuleIds ?? Enumerable.Empty()).Where(o => !string.IsNullOrWhiteSpace(o)));
+ var newApprovalRuleIds = currentApprovalRuleIds.Where(o => !ignoredRuleIdSet.Contains(o)).ToList();
+ if (!newApprovalRuleIds.Any())
+ {
+ res.ErrorMsg = string.Empty;
+ return true;
+ }
+ }
}
res.RetCode = isRiskApprovalWarning ? TradeOpenRetCode.RiskWarning : TradeOpenRetCode.QuotaTrialError;
return false;
@@ -4959,7 +4972,6 @@ namespace YLErp.Modules.RiskModule
result.RiskWarningDetails = riskWarningMsg ?? "";
result.QuotaWarningDetails = quotaWarningMsg ?? "";
result.AvailableForClient = availableMsg ?? "";
- result.TrialSource = trialSource;
// ===== 新风控引擎接入点(设计文档 4.6.1) =====
try
@@ -4970,7 +4982,6 @@ namespace YLErp.Modules.RiskModule
TradeId = tradeId,
TriggerPoint = "BOOK_CONFIRM"
};
- tradeObj.StockEqvNotional = 10000000000;
riskContext.DataMap["trade"] = tradeObj;
var sameUnderlyingTotalNotional = DbContext.trade
.Where(t => t.ValidState != "InValid"
@@ -5010,12 +5021,25 @@ namespace YLErp.Modules.RiskModule
}
if (riskResult.NeedApproval)
{
- if (result.TrialStatus != QuotaTrialStatusEnum.Error)
+ if (result.TrialStatus != QuotaTrialStatusEnum.Error && result.TrialStatus != QuotaTrialStatusEnum.Warning)
{
- result.TrialStatus = QuotaTrialStatusEnum.Warning;
+ result.TrialStatus = QuotaTrialStatusEnum.RiskWarning;
}
result.RiskWarningDetails += "[风控引擎] 规则触发:需审批\n";
- foreach (var triggeredRule in riskResult.TriggeredRules.Where(r => r.ControlStrategy == RiskControlStrategy.Approval))
+ var approvalTriggeredRules = riskResult.TriggeredRules.Where(r => r.ControlStrategy == RiskControlStrategy.Approval).ToList();
+ var approvalTriggeredRuleIds = approvalTriggeredRules.Select(r => r.RuleId)
+ .Where(r => !string.IsNullOrWhiteSpace(r))
+ .ToList();
+ var showTipTriggeredRules = riskResult.TriggeredRules.Where(r => r.ControlStrategy == RiskControlStrategy.ShowTip).ToList();
+ var showTipTriggeredRuleIds = showTipTriggeredRules.Select(r => r.RuleId)
+ .Where(r => !string.IsNullOrWhiteSpace(r))
+ .ToList();
+ // 二次确认时,需审批规则允许按首次命中结果忽略;同时已展示过的提示规则也不再重复展示
+ var processedRiskRuleIds = approvalTriggeredRuleIds.Concat(showTipTriggeredRuleIds)
+ .Distinct()
+ .ToList();
+ result.ApprovalRuleIds = processedRiskRuleIds;
+ foreach (var triggeredRule in approvalTriggeredRules)
{
result.RiskWarningDetails += $"规则ID:{triggeredRule.RuleId};规则名称:{triggeredRule.RuleName};规则说明:{triggeredRule.RuleText}\n";
}
diff --git a/YLErpDAL/Modules/RiskModule/TradeRiskCheckLogService.cs b/YLErpDAL/Modules/RiskModule/TradeRiskCheckLogService.cs
index 53ac78c3..a5d508d8 100644
--- a/YLErpDAL/Modules/RiskModule/TradeRiskCheckLogService.cs
+++ b/YLErpDAL/Modules/RiskModule/TradeRiskCheckLogService.cs
@@ -97,6 +97,9 @@ namespace YLErp.Modules.RiskModule
case QuotaTrialStatusEnum.Warning:
exportModel.trial_result = "预警";
break;
+ case QuotaTrialStatusEnum.RiskWarning:
+ exportModel.trial_result = "风控需审批";
+ break;
case QuotaTrialStatusEnum.Error:
exportModel.trial_result = "限额";
break;
diff --git a/YLErpDAL/Modules/TradeModule/DealModule/TradeConfirmService.cs b/YLErpDAL/Modules/TradeModule/DealModule/TradeConfirmService.cs
index bedb5873..7bca1074 100644
--- a/YLErpDAL/Modules/TradeModule/DealModule/TradeConfirmService.cs
+++ b/YLErpDAL/Modules/TradeModule/DealModule/TradeConfirmService.cs
@@ -46,7 +46,7 @@ namespace YLErp.Modules.TradeModule.DealModule
///
/// 2021-02-04改造:批量确认因为耗时太长,用户多页面(交易列表,今日交易)并发确认导致生成多条期权费资金记录
///
- public TradeConfirmResultModel tradeConfirm(IEnumerable tradeids, bool ignoreMoneyCheck = false, bool isSkipApproval = false, bool ignoreRiskWarning = false)
+ public TradeConfirmResultModel tradeConfirm(IEnumerable tradeids, bool ignoreMoneyCheck = false, bool isSkipApproval = false, bool ignoreRiskWarning = false, IEnumerable ignoreRiskRuleIds = null)
{
if (tradeids is null || !tradeids.Any(n => n > 0))
{
@@ -101,7 +101,8 @@ namespace YLErp.Modules.TradeModule.DealModule
confirmedTradeIds: result.confirmedTradeIds,
changeConfirmPaths: result.changeConfirmPaths,
continueQuotaCheck: idCache.Contains(pid),
- ignoreRiskWarning: ignoreRiskWarning);
+ ignoreRiskWarning: ignoreRiskWarning,
+ ignoreRiskRuleIds: ignoreRiskRuleIds);
if (pid > 0)
{
idCache.Add(pid);
@@ -120,8 +121,10 @@ namespace YLErp.Modules.TradeModule.DealModule
if (temp.RetCode == TradeOpenRetCode.QuotaTrialError || temp.RetCode == TradeOpenRetCode.FundStatus || temp.RetCode == TradeOpenRetCode.RiskWarning)
{
result.LackOfMoney = true;
+ result.OldRiskNeedSpecialApproval = temp.OldRiskNeedSpecialApproval;
result.TrialDataId = temp.TrialDataId;
result.TradeId = temp.Trade.id;
+ result.ignoreRiskRuleIds = temp.IgnoredRiskRuleIds;
sbError.AppendLine(temp.ErrorMsg);
break;
}
@@ -162,7 +165,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 ignoreRiskWarning = false)
+ bool ignoreMoneyCheck, bool isSkipApproval, List confirmedTradeIds, List changeConfirmPaths, bool continueQuotaCheck = false, bool ignoreRiskWarning = false, IEnumerable ignoreRiskRuleIds = null)
{
var td = DbContext.trade.FirstOrDefault(t => t.id == tid);
var result = new TradeOpenResult(td ?? new trade());
@@ -217,7 +220,7 @@ namespace YLErp.Modules.TradeModule.DealModule
}
var trialService = new QuotaMonitorService(this);
- if (!trialService.QuotaCheck(ref result, ignoreRiskWarning))
+ if (!trialService.QuotaCheck(ref result, ignoreRiskWarning, ignoreRiskRuleIds))
{
return result;
}
@@ -618,6 +621,7 @@ namespace YLErp.Modules.TradeModule.DealModule
public class TradeConfirmResultModel
{
public bool LackOfMoney;
+ public bool OldRiskNeedSpecialApproval;
public int TrialDataId;
public int TradeId;
public List confirmedTradeIds;
@@ -625,5 +629,6 @@ namespace YLErp.Modules.TradeModule.DealModule
public string errorMsg;
public string tipMsg;
public string type;
+ public List ignoreRiskRuleIds;
}
}
diff --git a/YLErpDAL/Modules/TradeModule/DealModule/TradeOpenService.cs b/YLErpDAL/Modules/TradeModule/DealModule/TradeOpenService.cs
index 448967ec..0915baff 100644
--- a/YLErpDAL/Modules/TradeModule/DealModule/TradeOpenService.cs
+++ b/YLErpDAL/Modules/TradeModule/DealModule/TradeOpenService.cs
@@ -787,6 +787,8 @@ namespace YLErp.Modules.TradeModule.DealModule
public TradeOpenRetCode RetCode;
public int TrialDataId;
+ public List IgnoredRiskRuleIds;
+ public bool OldRiskNeedSpecialApproval;
///
/// 错误信息
diff --git a/YLErpWeb/Controllers/tradeController.cs b/YLErpWeb/Controllers/tradeController.cs
index 06e7dae4..98a3d539 100644
--- a/YLErpWeb/Controllers/tradeController.cs
+++ b/YLErpWeb/Controllers/tradeController.cs
@@ -2447,7 +2447,7 @@ namespace YLErp.Web.Controllers
}
- public JsonResult tradeConfirm(string tradeids, string additionalProcessing, bool isSkipCheck = false)
+ public JsonResult tradeConfirm(string tradeids, string additionalProcessing, string ignoreRiskRuleIds, bool isSkipCheck = false)
{
var tradeidArr = DataConvert.ConvertCommaValuesToInt32Array(tradeids);
if (tradeids is null || !tradeids.Any(n => n > 0))
@@ -2457,32 +2457,37 @@ 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 ignoreRiskRuleIdArr = StringHelper.ConvertCommaValuesToStringArray(ignoreRiskRuleIds);
var isSkipApproval = false;
if (CurUser.交易管理_特批批量确认 && isSkipCheck)
{
ignoreMoneyCheck = true;
isSkipApproval = true;
}
- var result = new TradeConfirmService(CurUser).tradeConfirm(tradeidArr, ignoreMoneyCheck, isSkipApproval, ignoreRiskWarning);
+ var result = new TradeConfirmService(CurUser).tradeConfirm(tradeidArr, ignoreMoneyCheck, isSkipApproval, ignoreRiskWarning, ignoreRiskRuleIdArr);
//如果客户缺少资金而操作者有交易特批权限
if (!ignoreMoneyCheck && !ignoreRiskWarning && result.LackOfMoney)
{
if (result.type == TradeOpenRetCode.RiskWarning.ToString())
{
- return JsonSuccessData(new { proccessType = "AdditionalProcessing", type = tradeBLL.RiskWarningConfirm, TrialDataId = result.TrialDataId, message = result.errorMsg, typecode = result.type });
+ return JsonSuccessData(new { proccessType = "AdditionalProcessing", source = "RiskWarning", type = tradeBLL.RiskWarningConfirm, TrialDataId = result.TrialDataId, message = result.errorMsg, typecode = result.type, ignoreRiskRuleIds = result.ignoreRiskRuleIds });
}
if (result.type == TradeOpenRetCode.FundStatus.ToString())
{
- return JsonSuccessData(new { proccessType = "AdditionalProcessing", type = "", TrialDataId = result.TrialDataId, message = result.errorMsg, typecode = result.type });
+ return JsonSuccessData(new { proccessType = "AdditionalProcessing", source = "FundStatus", 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 });
+ return JsonSuccessData(new { proccessType = "AdditionalProcessing", source = "LackOfMoney", type = tradeBLL.LackOfMoney, TrialDataId = result.TrialDataId, message = result.errorMsg, typecode = result.type });
}
- //限额试算不通过(老风控,新风控检查失败),展示详情但不允许审批
+ //限额试算不通过时,延用master老逻辑:老风控走AdditionalProcessing并允许前端展示“交易特批”;新风控检查失败仍只展示详情不允许审批
if (result.type == TradeOpenRetCode.QuotaTrialError.ToString())
{
+ if (result.OldRiskNeedSpecialApproval)
+ {
+ return JsonSuccessData(new { proccessType = "AdditionalProcessing", source = "OldRiskQuotaTrialError", type = tradeBLL.LackOfMoney, TrialDataId = result.TrialDataId, message = result.errorMsg, typecode = result.type });
+ }
return JsonSuccessData(new { proccessType = "QuotaTrialError", TrialDataId = result.TrialDataId, message = result.errorMsg, typecode = result.type });
}
}
diff --git a/YLErpWeb/Views/trade/quotaTrial.cshtml b/YLErpWeb/Views/trade/quotaTrial.cshtml
index 7751409d..b04ecd25 100644
--- a/YLErpWeb/Views/trade/quotaTrial.cshtml
+++ b/YLErpWeb/Views/trade/quotaTrial.cshtml
@@ -101,6 +101,9 @@
case YLErp.Enums.QuotaTrialStatusEnum.Warning:
预警
break;
+ case YLErp.Enums.QuotaTrialStatusEnum.RiskWarning:
+ 风控需审批
+ break;
case YLErp.Enums.QuotaTrialStatusEnum.Error:
不通过
break;
diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js
index ad78eeb4..2f6eb85f 100644
--- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js
+++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js
@@ -49,11 +49,14 @@ function showSwapRate(timeRate, isMarginLeg) {
$("#swapIntervalModal").modal("show");
}
var layerIndex = -1;
-var confirmFunc = function (id, additionalProcessing) {
+var confirmFunc = function (id, additionalProcessing, ignoreRiskRuleIds) {
var pData = { tradeids: id };
if (!main.isEmpty(additionalProcessing)) {
pData.additionalProcessing = additionalProcessing;
}
+ if (ignoreRiskRuleIds && ignoreRiskRuleIds.length > 0) {
+ pData.ignoreRiskRuleIds = ignoreRiskRuleIds.join(',');
+ }
main.post("/trade/tradeConfirm", pData).done(function (data) {
if (!data.success) {
main.message(data.msg);
@@ -90,8 +93,16 @@ var confirmFunc = function (id, additionalProcessing) {
}
if (data.obj && data.obj.proccessType === "AdditionalProcessing") {
if (data.obj.TrialDataId) {
- var isRiskWarningConfirm = data.obj.type === "RiskWarningConfirm";
+ // 交易特批来源,优先使用后端新增的 source 字段区分老风控/新风控,同时兼容旧返回结构。
+ var additionalProcessingSource = data.obj.source || "";
+ // 新风控需审批场景:source=RiskWarning 或 type=RiskWarningConfirm,二次确认时需要继续透传 ignoreRiskRuleIds。
+ var isRiskWarningConfirm = data.obj.type === "RiskWarningConfirm" || additionalProcessingSource === "RiskWarning";
+ // 老风控兼容特批场景:source=OldRiskQuotaTrialError 或 type=LackOfMoney,保持 master 原有“交易特批”按钮逻辑。
+ var isOldRiskSpecialApproval = additionalProcessingSource === "OldRiskQuotaTrialError" || data.obj.type === "LackOfMoney";
+ // 新风控特批走 RiskWarningConfirm,老风控特批继续走 LackOfMoney,兼容当前 confirmFunc 的入参约定。
var additionalProcessingType = isRiskWarningConfirm ? "RiskWarningConfirm" : "LackOfMoney";
+ // 新风控需审批时由后端返回本次命中的审批规则 id,前端二次确认时原样透传。
+ var currentIgnoreRiskRuleIds = data.obj.ignoreRiskRuleIds || [];
var saveQuotaTrial = function (obj) {
var saveSuccess = true;
if (obj.Data.Remark && obj.Data.Remark.length > 0) {
@@ -135,8 +146,11 @@ var confirmFunc = function (id, additionalProcessing) {
return;
}
}
+ if (isOldRiskSpecialApproval) {
+ obj.Data.Remark = (obj.Data.Remark || "").trim();
+ }
layer.close(layerIndex);
- confirmFunc(id, additionalProcessingType);
+ confirmFunc(id, additionalProcessingType, currentIgnoreRiskRuleIds);
},
cancel: function () {
var iframeWindow = window["layui-layer-iframe" + this.index];