diff --git a/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs b/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs index 0b05ceee..41b62b1b 100644 --- a/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs +++ b/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs @@ -4929,6 +4929,22 @@ namespace YLErp.Modules.RiskModule return result; } + /// + /// 按 quotaTrial.id 校验试算结果是否已超过配置时效 + /// + /// quotaTrial.id + /// 有效时长(秒) + /// 查不到记录或已超时返回 true,否则返回 false + public bool IsQuotaTrialExpired(int trialDataId, int expireSeconds) + { + var quotaTrial = QueryQuotaTrial(trialDataId); + if (quotaTrial == null || !quotaTrial.OptDate.HasValue) + { + return true; + } + return DateTime.Now - quotaTrial.OptDate.Value > TimeSpan.FromSeconds(expireSeconds); + } + /// /// 查询试算历史 /// diff --git a/YLErpDAL/Modules/RiskModule/TradeRiskCheckLogService.cs b/YLErpDAL/Modules/RiskModule/TradeRiskCheckLogService.cs index a5d508d8..96d6303e 100644 --- a/YLErpDAL/Modules/RiskModule/TradeRiskCheckLogService.cs +++ b/YLErpDAL/Modules/RiskModule/TradeRiskCheckLogService.cs @@ -149,6 +149,26 @@ namespace YLErp.Modules.RiskModule DbContext.SaveChanges(); } + /// + /// 查询指定交易最近一次新风控“确认通过”的处理时间,用于控制二次审批放行仅在短时间内有效。 + /// + public DateTime? QueryLatestRiskWarningDecisionTime(int tradeId) + { + var tradeNumber = DbContext.trade.Where(x => x.id == tradeId).Select(x => x.TradeNumber).FirstOrDefault(); + if (string.IsNullOrWhiteSpace(tradeNumber)) + { + return null; + } + + return DbContext.trade_risk_check_log + .Where(x => x.trade_number == tradeNumber + && x.trial_result == (int)QuotaTrialStatusEnum.RiskWarning + && x.remark.Contains("风控预警处理结果:确认通过")) + .OrderByDescending(x => x.create_time) + .Select(x => (DateTime?)x.create_time) + .FirstOrDefault(); + } + public void AddWarningDecisionLog(int quotaTrialId, string decision) { var quotaTrial = DbContext.quotaTrial.FirstOrDefault(x => x.id == quotaTrialId); diff --git a/YLErpWeb/Controllers/tradeController.cs b/YLErpWeb/Controllers/tradeController.cs index 98a3d539..34c01daa 100644 --- a/YLErpWeb/Controllers/tradeController.cs +++ b/YLErpWeb/Controllers/tradeController.cs @@ -2447,7 +2447,8 @@ namespace YLErp.Web.Controllers } - public JsonResult tradeConfirm(string tradeids, string additionalProcessing, string ignoreRiskRuleIds, bool isSkipCheck = false) + // trialDataId: 二次确认时回传的 quotaTrial.id,用于超时校验;首次确认不传,为 null 时不做超时判断 + public JsonResult tradeConfirm(string tradeids, string additionalProcessing, string ignoreRiskRuleIds, int? trialDataId, bool isSkipCheck = false) { var tradeidArr = DataConvert.ConvertCommaValuesToInt32Array(tradeids); if (tradeids is null || !tradeids.Any(n => n > 0)) @@ -2455,9 +2456,35 @@ namespace YLErp.Web.Controllers return JsonError("请至少选择一项要生成确认书的交易,参数tradeids:" + tradeids); } var config = valuedateBLL.SystemDate; + var riskWarningConfirmExpireSeconds = 300; + var riskWarningConfirmExpireSecondsConfig = AppManager.GetAppConfigValue("ProjectConfig", "Trade.RiskWarningConfirmExpireSeconds"); + if (!string.IsNullOrWhiteSpace(riskWarningConfirmExpireSecondsConfig) + && int.TryParse(riskWarningConfirmExpireSecondsConfig, out var configuredRiskWarningConfirmExpireSeconds) + && configuredRiskWarningConfirmExpireSeconds > 0) + { + riskWarningConfirmExpireSeconds = configuredRiskWarningConfirmExpireSeconds; + } var ignoreMoneyCheck = tradeidArr.Count() == 1 && additionalProcessing == tradeBLL.LackOfMoney; var ignoreRiskWarning = tradeidArr.Count() == 1 && additionalProcessing == tradeBLL.RiskWarningConfirm; var ignoreRiskRuleIdArr = StringHelper.ConvertCommaValuesToStringArray(ignoreRiskRuleIds); + // 老风控交易特批与新风控二次审批统一基于 quotaTrial 做超时校验,超时后清空放行标记并重新校验。 + // 仅 swapTradeView.js 会回传 trialDataId 并触发 RiskWarningConfirm,按 quotaTrial.OptDate 精确校验; + // tradeview.js / tradeConfirmList.js / swapTradeConfirmList.js / quotaMonitor.js 只发 LackOfMoney,不传 trialDataId,不进入超时校验。 + if (ignoreMoneyCheck || ignoreRiskWarning) + { + var trialExpired = false; + if (trialDataId.HasValue) + { + // swapTradeView.js:按 quotaTrial.OptDate 校验,查不到记录也视为过期 + trialExpired = new QuotaMonitorService(CurUser).IsQuotaTrialExpired(trialDataId.Value, riskWarningConfirmExpireSeconds); + } + if (trialExpired) + { + ignoreMoneyCheck = false; + ignoreRiskWarning = false; + ignoreRiskRuleIdArr = Array.Empty(); + } + } var isSkipApproval = false; if (CurUser.交易管理_特批批量确认 && isSkipCheck) { diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js index 2f6eb85f..7b832f3c 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js @@ -49,7 +49,7 @@ function showSwapRate(timeRate, isMarginLeg) { $("#swapIntervalModal").modal("show"); } var layerIndex = -1; -var confirmFunc = function (id, additionalProcessing, ignoreRiskRuleIds) { +var confirmFunc = function (id, additionalProcessing, ignoreRiskRuleIds, trialDataId) { var pData = { tradeids: id }; if (!main.isEmpty(additionalProcessing)) { pData.additionalProcessing = additionalProcessing; @@ -57,6 +57,9 @@ var confirmFunc = function (id, additionalProcessing, ignoreRiskRuleIds) { if (ignoreRiskRuleIds && ignoreRiskRuleIds.length > 0) { pData.ignoreRiskRuleIds = ignoreRiskRuleIds.join(','); } + if (trialDataId) { + pData.trialDataId = trialDataId; + } main.post("/trade/tradeConfirm", pData).done(function (data) { if (!data.success) { main.message(data.msg); @@ -103,6 +106,7 @@ var confirmFunc = function (id, additionalProcessing, ignoreRiskRuleIds) { var additionalProcessingType = isRiskWarningConfirm ? "RiskWarningConfirm" : "LackOfMoney"; // 新风控需审批时由后端返回本次命中的审批规则 id,前端二次确认时原样透传。 var currentIgnoreRiskRuleIds = data.obj.ignoreRiskRuleIds || []; + var currentTrialDataId = data.obj.TrialDataId; var saveQuotaTrial = function (obj) { var saveSuccess = true; if (obj.Data.Remark && obj.Data.Remark.length > 0) { @@ -150,7 +154,7 @@ var confirmFunc = function (id, additionalProcessing, ignoreRiskRuleIds) { obj.Data.Remark = (obj.Data.Remark || "").trim(); } layer.close(layerIndex); - confirmFunc(id, additionalProcessingType, currentIgnoreRiskRuleIds); + confirmFunc(id, additionalProcessingType, currentIgnoreRiskRuleIds, currentTrialDataId); }, cancel: function () { var iframeWindow = window["layui-layer-iframe" + this.index];