cangku/frontend/src/constants/largeStockMonitor.js
lunanting 2bc949099b feat: 新增大货库存监控(26 仓)
按「库存监控」表格在侧边栏增加一级/二级菜单与共用页面
(路由 /planning/large-stock-monitor/:warehouseSlug)。

后端:
- 新增 largeStockMonitorConfig.js:26 仓配置,含 wms_co_id / 快照仓名 / routeKey / slug 映射
- 新增 largeStockMonitor.js:config / overview / sku-list 三个接口
- 新增 largeStockMonitorFlow.js:采购进仓、采购退货、销售出仓、销售退货四类单据流水取数
- 新增 verify-large-stock-monitor.js:26 仓端到端校验脚本
- index.js:挂载 /api/large-stock-monitor 路由

口径说明:
- 库存周转天数 = 月度发货总件数 / 月度日均库存件数 × 100%,下沉到 SQL CASE WHEN 计算
- 四级分级(滞销预警/慢流/呆滞/僵尸)按库龄 + 周转率双维度判定
- 期末数量取聚水潭实际库存 jst_inventoryInfo(按 wms_co_id 单仓),
  不用 pm_stockMonitor.jsStock —— 后者 warehouse 是「归属仓」口径,
  会把已调拨到外仓的货仍计在本仓名下(崔桥工厂仓因此虚高约 2.2 万)
- 截止时刻统一为快照日次日 01:00(跑批时点),库龄/分级均以此为基准,不再用 GETDATE()

前端:
- 新增 LargeStockMonitor 页面、18 列定义、API 封装与样式
- 路由与侧边栏:26 条二级菜单 + 权限点

冲突处理:
- 主仓库已下线 /api/dashboard(backend/src/api/dashboard.js 被移除),
  相应删除 dashboardRoutes 的 require 与挂载,避免后端启动时 require 失败崩溃
- 保留主仓库新增的「模块化库存监控」菜单组及其子项
2026-09-23 13:42:58 +08:00

