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];