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