实现中文公式预报前拦截、规则测试 API 及按销售产品分组返回查价运输方式选项。 Co-authored-by: Cursor <cursoragent@cursor.com>
186 lines
5.8 KiB
C#
186 lines
5.8 KiB
C#
using System.Text.Json;
|
||
using Oms.Domain.ChannelsAggregate.Consts;
|
||
using Oms.Domain.OrdersAggregate.Entities;
|
||
|
||
namespace Oms.Application.Services.Orders.Issues;
|
||
|
||
public static class AutoTriggerConditionMatcher
|
||
{
|
||
public static bool Matches(Order order, string? triggerCondition) =>
|
||
Matches(OrderRuleEvaluationContext.FromOrderOnly(order), triggerCondition);
|
||
|
||
public static bool Matches(OrderRuleEvaluationContext context, string? triggerCondition)
|
||
{
|
||
var text = triggerCondition?.Trim();
|
||
if (string.IsNullOrWhiteSpace(text))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
var parsed = AutoTriggerConditionParser.Parse(text);
|
||
if (ShouldUseFormulaEvaluator(parsed, text))
|
||
{
|
||
if (string.IsNullOrWhiteSpace(parsed.Expression))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
if (ChineseFormulaEvaluator.TryEvaluate(parsed.Expression, context, out var result))
|
||
{
|
||
return result;
|
||
}
|
||
}
|
||
|
||
if (parsed.IsLegacyJsonMatcher && text.StartsWith('{') && text.EndsWith('}'))
|
||
{
|
||
return MatchesLegacyJson(context.Order, text);
|
||
}
|
||
|
||
if (context.Order != null)
|
||
{
|
||
var lowered = text.ToLowerInvariant();
|
||
if (IssueTypeCodes.All.Contains(lowered))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
return ContainsKeyword(context.Order, text);
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
public static string ResolveIssueType(string? triggerCondition)
|
||
{
|
||
var text = triggerCondition?.Trim();
|
||
if (string.IsNullOrWhiteSpace(text))
|
||
{
|
||
return IssueTypeCodes.Other;
|
||
}
|
||
|
||
if (text.StartsWith('{') && text.EndsWith('}'))
|
||
{
|
||
try
|
||
{
|
||
using var doc = JsonDocument.Parse(text);
|
||
if (doc.RootElement.TryGetProperty("issueType", out var issueTypeElement))
|
||
{
|
||
var code = issueTypeElement.GetString()?.Trim().ToLowerInvariant();
|
||
if (!string.IsNullOrWhiteSpace(code) && IssueTypeCodes.All.Contains(code))
|
||
{
|
||
return code;
|
||
}
|
||
}
|
||
}
|
||
catch (JsonException)
|
||
{
|
||
// fall through
|
||
}
|
||
}
|
||
|
||
var lowered = text.ToLowerInvariant();
|
||
if (IssueTypeCodes.All.Contains(lowered))
|
||
{
|
||
return lowered;
|
||
}
|
||
|
||
return IssueTypeCodes.Other;
|
||
}
|
||
|
||
public static List<int> ParseNotifierUserIds(string? notifier)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(notifier))
|
||
{
|
||
return [];
|
||
}
|
||
|
||
return notifier
|
||
.Split([',', ';', ' ', ','], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||
.Select(x => int.TryParse(x, out var id) ? id : 0)
|
||
.Where(x => x > 0)
|
||
.Distinct()
|
||
.ToList();
|
||
}
|
||
|
||
private static bool ShouldUseFormulaEvaluator(ParsedTriggerCondition parsed, string rawText) =>
|
||
parsed.IsFormulaVersion
|
||
|| (!string.IsNullOrWhiteSpace(parsed.Expression) && AutoTriggerConditionParser.LooksLikeFormula(parsed.Expression))
|
||
|| (!parsed.IsLegacyJsonMatcher && AutoTriggerConditionParser.LooksLikeFormula(rawText));
|
||
|
||
private static bool MatchesLegacyJson(Order? order, string json)
|
||
{
|
||
if (order == null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
try
|
||
{
|
||
using var doc = JsonDocument.Parse(json);
|
||
var root = doc.RootElement;
|
||
if (root.TryGetProperty("orderStatus", out var statusElement))
|
||
{
|
||
var expected = statusElement.GetString()?.Trim();
|
||
if (!string.IsNullOrWhiteSpace(expected)
|
||
&& !string.Equals(order.OrderStatus, expected, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (root.TryGetProperty("productCode", out var productElement))
|
||
{
|
||
var expected = productElement.GetString()?.Trim();
|
||
if (!string.IsNullOrWhiteSpace(expected)
|
||
&& !string.Equals(order.ProductCode?.Trim(), expected, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (root.TryGetProperty("channelCode", out var channelElement))
|
||
{
|
||
var expected = channelElement.GetString()?.Trim();
|
||
if (!string.IsNullOrWhiteSpace(expected)
|
||
&& !string.Equals(order.ChannelCode?.Trim(), expected, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (root.TryGetProperty("keyword", out var keywordElement))
|
||
{
|
||
var keyword = keywordElement.GetString()?.Trim();
|
||
if (!string.IsNullOrWhiteSpace(keyword) && !ContainsKeyword(order, keyword))
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
catch (JsonException)
|
||
{
|
||
return ContainsKeyword(order, json);
|
||
}
|
||
}
|
||
|
||
private static bool ContainsKeyword(Order order, string keyword)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(keyword))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
return Contains(order.ServiceNo, keyword)
|
||
|| Contains(order.ClientNo, keyword)
|
||
|| Contains(order.TrackNo, keyword)
|
||
|| Contains(order.ProductCode, keyword)
|
||
|| Contains(order.ChannelCode, keyword);
|
||
}
|
||
|
||
private static bool Contains(string? source, string keyword) =>
|
||
!string.IsNullOrWhiteSpace(source)
|
||
&& source.Contains(keyword, StringComparison.OrdinalIgnoreCase);
|
||
}
|