fix: MKH-BOM 预警与库存口径修复:日销以出库为准 + 库存增加箱表兜底 #19

Merged
yanhui merged 3 commits from codex/mkh-sale-exclude-special-order-0919 into main 2026-09-22 23:14:37 +08:00
Collaborator

背景

业务口径:标了「已发货」但没有实际出库的 SKU,不需要出现缺货预警。

日销兜底(⑦-a)此前只看订单表的 send_date 是否为空来判定「已发货」,但订单表的「已发货」不等于真实销售。本 PR 把判据下沉到出库表。

口径

已发货分支计入销量需同时满足(待发货分支不变):

# 判据 来源
1 非样品/测试店铺 ⑦-a
2 非异常单:pay_amount <= 0.01 或 labels 含「特殊单」 ⑦-a
3 出库表能查到该 o_id ⑦-c(本 PR 新增)

出库表两张都要查:rpt.jst_salesOutboundInfo(京东/拼多多/抖音)+ rpt.jst_qmcust_salesoutbound(天猫/淘宝/1688)。实测 jst_qmcust_orderlist 来源的 208 张单里,7 张只有京东表有记录、1 张只有天猫表有记录。

🔥 两个必须豁免的情形(不豁免会大面积误杀真实单)

  • 拆分单 status='Split':父单被拆后出库记录挂在子单上,父单必然查不到。实测窗口内 40 张 Split 单 100% 无出库记录;不豁免会误杀「京东-HX旗舰店104 09-19 实付 1194.14 元」这类真实单。
  • 今天发货的单:当天出库尚未同步完(09-19 全天出库表仅 1 行,09-18 有 14,690 行)。09-19 的 7 张 Sent 无出库单里特殊单数为 0 → 属同步滞后而非虚假发货。
    ⚠️ 豁免只到「昨天」为止:09-18 的 18 张无出库 Sent 单 100% 是特殊单,且出库表 09-18 数据完整,豁免 09-18 会放过刷单。

豁免只免「出库校验」,金额/标签判据照样生效(拆分单里的 0.01 元单仍会被排除)。

真实数据验收(近 15 天窗口)

窗口内 1,086 张已发货 MKH 单:出库表命中 889 张 → 197 张无出库 = 40 张拆分单 + 157 张 Sent。
157 张里 131 张是 ≤0.01 元或「特殊单」(即上一版已排除的刷单),唯一 1 张正常金额的是 09-05 的 388 元单(标签「发货后取消 / 发货后售后 / 风控订单」)——判据 3 正是为它这类单兜底。

检查 结果
兜底生效 SKU 数 12(与只按金额/标签判据一致)
兜底日销最大值 0.2000
10 个异常单 SKU 是否残留 0 个 ✅
预警列表 总 27 条(缺货 21 + 仅预警 6),异常 SKU 零出现 ✅
MKHDNZ0409260601175(拆分单驱动,真实单 1194.14 元) 正确保留 ✅

性能与降级

  • 1086 个订单号分 2 块并行 UNION 查询:冷缓存约 0.4~1.4s。
    (整窗口拉全量 DISTINCT o_id 实测要 8s,故不采用。)
  • 出库数据取不到时整体跳过出库校验(退化成只按金额/标签判据)并标 degraded:漏杀只是多报一条预警,错杀会让真实缺货彻底隐身。

附带修复(⑦-d,commit 5e029f8):库存源时效兜底

现象:MKHDNZ0409260700491 当天已入库、实物有货,但页面「现有库存」一直是 0,点「同步最新数据」也不变。

根因:页面唯一的库存源 rpt.jst_inventoryInfo 是「有变化才写 updateTime」的同步表。该 SKU 在它里面只有主仓 14138566 的一行 qty=0,品牌仓连行都没有;而小时级的箱表 rpt.jst_packInventoryInfo 里,品牌仓 bin='P-1' / qty=4 / createTime 当天 15:19 —— 当日入库的���只有箱表看得见。

(「同步最新数据」按钮只清服务端缓存 clearMkhBomCache,不触发上游同步,所以点多少次都是 0。)

改法(只补 0,不覆盖有效值):

# 条件 采用值 stockSource
① inventoryInfo 品牌仓 qty > 0 该值 realtime(旧口径不变)
② 否则箱表品牌仓 bin 非空合计 > 0 该值 pack(新增)
③ 否则 inventoryInfo 有品牌仓行(qty≤0) 0 realtime
④ 否则回退监控快照 jsStock 快照值 monitor
  • 为什么不拿箱表当主源:两表都 > 0 且不等的 28 个成品里 25 个是 inventoryInfo 更大(箱表只记已装箱的货,散货不计),拿它当主源会系统性低估库存、误报缺货。
  • 必须过滤 bin 为空的行:那是装箱记录不是库存(品牌仓合计 21 件)。

影响面(实测):品牌仓 4,408 个成品里只有 3 个走箱表兜底(MKHDNZ0409260601548=5 / MKHDNZ0409260700059=4 / MKHDNZ0409260700491=4,同一箱 115870495000001201、货位 P-1、均为当天新建行);预警列表 27 → 24 条(缺货 21 → 18),少的正是这 3 条「入库当天被误判缺货」。

前端:「现有库存」列 tooltip 增加 pack 来源说明;导出列直接用 stock,自动跟随。
性能:箱表全量取数 87ms。

