Merge remote-tracking branch 'origin/feature/p132_74-risk-engine' into spike/merge-p132_74-to-1.4.2
This commit is contained in:
@@ -160,3 +160,17 @@ csharp_preserve_single_line_statements = true
|
||||
|
||||
# CA1819: Properties should not return arrays
|
||||
dotnet_diagnostic.CA1819.severity = silent
|
||||
|
||||
# JavaScript 和 TypeScript 文件
|
||||
[*.{js,jsx,ts,tsx}]
|
||||
# 缩进和间距
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
tab_width = 4
|
||||
|
||||
# 新行首选项
|
||||
end_of_line = crlf
|
||||
insert_final_newline = false
|
||||
|
||||
# 拖尾逗号不添加
|
||||
trailing_comma = none
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace YLErp.DBModels
|
||||
{
|
||||
public enum RiskControlStrategy
|
||||
{
|
||||
Block = 1,
|
||||
Approval = 2,
|
||||
ShowTip = 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace YLErp.DBModels
|
||||
{
|
||||
public enum RiskRuleStatus
|
||||
{
|
||||
Active = 1,
|
||||
Disabled = 2,
|
||||
Deleted = 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace YLErp.DBModels
|
||||
{
|
||||
public enum RiskVariableCategory
|
||||
{
|
||||
BookingElement = 1,
|
||||
MarketData = 2,
|
||||
SystemCalc = 3,
|
||||
BooleanCheck = 4
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace YLErp.DBModels
|
||||
{
|
||||
public enum RiskVariableDataType
|
||||
{
|
||||
Numeric = 1,
|
||||
Date = 2,
|
||||
Boolean = 3
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
/// <para>1:确认成交时系统调用;</para>
|
||||
/// </summary>
|
||||
public int TrialSource { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public List<string> ApprovalRuleIds { get; set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace YLErp.DBModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 风控规则定义表
|
||||
/// </summary>
|
||||
[Table("glms_risk_rule")]
|
||||
public class glms_risk_rule : DBModelWithOperator
|
||||
{
|
||||
/// <summary>规则名称</summary>
|
||||
public string RuleName { get; set; }
|
||||
/// <summary>规则描述文本</summary>
|
||||
public string RuleText { get; set; }
|
||||
/// <summary>结构化条件 JSON(变量+操作符+阈值列表)</summary>
|
||||
public string ConditionJson { get; set; }
|
||||
/// <summary>规则执行表达式(C# 布尔表达式,引擎用 Roslyn 编译执行)</summary>
|
||||
public string RuleExpr { get; set; }
|
||||
/// <summary>规则说明</summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>规则状态</summary>
|
||||
public RiskRuleStatus Status { get; set; } = RiskRuleStatus.Active;
|
||||
/// <summary>版本号(乐观锁)</summary>
|
||||
public int Version { get; set; } = 1;
|
||||
/// <summary>最后更新人工号</summary>
|
||||
public int? UpdateOptId { get; set; }
|
||||
/// <summary>最后更新人姓名</summary>
|
||||
public string UpdateOptName { get; set; }
|
||||
/// <summary>最后更新时间</summary>
|
||||
public DateTime? UpdateDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace YLErp.DBModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 风控规则应用配置表
|
||||
/// </summary>
|
||||
[Table("glms_risk_rule_application")]
|
||||
public class glms_risk_rule_application : DBModelWithOperator
|
||||
{
|
||||
/// <summary>关联规则 ID 列表(JSON 数组)</summary>
|
||||
public string RuleIds { get; set; }
|
||||
/// <summary>应用说明</summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>应用配置状态</summary>
|
||||
public RiskRuleStatus Status { get; set; } = RiskRuleStatus.Active;
|
||||
/// <summary>风控策略(预警/阻断)</summary>
|
||||
public RiskControlStrategy ControlStrategy { get; set; }
|
||||
/// <summary>触发点列表(JSON 数组)</summary>
|
||||
public string TriggerPoints { get; set; }
|
||||
/// <summary>适用范围-资产账簿 ID 列表(JSON 数组)</summary>
|
||||
public string ScopeAssetBookIds { get; set; }
|
||||
/// <summary>适用范围-客户 ID 列表(JSON 数组)</summary>
|
||||
public string ScopeClientIds { get; set; }
|
||||
/// <summary>适用范围-标的类型列表(JSON 数组)</summary>
|
||||
public string ScopeUnderlyingTypes { get; set; }
|
||||
/// <summary>适用范围-交易类型列表(JSON 数组)</summary>
|
||||
public string ScopeTradeTypes { get; set; }
|
||||
/// <summary>是否全局生效(0:否 1:是)</summary>
|
||||
public int ScopeIsGlobal { get; set; }
|
||||
/// <summary>版本号(乐观锁)</summary>
|
||||
public int Version { get; set; } = 1;
|
||||
/// <summary>最后更新人工号</summary>
|
||||
public int? UpdateOptId { get; set; }
|
||||
/// <summary>最后更新人姓名</summary>
|
||||
public string UpdateOptName { get; set; }
|
||||
/// <summary>最后更新时间</summary>
|
||||
public DateTime? UpdateDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace YLErp.DBModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 风控操作审计日志表
|
||||
/// </summary>
|
||||
[Table("glms_risk_rule_audit_log")]
|
||||
public class glms_risk_rule_audit_log : DBModelBase
|
||||
{
|
||||
/// <summary>操作类型(创建/更新/删除/启用/禁用)</summary>
|
||||
public string OperationType { get; set; }
|
||||
/// <summary>操作目标类型(Rule/Variable/Application)</summary>
|
||||
public string TargetType { get; set; }
|
||||
/// <summary>操作目标 ID</summary>
|
||||
public long TargetId { get; set; }
|
||||
/// <summary>操作目标名称</summary>
|
||||
public string TargetName { get; set; }
|
||||
/// <summary>操作详情描述</summary>
|
||||
public string OperationDetail { get; set; }
|
||||
/// <summary>操作结果(成功/失败)</summary>
|
||||
public string Result { get; set; }
|
||||
/// <summary>操作前数据快照(JSON)</summary>
|
||||
public string SnapshotData { get; set; }
|
||||
/// <summary>操作人工号</summary>
|
||||
public long OptId { get; set; }
|
||||
/// <summary>操作人姓名</summary>
|
||||
public string OptName { get; set; }
|
||||
/// <summary>操作时间</summary>
|
||||
public DateTime OptDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace YLErp.DBModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 风控变量池定义表
|
||||
/// </summary>
|
||||
[Table("glms_risk_variable")]
|
||||
public class glms_risk_variable : DBModelWithOperator
|
||||
{
|
||||
/// <summary>变量名称</summary>
|
||||
public string VariableName { get; set; }
|
||||
/// <summary>变量分类</summary>
|
||||
public RiskVariableCategory Category { get; set; }
|
||||
/// <summary>数据类型</summary>
|
||||
public RiskVariableDataType DataType { get; set; }
|
||||
/// <summary>单位</summary>
|
||||
public string Unit { get; set; }
|
||||
/// <summary>值域描述</summary>
|
||||
public string ValueDomain { get; set; }
|
||||
/// <summary>变量说明</summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>取值表达式(C# 表达式,用于计算变量值)</summary>
|
||||
public string VariableExpr { get; set; }
|
||||
/// <summary>版本号(乐观锁)</summary>
|
||||
public int Version { get; set; } = 1;
|
||||
/// <summary>排序序号</summary>
|
||||
public int SortOrder { get; set; }
|
||||
/// <summary>最后更新人工号</summary>
|
||||
public int? UpdateOptId { get; set; }
|
||||
/// <summary>最后更新人姓名</summary>
|
||||
public string UpdateOptName { get; set; }
|
||||
/// <summary>最后更新时间</summary>
|
||||
public DateTime? UpdateDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using YLErp.Models;
|
||||
using YLErp.Models;
|
||||
|
||||
namespace YLErp.Enums
|
||||
{
|
||||
@@ -143,6 +143,11 @@ namespace YLErp.Enums
|
||||
[Description("预警")]
|
||||
Warning,
|
||||
/// <summary>
|
||||
/// 风控需审批
|
||||
/// </summary>
|
||||
[Description("风控需审批")]
|
||||
RiskWarning,
|
||||
/// <summary>
|
||||
/// 不通过
|
||||
/// </summary>
|
||||
[Description("不通过")]
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
CREATE TABLE `glms_risk_rule` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键Id',
|
||||
`RuleName` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '规则名称',
|
||||
`RuleText` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '规则文本',
|
||||
`ConditionJson` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '条件JSON(结构化模式, RuleCondition数组)',
|
||||
`RuleExpr` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '规则表达式(自由文本模式, 类C#表达式)',
|
||||
`Description` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
|
||||
`Status` tinyint NOT NULL DEFAULT 1 COMMENT '状态: 1=Active, 2=Disabled, 3=Deleted',
|
||||
`Version` int NOT NULL DEFAULT 1 COMMENT '版本号(乐观锁)',
|
||||
`OptId` int NULL DEFAULT NULL COMMENT '创建人Id',
|
||||
`OptName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人名称',
|
||||
`OptDate` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`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,
|
||||
INDEX `idx_status`(`Status` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '风控规则定义表' ROW_FORMAT = Dynamic;
|
||||
|
||||
CREATE TABLE `glms_risk_rule_application` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键Id',
|
||||
`RuleIds` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '关联规则Id列表(逗号分隔)',
|
||||
`Description` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
|
||||
`Status` tinyint NOT NULL DEFAULT 1 COMMENT '状态: 1=Active, 2=Disabled, 3=Deleted',
|
||||
`ControlStrategy` tinyint NOT NULL COMMENT '控制策略: 1=Block, 2=Approval, 3=Warning',
|
||||
`TriggerPoints` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '触发时点(逗号分隔)',
|
||||
`ScopeAssetBookIds` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '适用账户Id列表',
|
||||
`ScopeClientIds` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '适用对手方Id列表',
|
||||
`ScopeUnderlyingTypes` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '适用标的类型',
|
||||
`ScopeTradeTypes` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '适用合约类型',
|
||||
`ScopeIsGlobal` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否全局适用: 0=否, 1=是',
|
||||
`Version` int NOT NULL DEFAULT 1 COMMENT '版本号(乐观锁)',
|
||||
`OptId` int NULL DEFAULT NULL COMMENT '创建人Id',
|
||||
`OptName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人名称',
|
||||
`OptDate` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`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,
|
||||
INDEX `idx_status`(`Status` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '风控规则应用配置表' ROW_FORMAT = Dynamic;
|
||||
|
||||
CREATE TABLE `glms_risk_rule_audit_log` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键Id',
|
||||
`OperationType` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '操作类型(RULE_CREATE/RULE_UPDATE等)',
|
||||
`TargetType` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '目标类型(RULE/APPLICATION/VARIABLE)',
|
||||
`TargetId` bigint NOT NULL COMMENT '目标记录Id',
|
||||
`TargetName` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '目标名称',
|
||||
`OperationDetail` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '操作详情',
|
||||
`Result` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作结果',
|
||||
`SnapshotData` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '快照数据(JSON)',
|
||||
`OptId` bigint NOT NULL COMMENT '操作人Id',
|
||||
`OptName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作人名称',
|
||||
`OptDate` datetime NOT NULL COMMENT '操作时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_opt_date`(`OptDate` ASC) USING BTREE,
|
||||
INDEX `idx_operation_type`(`OperationType` ASC) USING BTREE,
|
||||
INDEX `idx_target_type`(`TargetType` ASC) USING BTREE,
|
||||
INDEX `idx_target_id`(`TargetId` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '风控规则操作审计日志表' ROW_FORMAT = Dynamic;
|
||||
|
||||
CREATE TABLE `glms_risk_variable` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键Id',
|
||||
`VariableName` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '变量名称',
|
||||
`Category` tinyint NOT NULL COMMENT '分类: 1=BookingElement, 2=MarketData, 3=SystemCalc, 4=BooleanCheck',
|
||||
`DataType` tinyint NOT NULL COMMENT '数据类型: 1=Numeric, 2=Date, 3=Boolean',
|
||||
`Unit` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '单位',
|
||||
`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#表达式)',
|
||||
`Version` int NOT NULL DEFAULT 1 COMMENT '版本号(乐观锁)',
|
||||
`SortOrder` int NOT NULL DEFAULT 0 COMMENT '排序',
|
||||
`OptId` int NULL DEFAULT NULL COMMENT '创建人Id',
|
||||
`OptName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人名称',
|
||||
`OptDate` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`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
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '风控变量池定义表' ROW_FORMAT = Dynamic;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
@@ -0,0 +1,135 @@
|
||||
-- ============================================================
|
||||
-- 规则应用初始数据(对应设计文档 §4.9.1 通用规则预置参考)
|
||||
-- 依赖:seed_rules.sql(规则数据需先插入)
|
||||
--
|
||||
-- 每条规则对应一条应用配置(1:1),定义"何时、对谁、怎么处理"
|
||||
-- ControlStrategy: 1=Block(禁止), 2=Approval(审批), 3=Warning(提示)
|
||||
-- TriggerPoints: BOOK_CONFIRM=簿记交易确认
|
||||
-- ScopeIsGlobal: 1=全局适用, 0=按维度配置(Scope字段为NULL表示"全部")
|
||||
-- ============================================================
|
||||
|
||||
-- 规则1:挂钩标的集中度超阈值(审批,全局)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '挂钩标的集中度超阈值';
|
||||
|
||||
-- 规则2:挂钩标的到期日小于合约到期日(禁止,全局)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 1, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '挂钩标的到期日小于合约到期日';
|
||||
|
||||
-- 规则3:名义本金超阈值(审批,账户/合约类型)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '名义本金超阈值';
|
||||
|
||||
-- 规则4:保证金支付比例超阈值(审批,账户/标的类型)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '保证金支付比例超阈值';
|
||||
|
||||
-- 规则5:保证金利率偏离(审批,账户)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '保证金利率偏离';
|
||||
|
||||
-- 规则6:保证金收取比例低于最低标准(审批,账户/标的类型)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '保证金收取比例低于最低标准';
|
||||
|
||||
-- 规则7:起息日早于当前日期(审批,全局)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '起息日早于当前日期';
|
||||
|
||||
-- 规则8:支付日为银行间交易日(审批,全局)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '支付日为银行间交易日';
|
||||
|
||||
-- 规则9:到期日为银行间交易日(审批,全局)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '到期日为银行间交易日';
|
||||
|
||||
-- 规则10:平仓日为银行间交易日(审批,全局)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '平仓日为银行间交易日';
|
||||
|
||||
-- 规则11:合约期限超阈值(审批,账户)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '合约期限超阈值';
|
||||
|
||||
-- 规则12:债券类净价偏离(审批,全局)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '债券类净价偏离';
|
||||
|
||||
-- 规则13:债券类收益率偏离(审批,全局)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '债券类收益率偏离';
|
||||
|
||||
-- 规则14:非债券类价格偏离(审批,全局)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '非债券类价格偏离';
|
||||
|
||||
-- 规则15:单一交易对手累计标的数量超阈值(审批,对手方)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '单一交易对手累计标的数量超阈值';
|
||||
|
||||
-- 规则16:多头支付固定端利率偏离(审批,账户)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '多头支付固定端利率偏离';
|
||||
|
||||
-- 规则17:空头利率减点借贷加权偏离(审批,账户)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '空头利率减点借贷加权偏离';
|
||||
|
||||
-- 规则18:账户授权收支方向不匹配(禁止,账户)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 1, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '账户授权收支方向不匹配';
|
||||
|
||||
-- 规则19:执行价偏离超阈值(审批,账户)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '执行价偏离超阈值';
|
||||
|
||||
-- 规则20:希腊字母限额超阈值(提示,账户/标的,预留接口)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 3, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '希腊字母限额超阈值';
|
||||
|
||||
-- 规则21:接近/触发敲入敲出价(提示,全局,预留接口)
|
||||
INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`)
|
||||
SELECT
|
||||
CAST(r.id AS CHAR), 1, 3, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW()
|
||||
FROM `glms_risk_rule` r WHERE r.RuleName = '接近/触发敲入敲出价';
|
||||
@@ -0,0 +1,50 @@
|
||||
-- ============================================================
|
||||
-- 异常交易监控页面 - 操作权限(按钮)数据
|
||||
-- 依赖:异常交易监控菜单 id=1077 (type=1) 需先插入
|
||||
-- 对应 FunctionRight.xml 风险控制下风控相关操作权限(共16个按钮)
|
||||
-- type: 0=目录 1=菜单 2=按钮
|
||||
-- ============================================================
|
||||
|
||||
-- ----------------------------
|
||||
-- 1. sys_menu 页面+按钮(type=1,2)
|
||||
-- ----------------------------
|
||||
INSERT INTO `yltrs_admin`.`sys_menu` (`id`, `pid`, `pids`, `name`, `code`, `type`, `icon`, `router`, `component`, `permission`, `application`, `open_type`, `visible`, `link`, `redirect`, `weight`, `sort`, `remark`, `status`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES
|
||||
(1077, 99, NULL, '异常交易监控', NULL, 1, NULL, NULL, NULL, '异常交易监控', NULL, NULL, NULL, NULL, NULL, NULL, 0, '', 0, NULL, NULL, NULL, NULL),
|
||||
(1078, 1077, NULL, '风控规则查看', NULL, 2, NULL, NULL, NULL, '风控规则查看', NULL, NULL, NULL, NULL, NULL, NULL, 1, '', 0, NULL, NULL, NULL, NULL),
|
||||
(1079, 1077, NULL, '风控规则新增', NULL, 2, NULL, NULL, NULL, '风控规则新增', NULL, NULL, NULL, NULL, NULL, NULL, 2, '', 0, NULL, NULL, NULL, NULL),
|
||||
(1080, 1077, NULL, '风控规则编辑', NULL, 2, NULL, NULL, NULL, '风控规则编辑', NULL, NULL, NULL, NULL, NULL, NULL, 3, '', 0, NULL, NULL, NULL, NULL),
|
||||
(1081, 1077, NULL, '风控规则删除', NULL, 2, NULL, NULL, NULL, '风控规则删除', NULL, NULL, NULL, NULL, NULL, NULL, 4, '', 0, NULL, NULL, NULL, NULL),
|
||||
(1082, 1077, NULL, '风控规则启停', NULL, 2, NULL, NULL, NULL, '风控规则启停', NULL, NULL, NULL, NULL, NULL, NULL, 5, '', 0, NULL, NULL, NULL, NULL),
|
||||
(1083, 1077, NULL, '风控应用查看', NULL, 2, NULL, NULL, NULL, '风控应用查看', NULL, NULL, NULL, NULL, NULL, NULL, 6, '', 0, NULL, NULL, NULL, NULL),
|
||||
(1084, 1077, NULL, '风控应用新增', NULL, 2, NULL, NULL, NULL, '风控应用新增', NULL, NULL, NULL, NULL, NULL, NULL, 7, '', 0, NULL, NULL, NULL, NULL),
|
||||
(1085, 1077, NULL, '风控应用编辑', NULL, 2, NULL, NULL, NULL, '风控应用编辑', NULL, NULL, NULL, NULL, NULL, NULL, 8, '', 0, NULL, NULL, NULL, NULL),
|
||||
(1086, 1077, NULL, '风控应用删除', NULL, 2, NULL, NULL, NULL, '风控应用删除', NULL, NULL, NULL, NULL, NULL, NULL, 9, '', 0, NULL, NULL, NULL, NULL),
|
||||
(1087, 1077, NULL, '风控应用启停', NULL, 2, NULL, NULL, NULL, '风控应用启停', NULL, NULL, NULL, NULL, NULL, NULL, 10, '', 0, NULL, NULL, NULL, NULL),
|
||||
(1088, 1077, NULL, '风控变量查看', NULL, 2, NULL, NULL, NULL, '风控变量查看', NULL, NULL, NULL, NULL, NULL, NULL, 11, '', 0, NULL, NULL, NULL, NULL),
|
||||
(1089, 1077, NULL, '风控变量新增', NULL, 2, NULL, NULL, NULL, '风控变量新增', NULL, NULL, NULL, NULL, NULL, NULL, 12, '', 0, NULL, NULL, NULL, NULL),
|
||||
(1090, 1077, NULL, '风控变量编辑', NULL, 2, NULL, NULL, NULL, '风控变量编辑', NULL, NULL, NULL, NULL, NULL, NULL, 13, '', 0, NULL, NULL, NULL, NULL),
|
||||
(1091, 1077, NULL, '风控变量删除', NULL, 2, NULL, NULL, NULL, '风控变量删除', NULL, NULL, NULL, NULL, NULL, NULL, 14, '', 0, NULL, NULL, NULL, NULL),
|
||||
(1092, 1077, NULL, '风控日志查看', NULL, 2, NULL, NULL, NULL, '风控日志查看', NULL, NULL, NULL, NULL, NULL, NULL, 15, '', 0, NULL, NULL, NULL, NULL),
|
||||
(1093, 1077, NULL, '风控日志导出', NULL, 2, NULL, NULL, NULL, '风控日志导出', NULL, NULL, NULL, NULL, NULL, NULL, 16, '', 0, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- ----------------------------
|
||||
-- 2. sys_role_menu 角色1的按钮权限
|
||||
-- ----------------------------
|
||||
INSERT INTO `yltrs_admin`.`sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES
|
||||
(1097, 1, 1077),
|
||||
(1098, 1, 1078),
|
||||
(1099, 1, 1079),
|
||||
(1100, 1, 1080),
|
||||
(1101, 1, 1081),
|
||||
(1102, 1, 1082),
|
||||
(1103, 1, 1083),
|
||||
(1104, 1, 1084),
|
||||
(1105, 1, 1085),
|
||||
(1106, 1, 1086),
|
||||
(1107, 1, 1087),
|
||||
(1108, 1, 1088),
|
||||
(1109, 1, 1089),
|
||||
(1110, 1, 1090),
|
||||
(1111, 1, 1091),
|
||||
(1112, 1, 1092),
|
||||
(1113, 1, 1093);
|
||||
@@ -0,0 +1,292 @@
|
||||
-- ============================================================
|
||||
-- 规则初始数据(对应设计文档 §4.9.1 通用规则预置参考)
|
||||
-- 依赖:seed_variables.sql(变量池数据需先插入,ID 1~44)
|
||||
--
|
||||
-- 变量 ID 映射(seed_variables.sql 自增):
|
||||
-- 1=合约名义本金 2=合约起息日 3=合约到期日 4=合约平仓日
|
||||
-- 5=合约支付日 6=期初净价 7=期初全价 8=期初收益率
|
||||
-- 9=期初价格 10=期末全价 11=期末价格 12=保证金利率
|
||||
-- 13=保证金比例 14=客户授信额度
|
||||
-- 15=上一收盘日中债估值净价 16=上一收盘日中债估值全价 17=上一收盘日中债估值收益率
|
||||
-- 18=上一日收盘价 19=借贷加权费率 20=FR007 21=当前日期
|
||||
-- 22=挂钩标的到期日 23=标的发行余额
|
||||
-- 24=挂钩标的集中度 25=授信占用率 26=合约期限 27=Delta
|
||||
-- 28=Gamma 29=Vega 30=Theta 31=利息端利率
|
||||
-- 32=对手方累计标的数量 33=同一标的累计名义本金 34=同一客户累计名义本金
|
||||
-- 35=总持仓名义本金
|
||||
-- 36=到期日是否银行间交易日 37=平仓日是否银行间交易日 38=支付日是否银行间交易日
|
||||
-- 39=利息端/浮动端方向是否同向 40=关键业务要素是否一致
|
||||
-- 41=多空方向为多头 42=多空方向为空头
|
||||
-- 43=保证金收支方向为支付 44=保证金收支方向为收取
|
||||
-- 45=期初净价偏离度 46=期初收益率偏离度 47=期初价格偏离度
|
||||
-- 48=利息端利率与FR007偏离度 49=利息端利率与借贷加权费率偏离度
|
||||
-- 50=执行价偏离度 51=执行价 52=客户品种最低保证金率 53=参考价格
|
||||
--
|
||||
-- ConditionJson 精简设计:只存 VariableId/Operator/ThresholdType/Value/ThresholdVariableId
|
||||
-- 展示字段(VariableName/VariableType/Unit/ThresholdVariableName)从变量接口关联获取
|
||||
-- ============================================================
|
||||
|
||||
|
||||
-- ============================================================
|
||||
-- 插入 21 条通用规则
|
||||
-- ============================================================
|
||||
|
||||
-- 规则1:挂钩标的集中度超阈值(审批,全局)
|
||||
-- 条件:挂钩标的集中度(ID=24) > 30%
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('挂钩标的集中度超阈值',
|
||||
'挂钩标的集中度超过阈值(默认30%)时触发审批',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 24,
|
||||
'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 30
|
||||
)),
|
||||
'calc.UnderlyingConcentration > 30',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则2:挂钩标的到期日小于合约到期日(禁止,全局)
|
||||
-- 条件:挂钩标的到期日(ID=22) < 合约到期日(ID=3)
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('挂钩标的到期日小于合约到期日',
|
||||
'挂钩标的到期日早于合约到期日时禁止交易',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 22,
|
||||
'Operator', '早于', 'ThresholdType', 'Variable',
|
||||
'ThresholdVariableId', 3
|
||||
)),
|
||||
'market.UnderlyingMaturityDate < trade.ExerciseDate',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则3:名义本金超阈值(审批,账户/合约类型)
|
||||
-- 条件:合约名义本金(ID=1) > 100000000(1亿元)
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('名义本金超阈值',
|
||||
'合约名义本金超过阈值(默认1亿元)时触发审批',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 1,
|
||||
'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 100000000
|
||||
)),
|
||||
'trade.StockEqvNotional > 100000000',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则4:保证金支付比例超阈值(审批,账户/标的类型)
|
||||
-- 条件:保证金比例(ID=13) > 50%
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('保证金支付比例超阈值',
|
||||
'保证金比例超过阈值(默认50%)时触发审批',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 13,
|
||||
'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 50
|
||||
)),
|
||||
'trade.MarginRate > 50',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则5:保证金利率偏离(审批,账户)
|
||||
-- 条件:保证金利率(ID=12) 不介于 [2%, 5%]
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('保证金利率偏离',
|
||||
'保证金利率不在配置区间内(默认2%~5%)时触发审批',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 12,
|
||||
'Operator', '不介于', 'ThresholdType', 'Fixed', 'Value', JSON_ARRAY(2, 5),
|
||||
'IncludeLowerBound', true, 'IncludeUpperBound', true
|
||||
)),
|
||||
'!(client_marginrate.InitMarginRebateRate >= 2 && client_marginrate.InitMarginRebateRate <= 5)',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则6:保证金收取比例低于最低标准(审批,账户/标的类型)
|
||||
-- 条件:保证金比例(ID=13) < 客户品种最低保证金率(ID=52)
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('保证金收取比例低于最低标准',
|
||||
'保证金比例低于客户品种最低保证金率时触发审批',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 13,
|
||||
'Operator', '<', 'ThresholdType', 'Variable',
|
||||
'ThresholdVariableId', 52
|
||||
)),
|
||||
'trade.MarginRate < config.MinMarginRate',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则7:起息日早于当前日期(审批,全局)
|
||||
-- 条件:合约起息日(ID=2) < 当前日期(ID=21)
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('起息日早于当前日期',
|
||||
'合约起息日早于当前日期时触发审批',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 2,
|
||||
'Operator', '早于', 'ThresholdType', 'Variable',
|
||||
'ThresholdVariableId', 21
|
||||
)),
|
||||
'trade.StartDate < sys.CurrentDate',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则8:支付日为银行间交易日(审批,全局)
|
||||
-- 条件:支付日是否银行间交易日(ID=38) = 是
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('支付日为银行间交易日',
|
||||
'支付日为银行间交易日时触发审批',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 38,
|
||||
'Operator', '是', 'ThresholdType', 'Fixed', 'Value', true
|
||||
)),
|
||||
'calc.IsSettlementDateTradingDay == true',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则9:到期日为银行间交易日(审批,全局)
|
||||
-- 条件:到期日是否银行间交易日(ID=36) = 是
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('到期日为银行间交易日',
|
||||
'到期日为银行间交易日时触发审批',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 36,
|
||||
'Operator', '是', 'ThresholdType', 'Fixed', 'Value', true
|
||||
)),
|
||||
'calc.IsExerciseDateTradingDay == true',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则10:平仓日为银行间交易日(审批,全局)
|
||||
-- 条件:平仓日是否银行间交易日(ID=37) = 是
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('平仓日为银行间交易日',
|
||||
'平仓日为银行间交易日时触发审批',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 37,
|
||||
'Operator', '是', 'ThresholdType', 'Fixed', 'Value', true
|
||||
)),
|
||||
'calc.IsUnwindDateTradingDay == true',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则11:合约期限超阈值(审批,账户)
|
||||
-- 条件:合约期限(ID=26) > 365天
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('合约期限超阈值',
|
||||
'合约期限超过阈值(默认365天)时触发审批',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 26,
|
||||
'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 365
|
||||
)),
|
||||
'calc.MaturityDays > 365',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则12:债券类净价偏离(审批,全局)
|
||||
-- 条件:期初净价偏离度(ID=45) > 5%
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('债券类净价偏离',
|
||||
'期初净价与上一收盘日中债估值净价偏离度超阈值(默认5%)时触发审批',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 45,
|
||||
'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 5
|
||||
)),
|
||||
'Math.Abs(swap_position.PosiNetNoFeePrice - market.CBValuationNetPrice) / market.CBValuationNetPrice * 100 > 5',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则13:债券类收益率偏离(审批,全局)
|
||||
-- 条件:期初收益率偏离度(ID=46) > 5%
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('债券类收益率偏离',
|
||||
'期初收益率与上一收盘日中债估值收益率偏离度超阈值(默认5%)时触发审批',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 46,
|
||||
'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 5
|
||||
)),
|
||||
'Math.Abs(trade.InitYtm - market.CBValuationYtm) / market.CBValuationYtm * 100 > 5',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则14:非债券类价格偏离(审批,全局)
|
||||
-- 条件:期初价格偏离度(ID=47) > 5%
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('非债券类价格偏离',
|
||||
'期初价格与上一日收盘价偏离度超阈值(默认5%)时触发审批',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 47,
|
||||
'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 5
|
||||
)),
|
||||
'Math.Abs(trade.SpotPrice - market.LastClosePrice) / market.LastClosePrice * 100 > 5',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则15:单一交易对手累计标的数量超阈值(审批,对手方)
|
||||
-- 条件:对手方累计标的数量(ID=32) > 10个
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('单一交易对手累计标的数量超阈值',
|
||||
'对手方累计标的数量超过阈值(默认10个)时触发审批',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 32,
|
||||
'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 10
|
||||
)),
|
||||
'calc.CounterpartyUnderlyingCount > 10',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则16:多头支付固定端利率偏离(审批,账户)
|
||||
-- 条件:多空方向为多头(ID=41) AND 利息端利率与FR007偏离度(ID=48) > 3%
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('多头支付固定端利率偏离',
|
||||
'多头方向支付固定端场景下,利息端利率与FR007偏离度超阈值(默认3%)时触发审批',
|
||||
JSON_ARRAY(
|
||||
JSON_OBJECT(
|
||||
'VariableId', 41,
|
||||
'Operator', '是', 'ThresholdType', 'Fixed', 'Value', true
|
||||
),
|
||||
JSON_OBJECT(
|
||||
'VariableId', 48,
|
||||
'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 3
|
||||
)
|
||||
),
|
||||
'calc.IsLongDirection == true && Math.Abs(calc.InterestRate - market.FR007) / market.FR007 * 100 > 3',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则17:空头利率减点借贷加权偏离(审批,账户)
|
||||
-- 条件:多空方向为空头(ID=42) AND 利息端利率与借贷加权费率偏离度(ID=49) > 2%
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('空头利率减点借贷加权偏离',
|
||||
'空头方向场景下,利息端利率与借贷加权费率偏离度超阈值(默认2%)时触发审批',
|
||||
JSON_ARRAY(
|
||||
JSON_OBJECT(
|
||||
'VariableId', 42,
|
||||
'Operator', '是', 'ThresholdType', 'Fixed', 'Value', true
|
||||
),
|
||||
JSON_OBJECT(
|
||||
'VariableId', 49,
|
||||
'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 2
|
||||
)
|
||||
),
|
||||
'calc.IsShortDirection == true && Math.Abs(calc.InterestRate - market.BondLendingRate) / market.BondLendingRate * 100 > 2',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则18:账户授权收支方向不匹配(禁止,账户)
|
||||
-- 条件:保证金收支方向为支付(ID=43)(实际需结合账户授权方向判断,此处简化)
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('账户授权收支方向不匹配',
|
||||
'保证金收支方向与账户授权方向不匹配时禁止交易',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 43,
|
||||
'Operator', '是', 'ThresholdType', 'Fixed', 'Value', true
|
||||
)),
|
||||
'calc.IsMarginPay == true',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则19:执行价偏离超阈值(审批,账户)
|
||||
-- 条件:执行价偏离度(ID=50) > 5%
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('执行价偏离超阈值',
|
||||
'执行价与参考价格偏离度超阈值(默认5%)时触发审批',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 50,
|
||||
'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 5
|
||||
)),
|
||||
'Math.Abs(trade.StrikePrice - market.ReferencePrice) / market.ReferencePrice * 100 > 5',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则20:希腊字母限额超阈值(提示,预留接口,一期不纳入)
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('希腊字母限额超阈值',
|
||||
'Delta/Gamma/Vega/Theta任一超阈值时提示(预留接口,一期不纳入)',
|
||||
NULL,
|
||||
NULL,
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 规则21:接近/触发敲入敲出价(提示,预留接口,需确认具体判断逻辑)
|
||||
INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('接近/触发敲入敲出价',
|
||||
'标的价格接近敲入/敲出价时提示(预留接口,需确认具体判断逻辑)',
|
||||
NULL,
|
||||
NULL,
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
@@ -0,0 +1,73 @@
|
||||
-- ============================================================
|
||||
-- 变量池初始数据(对应设计文档 §4.4 变量池完整清单)
|
||||
-- ============================================================
|
||||
|
||||
-- 4.4.1 簿记要素类(Category=1)
|
||||
INSERT INTO `glms_risk_variable` (`VariableName`, `Category`, `DataType`, `Unit`, `ValueDomain`, `Description`, `VariableExpr`, `SortOrder`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('合约名义本金', 1, 1, '元', '≥ 0', 'trade.StockEqvNotional 或 swap_position.PosiNotionalValue', 'trade.StockEqvNotional', 101, 0, 'SYSTEM', NOW()),
|
||||
('合约起息日', 1, 2, NULL, NULL, 'trade.StartDate 或 swap_position.PosiStartDate', 'trade.StartDate', 102, 0, 'SYSTEM', NOW()),
|
||||
('合约到期日', 1, 2, NULL, NULL, 'trade.ExerciseDate 或 swap_position.PosiMatuirityDate', 'trade.ExerciseDate', 103, 0, 'SYSTEM', NOW()),
|
||||
('合约平仓日', 1, 2, NULL, NULL, 'trade.UnWindDate', 'trade.UnWindDate', 104, 0, 'SYSTEM', NOW()),
|
||||
('合约支付日', 1, 2, NULL, NULL, 'trade.SettlementDate', 'trade.SettlementDate', 105, 0, 'SYSTEM', NOW()),
|
||||
('期初净价', 1, 1, '元', '≥ 0', 'swap_position.PosiNetNoFeePrice(债券 TRS)', 'swap_position.PosiNetNoFeePrice', 106, 0, 'SYSTEM', NOW()),
|
||||
('期初全价', 1, 1, '元', '≥ 0', 'swap_position.PosiNetFeePrice(债券 TRS)/ trade.SpotPrice(其他)', 'swap_position.PosiNetFeePrice', 107, 0, 'SYSTEM', NOW()),
|
||||
('期初收益率', 1, 1, '%', NULL, 'trade.InitYtm 或 swap_position.InitYtm', 'trade.InitYtm', 108, 0, 'SYSTEM', NOW()),
|
||||
('期初价格', 1, 1, '元', '≥ 0', 'trade.SpotPrice(非债券类)', 'trade.SpotPrice', 109, 0, 'SYSTEM', NOW()),
|
||||
('期末全价', 1, 1, '元', '≥ 0', 'eod_swap_position.UnderlyingPrice(债券 TRS)', 'eod_swap_position.UnderlyingPrice', 110, 0, 'SYSTEM', NOW()),
|
||||
('期末价格', 1, 1, '元', '≥ 0', 'trade.FinalPrice', 'trade.FinalPrice', 111, 0, 'SYSTEM', NOW()),
|
||||
('保证金利率', 1, 1, '%', NULL, 'client_marginrate.InitMarginRebateRate', 'client_marginrate.InitMarginRebateRate', 112, 0, 'SYSTEM', NOW()),
|
||||
('保证金比例', 1, 1, '%', '0~100', 'trade.MarginRate', 'trade.MarginRate', 113, 0, 'SYSTEM', NOW()),
|
||||
('客户授信额度', 1, 1, '元', '≥ 0', 'credit.Credit', 'credit.Credit', 114, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 4.4.2 行情类(Category=2,统一取上一交易日收盘价)
|
||||
INSERT INTO `glms_risk_variable` (`VariableName`, `Category`, `DataType`, `Unit`, `ValueDomain`, `Description`, `VariableExpr`, `SortOrder`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('上一收盘日中债估值净价', 2, 1, '元', '≥ 0', '资讯数据', 'market.CBValuationNetPrice', 201, 0, 'SYSTEM', NOW()),
|
||||
('上一收盘日中债估值全价', 2, 1, '元', '≥ 0', '资讯数据', 'market.CBValuationFullPrice', 202, 0, 'SYSTEM', NOW()),
|
||||
('上一收盘日中债估值收益率', 2, 1, '%', NULL, '资讯数据', 'market.CBValuationYtm', 203, 0, 'SYSTEM', NOW()),
|
||||
('上一日收盘价', 2, 1, '元', '≥ 0', '行情数据,按标的区分', 'market.LastClosePrice', 204, 0, 'SYSTEM', NOW()),
|
||||
('借贷加权费率', 2, 1, '%', NULL, 'CMDM 标的债券借贷费率行情表', 'market.BondLendingRate', 205, 0, 'SYSTEM', NOW()),
|
||||
('FR007', 2, 1, '%', NULL, '上一交易日收盘价', 'market.FR007', 206, 0, 'SYSTEM', NOW()),
|
||||
('当前日期', 2, 2, NULL, NULL, 'DateTime.Today', 'sys.CurrentDate', 207, 0, 'SYSTEM', NOW()),
|
||||
('挂钩标的到期日', 2, 2, NULL, NULL, '资讯数据', 'market.UnderlyingMaturityDate', 208, 0, 'SYSTEM', NOW()),
|
||||
('标的发行余额', 2, 1, '元', '≥ 0', '资讯数据', 'market.UnderlyingIssueBalance', 209, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 4.4.3 系统计算值类(Category=3)
|
||||
INSERT INTO `glms_risk_variable` (`VariableName`, `Category`, `DataType`, `Unit`, `ValueDomain`, `Description`, `VariableExpr`, `SortOrder`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('挂钩标的集中度', 3, 1, '%', '0~100', '同一标的存续交易总名义本金 ÷ 标的发行余额 × 100', 'calc.UnderlyingConcentration', 301, 0, 'SYSTEM', NOW()),
|
||||
('授信占用率', 3, 1, '%', '0~100', '(已占用授信 + 本笔授信占用) ÷ 授信总额 × 100', 'calc.CreditUsageRate', 302, 0, 'SYSTEM', NOW()),
|
||||
('合约期限', 3, 1, '天', '≥ 0', '(ExerciseDate - StartDate).Days', 'calc.MaturityDays', 303, 0, 'SYSTEM', NOW()),
|
||||
('Delta', 3, 1, NULL, NULL, 'realtime_trade_risk.Delta(预留接口,一期不纳入)', 'realtime_trade_risk.Delta', 304, 0, 'SYSTEM', NOW()),
|
||||
('Gamma', 3, 1, NULL, NULL, 'realtime_trade_risk.Gamma(预留接口,一期不纳入)', 'realtime_trade_risk.Gamma', 305, 0, 'SYSTEM', NOW()),
|
||||
('Vega', 3, 1, NULL, NULL, 'realtime_trade_risk.Vega(预留接口,一期不纳入)', 'realtime_trade_risk.Vega', 306, 0, 'SYSTEM', NOW()),
|
||||
('Theta', 3, 1, NULL, NULL, 'realtime_trade_risk.Theta(预留接口,一期不纳入)', 'realtime_trade_risk.Theta', 307, 0, 'SYSTEM', NOW()),
|
||||
('利息端利率', 3, 1, '%', NULL, '固定利率 或 FR007 ± 加点', 'calc.InterestRate', 308, 0, 'SYSTEM', NOW()),
|
||||
('对手方累计标的数量', 3, 1, '个', '≥ 0', 'COUNT(DISTINCT UnderlyingId) 该对手方所有存续交易,含本笔', 'calc.CounterpartyUnderlyingCount', 309, 0, 'SYSTEM', NOW()),
|
||||
('同一标的累计名义本金', 3, 1, '元', '≥ 0', 'SUM(该标的所有存续交易的 StockEqvNotional),含本笔', 'calc.SameUnderlyingTotalNotional', 310, 0, 'SYSTEM', NOW()),
|
||||
('同一客户累计名义本金', 3, 1, '元', '≥ 0', 'SUM(该客户所有存续交易的 StockEqvNotional),含本笔', 'calc.SameClientTotalNotional', 311, 0, 'SYSTEM', NOW()),
|
||||
('总持仓名义本金', 3, 1, '元', '≥ 0', 'SUM(所有存续交易的 StockEqvNotional)', 'calc.TotalPositionNotional', 312, 0, 'SYSTEM', NOW());
|
||||
-- 新增变量(ID 45~53):
|
||||
-- 45=期初净价偏离度 46=期初收益率偏离度 47=期初价格偏离度
|
||||
-- 48=利息端利率与FR007偏离度 49=利息端利率与借贷加权费率偏离度
|
||||
-- 50=执行价偏离度 51=执行价 52=客户品种最低保证金率 53=参考价格
|
||||
INSERT INTO `glms_risk_variable` (`VariableName`, `Category`, `DataType`, `Unit`, `ValueDomain`, `Description`, `VariableExpr`, `SortOrder`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('期初净价偏离度', 3, 1, '%', '≥ 0', 'ABS(期初净价-中债估值净价)/中债估值净价*100', 'Math.Abs(swap_position.PosiNetNoFeePrice - market.CBValuationNetPrice) / market.CBValuationNetPrice * 100', 313, 0, 'SYSTEM', NOW()),
|
||||
('期初收益率偏离度', 3, 1, '%', '≥ 0', 'ABS(期初收益率-中债估值收益率)/中债估值收益率*100', 'Math.Abs(trade.InitYtm - market.CBValuationYtm) / market.CBValuationYtm * 100', 314, 0, 'SYSTEM', NOW()),
|
||||
('期初价格偏离度', 3, 1, '%', '≥ 0', 'ABS(期初价格-上一日收盘价)/上一日收盘价*100', 'Math.Abs(trade.SpotPrice - market.LastClosePrice) / market.LastClosePrice * 100', 315, 0, 'SYSTEM', NOW()),
|
||||
('利息端利率与FR007偏离度', 3, 1, '%', '≥ 0', 'ABS(利息端利率-FR007)/FR007*100', 'Math.Abs(calc.InterestRate - market.FR007) / market.FR007 * 100', 316, 0, 'SYSTEM', NOW()),
|
||||
('利息端利率与借贷加权费率偏离度', 3, 1, '%', '≥ 0', 'ABS(利息端利率-借贷加权费率)/借贷加权费率*100', 'Math.Abs(calc.InterestRate - market.BondLendingRate) / market.BondLendingRate * 100', 317, 0, 'SYSTEM', NOW()),
|
||||
('执行价偏离度', 3, 1, '%', '≥ 0', 'ABS(执行价-参考价格)/参考价格*100', 'Math.Abs(trade.StrikePrice - market.ReferencePrice) / market.ReferencePrice * 100', 318, 0, 'SYSTEM', NOW()),
|
||||
('执行价', 1, 1, '元', '≥ 0', 'trade.StrikePrice', 'trade.StrikePrice', 115, 0, 'SYSTEM', NOW()),
|
||||
('客户品种最低保证金率', 1, 1, '%', '0~100', '客户品种最低保证金率(由账户/标的配置决定)', 'config.MinMarginRate', 116, 0, 'SYSTEM', NOW()),
|
||||
('参考价格', 2, 1, '元', '≥ 0', '执行价参考价格(行情数据)', 'market.ReferencePrice', 210, 0, 'SYSTEM', NOW());
|
||||
|
||||
-- 4.4.4 布尔判断类(Category=4)
|
||||
INSERT INTO `glms_risk_variable` (`VariableName`, `Category`, `DataType`, `Unit`, `ValueDomain`, `Description`, `VariableExpr`, `SortOrder`, `OptId`, `OptName`, `OptDate`) VALUES
|
||||
('到期日是否银行间交易日', 4, 3, NULL, NULL, '查询银行间交易日历', 'calc.IsExerciseDateTradingDay', 401, 0, 'SYSTEM', NOW()),
|
||||
('平仓日是否银行间交易日', 4, 3, NULL, NULL, '查询银行间交易日历', 'calc.IsUnwindDateTradingDay', 402, 0, 'SYSTEM', NOW()),
|
||||
('支付日是否银行间交易日', 4, 3, NULL, NULL, '查询银行间交易日历', 'calc.IsSettlementDateTradingDay', 403, 0, 'SYSTEM', NOW()),
|
||||
('利息端/浮动端方向是否同向', 4, 3, NULL, NULL, '利息端"收取"↔浮动端"多头",利息端"支付"↔浮动端"空头"', 'calc.IsInterestFloatSameDirection', 404, 0, 'SYSTEM', NOW()),
|
||||
('关键业务要素是否一致', 4, 3, NULL, NULL, '交易确认书 vs 簿记要素(大模型方案)', 'calc.IsKeyElementsConsistent', 405, 0, 'SYSTEM', NOW()),
|
||||
('多空方向为多头', 4, 3, NULL, NULL, 'trade.BuySell == "买入" 或浮动端为多头', 'calc.IsLongDirection', 406, 0, 'SYSTEM', NOW()),
|
||||
('多空方向为空头', 4, 3, NULL, NULL, '与多头互斥', 'calc.IsShortDirection', 407, 0, 'SYSTEM', NOW()),
|
||||
('保证金收支方向为支付', 4, 3, NULL, NULL, '保证金方向为支付', 'calc.IsMarginPay', 408, 0, 'SYSTEM', NOW()),
|
||||
('保证金收支方向为收取', 4, 3, NULL, NULL, '与支付互斥', 'calc.IsMarginReceive', 409, 0, 'SYSTEM', NOW());
|
||||
@@ -408,5 +408,10 @@ namespace YLErp.BLL
|
||||
public DbSet<trade_contract_oa_result> tradeContractOaResult { get; set; }
|
||||
public DbSet<BondPayment> bondPayment { get; set; }
|
||||
|
||||
public DbSet<glms_risk_rule> glms_risk_rule { get; set; }
|
||||
public DbSet<glms_risk_rule_application> glms_risk_rule_application { get; set; }
|
||||
public DbSet<glms_risk_rule_audit_log> glms_risk_rule_audit_log { get; set; }
|
||||
public DbSet<glms_risk_variable> glms_risk_variable { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将逗号分隔的字符串转换为字符串数组,并去除空白项与重复项。
|
||||
/// </summary>
|
||||
/// <param name="commaValues">逗号分隔的字符串。</param>
|
||||
/// <returns>去除空白项与重复项后的字符串数组。</returns>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 规则编译结果。
|
||||
/// 用于承载公式校验、Roslyn 编译结果和错误信息。
|
||||
/// </summary>
|
||||
public class RuleCompileResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否编译成功。
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 编译失败时的错误信息。
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 编译成功后的可执行委托。
|
||||
/// </summary>
|
||||
public Func<RiskContext, bool> CompiledScript { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建成功结果。
|
||||
/// </summary>
|
||||
public static RuleCompileResult Ok(Func<RiskContext, bool> compiledScript)
|
||||
{
|
||||
return new RuleCompileResult
|
||||
{
|
||||
Success = true,
|
||||
CompiledScript = compiledScript
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建失败结果。
|
||||
/// </summary>
|
||||
public static RuleCompileResult Fail(string errorMessage)
|
||||
{
|
||||
return new RuleCompileResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = errorMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 规则编译结果的进程内缓存。
|
||||
/// 当前第一版按规则 Id 缓存 Roslyn 编译后的可执行委托。
|
||||
/// </summary>
|
||||
public static class RuleCompiledCache
|
||||
{
|
||||
/// <summary>
|
||||
/// 规则编译结果的进程内缓存。ConcurrentDictionary为线程安全的字典,用于缓存编译后的规则委托,性能略低。
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, Func<RiskContext, bool>> Cache
|
||||
= new ConcurrentDictionary<string, Func<RiskContext, bool>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// 写入或更新规则编译结果。
|
||||
/// </summary>
|
||||
public static void Set(string ruleCode, Func<RiskContext, bool> compiledScript)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ruleCode) || compiledScript == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Cache[ruleCode] = compiledScript;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取规则编译结果。
|
||||
/// </summary>
|
||||
public static bool TryGet(string ruleCode, out Func<RiskContext, bool> compiledScript)
|
||||
{
|
||||
compiledScript = null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ruleCode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Cache.TryGetValue(ruleCode, out compiledScript);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除指定规则的编译结果。
|
||||
/// </summary>
|
||||
public static void Remove(string ruleCode)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ruleCode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Cache.TryRemove(ruleCode, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空全部规则编译结果。
|
||||
/// </summary>
|
||||
public static void Clear()
|
||||
{
|
||||
Cache.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
using Microsoft.CodeAnalysis.CSharp.Scripting;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using YLErp.BLL;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Roslyn 脚本全局变量容器
|
||||
/// </summary>
|
||||
public class ScriptGlobals
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前交易ID
|
||||
/// </summary>
|
||||
public int TradeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前触发时点
|
||||
/// </summary>
|
||||
public string TriggerPoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库上下文,供规则公式直接查询数据库
|
||||
/// </summary>
|
||||
public YLContext DbContext { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 规则编译器(Roslyn 版)
|
||||
///
|
||||
/// 编译时机:规则定义时(零运行时解析开销)
|
||||
/// 执行方式:判风控时直接调用 rule.CompiledScript(context) → bool
|
||||
///
|
||||
/// 相比表达式树方案的优势:
|
||||
/// - 代码简洁:生成 C# 字符串即可,无需手动拼接 Expression 节点
|
||||
/// - 灵活性高:天然支持多条件 AND/OR、复杂运算、未来可扩展任意 C# 语法
|
||||
/// - 可调试:生成的脚本代码可直接阅读和理解
|
||||
///
|
||||
/// 示例:
|
||||
/// RuleExpr: Convert.ToDecimal(DbContext.trade.First(t => t.id == TradeId).StockEqvNotional) > 100000000m
|
||||
/// 编译结果:Func<RiskContext, bool>(内部通过 Roslyn 编译缓存)
|
||||
/// </summary>
|
||||
public static class RuleCompiler
|
||||
{
|
||||
static IYcLogger _logger = LogFactory.GetLogger("RuleCompiler");
|
||||
|
||||
/// <summary>
|
||||
/// 校验并编译规则表达式。
|
||||
/// 当前 RuleExpr 要求是 Roslyn 可直接执行的 bool 表达式。
|
||||
/// ruleId 可为空,用于规则未落库时的公式校验。
|
||||
/// </summary>
|
||||
public static RuleCompileResult ValidateAndCompileFormula(long? ruleId, string formulaExp)
|
||||
{
|
||||
var ruleIdText = ruleId.HasValue ? ruleId.Value.ToString() : "未落库";
|
||||
if (string.IsNullOrWhiteSpace(formulaExp))
|
||||
{
|
||||
_logger.Error($"规则表达式为空,无法编译 - RuleId: {ruleIdText}");
|
||||
return RuleCompileResult.Fail("规则 RuleExpr 不能为空");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var compiled = CompileScript(ruleId, formulaExp, out string compileErrorMessage);
|
||||
if (compiled == null)
|
||||
{
|
||||
return RuleCompileResult.Fail(compileErrorMessage ?? "脚本编译失败");
|
||||
}
|
||||
|
||||
return RuleCompileResult.Ok(compiled);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"规则表达式校验异常 - RuleId: {ruleIdText}, Error: {ex.Message}\n脚本代码:{formulaExp}");
|
||||
return RuleCompileResult.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验并编译规则,编译成功后同时写入规则对象和内存缓存。
|
||||
/// </summary>
|
||||
public static RuleCompileResult ValidateAndCompileRule(RiskRule rule)
|
||||
{
|
||||
if (rule == null)
|
||||
{
|
||||
_logger.Error("规则对象为空,无法编译 - RuleId: 0");
|
||||
return RuleCompileResult.Fail("规则不能为空");
|
||||
}
|
||||
|
||||
if (rule.Id <= 0)
|
||||
{
|
||||
_logger.Error($"规则 Id 非法,无法编译 - RuleId: {rule.Id}");
|
||||
return RuleCompileResult.Fail("规则 Id 不能为空");
|
||||
}
|
||||
if (rule.Status != RiskRuleStatus.Active)
|
||||
{
|
||||
_logger.Error($"规则状态非启用,跳过编译 - RuleId: {rule.Id}, Status: {rule.Status}");
|
||||
return RuleCompileResult.Fail("规则 状态为未启动");
|
||||
}
|
||||
|
||||
var result = ValidateAndCompileFormula(rule.Id, rule.RuleExpr);
|
||||
if (!result.Success)
|
||||
{
|
||||
_logger.Error($"规则校验失败 - RuleId: {rule.Id}, Error: {result.ErrorMessage}");
|
||||
rule.CompiledScript = null;
|
||||
return RuleCompileResult.Fail($"规则[{rule.Id}]编译失败:{result.ErrorMessage}");
|
||||
}
|
||||
|
||||
rule.CompiledScript = result.CompiledScript;
|
||||
RuleCompiledCache.Set(rule.Id.ToString(), result.CompiledScript);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用 Roslyn 编译 C# 脚本代码为可执行委托
|
||||
/// </summary>
|
||||
private static Func<RiskContext, bool> CompileScript(long? ruleId, string scriptCode, out string errorMessage)
|
||||
{
|
||||
errorMessage = null;
|
||||
var ruleIdText = ruleId.HasValue ? ruleId.Value.ToString() : "未落库";
|
||||
|
||||
// 配置编译选项:引用必要的程序集
|
||||
var options = ScriptOptions.Default
|
||||
.WithReferences(
|
||||
typeof(RiskContext).Assembly,
|
||||
typeof(YLContext).Assembly,
|
||||
typeof(YLErp.DBModels.trade).Assembly,
|
||||
typeof(QdpCalendarHelper).Assembly,
|
||||
typeof(JsonConvert).Assembly,
|
||||
typeof(Microsoft.EntityFrameworkCore.DbContext).Assembly,
|
||||
typeof(Queryable).Assembly
|
||||
)
|
||||
.WithImports("System", "System.Linq", "Newtonsoft.Json", "YLErp.DBModels", "YLErp.QdpModule");
|
||||
|
||||
// 创建脚本(尚未执行,仅编译)
|
||||
var script = CSharpScript.Create<bool>(scriptCode, options, globalsType: typeof(ScriptGlobals));
|
||||
|
||||
// 编译(提前发现语法错误)
|
||||
//后续编译需提供接口,返回前端编译信息,包含编译错误列表
|
||||
var compilation = script.GetCompilation();
|
||||
var diagnostics = compilation.GetDiagnostics();
|
||||
var errors = diagnostics.Where(d => d.Severity == Microsoft.CodeAnalysis.DiagnosticSeverity.Error).ToList();
|
||||
if (errors.Any())
|
||||
{
|
||||
errorMessage = string.Join("; ", errors.Select(e => e.GetMessage()));
|
||||
_logger.Error($"规则编译失败 - RuleId: {ruleIdText}, Error: {errorMessage}\n脚本代码:{scriptCode}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 生成可调用委托
|
||||
var runner = script.CreateDelegate();
|
||||
|
||||
// 包装为同步的 Func<RiskContext, bool>
|
||||
return ctx =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var globals = new ScriptGlobals
|
||||
{
|
||||
TradeId = ctx.TradeId,
|
||||
TriggerPoint = ctx.TriggerPoint,
|
||||
DbContext = ctx.DbContext
|
||||
};
|
||||
return runner(globals).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 脚本执行异常(如空引用、类型转换失败)视为规则不触发
|
||||
_logger.Error($"规则执行异常 - RuleId: {ruleIdText}, Error: {ex.Message}\n脚本代码:{scriptCode}");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class BuildMemberAccessResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
|
||||
public string Expression { get; set; }
|
||||
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
public static BuildMemberAccessResult Ok(string expression)
|
||||
{
|
||||
return new BuildMemberAccessResult
|
||||
{
|
||||
Success = true,
|
||||
Expression = expression
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将操作符统一为 C# 操作符
|
||||
/// </summary>
|
||||
private static string FormatOperator(string op)
|
||||
{
|
||||
return op?.Trim() switch
|
||||
{
|
||||
// 数值型 C# 符号
|
||||
"=" or "==" => "==",
|
||||
"!=" or "≠" => "!=",
|
||||
">" => ">",
|
||||
"<" => "<",
|
||||
">=" => ">=",
|
||||
"<=" => "<=",
|
||||
// 日期型中文语义 → C# 符号
|
||||
"早于" => "<",
|
||||
"晚于" => ">",
|
||||
"等于" => "==",
|
||||
"不早于" => ">=",
|
||||
"不晚于" => "<=",
|
||||
_ => throw new ArgumentException($"不支持的操作符:{op}")
|
||||
};
|
||||
}
|
||||
public static BuildMemberAccessResult Fail(string errorMessage)
|
||||
{
|
||||
return new BuildMemberAccessResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = errorMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 批量操作结果
|
||||
/// </summary>
|
||||
public class BatchOperationResult
|
||||
{
|
||||
/// <summary>是否全部成功</summary>
|
||||
public bool Success { get; set; }
|
||||
/// <summary>总数量</summary>
|
||||
public int TotalCount { get; set; }
|
||||
/// <summary>成功数量</summary>
|
||||
public int SuccessCount { get; set; }
|
||||
/// <summary>错误信息</summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建应用配置请求
|
||||
/// </summary>
|
||||
public class CreateRiskApplicationReq
|
||||
{
|
||||
/// <summary>关联规则 ID 列表(JSON 数组)</summary>
|
||||
public string RuleIds { get; set; }
|
||||
/// <summary>应用说明</summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>风控策略</summary>
|
||||
public RiskControlStrategy ControlStrategy { get; set; }
|
||||
/// <summary>触发点列表(JSON 数组)</summary>
|
||||
public string TriggerPoints { get; set; }
|
||||
/// <summary>适用范围-资产账簿 ID 列表</summary>
|
||||
public string ScopeAssetBookIds { get; set; }
|
||||
/// <summary>适用范围-客户 ID 列表</summary>
|
||||
public string ScopeClientIds { get; set; }
|
||||
/// <summary>适用范围-标的类型列表</summary>
|
||||
public string ScopeUnderlyingTypes { get; set; }
|
||||
/// <summary>适用范围-交易类型列表</summary>
|
||||
public string ScopeTradeTypes { get; set; }
|
||||
/// <summary>是否全局生效</summary>
|
||||
public int ScopeIsGlobal { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建风控规则请求
|
||||
/// </summary>
|
||||
public class CreateRiskRuleReq
|
||||
{
|
||||
/// <summary>规则名称</summary>
|
||||
public string RuleName { get; set; }
|
||||
/// <summary>规则描述文本</summary>
|
||||
public string RuleText { get; set; }
|
||||
/// <summary>结构化条件 JSON</summary>
|
||||
public string ConditionJson { get; set; }
|
||||
/// <summary>规则执行表达式</summary>
|
||||
public string RuleExpr { get; set; }
|
||||
/// <summary>规则说明</summary>
|
||||
public string Description { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建变量请求
|
||||
/// </summary>
|
||||
public class CreateRiskVariableReq
|
||||
{
|
||||
/// <summary>变量名称</summary>
|
||||
public string VariableName { get; set; }
|
||||
/// <summary>变量分类</summary>
|
||||
public RiskVariableCategory Category { get; set; }
|
||||
/// <summary>数据类型</summary>
|
||||
public RiskVariableDataType DataType { get; set; }
|
||||
/// <summary>单位</summary>
|
||||
public string Unit { get; set; }
|
||||
/// <summary>值域描述</summary>
|
||||
public string ValueDomain { get; set; }
|
||||
/// <summary>变量说明</summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>取值表达式</summary>
|
||||
public string VariableExpr { get; set; }
|
||||
/// <summary>排序序号</summary>
|
||||
public int SortOrder { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using BaseOUDAL;
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 查询应用配置请求
|
||||
/// </summary>
|
||||
public class QueryRiskApplicationReq : BaseSearchReq
|
||||
{
|
||||
/// <summary>规则名称</summary>
|
||||
public string RuleName { get; set; }
|
||||
/// <summary>应用配置状态</summary>
|
||||
public RiskRuleStatus? Status { get; set; }
|
||||
/// <summary>风控策略</summary>
|
||||
public RiskControlStrategy? Strategy { get; set; }
|
||||
/// <summary>触发点</summary>
|
||||
public string TriggerPoint { get; set; }
|
||||
/// <summary>适用范围-客户 ID 列表</summary>
|
||||
public string ScopeClientIds { get; set; }
|
||||
/// <summary>适用范围-标的类型列表</summary>
|
||||
public string ScopeUnderlyingTypes { get; set; }
|
||||
/// <summary>适用范围-交易类型列表</summary>
|
||||
public string ScopeTradeTypes { get; set; }
|
||||
/// <summary>适用范围-资产账簿 ID 列表</summary>
|
||||
public string ScopeAssetBookIds { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using BaseOUDAL;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 查询审计日志请求
|
||||
/// </summary>
|
||||
public class QueryRiskAuditLogReq : BaseSearchReq
|
||||
{
|
||||
/// <summary>操作类型</summary>
|
||||
public string OperationType { get; set; }
|
||||
/// <summary>操作类型列表(多选,逗号分隔)</summary>
|
||||
public string OperationTypes { get; set; }
|
||||
/// <summary>操作目标类型</summary>
|
||||
public string TargetType { get; set; }
|
||||
/// <summary>操作目标名称</summary>
|
||||
public string TargetName { get; set; }
|
||||
/// <summary>操作人姓名</summary>
|
||||
public string OptName { get; set; }
|
||||
/// <summary>开始日期</summary>
|
||||
public DateTime? StartDate { get; set; }
|
||||
/// <summary>结束日期</summary>
|
||||
public DateTime? EndDate { get; set; }
|
||||
/// <summary>搜索关键词</summary>
|
||||
public string Keyword { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using BaseOUDAL;
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 查询风控规则请求
|
||||
/// </summary>
|
||||
public class QueryRiskRuleReq : BaseSearchReq
|
||||
{
|
||||
/// <summary>搜索关键词</summary>
|
||||
public string Keyword { get; set; }
|
||||
/// <summary>规则状态</summary>
|
||||
public RiskRuleStatus? Status { get; set; }
|
||||
/// <summary>关联变量 ID</summary>
|
||||
public long? VariableId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using BaseOUDAL;
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 查询变量请求
|
||||
/// </summary>
|
||||
public class QueryRiskVariableReq : BaseSearchReq
|
||||
{
|
||||
/// <summary>变量分类</summary>
|
||||
public RiskVariableCategory? Category { get; set; }
|
||||
/// <summary>数据类型</summary>
|
||||
public RiskVariableDataType? DataType { get; set; }
|
||||
/// <summary>变量名称</summary>
|
||||
public string VariableName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用配置详情响应
|
||||
/// </summary>
|
||||
public class RiskApplicationDetail
|
||||
{
|
||||
/// <summary>应用配置 ID</summary>
|
||||
public long Id { get; set; }
|
||||
/// <summary>关联规则 ID 列表(JSON 数组)</summary>
|
||||
public string RuleIds { get; set; }
|
||||
/// <summary>应用说明</summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>应用配置状态</summary>
|
||||
public RiskRuleStatus Status { get; set; }
|
||||
/// <summary>风控策略</summary>
|
||||
public RiskControlStrategy ControlStrategy { get; set; }
|
||||
/// <summary>触发点列表(JSON 数组)</summary>
|
||||
public string TriggerPoints { get; set; }
|
||||
/// <summary>适用范围-资产账簿 ID 列表</summary>
|
||||
public string ScopeAssetBookIds { get; set; }
|
||||
/// <summary>适用范围-客户 ID 列表</summary>
|
||||
public string ScopeClientIds { get; set; }
|
||||
/// <summary>适用范围-标的类型列表</summary>
|
||||
public string ScopeUnderlyingTypes { get; set; }
|
||||
/// <summary>适用范围-交易类型列表</summary>
|
||||
public string ScopeTradeTypes { get; set; }
|
||||
/// <summary>是否全局生效</summary>
|
||||
public int ScopeIsGlobal { get; set; }
|
||||
/// <summary>版本号</summary>
|
||||
public int Version { get; set; }
|
||||
/// <summary>创建人姓名</summary>
|
||||
public string OptName { get; set; }
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime OptDate { get; set; }
|
||||
/// <summary>最后更新人姓名</summary>
|
||||
public string UpdateOptName { get; set; }
|
||||
/// <summary>最后更新时间</summary>
|
||||
public DateTime UpdateDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用配置列表项
|
||||
/// </summary>
|
||||
public class RiskApplicationListItem
|
||||
{
|
||||
/// <summary>应用配置 ID</summary>
|
||||
public long Id { get; set; }
|
||||
/// <summary>关联规则 ID 列表(JSON 数组)</summary>
|
||||
public string RuleIds { get; set; }
|
||||
/// <summary>应用说明</summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>应用配置状态</summary>
|
||||
public int Status { get; set; }
|
||||
/// <summary>风控策略</summary>
|
||||
public RiskControlStrategy ControlStrategy { get; set; }
|
||||
/// <summary>触发点列表(JSON 数组)</summary>
|
||||
public string TriggerPoints { get; set; }
|
||||
/// <summary>适用范围-资产账簿 ID 列表</summary>
|
||||
public string ScopeAssetBookIds { get; set; }
|
||||
/// <summary>适用范围-客户 ID 列表</summary>
|
||||
public string ScopeClientIds { get; set; }
|
||||
/// <summary>适用范围-标的类型列表</summary>
|
||||
public string ScopeUnderlyingTypes { get; set; }
|
||||
/// <summary>适用范围-交易类型列表</summary>
|
||||
public string ScopeTradeTypes { get; set; }
|
||||
/// <summary>是否全局生效</summary>
|
||||
public int ScopeIsGlobal { get; set; }
|
||||
/// <summary>版本号</summary>
|
||||
public int Version { get; set; }
|
||||
/// <summary>创建人姓名</summary>
|
||||
public string OptName { get; set; }
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime OptDate { get; set; }
|
||||
/// <summary>最后更新人姓名</summary>
|
||||
public string UpdateOptName { get; set; }
|
||||
/// <summary>最后更新时间</summary>
|
||||
public DateTime UpdateDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 审计日志详情响应
|
||||
/// </summary>
|
||||
public class RiskAuditLogDetail
|
||||
{
|
||||
/// <summary>日志 ID</summary>
|
||||
public long Id { get; set; }
|
||||
/// <summary>操作类型</summary>
|
||||
public string OperationType { get; set; }
|
||||
/// <summary>操作目标类型</summary>
|
||||
public string TargetType { get; set; }
|
||||
/// <summary>操作目标 ID</summary>
|
||||
public long TargetId { get; set; }
|
||||
/// <summary>操作目标名称</summary>
|
||||
public string TargetName { get; set; }
|
||||
/// <summary>操作详情描述</summary>
|
||||
public string OperationDetail { get; set; }
|
||||
/// <summary>操作结果</summary>
|
||||
public string Result { get; set; }
|
||||
/// <summary>操作前数据快照(JSON)</summary>
|
||||
public string SnapshotData { get; set; }
|
||||
/// <summary>操作人工号</summary>
|
||||
public long OptId { get; set; }
|
||||
/// <summary>操作人姓名</summary>
|
||||
public string OptName { get; set; }
|
||||
/// <summary>操作时间</summary>
|
||||
public DateTime OptDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 审计日志列表项
|
||||
/// </summary>
|
||||
public class RiskAuditLogListItem
|
||||
{
|
||||
/// <summary>日志 ID</summary>
|
||||
public long Id { get; set; }
|
||||
/// <summary>操作类型</summary>
|
||||
public string OperationType { get; set; }
|
||||
/// <summary>操作目标类型</summary>
|
||||
public string TargetType { get; set; }
|
||||
/// <summary>操作目标 ID</summary>
|
||||
public long TargetId { get; set; }
|
||||
/// <summary>操作目标名称</summary>
|
||||
public string TargetName { get; set; }
|
||||
/// <summary>操作详情描述</summary>
|
||||
public string OperationDetail { get; set; }
|
||||
/// <summary>操作结果</summary>
|
||||
public string Result { get; set; }
|
||||
/// <summary>操作人姓名</summary>
|
||||
public string OptName { get; set; }
|
||||
/// <summary>操作时间</summary>
|
||||
public DateTime OptDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 规则关联应用摘要
|
||||
/// </summary>
|
||||
public class RiskRuleApplicationSummary
|
||||
{
|
||||
/// <summary>应用配置 ID</summary>
|
||||
public long Id { get; set; }
|
||||
/// <summary>应用配置状态</summary>
|
||||
public RiskRuleStatus Status { get; set; }
|
||||
/// <summary>风控策略</summary>
|
||||
public RiskControlStrategy ControlStrategy { get; set; }
|
||||
/// <summary>触发点列表</summary>
|
||||
public string TriggerPoints { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 风控规则详情响应
|
||||
/// </summary>
|
||||
public class RiskRuleDetail
|
||||
{
|
||||
/// <summary>规则 ID</summary>
|
||||
public long Id { get; set; }
|
||||
/// <summary>规则名称</summary>
|
||||
public string RuleName { get; set; }
|
||||
/// <summary>规则描述文本</summary>
|
||||
public string RuleText { get; set; }
|
||||
/// <summary>结构化条件 JSON</summary>
|
||||
public string ConditionJson { get; set; }
|
||||
/// <summary>规则执行表达式</summary>
|
||||
public string RuleExpr { get; set; }
|
||||
/// <summary>规则说明</summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>规则状态</summary>
|
||||
public RiskRuleStatus Status { get; set; }
|
||||
/// <summary>版本号</summary>
|
||||
public int Version { get; set; }
|
||||
/// <summary>创建人姓名</summary>
|
||||
public string OptName { get; set; }
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime OptDate { get; set; }
|
||||
/// <summary>最后更新人姓名</summary>
|
||||
public string UpdateOptName { get; set; }
|
||||
/// <summary>最后更新时间</summary>
|
||||
public DateTime UpdateDate { get; set; }
|
||||
/// <summary>关联应用列表</summary>
|
||||
public List<RiskRuleApplicationSummary> Applications { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 风控规则列表项
|
||||
/// </summary>
|
||||
public class RiskRuleListItem
|
||||
{
|
||||
/// <summary>规则 ID</summary>
|
||||
public long Id { get; set; }
|
||||
/// <summary>规则名称</summary>
|
||||
public string RuleName { get; set; }
|
||||
/// <summary>规则描述文本</summary>
|
||||
public string RuleText { get; set; }
|
||||
/// <summary>结构化条件 JSON</summary>
|
||||
public string ConditionJson { get; set; }
|
||||
/// <summary>规则执行表达式</summary>
|
||||
public string RuleExpr { get; set; }
|
||||
/// <summary>规则说明</summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>规则状态</summary>
|
||||
public RiskRuleStatus Status { get; set; }
|
||||
/// <summary>版本号</summary>
|
||||
public int Version { get; set; }
|
||||
/// <summary>创建人姓名</summary>
|
||||
public string OptName { get; set; }
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime OptDate { get; set; }
|
||||
/// <summary>最后更新人姓名</summary>
|
||||
public string UpdateOptName { get; set; }
|
||||
/// <summary>最后更新时间</summary>
|
||||
public DateTime UpdateDate { get; set; }
|
||||
/// <summary>关联应用数量</summary>
|
||||
public int ApplicationCount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 风控规则简要信息
|
||||
/// </summary>
|
||||
public class RiskRuleSimpleItem
|
||||
{
|
||||
/// <summary>规则 ID</summary>
|
||||
public long Id { get; set; }
|
||||
/// <summary>规则名称</summary>
|
||||
public string RuleName { get; set; }
|
||||
/// <summary>规则描述文本</summary>
|
||||
public string RuleText { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 风控规则版本历史项
|
||||
/// </summary>
|
||||
public class RiskRuleVersionItem
|
||||
{
|
||||
/// <summary>版本号</summary>
|
||||
public int Version { get; set; }
|
||||
/// <summary>操作类型</summary>
|
||||
public string OperationType { get; set; }
|
||||
/// <summary>操作详情</summary>
|
||||
public string OperationDetail { get; set; }
|
||||
/// <summary>操作人姓名</summary>
|
||||
public string OptName { get; set; }
|
||||
/// <summary>操作时间</summary>
|
||||
public DateTime OptDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 变量详情响应
|
||||
/// </summary>
|
||||
public class RiskVariableDetail
|
||||
{
|
||||
/// <summary>变量 ID</summary>
|
||||
public long Id { get; set; }
|
||||
/// <summary>变量名称</summary>
|
||||
public string VariableName { get; set; }
|
||||
/// <summary>变量分类</summary>
|
||||
public RiskVariableCategory Category { get; set; }
|
||||
/// <summary>数据类型</summary>
|
||||
public RiskVariableDataType DataType { get; set; }
|
||||
/// <summary>单位</summary>
|
||||
public string Unit { get; set; }
|
||||
/// <summary>值域描述</summary>
|
||||
public string ValueDomain { get; set; }
|
||||
/// <summary>变量说明</summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>取值表达式</summary>
|
||||
public string VariableExpr { get; set; }
|
||||
/// <summary>版本号</summary>
|
||||
public int Version { get; set; }
|
||||
/// <summary>排序序号</summary>
|
||||
public int SortOrder { get; set; }
|
||||
/// <summary>创建人姓名</summary>
|
||||
public string OptName { get; set; }
|
||||
/// <summary>创建时间</summary>
|
||||
public DateTime OptDate { get; set; }
|
||||
/// <summary>最后更新人姓名</summary>
|
||||
public string UpdateOptName { get; set; }
|
||||
/// <summary>最后更新时间</summary>
|
||||
public DateTime UpdateDate { get; set; }
|
||||
/// <summary>被引用次数</summary>
|
||||
public int ReferenceCount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 变量列表项
|
||||
/// </summary>
|
||||
public class RiskVariableListItem
|
||||
{
|
||||
/// <summary>变量 ID</summary>
|
||||
public long Id { get; set; }
|
||||
/// <summary>变量名称</summary>
|
||||
public string VariableName { get; set; }
|
||||
/// <summary>变量分类</summary>
|
||||
public RiskVariableCategory Category { get; set; }
|
||||
/// <summary>数据类型</summary>
|
||||
public RiskVariableDataType DataType { get; set; }
|
||||
/// <summary>单位</summary>
|
||||
public string Unit { get; set; }
|
||||
/// <summary>值域描述</summary>
|
||||
public string ValueDomain { get; set; }
|
||||
/// <summary>变量说明</summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>取值表达式</summary>
|
||||
public string VariableExpr { get; set; }
|
||||
/// <summary>版本号</summary>
|
||||
public int Version { get; set; }
|
||||
/// <summary>排序序号</summary>
|
||||
public int SortOrder { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 变量简要信息
|
||||
/// </summary>
|
||||
public class RiskVariableSimpleItem
|
||||
{
|
||||
/// <summary>变量 ID</summary>
|
||||
public long Id { get; set; }
|
||||
/// <summary>变量名称</summary>
|
||||
public string VariableName { get; set; }
|
||||
/// <summary>变量分类</summary>
|
||||
public RiskVariableCategory Category { get; set; }
|
||||
/// <summary>数据类型</summary>
|
||||
public RiskVariableDataType DataType { get; set; }
|
||||
/// <summary>单位</summary>
|
||||
public string Unit { get; set; }
|
||||
/// <summary>取值表达式</summary>
|
||||
public string VariableExpr { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 规则条件项
|
||||
/// </summary>
|
||||
public class RuleCondition
|
||||
{
|
||||
/// <summary>条件变量 ID</summary>
|
||||
public long VariableId { get; set; }
|
||||
/// <summary>操作符(如 Equal、GreaterThan、Between 等)</summary>
|
||||
public string Operator { get; set; }
|
||||
/// <summary>阈值类型(FixedValue / VariableRef)</summary>
|
||||
public string ThresholdType { get; set; }
|
||||
/// <summary>阈值(固定值或变量引用)</summary>
|
||||
public object Value { get; set; }
|
||||
/// <summary>阈值变量 ID(当 ThresholdType 为 VariableRef 时)</summary>
|
||||
public long? ThresholdVariableId { get; set; }
|
||||
/// <summary>介于/不介于的下边界是否包含,默认 true</summary>
|
||||
public bool IncludeLowerBound { get; set; } = true;
|
||||
/// <summary>介于/不介于的上边界是否包含,默认 true</summary>
|
||||
public bool IncludeUpperBound { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 更新应用配置请求
|
||||
/// </summary>
|
||||
public class UpdateRiskApplicationReq
|
||||
{
|
||||
/// <summary>关联规则 ID 列表(JSON 数组)</summary>
|
||||
public string RuleIds { get; set; }
|
||||
/// <summary>应用说明</summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>风控策略</summary>
|
||||
public RiskControlStrategy ControlStrategy { get; set; }
|
||||
/// <summary>触发点列表(JSON 数组)</summary>
|
||||
public string TriggerPoints { get; set; }
|
||||
/// <summary>适用范围-资产账簿 ID 列表</summary>
|
||||
public string ScopeAssetBookIds { get; set; }
|
||||
/// <summary>适用范围-客户 ID 列表</summary>
|
||||
public string ScopeClientIds { get; set; }
|
||||
/// <summary>适用范围-标的类型列表</summary>
|
||||
public string ScopeUnderlyingTypes { get; set; }
|
||||
/// <summary>适用范围-交易类型列表</summary>
|
||||
public string ScopeTradeTypes { get; set; }
|
||||
/// <summary>是否全局生效</summary>
|
||||
public int ScopeIsGlobal { get; set; }
|
||||
/// <summary>版本号(乐观锁)</summary>
|
||||
public int Version { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 更新风控规则请求
|
||||
/// </summary>
|
||||
public class UpdateRiskRuleReq
|
||||
{
|
||||
/// <summary>规则名称</summary>
|
||||
public string RuleName { get; set; }
|
||||
/// <summary>规则描述文本</summary>
|
||||
public string RuleText { get; set; }
|
||||
/// <summary>结构化条件 JSON</summary>
|
||||
public string ConditionJson { get; set; }
|
||||
/// <summary>规则执行表达式</summary>
|
||||
public string RuleExpr { get; set; }
|
||||
/// <summary>规则说明</summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>版本号(乐观锁)</summary>
|
||||
public int Version { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 更新变量请求
|
||||
/// </summary>
|
||||
public class UpdateRiskVariableReq
|
||||
{
|
||||
/// <summary>变量名称</summary>
|
||||
public string VariableName { get; set; }
|
||||
/// <summary>变量分类</summary>
|
||||
public RiskVariableCategory Category { get; set; }
|
||||
/// <summary>数据类型</summary>
|
||||
public RiskVariableDataType DataType { get; set; }
|
||||
/// <summary>单位</summary>
|
||||
public string Unit { get; set; }
|
||||
/// <summary>值域描述</summary>
|
||||
public string ValueDomain { get; set; }
|
||||
/// <summary>变量说明</summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>取值表达式</summary>
|
||||
public string VariableExpr { get; set; }
|
||||
/// <summary>排序序号</summary>
|
||||
public int SortOrder { get; set; }
|
||||
/// <summary>版本号(乐观锁)</summary>
|
||||
public int Version { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using YLErp.BLL;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 风控上下文:封装一次风控检查所需的全部数据上下文
|
||||
/// </summary>
|
||||
public class RiskContext
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前交易ID
|
||||
/// </summary>
|
||||
public int TradeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前触发时点
|
||||
/// </summary>
|
||||
public string TriggerPoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库上下文,供规则公式直接查询数据库
|
||||
/// </summary>
|
||||
public YLContext DbContext { get; set; }
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 风控结果
|
||||
/// </summary>
|
||||
public class RiskResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否全部通过
|
||||
/// </summary>
|
||||
public bool Passed { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否被禁止
|
||||
/// </summary>
|
||||
public bool Blocked { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否需要审批
|
||||
/// </summary>
|
||||
public bool NeedApproval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否存在提示
|
||||
/// </summary>
|
||||
public bool ShowTip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 触发的规则列表
|
||||
/// </summary>
|
||||
public List<TriggeredRuleInfo> TriggeredRules { get; set; } = new List<TriggeredRuleInfo>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发规则详情
|
||||
/// </summary>
|
||||
public class TriggeredRuleInfo
|
||||
{
|
||||
public string RuleId { get; set; }
|
||||
public string RuleName { get; set; }
|
||||
public RiskControlStrategy ControlStrategy { get; set; }
|
||||
public string RuleText { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 风控规则定义。
|
||||
/// 只描述“规则是什么”,不包含状态、策略、触发时点、适用范围等应用配置。
|
||||
/// 这些执行维度由 RiskRuleApplication 承载。
|
||||
/// </summary>
|
||||
public class RiskRule
|
||||
{
|
||||
// === 基础信息 ===
|
||||
public long Id { get; set; }
|
||||
public string RuleName { get; set; }
|
||||
public string RuleText { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 【核心】条件JSON(结构化模式的条件列表存储),如
|
||||
/// {"conditions":[{"variableId":1,"operator":">","value":100000000}]}
|
||||
/// 由 RuleCompiler 解析并编译为 CompiledScript
|
||||
/// </summary>
|
||||
public string ConditionJson { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 【核心】规则表达式(自由文本模式,类C#表达式)
|
||||
/// 由 RuleCompiler 编译为 CompiledScript
|
||||
/// </summary>
|
||||
public string RuleExpr { get; set; }
|
||||
|
||||
// === 生命周期 ===
|
||||
public int Version { get; set; }
|
||||
public RiskRuleStatus Status { get; set; }
|
||||
|
||||
// === 审计字段 ===
|
||||
public int OptId { get; set; }
|
||||
public string OptName { get; set; }
|
||||
public DateTime OptDate { get; set; }
|
||||
public int UpdateOptId { get; set; }
|
||||
public string UpdateOptName { get; set; }
|
||||
public DateTime UpdateDate { get; set; }
|
||||
|
||||
// === 运行时内存字段(不持久化到数据库)===
|
||||
/// <summary>
|
||||
/// 【核心】预编译的可执行委托
|
||||
/// 在规则创建/加载时由 RuleCompiler.Compile(this) 生成
|
||||
/// 判风控时直接调用:rule.CompiledScript(context) → bool
|
||||
/// </summary>
|
||||
public Func<RiskContext, bool> CompiledScript { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 风控规则应用配置。
|
||||
///
|
||||
/// 设计原因:
|
||||
/// 依据设计文档,风控规则需要区分“规则定义”和“规则应用”两个层面。
|
||||
/// RiskRule 负责定义“规则是什么”,例如规则名称、规则编码、公式表达式、描述、版本等;
|
||||
/// RiskRuleApplication 负责定义“规则在什么场景下如何执行”,例如启用状态、校验策略、触发时点、适用范围。
|
||||
///
|
||||
/// 拆分后的好处是:
|
||||
/// 1. 同一条规则可以被多个应用配置复用,避免重复维护相同的公式定义。
|
||||
/// 2. 不同业务场景可以对同一条规则配置不同的执行策略,例如在簿记确认时禁止,在平仓审核时提示。
|
||||
/// 3. 调整触发时点、适用范围、控制策略时,不需要修改规则本身,降低配置耦合。
|
||||
/// 4. 后续如果要做规则管理页面、规则启停、按场景灰度投放,也更容易扩展。
|
||||
///
|
||||
/// 当前文件先定义第一版最小模型,用于支撑基础流程跑通和示例数据加载。
|
||||
/// </summary>
|
||||
public class RiskRuleApplication
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用配置主键ID。
|
||||
/// 用于唯一标识一条规则应用记录。
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联的规则ID列表。
|
||||
/// 通过该字段与 RiskRule.Id 关联,多个规则ID使用逗号分隔。
|
||||
/// </summary>
|
||||
|
||||
public string RuleIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用状态。
|
||||
/// 约定:
|
||||
/// 1 = Active(已生效)
|
||||
/// 2 = Disabled(已停用)
|
||||
/// 3 = Deleted(已删除)
|
||||
/// 在实际执行时,通常只加载 Active 状态的应用配置。
|
||||
/// </summary>
|
||||
public RiskRuleStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 校验策略。
|
||||
/// 约定:
|
||||
/// 1 = 禁止
|
||||
/// 2 = 审批
|
||||
/// 3 = 提示
|
||||
/// 当规则命中时,由该字段决定最终处理方式。
|
||||
/// </summary>
|
||||
public RiskControlStrategy ControlStrategy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 触发时点。
|
||||
/// 多个时点使用逗号分隔,例如:
|
||||
/// BOOK_CONFIRM,CLOSE_REVIEW
|
||||
/// 表示该应用配置会在这些业务时点参与风控判断。
|
||||
/// </summary>
|
||||
public string TriggerPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否全局适用。
|
||||
/// true 表示对所有交易都适用,此时通常忽略其他 Scope 字段;
|
||||
/// false 表示只在指定范围内适用,需要结合下方各 Scope 字段进行过滤。
|
||||
/// </summary>
|
||||
public int ScopeIsGlobal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 适用的资产簿记账户范围。
|
||||
/// 第一版使用逗号分隔的字符串保存,例如:
|
||||
/// 123,456,789
|
||||
/// 后续执行时可按约定解析为账户ID集合。
|
||||
/// </summary>
|
||||
public string ScopeAssetBookIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 适用的客户范围。
|
||||
/// 第一版使用逗号分隔的字符串保存客户ID,例如:
|
||||
/// 10001,10002
|
||||
/// 用于在执行前筛选该规则是否适用于当前客户。
|
||||
/// </summary>
|
||||
public string ScopeClientIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 适用的标的类型范围。
|
||||
/// 例如:
|
||||
/// 股票,ETF,债券
|
||||
/// 用于控制规则仅对特定标的类型生效。
|
||||
/// </summary>
|
||||
public string ScopeUnderlyingTypes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 适用的交易类型范围。
|
||||
/// 例如:
|
||||
/// 簿记交易,平仓交易
|
||||
/// 用于控制规则仅在指定交易类型下生效。
|
||||
/// </summary>
|
||||
public string ScopeTradeTypes { get; set; }
|
||||
|
||||
public int Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建人ID。
|
||||
/// 记录是谁创建了这条应用配置。
|
||||
/// </summary>
|
||||
public int OptId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建人名称。
|
||||
/// 便于界面展示和审计追踪。
|
||||
/// </summary>
|
||||
public string OptName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间。
|
||||
/// 记录应用配置的创建时刻。
|
||||
/// </summary>
|
||||
public DateTime OptDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最后更新人ID。
|
||||
/// 记录最近一次修改该应用配置的操作人。
|
||||
/// </summary>
|
||||
public int UpdateOptId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最后更新人名称。
|
||||
/// 便于界面展示和审计追踪。
|
||||
/// </summary>
|
||||
public string UpdateOptName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最后更新时间。
|
||||
/// 记录最近一次修改该应用配置的时间。
|
||||
/// </summary>
|
||||
public DateTime UpdateDate { get; set; }
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -50,6 +50,7 @@ using YLErp.Modules.TradeDalModule;
|
||||
using YLErp.Modules.TradeModule;
|
||||
using YLErp.Modules.TradeModule.DealModule;
|
||||
using YLErp.QdpModule;
|
||||
using YLErp.Modules.RiskEngine;
|
||||
using static YLErp.ConsGlobal;
|
||||
|
||||
namespace YLErp.Modules.RiskModule
|
||||
@@ -1012,7 +1013,7 @@ namespace YLErp.Modules.RiskModule
|
||||
|
||||
// 互换DV汇总:直接取eod_swap_position中已计算好的dv01
|
||||
var eodSwapDV = Convert.ToDouble(posiQuery.Sum(p => p.dv01));
|
||||
|
||||
|
||||
var setValue = new Action<QuotaMonitor_Global, List<QuotaSetting>>((obj, settings) =>
|
||||
{
|
||||
obj.StockEqvNotional = Convert.ToDouble(posiQuery.Sum(s => s.PosiNotionalValue));
|
||||
@@ -4464,7 +4465,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, IEnumerable<string> ignoreRiskRuleIds = null)
|
||||
{
|
||||
if (res == null || res.Trade == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(res));
|
||||
}
|
||||
@@ -4473,7 +4479,7 @@ namespace YLErp.Modules.RiskModule
|
||||
var tradeNumber = DbContext.trade.Where(O => O.id == tradeId).Select(O => O.TradeNumber).FirstOrDefault();
|
||||
var trialService = new QuotaMonitorService(this);
|
||||
var quotaObj = trialService.QuotaCheck(tradeId, 1);
|
||||
if (quotaObj.TrialStatus == QuotaTrialStatusEnum.Success)//这次计算结果是通过,直接通过
|
||||
if (quotaObj.TrialStatus == QuotaTrialStatusEnum.Success)//这次计算结果是通过,直接通过
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(quotaObj.AvailableForClient)) // 返回用户资金情况
|
||||
{
|
||||
@@ -4481,6 +4487,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);
|
||||
@@ -4491,6 +4503,41 @@ namespace YLErp.Modules.RiskModule
|
||||
//否则的情况是上次没算,这次是预警,或上次算了,结果是不通过\通过或预警,这次是预警或不通过,提示用户;
|
||||
res.RetCode = TradeOpenRetCode.QuotaTrialError;
|
||||
res.TrialDataId = quotaObj.id;
|
||||
//汇总各类检查详情,避免只展示RiskWarningDetails而漏掉其它老风控检查
|
||||
var detailParts = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(quotaObj.FundCheckDetails))
|
||||
detailParts.Add($"应付资金检查:{quotaObj.FundCheckDetails}");
|
||||
if (!string.IsNullOrWhiteSpace(quotaObj.QuotaCheckDetails))
|
||||
detailParts.Add($"限额检查:{quotaObj.QuotaCheckDetails}");
|
||||
if (!string.IsNullOrWhiteSpace(quotaObj.RiskWarningDetails))
|
||||
detailParts.Add($"风险预警:{quotaObj.RiskWarningDetails}");
|
||||
if (!string.IsNullOrWhiteSpace(quotaObj.QuotaWarningDetails))
|
||||
detailParts.Add($"限额预警:{quotaObj.QuotaWarningDetails}");
|
||||
res.ErrorMsg = string.Join("\n", detailParts);
|
||||
//确认本次为需审批后,二次特批。因为只靠点击“交易特批”的ignoreRiskWarning,不能保证本次校验通过。
|
||||
var isRiskApprovalWarning = quotaObj.TrialStatus == QuotaTrialStatusEnum.RiskWarning
|
||||
&& !string.IsNullOrWhiteSpace(quotaObj.RiskWarningDetails)
|
||||
&& (quotaObj.ApprovalRuleIds?.Any() ?? false);
|
||||
//当前为老风控特批,不是新风控需审批
|
||||
res.OldRiskNeedSpecialApproval = quotaObj.TrialStatus == QuotaTrialStatusEnum.Warning;
|
||||
if (isRiskApprovalWarning)
|
||||
{
|
||||
var currentApprovalRuleIds = quotaObj.ApprovalRuleIds ?? new List<string>();
|
||||
res.IgnoredRiskRuleIds = currentApprovalRuleIds;
|
||||
//需清除ErrorMsg,不然外部调用会认为失败
|
||||
//触发需审批,交易特批逻辑
|
||||
if (ignoreRiskWarning)
|
||||
{
|
||||
var ignoredRuleIdSet = new HashSet<string>((ignoreRiskRuleIds ?? Enumerable.Empty<string>()).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;
|
||||
}
|
||||
|
||||
@@ -4882,6 +4929,22 @@ namespace YLErp.Modules.RiskModule
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按 quotaTrial.id 校验试算结果是否已超过配置时效
|
||||
/// </summary>
|
||||
/// <param name="trialDataId">quotaTrial.id</param>
|
||||
/// <param name="expireSeconds">有效时长(秒)</param>
|
||||
/// <returns>查不到记录或已超时返回 true,否则返回 false</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询试算历史
|
||||
/// </summary>
|
||||
@@ -4904,14 +4967,14 @@ namespace YLErp.Modules.RiskModule
|
||||
_logger.Info($"[限额试算] 开始执行 - tradeId: {tradeId}, trialSource: {trialSource}");
|
||||
var result = new QuotaTrial();
|
||||
trade tradeObj = DbContext.trade.AsNoTracking().Where(O => O.id == tradeId).FirstOrDefault();
|
||||
|
||||
|
||||
// 检查交易对象是否存在
|
||||
if (tradeObj == null)
|
||||
{
|
||||
_logger.Info($"[限额试算] 找不到交易记录 - tradeId: {tradeId}");
|
||||
throw new ArgumentNullException(nameof(tradeObj), $"找不到ID为 {tradeId} 的交易记录");
|
||||
}
|
||||
|
||||
|
||||
_logger.Info($"[限额试算] 交易信息 - tradeId: {tradeObj.id}, TradeNumber: {tradeObj.TradeNumber}, TradeType: {tradeObj.TradeType}, ClientId: {tradeObj.ClientId}");
|
||||
|
||||
//// 检查关键字段是否为null
|
||||
@@ -5110,6 +5173,75 @@ namespace YLErp.Modules.RiskModule
|
||||
result.AvailableForClient = availableMsg ?? "";
|
||||
result.TrialSource = trialSource;
|
||||
_logger.Info($"[限额试算] 试算完成 - tradeId: {tradeId}, TrialStatus: {result.TrialStatus}, FundCheck: {fundStatus}, QuotaCheck: {quotaStatus}");
|
||||
|
||||
// ===== 新风控引擎接入点(设计文档 4.6.1) =====
|
||||
try
|
||||
{
|
||||
var riskEngine = new RiskEngineService(this);
|
||||
var riskContext = new RiskContext
|
||||
{
|
||||
TradeId = tradeId,
|
||||
TriggerPoint = "BOOK_CONFIRM"
|
||||
};
|
||||
var riskResult = riskEngine.EvaluateRisk(riskContext, "BOOK_CONFIRM");
|
||||
|
||||
_logger.Info($"[风控引擎] 簿记交易确认 - TradeId: {tradeId}, Passed: {riskResult.Passed}, Blocked: {riskResult.Blocked}, NeedApproval: {riskResult.NeedApproval}, ShowTip: {riskResult.ShowTip}");
|
||||
|
||||
// 按策略映射回 QuotaTrialStatusEnum
|
||||
if (riskResult.Blocked)
|
||||
{
|
||||
result.TrialStatus = QuotaTrialStatusEnum.Error;
|
||||
result.RiskWarningDetails += "[风控引擎] 规则触发:禁止\n";
|
||||
foreach (var triggeredRule in riskResult.TriggeredRules.Where(r => r.ControlStrategy == RiskControlStrategy.Block))
|
||||
{
|
||||
result.RiskWarningDetails += $"规则ID:{triggeredRule.RuleId};规则名称:{triggeredRule.RuleName};规则说明:{triggeredRule.RuleText}\n";
|
||||
}
|
||||
}
|
||||
if (riskResult.NeedApproval)
|
||||
{
|
||||
if (result.TrialStatus != QuotaTrialStatusEnum.Error && result.TrialStatus != QuotaTrialStatusEnum.Warning)
|
||||
{
|
||||
result.TrialStatus = QuotaTrialStatusEnum.RiskWarning;
|
||||
}
|
||||
result.RiskWarningDetails += "[风控引擎] 规则触发:需审批\n";
|
||||
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";
|
||||
}
|
||||
}
|
||||
if (riskResult.ShowTip)
|
||||
{
|
||||
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)
|
||||
{
|
||||
_logger.Error($"[风控引擎] 执行异常 - TradeId: {tradeId}, Error: {ex.Message}");
|
||||
// 第一版:异常不阻断原有流程,只记录日志
|
||||
}
|
||||
// ===== 新风控引擎接入结束 =====
|
||||
|
||||
SaveQuotaTrial(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -146,6 +149,59 @@ namespace YLErp.Modules.RiskModule
|
||||
DbContext.SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定交易最近一次新风控“确认通过”的处理时间,用于控制二次审批放行仅在短时间内有效。
|
||||
/// </summary>
|
||||
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);
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回退重置
|
||||
/// </summary>
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
/// <summary>
|
||||
/// 2021-02-04改造:批量确认因为耗时太长,用户多页面(交易列表,今日交易)并发确认导致生成多条期权费资金记录
|
||||
/// </summary>
|
||||
public TradeConfirmResultModel tradeConfirm(IEnumerable<int> tradeids, bool ignoreMoneyCheck = false, bool isSkipApproval = false)
|
||||
public TradeConfirmResultModel tradeConfirm(IEnumerable<int> tradeids, bool ignoreMoneyCheck = false, bool isSkipApproval = false, bool ignoreRiskWarning = false, IEnumerable<string> ignoreRiskRuleIds = null)
|
||||
{
|
||||
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<int>(),
|
||||
@@ -99,7 +100,9 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
isSkipApproval: isSkipApproval,
|
||||
confirmedTradeIds: result.confirmedTradeIds,
|
||||
changeConfirmPaths: result.changeConfirmPaths,
|
||||
continueQuotaCheck: idCache.Contains(pid));
|
||||
continueQuotaCheck: idCache.Contains(pid),
|
||||
ignoreRiskWarning: ignoreRiskWarning,
|
||||
ignoreRiskRuleIds: ignoreRiskRuleIds);
|
||||
if (pid > 0)
|
||||
{
|
||||
idCache.Add(pid);
|
||||
@@ -114,11 +117,14 @@ 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.OldRiskNeedSpecialApproval = temp.OldRiskNeedSpecialApproval;
|
||||
result.TrialDataId = temp.TrialDataId;
|
||||
result.TradeId = temp.Trade.id;
|
||||
result.ignoreRiskRuleIds = temp.IgnoredRiskRuleIds;
|
||||
sbError.AppendLine(temp.ErrorMsg);
|
||||
break;
|
||||
}
|
||||
@@ -128,6 +134,12 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
sbError.AppendLine(temp.ErrorMsg);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(temp.TipMsg))
|
||||
{
|
||||
result.TrialDataId = temp.TrialDataId;
|
||||
sbTip.AppendLine(temp.TipMsg);
|
||||
}
|
||||
|
||||
Helpers.LockHelper.Release(lockKeyId);
|
||||
}
|
||||
}
|
||||
@@ -135,6 +147,7 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
DbContext.SaveChanges();
|
||||
|
||||
result.errorMsg = sbError.ToString();
|
||||
result.tipMsg = sbTip.ToString();
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -152,7 +165,7 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
/// <param name="continueQuotaCheck"></param>
|
||||
/// <returns></returns>
|
||||
private TradeOpenResult ConfirmTrade(int tid, bool isBatch, bool hasTradeProcess,
|
||||
bool ignoreMoneyCheck, bool isSkipApproval, List<int> confirmedTradeIds, List<string> changeConfirmPaths, bool continueQuotaCheck = false)
|
||||
bool ignoreMoneyCheck, bool isSkipApproval, List<int> confirmedTradeIds, List<string> changeConfirmPaths, bool continueQuotaCheck = false, bool ignoreRiskWarning = false, IEnumerable<string> ignoreRiskRuleIds = null)
|
||||
{
|
||||
var td = DbContext.trade.FirstOrDefault(t => t.id == tid);
|
||||
var result = new TradeOpenResult(td ?? new trade());
|
||||
@@ -207,7 +220,7 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
}
|
||||
|
||||
var trialService = new QuotaMonitorService(this);
|
||||
if (!trialService.QuotaCheck(ref result))
|
||||
if (!trialService.QuotaCheck(ref result, ignoreRiskWarning, ignoreRiskRuleIds))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
@@ -223,6 +236,10 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
history = "特权账户批量确认自动" + history;
|
||||
}
|
||||
}
|
||||
else if (ignoreRiskWarning)
|
||||
{
|
||||
history = "风控预警确认-" + history;
|
||||
}
|
||||
//进入交易审批流程
|
||||
if (hasTradeProcess && !isSkipApproval)
|
||||
{
|
||||
@@ -604,11 +621,14 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
public class TradeConfirmResultModel
|
||||
{
|
||||
public bool LackOfMoney;
|
||||
public bool OldRiskNeedSpecialApproval;
|
||||
public int TrialDataId;
|
||||
public int TradeId;
|
||||
public List<int> confirmedTradeIds;
|
||||
public List<string> changeConfirmPaths;
|
||||
public string errorMsg;
|
||||
public string tipMsg;
|
||||
public string type;
|
||||
public List<string> ignoreRiskRuleIds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -794,11 +794,17 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
public TradeOpenRetCode RetCode;
|
||||
|
||||
public int TrialDataId;
|
||||
public List<string> IgnoredRiskRuleIds;
|
||||
public bool OldRiskNeedSpecialApproval;
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
public string ErrorMsg;
|
||||
/// <summary>
|
||||
/// 提示信息
|
||||
/// </summary>
|
||||
public string TipMsg;
|
||||
|
||||
/// <summary>
|
||||
/// 变更确认书路径
|
||||
@@ -838,6 +844,10 @@ namespace YLErp.Modules.TradeModule.DealModule
|
||||
/// </summary>
|
||||
FundStatus,
|
||||
/// <summary>
|
||||
/// 风控预警二次确认
|
||||
/// </summary>
|
||||
RiskWarning,
|
||||
/// <summary>
|
||||
/// 无审核权限
|
||||
/// </summary>
|
||||
NoRight
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace YLErp.BLL
|
||||
/// </summary>
|
||||
public const string LackOfMoney = "LackOfMoney";
|
||||
|
||||
public const string RiskWarningConfirm = "RiskWarningConfirm";
|
||||
|
||||
public const string IgnoreBreak = "IgnoreBreak";
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||
<PackageReference Include="FluentFTP" Version="45.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.12.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.10.0" />
|
||||
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
|
||||
<PackageReference Include="Oracle.EntityFrameworkCore" Version="6.21.90" />
|
||||
@@ -40,7 +42,7 @@
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.10.0" />
|
||||
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.0" />
|
||||
<PackageReference Include="Qdp.Pricing.Library.Options" Version="1.0.5" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" />
|
||||
<PackageReference Include="YLErp.Office" Version="1.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
using YLErp.DBModels.Consts;
|
||||
using YLErp.Modules.AppModule;
|
||||
using YLErp.Modules.EodModule.SettlementModule;
|
||||
using YLErp.Modules.RiskEngine;
|
||||
using YLErp.Modules.RiskModule;
|
||||
using YLErp.Modules.SuperviseReportModule.SAC.Service;
|
||||
using YLErp.Modules.TradeRiskCalcModule;
|
||||
@@ -124,6 +125,18 @@ namespace YLErp.Web.App
|
||||
PS.SetConfig(ConsAppConfig.YLErpWebUrlConfig, address);
|
||||
}
|
||||
|
||||
// 预热风控引擎:加载规则与应用到内存,并预编译所有规则
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
RiskEngineService.GetInstance().Preload();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogFactory.GetLogger("HostedTaskService").Error("[风控引擎] 启动预热失败", ex);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -60,6 +60,23 @@
|
||||
<FunctionSub Name="VaR计算" Title="VaR计算"></FunctionSub>
|
||||
<FunctionSub Name="观察事件" Title="观察事件"></FunctionSub>
|
||||
<FunctionSub Name="风险预警/禁止查询" Title="风险预警/禁止查询"></FunctionSub>
|
||||
<FunctionSub Name="异常交易监控" Title="异常交易监控"></FunctionSub>
|
||||
<FunctionSub Name="风控规则查看" Type="Operate" Note="是否有权限查询风控规则"></FunctionSub>
|
||||
<FunctionSub Name="风控规则新增" Type="Operate" Note="是否有权限新增风控规则"></FunctionSub>
|
||||
<FunctionSub Name="风控规则编辑" Type="Operate" Note="是否有权限编辑风控规则"></FunctionSub>
|
||||
<FunctionSub Name="风控规则删除" Type="Operate" Note="是否有权限删除/批量删除风控规则"></FunctionSub>
|
||||
<FunctionSub Name="风控规则启停" Type="Operate" Note="是否有权限启用/停用风控规则"></FunctionSub>
|
||||
<FunctionSub Name="风控应用查看" Type="Operate" Note="是否有权限查询风控应用"></FunctionSub>
|
||||
<FunctionSub Name="风控应用新增" Type="Operate" Note="是否有权限新增应用配置"></FunctionSub>
|
||||
<FunctionSub Name="风控应用编辑" Type="Operate" Note="是否有权限编辑应用配置"></FunctionSub>
|
||||
<FunctionSub Name="风控应用删除" Type="Operate" Note="是否有权限删除应用配置"></FunctionSub>
|
||||
<FunctionSub Name="风控应用启停" Type="Operate" Note="是否有权限启用/停用/批量操作应用配置"></FunctionSub>
|
||||
<FunctionSub Name="风控变量查看" Type="Operate" Note="是否有权限查询风控变量"></FunctionSub>
|
||||
<FunctionSub Name="风控变量新增" Type="Operate" Note="是否有权限新增变量"></FunctionSub>
|
||||
<FunctionSub Name="风控变量编辑" Type="Operate" Note="是否有权限编辑变量"></FunctionSub>
|
||||
<FunctionSub Name="风控变量删除" Type="Operate" Note="是否有权限删除变量"></FunctionSub>
|
||||
<FunctionSub Name="风控日志查看" Type="Operate" Note="是否有权限查询风控日志"></FunctionSub>
|
||||
<FunctionSub Name="风控日志导出" Type="Operate" Note="是否有权限导出日志Excel"></FunctionSub>
|
||||
</FunctionParent>
|
||||
<FunctionParent Name="结算管理" Title="结算财务">
|
||||
<FunctionSub Name="成交簿记" Title="成交簿记"></FunctionSub>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
{Name:"资金监控",Rights:["风险控制-资金监控"],Url:"client/clientRiskMonitor"},
|
||||
{Name:"市场风险",Rights:["风险控制-市场风险"],Url:"risk/RiskExposureReport"},
|
||||
{Name:"限额监控",Rights:["风险控制-限额监控"],Url:"risk/quotaMonitor"},
|
||||
{Name:"异常交易监控",Rights:["风险控制-异常交易监控"],Url:"v3/risk/risk-engine-config"},
|
||||
{Name:"白名单券池",Rights:["风险控制-白名单券池"],Url:"v3/data/underlying-pool"},
|
||||
{Name:"日终持仓风险",Rights:["风险控制-日终持仓风险"],Url:"trade/EodPositionRisks"},
|
||||
{Name:"日终持仓风险_互换",Rights:["风险控制-日终持仓风险_互换"],Url:"swaptrade2/EodPositionRisks"},
|
||||
|
||||
@@ -0,0 +1,698 @@
|
||||
using Qdp.Foundation.Utilities;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.Modules.RiskEngine;
|
||||
using YLErp.Modules.RiskEngine.Dto;
|
||||
|
||||
namespace YLErp.Web.Controllers
|
||||
{
|
||||
/// <summary>风控引擎管理控制器,提供规则、应用配置、变量池和审计日志的 CRUD 接口</summary>
|
||||
[Route("risk-engine")]
|
||||
public class RiskRuleController : BaseController
|
||||
{
|
||||
private readonly IYcLogger _logger = LogFactory.GetLogger("RiskRuleController");
|
||||
|
||||
#region Rule Management
|
||||
|
||||
[HttpGet("risk-rules")]
|
||||
[MyAuthorize("风险控制-风控规则查看")]
|
||||
/// <summary>查询风控规则列表(分页)</summary>
|
||||
public JsonResult QueryRiskRules(QueryRiskRuleReq req)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.QueryRuleList(req);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "查询风控规则列表");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("risk-rules/{id}")]
|
||||
[MyAuthorize("风险控制-风控规则查看")]
|
||||
/// <summary>获取风控规则详情</summary>
|
||||
public JsonResult GetRiskRule(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.GetRuleDetail(id);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "查询风控规则详情");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("risk-rules/{id}/versions")]
|
||||
[MyAuthorize("风险控制-风控规则查看")]
|
||||
/// <summary>获取风控规则版本历史</summary>
|
||||
public JsonResult GetRiskRuleVersions(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.GetRuleVersions(id);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "查询风控规则版本历史");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("risk-rules")]
|
||||
[MyAuthorize("风险控制-风控规则新增")]
|
||||
/// <summary>创建风控规则</summary>
|
||||
public JsonResult CreateRiskRule([FromBody] CreateRiskRuleReq req)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.CreateRule(req);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "创建风控规则");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("risk-rules/{id}")]
|
||||
[MyAuthorize("风险控制-风控规则编辑")]
|
||||
/// <summary>更新风控规则</summary>
|
||||
public JsonResult UpdateRiskRule(long id, [FromBody] UpdateRiskRuleReq req)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.UpdateRule(id, req);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "更新风控规则");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("risk-rules/{id}")]
|
||||
[MyAuthorize("风险控制-风控规则删除")]
|
||||
/// <summary>删除风控规则(软删除)</summary>
|
||||
public JsonResult DeleteRiskRule(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
service.DeleteRule(id);
|
||||
return Json(new { success = true });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "删除风控规则");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("risk-rules/batch")]
|
||||
[MyAuthorize("风险控制-风控规则删除")]
|
||||
/// <summary>批量删除风控规则</summary>
|
||||
public JsonResult BatchDeleteRiskRules([FromBody] List<long> ids)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.BatchDeleteRules(ids);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "批量删除风控规则");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("risk-rules/{id}/enable")]
|
||||
[MyAuthorize("风险控制-风控规则启停")]
|
||||
/// <summary>启用风控规则</summary>
|
||||
public JsonResult EnableRiskRule(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
service.EnableRule(id);
|
||||
return Json(new { success = true });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "启用风控规则");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("risk-rules/{id}/disable")]
|
||||
[MyAuthorize("风险控制-风控规则启停")]
|
||||
/// <summary>停用风控规则</summary>
|
||||
public JsonResult DisableRiskRule(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
service.DisableRule(id);
|
||||
return Json(new { success = true });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "停用风控规则");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("risk-rules/{id}/applications")]
|
||||
[MyAuthorize("风险控制-风控规则查看")]
|
||||
/// <summary>获取规则关联的应用配置列表</summary>
|
||||
public JsonResult GetRuleApplications(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.GetRuleApplications(id);
|
||||
return Json(new { success = true, data = new { rows = result, total = result.Count, records = result.Count, page = 1 } });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "查询规则关联应用列表");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("risk-rules/list")]
|
||||
[MyAuthorize("风险控制-风控规则查看")]
|
||||
/// <summary>获取所有启用规则列表(供下拉选择)</summary>
|
||||
public JsonResult GetAllRiskRules()
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.GetAllRuleList();
|
||||
return Json(new { success = true, data = new { rows = result, total = result.Count, records = result.Count, page = 1 } });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "查询规则下拉列表");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Application Management
|
||||
|
||||
[HttpGet("risk-applications")]
|
||||
[MyAuthorize("风险控制-风控应用查看")]
|
||||
/// <summary>查询应用配置列表(分页)</summary>
|
||||
public JsonResult QueryRiskApplications(QueryRiskApplicationReq req)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.QueryApplicationList(req);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "查询应用配置列表");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("risk-applications/{id}")]
|
||||
[MyAuthorize("风险控制-风控应用查看")]
|
||||
/// <summary>获取应用配置详情</summary>
|
||||
public JsonResult GetRiskApplication(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.GetApplicationDetail(id);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "查询应用配置详情");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("risk-applications")]
|
||||
[MyAuthorize("风险控制-风控应用新增")]
|
||||
/// <summary>创建应用配置</summary>
|
||||
public JsonResult CreateRiskApplication([FromBody] CreateRiskApplicationReq req)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.CreateApplication(req);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "创建应用配置");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("risk-applications/{id}")]
|
||||
[MyAuthorize("风险控制-风控应用编辑")]
|
||||
/// <summary>更新应用配置</summary>
|
||||
public JsonResult UpdateRiskApplication(long id, [FromBody] UpdateRiskApplicationReq req)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.UpdateApplication(id, req);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "更新应用配置");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("risk-applications/{id}")]
|
||||
[MyAuthorize("风险控制-风控应用删除")]
|
||||
/// <summary>删除应用配置(软删除)</summary>
|
||||
public JsonResult DeleteRiskApplication(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
service.DeleteApplication(id);
|
||||
return Json(new { success = true });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "删除应用配置");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("risk-applications/{id}/enable")]
|
||||
[MyAuthorize("风险控制-风控应用启停")]
|
||||
/// <summary>启用应用配置</summary>
|
||||
public JsonResult EnableRiskApplication(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
service.EnableApplication(id);
|
||||
return Json(new { success = true });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "启用应用配置");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("risk-applications/{id}/disable")]
|
||||
[MyAuthorize("风险控制-风控应用启停")]
|
||||
/// <summary>停用应用配置</summary>
|
||||
public JsonResult DisableRiskApplication(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
service.DisableApplication(id);
|
||||
return Json(new { success = true });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "停用应用配置");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("risk-applications/batch-enable")]
|
||||
[MyAuthorize("风险控制-风控应用启停")]
|
||||
/// <summary>批量启用应用配置</summary>
|
||||
public JsonResult BatchEnableRiskApplications([FromBody] List<long> ids)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.BatchEnableApplications(ids);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "批量启用应用配置");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("risk-applications/batch-disable")]
|
||||
[MyAuthorize("风险控制-风控应用启停")]
|
||||
/// <summary>批量停用应用配置</summary>
|
||||
public JsonResult BatchDisableRiskApplications([FromBody] List<long> ids)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.BatchDisableApplications(ids);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "批量停用应用配置");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Variable Management
|
||||
|
||||
[HttpGet("risk-variables")]
|
||||
[MyAuthorize("风险控制-风控变量查看")]
|
||||
/// <summary>查询变量列表</summary>
|
||||
public JsonResult QueryRiskVariables(QueryRiskVariableReq req)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.QueryVariableList(req);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "查询变量列表");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("risk-variables/{id}")]
|
||||
[MyAuthorize("风险控制-风控变量查看")]
|
||||
/// <summary>获取变量详情</summary>
|
||||
public JsonResult GetRiskVariable(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.GetVariableDetail(id);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "查询变量详情");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("risk-variables/list")]
|
||||
[MyAuthorize("风险控制-风控变量查看")]
|
||||
/// <summary>获取所有变量列表(供下拉选择)</summary>
|
||||
public JsonResult GetAllRiskVariables()
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.GetAllVariableList();
|
||||
return Json(new { success = true, data = new { rows = result, total = result.Count, records = result.Count, page = 1 } });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "查询全部变量列表");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("risk-variables")]
|
||||
[MyAuthorize("风险控制-风控变量新增")]
|
||||
/// <summary>创建变量</summary>
|
||||
public JsonResult CreateRiskVariable([FromBody] CreateRiskVariableReq req)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.CreateVariable(req);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "创建变量");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("risk-variables/{id}")]
|
||||
[MyAuthorize("风险控制-风控变量编辑")]
|
||||
/// <summary>更新变量(含级联更新引用规则的 RuleExpr)</summary>
|
||||
public JsonResult UpdateRiskVariable(long id, [FromBody] UpdateRiskVariableReq req)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.UpdateVariable(id, req);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "更新变量");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("risk-variables/{id}")]
|
||||
[MyAuthorize("风险控制-风控变量删除")]
|
||||
/// <summary>删除变量</summary>
|
||||
public JsonResult DeleteRiskVariable(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
service.DeleteVariable(id);
|
||||
return Json(new { success = true });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "删除变量");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Trade Types
|
||||
|
||||
[HttpGet("trade-types")]
|
||||
[MyAuthorize("风险控制-风控应用查看")]
|
||||
/// <summary>获取合约类型列表</summary>
|
||||
public JsonResult GetTradeTypes()
|
||||
{
|
||||
try
|
||||
{
|
||||
var rows = ConsTrade.AllTradeTypes.Select(t => new { label = t, value = t }).ToList();
|
||||
var total = rows.Count;
|
||||
return Json(new {
|
||||
success = true,
|
||||
data = new {
|
||||
page = 1,
|
||||
records = total,
|
||||
rows = rows,
|
||||
total = total
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "查询合约类型列表");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Audit Log
|
||||
|
||||
[HttpGet("risk-audit-logs")]
|
||||
[MyAuthorize("风险控制-风控日志查看")]
|
||||
/// <summary>查询风控操作日志列表(分页)</summary>
|
||||
public JsonResult QueryRiskAuditLogs(QueryRiskAuditLogReq req)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.QueryAuditLogs(req);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "查询风控操作日志列表");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("risk-audit-logs/{id}")]
|
||||
[MyAuthorize("风险控制-风控日志查看")]
|
||||
/// <summary>获取风控操作日志详情</summary>
|
||||
public JsonResult GetRiskAuditLogDetail(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var result = service.GetAuditLogDetail(id);
|
||||
return Json(new { success = true, data = result });
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "查询风控操作日志详情");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("risk-audit-logs/export")]
|
||||
[MyAuthorize("风险控制-风控日志导出")]
|
||||
/// <summary>导出风控操作日志为 Excel</summary>
|
||||
public IActionResult ExportRiskAuditLogs(QueryRiskAuditLogReq req)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new RiskRuleService(CurUser);
|
||||
var bytes = service.ExportAuditLogs(req);
|
||||
return File(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "风控操作日志.xlsx");
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "导出风控操作日志");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ using YLErp.BLL;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.DBModels.Consts;
|
||||
using YLErp.Modules.DictionaryModule;
|
||||
using YLErp.Modules.RiskEngine;
|
||||
using YLErp.Modules.SystemModule;
|
||||
using YLErp.Web.Models;
|
||||
|
||||
@@ -1356,6 +1357,8 @@ namespace YLErp.Web.Controllers
|
||||
Providers.MemoryCacheProvider.Default.FlushAll();
|
||||
Server.CacheProvider.FlushAll();
|
||||
Modules.DataCacheModule.DataCacheManager.ResetDataCache();
|
||||
// 刷新风控引擎缓存
|
||||
RiskEngineService.GetInstance().RefreshCache();
|
||||
return Json(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -2447,7 +2447,8 @@ namespace YLErp.Web.Controllers
|
||||
}
|
||||
|
||||
|
||||
public JsonResult tradeConfirm(string tradeids, string additionalProcessing, 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,19 +2456,67 @@ 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<string>();
|
||||
}
|
||||
}
|
||||
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, ignoreRiskRuleIdArr);
|
||||
|
||||
//如果客户缺少资金而操作者有交易特批权限
|
||||
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", 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", 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", 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 });
|
||||
}
|
||||
}
|
||||
|
||||
//生成交易确认书
|
||||
@@ -2475,7 +2524,7 @@ namespace YLErp.Web.Controllers
|
||||
{
|
||||
new TradeContractGenerateService(CurUser).GenerateContractsAsync(result.confirmedTradeIds, "确认书");
|
||||
}
|
||||
|
||||
//有ErrorMsg则认为失败
|
||||
if (!string.IsNullOrEmpty(result.errorMsg))
|
||||
{
|
||||
if (result.changeConfirmPaths?.Count > 0)
|
||||
@@ -2486,11 +2535,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, TrialDataId = result.TrialDataId });
|
||||
}
|
||||
return JsonSuccess("操作完成");
|
||||
return JsonSuccess(successMsg, new { TrialDataId = result.TrialDataId });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8399,6 +8449,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()
|
||||
{
|
||||
|
||||
@@ -101,6 +101,9 @@
|
||||
case YLErp.Enums.QuotaTrialStatusEnum.Warning:
|
||||
<i class="fa fa-exclamation-triangle status" style="color:gold" aria-hidden="true"></i><span class="status">预警</span>
|
||||
break;
|
||||
case YLErp.Enums.QuotaTrialStatusEnum.RiskWarning:
|
||||
<i class="fa fa-exclamation-triangle status" style="color:orange" aria-hidden="true"></i><span class="status">风控需审批</span>
|
||||
break;
|
||||
case YLErp.Enums.QuotaTrialStatusEnum.Error:
|
||||
<i class="fa fa-ban status" style="color:red" aria-hidden="true"></i><span class="status">不通过</span>
|
||||
break;
|
||||
|
||||
@@ -49,18 +49,86 @@ function showSwapRate(timeRate, isMarginLeg) {
|
||||
$("#swapIntervalModal").modal("show");
|
||||
}
|
||||
var layerIndex = -1;
|
||||
var confirmFunc = function (id, additionalProcessing) {
|
||||
var confirmFunc = function (id, additionalProcessing, ignoreRiskRuleIds, trialDataId) {
|
||||
var pData = { tradeids: id };
|
||||
if (!main.isEmpty(additionalProcessing)) {
|
||||
pData.additionalProcessing = additionalProcessing;
|
||||
}
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.obj && data.obj.proccessType === "QuotaTrialError") {
|
||||
//限额试算不通过(老风控检查失败),展示详情表格但不允许审批
|
||||
if (data.obj.TrialDataId) {
|
||||
var quotaTrialErrorLayerSetting = {
|
||||
type: 2,
|
||||
title: "提示",
|
||||
shadeClose: false,
|
||||
shade: 0.4,
|
||||
area: ['800px', '500px'],
|
||||
content: "/trade/showQuotaTrial?id=" + data.obj.TrialDataId,
|
||||
yes: function (index) {
|
||||
layer.close(layerIndex);
|
||||
},
|
||||
cancel: function () {
|
||||
if (window.parent && window.parent.reloadtrade) {
|
||||
window.parent.reloadtrade();
|
||||
}
|
||||
layer.close(layerIndex);
|
||||
}
|
||||
};
|
||||
quotaTrialErrorLayerSetting.btn = ["关闭"];
|
||||
layerIndex = layer.open(quotaTrialErrorLayerSetting);
|
||||
}
|
||||
else if (data.obj.message) {
|
||||
main.message(data.obj.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (data.obj && data.obj.proccessType === "AdditionalProcessing") {
|
||||
if (data.obj.TrialDataId) {
|
||||
// 交易特批来源,优先使用后端新增的 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 currentTrialDataId = data.obj.TrialDataId;
|
||||
var saveQuotaTrial = function (obj) {
|
||||
var saveSuccess = true;
|
||||
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: "提示",
|
||||
@@ -70,19 +138,30 @@ 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.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 (!obj.Data.Remark || obj.Data.Remark.length <= 0) {
|
||||
main.message("必须填写说明内容,才可以录入交易");
|
||||
return;
|
||||
}
|
||||
main.message("必须填写说明内容,才可以录入交易");
|
||||
if (!saveQuotaTrial(obj)) {
|
||||
return;
|
||||
}
|
||||
if (isRiskWarningConfirm) {
|
||||
if (!saveRiskWarningDecisionLog(obj.Data.id, "确认通过")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (isOldRiskSpecialApproval) {
|
||||
obj.Data.Remark = (obj.Data.Remark || "").trim();
|
||||
}
|
||||
layer.close(layerIndex);
|
||||
confirmFunc(id, additionalProcessingType, currentIgnoreRiskRuleIds, currentTrialDataId);
|
||||
},
|
||||
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();
|
||||
}
|
||||
@@ -92,8 +171,11 @@ 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 = ["确认", '取消'];
|
||||
}
|
||||
|
||||
@@ -106,6 +188,38 @@ var confirmFunc = function (id, additionalProcessing) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (data.msg && data.msg !== "操作完成") {
|
||||
//新风控 ShowTip 提示:交易已确认成功,但触发提示规则,弹窗展示试算详情
|
||||
if (data.obj && data.obj.TrialDataId) {
|
||||
var showTipLayerSetting = {
|
||||
type: 2,
|
||||
title: "提示",
|
||||
shadeClose: false,
|
||||
shade: 0.4,
|
||||
area: ['800px', '500px'],
|
||||
content: "/trade/showQuotaTrial?id=" + data.obj.TrialDataId,
|
||||
yes: function (index) {
|
||||
layer.close(layerIndex);
|
||||
closetradeWindow();
|
||||
},
|
||||
cancel: function () {
|
||||
if (window.parent && window.parent.reloadtrade) {
|
||||
window.parent.reloadtrade();
|
||||
}
|
||||
layer.close(layerIndex);
|
||||
closetradeWindow();
|
||||
}
|
||||
};
|
||||
showTipLayerSetting.btn = ["关闭"];
|
||||
layerIndex = layer.open(showTipLayerSetting);
|
||||
}
|
||||
else {
|
||||
main.alert(data.msg, function () {
|
||||
closetradeWindow();
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (window.parent && window.parent.reloadtrade) {
|
||||
window.parent.reloadtrade();
|
||||
}
|
||||
@@ -115,6 +229,8 @@ var confirmFunc = function (id, additionalProcessing) {
|
||||
}
|
||||
}
|
||||
closetradeWindow();
|
||||
}).fail(function (err) {
|
||||
console.log("[swapTradeView.tradeConfirm] fail, err=", err);
|
||||
});
|
||||
};
|
||||
function confirmTrade(id) {
|
||||
@@ -197,7 +313,7 @@ function unWindSwap(id) {
|
||||
main.open(title,
|
||||
srcurl,
|
||||
{
|
||||
area: ["90%", "800px"],
|
||||
area: ["90%", "780px"],
|
||||
end: function () {
|
||||
if (window.parent && window.parent.reloadtrade) {
|
||||
window.parent.reloadtrade();
|
||||
|
||||
@@ -128,7 +128,7 @@ var confirmFunc = function (id, additionalProcessing) {
|
||||
if (data.obj.type === "LackOfMoney") {
|
||||
layerSetting.btn = [page.buttonStr, '取消'];
|
||||
}
|
||||
// 只展示"资金状况"的情况
|
||||
// 只展示“资金状况”的情况
|
||||
if (data.obj.typecode === "FundStatus") {
|
||||
layerSetting.btn = ["确认", '取消'];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user