需求(yanhui 2026-10-20):在 MKH-BOM 列表左侧加一栏「仓储中心24小时发货时效」, 下列 11 个主仓(横林二仓、横林三仓、横林二仓品牌仓、崔桥工厂仓、崔桥工厂品牌仓、 崔桥工厂半成品仓、新苏北仓、睢宁仓、山东青岗集仓、山东品牌仓、河南洛宁仓), 从系统库存抓取已发货/未发货订单,按订单创建时间判定发货是否及时: · 每天 12:00 核验「当天创建时间 7:00 之前」的订单是否已完成发货 · 每天 16:00 核验「当天创建时间 16:00 之前」的订单是否已完成发货 已确认的三条设计决策: 1. 子仓归属 → 连子仓一起算(京东云仓/自营仓/运营C专用仓/唯品仓等并入主仓) 2. 崔桥工厂半成品仓 → 系统无同名仓,先留空占位(不映射到配件仓 15718196) 3. 展示结构 → 只显示超时未发 实现要点 - 仓库归属用「关键词最长匹配」(不写死 wms_co_id),新增子仓只要名字含主仓关键词 就自动归入,缓存 1h。实测 28 个子仓全部正确归入 11 个主仓,132 个代发仓零误并。 - 12:00 之前**不做核验**:resolveActiveRound 返回 null,服务层直接返回 notStarted, 既不查订单表(接口 165ms)也不回填昨天的数据。前端显示「未到核验时间」+ 「12:00 开始首轮核验(核验当日 07:00 前创建的订单)」,11 个仓结构保留、数字显示「—」。 - 订单取数:两张表店铺互斥(jst_Orders = 京东/拼多多/抖音/得物/唯品会, jst_qmcust_orderlist = 天猫/淘宝/1688),并行分开查 + Node 侧按 o_id 去重合并, 用 Promise.allSettled 保证单表失败时另一表数据仍可用(detailDegraded 标记)。 - 三态判定 pending / shippedLate / onTime:send_date 与出库表 io_date 取先到的发货痕迹, 避免上游 send_date 回写滞后导致「已发的被判成没发」。 - 状态过滤:排除 Cancelled / Merged / **Split**(拆单父单永不发货,不排除会捞出一年前 1.3 万条历史单),Question 仍计入待发。 - freshness 字段如实报告出库表最新 io_date,前端「发货数据可能滞后」告警, 不拿一份可能滞后的数据去考核仓库。 排除的两次错误优化(均在代码注释里留了实测数据与复测脚本路径) 1. 用 created 替代 order_date 做窗口过滤(快 10 倍)—— 已证伪:2581 行里完全相等的只有 4 行, 差 >1h 的 412 行,198 行跨日;典型形态 order_date=10-18 23:58 而 created=10-19 04:00:08 (上游凌晨 4 点批量补录,created 是灌库时间不是下单时间)。业务口径是「订单创建时间」, 只有 order_date 正确。 2. 「UNION ALL 一条 SQL 慢 5~8 倍」—— 被后续实测推翻(46.3s vs 并行 48.2s,基本持平)。 验证 - 新增 backend/test/warehouseShipTimeliness.test.js,10 条用例全绿,覆盖: 最长匹配不误并、子仓命名、山东/河南高频前缀不误并、占位仓、12:00 前返回 null、 12:00 轮窗口 00:00~07:00、16:00 轮窗口 00:00~16:00、状态排除表、三态分桶互斥、 跨表重复 o_id 去重、round.windowEnd 必须带出(否则前端渲染成「核验 前」)。 - check-syntax 185 文件通过;全量 node --test 415 项 / 413 过 / 2 fail, 两条失败均为已知环境因素(负缓存带 TTL 因 .env 有 CLOUD_DB_* 短路; resilientSecretStore 的 Windows 权限断言,基线恒失败)。 - 真渲染验收(组件级沙盒 + 真实接口数据):两态均 0 控制台错误; 已核验态 358 单超时未发、8 个仓有数字、点仓切换选中/再点取消均正常; 多分辨率验证 1920/1600 侧栏 208px、1440/1366 收窄 176px、列表始终竖向。
63 lines
2.1 KiB
JavaScript
63 lines
2.1 KiB
JavaScript
'use strict';
|
||
|
||
/**
|
||
* 仓储中心 24 小时发货时效 —— HTTP 路由(2026-10-20 新增)
|
||
* ────────────────────────────────────────────────────────────────────────────
|
||
* GET /api/warehouse-ship-timeliness/list
|
||
* query:
|
||
* round 'NOON' | 'AFTERNOON' 手动指定核验轮次;不传按当前时刻自动判定
|
||
* limit 明细条数上限(默认 500,最大 2000)
|
||
*
|
||
* GET /api/warehouse-ship-timeliness/rounds
|
||
* 只返回今日两轮的描述(页面顶部提示用,不打订单表,秒回)
|
||
*
|
||
* 挂载见 src/index.js:`/api/warehouse-ship-timeliness` + authMiddleware
|
||
*/
|
||
|
||
const express = require('express');
|
||
const { getShipTimeliness } = require('../services/warehouseShipTimelinessService');
|
||
const { describeRounds, CENTERS } = require('../services/warehouseShipTimelinessRegistry');
|
||
|
||
const router = express.Router();
|
||
|
||
function sendError(res, error, fallback) {
|
||
const status = Number(error?.statusCode) || 500;
|
||
console.error('[WarehouseShipTimeliness]', {
|
||
message: error?.message || fallback,
|
||
code: error?.code || null,
|
||
status,
|
||
});
|
||
res.status(status).json({ success: false, error: error.message || fallback });
|
||
}
|
||
|
||
// 时效总览:每个主仓「超时未发」数量 + 超时订单明细
|
||
router.get('/list', async (req, res) => {
|
||
try {
|
||
const data = await getShipTimeliness(req.query || {});
|
||
res.json({ success: true, data });
|
||
} catch (error) {
|
||
sendError(res, error, '加载仓储中心发货时效失败');
|
||
}
|
||
});
|
||
|
||
// 仅轮次信息(不查订单)
|
||
router.get('/rounds', async (_req, res) => {
|
||
try {
|
||
res.json({
|
||
success: true,
|
||
data: {
|
||
rounds: describeRounds(new Date()),
|
||
centers: CENTERS.map((c) => ({
|
||
key: c.key,
|
||
name: c.name,
|
||
placeholder: Boolean(c.placeholder),
|
||
})),
|
||
},
|
||
});
|
||
} catch (error) {
|
||
sendError(res, error, '加载核验轮次失败');
|
||
}
|
||
});
|
||
|
||
module.exports = router;
|