⚠️ 遗留(需上游跟进):inventoryInfo 当天 06:41 之后就再没更新过(09-17 / 09-18 都是全天逐小时更新),疑似上游同步任务异常。箱表兜底只解决「当日入库看不见」,存量库存仍依赖该表恢复。


验证

  • scripts/check-syntax.js:151 文件通过
  • node --test test/*.test.js:340/341(唯一失败为既有的 resilientSecretStore Windows 权限断言,与本改动无关)
  • test/mkhBomService.test.js:47/47,覆盖「无出库排除」「拆分单豁免」「今天发货豁免」「拆分单仍过金额判据」「出库源失败不砍销量」五个场景,以及 ⑦-d 的箱表兜底五分支
  • 前端 eslint src/pages/MkhBomData/index.jsx:通过
  • HTTP 实测:该 SKU stock=4 / stockSource=pack,点「同步最新数据」后仍正确

⚠️ 给后续维护者

  • rpt.jst_salesOutboundInfo.qty 全表 1,119,606 行恒为 0(MAX(qty)=0),判断「有没有真出库」只能看行数,不能用 SUM(qty)(永远得 0)。
  • 库存对不上时,先比两表的 MAX(updateTime):inventoryInfo 与 packInventoryInfo 哪个停更,一眼可辨是上游停了还是本页取数问题。

## 背景 业务口径:**标了「已发货」但没有实际出库的 SKU,不需要出现缺货预警。** 日销兜底(⑦-a)此前只看订单表的 `send_date` 是否为空来判定「已发货」,但订单表的「已发货」不等于真实销售。本 PR 把判据下沉到**出库表**。 ## 口径 已发货分支计入销量需**同时**满足(待发货分支不变): | # | 判据 | 来源 | |---|---|---| | 1 | 非样品/测试店铺 | ⑦-a | | 2 | 非异常单:`pay_amount <= 0.01` 或 `labels` 含「特殊单」 | ⑦-a | | 3 | **出库表能查到该 `o_id`** | ⑦-c(本 PR 新增) | 出库表两张都要查:`rpt.jst_salesOutboundInfo`(京东/拼多多/抖音)+ `rpt.jst_qmcust_salesoutbound`(天猫/淘宝/1688)。实测 `jst_qmcust_orderlist` 来源的 208 张单里,7 张只有京东表有记录、1 张只有天猫表有记录。 ### 🔥 两个必须豁免的情形(不豁免会大面积误杀真实单) - **拆分单 `status='Split'`**:父单被拆后出库记录挂在**子单**上,父单必然查不到。实测窗口内 40 张 Split 单 **100% 无出库记录**;不豁免会误杀「京东-HX旗舰店104 09-19 实付 **1194.14 元**」这类真实单。 - **今天发货的单**:当天出库尚未同步完(09-19 全天出库表仅 **1 行**,09-18 有 **14,690 行**)。09-19 的 7 张 Sent 无出库单里特殊单数为 0 → 属同步滞后而非虚假发货。 ⚠️ 豁免只到「**昨天**」为止:09-18 的 18 张无出库 Sent 单 100% 是特殊单,且出库表 09-18 数据完整,豁免 09-18 会放过刷单。 豁免**只免「出库校验」**,金额/标签判据照样生效(拆分单里的 0.01 元单仍会被排除)。 ## 真实数据验收(近 15 天窗口) 窗口内 1,086 张已发货 MKH 单:出库表命中 **889** 张 → 197 张无出库 = **40 张拆分单 + 157 张 Sent**。 157 张里 **131 张是 ≤0.01 元或「特殊单」**(即上一版已排除的刷单),唯一 1 张正常金额的是 09-05 的 388 元单(标签「发货后取消 / 发货后售后 / 风控订单」)——判据 3 正是为它这类单兜底。 | 检查 | 结果 | |---|---| | 兜底生效 SKU 数 | **12**(与只按金额/标签判据一致)| | 兜底日销最大值 | **0.2000** | | 10 个异常单 SKU 是否残留 | **0 个** ✅ | | 预警列表 | 总 27 条(缺货 21 + 仅预警 6),异常 SKU **零出现** ✅ | | `MKHDNZ0409260601175`(拆分单驱动,真实单 1194.14 元)| **正确保留** ✅ | ## 性能与降级 - 1086 个订单号分 2 块并行 UNION 查询:冷缓存约 **0.4~1.4s**。 (整窗口拉全量 `DISTINCT o_id` 实测要 **8s**,故不采用。) - 出库数据取不到时**整体跳过出库校验**(退化成只按金额/标签判据)并标 `degraded`:漏杀只是多报一条预警,错杀会让真实缺货彻底隐身。 --- ## 附带修复(⑦-d,commit `5e029f8`):库存源时效兜底 **现象**:`MKHDNZ0409260700491` 当天已入库、实物有货,但页面「现有库存」一直是 **0**,点「同步最新数据」也不变。 **根因**:页面唯一的库存源 `rpt.jst_inventoryInfo` 是「有变化才写 updateTime」的同步表。该 SKU 在它里面**只有主仓 `14138566` 的一行 `qty=0`,品牌仓连行都没有**;而小时级的箱表 `rpt.jst_packInventoryInfo` 里,品牌仓 `bin='P-1' / qty=4 / createTime 当天 15:19` —— **当日入库的���只有箱表看得见**。 (「同步最新数据」按钮只清服务端缓存 `clearMkhBomCache`,**不触发上游同步**,所以点多少次都是 0。) **改法(只补 0,不覆盖有效值)**: | # | 条件 | 采用值 | `stockSource` | |---|---|---|---| | ① | `inventoryInfo` 品牌仓 `qty > 0` | 该值 | `realtime`(旧口径不变)| | ② | 否则箱表品牌仓 `bin` 非空合计 > 0 | 该值 | **`pack`(新增)** | | ③ | 否则 `inventoryInfo` 有品牌仓行(qty≤0)| 0 | `realtime` | | ④ | 否则回退监控快照 `jsStock` | 快照值 | `monitor` | - **为什么不拿箱表当主源**:两表都 > 0 且不等的 28 个成品里 **25 个是 `inventoryInfo` 更大**(箱表只记已装箱的货,散货不计),拿它当主源会系统性低估库存、误报缺货。 - **必须过滤 `bin` 为空的行**:那是装箱记录不是库存(品牌仓合计 21 件)。 **影响面(实测)**:品牌仓 4,408 个成品里只有 **3 个**走箱表兜底(`MKHDNZ0409260601548=5` / `MKHDNZ0409260700059=4` / `MKHDNZ0409260700491=4`,同一箱 `115870495000001201`、货位 `P-1`、均为当天新建行);**预警列表 27 → 24 条(缺货 21 → 18)**,少的正是这 3 条「入库当天被误判缺货」。 **前端**:「现有库存」列 tooltip 增加 `pack` 来源说明;导出列直接用 `stock`,自动跟随。 **性能**:箱表全量取数 **87ms**。 ⚠️ **遗留(需上游跟进)**:`inventoryInfo` 当天 06:41 之后就再没更新过(09-17 / 09-18 都是全天逐小时更新),疑似上游同步任务异常。箱表兜底只解决「当日入库看不见」,**存量库存仍依赖该表恢复**。 --- ## 验证 - `scripts/check-syntax.js`:151 文件通过 - `node --test test/*.test.js`:**340/341**(唯一失败为既有的 `resilientSecretStore` Windows 权限断言,与本改动无关) - `test/mkhBomService.test.js`:**47/47**,覆盖「无出库排除」「拆分单豁免」「今天发货豁免」「拆分单仍过金额判据」「出库源失败不砍销量」五个场景,以及 ⑦-d 的箱表兜底五分支 - 前端 `eslint src/pages/MkhBomData/index.jsx`:通过 - HTTP 实测:该 SKU `stock=4 / stockSource=pack`,点「同步最新数据」后仍正确 ## ⚠️ 给后续维护者 - `rpt.jst_salesOutboundInfo.qty` **全表 1,119,606 行恒为 0**(`MAX(qty)=0`),判断「有没有真出库」**只能看行数**,不能用 `SUM(qty)`(永远得 0)。 - 库存对不上时,先比两表的 `MAX(updateTime)`:`inventoryInfo` 与 `packInventoryInfo` 哪个停更,一眼可辨是上游停了还是本页取数问题。 --- <!-- 本 PR 的三次提交是同一批业务反馈的递进修复: dff2422 排除 0.01 元特殊单 → 0e0fcbb 补齐出库校验 → 5e029f8 库存增加箱表兜底。 -->
yanhui added 1 commit 2026-09-19 14:50:19 +08:00
背景
- 业务反馈 MKHDNZ0410260700005 从没发过货,页面却显示日销 0.6 并报缺货。
- 根因是上一版日销兜底(⑦-a)只看 send_date,把「0.01 元特殊单」当成了真实销售:
  · 拼多多-亿家达书房家具官方旗舰店在 2026-09-18 10:35–10:36 **一分钟内**批量生成
    18 张 MKH 订单,实付合计仅 0.18 元、免额合计 16,344 元(单张:实付 0.01 元、
    商品额 908.01、免额 908 全额免),备注统一为 SSDD,标签含「特殊单」「转x00005」;
    同批还有 18 张商品码为占位码 1.X00005 的 0.01 元订单。
  · 这批单 status=Sent、有物流单号,但 **rpt.jst_salesOutboundInfo 里查不到对应出库记录**
    (同店同时段其他订单有 6,467 行出库)—— 即系统标了「已发货」,货并没有真出。
  · 后果:把 10 个 SKU 的日销从 0 抬到 0.6~1.8(共 162 件),凭空产出缺货预警。

改动
- loadOrderIndex 的**已发货分支**增加异常单过滤:命中任一即不计入日销
  · pay_amount <= 0.01(0.01 元全免单)
  · labels 含「特殊单」
- 查询补充取出 o.pay_amount / o.labels(两张订单表均有这两列,已确认)。
- 金额取不到(null / 空串 / 非数字)时**不排除** —— 宁可少排也别误杀,取不到就退化成旧口径。
- ⚠️ **不使用 free_amount > 0 作为判据**:正常订单普遍带折扣免额(窗口内实测 569/1086 行
  free_amount > 0),按它筛会把实付 356 / 458 / 1338 元的正常单一并误杀(第一版即踩此坑)。
- 本次只作用于「已发货」分支,待发货(⑩)口径未改动。

验证
- 语法检查:151 文件通过。
- 后端测试:338 项 337 通过。唯一失败为 resilientSecretStore 权限断言(期望 0o600、
  Windows 实际 0o666),平台固有,与本改动无关。mkhBomService 单测 44/44。
- 新增回归断言:实付 0.01 / 0 元及字符串金额判为异常单;标签含「特殊单」判为异常单;
  **有免额的正常单不得被排除**;金额缺省/为 null/非数字时不排除。
- 订单索引用例补充:0.01 元单与「特殊单」标签行不进入 shippedBySku,而带折扣免额的
  正常单照常计入(shippedBySku.size 由 1 调整为 2)。
- 真实数据端到端(业务库实时,窗口 15 天):
  · 应排除的 10 个异常单 SKU(MKHDNZ0410260700001/0002/0004/0005/0007/0008/0009/
    0010/0011/0012)**全部已排除**;
  · 应保留的 12 个真实单 SKU **全部保留**(含本问题之外的 MKHDNZ0409260601548 等);
  · 兜底生效 SKU 由 **22 → 12**;兜底日销最大值由 **1.8000 → 0.2000**
    (0.2 来自 MKHDNZ0409260700483 的 3 件真实发货,属正常)。

风险与回退
- 只影响「快照日销 ≤ 0 且近 15 天有发货」的商品;快照 > 0 的商品行为完全不变。
- 实付 ≤ 0.01 元的订单在任何业务口径下都不构成正常销售,排除不会误伤真实需求。
- 回退:本提交可整体 revert,无表结构变更、无数据迁移。
- 遗留(未在本次改动内):待发货(⑩)分支同样可能被 0.01 元单驱动,
  是否一并排除需业务确认;「售后 / 补发」类订单是否计入亦有待定夺。
yanhui reviewed 2026-09-19 17:19:31 +08:00
yanhui left a comment
Author
Collaborator

独立审核结论:通过(附条件)

审核范围:代码逻辑 + 边界处理 + 查询正确性 + 真实数据复算 + 测试基线。未发现阻断性问题,核心结论已用业务库实时数据独立复现。

一、复现结果

指标 PR 描述 独立复算
被排除的 SKU 10 个 10 个,编码完全一致 ✅
兜底日销最大值 1.8 → 0.2 1.8 → 0.2 ✅
亿家达 0.01 元单出库行数 0 行 0 行 ✅
两表列 pay_amount/labels 存在 存在(decimal / nvarchar)✅

测试(在 PR 分支重跑):check-syntax 151 文件通过;node --test 338/337,唯一失败为 resilientSecretStore Windows 权限断言(环境固有);mkhBomService.test.js 44/44。新增断言的反例保护(有免额的正常单不得被排除)质量很好。

判据宽度已做反例检查:(0.01, 1] 区间 19 行全部是 MKH- 组合装(被 isMkhProductCode 先排除,命中索引 0 行),当前窗口内无漏网。

二、建议补充(3 点,非代码缺陷)

1. 描述范围写小了 ⚠️
实测窗口内 ≤0.01 元的已发货 MKH 单共 137 行(104 张 0.01 元 + 33 张 0 元),且每月重复、且都是几秒内批量生成:

店铺 行数 时间跨度
拼多多-HX旗舰店106 60 09-09 11:17:28→32(4 秒)
拼多多-索菲亚客厅家具旗舰店 52 09-14 12:19:09→11(2 秒)
拼多多-亿家达书房家具官方旗舰店 18 09-18 10:36:27→30
其余店铺 7 09-05 ~ 09-15

这是系统性流程产物(标签 AB包/大单自动匹配/刷单待处理,备注 BBFF/SSDD),不是脏数据孤例。建议写进描述,供业务判断是否治上游。

2. 0 元单里混着「售后补发单」,被判据一刀切 ⚠️
逐单交叉出库表后,两类成因不同:

  • 0.01 元虚假单(亿家达 18 张)→ 出库表 0 行,排除正确;
  • 0 元单(7 张)→ 出库表有单据:order_type=补发订单、status=Confirmed、标签含 发货后售后 → 真实售后补发流程。

这两类被 pay_amount <= 0.01 一起排除了。当前影响为 0(这 7 张涉 6 个 SKU,其快照日销均 > 0,兜底本就不生效;天猫表单独复算 6→6、被排除 0),但若日后快照掉到 0 会漏算 —— 与遗留项 2 同源,建议明确口径:要么「0 元单一律不算销售」,要么把 0 元分支收窄为「带 特殊单/刷单待处理/AB包 标签的 0 元单」。

3. 顺带修复的隐性收益(建议写进描述)
判据同时排除了窗口内的负数金额单(天猫 9263814,−694.61 元)。这类退款/冲销单此前只要 send_date 非空就会累加件数、虚增日销,属正收益。

三、一个必须写进注释的坑 🔥

实测 rpt.jst_salesOutboundInfo 全表 1,119,606 行 qty 全部为 0(近 15 天 180,433 行同样,MAX(qty)=0)。

→ 该列不能作为「出库数量」判据,只能看行数/单据存在性。 PR 用「出库行数 = 0」判断虚假单,方法正确;建议补注释,防止后人误用 SUM(qty)(恒得 0)。

四、性能

同一查询取 5 列 vs 7 列:首次 1185ms / 二次 224ms,差异来自计划缓存而非列数;两列均为基表列,非计算列,无退化。

五、遗留项量化(供业务决策)

待发货(⑩)分支未改(PR 已声明),实测该分支异常单��� 8 张(京东 3 + 天猫 5,均为 0 元单),规模很小,建议保持本次不改、单独确认口径。


完整报告:outputs/审核-PR19-日销兜底排除异常单.md

建议:可合并;合并前建议把「范围补全」补进描述、把「qty 不可用」写进注释。

## 独立审核结论:通过(附条件) 审核范围:代码逻辑 + 边界处理 + 查询正确性 + 真实数据复算 + 测试基线。**未发现阻断性问题**,核心结论已用业务库实时数据独立复现。 ### 一、复现结果 | 指标 | PR 描述 | 独立复算 | |---|---|---| | 被排除的 SKU | 10 个 | **10 个,编码完全一致** ✅ | | 兜底日销最大值 | 1.8 → 0.2 | 1.8 → 0.2 ✅ | | 亿家达 0.01 元单出库行数 | 0 行 | 0 行 ✅ | | 两表列 `pay_amount`/`labels` | 存在 | 存在(decimal / nvarchar)✅ | 测试(在 PR 分支重跑):`check-syntax` 151 文件通过;`node --test` **338/337**,唯一失败为 `resilientSecretStore` Windows 权限断言(环境固有);`mkhBomService.test.js` **44/44**。新增断言的**反例保护**(有免额的正常单不得被排除)质量很好。 判据宽度已做反例检查:`(0.01, 1]` 区间 19 行全部是 `MKH-` 组合装(被 `isMkhProductCode` 先排除,命中索引 **0 行**),当前窗口内**无漏网**。 ### 二、建议补充(3 点,非代码缺陷) **1. 描述范围写小了 ⚠️** 实测窗口内 ≤0.01 元的已发货 MKH 单共 **137 行**(104 张 0.01 元 + **33 张 0 元**),且**每月重复**、且都是**几秒内批量生成**: | 店铺 | 行数 | 时间跨度 | |---|---|---| | 拼多多-HX旗舰店106 | 60 | 09-09 11:17:28→32(4 秒) | | 拼多多-索菲亚客厅家具旗舰店 | 52 | 09-14 12:19:09→11(2 秒) | | 拼多多-亿家达书房家具官方旗舰店 | 18 | 09-18 10:36:27→30 | | 其余店铺 | 7 | 09-05 ~ 09-15 | 这是**系统性流程产物**(标签 `AB包`/`大单自动匹配`/`刷单待处理`,备注 `BBFF`/`SSDD`),不是脏数据孤例。建议写进描述,供业务判断是否治上游。 **2. 0 元单里混着「售后补发单」,被判据一刀切 ⚠️** 逐单交叉出库表后,两类成因不同: - 0.01 元虚假单(亿家达 18 张)→ 出库表 **0 行**,排除正确; - 0 元单(7 张)→ 出库表**有单据**:`order_type=补发订单`、`status=Confirmed`、标签含 `发货后售后` → 真实售后补发流程。 这两类被 `pay_amount <= 0.01` 一起排除了。**当前影响为 0**(这 7 张涉 6 个 SKU,其快照日销均 > 0,兜底本就不生效;天猫表单独复算 6→6、被排除 0),但若日后快照掉到 0 会漏算 —— 与遗留项 2 同源,建议明确口径:要么「0 元单一律不算销售」,要么把 0 元分支收窄为「带 `特殊单`/`刷单待处理`/`AB包` 标签的 0 元单」。 **3. 顺带修复的隐性收益(建议写进描述)** 判据同时排除了窗口内的**负数金额单**(天猫 `9263814`,−694.61 元)。这类退款/冲销单此前只要 `send_date` 非空就会累加件数、**虚增日销**,属正收益。 ### 三、一个必须写进注释的坑 🔥 实测 `rpt.jst_salesOutboundInfo` **全表 1,119,606 行 `qty` 全部为 0**(近 15 天 180,433 行同样,`MAX(qty)=0`)。 → **该列不能作为「出库数量」判据,只能看行数/单据存在性。** PR 用「出库行数 = 0」判断虚假单,方法**正确**;建议补注释,防止后人误用 `SUM(qty)`(恒得 0)。 ### 四、性能 同一查询取 5 列 vs 7 列:首次 1185ms / 二次 224ms,差异来自计划缓存而非列数;两列均为基表列,非计算列,**无退化**。 ### 五、遗留项量化(供业务决策) 待发货(⑩)分支未改(PR 已声明),实测该分支异常单��� **8 张**(京东 3 + 天猫 5,均为 0 元单),规模很小,建议保持本次不改、单独确认口径。 --- 完整报告:`outputs/审核-PR19-日销兜底排除异常单.md` **建议**:可合并;合并前建议把「范围补全」补进描述、把「qty 不可用」写进注释。
yanhui reviewed 2026-09-19 17:19:40 +08:00
yanhui left a comment
Author
Collaborator

独立审核结论:通过(附条件)

审核范围:代码逻辑 + 边界处理 + 查询正确性 + 真实数据复算 + 测试基线。未发现阻断性问题,核心结论已用业务库实时数据独立复现。

一、复现结果

指标 PR 描述 独立复算
被排除的 SKU 10 个 10 个,编码完全一致 ✅
兜底日销最大值 1.8 → 0.2 1.8 → 0.2 ✅
亿家达 0.01 元单出库行数 0 行 0 行 ✅
两表列 pay_amount/labels 存在 存在(decimal / nvarchar)✅

测试(在 PR 分支重跑):check-syntax 151 文件通过;node --test 338/337,唯一失败为 resilientSecretStore Windows 权限断言(环境固有);mkhBomService.test.js 44/44。新增断言的反例保护(有免额的正常单不得被排除)质量很好。

判据宽度已做反例检查:(0.01, 1] 区间 19 行全部是 MKH- 组合装(被 isMkhProductCode 先排除,命中索引 0 行),当前窗口内无漏网。

二、建议补充(3 点,非代码缺陷)

1. 描述范围写小了 ⚠️
实测窗口内 ≤0.01 元的已发货 MKH 单共 137 行(104 张 0.01 元 + 33 张 0 元),且每月重复、且都是几秒内批量生成:

店铺 行数 时间跨度
拼多多-HX旗舰店106 60 09-09 11:17:28→32(4 秒)
拼多多-索菲亚客厅家具旗舰店 52 09-14 12:19:09→11(2 秒)
拼多多-亿家达书房家具官方旗舰店 18 09-18 10:36:27→30
其余店铺 7 09-05 ~ 09-15

这是系统性流程产物(标签 AB包/大单自动匹配/刷单待处理,备注 BBFF/SSDD),不是脏数据孤例。建议写进描述,供业务判断是否治上游。

2. 0 元单里混着「售后补发单」,被判据一刀切 ⚠️
逐单交叉出库表后,两类成因不同:

  • 0.01 元虚假单(亿家达 18 张)→ 出库表 0 行,排除正确;
  • 0 元单(7 张)→ 出库表有单据:order_type=补发订单、status=Confirmed、标签含 发货后售后 → 真实售后补发流程。

这两类被 pay_amount <= 0.01 一起排除了。当前影响为 0(这 7 张涉 6 个 SKU,其快照日销均 > 0,兜底本就不生效;天猫表单独复算 6→6、被排除 0),但若日后快照掉到 0 会漏算 —— 与遗留项 2 同源,建议明确口径:要么「0 元单一律不算销售」,要么把 0 元分支收窄为「带 特殊单/刷单待处理/AB包 标签的 0 元单」。

3. 顺带修复的隐性收益(建议写进描述)
判据同时排除了窗口内的负数金额单(天猫 9263814,−694.61 元)。这类退款/冲销单此前只要 send_date 非空就会累加件数、虚增日销,属正收益。

三、一个必须写进注释的坑 🔥

实测 rpt.jst_salesOutboundInfo 全表 1,119,606 行 qty 全部为 0(近 15 天 180,433 行同样,MAX(qty)=0)。

→ 该列不能作为「出库数量」判据,只能看行数/单据存在性。 PR 用「出库行数 = 0」判断虚假单,方法正确;建议补注释,防止后人误用 SUM(qty)(恒得 0)。

四、性能

同一查询取 5 列 vs 7 列:首次 1185ms / 二次 224ms,差异来自计划缓存而非列数;两列均为基表列,非计算列,无退化。

五、遗留项量化(供业务决策)

待发货(⑩)分支未改(PR 已声明),实测该分支异常单共 8 张(京东 3 + 天猫 5,均为 0 元单),规模很小,建议保持本次不改、单独确认口径。


完整报告:outputs/审核-PR19-日销兜底排除异常单.md

建议:可合并;合并前建议把「范围补全」补进描述、把「qty 不可用」写进注释。

## 独立审核结论:通过(附条件) 审核范围:代码逻辑 + 边界处理 + 查询正确性 + 真实数据复算 + 测试基线。**未发现阻断性问题**,核心结论已用业务库实时数据独立复现。 ### 一、复现结果 | 指标 | PR 描述 | 独立复算 | |---|---|---| | 被排除的 SKU | 10 个 | **10 个,编码完全一致** ✅ | | 兜底日销最大值 | 1.8 → 0.2 | 1.8 → 0.2 ✅ | | 亿家达 0.01 元单出库行数 | 0 行 | 0 行 ✅ | | 两表列 `pay_amount`/`labels` | 存在 | 存在(decimal / nvarchar)✅ | 测试(在 PR 分支重跑):`check-syntax` 151 文件通过;`node --test` **338/337**,唯一失败为 `resilientSecretStore` Windows 权限断言(环境固有);`mkhBomService.test.js` **44/44**。新增断言的**反例保护**(有免额的正常单不得被排除)质量很好。 判据宽度已做反例检查:`(0.01, 1]` 区间 19 行全部是 `MKH-` 组合装(被 `isMkhProductCode` 先排除,命中索引 **0 行**),当前窗口内**无漏网**。 ### 二、建议补充(3 点,非代码缺陷) **1. 描述范围写小了 ⚠️** 实测窗口内 ≤0.01 元的已发货 MKH 单共 **137 行**(104 张 0.01 元 + **33 张 0 元**),且**每月重复**、且都是**几秒内批量生成**: | 店铺 | 行数 | 时间跨度 | |---|---|---| | 拼多多-HX旗舰店106 | 60 | 09-09 11:17:28→32(4 秒) | | 拼多多-索菲亚客厅家具旗舰店 | 52 | 09-14 12:19:09→11(2 秒) | | 拼多多-亿家达书房家具官方旗舰店 | 18 | 09-18 10:36:27→30 | | 其余店铺 | 7 | 09-05 ~ 09-15 | 这是**系统性流程产物**(标签 `AB包`/`大单自动匹配`/`刷单待处理`,备注 `BBFF`/`SSDD`),不是脏数据孤例。建议写进描述,供业务判断是否治上游。 **2. 0 元单里混着「售后补发单」,被判据一刀切 ⚠️** 逐单交叉出库表后,两类成因不同: - 0.01 元虚假单(亿家达 18 张)→ 出库表 **0 行**,排除正确; - 0 元单(7 张)→ 出库表**有单据**:`order_type=补发订单`、`status=Confirmed`、标签含 `发货后售后` → 真实售后补发流程。 这两类被 `pay_amount <= 0.01` 一起排除了。**当前影响为 0**(这 7 张涉 6 个 SKU,其快照日销均 > 0,兜底本就不生效;天猫表单独复算 6→6、被排除 0),但若日后快照掉到 0 会漏算 —— 与遗留项 2 同源,建议明确口径:要么「0 元单一律不算销售」,要么把 0 元分支收窄为「带 `特殊单`/`刷单待处理`/`AB包` 标签的 0 元单」。 **3. 顺带修复的隐性收益(建议写进描述)** 判据同时排除了窗口内的**负数金额单**(天猫 `9263814`,−694.61 元)。这类退款/冲销单此前只要 `send_date` 非空就会累加件数、**虚增日销**,属正收益。 ### 三、一个必须写进注释的坑 🔥 实测 `rpt.jst_salesOutboundInfo` **全表 1,119,606 行 `qty` 全部为 0**(近 15 天 180,433 行同样,`MAX(qty)=0`)。 → **该列不能作为「出库数量」判据,只能看行数/单据存在性。** PR 用「出库行数 = 0」判断虚假单,方法**正确**;建议补注释,防止后人误用 `SUM(qty)`(恒得 0)。 ### 四、性能 同一查询取 5 列 vs 7 列:首次 1185ms / 二次 224ms,差异来自计划缓存而非列数;两列均为基表列,非计算列,**无退化**。 ### 五、遗留项量化(供业务决策) 待发货(⑩)分支未改(PR 已声明),实测该分支异常单共 **8 张**(京东 3 + 天猫 5,均为 0 元单),规模很小,建议保持本次不改、单独确认口径。 --- 完整报告:`outputs/审核-PR19-日销兜底排除异常单.md` **建议**:可合并;合并前建议把「范围补全」补进描述、把「qty 不可用」写进注释。
yanhui added 1 commit 2026-09-19 17:53:32 +08:00
业务口径:标了「已发货」但没有实际出库的 SKU,不需要出现缺货预警。
即在 ⑦-a 的金额/标签判据之上,再以出库表为准判定「有没有真出货」。

已发货分支计入销量需同时满足(待发货分支不变):
  1. 非样品/测试店铺(⑦-a)
  2. 非异常单:pay_amount <= 0.01 或 labels 含「特殊单」(⑦-a)
  3. 出库表能查到该 o_id:rpt.jst_salesOutboundInfo / rpt.jst_qmcust_salesoutbound

两个必须豁免的情形(否则会大面积误杀真实单):
  · 拆分单(status='Split'):出库记录挂在子单上,父单必然查不到。
    实测窗口内 40 张 Split 单 100% 无出库记录;不豁免会误杀
    「京东-HX旗舰店104 09-19 实付 1194.14 元」这类真实单
  · 今天发货的单:当天出库尚未同步完(09-19 全天出库表仅 1 行,09-18 有 14,690 行)。
    豁免只到「昨天」为止——09-18 的 18 张无出库 Sent 单 100% 是特殊单,
    且出库表 09-18 数据完整,豁免 09-18 会放过刷单
  豁免只免「出库校验」,金额/标签判据照样生效。

真实数据验收(近 15 天窗口,1,086 张已发货 MKH 单):
  · 出库表命中 889 张 → 197 张无出库 = 40 张拆分单 + 157 张 Sent
  · 157 张里 131 张是 ≤0.01 元或「特殊单」(即 ⑦-a 已排除的刷单),
    唯一 1 张正常金额的是 09-05 的 388 元单(标签「发货后取消/发货后售后/风控订单」)
  · 兜底生效 SKU = 12 个(与只按金额/标签判据一致),兜底日销最大值 = 0.2
  · 10 个异常单 SKU 在预警列表中零出现;总预警 27 条(缺货 21 + 仅预警 6)

性能:1086 个订单号分 2 块并行 UNION 查询,冷缓存约 0.4~1.4s;
      整窗口拉全量 DISTINCT o_id 实测要 8s,故不采用。

降级策略:出库数据取不到时整体跳过出库校验(退化成只按金额/标签判据)并标 degraded。
漏杀只是多报一条预警,错杀会让真实缺货彻底隐身。

注意:jst_salesOutboundInfo.qty 全表 1,119,606 行恒为 0,只能判「有没有出库行」,
不能用 SUM(qty)(永远得 0)。

验证:check-syntax 151 文件通过;node --test 338/339(唯一失败是既有的
resilientSecretStore Windows 权限断言,与本改动无关);mkhBomService.test.js 45/45。
yanhui changed title from fix: MKH-BOM 日销兜底排除 0.01 元特殊单,修复「没发过货却有日销」 to fix: MKH-BOM 日销兜底:以出库为准,标了「已发货」但无实际出库的单不计销量 2026-09-19 17:53:48 +08:00
Author
Collaborator

口径升级说明(请按最新提交 0e0fcbb 重新审核)

业务补充口径:「这些异常 SKU,系统标记了『已发货』,但没有实际出库的,不需要出现缺货预警」——即判据要从「金额/标签」下沉到「有没有真出库」。

本次新增提交 0e0fcbb(旧提交 dff2422 仍在,属递进关系):

  • 已发货分支新增判据 3:o_id 必须能在 rpt.jst_salesOutboundInfo / rpt.jst_qmcust_salesoutbound 查到
  • 两个豁免(不豁免会大面积误杀真实单,请重点复核):
    1. 拆分单 status='Split' —— 出库挂子单,窗口内 40 张 Split 单 100% 无出库;不豁免会误杀 09-19 那张 1194.14 元真实单
    2. 今天发货的单 —— 当天出库未同步完(09-19 全天出库表仅 1 行);豁免只到今天,不含昨天(09-18 的 18 张无出库单 100% 是特殊单)

审核时建议重点看两点:

  • 豁免窗口是否够窄:只豁免「拆分单 + 今天」,09-18 及更早必须走严格出库校验
  • 降级方向是否正确:出库数据取不到时跳过校验(退回金额判据)而不是「查不到就排除」——前者漏杀、后者错杀,错杀会让真实缺货隐身

最新数据(近 15 天窗口):兜底生效 SKU 12、日销最大值 0.2、10 个异常单 SKU 在预警列表零出现、总预警 27 条。详见 PR 描述。

## 口径升级说明(请按最新提交 `0e0fcbb` 重新审核) 业务补充口径:「**这些异常 SKU,系统标记了『已发货』,但没有实际出库的,不需要出现缺货预警**」——即判据要从「金额/标签」下沉到「有没有真出库」。 本次新增提交 `0e0fcbb`(旧提交 `dff2422` 仍在,属递进关系): - 已发货分支新增判据 3:`o_id` 必须能在 `rpt.jst_salesOutboundInfo` / `rpt.jst_qmcust_salesoutbound` 查到 - 两个**豁免**(不豁免会大面积误杀真实单,请重点复核): 1. 拆分单 `status='Split'` —— 出库挂子单,窗口内 40 张 Split 单 **100% 无出库**;不豁免会误杀 09-19 那张 **1194.14 元**真实单 2. 今天发货的单 —— 当天出库未同步完(09-19 全天出库表仅 1 行);**豁免只到今天,不含昨天**(09-18 的 18 张无出库单 100% 是特殊单) 审核时建议重点看两点: - **豁免窗口是否够窄**:只豁免「拆分单 + 今天」,09-18 及更早必须走严格出库校验 - **降级方向是否正确**:出库数据取不到时**跳过校验**(退回金额判据)而不是「查不到就排除」——前者漏杀、后者错杀,错杀会让真实缺货隐身 最新数据(近 15 天窗口):兜底生效 SKU **12**、日销最大值 **0.2**、10 个异常单 SKU 在预警列表**零出现**、总预警 27 条。详见 PR 描述。
yanhui added 1 commit 2026-09-19 18:11:56 +08:00
现象:MKHDNZ0409260700491 当天已入库、实物有货,但页面上「现有库存」一直是 0;
点「同步最新数据」也不变。

根因:页面唯一的库存源 rpt.jst_inventoryInfo 是「有变化才写 updateTime」的同步表,
品牌仓尤其稀疏。该 SKU 在 inventoryInfo 里只有主仓 14138566 的一行 qty=0,
品牌仓连行都没有;而小时级的箱表 rpt.jst_packInventoryInfo 里,
品牌仓 bin='P-1' / qty=4 / createTime 当天 15:19 —— 当日入库的货只有箱表看得见。
页面取不到实时值 → 回退 09-18 监控快照 jsStock=0 → 显示 0。
「同步最新数据」按钮只清服务端缓存,不触发上游同步,所以点多少次都是 0。

改法(只补 0,不覆盖有效值):
  ① inventoryInfo 品牌仓 qty > 0        → 用它(stockSource='realtime',旧口径不变)
  ② 否则箱表品牌仓 bin 非空行合计 > 0    → 用它(stockSource='pack')
  ③ 否则 inventoryInfo 有品牌仓行(qty≤0) → 用 0
  ④ 否则回退监控快照 jsStock             → stockSource='monitor'

为什么不拿箱表当主源:两表都 > 0 且不等的 28 个成品里 25 个是 inventoryInfo 更大
(箱表只记已装箱的货,散货不计),拿它当主源会系统性低估库存、误报缺货。
另外必须过滤 bin 为空的行——那是装箱记录不是库存(品牌仓合计 21 件)。

影响面(实测):品牌仓 4,408 个成品里只有 3 个走箱表兜底
(MKHDNZ0409260601548=5 / MKHDNZ0409260700059=4 / MKHDNZ0409260700491=4,
同一箱 115870495000001201、货位 P-1、均为当天新建行),
预警列表 27 → 24 条(缺货 21 → 18),少的正是这 3 条「入库当天被误判缺货」。

前端:「现有库存」列 tooltip 增加 pack 来源说明(当天入库、实时库存表尚未同步)。

验证:check-syntax 151 文件通过;node --test 339/340(唯一失败是既有的
resilientSecretStore Windows 权限断言);mkhBomService.test.js 47/47;
HTTP 实测该 SKU stock=4 / stockSource=pack,点「同步最新数据」后仍正确。

遗留(需业务/上游跟进):inventoryInfo 当天 06:41 后就没再更新过
(09-17 / 09-18 都是全天逐小时更新),疑似上游同步任务异常;
箱表兜底只解决「当日入库看不见」,存量库存仍依赖该表恢复。
yanhui changed title from fix: MKH-BOM 日销兜底:以出库为准,标了「已发货」但无实际出库的单不计销量 to fix: MKH-BOM 预警与库存口径修复:日销以出库为准 + 库存增加箱表兜底 2026-09-19 18:12:31 +08:00
yanhui merged commit bbabda5016 into main 2026-09-22 23:14:37 +08:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: lunanting/cangku#19
No description provided.