From d20c10a2f2038a89c63d090bd0d339f8411a447b Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Wed, 28 Oct 2026 15:07:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=87=AA=E5=8A=A8=E8=A7=A6=E5=8F=91?= =?UTF-8?q?=E5=85=AC=E5=BC=8F=E6=94=AF=E6=8C=81=E6=9C=89=E5=80=BC/?= =?UTF-8?q?=E4=B8=BA=E7=A9=BA=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .../Orders/Issues/ChineseFormulaEvaluator.cs | 70 +++++++++++++++++-- .../Issues/OrderFormulaFieldResolver.cs | 30 +++++++- 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/src/Oms.Application/Services/Orders/Issues/ChineseFormulaEvaluator.cs b/src/Oms.Application/Services/Orders/Issues/ChineseFormulaEvaluator.cs index 0017e30..b4dd466 100644 --- a/src/Oms.Application/Services/Orders/Issues/ChineseFormulaEvaluator.cs +++ b/src/Oms.Application/Services/Orders/Issues/ChineseFormulaEvaluator.cs @@ -5,7 +5,9 @@ namespace Oms.Application.Services.Orders.Issues; public static class ChineseFormulaEvaluator { - private const string ContainsKeyword = "包含"; + private const string ContainsKeyword = "包含"; + private const string HasValueKeyword = "有值"; + private const string IsEmptyKeyword = "为空"; public static bool TryEvaluate(string? expression, OrderRuleEvaluationContext context, out bool result) { @@ -53,6 +55,8 @@ public static class ChineseFormulaEvaluator LeftParen, RightParen, Contains, + HasValue, + IsEmpty, End } @@ -208,12 +212,14 @@ public static class ChineseFormulaEvaluator continue; } - _pos += label.Length; + var afterLabel = _pos + label.Length; + _pos = afterLabel; SkipWhitespace(); if (_pos + ContainsKeyword.Length <= source.Length && source.AsSpan(_pos, ContainsKeyword.Length).SequenceEqual(ContainsKeyword.AsSpan())) { - _pos += ContainsKeyword.Length; + var afterContainsKeyword = _pos + ContainsKeyword.Length; + _pos = afterContainsKeyword; SkipWhitespace(); if (_pos < source.Length && source[_pos] == '(') { @@ -221,6 +227,22 @@ public static class ChineseFormulaEvaluator token = new Token(TokenKind.Contains, label); return true; } + + // 「包含」后无「(」则回退,继续尝试有值()/为空()/() + _pos = afterLabel; + SkipWhitespace(); + } + + if (TryConsumeKeywordWithEmptyParens(HasValueKeyword)) + { + token = new Token(TokenKind.HasValue, label); + return true; + } + + if (TryConsumeKeywordWithEmptyParens(IsEmptyKeyword)) + { + token = new Token(TokenKind.IsEmpty, label); + return true; } if (_pos + 2 <= source.Length && source.AsSpan(_pos, 2).SequenceEqual("()".AsSpan())) @@ -230,12 +252,34 @@ public static class ChineseFormulaEvaluator return true; } - _pos -= label.Length; + _pos = afterLabel - label.Length; } return false; } + /// 匹配「有值()」「为空()」这类无参关键字。 + private bool TryConsumeKeywordWithEmptyParens(string keyword) + { + var saved = _pos; + if (_pos + keyword.Length > source.Length + || !source.AsSpan(_pos, keyword.Length).SequenceEqual(keyword.AsSpan())) + { + return false; + } + + _pos += keyword.Length; + SkipWhitespace(); + if (_pos + 2 <= source.Length && source.AsSpan(_pos, 2).SequenceEqual("()".AsSpan())) + { + _pos += 2; + return true; + } + + _pos = saved; + return false; + } + public int SavePosition() => _pos; public void RestorePosition(int pos) => _pos = pos; @@ -306,6 +350,11 @@ public static class ChineseFormulaEvaluator return ParseContains(); } + if (_current.Kind is TokenKind.HasValue or TokenKind.IsEmpty) + { + return ParsePresence(_current.Kind == TokenKind.HasValue); + } + var left = ParseAdditive(); if (_current.Kind is TokenKind.Greater or TokenKind.GreaterEqual or TokenKind.Less or TokenKind.LessEqual or TokenKind.Equal or TokenKind.NotEqual) { @@ -343,6 +392,19 @@ public static class ChineseFormulaEvaluator return OrderFormulaFieldResolver.StringContains(fieldRef, _context, needle); } + private bool ParsePresence(bool expectHasValue) + { + var label = _current.Text; + Advance(); + if (!AutoTriggerFormulaMeta.TryResolve(label, out var fieldRef) || fieldRef == null) + { + throw new FormulaParseException($"未知字段:{label}"); + } + + var hasValue = OrderFormulaFieldResolver.HasValue(fieldRef, _context); + return expectHasValue ? hasValue : !hasValue; + } + private decimal ParseAdditive() { var value = ParseMultiplicative(); diff --git a/src/Oms.Application/Services/Orders/Issues/OrderFormulaFieldResolver.cs b/src/Oms.Application/Services/Orders/Issues/OrderFormulaFieldResolver.cs index 3393c17..5aceb38 100644 --- a/src/Oms.Application/Services/Orders/Issues/OrderFormulaFieldResolver.cs +++ b/src/Oms.Application/Services/Orders/Issues/OrderFormulaFieldResolver.cs @@ -16,9 +16,10 @@ public static class OrderFormulaFieldResolver public static bool StringContains(FormulaFieldRef fieldRef, OrderRuleEvaluationContext context, string needle) { + // 空参数:表示「字段有值」(非 null / 非空白),用于必填校验,如 国家包含('') if (string.IsNullOrEmpty(needle)) { - return true; + return HasValue(fieldRef, context); } if (fieldRef.Table == "t_order_declaration") @@ -36,6 +37,33 @@ public static class OrderFormulaFieldResolver return ContainsValue(raw, needle); } + /// + /// 字段有值:非 null;字符串另要求非空白。数值 0 / 布尔 false 视为有值。 + /// + public static bool HasValue(FormulaFieldRef fieldRef, OrderRuleEvaluationContext context) => + IsPresent(GetRawValue(fieldRef, context)); + + /// + /// 字段为空:null,或字符串空白。 + /// + public static bool IsEmpty(FormulaFieldRef fieldRef, OrderRuleEvaluationContext context) => + !HasValue(fieldRef, context); + + private static bool IsPresent(object? value) + { + if (value == null) + { + return false; + } + + if (value is string s) + { + return !string.IsNullOrWhiteSpace(s); + } + + return true; + } + private static object? GetRawValue(FormulaFieldRef fieldRef, OrderRuleEvaluationContext context) { if (fieldRef.Virtual)