| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- using System.Globalization;
- namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse;
- /// <summary>录入类型(ado_s5_iqc_insp_bill_item.result_type 的合法值,与 ck_s5_iqc_item_result_type 对齐)。</summary>
- 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;
- }
- /// <summary>
- /// 判定值(与 ck_s5_iqc_item_judgement / ck_s5_iqc_sample_judgement 对齐)。
- /// 刻意**不含** PENDING —— Batch 1 已从枚举中移除:未录 = NULL,已录 = PASS/FAIL/INVALID 三值封闭。
- /// </summary>
- public static class IqcJudgement
- {
- public const string Pass = "PASS";
- public const string Fail = "FAIL";
- /// <summary>系统无法形成有效测量判定(限值缺失 / 值不可解析)。**不允许人工设置**。</summary>
- public const string Invalid = "INVALID";
- }
- /// <summary>判定出处(与 ck_s5_iqc_item_jsrc 对齐)。</summary>
- public static class IqcJudgementSource
- {
- /// <summary>由测量真源实时重算,每次保存无条件覆盖。</summary>
- public const string Auto = "AUTO";
- /// <summary>检验员显式结论;测量事实一旦变更即强制复位为 AUTO。</summary>
- public const string Manual = "MANUAL";
- }
- /// <summary>
- /// 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 语义一致,按同规则实现。
- /// </summary>
- public static class IqcInspectionJudge
- {
- /// <summary>
- /// 解析十进制数(限值与样本值共用)。空/空白/不可解析 → null。
- /// 口径沿用 S6ProcessInspectionJudge:NumberStyles.Any + InvariantCulture + Trim,
- /// 避免服务器区域设置漂移导致小数点/千分位误判(刻意不采用 S7 的默认 Culture 写法)。
- /// </summary>
- 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;
- }
- /// <summary>
- /// NUMERIC 逐样本判定。只用快照冻结的 upper_limit / lower_limit,**绝不解析 inspection_standard 自由文本**。
- ///
- /// 上下限均缺失 → INVALID(无判据,不可默认 PASS)
- /// 样本值不可解析 → INVALID(保留 raw 供复盘)
- /// 双侧齐备 → lower ≤ v ≤ upper
- /// 仅上限 → v ≤ upper
- /// 仅下限 → v ≥ lower
- /// </summary>
- 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;
- }
- /// <summary>
- /// NUMERIC 项目级聚合(由该 item 全部样本的逐样本判定得出)。
- ///
- /// 无样本 → null(未录;**不可自动 PASS**)
- /// 任一 INVALID → INVALID
- /// 任一 FAIL → FAIL
- /// 全部 PASS → PASS
- ///
- /// 刻意不引入「录满样本量才算完成」——IQC 无 required_sample_qty 数据来源。
- /// </summary>
- public static string? AggregateNumericItem(IReadOnlyList<string?>? 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;
- }
- /// <summary>
- /// NON_NUMERIC 项目级判定(直接消费计数真源,不经过样本)。
- ///
- /// unqualified > 0 → FAIL
- /// unqualified = 0 且 qualified 非空 → PASS
- /// 其余(数据不足:任一为空) → null(未录)
- ///
- /// 「数据不足」刻意判为 null 而非 INVALID:INVALID 的语义是「系统无法形成有效测量判定」,
- /// 而计数为空只是「还没录」,与 NUMERIC 无样本时返回 null 保持一致。
- /// **绝不**根据 Header 报检数量自动补齐 qualified。
- /// </summary>
- 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;
- }
- /// <summary>
- /// 人工判定合法性。只允许 PASS / FAIL —— 不得人工设置 INVALID
- /// (INVALID 表示系统无法判定,是派生事实,不是人可以宣称的结论)。
- /// </summary>
- public static bool IsValidManualJudgement(string? judgement)
- => judgement == IqcJudgement.Pass || judgement == IqcJudgement.Fail;
- /// <summary>提交放行判据:只有已给出 PASS / FAIL 的项目才算完成(NULL 与 INVALID 均不放行)。</summary>
- public static bool IsSubmittable(string? itemJudgement)
- => itemJudgement == IqcJudgement.Pass || itemJudgement == IqcJudgement.Fail;
- }
|