feat(wst): 发货时效两轮核验完成后各推一次钉钉群 #86

Merged
yanhui merged 1 commits from yanhui-wst-notify-20261027 into main 2026-10-27 15:45:40 +08:00
Collaborator

目的与范围

业务 2026-10-27 原话:「发货时效也需要钉钉通知:13 点核验 7 点前的超时单,核验完成后通知;18:00 核验的订单,核验完成后也通知。」

即 一天两推,各自绑一个已存在的核验轮次(ROUNDS 里的 NOON 13:00 / AFTERNOON 18:00),不新增核验口径。

改动文件:

文件 说明
backend/src/services/warehouseShipTimelinessNotifySettings.js 新增:配置(本地库 system_config)+ 加签密钥(resilientSecretStore)+ 推送状态
backend/src/jobs/warehouseShipTimelinessNotifyJob.js 新增:正文组装、每分钟 tick、启动后 30 分钟补推
backend/src/api/warehouseShipTimeliness.js 加 notify/settings(GET/PUT)、notify/state(GET)、notify/run(POST)
backend/src/index.js 注册 startShipTimelinessNotifyScheduler()
frontend/src/api/warehouseShipTimeliness.js 加 4 个通知接口封装
frontend/src/pages/WarehouseShipTimeliness/index.jsx 右上「钉钉通知」按钮 + 配置弹窗
frontend/src/pages/WarehouseShipTimeliness/style.css 弹窗样式
backend/test/warehouseShipTimelinessNotify.test.js 新增 17 条单测

同步与兼容性

  • 只新增文件与路由,不改任何既有核验口径 / 既有返回值;GET /list、GET /rounds 原样。
  • 推送默认 关闭(enabled=false)。未配 webhook 时 notify/run 返回 400 + missingFields,
    不查库、不发消息。因此上线后不会自动开始推送,要业务在页面上填 webhook 并打开开关。
  • 配置键与状态键都是新 key(warehouse_ship_timeliness_notify_config / _state),不与 WLE 通知互串。
  • 调度器受 DISABLE_WST_NOTIFY=true 控制,与 DISABLE_WLE_NOTIFY 独立。

口径要点(容易做错的地方)

  1. 默认核验点 + 5 分钟(13:05 / 18:05),不是整点。状态镜像 query_time 要略晚于核验时刻,
    整点推很容易 freshness.coversDeadline=false(数据没覆盖核验点 → 超时数偏大)。
    真出现这种情况,消息里会带一行 ⚠️ 提示(快照停到几点、本轮核验点是几点),仍照发。
  2. 去重键 = <北京日期>|<轮次键>,一天两轮各自独立;两个时刻万一配成同一个 HH:mm,
    两轮都会推(不会只推第一轮)。推送成功才写状态,失败绝不写。
  3. 0 单超时也推:群里看到「本轮没有超时未发货的订单」就知道任务在跑。
  4. 历史单的审核时间必须带 MM-DD:13:00 轮窗口上界是 07:00,但口径「含历史未发」,
    明细里会出现昨天 16:13 审核的单 —— 只写「审核 16:13」会被读成「今天 16:13」,
    看着像「审核晚于窗口却还算超时」,业务会对不上账。
  5. 明细按仓聚集 + 每仓保底 1 条(同 WLE 的教训:某仓吃满额度、其它仓一条都看不到)。
  6. 明细条数用默认 500(与页面同一档)→ 与页面共用结果缓存,推完业务点进页面是热的。

