Merge pull request 'fix: 排除拣选位报表中的箱记录' (#63) from codex/picking-bin-pack-filter-1012 into main

This commit is contained in:
lunanting 2026-10-12 14:39:06 +08:00
commit c6c7feaeea
4 changed files with 18 additions and 12 deletions

View File

@ -11,7 +11,8 @@
* *
* 口径: * 口径:
* 库存数量:rpt.jst_packInventoryInfo 按 wms_co_id + 仓位 + sku_id 汇总 qty * 库存数量:rpt.jst_packInventoryInfo 按 wms_co_id + 仓位 + sku_id 汇总 qty
* 统计范围:只统计拣选位(bin_group 为空);存货位(bin_group=存货)不纳入 * 统计范围:只统计真实拣选仓位(pack_type=Bin 且 bin_group 为空);
* 箱记录(pack_type=Pack)与存货位(bin_group=存货)不纳入
* 销量: rpt.jst_Orders × rpt.jst_OrderItem,按 send_date,排除 Delete/Cancelled * 销量: rpt.jst_Orders × rpt.jst_OrderItem,按 send_date,排除 Delete/Cancelled
* 日销: 商品窗口销量 / 窗口天数(整档,不按仓位分摊) * 日销: 商品窗口销量 / 窗口天数(整档,不按仓位分摊)
* 等级: 严格按商品日销判定,同一商品占多少个仓位都是同一等级 * 等级: 严格按商品日销判定,同一商品占多少个仓位都是同一等级
@ -36,8 +37,9 @@ const PICKING_BIN_WINDOW_OPTIONS = [7, 15, 30, 60];
const PICKING_BIN_DEFAULT_DAYS = 30; const PICKING_BIN_DEFAULT_DAYS = 30;
/** /**
* 仓位类型:本功能只统计拣选位(业务库 bin_group 为空即拣选位)。 * 仓位类型:本功能只统计真实拣选仓位(pack_type=Bin 且 bin_group 为空)。
* 存货位(bin_group=存货)不纳入动销等级统计,取数在仓位库存表上直接过滤, * 箱记录(pack_type=Pack)和存货位(bin_group=存货)不纳入动销等级统计,
* 取数在仓位库存表上直接过滤,
* 页面也不再提供类型切换。 * 页面也不再提供类型切换。
*/ */
const BIN_TYPE_PICKING = '拣选'; const BIN_TYPE_PICKING = '拣选';

View File

@ -4,7 +4,8 @@
* 拣选库位动销分析 —— 业务库取数 * 拣选库位动销分析 —— 业务库取数
* *
* 库存:rpt.jst_packInventoryInfo 按 wms_co_id + 仓位 + sku_id 汇总 qty(仓位为空的行不参与) * 库存:rpt.jst_packInventoryInfo 按 wms_co_id + 仓位 + sku_id 汇总 qty(仓位为空的行不参与)
* 范围:只统计拣选位(bin_group 为空);存货位(bin_group=存货)不纳入 * 范围:只统计真实拣选仓位(pack_type=Bin 且 bin_group 为空);
* 箱记录(pack_type=Pack)与存货位(bin_group=存货)不纳入
* 销量:rpt.jst_Orders × rpt.jst_OrderItem,按 send_date,排除 Delete/Cancelled * 销量:rpt.jst_Orders × rpt.jst_OrderItem,按 send_date,排除 Delete/Cancelled
* 粒度:仓位 × 商品编码(一个仓位有 N 个商品就输出 N 行),列表与导出共用 * 粒度:仓位 × 商品编码(一个仓位有 N 个商品就输出 N 行),列表与导出共用
* *
@ -84,8 +85,8 @@ async function fetchPickingBinSkuRows(cloudDb, wmsCoIds, options = {}) {
const binIn = buildInClause(wmsCoIds, 'w'); const binIn = buildInClause(wmsCoIds, 'w');
const shipIn = buildInClause(wmsCoIds, 'v'); const shipIn = buildInClause(wmsCoIds, 'v');
// 统计范围固定为拣选位:直接在仓位库存表上过滤 bin_group 为空, // 统计范围固定为真实拣选仓位:pack_type=Bin 且 bin_group 为空。
// 存货位(bin_group=存货)不参与动销等级统计。 // 仅过滤 bin_group 会把同样为空的 Pack 箱记录误标为拣选位。
const sqlText = ` const sqlText = `
WITH bin AS ( WITH bin AS (
SELECT p.wms_co_id AS wmsCoId, SELECT p.wms_co_id AS wmsCoId,
@ -97,6 +98,7 @@ async function fetchPickingBinSkuRows(cloudDb, wmsCoIds, options = {}) {
WHERE p.wms_co_id IN (${binIn.clause}) WHERE p.wms_co_id IN (${binIn.clause})
AND ISNULL(p.bin, '') <> '' AND ISNULL(p.bin, '') <> ''
AND ISNULL(p.qty, 0) <> 0 AND ISNULL(p.qty, 0) <> 0
AND p.pack_type = N'Bin'
AND ISNULL(p.bin_group, '') = '' AND ISNULL(p.bin_group, '') = ''
GROUP BY p.wms_co_id, GROUP BY p.wms_co_id,
LTRIM(RTRIM(p.bin)), LTRIM(RTRIM(p.bin)),

View File

@ -116,19 +116,21 @@ test('fetchPickingBinSkuRows 按仓位×商品输出,等级与日销来自商
// 商品销量按占用仓位数分摊(供求和),但等级依据的是未分摊的商品销量 // 商品销量按占用仓位数分摊(供求和),但等级依据的是未分摊的商品销量
assert.ok(captured.sqlText.includes('/ NULLIF(sp.binCount, 0)')); assert.ok(captured.sqlText.includes('/ NULLIF(sp.binCount, 0)'));
assert.ok(captured.sqlText.includes('ISNULL(s.salesQty, 0) / @days AS DECIMAL(18, 4)')); assert.ok(captured.sqlText.includes('ISNULL(s.salesQty, 0) / @days AS DECIMAL(18, 4)'));
// 统计范围固定为拣选位:bin_group 为空;只统计有库存的行 // 统计范围固定为真实拣选仓位:pack_type=Bin 且 bin_group 为空;只统计有库存的行
assert.ok(captured.sqlText.includes("p.pack_type = N'Bin'"));
assert.ok(captured.sqlText.includes("ISNULL(p.bin_group, '') = ''")); assert.ok(captured.sqlText.includes("ISNULL(p.bin_group, '') = ''"));
assert.ok(captured.sqlText.includes('ISNULL(p.qty, 0) <> 0')); assert.ok(captured.sqlText.includes('ISNULL(p.qty, 0) <> 0'));
assert.ok(captured.sqlText.includes('HAVING ABS(SUM(CAST(b.binSkuQty AS DECIMAL(18, 2)))) > 0.0001')); assert.ok(captured.sqlText.includes('HAVING ABS(SUM(CAST(b.binSkuQty AS DECIMAL(18, 2)))) > 0.0001'));
}); });
test('fetchPickingBinSkuRows 只统计拣选位,不带类型参数', async () => { test('fetchPickingBinSkuRows 只统计真实拣选仓位,排除 Pack 箱记录且不带类型参数', async () => {
let captured = null; let captured = null;
const db = { const db = {
query: async (sqlText, params) => { captured = { sqlText, params }; return []; }, query: async (sqlText, params) => { captured = { sqlText, params }; return []; },
}; };
await fetchPickingBinSkuRows(db, [101], { shipFrom: 'a', shipTo: 'b', days: 15 }); await fetchPickingBinSkuRows(db, [101], { shipFrom: 'a', shipTo: 'b', days: 15 });
// 存货位(bin_group=存货)被排除,且不再有 binType IN (...) 的可变过滤 // Pack 箱记录和存货位被排除,且不再有 binType IN (...) 的可变过滤
assert.ok(captured.sqlText.includes("p.pack_type = N'Bin'"));
assert.ok(captured.sqlText.includes("ISNULL(p.bin_group, '') = ''")); assert.ok(captured.sqlText.includes("ISNULL(p.bin_group, '') = ''"));
assert.ok(!captured.sqlText.includes('b.binType IN')); assert.ok(!captured.sqlText.includes('b.binType IN'));
assert.equal(captured.params.t0, undefined); assert.equal(captured.params.t0, undefined);

View File

@ -14,7 +14,7 @@ export const PICKING_BIN_COLUMNS = [
{ {
key: 'binType', key: 'binType',
label: '类型', label: '类型',
tip: '统计范围固定为拣选位(bin_group 为空的仓位);存货位不纳入动销分析,报表中不会出现', tip: '统计范围固定为真实拣选仓位(pack_type=Bin 且 bin_group 为空);箱记录和存货位不纳入动销分析',
}, },
{ {
key: 'skuId', key: 'skuId',
@ -63,7 +63,7 @@ export const PICKING_BIN_GRADE_COLORS = {
}; };
export const PICKING_BIN_DESCRIPTION = [ export const PICKING_BIN_DESCRIPTION = [
'【统计范围】只统计拣选位(bin_group 为空的仓位);存货位不纳入动销分析。', '【统计范围】只统计真实拣选仓位(pack_type=Bin 且 bin_group 为空);箱记录(pack_type=Pack)和存货位不纳入动销分析。',
'', '',
'【粒度】一行 = 一个「仓位 × 商品编码」组合。一个仓位放了 N 个商品就显示 N 行,列表与导出一致。', '【粒度】一行 = 一个「仓位 × 商品编码」组合。一个仓位放了 N 个商品就显示 N 行,列表与导出一致。',
' 库存数量 = 该商品在这个仓位上的实际库存(同一仓位多行相加 = 仓位总库存)。', ' 库存数量 = 该商品在这个仓位上的实际库存(同一仓位多行相加 = 仓位总库存)。',
@ -86,5 +86,5 @@ export const PICKING_BIN_DESCRIPTION = [
'', '',
'数据来源:库存取 rpt.jst_packInventoryInfo(按仓库 + 仓位 + 商品汇总);', '数据来源:库存取 rpt.jst_packInventoryInfo(按仓库 + 仓位 + 商品汇总);',
'销量取 rpt.jst_Orders × rpt.jst_OrderItem(按 send_date,排除 Delete/Cancelled),与「每日进销存」发货口径一致。', '销量取 rpt.jst_Orders × rpt.jst_OrderItem(按 send_date,排除 Delete/Cancelled),与「每日进销存」发货口径一致。',
'统计范围:只取 bin_group 为空的拣选位,存货位(bin_group=存货)不参与统计。', '统计范围:只取 pack_type=Bin 且 bin_group 为空的真实拣选仓位,箱记录(pack_type=Pack)和存货位(bin_group=存货)不参与统计。',
]; ];