|
|
@@ -0,0 +1,728 @@
|
|
|
+# S5 来料检验(IQC)链路打通方案与执行任务书
|
|
|
+
|
|
|
+| 项 | 内容 |
|
|
|
+|----|------|
|
|
|
+| 目标 | 让「扫码收货 → 自动报检 → IQC 检验合格 → 上架入库」在 **Ai-DOP** 里可见、可操作、可闭环 |
|
|
|
+| 编写日期 | 2026-08-11 |
|
|
|
+| 状态 | **决策全部完成;P0–P3 代码已落地(2026-08-11)**:后端 `1.0.332` / 前端 `2.4.284`。待重启后端后做 P4 端到端验收(认领→建单→提交→APP 上架) |
|
|
|
+| ⚠️ 前提变更 | **旧 DOP 已停运,仅作移植参考,不得要求任何旧 DOP 上的操作**(2026-08-11 确认)。由此:① 原 P0「在旧 DOP 跑一次真实检验」作废,改为从 165 低代码元数据静态取证,已完成;② §5 原「回退到旧 DOP Web 判定」的退路**不存在**,方案 B 是唯一可行路径 |
|
|
|
+| 触发场景 | UAT 实测:`PO202608110003` / 收货单 `RC202608110001` / 批号 `260811013` / 500 件 |
|
|
|
+| 关联文档 | [`旧DOP-MES-WMS/对接任务书/00-总体方案.md`](./旧DOP-MES-WMS/对接任务书/00-总体方案.md) §4.3、[`WP3-回写执行器`](./旧DOP-MES-WMS/对接任务书/WP3-回写执行器.md)、[`WP8-实时回读热链路`](./旧DOP-MES-WMS/对接任务书/WP8-实时回读热链路.md)、[`附录A-手册操作覆盖矩阵`](./旧DOP-MES-WMS/对接任务书/附录A-手册操作覆盖矩阵.md) §4.1 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 1. 结论摘要
|
|
|
+
|
|
|
+**Ai-DOP 的 IQC 功能不是没做,是三处断开**:
|
|
|
+
|
|
|
+| # | 断点 | 现状 | 后果 |
|
|
|
+|---|------|------|------|
|
|
|
+| **B1** | 165 的报检单/检验单没有回读到本库业务表 | `aidopdev.qms_qcp_inspecapplyn` / `qms_qcp_inspbill` 各只有 6 条 UAT 造数;真实报检单 `RQLLJYSQ202608110001` 只在 165 | Ai-DOP 三个 IQC 页面看不到这批 500,判定无从下手 |
|
|
|
+| **B2** | 本库没有「报检单 → 检验单」的生成动作 | 任务列表直读 `qms_qcp_inspbill`,无派工/建单入口 | 即使报检单回读进来,任务列表仍为空 |
|
|
|
+| **B3** | 合格回推打在占位目标上 | `TryEnqueueIqcPassOutboxAsync` 写 `target_source_code='QMS_API'`、`path='/iqc/result'`;`mdp_source.QMS_API` 的 `status=0` | 在 Ai-DOP 判合格,165 侧箱码不会解除待检,货放不出来 |
|
|
|
+
|
|
|
+**已有的部分比预想完整**:后端 `IqcInspBillFlowService`(`api/S5IqcInspBillFlow`)已实现检验员提交结果、主管通过/退回、SQE 处置四个动作与审批流;前端 `iqcTaskList.vue` 的详情抽屉已接这四个动作;再往下还有 L0/L1 合格入库过账骨架(`IqcReceiptStateMdpSyncService`、`IqcInventoryEventConsumer`、`AdoIqcInventoryPosting`)。**缺的是数据进出,不是业务逻辑。**
|
|
|
+
|
|
|
+**打通方案与既有架构定案冲突,Q1 已决为方案 B**(见 §4)。
|
|
|
+
|
|
|
+**旧 DOP 停运使这件事从「优化」变成「必做」**:IQC 判定原本只在旧 DOP Web 上有入口(165 的 APP 菜单 `type=1` 里没有判定录入项,见 §2.1),旧 DOP 停运后该动作**当前无任何系统可执行**——待检库的货只能进不能出。因此本方案不是三个候选之一,而是唯一出路,`§4` 的方案 A(守定案、判定留在旧 DOP)已随之失效。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 2. 取证基线(2026-08-11 实测)
|
|
|
+
|
|
|
+以下均为直连两库实测,非推断;标注「推断」者需 P0 取证。
|
|
|
+
|
|
|
+### 2.1 165(dopdemorq)侧
|
|
|
+
|
|
|
+| 对象 | 实测值 |
|
|
|
+|------|--------|
|
|
|
+| 报检单 `qms_qcp_inspecapplyn` | `FBILLNO=RQLLJYSQ202608110001`、`FBILLTYPE=来料检验申请`、`FAPPLYTIME=2026-08-11 13:09:37`、`FQUALITYORG=NULL`、`FINSPECORGID=NULL` |
|
|
|
+| 报检分录 `qms_qcp_insappnentry` | `FMATERIALCFG=81HC0744`、`FLOTNUMBER=260811013`、`FAPPLYQTY=500`、`FSRCORDERNUM=RC202608110001`、`FSRCORDERTYPE=po`、`shdh=10003232-20260811-0001`、`FSUPPLIER=10003232`、`FWAREHOUSEID=1000`、`FLOCATIONID=01-01-01`、**`FINSPECTSTATUS=未检验`**、`FINSPEDEPTID/FINSPECTORID/jyfzr=NULL` |
|
|
|
+| 检验单 `qms_qcp_inspbill` | **0 行**(尚未派工) |
|
|
|
+| 收货明细 `PurOrdRctDetail` | `RecID=48656`、**`RctType='rc'`**、`Receiver=RC202608110001`、`OrdNbr=PO202608110003`、`OrdLine=1`、`RctQty=500`、`QtyReceived=500`、**`QCNbr=''`、`StatusByQC=''`**、`Location=1000`、`InvShelf=01-01-01`、`IsChanged=0`、`Potype=po` |
|
|
|
+| 采购明细 `PurOrdDetail` | `RctQty=500`、**`ReceiptQty=0`**、`QtyReturned=0` |
|
|
|
+| 箱码 `MissedPrint` | `...0010001`:`Status='I'`、`RctNbr=RC202608110001`、`Qty=500`;另两箱 `...0010002`(500)、`...0010003`(67) 未收货、`RctNbr=''` |
|
|
|
+| 库位 | `1000=待检库`、`1001=合格库`;`LocationShelfMaster` 存在 `1001/01-01-01`,**不存在 `1000/01-01-01`** |
|
|
|
+| 上架人员 `EmpWorkDutyMaster` | `Duty` 含 `up-shelf` 的记录 **0 行**;`MobileTask` **0 行** |
|
|
|
+| APP 菜单 `rf_menu`(`type=1`) | `WMS菜单` 下有 `采购收货`、`上架移库`、`检验后部分收货`、`检验确认`、`IQC留样`;**无 IQC 判定录入入口** |
|
|
|
+| Web 菜单 `rf_menu`(`type=0`) | `数字化运营 > S5物料仓储 > 来料检验 > {来料检验申请列表 / 来料检验任务列表 / 来料检验结果列表}`;另有等价老路径 `质量管理 > 来料质量管理` |
|
|
|
+
|
|
|
+### 2.2 旧 DOP 的 QMS → WMS 写回链路(存储过程取证)
|
|
|
+
|
|
|
+`qms_WMS_SaveIQCResult @id`(`@id` = `qms_qcp_inspbill.id`)是唯一的桥,关联链为:
|
|
|
+
|
|
|
+```
|
|
|
+qms_qcp_inspbill a
|
|
|
+ ⋈ (a.lydjbh = b.FBILLNO) qms_qcp_inspecapplyn b
|
|
|
+ ⋈ (b.id = c.glid AND a.hid = c.id) qms_qcp_insappnentry c
|
|
|
+ ⋈ ('u_' + rf_user.id = a.jyr) rf_user d
|
|
|
+```
|
|
|
+
|
|
|
+**完整五分支判定矩阵**(`pd`=判定 0合格/1不合格,`clfs`=处理方式;`dhsl`=检验数量,`bhgsl`=不合格数量。2026-08-11 逐分支读全过程定本,P3 照此实现):
|
|
|
+
|
|
|
+| # | 分支条件 | `@QCStatus` | `@MRBStatus` | `@QtyAcc` | `@QtyReject` |
|
|
|
+|---|---------|-------------|--------------|-----------|--------------|
|
|
|
+| 1 | `pd=0` 且 `clfs` 空 —— 合格 | `YES` | 0 | `dhsl` | 0 |
|
|
|
+| 2 | `pd=0` 且 `clfs='3'` —— 合格但挑选 | `NO` | 1 | `dhsl` | `bhgsl` |
|
|
|
+| 3 | `pd=1` 且 `clfs='0'` —— 不合格退货 | `NO` | 2 | 0 | `dhsl+bhgsl` |
|
|
|
+| 4 | `pd=1` 且 `clfs='2'` —— 不合格让步接收 | `NO` | 3 | `dhsl+bhgsl` | 0 |
|
|
|
+| 5 | `pd=1` 且 `clfs='3'` —— 不合格挑选 | `NO` | 1 | `dhsl` | `bhgsl` |
|
|
|
+
|
|
|
+五分支恒定传 `@Domain='8010'`、`@Barcodes=''`、**`@Shelf=''`**;五条都不匹配时(如 `pd=1` 且 `clfs='1'`)过程直接返回 `Success=1` 放行,不写任何东西。
|
|
|
+
|
|
|
+五个分支都在成功后写 `UPDATE qms_qcp_insappnentry SET FINSPECTSTATUS='检验完成', jywcsj=convert(varchar(20),getdate(),120) WHERE id=a.hid`。
|
|
|
+
|
|
|
+> **注意 `MRBStatus` 语义与 §2.3 的入口校验注释不一致**:过程注释写 `{0 退货, 1 挑选使用, 2 退货, 3 让步接收}`,而实际调用是 `0=合格放行`、`1=挑选`、`2=退货`、`3=让步接收`。**以本矩阵的实际调用为准**,注释不可信。
|
|
|
+
|
|
|
+**合格分支不传货架**(`@Shelf=''`):上架是独立动作,由 WMS APP 的「上架移库」完成,或由自动推送的 `MobileTask` 承接。
|
|
|
+
|
|
|
+**写回时机(决定 P3 挂点,2026-08-11 定论)**:这对存储过程挂在检验单工作流(`rf_flow.id=414211110453317`「来料检验流程」,`status=1`)的**检验员填写节点**上,不在领导审批节点:
|
|
|
+
|
|
|
+```
|
|
|
+submitBefore : [sql][q]exec qms_WMS_SaveIQCcheck @id='{<InstanceId>}' → 预检,内部调 qms_WMS_SaveIQCResultcheck,返回 ResultCode
|
|
|
+submitAfter : [sql-json][q]exec qms_WMS_SaveIQCResult @id='{<InstanceId>}' → 真写,内部调 pr_WMS_SaveIQCResult
|
|
|
+```
|
|
|
+
|
|
|
+`{<InstanceId>}` 即 `qms_qcp_inspbill.id`(低代码平台的流程实例主键就是业务主表主键)。两支过程的五分支结构逐字一致,差别只在调预检版还是真写版。
|
|
|
+
|
|
|
+**由此可知**:① 旧系统是「检验员提交检验单即写 WMS」,领导审批只走流程;② `FINSPECTSTATUS='检验完成'` 是这对过程自动写的,任务列表上那个「检验完成」按钮只是手工兜底;③ 下游「领导审批」节点的两个按钮脚本分别只是 `completed()` 与 `send()`,**不碰 WMS**——所谓「合格入库」按钮只是结束流程,不做入库。
|
|
|
+
|
|
|
+### 2.3 `pr_WMS_SaveIQCResult` 的硬校验与写入范围
|
|
|
+
|
|
|
+**定位方式(只用三个参数定位,P3 必须保证这三者在 165 侧对得上)**:
|
|
|
+
|
|
|
+```sql
|
|
|
+-- 过程内部构造 @tbPurOrdDetails 的原文(2026-08-11 读取)
|
|
|
+select d.RecID, p.RecID, d.OrdNbr, d.OrdLine, p.Potype, d.QtyReceived,
|
|
|
+ IsNull(p.ReceiptQty,0), d.RctQty, d.QCNbr, IsNull(p.Op,0), IsNull(d.Location,''), d.Delivery
|
|
|
+ from PurOrdRctDetail d with(nolock)
|
|
|
+ left join PurOrdDetail p with(nolock)
|
|
|
+ on d.Domain=p.Domain and d.OrdNbr=p.PurOrd and d.OrdLine=p.Line
|
|
|
+ where d.Domain=@Domain and d.Receiver=@Receiver and d.ItemNum=@ItemNum
|
|
|
+```
|
|
|
+
|
|
|
+即定位键 = `Domain + Receiver(收货单号)+ ItemNum(物料号)`,**粒度是「收货单 + 物料」,不是箱码、不是行号**。派生变量:
|
|
|
+
|
|
|
+| 变量 | 来源 | 本例实测值 | 作用 |
|
|
|
+|------|------|-----------|------|
|
|
|
+| `@QtyReceived` | `sum(PurOrdRctDetail.QtyReceived)` | 500 | 数量平衡校验 |
|
|
|
+| `@RctQCNbr` | `max(PurOrdRctDetail.QCNbr)` | `''` | **幂等判据**:非空即拒 |
|
|
|
+| `@IsRctTemp` | `max(case when PurOrdDetail.ReceiptQty>0 then 1 else 0 end)` | **0** | 门控收货过账与上架任务 |
|
|
|
+| `@IsCommission` | `max(case when PurOrdDetail.Potype='PW' then 1 else 0 end)` | 0 | 委外标记 |
|
|
|
+
|
|
|
+入口校验(踩到即拒,P3 入队前必须自行预检,否则消息会在 Outbox 里反复失败):
|
|
|
+
|
|
|
+- 定位不到收货记录 → 「该收货单号(x)没有找到符合的收货记录」
|
|
|
+- `PurOrdRctDetail` join 不到 `PurOrdDetail`(`PurOrdRecID is null`)→ 「没有找到对应的采购单明细」。**上一轮已把自建单的 `PurOrdMaster`/`PurOrdDetail` 推入 165,此条才得以满足;`Domain`/`PurOrd`/`Line` 三者必须严格对齐**
|
|
|
+- `@QCStatus` 只接受 `YES`/`NO`
|
|
|
+- `YES` 时 `@QtyAcc>0` 且 `@QtyReject=0`
|
|
|
+- 该收货单该物料已有 `QCNbr` → 报「已有检验单号,不要重复推送」(`@PartRctFlag<>2` 时)
|
|
|
+- `@QtyAcc + @QtyReject` 必须等于 `sum(QtyReceived)`;`@PartRctFlag=2`(挑选)时改为校验 `@QtyAcc = @RctQty`
|
|
|
+- 箱码总数校验:`MissedPrint`(`RctNbr=@Receiver`、`ItemNum`、`Status<>'C'`、`Type<>'Card'`)的 `sum(Qty)` 必须等于收货数
|
|
|
+
|
|
|
+写入范围(关键:受 `@IsRctTemp` 门控):
|
|
|
+
|
|
|
+| 写入动作 | 门控条件 | 对本例(`RctType='rc'`、`ReceiptQty=0`)是否命中 |
|
|
|
+|---------|---------|------------------------------|
|
|
|
+| `UPDATE MissedPrint SET Status/InvStatus/QtyAccN/QtyRejectN/OrdNbr/Remark` | 无门控(`@IsRctTemp=0` 时 WHERE 恒真) | ✅ 命中,`Status` 由 `I` → `N` |
|
|
|
+| `INSERT MissedPrintTransHist` 一行 | 同上 | ✅ 命中,`TransType='来料检验-通过'` |
|
|
|
+| `UPDATE PurOrdRctDetail SET RctQty/QtyReturn/Qc/QcDate/QCNbr/StatusByQC/QCDescr` | `RctType='temp'` 分支 | ❌ 不命中 |
|
|
|
+| `exec pr_WMS_BPM_RctPurOrdAndPurOrdByBarcode`(真正的收货过账:`PurOrdRct*`/`InvTransHist`/`LocationDetail`) | `@IsRctTemp>0 and @QtyAcc>0` | ❌ 不命中 |
|
|
|
+| `exec pr_WMS_BPM_AddMobileTask @Name='InvUpShelf'`(推上架任务) | 同上 | ❌ 不命中 |
|
|
|
+
|
|
|
+> **✅ 原「最大口径冲突」已于 2026-08-11 静态取证定论,不再是阻断项。** 归因修正如下(原文写的是 `RctType` 门控,**归因有误**):
|
|
|
+>
|
|
|
+> 门控变量 `@IsRctTemp` **不取自 `PurOrdRctDetail.RctType`,而取自 `PurOrdDetail.ReceiptQty`**(`max(case when ReceiptQty>0 then 1 else 0 end)`)。本例 `ReceiptQty=0` → `@IsRctTemp=0` → 收货过账与上架任务分支不命中。结论方向不变,但根因与应对完全不同。
|
|
|
+>
|
|
|
+> **165 存在两条收货路径,本例走甲,两条都是旧系统的正常设计,不是缺陷**:
|
|
|
+>
|
|
|
+> | | 甲 · 正式收货(本例) | 乙 · 暂收在检 |
|
|
|
+> |---|---|---|
|
|
|
+> | 入口 | WMS APP「采购收货」 | WMS APP「检验后部分收货」/「检验确认」 |
|
|
|
+> | 收货时 | 直接建正式收货行,货入待检库 1000,`ReceiptQty=0` | 先暂收,`PurOrdDetail.ReceiptQty>0` |
|
|
|
+> | IQC 合格的净效果 | **只有** `MissedPrint` `I→N` + `MissedPrintTransHist` 一行 | 触发完整收货过账 + 自动推上架 `MobileTask` |
|
|
|
+> | 入库靠什么 | 收货时已入待检库;靠 APP「上架移库」移到合格库 1001 | IQC 时才过账入库 |
|
|
|
+> | 对应过程 | `pr_WMS_SaveIQCResult`(本方案复刻对象) | 疑为 `pr_WMS_SaveIQCResultByRctNbr`(加密,**不在复刻范围**) |
|
|
|
+>
|
|
|
+> **对 P3 的三条硬结论**:
|
|
|
+>
|
|
|
+> 1. **`PurOrdRctDetail.QCNbr/StatusByQC` 在甲路径下旧系统本就不写**。因此 P3 **不得**「显式补写」这两列——补写等于新增旧 DOP 没有的行为,违反 `00B` §7.4 的例外范围(例外只允许复刻旧行为)。原 §6.3 矩阵中该行已据此删除。
|
|
|
+> 2. **判合格 ≠ 合格入库**。甲路径下 IQC 不产生 `InvTransHist`,合格入库数(`pcrksl`)必须等 WMS APP 上架移库之后回读,见 §6.1 状态机第 6–7 步。执行者若在判合格后立刻期待合格入库出数,会误判为 bug。
|
|
|
+> 3. **加密的 `pr_WMS_SaveIQCResultByRctNbr` 已从阻断项移除**:它属乙路径(APP 自用),本方案既不调用也不复刻,其语义未知不影响交付。原 §9 U1 据此关闭。
|
|
|
+
|
|
|
+### 2.4 Ai-DOP(aidopdev)侧
|
|
|
+
|
|
|
+| 对象 | 实测值 |
|
|
|
+|------|--------|
|
|
|
+| 页面 | `FUNC-S5-019 来料检验申请列表`(只读)、`FUNC-S5-001 来料检验任务列表`(含流程抽屉)、`FUNC-S5-002 来料检验结果列表`(空骨架,不发请求) |
|
|
|
+| 判定入口 | 任务列表 → 「查看」→ `IqcInspBillDetailDrawer` → `submit-result` / `approve` / `reject` / `sqe-submit` |
|
|
|
+| 后端 | `api/S5IqcInspBillFlow`:`submit-result` 写 `qms_qcp_inspbill.pd/dhsl/bhgsl/clfs` 并推进 N1→N2;`supervisor-approve` N2→完成后调 `TryEnqueueIqcPassOutboxAsync` |
|
|
|
+| 数据源 | `iqcTaskList` 直读 `aidopdev.qms_qcp_inspbill`(注释:单表直读,表无 `tenant_id` 过滤诉求,`FORGID` 全 NULL) |
|
|
|
+| 本库数据量 | `qms_qcp_inspecapplyn` 6 行、`qms_qcp_inspbill` 6 行,全部为 `IQC-UAT-*` / `B-IQC-APP-001` 造数 |
|
|
|
+| 表结构 | 两库 `qms_qcp_inspbill` 列**完全同构**(63 列同名同序),本库仅多 `tenant_id` |
|
|
|
+| 合格回推 | `mdp_outbox`:`target_source_code='QMS_API'`、`action_code='S5_IQC_RESULT_PUSH'`、`idem_key=FBILLNO`、`payload={path:'/iqc/result',method:'POST',body:{billId,billNo,pd,dhsl,bhgsl}}`;**`mdp_source.QMS_API.status=0`**(P-017 占位);当前该类消息 **0 条**(从未走通过一次合格闭环) |
|
|
|
+| 冷链入站 | `mdp_entity.S5_IQC_INSPBILL_SQLSERVER`(source 12)→ 落 `mdp_stg_iqc_pull`,remark「WP1 MES/WMS 冷链实体 disabled」;**`biz_key_expr='Domain,BillNbr'` 与表结构不符**(`qms_qcp_inspbill` 无 `Domain`、无 `BillNbr` 列)→ 既有缺陷,启用前必须修正 |
|
|
|
+| L0 过账骨架 | `IqcReceiptStateMdpSyncService` 注释自述「**未接线/未实库验证**,不入任何 cron/job」,且「源 B=dopdemorq 方言适配留待,dopdemorq 六表当前空、默认禁用」——**该前提已失效**,165 六表现已有真实数据 |
|
|
|
+| 写源 | `mdp_source.DOPDEMORQ_SQLSERVER`(id 12)`status=1`,`MdpDbPushExecutor` 已实测可写 165(本轮采购单/送货单/箱码推送已跑通) |
|
|
|
+
|
|
|
+### 2.5 旧系统 IQC 行为规格(静态取证产出,替代原 P0 变动列清单)
|
|
|
+
|
|
|
+> **取证方式**:旧 DOP 已停运,无法跑实例。改为直读 165 内的低代码平台元数据 + 存储过程定义反推行为。**本节即原 P0 卡片的产出物,P1–P3 以本节为实现基准。**
|
|
|
+>
|
|
|
+> **重要前提**:旧 DOP 的 IQC **不是 C# 微服务实现的**。旧 DOP 源码(`ZZYDOP_OLD`)全仓搜索 `qms_qcp_inspbill`、`qms_WMS_SaveIQCResult` **零命中**;实现全部在 165 的低代码元数据里,前端按钮通过 `utils.execdb('dopflow', sql)` 直接下 SQL。因此「参考旧 DOP 移植」的对象是本节,不是旧仓源码。
|
|
|
+
|
|
|
+#### 2.5.1 元数据锚点(需复查时按此定位,勿重新摸索)
|
|
|
+
|
|
|
+```
|
|
|
+rf_menu(type=0).applibraryid → rf_applibrary.address = /program/run/index?programid=N → rf_program(N)
|
|
|
+rf_program_button.programid = N -- 列表按钮与脚本
|
|
|
+rf_program.sqlstring -- 列表查询 SQL
|
|
|
+rf_flow(414211110453317).designjson / runjson -- 检验单工作流(含节点事件)
|
|
|
+rf_flow_button.scripts -- 流程按钮脚本
|
|
|
+```
|
|
|
+
|
|
|
+| 菜单(`rf_menu.title`) | `applibraryid` | `programid` | 备注 |
|
|
|
+|---|---|---|---|
|
|
|
+| 来料检验申请列表 | 420078661939269 | 420077129764933 | 菜单 id 有两条:`420100666445893`、`672507140161605` |
|
|
|
+| 来料检验任务列表 | 396053775429701 | 396052925665349 | 菜单 id:`400424766808133`、`678466281668677` |
|
|
|
+| 来料检验单列表 / 来料检验结果列表 | 396054668427333 | 396053981880389 | 两个菜单名共用同一 program |
|
|
|
+| 来料检验流程(检验单表单) | — | `rf_flow.id=414211110453317` | `status=1`;`formId=395649084129349` |
|
|
|
+| 流程模板 type | — | 来料检验 `396760264101957`;委外生产检验 `396760317206597` | 由 `FBIZTYPE` 决定 |
|
|
|
+
|
|
|
+#### 2.5.2 真实操作序列(与原任务书假设不同,P2 据此设计入口)
|
|
|
+
|
|
|
+```
|
|
|
+来料检验任务列表(program 396052925665349)
|
|
|
+ ① 认领 → 写 jyfzr = 当前用户
|
|
|
+ ② 来料检验 → 校验认领人 + 幂等 → 置「检验中」→ 打开检验单工作流
|
|
|
+ ③ 检验单填写提交 → submitBefore 预检 → submitAfter 真写 WMS(此刻箱码解禁)
|
|
|
+ ④ 领导审批 → 「合格入库」= completed() 结束流程;「不合格处理」= send() 转下一节点
|
|
|
+```
|
|
|
+
|
|
|
+**「来料检验申请列表」(program 420077129764933)只有 添加 / 编辑 / 查看 / 删除 四个按钮,没有派工入口**——原任务书 §7 P0 步骤①「在申请列表指派检验员并生成检验任务」的描述与实际不符,派工的真实载体是任务列表的「认领」+「来料检验」。
|
|
|
+
|
|
|
+#### 2.5.3 任务列表四个按钮的确切实现(Q6=全复刻,P2 逐条对照)
|
|
|
+
|
|
|
+| 按钮 | `showtype` | 行为(原文 SQL,`{<UserId>}`/`{<UserName>}` 为平台占位符) |
|
|
|
+|------|-----------|--------------------------------------------------|
|
|
|
+| 认领 | 1 批量 | `update qms_qcp_insappnentry set jyfzr='{<UserId>}' where id in (@ids)` |
|
|
|
+| 检验员调配 | 1 批量 | `update qms_qcp_insappnentry set jyfzr='<表单选定人 jyfzr01>' where id in (@ids)` |
|
|
|
+| 优先级调整 | 1 批量 | 同构 UPDATE(字段为优先级列) |
|
|
|
+| 检验完成 | 1 批量 | `update qms_qcp_insappnentry set FINSPECTSTATUS='检验完成', jywcsj=convert(varchar(20),getdate(),120) where id in (@ids)` |
|
|
|
+| 来料检验 | 2 行级 | 见 2.5.4 |
|
|
|
+
|
|
|
+> `jyfzr` 存在**两种写法**:认领按钮写 `{<UserId>}`(用户 id),而「来料检验」按钮用 `row.jyfzr == '{<UserName>}'`(用户名)做比较,且 `qms_WMS_SaveIQCcheck` 里又按 `'u_' + rf_user.id = inspbill.jyr` 关联。**这是旧系统的不一致,P2 复刻时必须统一为一种口径(建议存用户 id),并在 §9 记录偏离。**
|
|
|
+>
|
|
|
+> 「检验完成」按钮是**手工兜底**:正常路径下该状态由 §2.2 的那对存储过程自动写。
|
|
|
+
|
|
|
+#### 2.5.4 「来料检验」按钮完整逻辑(P2 的核心规格)
|
|
|
+
|
|
|
+```
|
|
|
+1. 前置校验:row.jyfzr 必须等于当前用户 → 否则 alert「尚未认领或非认领人操作!」
|
|
|
+2. 幂等保护:select count(*) from qms_qcp_inspecapplyn a
|
|
|
+ inner join qms_qcp_inspbill b on a.FBILLNO=b.lydjbh
|
|
|
+ where a.FBILLNO=<row.FBILLNO>
|
|
|
+ count>0 → alert「该检验任务已经有检验单,不能重新生成!」
|
|
|
+3. 模板映射:FBIZTYPE='来料检验'→396760264101957;'委外生产检验'→396760317206597
|
|
|
+4. 置状态: update qms_qcp_insappnentry
|
|
|
+ set jykssj=convert(varchar(20),getdate(),20), FINSPECTSTATUS='检验中'
|
|
|
+ where id=<row.id>
|
|
|
+5. 打开表单:/flow/run/index?flowid=414211110453317&type=<模板>&id=<分录id>&po=<FBILLNO>
|
|
|
+```
|
|
|
+
|
|
|
+注意第 4 步 `jykssj` 用格式 **20**(`yyyy-mm-dd hh:mi:ss`),而「检验完成」用格式 **120**——旧系统混用,P2 统一按 `120` 写即可。
|
|
|
+
|
|
|
+#### 2.5.5 检验单 → 报检单 → 收货单的关联链与字段映射(P3 取参照此)
|
|
|
+
|
|
|
+```
|
|
|
+qms_qcp_inspbill a -- 检验单(判定结果所在)
|
|
|
+ ⋈ a.lydjbh = b.FBILLNO qms_qcp_inspecapplyn b -- 报检单
|
|
|
+ ⋈ b.id = c.glid AND a.hid = c.id qms_qcp_insappnentry c -- 报检分录(a.hid 指向分录 id)
|
|
|
+ ⋈ 'u_' + cast(d.id as varchar(50)) = a.jyr rf_user d -- 检验员
|
|
|
+where a.id = @id
|
|
|
+```
|
|
|
+
|
|
|
+| `pr_WMS_SaveIQCResult` 参数 | 取值来源 | 本例值 |
|
|
|
+|---|---|---|
|
|
|
+| `@Domain` | 常量 | `8010` |
|
|
|
+| `@Receiver` | `qms_qcp_insappnentry.FSRCORDERNUM` | `RC202608110001` |
|
|
|
+| `@ItemNum` | `qms_qcp_insappnentry.FMATERIALCFG` | `81HC0744` |
|
|
|
+| `@QCNbr` | `qms_qcp_inspbill.lydjbh` | `RQLLJYSQ202608110001` |
|
|
|
+| `@QtyAcc` / `@QtyReject` | 按 §2.2 五分支矩阵由 `dhsl`/`bhgsl` 算 | 500 / 0 |
|
|
|
+| `@UserNo` | `rf_user.account`(经 `a.jyr` 解析) | — |
|
|
|
+| `@Barcodes` / `@Shelf` | 恒为空串 | `''` |
|
|
|
+
|
|
|
+> **`@QCNbr` 传的是报检单号(`lydjbh`),不是检验单号**——字段名有误导性,P3 勿传 `qms_qcp_inspbill.FBILLNO`。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 3. 已跑通的上游(本方案的前置事实)
|
|
|
+
|
|
|
+2026-08-11 已完成并提交(后端 1.0.331):
|
|
|
+
|
|
|
+1. 自建采购单推 165:`PurOrdMaster`/`PurOrdDetail`/`srm_polist_ds`/`scm_shd`/`scm_shdzb`/`scm_shdshph`/`MissedPrint`,`MdpDbPushExecutor` 支持受限 resolve 子查询解外键
|
|
|
+2. WMS APP 扫码收货实测通过:`PurOrdRctMaster/Detail` 建单、`InvTransHist` 落 `rct-po-ins` 入待检库 1000、箱码 `U→I`
|
|
|
+3. 收货结果回读:`MdpHotWatchService` 新增 `PUR_ORDER` 热关注,回读 `PurOrdDetail.RctQty/ReceiptQty/QtyReturned`、`MissedPrint.Status/RctNbr/Location/Shelf/InvStatus`,并据箱码推进 `scm_shd/scm_shdzb.shzt`
|
|
|
+
|
|
|
+**本方案要补的正是这条链的下半段。**
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 4. 与既有架构定案的冲突(Q1 已决 = 方案 B)
|
|
|
+
|
|
|
+[`00-总体方案.md`](./旧DOP-MES-WMS/对接任务书/00-总体方案.md) §4.3 数据归属划分原文:
|
|
|
+
|
|
|
+> | 执行域(收发存、标签、报工、**检验**) | **165 dopdemorq** | 新系统只入站,**不回写执行字段** |
|
|
|
+
|
|
|
+同文 §4.2 另有:
|
|
|
+
|
|
|
+> **明确不做**(经决策):不新增旧 DOP 没有的自动回写闭环;不开「业务页面直读 165」的旁路。
|
|
|
+
|
|
|
+并且 [`附录A`](./旧DOP-MES-WMS/对接任务书/附录A-手册操作覆盖矩阵.md) 把 IQC 归为「供/读 → 回读 `qms_qcp_inspbill`、`PurOrdRct*`」,[`WP5`](./旧DOP-MES-WMS/对接任务书/WP5-主链路端到端验证.md) 场景 2 第 5 步写的是「IQC 判定 → **WMS APP**」。
|
|
|
+
|
|
|
+**因此「在 Ai-DOP 里做 IQC 判定并回写 165」不是补一个缺口,而是改一条架构定案。** 两个方案对应两种定案:
|
|
|
+
|
|
|
+| | ~~方案 A · 守定案~~(**已失效**) | 方案 B · 改定案 |
|
|
|
+|---|---|---|
|
|
|
+| 判定在哪 | ~~旧 DOP Web~~ → **已停运,无处可做** | **Ai-DOP** |
|
|
|
+| Ai-DOP 角色 | ~~只读可见~~ | 主导判定 + 字段级回写 165 |
|
|
|
+| 触碰红线 | 无 | 需为「检验」一项破 §4.3「不回写执行字段」 |
|
|
|
+| 工作量 | ~~小(1 个卡片)~~ | 中(3 个卡片,P0 已用静态取证完成) |
|
|
|
+| 风险 | ~~低~~ → 实为**最高**:待检库的货无法放行 | 中(双写覆盖;原「`rc`/`temp` 口径冲突」与「加密过程语义未知」两项已于 §2.3 定论关闭) |
|
|
|
+| 用户体感 | ~~判定要切到旧系统~~ | 全流程在 Ai-DOP |
|
|
|
+
|
|
|
+> **Q1 已决(2026-08-11):选方案 B。** 判定权威落在 Ai-DOP,回写 165 按 §6.3 字段共管矩阵执行。
|
|
|
+>
|
|
|
+> **随之而来的强制配套动作**:修订 [`00-总体方案.md`](./旧DOP-MES-WMS/对接任务书/00-总体方案.md) §4.3,把「检验」从执行域的「不回写执行字段」中划为例外,并写明例外范围仅限 §6.3 白名单列;同时在 [`附录A`](./旧DOP-MES-WMS/对接任务书/附录A-手册操作覆盖矩阵.md) 与 [`WP5`](./旧DOP-MES-WMS/对接任务书/WP5-主链路端到端验证.md) 场景 2 第 5 步把 IQC 判定的承接方从「WMS APP」改为「Ai-DOP」。**此项不做,后续执行者会按旧定案否掉本方案**(列入 P3 交付物)。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 5. 第一层:回读(原方案 A;Q1=B 后成为方案 B 的必备底座)
|
|
|
+
|
|
|
+> Q1 已选方案 B,本节不再是「可选方案」,而是 **P1 卡片的设计说明**:无论判定在哪一侧做,报检单都必须先回读进本库业务表,否则 Ai-DOP 无从展示、无从派工。
|
|
|
+>
|
|
|
+> ⚠️ **原「退路」条款已作废**:本节原写「若风险不可接受可暂缓 P2/P3,回退到 165 权威、判定切回旧 DOP Web」。**旧 DOP 已停运,该退路不存在**。P1 单独交付仍有价值(页面能看到真实报检单、不产生废弃代码),但**只交付 P1 等于 IQC 判定仍然无人能做、待检库的货放不出来**,不构成可接受的终态。
|
|
|
+
|
|
|
+### 5.1 范围
|
|
|
+
|
|
|
+把 165 的报检单 / 报检分录 / 检验单回读进本库**业务表**(不是只落贴源表),让三个 S5 IQC 页面显示真实数据。判定结果(无论由 Ai-DOP 还是旧 DOP 产生)都经同一条回读链路回到本库,保证两侧口径一致。
|
|
|
+
|
|
|
+### 5.2 为什么不能只启用冷链实体
|
|
|
+
|
|
|
+`S5_IQC_INSPBILL_SQLSERVER` 落的是 `mdp_stg_iqc_pull` 贴源表,而页面读的是业务表 `aidopdev.qms_qcp_inspbill`。**贴源层 ≠ 业务表**,启用冷链不会让页面出数。且冷链最长 1 小时,达不到人员作业要求(见 `00-总体方案` §4.2)。因此走已跑通的热关注回读(与 §3.3 同一机制)。
|
|
|
+
|
|
|
+### 5.3 改动点(待确认后执行)
|
|
|
+
|
|
|
+| 文件 | 改动 |
|
|
|
+|------|------|
|
|
|
+| `DataPlatform/HotWatch/MdpHotWatchService.cs` | `PUR_ORDER` 关注表清单增加 `qms_qcp_inspecapplyn`、`qms_qcp_insappnentry`、`qms_qcp_inspbill`;新增 `ApplyIqcAsync` 把三表按 §5.4 映射 UPSERT 进本库业务表 |
|
|
|
+| 同上 | `ShouldTerminateAsync` 的 `PUR_ORDER` 终止条件补一条:报检分录 `FINSPECTSTATUS='检验完成'` 且箱码全部离开待检 |
|
|
|
+| `Web/src/views/aidop/s5/iqc/iqcResultList.vue` | 接真实接口(当前是空骨架,不发请求) |
|
|
|
+| `Web/src/views/aidop/s5/iqc/iqcTaskList.vue` | 删除已过期的「只读」注释(第 122–125 行),实际已接流程抽屉 |
|
|
|
+| `mdp_entity.S5_IQC_INSPBILL_SQLSERVER` | 修正 `biz_key_expr`(现为不存在的 `Domain,BillNbr`,应为 `FBILLNO` 或 `id`);是否启用另议 |
|
|
|
+
|
|
|
+### 5.4 回读映射
|
|
|
+
|
|
|
+三表两库列同构,映射规则统一为「同名列 1:1 + 补 `tenant_id`」:
|
|
|
+
|
|
|
+| 165 源表 | 本库目标表 | 业务键 | 补列 |
|
|
|
+|---------|-----------|-------|------|
|
|
|
+| `qms_qcp_inspecapplyn` | 同名 | `FBILLNO` | `tenant_id` |
|
|
|
+| `qms_qcp_insappnentry` | 同名 | `glid` + `FSRCORDERNUM` + `FMATERIALCFG` + `FLOTNUMBER`(`id` 为 165 侧生成,不可直接复制,理由同 `RecID`) | `tenant_id` |
|
|
|
+| `qms_qcp_inspbill` | 同名 | `FBILLNO`(回退 `lydjbh`+`hid`) | `tenant_id` |
|
|
|
+
|
|
|
+租户解析沿用 `ResolveLocalPurOrdTenantAsync`(由 `FSRCORDERNUM` → `PurOrdRctDetail` → `PurOrdDetail.tenant_id`)。
|
|
|
+
|
|
|
+### 5.4.1 回读字段归属(2026-08-11 补)
|
|
|
+
|
|
|
+「同名列 1:1」**不适用于** Ai-DOP 在本库独占写入的列。`qms_qcp_insappnentry` 的 `jyfzr`(认领人)、`yxj`(本库优先级)**不得**被 165 回读覆盖;`FINSPECTSTATUS` 只许前进;`jykssj`/`jywcsj` 空值不覆盖。完整矩阵与修复见 [`S5-IQC热回读覆盖本地自有字段修复执行任务书.md`](./S5-IQC热回读覆盖本地自有字段修复执行任务书.md) §4(后端 `1.0.334`)。
|
|
|
+
|
|
|
+### 5.5 验收
|
|
|
+
|
|
|
+**P1 的验收不依赖任何判定动作**(旧 DOP 已停运,无法造「判定合格」这个前置状态)。用现存的活样本直接验:
|
|
|
+
|
|
|
+1. 本例报检单 `RQLLJYSQ202608110001` 已存在于 165 且 `FINSPECTSTATUS='未检验'` → 登记热关注后 **10 秒内** Ai-DOP「来料检验申请列表」出现该单,状态「未检验」,`FMATERIALCFG=81HC0744`、`FAPPLYQTY=500`、`FSRCORDERNUM=RC202608110001` 与 165 逐列一致
|
|
|
+2. 本库 `MissedPrint.Status` 与 165 保持一致(当前应为 `I`)
|
|
|
+3. 165 侧字段被外部改动后(可用一次**只改本库不改 165** 的反向核对,或等 P3 判定后一并验证)能在 10 秒内跟上;`qms_qcp_inspbill` 在 P2 建单后回读到本库
|
|
|
+4. 全程 165 上无任何新增表/触发器/作业,无整行覆盖
|
|
|
+
|
|
|
+> 「判定完成后状态同步为『检验完成』」这条**移到 P3/P4 验收**,因为在 P3 交付前无人能产生该状态。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 6. 第二层:Ai-DOP 主导判定 + 字段级回写 165(方案 B 主体)
|
|
|
+
|
|
|
+在 §5 回读底座之上追加三段。**Q1 已选 B,本层确定执行。**
|
|
|
+
|
|
|
+### 6.1 目标链路
|
|
|
+
|
|
|
+```
|
|
|
+165 自动报检(WMS 收货触发)
|
|
|
+ → 【P1】回读报检单/分录 → 本库
|
|
|
+ → 【P2】本库认领 + 生成检验单(编号走 165 NbrControl 的 IQC 段:前缀 IQC/1160/YYYYMMDD)
|
|
|
+ → 【已有】Ai-DOP 判定:检验员提交结果 ──【P3】此刻即回推 165(Q5)
|
|
|
+ → 【已有】主管通过(仅 Ai-DOP 内部流程,不再碰 165)
|
|
|
+ → 【WMS APP】上架移库(扫箱码 + 货架 1001/01-01-01)完成入库
|
|
|
+ → 【已有】收货/上架结果回读(§3 已跑通)→ 合格入库数出数
|
|
|
+```
|
|
|
+
|
|
|
+**全链路状态机(165 侧关键列的逐步取值,P4 逐行核对;斜体为本方案交付后才会发生的步骤)**:
|
|
|
+
|
|
|
+| 步 | 动作 | 执行方 | `MissedPrint.Status` | `PurOrdDetail` | 报检分录 `FINSPECTSTATUS` | 库存位置 |
|
|
|
+|---|---|---|---|---|---|---|
|
|
|
+| 1 | 生成标签 | Ai-DOP | `U` | — | — | — |
|
|
|
+| 2 | 扫码收货 | WMS APP | `I` | `RctQty=500`、`ReceiptQty=0` | `未检验`(自动报检) | 待检库 1000 |
|
|
|
+| 3 | 回读 | Ai-DOP | 同步 `I` | 同步 | 同步 | — |
|
|
|
+| 4 | *认领 + 生成检验单* | *Ai-DOP(P2)* | `I` | — | *`检验中`* | 待检库 1000 |
|
|
|
+| 5 | *提交结果(判合格)* | *Ai-DOP(P3)* | *`I`→`N`* | — | *`检验完成`* | **仍在 1000** |
|
|
|
+| 6 | 上架移库 | WMS APP | `N`→(APP 置位) | — | `检验完成` | **1000→1001 合格库** |
|
|
|
+| 7 | 回读 | Ai-DOP | 同步 | — | — | 合格入库数(`pcrksl`)出数 |
|
|
|
+
|
|
|
+**第 5 步不移库、不产生 `InvTransHist`**(§2.3 已定论)。合格入库数在第 7 步才出数,判合格后立刻查会是 0,属正常。
|
|
|
+
|
|
|
+> **Q5 已决(2026-08-11):写回时机 = 检验员提交结果即推 165**,与旧系统 `submitAfter` 挂点一致(§2.2)。主管通过/退回只走 Ai-DOP 内部审批流,不再触发 165 写入。
|
|
|
+>
|
|
|
+> 理由:① 与旧系统时序一致,WMS 侧行为可预期;② 待检库的货不会因为主管未及时审批而卡住;③ 现有 `IqcInspBillFlowService` 的 `submit-result` 已是天然挂点,`supervisor-approve` 里的 `TryEnqueueIqcPassOutboxAsync` 调用需**移到** `submit-result`,不是新增。
|
|
|
+>
|
|
|
+> **副作用须知**:提交即推意味着主管退回时,165 侧箱码已解禁。退回不做 165 侧回滚(旧系统同样不回滚),如需纠正走「不合格处理」重新判定。此项列入 §9 未解问题 U7。
|
|
|
+
|
|
|
+### 6.2 P2 检验单生成的决策(Q2)
|
|
|
+
|
|
|
+165 上本例检验单为 0 行,说明旧 DOP 的派工是人工 Web 动作。两条路:
|
|
|
+
|
|
|
+| 选项 | 做法 | 代价 |
|
|
|
+|------|------|------|
|
|
|
+| **B-2a** | Ai-DOP 自建检验单(本库 `qms_qcp_inspbill` 插行,`lydjbh=报检单FBILLNO`、`hid=报检分录id`),合格后连检验单一起推 165 | 需实现取号与派工 UI;165 上出现「由 Ai-DOP 建的检验单」,需确认旧系统是否接受 |
|
|
|
+| **B-2b** | 派工仍在旧 DOP,Ai-DOP 只回读检验单后做判定 | 用户仍要切系统一次,未彻底打通 |
|
|
|
+
|
|
|
+> **Q2 已决(2026-08-11):选 B-2a**,派工落在 Ai-DOP,检验单在本库自建后随合格闭环推送 165。
|
|
|
+>
|
|
|
+> **取号方案已于 2026-08-11 静态取证更正(原写「走 165 `NbrControl.IQC` 段、与 WMS APP 共用计数器」,依据错误,作废)。**
|
|
|
+>
|
|
|
+> **实测证据**:旧 DOP 的单号由低代码平台的 `rf_serialnumber` 表生成,与 165 的 `NbrControl` 是两套互不相干的机制:
|
|
|
+>
|
|
|
+> | `rf_serialnumber.id` | 用途 | `format` | `numbersize` | `numbertype` | `lasttime` |
|
|
|
+> |---|---|---|---|---|---|
|
|
|
+> | 396082363535429 | 来料检验**申请**单 | `RQLLJYSQ{DateTime<yyyyMMdd>}{number}` | 4 | 0 | **2026-08-11 13:09:37** |
|
|
|
+> | 396082502635589 | 来料**检验单** | `IQC{DateTime<yyyyMMdd>}{number}` | **4** | 3 | 2025-07-15 |
|
|
|
+>
|
|
|
+> 第一行的 `lasttime` 与本例报检单 `RQLLJYSQ202608110001` 的 `FAPPLYTIME`(`2026-08-11 13:09:37`)**精确一致**,且格式与实际单号逐字吻合 → 铁证。由此连带解决原「序号 3 位还是 4 位」的疑问:**4 位,按日重置**。
|
|
|
+>
|
|
|
+> **Q7 已决(2026-08-11):检验单号在 Ai-DOP 本库自建序列,格式复刻 `IQC` + `yyyyMMdd` + 4 位(如 `IQC202608110001`),不碰 165 的任何计数器。**
|
|
|
+>
|
|
|
+> 理由:① `rf_serialnumber` 是低代码平台的配置表,写它属于改 165 数据,破 `00B` §7.1;② 旧 DOP 停运后该表不再有人取号;③ 165 的 `NbrControl.IQC`(`RecID=20`、`NextValue=1`)从未被使用过,且 WMS APP 无 IQC 判定入口(§2.1 实测 `type=1` 菜单无该项)→ **撞号风险为零**,无需跨库共用计数器;④ 本库自建序列可直接用现成的租户内原子分配,不引入跨库事务。
|
|
|
+>
|
|
|
+> ⚠️ 与 P2 卡片里「走 `NbrSequenceService` 取 165 计数器」的旧描述冲突时,**以本条为准**。`numbertype` 的枚举语义(0/1/3/4)平台未文档化,不影响实现——本方案只需「按日重置、4 位」这一确定行为。
|
|
|
+
|
|
|
+### 6.3 P3 回写字段共管矩阵(回写白名单)
|
|
|
+
|
|
|
+遵守红线「回写只允许字段级 UPDATE」。**基线取自 §2.2/§2.3/§2.5 的静态取证,已定本**(原「最终以 P0 实测差异为准」的悬空条款作废:旧 DOP 停运,不存在实测差异这一步)。
|
|
|
+
|
|
|
+| 165 表 | 允许写列 | 值(合格分支,即 §2.2 分支 1) | 幂等键 |
|
|
|
+|-------|---------|--------------|-------|
|
|
|
+| `MissedPrint` | **仅** `Status`、`QtyAccN`、`QtyRejectN`、`UpdateUser`、`UpdateTime` | `Status='N'`、`QtyAccN=Qty`、`QtyRejectN=0` | `Domain+BarCode` |
|
|
|
+| `MissedPrintTransHist` | 整行 INSERT | `TransType='来料检验-通过'`、`Remark='来料检验单:'+QCNbr` | `Domain+BarCode+TransType+CreateTime` |
|
|
|
+| `qms_qcp_insappnentry` | `FINSPECTSTATUS`、`jywcsj`(+ 可选 `FINSPECTORID`、`jyfzr`) | `FINSPECTSTATUS='检验完成'`、`jywcsj=now(格式 120)` | `id`(165 侧) |
|
|
|
+| `qms_qcp_inspbill` | 整行 INSERT(仅 B-2a) | 本库检验单同构推送,`id` 由 165 生成不可复制 | `FBILLNO` |
|
|
|
+
|
|
|
+**箱码状态与数量的赋值规则(`pr_WMS_SaveIQCResult` 原文逻辑,P3 照此实现;无循环、无按箱分摊,比预想简单)**:
|
|
|
+
|
|
|
+```sql
|
|
|
+-- 合格(@QCStatus='YES',即 §2.2 分支 1):所有箱码统一置 N
|
|
|
+QtyAccN = Qty, QtyRejectN = 0, Status = 'N'
|
|
|
+
|
|
|
+-- 其余(@QCStatus='NO'):
|
|
|
+QtyAccN = case when @MRBStatus=3 or IsAccN=1 then Qty else 0 end
|
|
|
+QtyRejectN = case when @MRBStatus in (0,2) then Qty else 0 end
|
|
|
+Status = case when @MRBStatus=3 or IsAccN=1 then 'N'
|
|
|
+ when @MRBStatus in (0,2) or @PartRctFlag=2 then 'R'
|
|
|
+ else Status end -- 保持原状态
|
|
|
+```
|
|
|
+
|
|
|
+| 分支(§2.2 编号) | `@MRBStatus` | 箱码 `Status` 结果 | `QtyAccN` | 结果描述 `@ResultDescr` |
|
|
|
+|---|---|---|---|---|
|
|
|
+| 1 合格 | 0 | 全部 `N` | `Qty` | `通过` |
|
|
|
+| 2 合格但挑选 | 1 | 被挑中(`IsAccN=1`)→`N`,其余保持原状态 | 挑中箱=`Qty` | `MRB挑选使用` |
|
|
|
+| 3 不合格退货 | 2 | 全部 `R` | 0 | `MRB退货` |
|
|
|
+| 4 不合格让步接收 | 3 | 全部 `N` | `Qty` | `MRB让步接收` |
|
|
|
+| 5 不合格挑选 | 1 | 同分支 2 | 同上 | `MRB挑选使用` |
|
|
|
+
|
|
|
+`IsAccN` 仅在挑选场景(`@PartRctFlag=2`,需传 `@Barcodes` 箱码清单)置 1;本方案 `@Barcodes=''`,故分支 2/5 在不传箱码时**不会解禁任何箱码**。
|
|
|
+
|
|
|
+**合格分支的精确最小写入集**(其余列在 `@IsRctTemp=0` 时全部保持原值,勿多写):`Status`:`I`→`N`;`QtyAccN`:`0`→`500`;`QtyRejectN`:`0`;`UpdateUser`/`UpdateTime`。**`InvStatus`、`PurOrd`、`OrdNbr`、`Remark` 均不变**——它们的 `case` 分支都以 `@IsRctTemp>0` 或 `Status='J'` 为条件。
|
|
|
+
|
|
|
+> **`Status='J'`(待点数复核)分支不会触发**:其开关 `GeneralizedCodeMaster`(`FldName='SystemConfig'`、`Val='IsPurOrdRctReconfirmQty'`、判据 `Comments='1'`)在 165 上**未配置该行**,取默认 0。原 P0 第 ⑹ 项据此关闭。
|
|
|
+
|
|
|
+**禁止写(逐条附理由,执行者不得自行放宽)**:
|
|
|
+
|
|
|
+| 表 / 列 | 为什么不写 |
|
|
|
+|---|---|
|
|
|
+| `PurOrdRctDetail.QCNbr` / `StatusByQC` / `QCDescr` / `Qc` / `QcDate` | **旧系统在本例路径(`ReceiptQty=0` → `@IsRctTemp=0`)下本就不写这些列**(§2.3 已定论)。补写等于新增旧 DOP 没有的行为,超出 `00B` §7.4 的例外范围。原矩阵含此行,已删除 |
|
|
|
+| `PurOrdRctDetail.RctQty` / `QtyReturn` | 同上,属 `temp` 分支产物 |
|
|
|
+| `InvTransHist`、`LocationDetail`、`PurOrdRctMaster` | 收货/上架过账产物,由 165 的 `pr_WMS_BPM_RctPurOrdAndPurOrdByBarcode` 与「上架移库」负责;复刻过账等于双写库存 |
|
|
|
+| `PurOrdDetail.ReceiptQty` | **它就是 `@IsRctTemp` 的取值源**(§2.3)。改它会把 165 的收货路径从甲切到乙,行为剧变 —— 最高危禁写项 |
|
|
|
+
|
|
|
+**合格入库数(`pcrksl`)仍从回读的 `InvTransHist` 推导,不由 Ai-DOP 生成**;出数时点见 §6.1 状态机第 7 步。
|
|
|
+
|
|
|
+### 6.4 P3 的执行器改造(Q3)
|
|
|
+
|
|
|
+现有 `TryEnqueueIqcPassOutboxAsync` 打向 `QMS_API` 占位源。两条路:
|
|
|
+
|
|
|
+| 选项 | 做法 | 评价 |
|
|
|
+|------|------|------|
|
|
|
+| **B-3a** | 改为 `target_source_code='DOPDEMORQ_SQLSERVER'`,按 §6.3 矩阵下发多表字段级 UPDATE/INSERT(沿用本轮已跑通的 `MdpDbPushExecutor` + resolve 子查询) | 与红线一致(零存储过程、C# 复刻、字段级),复用已验证通道 |
|
|
|
+| **B-3b** | 推检验单行到 165 后,调用 165 现成的 `qms_WMS_SaveIQCResult @id` 让旧系统自己写 | 语义最保真,但需执行器新增「调存储过程」能力,且与红线 3「新系统零存储过程、用 C# 复刻」的取向相悖 |
|
|
|
+
|
|
|
+> **Q3 已决(2026-08-11):选 B-3a**,改打 `DOPDEMORQ_SQLSERVER`,按 §6.3 矩阵下发字段级 UPDATE / 行 INSERT,复用本轮已实测跑通的 `MdpDbPushExecutor`(含 resolve 子查询解外键)。执行器**不新增调存储过程能力**。
|
|
|
+>
|
|
|
+> **2026-08-11 复核确认 B-3a 可行(曾一度考虑改 B-3b,已否)**:`pr_WMS_SaveIQCResult` 全文 14442 字符、依赖 12 个对象、内部还调两个子过程,看似不可能用字段级复刻。但 §2.3 定论后可知,在本例路径(`@IsRctTemp=0`)下它的**净写入面只有两处**——`UPDATE MissedPrint` 与 `INSERT MissedPrintTransHist`(其余分支全部不命中)。这两处都是简单的字段级写入,`MdpDbPushExecutor` 现有能力即可覆盖。
|
|
|
+>
|
|
|
+> 因此:**无需为本包扩展执行器的存储过程调用能力,`00B` §7.2 保持不开例外**(§11.2 已据此收紧)。若日后要支持乙路径(暂收在检),才需重新评估。
|
|
|
+>
|
|
|
+> 保留 `QMS_API` 分支代码:真实 QMS 地址落地后可双投,不删除。
|
|
|
+
|
|
|
+### 6.5 上架不在本方案范围(Q4 已决:不做)
|
|
|
+
|
|
|
+旧 DOP 合格分支 `@Shelf=''`,上架是独立动作。**Q4 已决(2026-08-11):本次不搬上架**——本方案完成后,上架仍在 WMS APP「上架移库」做(扫箱码 + `1001/01-01-01`)。
|
|
|
+
|
|
|
+若日后要搬进 Ai-DOP,属另一份任务书,且需先补 165 的 `EmpWorkDutyMaster` up-shelf 职责数据(现 0 行,见 [`WP7 §3.5`](./旧DOP-MES-WMS/对接任务书/WP7-基础数据与任务推送.md) 派发卡片)。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 7. 执行任务书(卡片)
|
|
|
+
|
|
|
+### P0 · 静态取证(✅ 已完成 2026-08-11,无需任何操作)
|
|
|
+
|
|
|
+> **原卡片「在旧 DOP 跑通一次真实检验并抓变动列」已作废**:旧 DOP 已停运,仅作移植参考,不得要求任何旧 DOP 上的操作。改用**静态取证**——直读 165 的低代码平台元数据(`rf_program`/`rf_flow`/`rf_serialnumber`)与存储过程定义反推旧系统行为。
|
|
|
+>
|
|
|
+> **静态取证比原计划更完整**:跑一次实例只能覆盖一条判定路径(合格),而静态取证拿到了全部五个分支的规格。**产出即 §2.2、§2.3、§2.5、§6.3,无独立交付物。P1–P3 可直接开工。**
|
|
|
+
|
|
|
+原卡片的六项「关键待验证」逐条结论:
|
|
|
+
|
|
|
+| # | 原待验证项 | 结论 | 依据 |
|
|
|
+|---|-----------|------|------|
|
|
|
+| ⑴ | `PurOrdRctDetail.QCNbr/StatusByQC` 是否被写 | **不写**。且 P3 **不得**补写(原卡片写「若未写则 P3 必须显式补写」,该指示作废——补写属新增旧系统没有的行为) | §2.3 门控表 + `@IsRctTemp` 归因 |
|
|
|
+| ⑵ | 箱码是否 `I→N` | **是**,合格分支全部箱码统一置 `N` | §6.3 赋值规则 |
|
|
|
+| ⑶ | 是否产生 `MobileTask`(上架任务) | **不产生**。`pr_WMS_BPM_AddMobileTask` 的门控是 `@IsRctTemp>0`,本例 `=0` | §2.3 |
|
|
|
+| ⑷ | 库存是否离开待检库 1000 | **不离开**。IQC 不过账,靠 WMS APP「上架移库」移到 1001 | §2.3 + §6.1 状态机 |
|
|
|
+| ⑸ | 检验单号格式(3 位还是 4 位、走哪个计数器) | **`IQC`+`yyyyMMdd`+`4` 位,按日重置**;走低代码 `rf_serialnumber`(id `396082502635589`),**不是** 165 `NbrControl` | §6.2 Q7(`lasttime` 与报检单 `FAPPLYTIME` 精确吻合) |
|
|
|
+| ⑹ | `IsPurOrdRctReconfirmQty` 取值(`Status='J'` 复核分支) | **未配置 → 默认 0,分支不触发**;且它仅在 `@IsRctTemp>0` 时生效,与本例双重无关 | §6.3 末尾注 |
|
|
|
+
|
|
|
+**原卡片「风险:判定不可逆,本例 500 是唯一活样本」现已消解**:静态取证不消耗样本。`RQLLJYSQ202608110001` / 500 件仍处于 `未检验`,**完整保留给 P4 端到端验收**,另两箱(`...0010002`、`...0010003`)仍可作备用样本。
|
|
|
+
|
|
|
+### P1 · 报检/检验单回读(§5 底座,必做)
|
|
|
+
|
|
|
+| 项 | 内容 |
|
|
|
+|----|------|
|
|
|
+| 目标 | 三表回读到本库业务表,S5 三个 IQC 页面出真实数据 |
|
|
|
+| 前置 | **无(P0 已完成)**。`FINSPECTSTATUS` 的完整流转取值已取证:`未检验` →(认领后仍为 `未检验`)→ `检验中`(「来料检验」按钮写)→ `检验完成`(写回过程自动写,或「检验完成」按钮手工兜底),见 §2.5 |
|
|
|
+| 改动 | 见 §5.3;映射见 §5.4 |
|
|
|
+| 验收 | 见 §5.5 |
|
|
|
+| 不做 | 不启用冷链实体;不改贴源层;不动 L0/L1 过账骨架 |
|
|
|
+
|
|
|
+### P2 · 本库检验单生成(Q2=B-2a,确定要做)
|
|
|
+
|
|
|
+| 项 | 内容 |
|
|
|
+|----|------|
|
|
|
+| 目标 | 报检单 → 检验单的派工动作落在 Ai-DOP |
|
|
|
+| 前置 | P1(P0 第 ⑸ 项已由静态取证关闭) |
|
|
|
+| 改动 | ① **任务列表**(不是申请列表,见 §2.5.2)增「认领」「检验员调配」「优先级调整」「检验完成」四个操作 + 「来料检验」(生成检验单)入口,逐条对照 §2.5.3 / §2.5.4 复刻,含「非认领人不得操作」与「已有检验单不得重复生成」两道校验;② 单号按 **Q7**:本库自建序列,格式 `IQC`+`yyyyMMdd`+4 位,**不碰 165 任何计数器**;③ 写本库 `qms_qcp_inspbill`(`lydjbh=报检单FBILLNO`、`hid=报检分录id`、`FMATERIALCFG`、`FRINSQTY`、`jyr`、`FINSPESTARTDATE` 等) |
|
|
|
+| 验收 | 认领后 `jyfzr` 写入;非认领人点「来料检验」被拒;重复生成检验单被拒;生成后任务列表出现该检验单且流程抽屉可提交结果;单号形如 `IQC202608110001`,当日连号 |
|
|
|
+| 不做 | 不改 165 的 `rf_serialnumber` 与 `NbrControl`;不复刻低代码工作流引擎本身(Ai-DOP 用现有 `IqcInspBillFlowService` 审批流即可) |
|
|
|
+| 风险 | `jyfzr` 口径:旧系统混用「用户 id」与「用户名」(§2.5.3 注),本包**统一存用户 id**,需在 UI 显示时解析为姓名;序号 4 位单日上限 9999,超限行为需定义(旧系统未定义) |
|
|
|
+
|
|
|
+### P3 · 合格回推 165(Q3=B-3a,确定要做)
|
|
|
+
|
|
|
+| 项 | 内容 |
|
|
|
+|----|------|
|
|
|
+| 目标 | Ai-DOP 判合格后,165 侧箱码解除待检,可被 WMS APP 上架 |
|
|
|
+| 前置 | P1、P2(P0 已完成)。**另有一项硬前置**:165 侧 `MissedPrint.PurLine` 必须与 `PurOrdDetail.Line` 一致——`pr_WMS_SaveIQCResult` 用 `t.PurOrd=m.PurOrd AND t.PurLine=m.PurLine` 关联箱码,`PurLine` 错则箱码集为空、报「标签总数与收货数不一致」。该 bug 已于本轮修复(§11.4 坑 5),**推送前须抽查真实值不为 0** |
|
|
|
+| 挂点 | **检验员提交结果时(Q5)**:把 `TryEnqueueIqcPassOutboxAsync` 的调用从 `supervisor-approve` **移到** `submit-result`,不是新增调用点 |
|
|
|
+| 改动 | ① 改造 `TryEnqueueIqcPassOutboxAsync`:`target_source_code` 由 `QMS_API` 改为 `DOPDEMORQ_SQLSERVER`,payload 按 §6.3 矩阵多表字段级下发,箱码状态按 §6.3 赋值规则计算(保留 `QMS_API` 分支代码);② `MdpDbPushExecutor.AllowedTables` 增 `MissedPrintTransHist`、`qms_qcp_insappnentry`、`qms_qcp_inspbill`(**不增 `PurOrdRctDetail` 写列,不新增 `op=PROC` 能力**);③ 入队前按 §2.3 的入口校验清单自检(数量平衡、`QCNbr` 未占用、箱码总数一致),不通过则不入队并给出业务提示;④ **修订 `00-总体方案.md` §4.3 + 附录A + WP5 场景 2 第 5 步**(§4 强制配套动作,本卡交付物之一) |
|
|
|
+| 验收 | 提交合格后:165 `MissedPrint.Status` 由 `I`→`N`、`QtyAccN=500`、`QtyRejectN=0`;`MissedPrintTransHist` 新增一行 `TransType='来料检验-通过'`;`qms_qcp_insappnentry.FINSPECTSTATUS='检验完成'` 且 `jywcsj` 有值;`InvStatus`/`PurOrd`/`OrdNbr`/`Remark` **保持原值不变**;随后 WMS APP「上架移库」可正常扫码上架;重复触发不产生重复写(`idem_key` 生效) |
|
|
|
+| 不做 | 不写 `PurOrdRctDetail` 任何列(§6.3 禁止写,旧系统本路径下也不写);不写库存与过账表;执行器不新增调存储过程能力;不因主管退回而回滚 165 |
|
|
|
+| 风险 | 双写覆盖。回写须带乐观校验(如 `WHERE IsNull(Status,'')='I'`)确认箱码未被 WMS 先改过;**注意不能再用 `StatusByQC` 做判据**(该列本路径下恒为空) |
|
|
|
+
|
|
|
+### P4 · 端到端验收
|
|
|
+
|
|
|
+| 项 | 内容 |
|
|
|
+|----|------|
|
|
|
+| 场景 | **全程只用 Ai-DOP + WMS APP,不涉及旧 DOP**:① 用现存活样本 `RQLLJYSQ202608110001`(500 件,仍为 `未检验`)直接从第 4 步接入;② 或新开一箱(`...0010002`)从生成标签走全程 |
|
|
|
+| 步骤 | Ai-DOP 认领 → 生成检验单 → 提交结果(判合格)→ 查 165 箱码解禁 → WMS APP 上架移库 → Ai-DOP 看到合格入库数 |
|
|
|
+| 核对 | 逐步对照 §6.1 状态机的 7 行;**第 5 步后合格入库数应仍为 0**(正常,非缺陷),第 7 步才出数 |
|
|
|
+| 时延 | 各段回读 P95 ≤ 10 秒(沿用 WP8 口径) |
|
|
|
+| 留证 | `doc/plan/UAT留证/` 步骤记录 + 关键表前后值(基线见 §7.5) |
|
|
|
+| 通过判据 | 最终判据是**「WMS APP 上架移库能扫成功」**——旧 DOP 已停运,无法用「与旧系统行为一致」做判据,改用下游可继续作业作为等价验收 |
|
|
|
+
|
|
|
+### 7.5 基线快照(2026-08-11 已采集,`LotSerial=260811013`)
|
|
|
+
|
|
|
+> 原为 P0 的 diff 基线。P0 改静态取证后,**本快照的用途转为 P3/P4 的验收对照基线**——样本未被消耗,下表各值至今仍然有效,P3 判定后逐列比对即可。
|
|
|
+
|
|
|
+| 对象 | 检验前值(= 当前值) |
|
|
|
+|------|---------|
|
|
|
+| `PurOrdDetail` | `RctQty=500`、`ReceiptQty=0`、`QtyReturned=0` |
|
|
|
+| `PurOrdRctDetail` | `QCNbr=''`、`StatusByQC=''`、`QcQty=0`、`Location=1000` |
|
|
|
+| `MissedPrint` | `Status='I'`、`RctNbr=RC202608110001`、`Location=1000`、`Shelf=01-01-01` |
|
|
|
+| `LocationDetail` | 1 行(`RecID=56676`)、`Location=1000`、`QtyOnHand=500` |
|
|
|
+| `InvTransHist` | 1 行(`rct-po-ins` 采购收货) |
|
|
|
+| `MissedPrintTransHist` | 1 行 |
|
|
|
+| `WMSInvTransHist` / `MissedPrintTraceRecord` / `PurOrdRctHist` / `PurOrdDetailBatch` | 0 行 |
|
|
|
+| 报检分录 | `FINSPECTSTATUS='未检验'`、`FJOINQTY=NULL` |
|
|
|
+| 检验单 / 检验结果 | 0 行 / 0 行 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 8. 决策清单
|
|
|
+
|
|
|
+| 编号 | 决策 | 影响 | 状态 |
|
|
|
+|------|------|------|------|
|
|
|
+| **Q1** | 方案 A(165 权威、只读)还是方案 B(Ai-DOP 判定 + 回写) | 决定 P2/P3 是否开工;选 B 须修订 `00-总体方案` §4.3 | ✅ **已定 = B**(2026-08-11) |
|
|
|
+| **Q2** | 检验单由 Ai-DOP 自建(B-2a)还是回读 165(B-2b) | 决定 P2 是否存在 | ✅ **已定 = B-2a**(2026-08-11) |
|
|
|
+| **Q3** | 回推走 DB 字段级 UPDATE(B-3a)还是调 165 存储过程(B-3b) | 决定执行器是否新增调 SP 能力 | ✅ **已定 = B-3a**(2026-08-11);**已被** [`S5-IQC合格上架过账(QcCheck)`](./S5-IQC合格上架过账(QcCheck)执行任务书.md) **Q9 部分推翻**:甲路径上架过账须受限 `op=PROC`,其余字段级回写仍守 B-3a |
|
|
|
+| **Q4** | 是否同期把「上架」也搬进 Ai-DOP | 需先补 `EmpWorkDutyMaster` up-shelf 数据 | ✅ **已定 = 本次不做**(2026-08-11);**已被 QcCheck 任务书 Q8 推翻**:APP「上架移库」对质检库存不可用,改由 Ai-DOP 调 `SaveInvUpShelf @TransCode=QcCheck` |
|
|
|
+| **Q5** | 回写 165 的时机:检验员提交即推,还是主管审核通过才推 | 决定 P3 挂点;影响待检库放行时效 | ✅ **已定 = 提交即推**(2026-08-11),与旧系统 `submitAfter` 一致,见 §6.1 |
|
|
|
+| **Q6** | 任务列表的认领/调配/优先级/检验完成是否一并复刻 | 决定 P2 范围;「认领」是「来料检验」按钮的前置硬校验 | ✅ **已定 = 全部复刻**(2026-08-11),规格见 §2.5.3 |
|
|
|
+| **Q7** | 检验单号走 165 计数器还是本库自建序列 | 决定 P2 取号实现;原定「与 WMS APP 共用 `NbrControl`」依据错误 | ✅ **已定 = 本库自建,复刻 `IQC`+`yyyyMMdd`+4 位**(2026-08-11),见 §6.2 |
|
|
|
+
|
|
|
+> 决策已闭合,**P0 已完成(静态取证)**;P1–P3 可直接按卡片派发,无需等待任何前置取证。
|
|
|
+
|
|
|
+## 9. 未解问题
|
|
|
+
|
|
|
+| # | 问题 | 处置 |
|
|
|
+|---|------|------|
|
|
|
+| ~~U1~~ | ~~`pr_WMS_SaveIQCResultByRctNbr` 已加密,实际写入未知~~ | ✅ **已关闭**:该过程属乙路径(APP「检验确认/检验后部分收货」自用),本方案既不调用也不复刻,其语义不影响交付(§2.3) |
|
|
|
+| ~~U2~~ | ~~`RctType='rc'` 与 `temp` 的口径冲突根因~~ | ✅ **已关闭**:门控实为 `PurOrdDetail.ReceiptQty`(非 `RctType`),本例 `=0` 故走甲路径;`IsPurOrdRctReconfirmQty` 未配置(§2.3、§6.3) |
|
|
|
+| U3 | 报检单 `FQUALITYORG`/`FINSPEDEPTID` 为空,任务列表是否需按组织过滤 | 旧 DOP 已停运无法实操验证;查 `rf_program.sqlstring`(program `396052925665349`)可静态确认其 WHERE 是否含组织条件 —— **P2 开工时顺带查,不阻断** |
|
|
|
+| U4 | `mdp_entity.S5_IQC_INSPBILL_SQLSERVER.biz_key_expr='Domain,BillNbr'` 与表结构不符 | 既有缺陷,P1 顺手修正,勿启用后再改 |
|
|
|
+| U5 | `1000/01-01-01` 不在 `LocationShelfMaster` 中,但箱码与报检分录都记着该货架 | 待检货架是否需登记,属基础数据议题,记录不阻断 |
|
|
|
+| U6 | 环境遗留:旧后端实例 `54820` 无法结束,仍在抢 `mdp_outbox` | 联调期需注意消息被旧实例消费;已知项 |
|
|
|
+| **U7** | Q5「提交即推」下,主管退回时 165 侧箱码已解禁,本方案不回滚 | 旧系统同样不回滚(`submitAfter` 一旦执行无补偿动作)。纠正走「不合格处理」重新判定。**若业务不接受,需另设计补偿动作**,属范围外 |
|
|
|
+| **U8** | 165 存在配置项 `SystemConfig / IsPurOrdRctTemp`(「WMS是否走采购暂收」,`IsActive=1`) | **风险提示**:若有人启用采购暂收,`PurOrdDetail.ReceiptQty` 将 >0 → `@IsRctTemp=1` → IQC 的写入面剧变(触发收货过账与上架任务),本方案的字段级复刻会与之偏离。**变更该配置前必须重新评估 P3**,建议纳入 165 配置变更评审清单 |
|
|
|
+| **U9** | 旧系统 `jyfzr` 混用「用户 id」(认领按钮)与「用户名」(校验比较)两种口径 | 本包统一存用户 id(P2 卡片已定)。此偏离为**有意为之**,非缺陷 |
|
|
|
+
|
|
|
+## 10. 红线自检(交付前逐条确认,与 §11.8 合并执行)
|
|
|
+
|
|
|
+- [ ] 165 上无新增表、触发器、作业,未改任何存储过程与表结构
|
|
|
+- [ ] 回写全部为字段级 UPDATE / 明确的行 INSERT,无整行覆盖
|
|
|
+- [ ] 新系统未新增存储过程
|
|
|
+- [ ] 未新增「业务页面直读 165」旁路
|
|
|
+- [ ] 热链查询走业务键 `IN` 窄查询,未对 `MissedPrint`/`InvTransHist` 做时间戳全表扫
|
|
|
+- [ ] `00-总体方案.md` §4.3 与 `00B` §7.4 的例外修订已完成(Q1=B 的强制配套,见 §11.2)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 11. 执行者输入契约(派发时必须一并交付)
|
|
|
+
|
|
|
+> 本节是为「把某张卡片直接交给另一个人或模型执行」准备的。缺本节,执行者会按 `00B` 的原文自检并判定本方案违规。
|
|
|
+
|
|
|
+### 11.1 交付包清单
|
|
|
+
|
|
|
+派发任一卡片时,必须同时给出:
|
|
|
+
|
|
|
+1. 本文档(全文,不可只给单张卡片)
|
|
|
+2. [`00B-执行须知(模型输入契约)`](./旧DOP-MES-WMS/对接任务书/00B-执行须知(模型输入契约).md)——连接、代码落点、产出规范、自检、禁止事项
|
|
|
+3. [`00-总体方案.md`](./旧DOP-MES-WMS/对接任务书/00-总体方案.md) §1 硬约束、§4.2 时效模型、§4.3 数据归属
|
|
|
+4. ~~P0 的变动列清单~~ → **已不需要**:P0 改为静态取证并已完成,其产出就在本文档内(§2.2 判定矩阵、§2.3 写入门控、§2.5 行为规格、§6.3 赋值规则)。**P1/P2/P3 无外部前置,可直接开工。**
|
|
|
+
|
|
|
+**执行者硬性须知(本包最容易踩的前提错误)**:
|
|
|
+
|
|
|
+- **旧 DOP 已停运,仅作移植参考。任务描述、验收步骤、复现指引中都不得出现「在旧 DOP 上操作」**。若发现某项验证只能靠旧 DOP 完成,说明该项设计有问题,应改为静态取证或改用「WMS APP 能否继续作业」作为等价判据。
|
|
|
+- **旧 DOP 的 IQC 不在旧仓 C# 源码里**(`ZZYDOP_OLD` 全仓零命中),去那里找实现是白费功夫;实现在 165 的低代码元数据中,定位路径见 §2.5.1。
|
|
|
+- 本文档内凡标注「已定论 / 已关闭 / 已作废」的条款,**其被推翻的原文一并保留**(含删除线或「原文写…依据错误」字样),目的是防止执行者从旧版本或摘要中拿到已作废的结论。**遇到冲突一律以标注日期最新的条款为准。**
|
|
|
+
|
|
|
+### 11.2 本包对既有禁令的例外(Q1=B 的必然结果)
|
|
|
+
|
|
|
+| 禁令 | 本包处置 |
|
|
|
+|------|---------|
|
|
|
+| `00B` §7.4「**不新增旧 DOP 没有的自动回写闭环**」 | **开例外**:Ai-DOP 判定合格 → 自动回写 165 属本包核心。例外范围**严格限于 §6.3 白名单列**,其余场景该禁令继续生效。修订 `00B` §7.4 加注例外指向本文档,属 P3 交付物 |
|
|
|
+| `00-总体方案` §4.3「执行域不回写执行字段」 | **开例外**:仅「检验」一项,范围同上(见 §4) |
|
|
|
+| `00B` §7.1「不改 165 任何结构」 | **不开例外**。含 `NbrControl` 既有行也不改——P2 只按现有 `IQC` 段取号,不改配置 |
|
|
|
+| `00B` §7.2「不在新系统里调用或新建存储过程」 | **不开例外**。Q3 定 B-3a 而非 B-3b 正是为守这一条;2026-08-11 复核确认可守——`pr_WMS_SaveIQCResult` 在本例路径下净写入面只有两处,字段级足够覆盖(§6.4)。**执行器不得新增 `op=PROC` 之类的存储过程调用能力** |
|
|
|
+| `00B` §7.3「回写不得整行覆盖」 | **不开例外**。§6.3 全部为字段级 UPDATE 或明确的行 INSERT,且必须带业务键 |
|
|
|
+| `00B` §7.5「不开业务页面直读 165 旁路」 | **不违反**。P1 是「165 → 回读 → 本库业务表 → 页面读本库」,页面不直连 165。S5 三个 IQC 页面读本库业务表 `qms_qcp_*` 是既有实现,本包不改其数据源方向 |
|
|
|
+
|
|
|
+### 11.3 实现范本锚点(照抄现成实现,勿另发明)
|
|
|
+
|
|
|
+本轮(后端 1.0.330 / 1.0.331)已落地的采购单推送与收货回读,是本包每一段的现成范本:
|
|
|
+
|
|
|
+| 卡片 / 环节 | 范本位置 | 说明 |
|
|
|
+|-----------|---------|------|
|
|
|
+| P1 热关注登记 | `DataPlatform/Wms/PurOrdWmsPushService.cs` → `EnqueueBarcodesAsync` 末尾的 `EnrollPurOrderAsync` 调用点 | 登记失败只 `LogWarning`、不阻断主流程的写法照抄 |
|
|
|
+| P1 关注表清单 | `DataPlatform/HotWatch/MdpHotWatchService.cs` → `EnrollPurOrderAsync` | 现为 `PurOrdMaster`/`PurOrdDetail`/`MissedPrint`,本包在此追加 qms 三表 |
|
|
|
+| P1 回读落库 | 同上 → `ApplyPurchaseReceiptAsync`、`ApplyShipmentStatusAsync` | **新增的 `ApplyIqcAsync` 照这两个方法的结构写**:参数签名、`tenantId` 解析、按业务键 UPDATE、逐表 try 隔离 |
|
|
|
+| P1 租户解析 | 同上 → `ResolveLocalPurOrdTenantAsync` | 由 `FSRCORDERNUM` → `PurOrdRctDetail` → `PurOrdDetail.tenant_id` 的等价写法 |
|
|
|
+| P1 关注终止 | 同上 → `ShouldTerminateAsync` 的 `PUR_ORDER` 分支 | 追加「报检分录 `FINSPECTSTATUS='检验完成'`」条件,注意保留 `total>0` 的守卫(标签未推达时不得终结) |
|
|
|
+| P2 取号 | `NbrSequenceService`(WP2 产出) | 与 WMS APP 共用 165 计数器,原子分配 |
|
|
|
+| P3 出站入队 | `PurOrdWmsPushService.EnqueueShipmentChainAsync` | `idem_key` 构造、多表分条入队、`MdpOutboxWakeSignal` 唤醒的写法 |
|
|
|
+| P3 执行器 | `DataPlatform/Executors/MdpDbPushExecutor.cs` → `AllowedTables`、`ApplyResolvesAsync`、`ResolveSpec` | 白名单扩表与受限 resolve 子查询解外键(`RecID` 类外键不可直接复制) |
|
|
|
+
|
|
|
+### 11.4 已知坑(本轮实测踩过,必读)
|
|
|
+
|
|
|
+| # | 坑 | 症状 | 处置 |
|
|
|
+|---|----|------|------|
|
|
|
+| 1 | **Ai-DOP 自建单的 `Domain` 为 `NULL`** | 回读 UPDATE 影响 0 行,静默无效 | `WHERE` 一律写 `IFNULL(Domain,'') IN ('', @Domain)`,不要写 `Domain=@Domain` |
|
|
|
+| 2 | **MySQL collation 冲突** | 更新 `scm_shdzb` 时报 `Illegal mix of collations`(`CAST(d.id AS CHAR)=z.glid` 这类 JOIN) | 先单独查出父表 `id`,再作为字符串参数分开执行 UPDATE,避免跨表 JOIN 比较 |
|
|
|
+| 3 | **165 侧 `id`/`RecID` 是 IDENTITY** | `不能为表 'xxx' 中的标识列插入显式值` | 推送一律用自然键(如 `scm_shdshph` 用 `(shdh, xh)`);`qms_qcp_*` 的 `id` 同理,映射见 §5.4 |
|
|
|
+| 4 | **Ai-DOP 的 `MissedPrint` 表没有 `tenant_id` 列** | `Unknown column 'tenant_id' in 'where clause'`,HTTP 500 | 查该表不要加租户过滤;租户从关联的采购单侧解析 |
|
|
|
+| 5 | **SQL 别名与 C# DTO 属性名不一致会静默取到默认值** | `MissedPrint.PurLine` 被写成 0 而不报错(`po_billline` vs `PoBillLine`) | 用 SqlSugar 手写 SQL 映射 DTO 时,别名必须与属性名逐字对应;落库后必须抽查关键列真实值 |
|
|
|
+| 6 | **改 `csproj` 要保持 UTF-8** | 曾因双重编码(UTF-8 被当 GBK 再存)损坏 `<Description>` 标签,导致 MSBuild XML 解析失败、`dotnet run` 起不来 | 只用编辑工具改,不用 shell 重定向;改完立刻 `dotnet build` 验证 |
|
|
|
+| 7 | **环境遗留:旧后端实例 `54820` 仍在抢 `mdp_outbox`** | 出站消息被无法访问的旧进程消费,回写看似丢失 | 联调期确认只有一个后端实例在跑;必要时用 `force-repoll` 手动触发 |
|
|
|
+| 8 | **门控归因陷阱:`@IsRctTemp` 取自 `PurOrdDetail.ReceiptQty`,不是 `PurOrdRctDetail.RctType`** | 按 `RctType` 推断写入面,得出「需补写 `PurOrdRctDetail.QCNbr`」等错误结论(本任务书 §2.3 原文即如此,已更正) | 判断 165 存储过程分支是否命中时,务必读变量赋值原文,不要按变量名或注释推断。`MRBStatus` 的注释同样与实际调用不一致(§2.2 注) |
|
|
|
+| 9 | **`MissedPrint.PurLine` 是 IQC 回写的隐性命门** | `pr_WMS_SaveIQCResult` 用 `PurOrd+PurLine` 关联箱码;`PurLine=0` 时箱码集为空 → 报「标签总数与收货数不一致」,但错误信息完全不指向 `PurLine` | 坑 5 的下游影响。推送标签后必须抽查 165 侧 `MissedPrint.PurLine` 真实值与 `PurOrdDetail.Line` 一致 |
|
|
|
+| 10 | **MCP SQL 通道会拦截含写操作关键字的语句** | 即使是 `SELECT CHARINDEX('upd'+'ate ...')` 这类只读查询,只要出现 `update` 字面量就被拒(`SQL contains denied statement`) | 取证时用字符串拼接绕开(`'upd'+'ate'`)。另:`sys.dm_sql_referenced_entities` 可快速拿到某过程的读写对象清单,比逐段读定义高效 |
|
|
|
+
|
|
|
+### 11.5 DDL 判定(各卡片是否需要 UpdateScript)
|
|
|
+
|
|
|
+| 卡片 | 是否需要 `UpdateScripts/<版本号>.sql` |
|
|
|
+|------|-----------------------------------|
|
|
|
+| P1 | **不需要**。本库 `qms_qcp_inspecapplyn`/`insappnentry`/`inspbill` 三表已存在且与 165 列同构(本库多 `tenant_id`);`ado_mdp_hot_watch` 已存在 |
|
|
|
+| P2 | **不需要建表**(写既有 `qms_qcp_inspbill`)。若最终决定另建派工记录表,才写新脚本 |
|
|
|
+| P3 | **不需要**。`mdp_outbox` 已存在;执行器白名单是代码常量,非 DDL |
|
|
|
+| 通用 | 若确需新脚本:取**大于 `1.0.331`** 的版本号,必须可重复执行(`IF NOT EXISTS` 保护),不得复用或修改已有脚本。当前最新已编号脚本为 `1.0.328.sql`;目录下另有未编号的 `WIP-S5-IQC-INVPOSTING.sql`(L0/L1 过账骨架用),**勿复用、勿修改** |
|
|
|
+
|
|
|
+### 11.6 版本号现值(`00B` §5.2 的记载已过期)
|
|
|
+
|
|
|
+| 项 | `00B` §5.2 记载 | **实际现值(HEAD `b58e50dfd`)** |
|
|
|
+|----|----------------|------------------------------|
|
|
|
+| 后端 `Admin.NET.Web.Entry.csproj` | `1.0.299` | **`1.0.331`**(三处 `<Version>`/`<AssemblyVersion>`/`<FileVersion>` 同号) |
|
|
|
+| 前端 `Web/package.json` | `2.4.275` | **`2.4.283`** |
|
|
|
+
|
|
|
+升号规则不变:只跟随本次提交实际纳入的对应端改动 patch +1;纯文档提交不升号。
|
|
|
+
|
|
|
+### 11.7 造数与打标
|
|
|
+
|
|
|
+本例活样本锚点(P0/P4 验证都用它):
|
|
|
+
|
|
|
+| 项 | 值 |
|
|
|
+|----|----|
|
|
|
+| 采购单 | `PO202608110003` 行 1,物料 `81HC0744`,供应商 `10003232` |
|
|
|
+| 送货单 | `10003232-20260811-0001` |
|
|
|
+| 收货单 | `RC202608110001` |
|
|
|
+| 批号 | `260811013` |
|
|
|
+| 已收箱码 | `10003232-20260811-00010010001`(500,`Status='I'`) |
|
|
|
+| 未收箱码 | `...0010002`(500)、`...0010003`(67) |
|
|
|
+| 报检单 | `RQLLJYSQ202608110001` |
|
|
|
+
|
|
|
+新造数据一律带可识别前缀(沿用 `2608` 批号段),并在 [`doc/plan/UAT留证/`](./UAT留证/) 留步骤记录。**不得在 165 上留无法识别的测试数据。**
|
|
|
+
|
|
|
+### 11.8 自检清单(`00B` §6 + 本包附加)
|
|
|
+
|
|
|
+提交前逐条过:
|
|
|
+
|
|
|
+- [ ] 后端可编译:`dotnet build server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj`
|
|
|
+- [ ] 全仓无存储过程调用:`rg -n "EXEC\s+pr_|CALL\s+pr_" server/ Web/` 应无命中(守 `00B` §7.2 / Q3=B-3a)
|
|
|
+- [ ] 无明文密码入库:`rg -n "dopsa" server/ --glob "!*.md"` 人工确认
|
|
|
+- [ ] UpdateScript 版本号未与现有冲突(见 §11.5)
|
|
|
+- [ ] 版本号已按实际提交端 patch +1(见 §11.6)
|
|
|
+- [ ] 回写语句全部带业务键,`keys` 为空时抛异常,无整行覆盖
|
|
|
+- [ ] 回写带乐观校验(箱码用 `WHERE IsNull(Status,'')='I'`;**不得用 `StatusByQC`**,该列在本路径下恒空,见 P3 卡片风险栏)
|
|
|
+- [ ] 重复触发同一合格闭环不产生重复写(`idem_key` 生效,实测一次重放)
|
|
|
+- [ ] §11.4 的 **10** 个坑逐条确认未复现
|
|
|
+- [ ] 交付物中**没有任何要求在旧 DOP 上操作**的步骤(§11.1 硬性须知)
|
|
|
+- [ ] 未写 `PurOrdRctDetail` 任何列、未写 `PurOrdDetail.ReceiptQty`(§6.3 禁止写清单)
|
|
|
+- [ ] 未新增执行器的存储过程调用能力(§11.2 / §6.4)
|
|
|
+- [ ] 检验单号形如 `IQC202608110001`,且未读写 165 的 `rf_serialnumber` / `NbrControl`(Q7)
|
|
|
+- [ ] 推送前已抽查 165 侧 `MissedPrint.PurLine` 与 `PurOrdDetail.Line` 一致(§11.4 坑 9)
|
|
|
+- [ ] §10 红线自检全部通过
|
|
|
+- [ ] 本卡片「验收」栏逐条自评并留证
|