using Admin.NET.Plugin.AiDOP.Manufacturing.Dto;
using System.Globalization;
namespace Admin.NET.Plugin.AiDOP.Manufacturing;
///
/// S6 过程检规 自动唯一解析器(Phase 1B)。
///
/// 输入 (CurrentTenant, ItemCode, ReferenceDate) → 唯一有效过程检规 或 明确的不可解析原因。
/// 运行期只查规范映射表 ado_s6_ipqc_spec_material_map(不再 split wlbm blob)+ S0 检规头/明细。
/// 裁决规则(wjbh 分组,见 ):
/// 候选 = map(tenant,item,EXACT) ⋈ qms_gcjygf(同租户),取有效者(sxrj 可解析、sxrj <= ReferenceDate、明细行数 > 0);
/// 按 wjbh(文件编号)分组,组内取 sxrj 最新(并列取 version 最大)为有效版本;
/// 恰好 1 个 wjbh 组有效 → MATCHED;≥2 个 → AMBIGUOUS;
/// 有候选但均无明细/日期非法 → INVALID_SPEC;无候选/仅未来生效 → NO_MATCH。
/// 全程 tenant 显式过滤,绝不跨租户。禁止人工选择、禁止 id desc 兜底。
///
public class S6ProcessSpecResolver : ITransient
{
private readonly ISqlSugarClient _db;
public S6ProcessSpecResolver(ISqlSugarClient db) => _db = db;
public async Task ResolveAsync(long tenantId, string? itemCode, DateTime referenceDate)
{
if (tenantId <= 0 || string.IsNullOrWhiteSpace(itemCode))
return new S6ProcessSpecResolveResult { Status = S6SpecResolveStatus.NoMatch, Message = "缺少物料编码,无法解析过程检验规范。" };
var candidates = await _db.Ado.SqlQueryAsync(
"""
SELECT g.id AS Id, g.wjbh AS Wjbh, g.bb AS Bb, g.version AS Version, g.sxrj AS Sxrj,
(SELECT COUNT(1) FROM qms_gcjygfzb z WHERE z.tenant_id=@TenantId AND z.glid=g.id) AS DetailCount
FROM ado_s6_ipqc_spec_material_map m
JOIN qms_gcjygf g ON g.id=m.spec_id AND g.tenant_id=@TenantId
WHERE m.tenant_id=@TenantId AND m.item_code=@ItemCode AND m.match_status='EXACT'
""",
new List { new("@TenantId", tenantId), new("@ItemCode", itemCode.Trim()) });
return Decide(candidates, referenceDate);
}
///
/// 纯裁决逻辑(无 DB,便于单测)。candidates 为已按 (tenant,item,EXACT) 命中的检规候选。
///
public static S6ProcessSpecResolveResult Decide(IReadOnlyList? candidates, DateTime referenceDate)
{
var result = new S6ProcessSpecResolveResult();
if (candidates == null || candidates.Count == 0)
{
result.Status = S6SpecResolveStatus.NoMatch;
result.Message = "当前物料未配置有效过程检验规范,请维护 S0 过程检验规范。";
return result;
}
foreach (var c in candidates) c.NormalizedEffective = NormalizeDate(c.Sxrj);
var effective = candidates
.Where(c => c.NormalizedEffective != null && c.NormalizedEffective.Value.Date <= referenceDate.Date && c.DetailCount > 0)
.ToList();
var structurallyInvalid = candidates.Any(c => c.DetailCount <= 0 || c.NormalizedEffective == null);
if (effective.Count > 0)
{
var groups = effective.GroupBy(c => (c.Wjbh ?? string.Empty).Trim()).ToList();
if (groups.Count == 1)
{
var winner = groups[0]
.OrderByDescending(c => c.NormalizedEffective)
.ThenByDescending(c => c.Version ?? 0)
.ThenByDescending(c => c.Id)
.First();
result.Status = S6SpecResolveStatus.Matched;
result.MatchedSpecId = winner.Id;
result.SpecCode = winner.Wjbh;
result.SpecVersion = winner.Bb;
result.EffectiveDate = winner.Sxrj;
result.CandidateCount = 1;
return result;
}
result.Status = S6SpecResolveStatus.Ambiguous;
result.CandidateCount = groups.Count;
result.Message = "当前物料存在多个同时有效过程检验规范,请修正 S0 规范配置。";
return result;
}
if (structurallyInvalid)
{
result.Status = S6SpecResolveStatus.InvalidSpec;
result.Message = "过程检验规范配置无效,请检查版本/生效日期/明细。";
return result;
}
result.Status = S6SpecResolveStatus.NoMatch;
result.Message = "当前物料未配置有效过程检验规范,请维护 S0 过程检验规范。";
return result;
}
///
/// 归一化 S0 sxrj(生效日期)自由文本为日期。支持 2026-08-28 / 2025.12.12 / 2025-05-28 00:00:00 等;不可解析返回 null。
///
public static DateTime? NormalizeDate(string? raw)
{
if (string.IsNullOrWhiteSpace(raw)) return null;
var s = raw.Trim().Replace('.', '-').Replace('/', '-');
var sp = s.IndexOf(' ');
if (sp > 0) s = s[..sp];
if (DateTime.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.None, out var d)) return d;
if (DateTime.TryParseExact(s, new[] { "yyyy-M-d", "yyyy-MM-dd" }, CultureInfo.InvariantCulture, DateTimeStyles.None, out d)) return d;
return null;
}
/// tokenize 多值 wlbm(英/中分号、逗号、空白、换行)→ 规范化片段(建立映射用,非运行期)。
public static IReadOnlyList Tokenize(string? wlbm)
{
if (string.IsNullOrWhiteSpace(wlbm)) return Array.Empty();
var parts = wlbm.Split(new[] { ';', ';', ',', ',', '、', ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
return parts.Select(p => p.Trim().ToUpperInvariant()).Where(p => p.Length > 0).Distinct().ToList();
}
}