using BaseOUDAL; using Newtonsoft.Json; namespace YLErp.Helpers { /// /// 审批条件求值器:统一支撑「节点触发条件」与「分支网关条件」。 /// 需求①(节点触发)与需求③(多分支)共用同一套条件模型,避免两套条件语义。 /// 支持混合「且/或」与「括号」的布尔表达式,如 A and (B or C)。 /// public static class ConditionEvaluator { /// /// 求值条件 JSON。 /// /// 条件 JSON(结构见 ConditionExpressionConfig);为空/null 视为无条件,返回 false(不触发)。 /// 业务上下文(交易/发起人等)。 /// 是否满足条件 public static bool Evaluate(string conditionJson, ConditionContext context) { if (string.IsNullOrWhiteSpace(conditionJson)) { return false; } ConditionExpressionConfig config; try { config = JsonConvert.DeserializeObject(conditionJson); } catch { // 容错:非法 JSON 不阻断审批主流程,视为不触发。 return false; } if (config == null || config.tokens == null || config.tokens.Count == 0) { return false; } // 递归下降求值(支持括号、且/或混合)。 var parser = new ConditionParser(config.tokens, context); return parser.Parse(); } /// /// 从起点节点开始,向后查找第一个满足触发条件(或无触发条件)的审批节点(需求①)。 /// 用于交易提交进入审批流程时确定起始审批节点:若起点节点配置了触发条件且当前业务不满足, /// 则跳过该节点继续向后找,直到找到可进入的节点;若从起点到末尾均不满足则返回 null(表示无需审批,直接通过)。 /// /// 流程全部节点(已按 order 排序) /// 起点节点 /// 条件求值上下文 /// 第一个应进入审批的节点;若无需审批则返回 null public static approvalprocess FindFirstTriggeredNode( List tradeProcess, approvalprocess start, ConditionContext ctx) { if (start == null) return null; var current = start; while (current != null) { // 无触发条件,或满足触发条件 → 该节点需审批 if (string.IsNullOrWhiteSpace(current.triggerCondition) || Evaluate(current.triggerCondition, ctx)) { return current; } // 不满足 → 向后取下一个主干节点(node=0) current = tradeProcess.FirstOrDefault(x => x.order > current.order && x.node == 0); } return null; } /// 求值单个条件。 internal static bool EvaluateSingle(ConditionItem cond, ConditionContext context) { if (cond == null || string.IsNullOrEmpty(cond.field) || string.IsNullOrEmpty(cond.op)) { return false; } var leftValue = FieldResolver.ResolveValue(cond.field, context); return OperatorCompare(leftValue, cond.op, cond.value); } /// 比较:能转数值时按数值比,否则按字符串比。 private static bool OperatorCompare(object left, string op, object right) { if (TryToDouble(left, out var ld) && TryToDouble(right, out var rd)) { return CompareNumeric(ld, op, rd); } var ls = left?.ToString() ?? string.Empty; var rs = right?.ToString() ?? string.Empty; return CompareString(ls, op, rs); } private static bool CompareNumeric(double left, string op, double right) { return NormalizeOp(op) switch { ">" => left > right, "<" => left < right, ">=" => left >= right, "<=" => left <= right, "==" => Math.Abs(left - right) < 1e-9, "!=" => Math.Abs(left - right) >= 1e-9, _ => false }; } private static bool CompareString(string left, string op, string right) { return NormalizeOp(op) switch { "==" => left == right, "!=" => left != right, ">" => string.Compare(left, right, StringComparison.Ordinal) > 0, "<" => string.Compare(left, right, StringComparison.Ordinal) < 0, ">=" => string.Compare(left, right, StringComparison.Ordinal) >= 0, "<=" => string.Compare(left, right, StringComparison.Ordinal) <= 0, "in" => right.Split(',', StringSplitOptions.RemoveEmptyEntries) .Any(r => string.Equals(r.Trim(), left, StringComparison.OrdinalIgnoreCase)), _ => false }; } /// /// 归一化操作符:兼容字母标识符(gt/lt/gte/lte/eq/neq)与符号(> < >= <= == != =)。 /// private static string NormalizeOp(string op) { if (string.IsNullOrEmpty(op)) return op; return op.ToLowerInvariant() switch { "gt" or ">" => ">", "lt" or "<" => "<", "gte" or ">=" => ">=", "lte" or "<=" => "<=", "eq" or "==" or "=" => "==", "neq" or "!=" or "<>" => "!=", _ => op }; } private static bool TryToDouble(object value, out double result) { result = 0; if (value == null) return false; return double.TryParse(value.ToString(), out result); } } /// /// 递归下降解析器:按 token 顺序求值布尔表达式,支持括号与「且/或」优先级。 /// 文法:Expr := Term (("and"|"or") Term)* ;Term := condition | "(" Expr ")"。 /// 优先级:and 高于 or(与常规布尔代数一致);同级从左到右。 /// internal class ConditionParser { private readonly List _tokens; private readonly ConditionContext _context; private int _pos; public ConditionParser(List tokens, ConditionContext context) { _tokens = tokens ?? new List(); _context = context; _pos = 0; } public bool Parse() { if (_tokens.Count == 0) return false; return ParseOr(); } // 低优先级:ParseOr := ParseAnd ( "or" ParseAnd )* 左结合,遇 or 短路(为 true 直接返回后续不再求值) private bool ParseOr() { var left = ParseAnd(); while (true) { var op = Peek(); if (op == null || op.type != "operator" || !IsOr(op.connector)) break; _pos++; // 消费 or var right = ParseAnd(); left = left || right; } return left; } // 高优先级:ParseAnd := ParseTerm ( "and" ParseTerm )* 左结合,遇 and 短路(为 false 直接返回) private bool ParseAnd() { var left = ParseTerm(); while (true) { var op = Peek(); if (op == null || op.type != "operator" || IsOr(op.connector)) break; _pos++; // 消费 and var right = ParseTerm(); left = left && right; } return left; } // Term := condition | "(" ParseOr ")" private bool ParseTerm() { var tok = Peek(); if (tok == null) return false; if (tok.type == "lparen") { _pos++; // 消费 "(" var val = ParseOr(); var rp = Peek(); if (rp != null && rp.type == "rparen") _pos++; // 消费 ")" return val; } if (tok.type == "condition") { _pos++; return ConditionEvaluator.EvaluateSingle(tok.condition, _context); } return false; } private ConditionToken Peek() => _pos < _tokens.Count ? _tokens[_pos] : null; private static bool IsOr(string connector) => string.Equals(connector, "or", StringComparison.OrdinalIgnoreCase); } /// 条件业务上下文:求值时由调用方构造,封装可参与判断的业务字段。 public class ConditionContext { /// 发起人 userId(用于查发起人审批组)。 public int? UserId { get; set; } /// 交易实体(期初名义本金等字段来源)。开户场景可为 null。 public trade Trade { get; set; } /// 交易流程类别:开仓(TradeProcess) / 了结(CloseProcess)。需求②用。 public string ProcessCategory { get; set; } /// 预解析的发起人审批组(避免重复查库;为空时由 FieldResolver 查)。 public int? InitGroupId { get; set; } /// 本次交易名义本金的取值(了结场景由调用方从 trade_cash.UnwindStockEqvNotional 取绝对值传入)。需求①。 public double? CurrentNotional { get; set; } } /// /// 条件表达式配置(对应 conditionConfig / triggerCondition 列的 JSON 结构)。 /// tokens:token 序列,支持「且/或」混合与括号,按表达式顺序排列。 /// public class ConditionExpressionConfig { /// token 序列:条件 / 且或连接符 / 左右括号,按表达式顺序排列。 public List tokens { get; set; } } /// /// 表达式 token:一个条件、一个连接符、或一个括号。 /// public class ConditionToken { /// token 类型:condition | operator | lparen | rparen public string type { get; set; } /// 当 type=condition 时的条件体。 public ConditionItem condition { get; set; } /// 当 type=operator 时的连接符:and | or public string connector { get; set; } } /// 单个条件:左值字段 + 操作符 + 右值。 public class ConditionItem { /// 左值字段 key,见 FieldResolver(initGroup/notional/tradeType 等)。 public string field { get; set; } /// 操作符:> < >= <= == != = in public string op { get; set; } /// 右值 public object value { get; set; } } /// /// 条件左值解析:把 field key 映射到具体业务字段值。 /// 触发条件字段(需求①): /// - initialNotional:交易的期初名义本金(trade.OriginalStockEqvNotional,已取绝对值) /// - currentNotional :本次交易名义本金(了结场景,由调用方从 trade_cash 取本次影响金额绝对值传入) /// 历史兼容:initGroup/tradeType/processCategory/notional 仍可解析。 /// public static class FieldResolver { public static object ResolveValue(string field, ConditionContext context) { if (string.IsNullOrEmpty(field) || context == null) { return null; } switch (field.ToLowerInvariant()) { // 需求①:触发条件字段(仅这两个对外暴露) case "initialnotional": // 交易的期初名义本金 return context.Trade?.OriginalStockEqvNotional ?? 0; case "currentnotional": // 本次交易名义本金(了结时按本次影响金额绝对值,由调用方传入) return context.CurrentNotional ?? 0; // 以下为历史兼容,前端不再暴露 case "notional": return context.Trade?.StockEqvNotional ?? 0; case "initgroup": if (context.InitGroupId.HasValue) { return context.InitGroupId.Value; } return context.UserId.HasValue ? UserBLL.GetApprovalProcessGroup(context.UserId.Value) : 0; case "tradetype": return context.Trade?.TradeType ?? string.Empty; case "processcategory": return context.ProcessCategory ?? string.Empty; default: return null; } } } }