feat: 批量操作交互逻辑统一&解耦规则与应用的状态
This commit is contained in:
@@ -23,6 +23,8 @@ namespace YLErp.DBModels
|
||||
public string Description { get; set; }
|
||||
/// <summary>取值表达式(C# 表达式,用于计算变量值)</summary>
|
||||
public string VariableExpr { get; set; }
|
||||
/// <summary>状态: 1=Active, 2=Disabled, 3=Deleted</summary>
|
||||
public RiskRuleStatus Status { get; set; } = RiskRuleStatus.Active;
|
||||
/// <summary>版本号(乐观锁)</summary>
|
||||
[ConcurrencyCheck]
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
-- ============================================================
|
||||
-- 增量迁移:为 glms_risk_variable 表增加 Status 字段
|
||||
-- 目的:将变量删除由硬删除改造为软删除,与 glms_risk_rule / glms_risk_rule_application 保持一致
|
||||
-- 状态:1=Active, 2=Disabled, 3=Deleted
|
||||
-- 默认值:1(Active),存量数据自动标记为可用
|
||||
-- ============================================================
|
||||
|
||||
ALTER TABLE `yltrs_ylcms`.`glms_risk_variable`
|
||||
ADD COLUMN `Status` tinyint NOT NULL DEFAULT 1 COMMENT '状态: 1=Active, 2=Disabled, 3=Deleted' AFTER `VariableExpr`;
|
||||
|
||||
ALTER TABLE `yltrs_ylcms`.`glms_risk_variable`
|
||||
ADD INDEX `idx_status`(`Status` ASC) USING BTREE;
|
||||
@@ -68,6 +68,7 @@ CREATE TABLE `yltrs_ylcms`.`glms_risk_variable` (
|
||||
`ValueDomain` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '值域描述',
|
||||
`Description` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
|
||||
`VariableExpr` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '变量表达式(C#表达式)',
|
||||
`Status` tinyint NOT NULL DEFAULT 1 COMMENT '状态: 1=Active, 2=Disabled, 3=Deleted',
|
||||
`Version` int NOT NULL DEFAULT 1 COMMENT '版本号(乐观锁)',
|
||||
`SortOrder` int NOT NULL DEFAULT 0 COMMENT '排序',
|
||||
`OptId` int NULL DEFAULT NULL COMMENT '创建人Id',
|
||||
@@ -76,5 +77,6 @@ CREATE TABLE `yltrs_ylcms`.`glms_risk_variable` (
|
||||
`UpdateOptId` int NULL DEFAULT NULL COMMENT '最后修改人Id',
|
||||
`UpdateOptName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最后修改人名称',
|
||||
`UpdateDate` datetime NULL DEFAULT NULL COMMENT '最后修改时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_status`(`Status` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '风控变量池定义表' ROW_FORMAT = Dynamic;
|
||||
@@ -13,7 +13,7 @@
|
||||
-- 说明:结构化 RuleExpr 的固定数值不追加 m;本文件 VariableExpr 为人工维护的 C# 表达式,原有 m 后缀保持不变。
|
||||
|
||||
-- 插入当前数据库中的 6 条变量,保留原始 ID 以匹配规则 ConditionJson 中的 VariableId
|
||||
INSERT INTO `yltrs_ylcms`.`glms_risk_variable` (`id`, `VariableName`, `Category`, `DataType`, `Unit`, `ValueDomain`, `Description`, `VariableExpr`, `Version`, `SortOrder`, `OptId`, `OptName`, `OptDate`, `UpdateOptId`, `UpdateOptName`, `UpdateDate`) VALUES
|
||||
INSERT INTO `yltrs_ylcms`.`glms_risk_variable` (`id`, `VariableName`, `Category`, `DataType`, `Unit`, `ValueDomain`, `Description`, `VariableExpr`, `Status`, `Version`, `SortOrder`, `OptId`, `OptName`, `OptDate`, `UpdateOptId`, `UpdateOptName`, `UpdateDate`) VALUES
|
||||
(1,
|
||||
'合约名义本金',
|
||||
1,
|
||||
@@ -22,6 +22,7 @@ INSERT INTO `yltrs_ylcms`.`glms_risk_variable` (`id`, `VariableName`, `Category`
|
||||
'≥ 0',
|
||||
'',
|
||||
'DbContext.trade.First(t => t.id == TradeId).StockEqvNotional',
|
||||
1,
|
||||
18,
|
||||
0,
|
||||
0,
|
||||
@@ -38,6 +39,7 @@ INSERT INTO `yltrs_ylcms`.`glms_risk_variable` (`id`, `VariableName`, `Category`
|
||||
'≥ 0',
|
||||
'当前交易对手方所有存续交易涉及的标的数量(去重)合计(含本笔)',
|
||||
'DbContext.swap_position.Where(p => !string.IsNullOrEmpty(p.UnderlyingCode) && !p.IsInitial && p.PosiQuantity > 0 && !p.Invalid && p.PosiDirection > 0 && DbContext.trade.Any(t => t.id == p.SwapTradeId && t.ValidState != "InValid" && t.ClientId == DbContext.trade.First(x => x.id == TradeId).ClientId)).Select(p => p.UnderlyingCode).Distinct().Count()',
|
||||
1,
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
@@ -54,6 +56,7 @@ INSERT INTO `yltrs_ylcms`.`glms_risk_variable` (`id`, `VariableName`, `Category`
|
||||
'≥ 0',
|
||||
'abs(期初标的交割净价% - 上一收盘日中债估值净价)',
|
||||
'Math.Abs(DbContext.swap_position.First(p => p.SwapTradeId == TradeId && p.IsInitial && !p.Invalid && p.PosiDirection == 2 && !string.IsNullOrEmpty(p.UnderlyingCode)).PosiNetNoFeePrice.Value * 100 - DbContext.china_bond_valuation.Where(v => v.bond_id == DbContext.swap_position.First(p => p.SwapTradeId == TradeId && p.IsInitial && !p.Invalid && p.PosiDirection == 2 && !string.IsNullOrEmpty(p.UnderlyingCode)).UnderlyingCode && v.valuation_date < DbContext.trade.First(t => t.id == TradeId).TradeDate.Value.Date).OrderByDescending(v => v.valuation_date).First().net_price.Value)',
|
||||
1,
|
||||
6,
|
||||
0,
|
||||
0,
|
||||
@@ -70,6 +73,7 @@ INSERT INTO `yltrs_ylcms`.`glms_risk_variable` (`id`, `VariableName`, `Category`
|
||||
'≥ 0',
|
||||
'abs(期初标的成交收益率% - 上一收盘日中债估值收益率)',
|
||||
'Math.Abs(DbContext.swap_position.First(p => p.SwapTradeId == TradeId && p.IsInitial && !p.Invalid && p.PosiDirection == 2 && !string.IsNullOrEmpty(p.UnderlyingCode)).InitYtm.Value * 100 - DbContext.china_bond_valuation.Where(v => v.bond_id == DbContext.swap_position.First(p => p.SwapTradeId == TradeId && p.IsInitial && !p.Invalid && p.PosiDirection == 2 && !string.IsNullOrEmpty(p.UnderlyingCode)).UnderlyingCode && v.valuation_date < DbContext.trade.First(t => t.id == TradeId).TradeDate.Value.Date).OrderBy(v => v.credibility).ThenByDescending(v => v.valuation_date).First().yield.Value)',
|
||||
1,
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
@@ -86,6 +90,7 @@ INSERT INTO `yltrs_ylcms`.`glms_risk_variable` (`id`, `VariableName`, `Category`
|
||||
'≥ 0',
|
||||
'abs(期初标的交割全价% - 上一日标的收盘价)',
|
||||
'Math.Abs(DbContext.swap_position.First(p => p.SwapTradeId == TradeId && p.IsInitial && !p.Invalid && p.PosiDirection == 2 && !string.IsNullOrEmpty(p.UnderlyingCode)).PosiGrossPrice * 100 - Convert.ToDecimal(DbContext.eod_commodity_future_price.Where(e => e.UnderlyingCode == DbContext.swap_position.First(p => p.SwapTradeId == TradeId && p.IsInitial && !p.Invalid && p.PosiDirection == 2 && !string.IsNullOrEmpty(p.UnderlyingCode)).UnderlyingCode && e.ValueDate < DbContext.trade.First(t => t.id == TradeId).TradeDate.Value.Date).OrderByDescending(e => e.ValueDate).First().ClosePrice))',
|
||||
1,
|
||||
4,
|
||||
0,
|
||||
0,
|
||||
@@ -102,6 +107,7 @@ INSERT INTO `yltrs_ylcms`.`glms_risk_variable` (`id`, `VariableName`, `Category`
|
||||
'≥ 0',
|
||||
'ABS(利息端利率-FR007)/FR007*100',
|
||||
'Math.Abs(DbContext.swap_position.First(p => p.SwapTradeId == TradeId && p.InterestDirection == 1).InterestRateDefault)*100m',
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.Modules.RiskEngine.Dto;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine
|
||||
{
|
||||
[TestClass]
|
||||
public class RiskRuleApplicationStatusDecoupleContractTest
|
||||
{
|
||||
private static readonly string[] AvailabilityPropertyNames =
|
||||
{
|
||||
"AssociatedRuleCount",
|
||||
"EffectiveRuleCount",
|
||||
"CanEnable",
|
||||
"CanDisable",
|
||||
"EnableButtonTooltip",
|
||||
"DisableButtonTooltip"
|
||||
};
|
||||
|
||||
[TestMethod]
|
||||
public void DisableRuleResult_UsesDecoupledContract()
|
||||
{
|
||||
AssertProperty<DisableRuleResult>("RuleId", typeof(long));
|
||||
AssertProperty<DisableRuleResult>("AffectedActiveAppCount", typeof(int));
|
||||
Assert.IsNull(typeof(DisableRuleResult).GetProperty("CascadedApplicationIds"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DeleteRuleResult_ReportsAffectedApplications()
|
||||
{
|
||||
AssertProperty<DeleteRuleResult>("RuleId", typeof(long));
|
||||
AssertProperty<DeleteRuleResult>("AffectedActiveAppCount", typeof(int));
|
||||
AssertProperty<DeleteRuleResult>("CausedNoAvailableRuleAppIds", typeof(List<long>));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ApplicationDtos_ExposeAvailabilityFields()
|
||||
{
|
||||
AssertAvailabilityProperties<RiskApplicationListItem>();
|
||||
AssertAvailabilityProperties<RiskApplicationDetail>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BatchOperationResult_ExposesDecoupledApplicationImpactFields()
|
||||
{
|
||||
AssertProperty<BatchOperationItemResult>("AffectedApplicationIds", typeof(List<long>));
|
||||
AssertProperty<BatchOperationItemResult>("CausedNoAvailableRuleApplicationIds", typeof(List<long>));
|
||||
AssertProperty<BatchOperationResult>("AffectedApplicationIds", typeof(List<long>));
|
||||
AssertProperty<BatchOperationResult>("CausedNoAvailableRuleApplicationIds", typeof(List<long>));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VariableDeleteReferenceMessage_ListsDistinctSortedRuleIds()
|
||||
{
|
||||
var method = typeof(RiskRuleService).GetMethod(
|
||||
"BuildVariableDeleteReferenceMessage", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
Assert.IsNotNull(method);
|
||||
|
||||
var rules = new List<glms_risk_rule>
|
||||
{
|
||||
new glms_risk_rule { id = 12 },
|
||||
new glms_risk_rule { id = 10 },
|
||||
new glms_risk_rule { id = 12 }
|
||||
};
|
||||
|
||||
var message = (string)method.Invoke(null, new object[] { rules });
|
||||
|
||||
Assert.AreEqual("该变量被 2 个规则引用(规则ID:10、12),无法删除", message);
|
||||
}
|
||||
|
||||
private static void AssertAvailabilityProperties<T>()
|
||||
{
|
||||
foreach (var propertyName in AvailabilityPropertyNames)
|
||||
{
|
||||
var expectedType = propertyName.EndsWith("Tooltip", StringComparison.Ordinal)
|
||||
? typeof(string)
|
||||
: propertyName.StartsWith("Can", StringComparison.Ordinal)
|
||||
? typeof(bool)
|
||||
: typeof(int);
|
||||
|
||||
AssertProperty<T>(propertyName, expectedType);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertProperty<T>(string propertyName, Type expectedType)
|
||||
{
|
||||
var property = typeof(T).GetProperty(propertyName);
|
||||
Assert.IsNotNull(property, $"{typeof(T).Name}.{propertyName} should exist.");
|
||||
Assert.AreEqual(expectedType, property.PropertyType, $"{typeof(T).Name}.{propertyName} type mismatch.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
@@ -31,6 +32,11 @@ namespace YLErp.Modules.RiskEngine.Dto
|
||||
public BatchOperationItemStatus Status { get; set; }
|
||||
public string Code { get; set; }
|
||||
public string Message { get; set; }
|
||||
/// <summary>操作影响到的应用 ID(不代表应用状态发生变化)</summary>
|
||||
public List<long> AffectedApplicationIds { get; set; } = new List<long>();
|
||||
/// <summary>操作后不再包含任何启用规则的应用 ID</summary>
|
||||
public List<long> CausedNoAvailableRuleApplicationIds { get; set; } = new List<long>();
|
||||
/// <summary>旧版级联字段,解耦后始终为空,仅为兼容旧客户端保留</summary>
|
||||
public List<long> CascadedApplicationIds { get; set; } = new List<long>();
|
||||
}
|
||||
|
||||
@@ -43,6 +49,11 @@ namespace YLErp.Modules.RiskEngine.Dto
|
||||
public int FailedCount { get; set; }
|
||||
public bool HasChanges { get; set; }
|
||||
public List<BatchOperationItemResult> Items { get; set; } = new List<BatchOperationItemResult>();
|
||||
/// <summary>所有请求项影响到的应用 ID 去重并集</summary>
|
||||
public List<long> AffectedApplicationIds { get; set; } = new List<long>();
|
||||
/// <summary>操作后不再包含任何启用规则的应用 ID 去重并集</summary>
|
||||
public List<long> CausedNoAvailableRuleApplicationIds { get; set; } = new List<long>();
|
||||
/// <summary>旧版级联字段,解耦后始终为空,仅为兼容旧客户端保留</summary>
|
||||
public List<long> CascadedApplicationIds { get; set; } = new List<long>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 删除规则结果
|
||||
/// </summary>
|
||||
public class DeleteRuleResult
|
||||
{
|
||||
/// <summary>规则 ID</summary>
|
||||
public long RuleId { get; set; }
|
||||
|
||||
/// <summary>删除前引用该规则的启用中应用数量</summary>
|
||||
public int AffectedActiveAppCount { get; set; }
|
||||
|
||||
/// <summary>删除后不再包含任何启用规则的应用 ID</summary>
|
||||
public List<long> CausedNoAvailableRuleAppIds { get; set; } = new List<long>();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
@@ -7,7 +5,10 @@ namespace YLErp.Modules.RiskEngine.Dto
|
||||
/// </summary>
|
||||
public class DisableRuleResult
|
||||
{
|
||||
/// <summary>级联停用的应用 ID 列表</summary>
|
||||
public List<long> CascadedApplicationIds { get; set; } = new List<long>();
|
||||
/// <summary>规则 ID</summary>
|
||||
public long RuleId { get; set; }
|
||||
|
||||
/// <summary>引用该规则的启用中应用数量</summary>
|
||||
public int AffectedActiveAppCount { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,5 +14,7 @@ namespace YLErp.Modules.RiskEngine.Dto
|
||||
public RiskVariableDataType? DataType { get; set; }
|
||||
/// <summary>变量名称</summary>
|
||||
public string VariableName { get; set; }
|
||||
/// <summary>状态筛选(不传则返回非 Deleted 的全部)</summary>
|
||||
public RiskRuleStatus? Status { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,18 @@ namespace YLErp.Modules.RiskEngine.Dto
|
||||
public string RuleIds { get; set; }
|
||||
/// <summary>应用说明</summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>关联规则数(不含已删除规则)</summary>
|
||||
public int AssociatedRuleCount { get; set; }
|
||||
/// <summary>生效规则数(关联规则中已启用的数量)</summary>
|
||||
public int EffectiveRuleCount { get; set; }
|
||||
/// <summary>当前状态下是否允许启用</summary>
|
||||
public bool CanEnable { get; set; }
|
||||
/// <summary>当前状态下是否允许停用</summary>
|
||||
public bool CanDisable { get; set; }
|
||||
/// <summary>启用按钮不可用时的提示</summary>
|
||||
public string EnableButtonTooltip { get; set; }
|
||||
/// <summary>停用按钮不可用时的提示</summary>
|
||||
public string DisableButtonTooltip { get; set; }
|
||||
/// <summary>应用配置状态</summary>
|
||||
public RiskRuleStatus Status { get; set; }
|
||||
/// <summary>风控策略</summary>
|
||||
|
||||
@@ -14,6 +14,18 @@ namespace YLErp.Modules.RiskEngine.Dto
|
||||
public string RuleIds { get; set; }
|
||||
/// <summary>应用说明</summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>关联规则数(不含已删除规则)</summary>
|
||||
public int AssociatedRuleCount { get; set; }
|
||||
/// <summary>生效规则数(关联规则中已启用的数量)</summary>
|
||||
public int EffectiveRuleCount { get; set; }
|
||||
/// <summary>当前状态下是否允许启用</summary>
|
||||
public bool CanEnable { get; set; }
|
||||
/// <summary>当前状态下是否允许停用</summary>
|
||||
public bool CanDisable { get; set; }
|
||||
/// <summary>启用按钮不可用时的提示</summary>
|
||||
public string EnableButtonTooltip { get; set; }
|
||||
/// <summary>停用按钮不可用时的提示</summary>
|
||||
public string DisableButtonTooltip { get; set; }
|
||||
/// <summary>应用配置状态</summary>
|
||||
public int Status { get; set; }
|
||||
/// <summary>风控策略</summary>
|
||||
|
||||
@@ -28,6 +28,8 @@ namespace YLErp.Modules.RiskEngine.Dto
|
||||
public int Version { get; set; }
|
||||
/// <summary>排序序号</summary>
|
||||
public int SortOrder { get; set; }
|
||||
/// <summary>状态</summary>
|
||||
public RiskRuleStatus Status { get; set; }
|
||||
/// <summary>创建人姓名</summary>
|
||||
public string OptName { get; set; }
|
||||
/// <summary>创建时间</summary>
|
||||
|
||||
@@ -27,5 +27,7 @@ namespace YLErp.Modules.RiskEngine.Dto
|
||||
public int Version { get; set; }
|
||||
/// <summary>排序序号</summary>
|
||||
public int SortOrder { get; set; }
|
||||
/// <summary>状态</summary>
|
||||
public RiskRuleStatus Status { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,5 +19,7 @@ namespace YLErp.Modules.RiskEngine.Dto
|
||||
public string Unit { get; set; }
|
||||
/// <summary>取值表达式</summary>
|
||||
public string VariableExpr { get; set; }
|
||||
/// <summary>状态</summary>
|
||||
public RiskRuleStatus Status { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ using YLErp.Modules.RiskEngine.Dto;
|
||||
│ - ConditionJson 完整校验(结构化模式:变量存在性、操作符、类型匹配)│
|
||||
│ - RuleExpr 基础语法校验(自由文本模式:括号匹配、变量引用校验) │
|
||||
│ - 乐观锁并发控制(Version 字段) │
|
||||
│ - 删除保护(有 Active 引用时拒绝删除) │
|
||||
│ - 规则删除同步解除应用关联;变量被任一未删除规则引用时禁止删除 │
|
||||
│ - CRUD 后触发 RiskEngineService.RefreshCache() │
|
||||
│ │
|
||||
│ RiskEngineService(另一团队开发,单例) │
|
||||
@@ -76,26 +76,26 @@ using YLErp.Modules.RiskEngine.Dto;
|
||||
GetRuleDetail(long) 获取规则详情(含关联应用摘要)
|
||||
CreateRule(CreateRiskRuleReq) 新建规则(ConditionJson 完整校验)
|
||||
UpdateRule(long, UpdateRiskRuleReq) 修改规则(乐观锁校验,Version+1)
|
||||
DeleteRule(long) 删除规则(强保护:有 Active 应用时拒绝)
|
||||
DeleteRule(long) 删除规则(从未删除应用中移除关联)
|
||||
EnableRule(long) 启用规则(仅 Disabled → Active)
|
||||
DisableRule(long) 停用规则(仅 Active → Disabled,级联停用关联应用)
|
||||
DisableRule(long) 停用规则(仅 Active → Disabled,不修改应用状态)
|
||||
GetRuleVersions(long) 获取规则版本历史(从审计日志 SnapshotData 提取版本号)
|
||||
BatchDeleteRules(List<long>) 批量删除规则(逐条保护检查,单事务)
|
||||
BatchDeleteRules(List<long>) 批量删除规则(同步移除应用关联,单次提交)
|
||||
BatchEnableRules(List<long>) 批量启用规则(逐条编译校验)
|
||||
BatchDisableRules(List<long>) 批量停用规则(级联停用关联应用)
|
||||
BatchDisableRules(List<long>) 批量停用规则(不修改应用状态)
|
||||
GetRuleApplications(long) 获取规则关联的应用配置列表
|
||||
GetAllRuleList() 获取所有启用规则(轻量字段,供应用配置下拉)
|
||||
GetAllRuleList() 获取所有未删除规则(轻量字段,供应用配置下拉)
|
||||
|
||||
── 应用配置管理(9 个) ───────────────────────────────────────────
|
||||
QueryApplicationList(QueryRiskApplicationReq) 查询应用列表(多条件筛选,内存关联规则名称)
|
||||
QueryApplicationList(QueryRiskApplicationReq) 查询应用列表(分页后批量计算规则统计)
|
||||
GetApplicationDetail(long) 获取应用详情(含关联规则信息)
|
||||
CreateApplication(CreateRiskApplicationReq) 新建应用(关联规则 Active 校验,触发时点/Scope 校验)
|
||||
UpdateApplication(long, UpdateRiskApplicationReq) 修改应用(乐观锁校验)
|
||||
DeleteApplication(long) 删除应用(软删除)
|
||||
BatchDeleteApplications(List<long>) 批量删除应用(软删除)
|
||||
EnableApplication(long) 启用应用(校验关联规则状态)
|
||||
EnableApplication(long) 启用应用(至少一条关联规则 Active)
|
||||
DisableApplication(long) 停用应用
|
||||
BatchEnableApplications(List<long>) 批量启用(逐条校验关联规则)
|
||||
BatchEnableApplications(List<long>) 批量启用(逐条校验至少一条 Active 规则)
|
||||
BatchDisableApplications(List<long>) 批量停用
|
||||
|
||||
── 变量管理(6 个) ───────────────────────────────────────────────
|
||||
@@ -103,7 +103,7 @@ using YLErp.Modules.RiskEngine.Dto;
|
||||
GetVariableDetail(long) 获取变量详情(含 ReferenceCount 引用统计)
|
||||
CreateVariable(CreateRiskVariableReq) 新建变量(VariableExpr 长度校验 ≤10000)
|
||||
UpdateVariable(long, UpdateRiskVariableReq) 修改变量(乐观锁,DataType 变更保护)
|
||||
DeleteVariable(long) 删除变量(引用保护:有 Active 规则引用时拒绝,硬删除)
|
||||
DeleteVariable(long) 删除变量(引用保护:有未删除规则引用时拒绝,软删除)
|
||||
GetAllVariableList() 获取所有已实现变量(轻量字段,供规则编辑器下拉)
|
||||
|
||||
── 审计日志(4 个) ───────────────────────────────────────────────
|
||||
@@ -128,14 +128,14 @@ using YLErp.Modules.RiskEngine.Dto;
|
||||
1. 数据访问:直接使用 EF Core DbContext(无独立 Repository,与项目风格一致)
|
||||
2. 事务策略:Create* 方法使用 BeginTransaction 包裹双 SaveChanges(实体 + 审计日志同事务)
|
||||
3. 并发控制:乐观锁(Version 字段),修改时版本不匹配抛 ServiceException
|
||||
4. 删除保护:强保护模式,有 Active 引用时拒绝删除规则/变量
|
||||
4. 删除策略:规则删除同步从应用移除;变量删除保护所有未删除规则引用
|
||||
5. ConditionJson 校验:变量存在性(按 VariableId)+ 操作符合法性(按 DataType 映射)+ 类型匹配 + 阈值变量引用
|
||||
6. RuleExpr:自由文本模式下前端直接编辑,后端做基础语法校验(括号匹配 + 变量引用校验)
|
||||
7. Scope 字段:逗号分隔字符串存储,全局适用时不允许指定其他维度
|
||||
8. 缓存刷新:规则变更后调用 RefreshOneRuleCache(ruleId),应用变更后调用 RefreshApplication()
|
||||
9. 批量操作:单事务批量,全部成功或全部失败
|
||||
9. 批量操作:逐项返回成功/未变更/失败,成功项统一提交
|
||||
10. 变量缓存:5 分钟本地 HashSet<string> 缓存,CRUD 后 InvalidateVariableCache()
|
||||
11. 审计日志:16 种操作类型,CRUD + 审计日志同一事务,SnapshotData 存储版本快照
|
||||
11. 审计日志:CRUD + 审计日志同次提交,SnapshotData 存储版本快照
|
||||
12. 多规则支持:Application 通过 RuleIds(逗号分隔)关联多条规则,内存解析关联关系
|
||||
|
||||
【数据库表】(MySQL,迁移脚本位于 Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/prod.sql)
|
||||
@@ -181,7 +181,7 @@ using YLErp.Modules.RiskEngine.Dto;
|
||||
【注意事项】
|
||||
1. RiskEngineService 由另一个团队开发实现,本服务通过 GetInstance() 获取单例引用
|
||||
2. 规则变更后调用 RefreshOneRuleCache(ruleId),应用变更后调用 RefreshApplication(),变量变更不触发引擎缓存刷新
|
||||
3. 变量删除为硬删除(物理删除),规则/应用删除为软删除(Status=Deleted)
|
||||
3. 变量/规则/应用删除均为软删除(Status=Deleted),新建变量默认 Active
|
||||
4. ExportAuditLogs 按查询条件导出,并生成包含查询条件的文件名
|
||||
5. VariableExpr 当前仅做基础长度校验(≤10000),完整编译校验待引入 Roslyn 库
|
||||
6. ValidateScopeFields 的 ID 存在性校验已跳过(前端下拉选择器保证有效性)
|
||||
@@ -225,7 +225,9 @@ namespace YLErp.Modules.RiskEngine
|
||||
{ "APP_BATCH_ENABLE", "批量启用应用" },
|
||||
{ "APP_BATCH_DISABLE", "批量停用应用" },
|
||||
{ "APP_BATCH_DELETE", "批量删除应用" },
|
||||
// 仅用于历史审计日志展示;解耦后不再写入该操作类型。
|
||||
{ "RULE_CASCADE_DISABLE_APP", "规则级联停用应用" },
|
||||
{ "RULE_REMOVE_FROM_APP", "规则从应用移除" },
|
||||
{ "VAR_CREATE", "新增变量" },
|
||||
{ "VARIABLE_CREATE", "新增变量" },
|
||||
{ "VAR_UPDATE", "修改变量" },
|
||||
@@ -504,9 +506,10 @@ namespace YLErp.Modules.RiskEngine
|
||||
/// </summary>
|
||||
private glms_risk_variable GetVariableOrThrow(long variableId)
|
||||
{
|
||||
var variable = DbContext.glms_risk_variable.FirstOrDefault(v => v.id == variableId);
|
||||
var variable = DbContext.glms_risk_variable
|
||||
.FirstOrDefault(v => v.id == variableId && v.Status != RiskRuleStatus.Deleted);
|
||||
if (variable == null)
|
||||
throw new ServiceException("变量不存在");
|
||||
throw new ServiceException("变量不存在或已删除");
|
||||
return variable;
|
||||
}
|
||||
|
||||
@@ -559,6 +562,84 @@ namespace YLErp.Modules.RiskEngine
|
||||
return ids;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析数据库中已保存的规则 ID。删除应用关联的最后一条规则后允许为空。
|
||||
/// </summary>
|
||||
private static List<long> ParseStoredRuleIds(string ruleIds)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ruleIds))
|
||||
return new List<long>();
|
||||
|
||||
var ids = new List<long>();
|
||||
foreach (var part in ruleIds.Split(','))
|
||||
{
|
||||
var value = part.Trim();
|
||||
if (string.IsNullOrEmpty(value))
|
||||
continue;
|
||||
if (!long.TryParse(value, out var id))
|
||||
throw new ServiceException($"规则 ID 列表格式不合法:'{value}'");
|
||||
ids.Add(id);
|
||||
}
|
||||
return ids.Distinct().ToList();
|
||||
}
|
||||
|
||||
private bool HasAnyActiveRule(string ruleIds)
|
||||
{
|
||||
var ids = ParseStoredRuleIds(ruleIds);
|
||||
return ids.Count > 0 && DbContext.glms_risk_rule
|
||||
.Any(r => ids.Contains(r.id) && r.Status == RiskRuleStatus.Active);
|
||||
}
|
||||
|
||||
private Dictionary<long, RiskRuleStatus> LoadRuleStatuses(IEnumerable<string> storedRuleIds)
|
||||
{
|
||||
var ids = storedRuleIds.SelectMany(ParseStoredRuleIds).Distinct().ToList();
|
||||
if (ids.Count == 0)
|
||||
return new Dictionary<long, RiskRuleStatus>();
|
||||
|
||||
return DbContext.glms_risk_rule
|
||||
.Where(r => ids.Contains(r.id))
|
||||
.Select(r => new { r.id, r.Status })
|
||||
.ToDictionary(r => (long)r.id, r => r.Status);
|
||||
}
|
||||
|
||||
private static void PopulateApplicationAvailability(
|
||||
RiskApplicationListItem item, IReadOnlyDictionary<long, RiskRuleStatus> ruleStatuses)
|
||||
{
|
||||
var statuses = ParseStoredRuleIds(item.RuleIds)
|
||||
.Where(ruleStatuses.ContainsKey)
|
||||
.Select(id => ruleStatuses[id])
|
||||
.ToList();
|
||||
item.AssociatedRuleCount = statuses.Count(status => status != RiskRuleStatus.Deleted);
|
||||
item.EffectiveRuleCount = statuses.Count(status => status == RiskRuleStatus.Active);
|
||||
item.CanEnable = item.Status == (int)RiskRuleStatus.Disabled && item.EffectiveRuleCount > 0;
|
||||
item.CanDisable = item.Status == (int)RiskRuleStatus.Active && item.EffectiveRuleCount > 0;
|
||||
item.EnableButtonTooltip = item.Status == (int)RiskRuleStatus.Disabled && !item.CanEnable
|
||||
? "该应用下所有规则均已全局停用,无法启用应用。请先在规则管理中启用至少一条规则"
|
||||
: string.Empty;
|
||||
item.DisableButtonTooltip = item.Status == (int)RiskRuleStatus.Active && !item.CanDisable
|
||||
? "该应用下所有规则均已全局停用,应用无法生效。请先在规则管理中启用至少一条关联规则"
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
private static void PopulateApplicationAvailability(
|
||||
RiskApplicationDetail item, IReadOnlyDictionary<long, RiskRuleStatus> ruleStatuses)
|
||||
{
|
||||
var statuses = ParseStoredRuleIds(item.RuleIds)
|
||||
.Where(ruleStatuses.ContainsKey)
|
||||
.Select(id => ruleStatuses[id])
|
||||
.ToList();
|
||||
item.AssociatedRuleCount = statuses.Count(status => status != RiskRuleStatus.Deleted);
|
||||
item.EffectiveRuleCount = statuses.Count(status => status == RiskRuleStatus.Active);
|
||||
item.CanEnable = item.Status == RiskRuleStatus.Disabled && item.EffectiveRuleCount > 0;
|
||||
item.CanDisable = item.Status == RiskRuleStatus.Active && item.EffectiveRuleCount > 0;
|
||||
item.EnableButtonTooltip = item.Status == RiskRuleStatus.Disabled && !item.CanEnable
|
||||
? "该应用下所有规则均已全局停用,无法启用应用。请先在规则管理中启用至少一条规则"
|
||||
: string.Empty;
|
||||
item.DisableButtonTooltip = item.Status == RiskRuleStatus.Active && !item.CanDisable
|
||||
? "该应用下所有规则均已全局停用,应用无法生效。请先在规则管理中启用至少一条关联规则"
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 规范化逗号分隔的规则 ID:去除空格和重复项,统一为无空格格式。
|
||||
/// </summary>
|
||||
@@ -723,7 +804,8 @@ namespace YLErp.Modules.RiskEngine
|
||||
}
|
||||
|
||||
private static BatchOperationItemResult BatchItem(long id, BatchOperationItemStatus status,
|
||||
string code, string message, IEnumerable<long> cascadedApplicationIds = null)
|
||||
string code, string message, IEnumerable<long> affectedApplicationIds = null,
|
||||
IEnumerable<long> causedNoAvailableRuleApplicationIds = null)
|
||||
{
|
||||
return new BatchOperationItemResult
|
||||
{
|
||||
@@ -731,7 +813,9 @@ namespace YLErp.Modules.RiskEngine
|
||||
Status = status,
|
||||
Code = code,
|
||||
Message = message,
|
||||
CascadedApplicationIds = cascadedApplicationIds?.Distinct().ToList() ?? new List<long>()
|
||||
AffectedApplicationIds = affectedApplicationIds?.Distinct().ToList() ?? new List<long>(),
|
||||
CausedNoAvailableRuleApplicationIds = causedNoAvailableRuleApplicationIds?.Distinct().ToList() ?? new List<long>(),
|
||||
CascadedApplicationIds = new List<long>()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -747,7 +831,9 @@ namespace YLErp.Modules.RiskEngine
|
||||
SucceededCount = items.Count(i => i.Status == BatchOperationItemStatus.Succeeded),
|
||||
UnchangedCount = items.Count(i => i.Status == BatchOperationItemStatus.Unchanged),
|
||||
FailedCount = items.Count(i => i.Status == BatchOperationItemStatus.Failed),
|
||||
CascadedApplicationIds = items.SelectMany(i => i.CascadedApplicationIds).Distinct().ToList()
|
||||
AffectedApplicationIds = items.SelectMany(i => i.AffectedApplicationIds).Distinct().ToList(),
|
||||
CausedNoAvailableRuleApplicationIds = items.SelectMany(i => i.CausedNoAvailableRuleApplicationIds).Distinct().ToList(),
|
||||
CascadedApplicationIds = new List<long>()
|
||||
};
|
||||
result.HasChanges = result.SucceededCount > 0;
|
||||
result.Outcome = result.FailedCount == 0
|
||||
@@ -974,28 +1060,57 @@ namespace YLErp.Modules.RiskEngine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除规则(软删除,有 Active 应用引用时拒绝)
|
||||
/// 删除规则(软删除,并从所有未删除应用中移除关联)
|
||||
/// </summary>
|
||||
public void DeleteRule(long ruleId)
|
||||
public DeleteRuleResult DeleteRule(long ruleId)
|
||||
{
|
||||
var rule = GetRuleOrThrow(ruleId);
|
||||
|
||||
var activeCount = DbContext.glms_risk_rule_application
|
||||
.Where(a => a.Status == RiskRuleStatus.Active)
|
||||
.Count(RuleIdsMatchExpr(ruleId));
|
||||
if (activeCount > 0)
|
||||
throw new ServiceException($"该规则仍有 {activeCount} 个生效中的应用配置,请先停用或删除相关应用");
|
||||
var affectedApps = DbContext.glms_risk_rule_application
|
||||
.Where(a => a.Status != RiskRuleStatus.Deleted)
|
||||
.Where(RuleIdsMatchExpr(ruleId))
|
||||
.ToList();
|
||||
var affectedActiveAppCount = affectedApps.Count(a => a.Status == RiskRuleStatus.Active);
|
||||
var activeRuleIds = DbContext.glms_risk_rule
|
||||
.Where(r => r.Status == RiskRuleStatus.Active && r.id != ruleId)
|
||||
.Select(r => (long)r.id)
|
||||
.ToHashSet();
|
||||
var causedNoAvailableRuleAppIds = new List<long>();
|
||||
|
||||
rule.Status = RiskRuleStatus.Deleted;
|
||||
rule.Version = rule.Version + 1;
|
||||
rule.UpdateOptId = UserId;
|
||||
rule.UpdateOptName = UserName;
|
||||
rule.UpdateDate = DateTime.Now;
|
||||
|
||||
WriteAuditLog("RULE_DELETE", "RULE", ruleId, rule.RuleName, "删除规则");
|
||||
SaveChangesWithConcurrencyCheck();
|
||||
|
||||
RiskEngineService.GetInstance().RefreshOneRuleCache(ruleId);
|
||||
foreach (var app in affectedApps)
|
||||
{
|
||||
var remainingIds = ParseStoredRuleIds(app.RuleIds).Where(id => id != ruleId).ToList();
|
||||
app.RuleIds = string.Join(",", remainingIds);
|
||||
app.Version++;
|
||||
app.UpdateOptId = UserId;
|
||||
app.UpdateOptName = UserName;
|
||||
app.UpdateDate = DateTime.Now;
|
||||
if (!remainingIds.Any(activeRuleIds.Contains))
|
||||
causedNoAvailableRuleAppIds.Add(app.id);
|
||||
|
||||
WriteAuditLog("RULE_REMOVE_FROM_APP", "APPLICATION", app.id, $"应用{app.id}",
|
||||
$"删除规则'{rule.RuleName}'(ID:{ruleId}),已从应用关联规则中移除");
|
||||
}
|
||||
|
||||
SaveChangesWithConcurrencyCheck();
|
||||
TryRefreshCache(() => RiskEngineService.GetInstance().RefreshOneRuleCache(ruleId),
|
||||
"风控规则缓存", new[] { ruleId });
|
||||
if (affectedApps.Any())
|
||||
TryRefreshCache(() => RiskEngineService.GetInstance().RefreshApplication(),
|
||||
"风控应用缓存", affectedApps.Select(a => (long)a.id));
|
||||
|
||||
return new DeleteRuleResult
|
||||
{
|
||||
RuleId = ruleId,
|
||||
AffectedActiveAppCount = affectedActiveAppCount,
|
||||
CausedNoAvailableRuleAppIds = causedNoAvailableRuleAppIds.Distinct().ToList()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1025,7 +1140,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停用规则(仅 Active → Disabled)
|
||||
/// 停用规则(仅 Active → Disabled,不修改关联应用状态)
|
||||
/// </summary>
|
||||
public DisableRuleResult DisableRule(long ruleId)
|
||||
{
|
||||
@@ -1033,47 +1148,24 @@ namespace YLErp.Modules.RiskEngine
|
||||
if (rule == null)
|
||||
throw new ServiceException("仅已启用的规则可以停用");
|
||||
|
||||
rule.Status = RiskRuleStatus.Disabled;
|
||||
var affectedActiveAppCount = DbContext.glms_risk_rule_application
|
||||
.Where(a => a.Status == RiskRuleStatus.Active)
|
||||
.Count(RuleIdsMatchExpr(ruleId));
|
||||
|
||||
rule.Status = RiskRuleStatus.Disabled;
|
||||
rule.Version = rule.Version + 1;
|
||||
rule.UpdateOptId = UserId;
|
||||
rule.UpdateOptName = UserName;
|
||||
rule.UpdateDate = DateTime.Now;
|
||||
|
||||
WriteAuditLog("RULE_DISABLE", "RULE", ruleId, rule.RuleName, "停用规则");
|
||||
|
||||
// 级联停用关联的 Active 应用配置
|
||||
var activeApps = DbContext.glms_risk_rule_application
|
||||
.Where(a => a.Status == RiskRuleStatus.Active)
|
||||
.Where(RuleIdsMatchExpr(ruleId))
|
||||
.ToList();
|
||||
|
||||
var cascadedIds = new List<long>();
|
||||
foreach (var app in activeApps)
|
||||
{
|
||||
app.Status = RiskRuleStatus.Disabled;
|
||||
app.Version = app.Version + 1;
|
||||
app.UpdateOptId = UserId;
|
||||
app.UpdateOptName = UserName;
|
||||
app.UpdateDate = DateTime.Now;
|
||||
cascadedIds.Add(app.id);
|
||||
|
||||
WriteAuditLog("RULE_CASCADE_DISABLE_APP", "APPLICATION", app.id, $"应用{app.id}",
|
||||
$"因规则'{rule.RuleName}'(ID:{ruleId})停用,应用配置被级联停用");
|
||||
}
|
||||
|
||||
if (cascadedIds.Any())
|
||||
{
|
||||
_logger.Info($"[停用规则] 规则ID: {ruleId}, 级联停用应用: {string.Join(",", cascadedIds)}");
|
||||
}
|
||||
|
||||
SaveChangesWithConcurrencyCheck();
|
||||
|
||||
RiskEngineService.GetInstance().RefreshOneRuleCache(ruleId);
|
||||
if (cascadedIds.Any())
|
||||
RiskEngineService.GetInstance().RefreshApplication();
|
||||
|
||||
return new DisableRuleResult { CascadedApplicationIds = cascadedIds };
|
||||
return new DisableRuleResult
|
||||
{
|
||||
RuleId = ruleId,
|
||||
AffectedActiveAppCount = affectedActiveAppCount
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1117,7 +1209,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量删除规则(逐条保护检查)
|
||||
/// 批量删除规则,并从所有未删除应用中移除成功删除的规则关联
|
||||
/// </summary>
|
||||
public BatchOperationResult BatchDeleteRules(List<long> ruleIds)
|
||||
{
|
||||
@@ -1126,45 +1218,75 @@ namespace YLErp.Modules.RiskEngine
|
||||
|
||||
var ids = DistinctIds(ruleIds);
|
||||
var rules = DbContext.glms_risk_rule.Where(r => ids.Contains(r.id)).ToDictionary(r => (long)r.id);
|
||||
var activeApps = DbContext.glms_risk_rule_application
|
||||
.Where(a => a.Status == RiskRuleStatus.Active)
|
||||
var changedIds = ids
|
||||
.Where(id => rules.TryGetValue(id, out var rule) && rule.Status != RiskRuleStatus.Deleted)
|
||||
.ToList();
|
||||
var items = new List<BatchOperationItemResult>();
|
||||
var changedIds = new List<long>();
|
||||
var changedIdSet = changedIds.ToHashSet();
|
||||
var affectedApps = changedIds.Count == 0
|
||||
? new List<glms_risk_rule_application>()
|
||||
: DbContext.glms_risk_rule_application
|
||||
.Where(a => a.Status != RiskRuleStatus.Deleted && a.RuleIds != null && a.RuleIds != "")
|
||||
.ToList()
|
||||
.Where(a => ParseStoredRuleIds(a.RuleIds).Any(changedIdSet.Contains))
|
||||
.ToList();
|
||||
var affectedByRule = changedIds.ToDictionary(id => id, _ => new List<long>());
|
||||
var noAvailableByRule = changedIds.ToDictionary(id => id, _ => new List<long>());
|
||||
var activeRuleIdsAfterDelete = DbContext.glms_risk_rule
|
||||
.Where(r => r.Status == RiskRuleStatus.Active && !changedIds.Contains(r.id))
|
||||
.Select(r => (long)r.id)
|
||||
.ToHashSet();
|
||||
|
||||
foreach (var app in affectedApps)
|
||||
{
|
||||
var currentIds = ParseStoredRuleIds(app.RuleIds);
|
||||
var removedIds = currentIds.Where(changedIdSet.Contains).Distinct().ToList();
|
||||
var remainingIds = currentIds.Where(id => !changedIdSet.Contains(id)).ToList();
|
||||
app.RuleIds = string.Join(",", remainingIds);
|
||||
app.Version++;
|
||||
app.UpdateOptId = UserId;
|
||||
app.UpdateOptName = UserName;
|
||||
app.UpdateDate = DateTime.Now;
|
||||
var hasActiveRule = remainingIds.Any(activeRuleIdsAfterDelete.Contains);
|
||||
|
||||
foreach (var removedId in removedIds)
|
||||
{
|
||||
affectedByRule[removedId].Add(app.id);
|
||||
if (!hasActiveRule)
|
||||
noAvailableByRule[removedId].Add(app.id);
|
||||
WriteAuditLog("RULE_REMOVE_FROM_APP", "APPLICATION", app.id, $"应用{app.id}",
|
||||
$"批量删除规则'{rules[removedId].RuleName}'(ID:{removedId}),已从应用关联规则中移除");
|
||||
}
|
||||
}
|
||||
|
||||
var items = new List<BatchOperationItemResult>();
|
||||
foreach (var id in ids)
|
||||
{
|
||||
if (!rules.TryGetValue(id, out var rule) || rule.Status == RiskRuleStatus.Deleted)
|
||||
if (!changedIdSet.Contains(id))
|
||||
{
|
||||
items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "NOT_FOUND", "规则不存在或已删除"));
|
||||
continue;
|
||||
}
|
||||
|
||||
var referenceCount = activeApps.Count(a => ParseRuleIds(a.RuleIds).Contains(id));
|
||||
if (referenceCount > 0)
|
||||
{
|
||||
items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "ACTIVE_APPLICATION_REFERENCE",
|
||||
$"该规则仍有 {referenceCount} 个生效中的应用配置,请先停用或删除相关应用"));
|
||||
continue;
|
||||
}
|
||||
|
||||
var rule = rules[id];
|
||||
rule.Status = RiskRuleStatus.Deleted;
|
||||
rule.Version++;
|
||||
rule.UpdateOptId = UserId;
|
||||
rule.UpdateOptName = UserName;
|
||||
rule.UpdateDate = DateTime.Now;
|
||||
WriteAuditLog("RULE_BATCH_DELETE", "RULE", rule.id, rule.RuleName, "批量删除规则");
|
||||
changedIds.Add(id);
|
||||
items.Add(BatchItem(id, BatchOperationItemStatus.Succeeded, "DELETED", "规则已删除"));
|
||||
items.Add(BatchItem(id, BatchOperationItemStatus.Succeeded, "DELETED", "规则已删除",
|
||||
affectedByRule[id], noAvailableByRule[id]));
|
||||
}
|
||||
|
||||
if (changedIds.Any())
|
||||
{
|
||||
// 所有成功项一次原子提交;提交完成后,缓存失败仅告警,不改变数据库成功结果。
|
||||
SaveChangesWithConcurrencyCheck();
|
||||
TryRefreshCache(
|
||||
() => changedIds.ForEach(id => RiskEngineService.GetInstance().RefreshOneRuleCache(id)),
|
||||
"风控规则缓存", changedIds);
|
||||
if (affectedApps.Any())
|
||||
TryRefreshCache(() => RiskEngineService.GetInstance().RefreshApplication(),
|
||||
"风控应用缓存", affectedApps.Select(a => (long)a.id));
|
||||
}
|
||||
|
||||
var result = BuildBatchResult(items);
|
||||
@@ -1231,7 +1353,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量停用规则(仅 Active → Disabled,级联停用关联应用)
|
||||
/// 批量停用规则(仅 Active → Disabled,不修改关联应用状态)
|
||||
/// </summary>
|
||||
public BatchOperationResult BatchDisableRules(List<long> ruleIds)
|
||||
{
|
||||
@@ -1240,14 +1362,11 @@ namespace YLErp.Modules.RiskEngine
|
||||
|
||||
var ids = DistinctIds(ruleIds);
|
||||
var rules = DbContext.glms_risk_rule.Where(r => ids.Contains(r.id)).ToDictionary(r => (long)r.id);
|
||||
var activeRules = ids.Where(id => rules.TryGetValue(id, out var rule) && rule.Status == RiskRuleStatus.Active).ToList();
|
||||
var activeApps = DbContext.glms_risk_rule_application
|
||||
.Where(a => a.Status == RiskRuleStatus.Active)
|
||||
.Where(a => a.Status == RiskRuleStatus.Active && a.RuleIds != null && a.RuleIds != "")
|
||||
.ToList();
|
||||
var cascadesByRule = activeRules.ToDictionary(
|
||||
id => id,
|
||||
id => activeApps.Where(a => ParseRuleIds(a.RuleIds).Contains(id)).Select(a => (long)a.id).Distinct().ToList());
|
||||
var items = new List<BatchOperationItemResult>();
|
||||
var changedIds = new List<long>();
|
||||
|
||||
foreach (var id in ids)
|
||||
{
|
||||
@@ -1262,38 +1381,28 @@ namespace YLErp.Modules.RiskEngine
|
||||
continue;
|
||||
}
|
||||
|
||||
var affectedApplicationIds = activeApps
|
||||
.Where(a => ParseStoredRuleIds(a.RuleIds).Contains(id))
|
||||
.Select(a => (long)a.id)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
rule.Status = RiskRuleStatus.Disabled;
|
||||
rule.Version++;
|
||||
rule.UpdateOptId = UserId;
|
||||
rule.UpdateOptName = UserName;
|
||||
rule.UpdateDate = DateTime.Now;
|
||||
WriteAuditLog("RULE_BATCH_DISABLE", "RULE", rule.id, rule.RuleName, "批量停用规则");
|
||||
items.Add(BatchItem(id, BatchOperationItemStatus.Succeeded, "DISABLED", "规则已停用", cascadesByRule[id]));
|
||||
changedIds.Add(id);
|
||||
items.Add(BatchItem(id, BatchOperationItemStatus.Succeeded, "DISABLED", "规则已停用",
|
||||
affectedApplicationIds));
|
||||
}
|
||||
|
||||
// 多条规则可能关联同一应用;实际状态修改、审计和顶层级联结果均按应用 ID 去重。
|
||||
var cascadedIds = items.SelectMany(i => i.CascadedApplicationIds).Distinct().ToHashSet();
|
||||
foreach (var app in activeApps.Where(a => cascadedIds.Contains(a.id)))
|
||||
if (changedIds.Any())
|
||||
{
|
||||
app.Status = RiskRuleStatus.Disabled;
|
||||
app.Version++;
|
||||
app.UpdateOptId = UserId;
|
||||
app.UpdateOptName = UserName;
|
||||
app.UpdateDate = DateTime.Now;
|
||||
WriteAuditLog("RULE_CASCADE_DISABLE_APP", "APPLICATION", app.id, $"应用{app.id}",
|
||||
$"因规则批量停用,应用配置被级联停用(规则IDs:{string.Join(",", activeRules.Where(id => cascadesByRule[id].Contains(app.id)))})");
|
||||
}
|
||||
|
||||
if (items.Any(i => i.Status == BatchOperationItemStatus.Succeeded))
|
||||
{
|
||||
// 规则及其级联应用在同一次数据库提交中生效。
|
||||
SaveChangesWithConcurrencyCheck();
|
||||
TryRefreshCache(
|
||||
() => activeRules.ForEach(id => RiskEngineService.GetInstance().RefreshOneRuleCache(id)),
|
||||
"风控规则缓存", activeRules);
|
||||
if (cascadedIds.Any())
|
||||
TryRefreshCache(() => RiskEngineService.GetInstance().RefreshApplication(),
|
||||
"风控应用缓存", cascadedIds);
|
||||
() => changedIds.ForEach(id => RiskEngineService.GetInstance().RefreshOneRuleCache(id)),
|
||||
"风控规则缓存", changedIds);
|
||||
}
|
||||
|
||||
var result = BuildBatchResult(items);
|
||||
@@ -1313,14 +1422,15 @@ namespace YLErp.Modules.RiskEngine
|
||||
.Where(RuleIdsMatchExpr(ruleId))
|
||||
.OrderByDescending(a => a.UpdateDate)
|
||||
.ToList();
|
||||
|
||||
var ruleStatuses = LoadRuleStatuses(apps.Select(a => a.RuleIds));
|
||||
var result = new List<RiskApplicationListItem>();
|
||||
foreach (var app in apps)
|
||||
{
|
||||
result.Add(new RiskApplicationListItem
|
||||
var item = new RiskApplicationListItem
|
||||
{
|
||||
Id = app.id,
|
||||
RuleIds = app.RuleIds,
|
||||
Description = app.Description,
|
||||
Status = (int)app.Status,
|
||||
ControlStrategy = app.ControlStrategy,
|
||||
TriggerPoints = app.TriggerPoints,
|
||||
@@ -1334,7 +1444,9 @@ namespace YLErp.Modules.RiskEngine
|
||||
OptDate = app.OptDate.GetValueOrDefault(),
|
||||
UpdateOptName = app.UpdateOptName,
|
||||
UpdateDate = app.UpdateDate ?? app.OptDate.GetValueOrDefault()
|
||||
});
|
||||
};
|
||||
PopulateApplicationAvailability(item, ruleStatuses);
|
||||
result.Add(item);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -1363,7 +1475,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
#region Application Management
|
||||
|
||||
/// <summary>
|
||||
/// 查询应用配置列表(多条件筛选,内存关联规则名称)
|
||||
/// 查询应用配置列表(多条件筛选,批量计算关联规则统计)
|
||||
/// </summary>
|
||||
public SearchListResult<RiskApplicationListItem> QueryApplicationList(QueryRiskApplicationReq req)
|
||||
{
|
||||
@@ -1371,20 +1483,11 @@ namespace YLErp.Modules.RiskEngine
|
||||
.Where(a => a.Status != RiskRuleStatus.Deleted);
|
||||
|
||||
if (req.Status.HasValue)
|
||||
{
|
||||
query = query.Where(a => a.Status == req.Status.Value);
|
||||
}
|
||||
|
||||
if (req.ControlStrategy.HasValue)
|
||||
{
|
||||
query = query.Where(a => a.ControlStrategy == req.ControlStrategy.Value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.TriggerPoint))
|
||||
{
|
||||
query = query.Where(a => a.TriggerPoints.Contains(req.TriggerPoint));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.Keyword))
|
||||
{
|
||||
query = query.Where(a => DbContext.glms_risk_rule
|
||||
@@ -1396,16 +1499,12 @@ namespace YLErp.Modules.RiskEngine
|
||||
|| a.RuleIds.EndsWith("," + r.id.ToString())
|
||||
|| a.RuleIds.Contains("," + r.id.ToString() + ","))));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.ScopeClientIds))
|
||||
query = ApplyScopeFilter(query, a => a.ScopeClientIds, req.ScopeClientIds);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.ScopeUnderlyingTypes))
|
||||
query = ApplyScopeFilter(query, a => a.ScopeUnderlyingTypes, req.ScopeUnderlyingTypes);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.ScopeTradeTypes))
|
||||
query = ApplyScopeFilter(query, a => a.ScopeTradeTypes, req.ScopeTradeTypes);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.ScopeAssetBookIds))
|
||||
query = ApplyScopeFilter(query, a => a.ScopeAssetBookIds, req.ScopeAssetBookIds);
|
||||
|
||||
@@ -1431,11 +1530,16 @@ namespace YLErp.Modules.RiskEngine
|
||||
})
|
||||
.ToSearchList(req);
|
||||
|
||||
var items = pagedResult.rows?.ToList() ?? new List<RiskApplicationListItem>();
|
||||
var ruleStatuses = LoadRuleStatuses(items.Select(item => item.RuleIds));
|
||||
foreach (var item in items)
|
||||
PopulateApplicationAvailability(item, ruleStatuses);
|
||||
pagedResult.rows = items;
|
||||
return pagedResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取应用配置详情(含关联规则信息)
|
||||
/// 获取应用配置详情(含关联规则信息和可用状态)
|
||||
/// </summary>
|
||||
public RiskApplicationDetail GetApplicationDetail(long applicationId)
|
||||
{
|
||||
@@ -1444,7 +1548,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
if (app == null)
|
||||
throw new ServiceException("应用配置不存在或已删除");
|
||||
|
||||
return new RiskApplicationDetail
|
||||
var result = new RiskApplicationDetail
|
||||
{
|
||||
Id = app.id,
|
||||
RuleIds = app.RuleIds,
|
||||
@@ -1463,6 +1567,8 @@ namespace YLErp.Modules.RiskEngine
|
||||
UpdateOptName = app.UpdateOptName,
|
||||
UpdateDate = app.UpdateDate ?? app.OptDate.GetValueOrDefault()
|
||||
};
|
||||
PopulateApplicationAvailability(result, LoadRuleStatuses(new[] { app.RuleIds }));
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1636,7 +1742,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用应用配置(校验关联规则状态)
|
||||
/// 启用应用配置(至少存在一条 Active 关联规则)
|
||||
/// </summary>
|
||||
public void EnableApplication(long applicationId)
|
||||
{
|
||||
@@ -1644,12 +1750,10 @@ namespace YLErp.Modules.RiskEngine
|
||||
if (app == null)
|
||||
throw new ServiceException("仅已停用的应用配置可以启用");
|
||||
|
||||
var errors = ValidateAndEnsureRuleCache(app.RuleIds);
|
||||
if (errors.Any())
|
||||
throw new ServiceException($"以下关联规则不可用:{string.Join(";", errors)}");
|
||||
if (!HasAnyActiveRule(app.RuleIds))
|
||||
throw new ServiceException("该应用下所有规则均已停用,无法启用应用。请先在规则管理中启用至少一条关联规则");
|
||||
|
||||
app.Status = RiskRuleStatus.Active;
|
||||
|
||||
app.Version = app.Version + 1;
|
||||
app.UpdateOptId = UserId;
|
||||
app.UpdateOptName = UserName;
|
||||
@@ -1657,7 +1761,6 @@ namespace YLErp.Modules.RiskEngine
|
||||
|
||||
WriteAuditLog("APP_ENABLE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds), "启用应用配置");
|
||||
SaveChangesWithConcurrencyCheck();
|
||||
|
||||
RiskEngineService.GetInstance().RefreshApplication();
|
||||
}
|
||||
|
||||
@@ -1684,7 +1787,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量启用应用配置(逐条校验关联规则)
|
||||
/// 批量启用应用配置(逐条校验至少一条关联规则已启用)
|
||||
/// </summary>
|
||||
public BatchOperationResult BatchEnableApplications(List<long> applicationIds)
|
||||
{
|
||||
@@ -1713,11 +1816,10 @@ namespace YLErp.Modules.RiskEngine
|
||||
continue;
|
||||
}
|
||||
|
||||
var errors = ValidateAndEnsureRuleCache(app.RuleIds);
|
||||
if (errors.Any())
|
||||
if (!HasAnyActiveRule(app.RuleIds))
|
||||
{
|
||||
items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "RELATED_RULE_UNAVAILABLE",
|
||||
$"关联规则不可用:{string.Join(";", errors)}"));
|
||||
items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "NO_ACTIVE_RULE",
|
||||
"该应用下所有规则均已停用,无法启用应用。请先在规则管理中启用至少一条关联规则"));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1806,6 +1908,20 @@ namespace YLErp.Modules.RiskEngine
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成变量删除被阻止时的引用规则提示。
|
||||
/// </summary>
|
||||
private static string BuildVariableDeleteReferenceMessage(IEnumerable<glms_risk_rule> referencedRules)
|
||||
{
|
||||
var ruleIds = referencedRules
|
||||
.Select(rule => rule.id)
|
||||
.Distinct()
|
||||
.OrderBy(id => id)
|
||||
.ToList();
|
||||
|
||||
return $"该变量被 {ruleIds.Count} 个规则引用(规则ID:{string.Join("、", ruleIds)}),无法删除";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据结构化 ConditionJson 和当前变量池定义重新生成简洁 RuleExpr。
|
||||
/// </summary>
|
||||
@@ -1814,7 +1930,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
var conditions = RuleConditionExpressionBuilder.DeserializeConditions(conditionJson);
|
||||
var variableIds = RuleConditionExpressionBuilder.GetReferencedVariableIds(conditions).ToList();
|
||||
var variables = DbContext.glms_risk_variable
|
||||
.Where(variable => variableIds.Contains(variable.id))
|
||||
.Where(variable => variableIds.Contains(variable.id) && variable.Status != RiskRuleStatus.Deleted)
|
||||
.ToList()
|
||||
.ToDictionary(variable => (long)variable.id);
|
||||
|
||||
@@ -1831,7 +1947,8 @@ namespace YLErp.Modules.RiskEngine
|
||||
/// </summary>
|
||||
public SearchListResult<RiskVariableListItem> QueryVariableList(QueryRiskVariableReq req)
|
||||
{
|
||||
var query = DbContext.glms_risk_variable.AsQueryable();
|
||||
var query = DbContext.glms_risk_variable
|
||||
.Where(v => v.Status != RiskRuleStatus.Deleted);
|
||||
|
||||
if (req.Category.HasValue)
|
||||
{
|
||||
@@ -1848,6 +1965,11 @@ namespace YLErp.Modules.RiskEngine
|
||||
query = query.Where(v => v.VariableName.Contains(req.VariableName));
|
||||
}
|
||||
|
||||
if (req.Status.HasValue)
|
||||
{
|
||||
query = query.Where(v => v.Status == req.Status.Value);
|
||||
}
|
||||
|
||||
var result = query.OrderBy(v => v.SortOrder).ThenBy(v => v.VariableName)
|
||||
.Select(v => new RiskVariableListItem
|
||||
{
|
||||
@@ -1860,7 +1982,8 @@ namespace YLErp.Modules.RiskEngine
|
||||
Description = v.Description,
|
||||
VariableExpr = v.VariableExpr,
|
||||
Version = v.Version,
|
||||
SortOrder = v.SortOrder
|
||||
SortOrder = v.SortOrder,
|
||||
Status = v.Status
|
||||
});
|
||||
|
||||
return result.ToSearchList(req);
|
||||
@@ -1888,6 +2011,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
VariableExpr = variable.VariableExpr,
|
||||
Version = variable.Version,
|
||||
SortOrder = variable.SortOrder,
|
||||
Status = variable.Status,
|
||||
OptName = variable.OptName,
|
||||
OptDate = variable.OptDate.GetValueOrDefault(),
|
||||
UpdateOptName = variable.UpdateOptName,
|
||||
@@ -1917,6 +2041,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
ValueDomain = req.ValueDomain,
|
||||
Description = req.Description,
|
||||
VariableExpr = req.VariableExpr,
|
||||
Status = RiskRuleStatus.Active,
|
||||
SortOrder = req.SortOrder,
|
||||
Version = 1
|
||||
};
|
||||
@@ -2061,18 +2186,21 @@ namespace YLErp.Modules.RiskEngine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除变量(有 Active 引用时拒绝)
|
||||
/// 删除变量(软删除:有未删除规则引用时拒绝)
|
||||
/// </summary>
|
||||
public void DeleteVariable(long variableId)
|
||||
{
|
||||
var variable = GetVariableOrThrow(variableId);
|
||||
|
||||
var activeRefCount = GetRulesByVariableId(variableId)
|
||||
.Count(r => r.Status == RiskRuleStatus.Active);
|
||||
if (activeRefCount > 0)
|
||||
throw new ServiceException($"该变量被 {activeRefCount} 个生效中的规则引用,无法删除");
|
||||
var referencedRules = GetRulesByVariableId(variableId);
|
||||
if (referencedRules.Count > 0)
|
||||
throw new ServiceException(BuildVariableDeleteReferenceMessage(referencedRules));
|
||||
|
||||
DbContext.glms_risk_variable.Remove(variable);
|
||||
variable.Status = RiskRuleStatus.Deleted;
|
||||
variable.Version = variable.Version + 1;
|
||||
variable.UpdateOptId = UserId;
|
||||
variable.UpdateOptName = UserName;
|
||||
variable.UpdateDate = DateTime.Now;
|
||||
|
||||
WriteAuditLog("VAR_DELETE", "VARIABLE", variableId, variable.VariableName, $"删除变量:{variable.VariableName}");
|
||||
SaveChangesWithConcurrencyCheck();
|
||||
@@ -2081,7 +2209,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量删除变量(不存在或被生效规则引用的变量将被跳过)
|
||||
/// 批量删除变量(不存在或被未删除规则引用的变量将被跳过)
|
||||
/// </summary>
|
||||
public BatchOperationResult BatchDeleteVariables(List<long> variableIds)
|
||||
{
|
||||
@@ -2089,9 +2217,11 @@ namespace YLErp.Modules.RiskEngine
|
||||
throw new ServiceException("变量ID列表不能为空");
|
||||
|
||||
var ids = DistinctIds(variableIds);
|
||||
var variables = DbContext.glms_risk_variable.Where(v => ids.Contains(v.id)).ToDictionary(v => (long)v.id);
|
||||
var activeRules = DbContext.glms_risk_rule
|
||||
.Where(r => r.Status == RiskRuleStatus.Active && r.ConditionJson != null)
|
||||
var variables = DbContext.glms_risk_variable
|
||||
.Where(v => ids.Contains(v.id) && v.Status != RiskRuleStatus.Deleted)
|
||||
.ToDictionary(v => (long)v.id);
|
||||
var rules = DbContext.glms_risk_rule
|
||||
.Where(r => r.Status != RiskRuleStatus.Deleted && r.ConditionJson != null && r.ConditionJson != "")
|
||||
.ToList();
|
||||
var items = new List<BatchOperationItemResult>();
|
||||
|
||||
@@ -2099,20 +2229,25 @@ namespace YLErp.Modules.RiskEngine
|
||||
{
|
||||
if (!variables.TryGetValue(id, out var variable))
|
||||
{
|
||||
items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "NOT_FOUND", "变量不存在"));
|
||||
items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "NOT_FOUND", "变量不存在或已删除"));
|
||||
continue;
|
||||
}
|
||||
|
||||
var activeRefCount = activeRules.Count(rule =>
|
||||
RuleConditionExpressionBuilder.IsVariableReferenced(rule.ConditionJson, id));
|
||||
if (activeRefCount > 0)
|
||||
var referencedRules = rules.Where(rule =>
|
||||
RuleConditionExpressionBuilder.IsVariableReferenced(rule.ConditionJson, id))
|
||||
.ToList();
|
||||
if (referencedRules.Count > 0)
|
||||
{
|
||||
items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "ACTIVE_RULE_REFERENCE",
|
||||
$"该变量被 {activeRefCount} 个生效中的规则引用,无法删除"));
|
||||
items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "RULE_REFERENCE",
|
||||
BuildVariableDeleteReferenceMessage(referencedRules)));
|
||||
continue;
|
||||
}
|
||||
|
||||
DbContext.glms_risk_variable.Remove(variable);
|
||||
variable.Status = RiskRuleStatus.Deleted;
|
||||
variable.Version = variable.Version + 1;
|
||||
variable.UpdateOptId = UserId;
|
||||
variable.UpdateOptName = UserName;
|
||||
variable.UpdateDate = DateTime.Now;
|
||||
WriteAuditLog("VAR_BATCH_DELETE", "VARIABLE", variable.id, variable.VariableName, $"批量删除变量:{variable.VariableName}");
|
||||
items.Add(BatchItem(id, BatchOperationItemStatus.Succeeded, "DELETED", "变量已删除"));
|
||||
}
|
||||
@@ -2135,7 +2270,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
public List<RiskVariableSimpleItem> GetAllVariableList()
|
||||
{
|
||||
var result = DbContext.glms_risk_variable
|
||||
.Where(v => v.VariableExpr != null && v.VariableExpr != "")
|
||||
.Where(v => v.VariableExpr != null && v.VariableExpr != "" && v.Status != RiskRuleStatus.Deleted)
|
||||
.OrderBy(v => v.SortOrder)
|
||||
.Select(v => new RiskVariableSimpleItem
|
||||
{
|
||||
@@ -2144,7 +2279,8 @@ namespace YLErp.Modules.RiskEngine
|
||||
Category = v.Category,
|
||||
DataType = v.DataType,
|
||||
Unit = v.Unit,
|
||||
VariableExpr = v.VariableExpr
|
||||
VariableExpr = v.VariableExpr,
|
||||
Status = v.Status
|
||||
})
|
||||
.ToList();
|
||||
|
||||
@@ -2331,4 +2467,4 @@ namespace YLErp.Modules.RiskEngine
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,8 +145,8 @@ namespace YLErp.Web.Controllers
|
||||
try
|
||||
{
|
||||
var service = GetRiskRuleService();
|
||||
await Task.Run(() => service.DeleteRule(id));
|
||||
return Json(new { success = true });
|
||||
var result = await Task.Run(() => service.DeleteRule(id));
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
@@ -215,8 +215,7 @@ namespace YLErp.Web.Controllers
|
||||
return Json(new
|
||||
{
|
||||
success = true,
|
||||
cascadedAppIds = result.CascadedApplicationIds,
|
||||
cascadedAppCount = result.CascadedApplicationIds.Count
|
||||
affectedActiveAppCount = result.AffectedActiveAppCount
|
||||
});
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
@@ -658,7 +657,7 @@ namespace YLErp.Web.Controllers
|
||||
|
||||
[HttpDelete("risk-variables/{id}")]
|
||||
[MyAuthorize("风险控制-风控变量删除")]
|
||||
/// <summary>删除变量</summary>
|
||||
/// <summary>删除变量(软删除)</summary>
|
||||
public async Task<JsonResult> DeleteRiskVariable(long id)
|
||||
{
|
||||
try
|
||||
@@ -680,7 +679,7 @@ namespace YLErp.Web.Controllers
|
||||
|
||||
[HttpDelete("risk-variables/batch")]
|
||||
[MyAuthorize("风险控制-风控变量删除")]
|
||||
/// <summary>批量删除变量</summary>
|
||||
/// <summary>批量删除变量(软删除)</summary>
|
||||
public async Task<JsonResult> BatchDeleteRiskVariables([FromBody] BatchIdsReq req)
|
||||
{
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user