feat: 客退数据监控五仓看板 + 本地运行稳定性加固 #40

Merged
Xkh5201314 merged 4 commits from codex/cangku-returns-monitor-001 into main 2026-09-24 16:06:04 +08:00
Collaborator

目的与范围

  • 解决的问题:
    • 客退(退货)库存此前没有看板,横林二仓/苏北/睢宁/山东青岗集/河南洛宁五个仓的
      「客退上架仓」在库情况无法查看、无法导出。
    • 副本库只有「仓库级数量」,没有箱级维度,因此真实上架时间取不到;
      本 PR 内置「导入聚水潭导出文件」通道来覆盖创建时间/件数/供应商。
    • 本地运行时几个稳定性问题:后端重启窗口内页面直接抛 500、异常返回 HTML 看不到原因、
      隧道在非交互式启动时 ssh 找不到、业务库 DATETIME 显示整体 +8 小时。
  • 修改的模块/文件:
    • 后端 src/api/returnStockWarning.js、returnMonitor.js、returnInventoryWarning.js;
      src/services/returnStockWarningService.js、returnStockWarehouseRegistry.js、
      returnStockWarningImport.js、returnStockWarningSchema.js、returnMonitorService.js、
      returnMonitorSchema.js、exportWorkbook.js;src/jobs/returnMonitorJob.js;
      src/index.js(路由 + 全局 JSON 错误兜底 + /api JSON 404);
      src/config/cloudDatabase.js(useUTC: false)。
    • 前端 src/pages/ReturnsInventoryWarning/*、src/router/index.jsx、
      src/config/sidebarNavigation.js、src/layouts/BasicLayout.jsx、
      src/api/request.js(自动重连)、src/utils/tableExport.js。
    • scripts/start-remote.mjs(ssh 绝对路径 + 窗口隐藏)。
  • 是否使用 AI 及负责开发者:使用 AI 辅助;开发者 lunanting。

同步与兼容性

  • 起始基线 SHA:6b0bf59e744cfa8a04f41a68a2d4a493f6fb3130
  • 最近同步的 main SHA:11ef98b8c1893e6b6054ba660c56e83031293461(含 #25 ~ #38,2026-09-24 再次同步)
  • 待审核任务分支 SHA:90fa54b(分支 codex/cangku-returns-monitor-001)
  • 并行 PR、共享接口、迁移/配置影响:
    • 共享文件 backend/src/index.js、frontend/src/router/index.jsx:
      采用「双方注册内容都保留」的方式合并,已逐行核对远程的
      modularStockMonitor / mkhBom 与本地的 return-monitor / return-stock-warning 均在。
    • 新增路由 /api/return-monitor、/api/return-stock-warning、/api/return-inventory-warning,
      均挂在 authMiddleware 之后,不影响既有接口。
    • 新增本地系统库表(见下节),不改动既有表结构。
  • 冲突及处理理由:
    • backend/src/config/cloudDatabase.js(7 处):与上游 38fa3f2
      「隧道断线自愈 + 修复配置负缓存」是同一问题的两份实现
      (本地 cloudConfigRetryAfter vs 上游 cachedCloudConfigExpiresAt)。
      采用上游实现(已合入且带单测 test/cloudDatabaseConfigCache.test.js),
      并清掉本地残留引用。本地独有改动 useUTC: false 予以保留。
    • scripts/start-remote.mjs(1 处):ServerAliveCountMax / ConnectTimeout
      取值不同;上游测试 test/remote-access.test.mjs 断言了 CountMax=3 / ConnectTimeout=10,
      故取上游值。本地独有的 resolveSshBinary() 与 windowsHide 保留。
    • backend/src/index.js 无文本冲突(双方各自的路由注册行自动合并)。
    • 2026-09-24 二次同步时新增 4 处冲突(main 前进 41 个提交,引入「每日进销存」等模块):
      backend/src/index.js、frontend/src/router/index.jsx、
      frontend/src/config/sidebarNavigation.js、frontend/src/layouts/BasicLayout.jsx。
      这 4 处均由 CRLF/LF 行尾差异放大成「全文冲突」(冲突标记从第 1 行跨到最后一行),
      实质仍只是插入点不同。处理:git checkout --ours 取本分支版本,
      再逐项补回 main 独有的内容 —— dailyInventory 的 require / app.use / lazy import / 路由、
      侧边栏 large-stock-monitor 分组(order 200)+ daily-inventory 项、
      BasicLayout 的 'daily-inventory' 路由映射。
      BasicLayout.jsx 保留本分支的递归 SidebarNavNode(支持任意层级),
      main 版本是「分组 + 一层子项」的硬编码写法,已被递归版取代。
      另在 frontend/src/constants/sidebarPages.js 补齐 5 个客退看板的权限条目
      (原清单只有 3 项,新增页面未登记则管理员无法在权限页配置)。
  • 如何确认保留了其他开发者的已有行为:合并后用 grep 逐项核对远程的
    modularStockMonitor / mkhBom require 与 app.use 仍在,
    本地三条客退路由与全局错误处理器也仍在;远程新增测试全部通过。

数据库证据(按仓库规范 3.1)

表规模假设:查询对象为第三方副本库 JSReportReplica(MSSQL 218.93.9.21:1433),
jst_inventoryInfo 按 wms_co_id 分仓;退货明细(售后单 + 奇门退款单)量级约 131 万行。
本 PR 不在第三方库执行 DDL、不建索引、不写入。

新增本地表与索引(均为 MySQL 本地系统库,主键/唯一键保证幂等):

表 主键 / 唯一键 普通索引 对应查询
return_stock_warning_import PRIMARY (warehouse_key, product_code) idx_rswi_batch (batch_id) 按仓取导入覆盖值
return_stock_warning_import_batch PRIMARY (batch_id) idx_rswibatch_time (imported_at)、idx_rswibatch_wh (warehouse_key, imported_at) 批次列表/按仓查最新批次
return_monitor_inbound UNIQUE uk_rm_inbound_line (warehouse_key, return_no, product_code, inbound_date) idx_rm_inbound_status (warehouse_key,status,inbound_date)、idx_rm_inbound_code、idx_rm_inbound_date 按仓+状态+库龄筛、按编码汇总
return_monitor_stock PRIMARY (warehouse_key, stat_date, product_code) idx_rm_stock_code (warehouse_key, product_code) 每日库存快照比对
return_monitor_discrepancy UNIQUE uk_rm_discrepancy_code (warehouse_key, product_code) idx_rm_discrepancy_status (warehouse_key,status,last_seen_date) 差异清单(一编码一行,upsert 幂等)
return_monitor_job_log PRIMARY (id) idx_rm_job_log_date (warehouse_key, biz_date, created_at) 任务幂等(每天一轮)

查询边界与耗时证据:

  • 关键优化:first_in CTE 必须写成 sku_id IN (SELECT product_code FROM stock)(stock CTE 在前),
    否则会对两张退货明细大表全量 GROUP BY。实测 17.8s → 2.9s。
  • 即便如此单次仍在 3~10s,故服务内加 10 分钟缓存 + 启动时预热;
    口径/排序/分页/关键字全在内存做,只有显式 refresh=true 才回源,
    避免页面请求反复扫描远程大表。
  • 查询只取必要列;分页上限由服务控制;导出走服务端流式生成,不受前端内存限制。
  • 同步任务 returnMonitorJob.js 默认每天 07:00 一轮,按 biz_date 幂等;
    首次接入回填 180 天历史。

迁移与回退:建表全部用 CREATE TABLE IF NOT EXISTS,列/索引变更走
information_schema 探测后再 ALTER,可重复执行。回退:删除上述 6 张本地表
(不影响第三方库),并移除前端路由与后端 app.use 注册即可;无不可逆数据迁移。

验证

  • 实际执行的命令及结果(2026-09-24 二次同步后重跑):
    • backend/npm run syntax → 通过 165 个 JavaScript 文件
    • frontend/npm run build → ✓ built in 16.16s,产物同时��� Monitor(客退看板)
      与 DailyInventory(上游每日进销存)两个 chunk,证明双方模块都在
    • 本地重启后接口探测:/api/return-stock-warning、/api/return-monitor、
      /api/return-inventory-warning、/api/daily-inventory、/api/mkh-bom
      的 ping 全部 401(= 路由已注册且鉴权生效);前端 5173 返回 200
    • backend/node scripts/check-syntax.js → 通过 163 个 JavaScript 文件(首次同步时)
    • node --test test/*.test.js → 346 个测试,345 通过
    • backend/test/cloudDatabaseConfigCache.test.js → 2/2 通过(直接验证本次冲突解法)
    • frontend/vite build → ✓ built(含上游 #24 的 vite.config.js 改动后重跑)
    • frontend/scripts/verify-planning-entry.mjs → Verified planning entry
  • 未运行或失败的检查及原因:
    • test/resilientSecretStore.test.js 的
      「secret backup survives outside the repository with owner-only permissions」失败
      (期望 384=0o600,实际 438=0o666)。原因:Windows 不支持 Unix 权限位,
      chmod(0o600) 无效。该文件在本次合并前就存在(提交 9b173a0 引入),
      本次未改动,Linux 环境不受影响。
  • 人工验证路径:进入「退货库存监控 → 横林二仓客退数据监控」,
    核对 KPI、按编码汇总、导出 xlsx;切换其余四仓;上传聚水潭导出的
    「箱及仓位库存」文件验证创建时间被覆盖。

风险与回退

  • 已知风险:
    • 各仓客退位对应的自定义库存位不同(横林/苏北/睢宁 customize_qty_1、
      山东 customize_qty_2、河南当前为 0),若聚水潭侧调整仓位绑定需同步改
      returnStockWarehouseRegistry.js 的 returnSlot。
    • 创建时间在副本里是「该编码+仓 记录首次建仓时间」,对老库存可能是
      2025-12-02/03 的期初批量建账时间;真实箱级上架时间只能靠导入。
    • 本地 frontend/node_modules/vite 为 7.2.5,而 package.json 锁定 7.2.7
      (上游 #24 引入的补丁版差异),本地未重装,构建通过;生产部署脚本会自行安装。
  • 回退方式和数据兼容性:
    • 代码回退:revert 本 PR 的合并提交即可,无跨模块耦合。
    • 数据:新增表可单独删除;导入的覆盖数据通过 DELETE /api/return-stock-warning/import
      清除,清除后页面自动回落到副本推算值,不残留脏数据。
## 目的与范围 - 解决的问题: - 客退(退货)库存此前没有看板,横林二仓/苏北/睢宁/山东青岗集/河南洛宁五个仓的 「客退上架仓」在库情况无法查看、无法导出。 - 副本库只有「仓库级数量」,没有箱级维度,因此真实上架时间取不到; 本 PR 内置「导入聚水潭导出文件」通道来覆盖创建时间/件数/供应商。 - 本地运行时几个稳定性问题:后端重启窗口内页面直接抛 500、异常返回 HTML 看不到原因、 隧道在非交互式启动时 ssh 找不到、业务库 DATETIME 显示整体 +8 小时。 - 修改的模块/文件: - 后端 `src/api/returnStockWarning.js`、`returnMonitor.js`、`returnInventoryWarning.js`; `src/services/returnStockWarningService.js`、`returnStockWarehouseRegistry.js`、 `returnStockWarningImport.js`、`returnStockWarningSchema.js`、`returnMonitorService.js`、 `returnMonitorSchema.js`、`exportWorkbook.js`;`src/jobs/returnMonitorJob.js`; `src/index.js`(路由 + 全局 JSON 错误兜底 + /api JSON 404); `src/config/cloudDatabase.js`(useUTC: false)。 - 前端 `src/pages/ReturnsInventoryWarning/*`、`src/router/index.jsx`、 `src/config/sidebarNavigation.js`、`src/layouts/BasicLayout.jsx`、 `src/api/request.js`(自动重连)、`src/utils/tableExport.js`。 - `scripts/start-remote.mjs`(ssh 绝对路径 + 窗口隐藏)。 - 是否使用 AI 及负责开发者:使用 AI 辅助;开发者 lunanting。 ## 同步与兼容性 - 起始基线 SHA:`6b0bf59e744cfa8a04f41a68a2d4a493f6fb3130` - 最近同步的 main SHA:`11ef98b8c1893e6b6054ba660c56e83031293461`(含 #25 ~ #38,2026-09-24 再次同步) - 待审核任务分支 SHA:`90fa54b`(分支 `codex/cangku-returns-monitor-001`) - 并行 PR、共享接口、迁移/配置影响: - 共享文件 `backend/src/index.js`、`frontend/src/router/index.jsx`: 采用「双方注册内容都保留」的方式合并,已逐行核对远程的 modularStockMonitor / mkhBom 与本地的 return-monitor / return-stock-warning 均在。 - 新增路由 `/api/return-monitor`、`/api/return-stock-warning`、`/api/return-inventory-warning`, 均挂在 `authMiddleware` 之后,不影响既有接口。 - 新增本地系统库表(见下节),不改动既有表结构。 - 冲突及处理理由: - `backend/src/config/cloudDatabase.js`(7 处):与上游 `38fa3f2` 「隧道断线自愈 + 修复配置负缓存」是**同一问题的两份实现** (本地 `cloudConfigRetryAfter` vs 上游 `cachedCloudConfigExpiresAt`)。 **采用上游实现**(已合入且带单测 `test/cloudDatabaseConfigCache.test.js`), 并清掉本地残留引用。本地独有改动 `useUTC: false` 予以保留。 - `scripts/start-remote.mjs`(1 处):`ServerAliveCountMax` / `ConnectTimeout` 取值不同;上游测试 `test/remote-access.test.mjs` 断言了 `CountMax=3` / `ConnectTimeout=10`, 故取上游值。本地独有的 `resolveSshBinary()` 与 `windowsHide` 保留。 - `backend/src/index.js` 无文本冲突(双方各自的路由注册行自动合并)。 - **2026-09-24 二次同步时新增 4 处冲突**(main 前进 41 个提交,引入「每日进销存」等模块): `backend/src/index.js`、`frontend/src/router/index.jsx`、 `frontend/src/config/sidebarNavigation.js`、`frontend/src/layouts/BasicLayout.jsx`。 这 4 处**均由 CRLF/LF 行尾差异放大成「全文冲突」**(冲突标记从第 1 行跨到最后一行), 实质仍只是插入点不同。处理:`git checkout --ours` 取本分支版本, 再逐项补回 main 独有的内容 —— dailyInventory 的 require / `app.use` / lazy import / 路由、 侧边栏 `large-stock-monitor` 分组(order 200)+ `daily-inventory` 项、 BasicLayout 的 `'daily-inventory'` 路由映射。 `BasicLayout.jsx` 保留**本分支的递归 `SidebarNavNode`**(支持任意层级), main 版本是「分组 + 一层子项」的硬编码写法,已被递归版取代。 另在 `frontend/src/constants/sidebarPages.js` 补齐 5 个客退看板的权限条目 (原清单只有 3 项,新增页面未登记则管理员无法在权限页配置)。 - 如何确认保留了其他开发者的已有行为:合并后用 grep 逐项核对远程的 `modularStockMonitor` / `mkhBom` require 与 `app.use` 仍在, 本地三条客退路由与全局错误处理器也仍在;远程新增测试全部通过。 ## 数据库证据(按仓库规范 3.1) **表规模假设**:查询对象为第三方副本库 `JSReportReplica`(MSSQL `218.93.9.21:1433`), `jst_inventoryInfo` 按 `wms_co_id` 分仓;退货明细(售后单 + 奇门退款单)量级约 131 万行。 本 PR 不在第三方库执行 DDL、不建索引、不写入。 **新增本地表与索引**(均为 MySQL 本地系统库,主键/唯一键保证幂等): | 表 | 主键 / 唯一键 | 普通索引 | 对应查询 | |---|---|---|---| | `return_stock_warning_import` | `PRIMARY (warehouse_key, product_code)` | `idx_rswi_batch (batch_id)` | 按仓取导入覆盖值 | | `return_stock_warning_import_batch` | `PRIMARY (batch_id)` | `idx_rswibatch_time (imported_at)`、`idx_rswibatch_wh (warehouse_key, imported_at)` | 批次列表/按仓查最新批次 | | `return_monitor_inbound` | `UNIQUE uk_rm_inbound_line (warehouse_key, return_no, product_code, inbound_date)` | `idx_rm_inbound_status (warehouse_key,status,inbound_date)`、`idx_rm_inbound_code`、`idx_rm_inbound_date` | 按仓+状态+库龄筛、按编码汇总 | | `return_monitor_stock` | `PRIMARY (warehouse_key, stat_date, product_code)` | `idx_rm_stock_code (warehouse_key, product_code)` | 每日库存快照比对 | | `return_monitor_discrepancy` | `UNIQUE uk_rm_discrepancy_code (warehouse_key, product_code)` | `idx_rm_discrepancy_status (warehouse_key,status,last_seen_date)` | 差异清单(一编码一行,upsert 幂等) | | `return_monitor_job_log` | `PRIMARY (id)` | `idx_rm_job_log_date (warehouse_key, biz_date, created_at)` | 任务幂等(每天一轮) | **查询边界与耗时证据**: - 关键优化:`first_in` CTE 必须写成 `sku_id IN (SELECT product_code FROM stock)`(stock CTE 在前), 否则会对两张退货明细大表全量 `GROUP BY`。实测 **17.8s → 2.9s**。 - 即便如此单次仍在 3~10s,故服务内加 **10 分钟缓存 + 启动时预热**; 口径/排序/分页/关键字全在内存做,只有显式 `refresh=true` 才回源, 避免页面请求反复扫描远程大表。 - 查询只取必要列;分页上限由服务控制;导出走服务端流式生成,不受前端内存限制。 - 同步任务 `returnMonitorJob.js` 默认每天 07:00 一轮,按 `biz_date` 幂等; 首次接入回填 180 天历史。 **迁移与回退**:建表全部用 `CREATE TABLE IF NOT EXISTS`,列/索引变更走 `information_schema` 探测后再 `ALTER`,可重复执行。回退:删除上述 6 张本地表 (不影响第三方库),并移除前端路由与后端 `app.use` 注册即可;无不可逆数据迁移。 ## 验证 - 实际执行的命令及结果(2026-09-24 二次同步后重跑): - `backend/npm run syntax` → **通过 165 个 JavaScript 文件** - `frontend/npm run build` → **✓ built in 16.16s**,产物同时��� `Monitor`(客退看板) 与 `DailyInventory`(上游每日进销存)两个 chunk,证明双方模块都在 - 本地重启后接口探测:`/api/return-stock-warning`、`/api/return-monitor`、 `/api/return-inventory-warning`、`/api/daily-inventory`、`/api/mkh-bom` 的 ping **全部 401**(= 路由已注册且鉴权生效);前端 5173 返回 200 - `backend/node scripts/check-syntax.js` → **通过 163 个 JavaScript 文件**(首次同步时) - `node --test test/*.test.js` → **346 个测试,345 通过** - `backend/test/cloudDatabaseConfigCache.test.js` → **2/2 通过**(直接验证本次冲突解法) - `frontend/vite build` → **✓ built**(含上游 #24 的 vite.config.js 改动后重跑) - `frontend/scripts/verify-planning-entry.mjs` → **Verified planning entry** - 未运行或失败的检查及原因: - `test/resilientSecretStore.test.js` 的 「secret backup survives outside the repository with owner-only permissions」**失败** (期望 384=0o600,实际 438=0o666)。原因:**Windows 不支持 Unix 权限位**, `chmod(0o600)` 无效。该文件在本次合并前就存在(提交 `9b173a0` 引入), 本次未改动,Linux 环境不受影响。 - 人工验证路径:进入「退货库存监控 → 横林二仓客退数据监控」, 核对 KPI、按编码汇总、导出 xlsx;切换其余四仓;上传聚水潭导出的 「箱及仓位库存」文件验证创建时间被覆盖。 ## 风险与回退 - 已知风险: - 各仓客退位对应的自定义库存位不同(横林/苏北/睢宁 `customize_qty_1`、 山东 `customize_qty_2`、河南当前为 0),若聚水潭侧调整仓位绑定需同步改 `returnStockWarehouseRegistry.js` 的 `returnSlot`。 - 创建时间在副本里是「该编码+仓 记录首次建仓时间」,对老库存可能是 2025-12-02/03 的期初批量建账时间;**真实箱级上架时间只能靠导入**。 - 本地 `frontend/node_modules/vite` 为 7.2.5,而 `package.json` 锁定 7.2.7 (上游 #24 引入的补丁版差异),本地未重装,构建通过;生产部署脚本会自行安装。 - 回退方式和数据兼容性: - 代码回退:revert 本 PR 的合并提交即可,无跨模块耦合。 - 数据:新增表可单独删除;导入的覆盖数据通过 `DELETE /api/return-stock-warning/import` 清除,清除后页面自动回落到副本推算值,不残留脏数据。
Xkh5201314 added 4 commits 2026-09-24 16:05:49 +08:00
功能:
- 退货库存预警:横林二仓/苏北/睢宁/山东青岗集/河南洛宁 五张看板,
  按各仓客退上架仓对应的自定义库存位取数(各仓位不同:横林/苏北/睢宁 c1,山东 c2)
- 支持导入聚水潭「箱及仓位库存」导出文件覆盖创建时间、件数、供应商
  (副本只有仓库级数量,没有箱级维度,真实上架时间只能靠导入)
- 退货入库核销与每日库存监控:状态落本地系统库,含定时任务与差异清单
- 服务端导出(xlsx/csv),不受列表分页与展示上限约束

稳定性:
- 前端幂等请求在后端重启窗口内自动退避重连,不再直接抛 500
- 后端补齐统一 JSON 错误兜底与 /api JSON 404,异常不再返回 HTML
- 隧道 ssh 改为绝对路径解析并隐藏窗口,避免非交互式启动 ENOENT 与黑窗弹窗
- 业务库 DATETIME 按北京时间解析(useUTC: false),修复时间显示整体 +8 小时

合并:与上游「隧道断线自愈 + 配置负缓存」为同一问题的两份实现,
本分支采用上游方案(带 TTL 的负缓存 + 统一失效函数 + 单测)。
Xkh5201314 merged commit 7abb75f803 into main 2026-09-24 16:06:04 +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#40
No description provided.