验证

  • 单测:node --test test/warehouseShipTimelinessNotify.test.js → 17/17 通过。
  • 后端 npm run check 的 syntax 通过;全套 node --test test/*.test.js → 491 tests / 489 pass / 2 fail,
    失败的 cloudDatabaseConfigCache 负缓存用例(依赖数据库不可达才能拒绝,本地可达所以没拒绝)与
    resilientSecretStore 的 0o600 权限断言(Windows ��� 0o666)都是既有环境问题,
    与本改动无关,改动前后基线一致。
  • 前端 eslint src/pages/WarehouseShipTimeliness src/api/warehouseShipTimeliness.js → 0 错;
    verify-router-lazy + vite build 通过。
  • 真渲染(本地 5173,React 19 + antd v5):右上出现「钉钉通知」,弹窗 9 个配置项全部渲染,
    点「预览正文」出受控预览块(非静态 Modal),无 pageerror。
    ⚠️ 验收时把 /api/warehouse-ship-timeliness/notify/** 全部 mock —— 本地系统库与生产共用,
    避免把生产配置写坏。
  • 本地实数据预览(13:00 轮,冷查 46s):超时 15 单 / 4 个仓 / 不计考核 672 / 剔除 1 单不考核快递;
    同一时刻请求 18:00 轮返回 before_deadline(未到 18:00 不推半成品),符合预期。

风险与回退

  • 未配置即不推,默认关闭,风险面只在「开启后推错内容」。内容口径已由 17 条单测锁住。
  • 群机器人 webhook 只支持 text/markdown,发不了文件;消息里给了页面链接,要 Excel 去页面导出。
  • 急着停推送:把页面开关关掉,或后端加 DISABLE_WST_NOTIFY=true。
  • 代码回退:删掉 index.js 里 startShipTimelinessNotifyScheduler() 一行即可停止调度;
    新增文件无副作用,不回滚也不影响既有功能。
## 目的与范围 业务 2026-10-27 原话:「发货时效也需要钉钉通知:13 点核验 7 点前的超时单,核验完成后通知;18:00 核验的订单,核验完成后也通知。」 即 **一天两推**,各自绑一个已存在的核验轮次(`ROUNDS` 里的 `NOON` 13:00 / `AFTERNOON` 18:00),不新增核验口径。 改动文件: | 文件 | 说明 | |---|---| | `backend/src/services/warehouseShipTimelinessNotifySettings.js` | 新增:配置(本地库 `system_config`)+ 加签密钥(`resilientSecretStore`)+ 推送状态 | | `backend/src/jobs/warehouseShipTimelinessNotifyJob.js` | 新增:正文组装、每分钟 tick、启动后 30 分钟补推 | | `backend/src/api/warehouseShipTimeliness.js` | 加 `notify/settings`(GET/PUT)、`notify/state`(GET)、`notify/run`(POST) | | `backend/src/index.js` | 注册 `startShipTimelinessNotifyScheduler()` | | `frontend/src/api/warehouseShipTimeliness.js` | 加 4 个通知接口封装 | | `frontend/src/pages/WarehouseShipTimeliness/index.jsx` | 右上「钉钉通知」按钮 + 配置弹窗 | | `frontend/src/pages/WarehouseShipTimeliness/style.css` | 弹窗样式 | | `backend/test/warehouseShipTimelinessNotify.test.js` | 新增 17 条单测 | ## 同步与兼容性 - 只新增文件与路由,**不改任何既有核验口径 / 既有返回值**;`GET /list`、`GET /rounds` 原样。 - 推送默认 **关闭**(`enabled=false`)。未配 webhook 时 `notify/run` 返回 400 + `missingFields`, 不查库、不发消息。因此上线后**不会自动开始推送**,要业务在页面上填 webhook 并打开开关。 - 配置键与状态键都是新 key(`warehouse_ship_timeliness_notify_config` / `_state`),不与 WLE 通知互串。 - 调度器受 `DISABLE_WST_NOTIFY=true` 控制,与 `DISABLE_WLE_NOTIFY` 独立。 ## 口径要点(容易做错的地方) 1. **默认核验点 + 5 分钟(13:05 / 18:05),不是整点**。状态镜像 `query_time` 要略晚于核验时刻, 整点推很容易 `freshness.coversDeadline=false`(数据没覆盖核验点 → 超时数偏大)。 真出现这种情况,消息里会带一行 ⚠️ 提示(快照停到几点、本轮核验点是几点),仍照发。 2. **去重键 = `<北京日期>|<轮次键>`**,一天两轮各自独立;两个时刻万一配成同一个 HH:mm, 两轮都会推(不会只推第一轮)。推送成功才写状态,失败绝不写。 3. **0 单超时也推**:群里看到「本轮没有超时未发货的订单」就知道任务在跑。 4. **历史单的审核时间必须带 MM-DD**:13:00 轮窗口上界是 07:00,但口径「含历史未发」, 明细里会出现昨天 16:13 审核的单 —— 只写「审核 16:13」会被读成「今天 16:13」, 看着像「审核晚于窗口却还算超时」,业务会对不上账。 5. **明细按仓聚集 + 每仓保底 1 条**(同 WLE 的教训:某仓吃满额度、其它仓一条都看不到)。 6. 明细条数用默认 500(与页面同一档)→ 与页面共用结果缓存,推完业务点进页面是热的。 ## 验证 - 单测:`node --test test/warehouseShipTimelinessNotify.test.js` → 17/17 通过。 - 后端 `npm run check` 的 syntax 通过;全套 `node --test test/*.test.js` → 491 tests / 489 pass / 2 fail, 失败的 `cloudDatabaseConfigCache` 负缓存用例(依赖数据库**不可达**才能拒绝,本地可达所以没拒绝)与 `resilientSecretStore` 的 `0o600` 权限断言(Windows ��� `0o666`)**都是既有环境问题**, 与本改动无关,改动前后基线一致。 - 前端 `eslint src/pages/WarehouseShipTimeliness src/api/warehouseShipTimeliness.js` → 0 错; `verify-router-lazy` + `vite build` 通过。 - 真渲染(本地 5173,React 19 + antd v5):右上出现「钉钉通知」,弹窗 9 个配置项全部渲染, 点「预览正文」出受控预览块(非静态 Modal),无 pageerror。 ⚠️ 验收时把 `/api/warehouse-ship-timeliness/notify/**` 全部 mock —— 本地系统库与生产共用, 避免把生产配置写坏。 - 本地实数据预览(13:00 轮,冷查 46s):超时 15 单 / 4 个仓 / 不计考核 672 / 剔除 1 单不考核快递; 同一时刻请求 18:00 轮返回 `before_deadline`(未到 18:00 不推半成品),符合预期。 ## 风险与回退 - 未配置即不推,默认关闭,风险面只在「开启后推错内容」。内容口径已由 17 条单测锁住。 - 群机器人 webhook 只支持 text/markdown,**发不了文件**;消息里给了页面链接,要 Excel 去页面导出。 - 急着停推送:把页面开关关掉,或后端加 `DISABLE_WST_NOTIFY=true`。 - 代码回退:删掉 `index.js` 里 `startShipTimelinessNotifyScheduler()` 一行即可停止调度; 新增文件无副作用,不回滚也不影响既有功能。
yanhui added 1 commit 2026-10-27 15:43:59 +08:00
业务原话(2026-10-27):「发货时效也需要钉钉通知:13 点核验 7 点前的超时单,
核验完成后通知;18:00 核验的订单,核验完成后也通知。」

## 做了什么

- 新增 `warehouseShipTimelinessNotifySettings`:配置与状态存取,两个轮次各一个推送时刻
  (NOON 默认 13:05 / AFTERNOON 默认 18:05),加签密钥走 resilientSecretStore。
- 新增 `warehouseShipTimelinessNotifyJob`:正文组装 + 每分钟 tick + 启动后 30 分钟补推窗口。
- 路由:`GET/PUT /api/warehouse-ship-timeliness/notify/settings`、
  `GET .../notify/state`、`POST .../notify/run`(dryRun / probe / force)。
- `index.js` 注册 `startShipTimelinessNotifyScheduler()`。
- 前端:页面右上「钉钉通知」→ 弹窗(开关、两个时刻、webhook、加签密钥、明细条数、
  @所有人、页面链接、受控预览块、上次推送)。顺带把 held 明细弹窗的 `destroyOnClose`
  改成 `destroyOnHidden`(antd 5.25 已弃用)。

## 口径要点

- **一天两推,各绑一个轮次**:13:00 轮核验「今天 07:00 前审核」、18:00 轮核验
  「截止 16:00 前审核」(都含历史未发)。两轮互不影响,两个时刻配成同一个 HH:mm 时两轮都推。
- **默认核验点 + 5 分钟**,不是整点:状态镜像 `query_time` 要略晚于核验时刻,
  整点推容易 `coversDeadline=false`(数据没覆盖核验点、超时数偏大)。
  这种时候消息里会带一行提示,仍照发(业务要的是「核验完就通知」)。
- **去重键 `<北京日期>|<轮次键>`**,一天两轮各自独立;推送成功才写状态,失败不写。
- **0 单超时也推**(群里看到「本轮 0 单」= 任务没挂)。
- **明细一行 = 仓|内部单号|线上单号|审核 时间|已等 X 小时**;
  历史单的审核时间带 MM-DD —— 13:00 轮含历史未发单,只写「审核 16:13」会被读成今天。
- 明细按仓聚集 + 每仓保底 1 条,钉钉 text 超长时从末尾砍。

## 验证

- `backend/test/warehouseShipTimelinessNotify.test.js` 新增 17 条,全过。
- 后端 `check-syntax` 通过;`node --test test/*.test.js` 491 条 / 489 过 / 2 失败,
  失败两条是既有环境问题(`cloudDatabaseConfigCache` 负缓存用例依赖数据库不可达、
  `resilientSecretStore` 的 0600 权限断言在 Windows 恒为 0o666),与本改动无关。
- 前端 `eslint src/pages/WarehouseShipTimeliness src/api/warehouseShipTimeliness.js` 0 错;
  `vite build` 通过(含 `verify-router-lazy`)。
- 本地真渲染:页面右上出现「钉钉通知」,弹窗完整渲染、点「预览正文」出正文块,无 pageerror
  (验收时把 `/notify/**` 全部 mock —— 本地系统库与生产是同一个,避免写坏生产配置)。
- 本地实数据预览(13:00 轮,冷查 46s):超时 15 单 / 4 个仓 / 不计考核 672 / 剔除 1;
  同一时刻请求 18:00 轮返回 `before_deadline`(未到 18:00 不推半成品)。

## 风险与回退

- 默认关闭(`enabled=false`)且未配 webhook 时一切接口只返回 `not_configured`,不会发消息。
- 想彻底停掉:`DISABLE_WST_NOTIFY=true` 或把推送开关关掉;两者都不影响页面本身。
- 回退:删掉 `startShipTimelinessNotifyScheduler()` 一行即可停调度,新增文件无副作用。
yanhui merged commit 33bfc7eacc into main 2026-10-27 15:45:40 +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#86
No description provided.