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(); } }