forked from lunanting/cangku
功能: - 退货库存预警:横林二仓/苏北/睢宁/山东青岗集/河南洛宁 五张看板, 按各仓客退上架仓对应的自定义库存位取数(各仓位不同:横林/苏北/睢宁 c1,山东 c2) - 支持导入聚水潭「箱及仓位库存」导出文件覆盖创建时间、件数、供应商 (副本只有仓库级数量,没有箱级维度,真实上架时间只能靠导入) - 退货入库核销与每日库存监控:状态落本地系统库,含定时任务与差异清单 - 服务端导出(xlsx/csv),不受列表分页与展示上限约束 稳定性: - 前端幂等请求在后端重启窗口内自动退避重连,不再直接抛 500 - 后端补齐统一 JSON 错误兜底与 /api JSON 404,异常不再返回 HTML - 隧道 ssh 改为绝对路径解析并隐藏窗口,避免非交互式启动 ENOENT 与黑窗弹窗 - 业务库 DATETIME 按北京时间解析(useUTC: false),修复时间显示整体 +8 小时 合并:与上游「隧道断线自愈 + 配置负缓存」为同一问题的两份实现, 本分支采用上游方案(带 TTL 的负缓存 + 统一失效函数 + 单测)。
145 lines
6.7 KiB
JavaScript
145 lines
6.7 KiB
JavaScript
'use strict';
|
||
|
||
/**
|
||
* 客退数据监控 —— 仓库注册表
|
||
* --------------------------------------------------------------------------
|
||
* 每个仓库一张「客退数据监控」看板,页面结构完全一致,差异只有三件事:
|
||
* 1. 库存取自哪个仓(jst_inventoryInfo.wms_co_id);
|
||
* 2. 「客退临时上架仓」的库存在哪个自定义库存位上;
|
||
* 3. 退货单表(售后单 / 奇门退款单)里的销退仓名称(仅「最早入库时间」口径用)。
|
||
*
|
||
* 关于第 2 点——2026-09-17 用户给出了每个仓在聚水潭里的**准确仓位名**,并明确
|
||
* 「看板数据取自该仓位」。聚水潭给每个仓库单独开「自定义库存位」来做客退上架仓,
|
||
* **各仓用的位不一样**(2026-09-16 全库核对结论),所以两个字段要分开写:
|
||
* · returnBinName = 用户口径的聚水潭仓位名,写进页面/导出的「数据来源」,便于核对;
|
||
* · returnSlot = 副本里真正取数的那一列,必须与上面的仓位名一一对应。
|
||
*
|
||
* 仓位名 → 库存位 对照(2026-09-17 实测,条件 ISNULL(col,0) > 0):
|
||
* 箱及仓位库存 · 简尚-横林二仓 · 客退临时上架仓 customize_qty_1 1274 编码 / 3271 件
|
||
* 箱及仓位库存 · 简尚-新苏北仓 · 新苏北客退上架仓 customize_qty_1 83 编码 / 317 件
|
||
* 箱及仓位库存 · 简尚-睢宁仓 · 睢宁客退上架仓 customize_qty_1 46 编码 / 173 件
|
||
* 箱及仓位库存 · 简尚-山东青岗集仓 · 青岗集客退上架仓 customize_qty_2 324 编码 / 625 件
|
||
* 箱及仓位库存 · 简尚-河南洛宁仓 · 河南洛宁客退上架仓 三位全为 0(无数据)
|
||
*
|
||
* ⚠ 副本里**没有任何仓位维度**(2026-09-17 复核:218 张表 / 5,496 个字段中,列名含
|
||
* bin / box / position / location / shelf / slot 的为 0 个;jst_sku.bin、jst_sku.other_bin、
|
||
* jst_returnAndRefundItem.box_id、jst_packInventoryInfo.bin 四个疑似字段 100% 为空)。
|
||
* 所以「按仓位名取数」在副本上做不到,只能用该仓位绑定的自定义库存位近似;
|
||
* 要逐箱还原(含真实的箱上架时间),必须导入聚水潭导出的文件。
|
||
*
|
||
* 河南洛宁仓(15539084)当天客退库存为 0:仓库存在、也有退货单,但三个自定义库存位
|
||
* 全为 0(只有 qty 总库存 83,273 件)。看板照样建,只是空表——页面会提示
|
||
* 「该仓当前没有客退在库数据」,等聚水潭侧同步进来后自动有内容。
|
||
*/
|
||
|
||
// 客退临时上架仓在 jst_inventoryInfo 里的候选列
|
||
const RETURN_SLOT_COLUMNS = ['customize_qty_1', 'customize_qty_2', 'customize_qty_3'];
|
||
|
||
const WAREHOUSE_LIST = [
|
||
{
|
||
key: 'henlin2',
|
||
// 页面标题 / 侧边栏名称
|
||
title: '横林二仓客退数据监控',
|
||
shortName: '横林二仓',
|
||
// 仓库主表 jst_warehouseInfo.wms_co_id
|
||
wmsCoId: 15033302,
|
||
warehouseName: '简尚-横林二仓',
|
||
// 退货单(售后单 / 奇门退款单)里的销退仓名称,仅 firstInbound 口径用到
|
||
returnWarehouseDb: '简尚-横林二仓销退仓',
|
||
// 聚水潭「箱及仓位库存」里对应的仓位名(页面/导出的「数据来源」直接显示它)
|
||
returnBinName: '客退临时上架仓',
|
||
// 客退临时上架仓占用的自定义库存位
|
||
returnSlot: 'customize_qty_1',
|
||
// 该仓当天是否有客退在库数据(2026-09-17 实测);空仓时页面给提示
|
||
hasReturnStock: true,
|
||
},
|
||
{
|
||
key: 'subei',
|
||
title: '苏北客退数据监控',
|
||
shortName: '苏北',
|
||
wmsCoId: 15038347,
|
||
warehouseName: '简尚-新苏北仓',
|
||
returnWarehouseDb: '简尚-新苏北仓销退仓',
|
||
// 2026-09-17 用户指定:苏北客退数据取自「新苏北客退上架仓」
|
||
returnBinName: '新苏北客退上架仓',
|
||
returnSlot: 'customize_qty_1',
|
||
hasReturnStock: true,
|
||
},
|
||
{
|
||
key: 'suining',
|
||
title: '睢宁客退数据监控',
|
||
shortName: '睢宁',
|
||
wmsCoId: 15032603,
|
||
warehouseName: '简尚-睢宁仓',
|
||
returnWarehouseDb: '简尚-睢宁仓销退仓',
|
||
// 2026-09-17 用户指定:睢宁客退数据取自「睢宁客退上架仓」
|
||
returnBinName: '睢宁客退上架仓',
|
||
returnSlot: 'customize_qty_1',
|
||
hasReturnStock: true,
|
||
},
|
||
{
|
||
key: 'shandong',
|
||
title: '山东客退数据监控',
|
||
shortName: '山东',
|
||
wmsCoId: 15137523,
|
||
warehouseName: '简尚-山东青岗集仓',
|
||
returnWarehouseDb: '简尚-山东青岗集仓销退仓',
|
||
// 2026-09-17 用户指定:山东客退数据取自「青岗集客退上架仓」
|
||
returnBinName: '青岗集客退上架仓',
|
||
// ★ 山东用的是第 2 个自定义库存位,与横林/苏北/睢宁不同
|
||
returnSlot: 'customize_qty_2',
|
||
hasReturnStock: true,
|
||
},
|
||
{
|
||
key: 'henan',
|
||
title: '河南客退数据监控',
|
||
shortName: '河南',
|
||
wmsCoId: 15539084,
|
||
warehouseName: '简尚-河南洛宁仓',
|
||
returnWarehouseDb: '简尚-河南洛宁仓销退仓',
|
||
// 2026-09-17 用户指定:河南客退数据取自「河南洛宁客退上架仓」
|
||
returnBinName: '河南洛宁客退上架仓',
|
||
// 该仓三个自定义库存位全为 0,河南洛宁客退上架仓在副本里取不到数据
|
||
returnSlot: 'customize_qty_1',
|
||
// 当前该仓客退库存为 0(仓库与退货单都在,只是当天没有客退在库)
|
||
hasReturnStock: false,
|
||
},
|
||
];
|
||
|
||
const DEFAULT_WAREHOUSE_KEY = 'henlin2';
|
||
|
||
const WAREHOUSE_MAP = new Map(WAREHOUSE_LIST.map((item) => [item.key, item]));
|
||
|
||
/** 归一化仓库标识:认 key(henlin2 / subei / ...),也容忍直接传 wms_co_id */
|
||
function resolveWarehouseKey(value) {
|
||
const raw = String(value === null || value === undefined ? '' : value).trim();
|
||
if (!raw) return DEFAULT_WAREHOUSE_KEY;
|
||
const byKey = WAREHOUSE_MAP.get(raw.toLowerCase());
|
||
if (byKey) return byKey.key;
|
||
const byId = WAREHOUSE_LIST.find((item) => String(item.wmsCoId) === raw);
|
||
return byId ? byId.key : DEFAULT_WAREHOUSE_KEY;
|
||
}
|
||
|
||
/** 取仓库配置(永远返回一个有效配置,非法值回落到默认仓) */
|
||
function resolveWarehouse(value) {
|
||
return WAREHOUSE_MAP.get(resolveWarehouseKey(value));
|
||
}
|
||
|
||
/**
|
||
* 页面/导出文案里的「数据来源」,例如
|
||
* 「箱及仓位库存 · 简尚-新苏北仓 · 新苏北客退上架仓」
|
||
* 仓位名逐仓不同(用户 2026-09-17 口径),必须读注册表,不能写死。
|
||
*/
|
||
function getWarehouseStockLabel(warehouse) {
|
||
return `箱及仓位库存 · ${warehouse.warehouseName} · ${warehouse.returnBinName}`;
|
||
}
|
||
|
||
module.exports = {
|
||
WAREHOUSE_LIST,
|
||
RETURN_SLOT_COLUMNS,
|
||
DEFAULT_WAREHOUSE_KEY,
|
||
resolveWarehouse,
|
||
resolveWarehouseKey,
|
||
getWarehouseStockLabel,
|
||
};
|