using System.Text.RegularExpressions;
using SqlSugar;
namespace Admin.NET.Plugin.AiDOP.DataPlatform.Wms;
///
/// 领料保管员解析。移植基准:旧 DOP ResourceExamineAppService.GenerateNbr
/// (按 EmpWorkDutyMaster.ItemNum1..ItemNum2 字符串区间匹配 Employee)。
/// 比较口径见 WP7 §3.6(B5):Trim + ToUpperInvariant 后 Ordinal;最窄区间优先。
///
public sealed class KeeperResolveService : ITransient
{
private static readonly Regex SafeItemNum = new(@"^[0-9A-Z]+$", RegexOptions.Compiled);
private readonly MdpSourceScopeFactory _scopeFactory;
public KeeperResolveService(MdpSourceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public sealed class KeeperHit
{
public string ItemNum { get; set; } = "";
public int DutyRecID { get; set; }
public string Employee { get; set; } = "";
public string? Location { get; set; }
}
public sealed class KeeperResolveResult
{
public IReadOnlyList Hits { get; init; } = Array.Empty();
/// 未匹配到职责的物料号
public IReadOnlyList MissingItems { get; init; } = Array.Empty();
/// 命中但 Location 为空的物料号
public IReadOnlyList EmptyLocationItems { get; init; } = Array.Empty();
/// 字符集越界(物料号或职责区间)
public IReadOnlyList UnsafeItems { get; init; } = Array.Empty();
public bool Ok => MissingItems.Count == 0 && EmptyLocationItems.Count == 0 && UnsafeItems.Count == 0;
}
///
/// 按物料号匹配职责;返回命中与 WP7 §3.2 硬校验 / §3.6 字符集校验结果。
///
public async Task ResolveByItemsAsync(
string domain, IEnumerable itemNums, CancellationToken ct = default)
{
var items = itemNums.Where(x => !string.IsNullOrWhiteSpace(x)).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
if (items.Count == 0)
return new KeeperResolveResult();
var db = await _scopeFactory.GetScopeAsync("DOPDEMORQ_SQLSERVER", ct);
var duties = await db.Ado.SqlQueryAsync(
@"SELECT RecID, Employee, ItemNum1, ItemNum2, Location
FROM EmpWorkDutyMaster
WHERE Domain=@d
ORDER BY ItemNum1, ItemNum2, RecID",
new SugarParameter("@d", domain));
var hits = new List();
var missing = new List();
var emptyLoc = new List();
var unsafeItems = new List();
var hasUnsafeDuty = duties.Any(s => !IsSafe(Norm(s.ItemNum1)) || !IsSafe(Norm(s.ItemNum2)));
foreach (var raw in items)
{
var key = Norm(raw);
if (!IsSafe(key))
{
unsafeItems.Add(raw);
continue;
}
// 最窄区间优先:下界最大、上界最小、同宽 RecID 小者
var match = duties
.Select(d => (Row: d, A: Norm(d.ItemNum1), B: Norm(d.ItemNum2)))
.Where(d => IsSafe(d.A) && IsSafe(d.B)
&& string.Compare(d.A, key, StringComparison.Ordinal) <= 0
&& string.Compare(key, d.B, StringComparison.Ordinal) <= 0)
.OrderByDescending(d => d.A, StringComparer.Ordinal)
.ThenBy(d => d.B, StringComparer.Ordinal)
.ThenBy(d => d.Row.RecID)
.Select(d => d.Row)
.FirstOrDefault();
if (match == null)
{
if (hasUnsafeDuty) unsafeItems.Add(raw);
else missing.Add(raw);
continue;
}
if (string.IsNullOrWhiteSpace(match.Employee))
{
missing.Add(raw);
continue;
}
if (string.IsNullOrWhiteSpace(match.Location))
{
emptyLoc.Add(raw);
continue;
}
hits.Add(new KeeperHit
{
ItemNum = raw,
DutyRecID = match.RecID,
Employee = match.Employee!,
Location = match.Location
});
}
return new KeeperResolveResult
{
Hits = hits,
MissingItems = missing,
EmptyLocationItems = emptyLoc,
UnsafeItems = unsafeItems
};
}
/// 拼 NbrMaster.User1(逗号)与 User2(JSON 数组字符串),与旧 DOP 一致。
public static (string user1, string user2) FormatKeeperUsers(IEnumerable hits)
{
var emps = hits.Select(x => x.Employee).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToList();
if (emps.Count == 0) return ("", "[]");
var user1 = string.Join(",", emps);
var user2 = System.Text.Json.JsonSerializer.Serialize(emps);
return (user1, user2);
}
internal static string Norm(string? s) => (s ?? string.Empty).Trim().ToUpperInvariant();
private static bool IsSafe(string s) => SafeItemNum.IsMatch(s);
private sealed class DutyRow
{
public int RecID { get; set; }
public string? Employee { get; set; }
public string? ItemNum1 { get; set; }
public string? ItemNum2 { get; set; }
public string? Location { get; set; }
}
}