using System.Globalization; namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse; /// 录入类型(ado_s5_iqc_insp_bill_item.result_type 的合法值,与 ck_s5_iqc_item_result_type 对齐)。 public static class IqcResultType { public const string Numeric = "NUMERIC"; public const string NonNumeric = "NON_NUMERIC"; public static bool IsValid(string? v) => v == Numeric || v == NonNumeric; } /// /// 判定值(与 ck_s5_iqc_item_judgement / ck_s5_iqc_sample_judgement 对齐)。 /// 刻意**不含** PENDING —— Batch 1 已从枚举中移除:未录 = NULL,已录 = PASS/FAIL/INVALID 三值封闭。 /// public static class IqcJudgement { public const string Pass = "PASS"; public const string Fail = "FAIL"; /// 系统无法形成有效测量判定(限值缺失 / 值不可解析)。**不允许人工设置**。 public const string Invalid = "INVALID"; } /// 判定出处(与 ck_s5_iqc_item_jsrc 对齐)。 public static class IqcJudgementSource { /// 由测量真源实时重算,每次保存无条件覆盖。 public const string Auto = "AUTO"; /// 检验员显式结论;测量事实一旦变更即强制复位为 AUTO。 public const string Manual = "MANUAL"; } /// /// S5 IQC 项目级判定引擎(Batch 3,纯函数,后端唯一判定真源)。 /// /// 只依据「快照冻结的限值」+「实录测量事实」判定,**绝不回查 qms_jygfzb**(否则 S0 改检规会篡改历史判定)。 /// 前端传入的任何 judgement / judgementSource 一律忽略。 /// /// 【单一真源】 /// NUMERIC → 测量真源 = ado_s5_iqc_insp_bill_item_sample 行集;item 上两个 qty 必须为 NULL /// NON_NUMERIC → 测量真源 = item.qualified_qty / unqualified_qty;该 item 不得有 sample 行 /// /// 【与 S6 的差异(刻意)】 /// ① S6 的 NON_NUMERIC 是逐样本 OK/NG 模型(ado_s6_process_inspection_result_item.actual_non_numeric), /// IQC 是 item 级计数模型且禁止样本行 ⇒ S6 该分支代码不可迁移; /// ② S6 的 AggregateItem / AllItemsCompleted 依赖快照的 sample_count(要求样本量), /// IQC 无该数据来源(qms_jygfzb 无对应列)⇒ 完整性只能基于 item_judgement 是否已定, /// 不引入「要求样本量」概念; /// ③ NUMERIC 逐样本判定的四个分支与 S6ProcessInspectionJudge.JudgeSample 语义一致,按同规则实现。 /// public static class IqcInspectionJudge { /// /// 解析十进制数(限值与样本值共用)。空/空白/不可解析 → null。 /// 口径沿用 S6ProcessInspectionJudge:NumberStyles.Any + InvariantCulture + Trim, /// 避免服务器区域设置漂移导致小数点/千分位误判(刻意不采用 S7 的默认 Culture 写法)。 /// public static decimal? ParseNumeric(string? raw) { if (string.IsNullOrWhiteSpace(raw)) return null; return decimal.TryParse(raw.Trim(), NumberStyles.Any, CultureInfo.InvariantCulture, out var d) ? d : null; } /// /// NUMERIC 逐样本判定。只用快照冻结的 upper_limit / lower_limit,**绝不解析 inspection_standard 自由文本**。 /// /// 上下限均缺失 → INVALID(无判据,不可默认 PASS) /// 样本值不可解析 → INVALID(保留 raw 供复盘) /// 双侧齐备 → lower ≤ v ≤ upper /// 仅上限 → v ≤ upper /// 仅下限 → v ≥ lower /// public static string JudgeSampleNumeric(string? actualRaw, string? lowerLimitRaw, string? upperLimitRaw) { var lo = ParseNumeric(lowerLimitRaw); var up = ParseNumeric(upperLimitRaw); if (lo == null && up == null) return IqcJudgement.Invalid; var actual = ParseNumeric(actualRaw); if (actual == null) return IqcJudgement.Invalid; var ok = (lo == null || actual.Value >= lo.Value) && (up == null || actual.Value <= up.Value); return ok ? IqcJudgement.Pass : IqcJudgement.Fail; } /// /// NUMERIC 项目级聚合(由该 item 全部样本的逐样本判定得出)。 /// /// 无样本 → null(未录;**不可自动 PASS**) /// 任一 INVALID → INVALID /// 任一 FAIL → FAIL /// 全部 PASS → PASS /// /// 刻意不引入「录满样本量才算完成」——IQC 无 required_sample_qty 数据来源。 /// public static string? AggregateNumericItem(IReadOnlyList? sampleJudgements) { if (sampleJudgements == null || sampleJudgements.Count == 0) return null; if (sampleJudgements.Any(j => j == IqcJudgement.Invalid)) return IqcJudgement.Invalid; if (sampleJudgements.Any(j => j == IqcJudgement.Fail)) return IqcJudgement.Fail; if (sampleJudgements.All(j => j == IqcJudgement.Pass)) return IqcJudgement.Pass; // 出现非三值枚举的脏值(理论不可达:judgement 由本引擎唯一写入 + DB CHECK 兜底) return IqcJudgement.Invalid; } /// /// NON_NUMERIC 项目级判定(直接消费计数真源,不经过样本)。 /// /// unqualified > 0 → FAIL /// unqualified = 0 且 qualified 非空 → PASS /// 其余(数据不足:任一为空) → null(未录) /// /// 「数据不足」刻意判为 null 而非 INVALID:INVALID 的语义是「系统无法形成有效测量判定」, /// 而计数为空只是「还没录」,与 NUMERIC 无样本时返回 null 保持一致。 /// **绝不**根据 Header 报检数量自动补齐 qualified。 /// public static string? JudgeNonNumericItem(decimal? qualifiedQty, decimal? unqualifiedQty) { if (unqualifiedQty == null || qualifiedQty == null) return null; if (unqualifiedQty.Value > 0m) return IqcJudgement.Fail; return IqcJudgement.Pass; } /// /// 人工判定合法性。只允许 PASS / FAIL —— 不得人工设置 INVALID /// (INVALID 表示系统无法判定,是派生事实,不是人可以宣称的结论)。 /// public static bool IsValidManualJudgement(string? judgement) => judgement == IqcJudgement.Pass || judgement == IqcJudgement.Fail; /// 提交放行判据:只有已给出 PASS / FAIL 的项目才算完成(NULL 与 INVALID 均不放行)。 public static bool IsSubmittable(string? itemJudgement) => itemJudgement == IqcJudgement.Pass || itemJudgement == IqcJudgement.Fail; }