Explorar o código

feat(s5): snapshot IQC inspection items on bill generation | server 1.0.530

YY968XX hai 4 días
pai
achega
f342cb3faf

+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET 通用权限开发平台</Description>
     <Description>Admin.NET 通用权限开发平台</Description>
-    <AssemblyVersion>1.0.529</AssemblyVersion>
-    <FileVersion>1.0.529</FileVersion>
-    <Version>1.0.529</Version>
+    <AssemblyVersion>1.0.530</AssemblyVersion>
+    <FileVersion>1.0.530</FileVersion>
+    <Version>1.0.530</Version>
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>

+ 11 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/MaterialWarehouse/Dto/IqcTaskEntryDto.cs

@@ -80,4 +80,15 @@ public class IqcGenerateInspBillOutput
 
 
     /// <summary>候选检规数量(可观测性用;0 表示该物料无检规映射)。</summary>
     /// <summary>候选检规数量(可观测性用;0 表示该物料无检规映射)。</summary>
     public int SpecCandidateCount { get; set; }
     public int SpecCandidateCount { get; set; }
+
+    /// <summary>
+    /// B2:本单实际冻结的检规明细条数(写入 ado_s5_iqc_insp_bill_item 的行数)。
+    ///
+    /// 与 <see cref="SpecStatus"/> 正交,不可互相推断:
+    ///   · SpecStatus=MATCHED* 且 ItemCount&gt;0 → 已冻结 N 条检验项目
+    ///   · SpecStatus=MATCHED* 且 ItemCount=0 → 合法状态:命中检规,但该检规未维护明细
+    ///   · SpecStatus=NO_SPEC / NO_EFFECTIVE_SPEC / AMBIGUOUS → 无快照对象,恒为 0
+    /// 0 一律不视为错误(S5 刻意放宽,见 S5IqcSpecResolver.cs:76-79)。
+    /// </summary>
+    public int ItemCount { get; set; }
 }
 }

+ 42 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/MaterialWarehouse/IqcTaskEntryService.cs

@@ -270,6 +270,10 @@ public class IqcTaskEntryService : IDynamicApiController, ITransient
         // 真正的系统错误(DB 不可用 / SQL 异常)仍按异常向上抛,不吞。
         // 真正的系统错误(DB 不可用 / SQL 异常)仍按异常向上抛,不吞。
         var spec = await _specResolver.ResolveAsync(tenantId, entry.MaterialCode, now);
         var spec = await _specResolver.ResolveAsync(tenantId, entry.MaterialCode, now);
 
 
+        // B2:本单冻结的检规明细条数。闭包内赋值,事务回滚时该返回值不会被使用(324 行直接抛)。
+        // 与 SpecStatus 是两个正交事实:SpecStatus=MATCHED 且 ItemCount=0 是合法状态(检规未维护明细)。
+        var itemCount = 0;
+
         var tran = await _db.Ado.UseTranAsync(async () =>
         var tran = await _db.Ado.UseTranAsync(async () =>
         {
         {
             await _db.Ado.ExecuteCommandAsync(
             await _db.Ado.ExecuteCommandAsync(
@@ -320,6 +324,43 @@ public class IqcTaskEntryService : IDynamicApiController, ITransient
                 new SugarParameter("@Jygfid", spec.IsMatched && spec.SpecId.HasValue
                 new SugarParameter("@Jygfid", spec.IsMatched && spec.SpecId.HasValue
                     ? spec.SpecId.Value.ToString()
                     ? spec.SpecId.Value.ToString()
                     : (object)DBNull.Value));
                     : (object)DBNull.Value));
+
+            // ── B2 检规明细快照(与 Header 同一事务)────────────────────────────────
+            // 把"生成时点"的检规明细只读冻结进 ado_s5_iqc_insp_bill_item,此后详情/审核
+            // 一律读该快照,绝不回查 qms_jygfzb(否则 S0 改检规会篡改历史检验单)。
+            //
+            // 【租户边界】源侧 z.tenant_id=@TenantId 过滤;写入的 tenant_id 取调用方解析值,
+            //   **绝不透传 z.tenant_id**,杜绝跨租户检规被快照进本租户单据。
+            // 【只读边界】qms_jygfzb 仅 SELECT,本方法不对任何 qms_* 做写入。
+            // 【0 行合法】无快照对象时写 0 行且不抛错,Header 照常提交:
+            //   · IsMatched=false(NO_SPEC / NO_EFFECTIVE_SPEC / AMBIGUOUS)→ 不执行本 INSERT
+            //   · IsMatched=true 但该检规 0 明细 → INSERT…SELECT 命中 0 行
+            //   刻意不照抄 S6 的 "lineCount<=0 → throw"(S6ProcessInspectionService.cs:157-158):
+            //   S5 的报检分录是 165 贴源既成事实,Ai-DOP 无权因检规配置不全拒绝建检验单
+            //   (依据 S5IqcSpecResolver.cs:76-79 的刻意差异)。
+            // 【result_type 不推导】Result 组 8 列一律不写、保持 NULL;录入类型由 N1 首次
+            //   录入时选定(qms_jygfzb 无 lrlx 列,不存在可冻结的来源)。
+            if (spec.IsMatched && spec.SpecId.HasValue)
+            {
+                itemCount = await _db.Ado.ExecuteCommandAsync(
+                    """
+                    INSERT INTO ado_s5_iqc_insp_bill_item (
+                        tenant_id, inspection_bill_id, seq, source_spec_id, source_spec_item_id,
+                        inspection_item, inspection_standard, method_tool, image_category,
+                        sampling_scheme, spec_remark, upper_limit, lower_limit
+                    )
+                    SELECT
+                        @TenantId, @BillId, z.xh, @SpecId, z.id,
+                        z.jyxm, z.jybz, z.jygjjyff, z.txfl,
+                        z.cyfa, z.zs, z.sx, z.xx
+                    FROM qms_jygfzb z
+                    WHERE z.tenant_id = @TenantId AND z.glid = @SpecId
+                    ORDER BY z.xh, z.id
+                    """,
+                    new SugarParameter("@TenantId", tenantId),
+                    new SugarParameter("@BillId", billId),
+                    new SugarParameter("@SpecId", spec.SpecId.Value));
+            }
         });
         });
         if (!tran.IsSuccess) throw tran.ErrorException ?? Oops.Oh("生成检验单失败");
         if (!tran.IsSuccess) throw tran.ErrorException ?? Oops.Oh("生成检验单失败");
 
 
@@ -331,6 +372,7 @@ public class IqcTaskEntryService : IDynamicApiController, ITransient
             SpecNo = spec.IsMatched ? spec.SpecNo : null,
             SpecNo = spec.IsMatched ? spec.SpecNo : null,
             SpecWarning = spec.Warning,
             SpecWarning = spec.Warning,
             SpecCandidateCount = spec.CandidateCount,
             SpecCandidateCount = spec.CandidateCount,
+            ItemCount = itemCount,
         };
         };
     }
     }