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