165 lines
9.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 大货库存监控配置(前端镜像)
// 与 backend/src/config/largeStockMonitorConfig.js 同步
// 修改需双端同步;运行期也可通过 /api/large-stock-monitor/config 获取
const RAW_WAREHOUSES = [
{ idx: 1, group: '半成品部件仓', warehouse: '简尚-崔桥工厂半成品包装项目仓主仓' },
{ idx: 2, group: '运营C', warehouse: '简尚-运营C崔桥工厂仓专用仓主仓' },
{ idx: 3, group: '崔桥工厂仓', warehouse: '简尚-崔桥工厂仓主仓' },
{ idx: 4, group: '崔桥工厂仓', warehouse: '简尚-崔桥工厂京东仓主仓' },
{ idx: 5, group: '崔桥工厂品牌仓', warehouse: '简尚-崔桥工厂品牌仓主仓' },
{ idx: 6, group: '河南洛宁仓', warehouse: '简尚-河南洛宁仓主仓' },
{ idx: 7, group: '运营D', warehouse: '简尚-运营D专用仓库主仓' },
{ idx: 8, group: '运营C', warehouse: '简尚-运营C横林二仓专用仓主仓' },
{ idx: 9, group: '横林二仓', warehouse: '简尚-横林二仓主仓' },
{ idx: 10, group: '横林三仓', warehouse: '简尚-横林三仓主仓' },
{ idx: 11, group: '山东品牌仓', warehouse: '简尚-山东品牌仓主仓' },
{ idx: 12, group: '山东青集岗仓', warehouse: '简尚-山东青岗集京东仓主仓' },
{ idx: 13, group: '山东青集岗仓', warehouse: '简尚-山东青岗集仓主仓' },
{ idx: 14, group: '运营D', warehouse: '简尚-运营D苏北专用仓主仓' },
{ idx: 15, group: '运营C', warehouse: '简尚-运营C新苏北专用仓主仓' },
{ idx: 16, group: '苏北仓', warehouse: '简尚-新苏北仓主仓' },
{ idx: 17, group: '分销仓', warehouse: '简尚-分销部苏北专用仓主仓' },
{ idx: 18, group: '运营D', warehouse: '简尚-运营D睢宁专用仓主仓' },
{ idx: 19, group: '运营C', warehouse: '简尚-运营C睢宁专用仓主仓' },
{ idx: 20, group: '睢宁仓', warehouse: '简尚-睢宁仓主仓' },
{ idx: 21, group: '沭阳金源涌动', warehouse: '简尚-沭阳金源涌动项目仓主仓' },
{ idx: 22, group: '分销仓', warehouse: '简尚-分销部睢宁专用仓主仓' },
{ idx: 23, group: '外贸仓', warehouse: '简尚-新外贸仓主仓' },
{ idx: 24, group: '外贸仓', warehouse: '简尚-济南外贸仓主仓' },
{ idx: 25, group: '分销仓', warehouse: '简尚-分销部专用仓主仓' },
{ idx: 26, group: '分销仓', warehouse: '简尚-分销部崔桥专用仓主仓' },
];
function slugify(value) {
return String(value || '')
.toLowerCase()
.replace(/[^a-z0-9\u4e00-\u9fa5-]+/g, '-')
.replace(/^-+|-+$/g, '');
}
export const LARGE_STOCK_MONITOR_WAREHOUSES = RAW_WAREHOUSES.map((entry) => ({
...entry,
routeKey: `stock-monitor-large-${entry.idx}`,
slug: slugify(entry.warehouse) || `warehouse-${entry.idx}`,
}));
// 18 列表头(按 Excel 第 1 行 D..U 列)
// available=false:业务库(220 张表)中不存在该单据类型,无数据源,显示「—」
// estimated=true:由已有字段推导(非原始字段),前端标「估」并在悬浮说明中写明推导式
export const OVERVIEW_COLUMNS = [
{
key: 'periodStart',
label: '期初数据',
available: true,
estimated: true,
tip: '业务库仅保留最新单日库存快照,无上月末历史。此值由「期末 − 采购进仓 + 采购退货 + 销售出仓 − 销售退货」倒推,未包含加工/调拨/盘点/其它出库。倒推为负时显示「—」。',
},
{ key: 'processIn', label: '加工进仓', available: false, tip: '业务库无加工单据表,无数据源' },
{
key: 'purchaseReturn',
label: '采购退货',
available: true,
tip: '本月采购退货出库单数量:rpt.jst_purchaseout + jst_purchaseoutItem(状态 Confirmed)',
},
{
key: 'purchaseIn',
label: '采购进仓',
available: true,
tip: '本月采购单已入库数量:rpt.jst_purchase + jst_purchaseItem 的 inQty(状态 Confirmed/Finished)',
},
{
key: 'salesOut',
label: '销售出仓',
available: true,
tip: '本月销售出库单件数:rpt.jst_salesOutboundInfo(状态 Confirmed)关联 jst_OrderItem.qty',
},
{ key: 'stocktake', label: '盘点', available: false, tip: '业务库无盘点单据表,无数据源' },
{ key: 'processOut', label: '加工出仓', available: false, tip: '业务库无加工单据表,无数据源' },
{ key: 'transferOut', label: '调拨出', available: false, tip: '业务库无调拨单据表,无数据源' },
{
key: 'salesReturn',
label: '销售退货',
available: true,
tip: '本月退货退款单实退数量:rpt.jst_returnAndRefundInfo + jst_returnAndRefundItem 的 r_qty(状态 Confirmed,按仓库名称匹配)',
},
{ key: 'transferIn', label: '调拨入', available: false, tip: '业务库无调拨单据表,无数据源' },
{
key: 'openingBalance',
label: '期初',
available: true,
estimated: true,
tip: '同期初数据(倒推估算值)',
},
{ key: 'otherOut', label: '其它出库', available: false, tip: '业务库无其它出库单据表,无数据源' },
{
key: 'closingQty',
label: '期末数量',
available: true,
tip: '聚水潭实际库存 rpt.jst_inventoryInfo(按仓库 wms_co_id 单仓实物口径,实时值)。业务库无历史时点库存快照,此值随每次刷新滚动到最新;与某日「期末结存」的差额即该日之后的出入库与仓间调拨',
},
{ key: 'turnoverDays', label: '库存周转天数', available: true, tip: '月度发货总件数 / 月度日均库存件数 × 100%(按业务库口径,数值实为周转率 %)' },
{ key: 'warnLevel', label: '滞销预警级库存量', available: true, tip: '库龄 < 3 个月 且 30% ≤ 周转率 ≤ 199% 的 SKU 库存合计(按库存快照分级,口径为归属库存,与期末实际库存不同)' },
{ key: 'slowLevel', label: '商品慢流级库存量', available: true, tip: '库龄 < 3 个月 且 周转率 ≥ 200%;或 库龄 > 3 个月 且 30% ≤ 周转率 ≤ 99%' },
{ key: 'staleLevel', label: '商品呆滞级库存量', available: true, tip: '库龄 3~12 个月 且 周转率 ≥ 100%;或 库龄 > 12 个月 且 100% ≤ 周转率 ≤ 199%' },
{ key: 'zombieLevel', label: '商品僵尸级库存量', available: true, tip: '库龄 > 12 个月 且 周转率 ≥ 200%' },
];
// 分级标签(与后端 levelExpr 返回值一致)
export const LEVEL_LABEL = {
warn: '滞销预警',
slow: '商品慢流',
stale: '商品呆滞',
zombie: '商品僵尸',
};
export const LEVEL_FILTER_OPTIONS = [
{ label: '全部级别', value: '' },
{ label: '滞销预警', value: 'warn' },
{ label: '商品慢流', value: 'slow' },
{ label: '商品呆滞', value: 'stale' },
{ label: '商品僵尸', value: 'zombie' },
];
export const LEVEL_COLOR = {
warn: 'gold',
slow: 'orange',
stale: 'volcano',
zombie: 'magenta',
};
export function findWarehouseBySlug(slug) {
return LARGE_STOCK_MONITOR_WAREHOUSES.find((entry) => entry.slug === slug) || null;
}
export function findWarehouseByName(name) {
return LARGE_STOCK_MONITOR_WAREHOUSES.find((entry) => entry.warehouse === name) || null;
}
export const PAGE_KEY = 'large_stock_monitor';
export const PAGE_PATH = '/planning/large-stock-monitor/:warehouseSlug';
export const PAGE_NAME = '大货库存监控';
// 顶部"算式说明"提示
export const FORMULA_DESCRIPTION = [
'库存周转天数 = 月度发货总件数 / 月度日均库存件数 × 100%(按业务库约定列名沿用「周转天数」,数值实际为周转率,单位 %)',
'月度发货总件数 ≈ threeAvgSale × 30 (业务库仅有最近 3 天日均销量)',
'月度日均库存件数 ≈ jsStock (业务库仅保留最近一天库存快照,作为日均近似)',
'数据截止时刻:库存快照日次日的凌晨 01:00(业务库每天凌晨 1 点跑批,快照标注为前一日 T-1)',
'库龄(月) = (数据截止时刻 - SKU 入库时间) / 30',
'分级口径:',
'· 滞销预警:库龄 < 3 个月 且 30% ≤ 周转天数 ≤ 199%',
'· 商品慢流:库龄 < 3 个月 且 周转天数 ≥ 200%;或 库龄 > 3 个月 且 30% ≤ 周转天数 ≤ 99%',
'· 商品呆滞:库龄 3~12 个月 且 周转天数 ≥ 100%;或 库龄 > 12 个月 且 100% ≤ 周转天数 ≤ 199%',
'· 商品僵尸:库龄 > 12 个月 且 周转天数 ≥ 200%',
'流水类字段(本期 = 快照所在自然月 1 日 00:00 ~ 数据截止时刻,左闭右开):',
'· 采购进仓:jst_purchase + jst_purchaseItem.inQty(Confirmed/Finished)',
'· 采购退货:jst_purchaseout + jst_purchaseoutItem.qty(Confirmed)',
'· 销售出仓:jst_salesOutboundInfo 关联 jst_OrderItem.qty(Confirmed)',
'· 销售退货:jst_returnAndRefundInfo + jst_returnAndRefundItem.r_qty(Confirmed,按仓库名匹配)',
'· 期初数据:业务库无上月末快照,由「期末 − 采购进仓 + 采购退货 + 销售出仓 − 销售退货」倒推(标「估」,倒推为负时显示「—」)',
'· 加工进仓 / 加工出仓 / 盘点 / 调拨出 / 调拨入 / 其它出库:业务库无对应单据表,无数据源',
'期末数量:聚水潭实际库存 jst_inventoryInfo(按 wms_co_id 单仓实物口径)',
'注意:pm_stockMonitor 的 warehouse 是「归属仓」,jsStock 含已调拨到外仓的货,不能代表本仓实物库存',
'四级库存量:只能由库存快照分级得出(实时库存表无入库时间与销量字段),口径为归属库存,与期末实际库存不同',
];