2026102801 #17
@ -314,7 +314,9 @@ public static class ChineseFormulaEvaluator
|
|||||||
while (_current.Kind == TokenKind.Or)
|
while (_current.Kind == TokenKind.Or)
|
||||||
{
|
{
|
||||||
Advance();
|
Advance();
|
||||||
value = value || ParseAnd();
|
// 必须先解析右侧,禁止用 || 短路跳过,否则剩余 token 会让 ExpectEnd 失败
|
||||||
|
var right = ParseAnd();
|
||||||
|
value = value || right;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
@ -326,7 +328,9 @@ public static class ChineseFormulaEvaluator
|
|||||||
while (_current.Kind == TokenKind.And)
|
while (_current.Kind == TokenKind.And)
|
||||||
{
|
{
|
||||||
Advance();
|
Advance();
|
||||||
value = value && ParseNot();
|
// 同上:禁止 && 短路跳过右侧解析
|
||||||
|
var right = ParseNot();
|
||||||
|
value = value && right;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
@ -510,8 +514,18 @@ public static class ChineseFormulaEvaluator
|
|||||||
_ => false
|
_ => false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 括号内是否应按布尔表达式解析。
|
||||||
|
/// 注意:调用前已 Advance 掉 '(',第一个内部 token 在 _current;词法位置在其后。
|
||||||
|
/// 旧逻辑只向后扫逻辑运算符,导致括号内的包含/有值/为空/比较被误判为数值式而解析失败。
|
||||||
|
/// </summary>
|
||||||
private bool LooksLikeBooleanGroup()
|
private bool LooksLikeBooleanGroup()
|
||||||
{
|
{
|
||||||
|
if (IsBooleanAtomKind(_current.Kind))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
var savedPos = _lexer.SavePosition();
|
var savedPos = _lexer.SavePosition();
|
||||||
var depth = 1;
|
var depth = 1;
|
||||||
while (depth > 0)
|
while (depth > 0)
|
||||||
@ -534,7 +548,7 @@ public static class ChineseFormulaEvaluator
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (depth == 1 && token.Kind is TokenKind.And or TokenKind.Or)
|
if (depth == 1 && IsBooleanGroupIndicator(token.Kind))
|
||||||
{
|
{
|
||||||
_lexer.RestorePosition(savedPos);
|
_lexer.RestorePosition(savedPos);
|
||||||
return true;
|
return true;
|
||||||
@ -545,6 +559,15 @@ public static class ChineseFormulaEvaluator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsBooleanAtomKind(TokenKind kind) =>
|
||||||
|
kind is TokenKind.Contains or TokenKind.HasValue or TokenKind.IsEmpty or TokenKind.Not;
|
||||||
|
|
||||||
|
private static bool IsBooleanGroupIndicator(TokenKind kind) =>
|
||||||
|
kind is TokenKind.And or TokenKind.Or
|
||||||
|
or TokenKind.Contains or TokenKind.HasValue or TokenKind.IsEmpty or TokenKind.Not
|
||||||
|
or TokenKind.Greater or TokenKind.GreaterEqual or TokenKind.Less or TokenKind.LessEqual
|
||||||
|
or TokenKind.Equal or TokenKind.NotEqual;
|
||||||
|
|
||||||
private void Advance() => _current = _lexer.Next();
|
private void Advance() => _current = _lexer.Next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
73
src/Oms.Tests/ChineseFormulaEvaluatorTests.cs
Normal file
73
src/Oms.Tests/ChineseFormulaEvaluatorTests.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Oms.Application.Dtos.Orders;
|
||||||
|
using Oms.Application.Services.Orders.Issues;
|
||||||
|
using Oms.Domain.CustomersAggregate.Entities;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Oms.Tests;
|
||||||
|
|
||||||
|
public class ChineseFormulaEvaluatorTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("产品标识编码为空()", true)]
|
||||||
|
[InlineData("产品标识编码有值()", false)]
|
||||||
|
[InlineData("!产品标识编码有值()", true)]
|
||||||
|
[InlineData("销售产品包含('US02-5DAY')", true)]
|
||||||
|
[InlineData("销售产品包含('US02-5DAY')&&产品标识编码为空()", true)]
|
||||||
|
[InlineData("(销售产品包含('US02-5DAY'))&&产品标识编码为空()", true)]
|
||||||
|
[InlineData("销售产品包含('US02-5DAY')&&(产品标识编码为空())", true)]
|
||||||
|
[InlineData("产品标识编码为空()||非标产品标识编码为空()", true)]
|
||||||
|
[InlineData("(销售产品包含('US02-5DAY')||销售产品包含('OTHER'))&&(产品标识编码为空()||非标产品标识编码为空())", true)]
|
||||||
|
[InlineData("(销售产品包含('3PE快递美国专线-5日达')||销售产品包含('US02-5DAY'))&&(产品标识编码为空()||非标产品标识编码为空())", true)]
|
||||||
|
[InlineData("(实重()>1)&&产品标识编码为空()", true)]
|
||||||
|
public void TryEvaluate_EmptyProductIds_ShouldMatch(string expression, bool expected)
|
||||||
|
{
|
||||||
|
var context = BuildPreCommitContext(productCode: "US02-5DAY", productId: "", nonStdProductId: "", actualWeight: 1.2m);
|
||||||
|
|
||||||
|
var ok = ChineseFormulaEvaluator.TryEvaluate(expression, context, out var matched);
|
||||||
|
|
||||||
|
ok.Should().BeTrue($"expression should parse: {expression}");
|
||||||
|
matched.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryEvaluate_FilledProductIds_ShouldNotMatchEmptyCheck()
|
||||||
|
{
|
||||||
|
var context = BuildPreCommitContext(productCode: "US02-5DAY", productId: "P1", nonStdProductId: "N1", actualWeight: 1.2m);
|
||||||
|
var expression =
|
||||||
|
"(销售产品包含('US02-5DAY'))&&(产品标识编码为空()||非标产品标识编码为空())";
|
||||||
|
|
||||||
|
var ok = ChineseFormulaEvaluator.TryEvaluate(expression, context, out var matched);
|
||||||
|
|
||||||
|
ok.Should().BeTrue();
|
||||||
|
matched.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OrderRuleEvaluationContext BuildPreCommitContext(
|
||||||
|
string productCode,
|
||||||
|
string? productId,
|
||||||
|
string? nonStdProductId,
|
||||||
|
decimal actualWeight)
|
||||||
|
{
|
||||||
|
var input = new OrderParcelCreateInput
|
||||||
|
{
|
||||||
|
ProductCode = productCode,
|
||||||
|
ActualWeight = actualWeight,
|
||||||
|
Items =
|
||||||
|
[
|
||||||
|
new OrderParcelItemInput
|
||||||
|
{
|
||||||
|
CnName = "手机壳",
|
||||||
|
ProductId = productId,
|
||||||
|
NonStdProductId = nonStdProductId
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
return OrderRuleEvaluationContext.FromPreCommit(
|
||||||
|
input,
|
||||||
|
new Customer { CustomerName = "test" },
|
||||||
|
"US",
|
||||||
|
"美国");
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user