2026102801 #17

Merged
zzh merged 2 commits from test into master 2026-10-28 16:34:24 +08:00
2 changed files with 95 additions and 5 deletions
Showing only changes of commit d20c10a2f2 - Show all commits

View File

@ -5,7 +5,9 @@ namespace Oms.Application.Services.Orders.Issues;
public static class ChineseFormulaEvaluator 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) public static bool TryEvaluate(string? expression, OrderRuleEvaluationContext context, out bool result)
{ {
@ -53,6 +55,8 @@ public static class ChineseFormulaEvaluator
LeftParen, LeftParen,
RightParen, RightParen,
Contains, Contains,
HasValue,
IsEmpty,
End End
} }
@ -208,12 +212,14 @@ public static class ChineseFormulaEvaluator
continue; continue;
} }
_pos += label.Length; var afterLabel = _pos + label.Length;
_pos = afterLabel;
SkipWhitespace(); SkipWhitespace();
if (_pos + ContainsKeyword.Length <= source.Length if (_pos + ContainsKeyword.Length <= source.Length
&& source.AsSpan(_pos, ContainsKeyword.Length).SequenceEqual(ContainsKeyword.AsSpan())) && source.AsSpan(_pos, ContainsKeyword.Length).SequenceEqual(ContainsKeyword.AsSpan()))
{ {
_pos += ContainsKeyword.Length; var afterContainsKeyword = _pos + ContainsKeyword.Length;
_pos = afterContainsKeyword;
SkipWhitespace(); SkipWhitespace();
if (_pos < source.Length && source[_pos] == '(') if (_pos < source.Length && source[_pos] == '(')
{ {
@ -221,6 +227,22 @@ public static class ChineseFormulaEvaluator
token = new Token(TokenKind.Contains, label); token = new Token(TokenKind.Contains, label);
return true; 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())) if (_pos + 2 <= source.Length && source.AsSpan(_pos, 2).SequenceEqual("()".AsSpan()))
@ -230,12 +252,34 @@ public static class ChineseFormulaEvaluator
return true; return true;
} }
_pos -= label.Length; _pos = afterLabel - label.Length;
} }
return false; return false;
} }
/// <summary>匹配「有值()」「为空()」这类无参关键字。</summary>
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 int SavePosition() => _pos;
public void RestorePosition(int pos) => _pos = pos; public void RestorePosition(int pos) => _pos = pos;
@ -306,6 +350,11 @@ public static class ChineseFormulaEvaluator
return ParseContains(); return ParseContains();
} }
if (_current.Kind is TokenKind.HasValue or TokenKind.IsEmpty)
{
return ParsePresence(_current.Kind == TokenKind.HasValue);
}
var left = ParseAdditive(); var left = ParseAdditive();
if (_current.Kind is TokenKind.Greater or TokenKind.GreaterEqual or TokenKind.Less or TokenKind.LessEqual or TokenKind.Equal or TokenKind.NotEqual) 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); 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() private decimal ParseAdditive()
{ {
var value = ParseMultiplicative(); var value = ParseMultiplicative();

View File

@ -16,9 +16,10 @@ public static class OrderFormulaFieldResolver
public static bool StringContains(FormulaFieldRef fieldRef, OrderRuleEvaluationContext context, string needle) public static bool StringContains(FormulaFieldRef fieldRef, OrderRuleEvaluationContext context, string needle)
{ {
// 空参数:表示「字段有值」(非 null / 非空白),用于必填校验,如 国家包含('')
if (string.IsNullOrEmpty(needle)) if (string.IsNullOrEmpty(needle))
{ {
return true; return HasValue(fieldRef, context);
} }
if (fieldRef.Table == "t_order_declaration") if (fieldRef.Table == "t_order_declaration")
@ -36,6 +37,33 @@ public static class OrderFormulaFieldResolver
return ContainsValue(raw, needle); return ContainsValue(raw, needle);
} }
/// <summary>
/// 字段有值:非 null;字符串另要求非空白。数值 0 / 布尔 false 视为有值。
/// </summary>
public static bool HasValue(FormulaFieldRef fieldRef, OrderRuleEvaluationContext context) =>
IsPresent(GetRawValue(fieldRef, context));
/// <summary>
/// 字段为空:null,或字符串空白。
/// </summary>
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) private static object? GetRawValue(FormulaFieldRef fieldRef, OrderRuleEvaluationContext context)
{ {
if (fieldRef.Virtual) if (fieldRef.Virtual)