IqcInspBillItemReader.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. using Admin.NET.Plugin.AiDOP.MaterialWarehouse.Dto;
  2. namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse;
  3. /// <summary>
  4. /// S5 IQC 项目实例 + 逐样本 的只读读取器(Batch 3)。
  5. ///
  6. /// 单独成类的原因:Detail(GET)与 save-detail(POST 回执)需要**完全相同**的行形状与汇总口径,
  7. /// 放一处可避免两边 SQL 漂移。本类只读,不含任何写入。
  8. ///
  9. /// 【租户边界】所有查询都显式带 tenant_id 谓词,且首列即 tenant_id(命中 uk/idx 最左前缀)。
  10. /// 绝不依赖 billId / itemId 的全局唯一性作为隔离手段。
  11. /// </summary>
  12. public class IqcInspBillItemReader : ITransient
  13. {
  14. private readonly ISqlSugarClient _db;
  15. public IqcInspBillItemReader(ISqlSugarClient db) => _db = db;
  16. /// <summary>
  17. /// 该检验单的项目实例条数。0 ⇒ LEGACY MODE(历史单 / NO_SPEC 新单 / MATCHED+0 明细新单,同一语义)。
  18. /// 判据只看数据,不看日期 / 单号 / 版本。
  19. /// </summary>
  20. public async Task<int> CountItemsAsync(long billId, long tenantId)
  21. => await _db.Ado.GetIntAsync(
  22. "SELECT COUNT(1) FROM ado_s5_iqc_insp_bill_item WHERE tenant_id=@TenantId AND inspection_bill_id=@BillId",
  23. new SugarParameter("@TenantId", tenantId),
  24. new SugarParameter("@BillId", billId));
  25. /// <summary>
  26. /// 载入该检验单的全部项目实例(含其样本,按 seq/id 与 sample_index 升序)。
  27. /// itemCount=0 时返回空列表(不是 null)。
  28. /// </summary>
  29. public async Task<List<IqcInspBillItemLineRow>> LoadLinesAsync(long billId, long tenantId)
  30. {
  31. var pars = new List<SugarParameter>
  32. {
  33. new("@TenantId", tenantId),
  34. new("@BillId", billId),
  35. };
  36. var lines = await _db.Ado.SqlQueryAsync<IqcInspBillItemLineRow>(
  37. """
  38. SELECT
  39. id AS Id,
  40. seq AS Seq,
  41. source_spec_id AS SourceSpecId,
  42. source_spec_item_id AS SourceSpecItemId,
  43. inspection_item AS InspectionItem,
  44. inspection_standard AS InspectionStandard,
  45. method_tool AS MethodTool,
  46. image_category AS ImageCategory,
  47. sampling_scheme AS SamplingScheme,
  48. spec_remark AS SpecRemark,
  49. upper_limit AS UpperLimit,
  50. lower_limit AS LowerLimit,
  51. result_type AS ResultType,
  52. item_judgement AS ItemJudgement,
  53. judgement_source AS JudgementSource,
  54. qualified_qty AS QualifiedQty,
  55. unqualified_qty AS UnqualifiedQty,
  56. result_remark AS ResultRemark,
  57. last_result_by AS LastResultBy,
  58. last_result_at AS LastResultAt
  59. FROM ado_s5_iqc_insp_bill_item
  60. WHERE tenant_id = @TenantId AND inspection_bill_id = @BillId
  61. ORDER BY seq, id
  62. """,
  63. pars);
  64. if (lines.Count == 0) return lines;
  65. // 样本按单一次取(反规范化的 inspection_bill_id 使其免 JOIN),再按 item 分组
  66. var samples = await _db.Ado.SqlQueryAsync<SampleRow>(
  67. """
  68. SELECT
  69. id AS Id,
  70. item_id AS ItemId,
  71. sample_index AS SampleIndex,
  72. sample_value_raw AS SampleValueRaw,
  73. sample_numeric AS SampleNumeric,
  74. judgement AS Judgement,
  75. remark AS Remark,
  76. inspector_id AS InspectorId,
  77. inspection_time AS InspectionTime
  78. FROM ado_s5_iqc_insp_bill_item_sample
  79. WHERE tenant_id = @TenantId AND inspection_bill_id = @BillId
  80. ORDER BY item_id, sample_index
  81. """,
  82. pars);
  83. var byItem = samples.GroupBy(s => s.ItemId).ToDictionary(g => g.Key, g => g.ToList());
  84. foreach (var line in lines)
  85. {
  86. if (!byItem.TryGetValue(line.Id, out var list)) continue;
  87. line.Samples = list.Select(s => new IqcInspBillItemSampleRow
  88. {
  89. Id = s.Id,
  90. SampleIndex = s.SampleIndex,
  91. SampleValueRaw = s.SampleValueRaw,
  92. SampleNumeric = s.SampleNumeric,
  93. Judgement = s.Judgement,
  94. Remark = s.Remark,
  95. InspectorId = s.InspectorId,
  96. InspectionTime = s.InspectionTime,
  97. }).ToList();
  98. }
  99. return lines;
  100. }
  101. /// <summary>实时聚合汇总(不落表,避免与 item 行形成第二个判定真源)。</summary>
  102. public static IqcInspBillLineSummary Summarize(IReadOnlyList<IqcInspBillItemLineRow> lines)
  103. {
  104. var s = new IqcInspBillLineSummary { ItemCount = lines.Count };
  105. foreach (var l in lines)
  106. {
  107. switch (l.ItemJudgement)
  108. {
  109. case IqcJudgement.Pass: s.PassCount++; s.JudgedCount++; break;
  110. case IqcJudgement.Fail: s.FailCount++; s.JudgedCount++; break;
  111. case IqcJudgement.Invalid: s.InvalidCount++; break;
  112. default: s.UnjudgedCount++; break;
  113. }
  114. }
  115. return s;
  116. }
  117. /// <summary>样本原始行(含 ItemId 用于分组,不外泄到 API 契约)。</summary>
  118. private sealed class SampleRow
  119. {
  120. public long Id { get; set; }
  121. public long ItemId { get; set; }
  122. public int SampleIndex { get; set; }
  123. public string? SampleValueRaw { get; set; }
  124. public decimal? SampleNumeric { get; set; }
  125. public string? Judgement { get; set; }
  126. public string? Remark { get; set; }
  127. public long? InspectorId { get; set; }
  128. public DateTime? InspectionTime { get; set; }
  129. }
  130. }