From fa0e604bf88316f233a8d42dc8fb1c77a6e69fed Mon Sep 17 00:00:00 2001 From: 134 Date: Mon, 12 Oct 2026 14:31:45 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=8E=92=E9=99=A4=E6=8B=A3=E9=80=89?= =?UTF-8?q?=E4=BD=8D=E6=8A=A5=E8=A1=A8=E4=B8=AD=E7=9A=84=E7=AE=B1=E8=AE=B0?= =?UTF-8?q?=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/config/pickingBinSalesAnalysis.js | 8 +++++--- backend/src/services/pickingBinSalesAnalysis.js | 8 +++++--- backend/test/pickingBinSalesAnalysis.test.js | 8 +++++--- frontend/src/constants/pickingBinSalesAnalysis.js | 6 +++--- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/backend/src/config/pickingBinSalesAnalysis.js b/backend/src/config/pickingBinSalesAnalysis.js index aac70a9..5097f69 100644 --- a/backend/src/config/pickingBinSalesAnalysis.js +++ b/backend/src/config/pickingBinSalesAnalysis.js @@ -11,7 +11,8 @@ * * 口径: * 库存数量: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 * 日销: 商品窗口销量 / 窗口天数(整档,不按仓位分摊) * 等级: 严格按商品日销判定,同一商品占多少个仓位都是同一等级 @@ -36,8 +37,9 @@ const PICKING_BIN_WINDOW_OPTIONS = [7, 15, 30, 60]; const PICKING_BIN_DEFAULT_DAYS = 30; /** - * 仓位类型:本功能只统计拣选位(业务库 bin_group 为空即拣选位)。 - * 存货位(bin_group=存货)不纳入动销等级统计,取数在仓位库存表上直接过滤, + * 仓位类型:本功能只统计真实拣选仓位(pack_type=Bin 且 bin_group 为空)。 + * 箱记录(pack_type=Pack)和存货位(bin_group=存货)不纳入动销等级统计, + * 取数在仓位库存表上直接过滤, * 页面也不再提供类型切换。 */ const BIN_TYPE_PICKING = '拣选'; diff --git a/backend/src/services/pickingBinSalesAnalysis.js b/backend/src/services/pickingBinSalesAnalysis.js index 0c9f946..9eaec7e 100644 --- a/backend/src/services/pickingBinSalesAnalysis.js +++ b/backend/src/services/pickingBinSalesAnalysis.js @@ -4,7 +4,8 @@ * 拣选库位动销分析 —— 业务库取数 * * 库存: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 * 粒度:仓位 × 商品编码(一个仓位有 N 个商品就输出 N 行),列表与导出共用 * @@ -84,8 +85,8 @@ async function fetchPickingBinSkuRows(cloudDb, wmsCoIds, options = {}) { const binIn = buildInClause(wmsCoIds, 'w'); const shipIn = buildInClause(wmsCoIds, 'v'); - // 统计范围固定为拣选位:直接在仓位库存表上过滤 bin_group 为空, - // 存货位(bin_group=存货)不参与动销等级统计。 + // 统计范围固定为真实拣选仓位:pack_type=Bin 且 bin_group 为空。 + // 仅过滤 bin_group 会把同样为空的 Pack 箱记录误标为拣选位。 const sqlText = ` WITH bin AS ( 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}) AND ISNULL(p.bin, '') <> '' AND ISNULL(p.qty, 0) <> 0 + AND p.pack_type = N'Bin' AND ISNULL(p.bin_group, '') = '' GROUP BY p.wms_co_id, LTRIM(RTRIM(p.bin)), diff --git a/backend/test/pickingBinSalesAnalysis.test.js b/backend/test/pickingBinSalesAnalysis.test.js index ccffa0a..3e923e5 100644 --- a/backend/test/pickingBinSalesAnalysis.test.js +++ b/backend/test/pickingBinSalesAnalysis.test.js @@ -116,19 +116,21 @@ test('fetchPickingBinSkuRows 按仓位×商品输出,等级与日销来自商 // 商品销量按占用仓位数分摊(供求和),但等级依据的是未分摊的商品销量 assert.ok(captured.sqlText.includes('/ NULLIF(sp.binCount, 0)')); 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.qty, 0) <> 0')); 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; const db = { query: async (sqlText, params) => { captured = { sqlText, params }; return []; }, }; 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('b.binType IN')); assert.equal(captured.params.t0, undefined); diff --git a/frontend/src/constants/pickingBinSalesAnalysis.js b/frontend/src/constants/pickingBinSalesAnalysis.js index d569d6d..10e40ae 100644 --- a/frontend/src/constants/pickingBinSalesAnalysis.js +++ b/frontend/src/constants/pickingBinSalesAnalysis.js @@ -14,7 +14,7 @@ export const PICKING_BIN_COLUMNS = [ { key: 'binType', label: '类型', - tip: '统计范围固定为拣选位(bin_group 为空的仓位);存货位不纳入动销分析,报表中不会出现', + tip: '统计范围固定为真实拣选仓位(pack_type=Bin 且 bin_group 为空);箱记录和存货位不纳入动销分析', }, { key: 'skuId', @@ -63,7 +63,7 @@ export const PICKING_BIN_GRADE_COLORS = { }; export const PICKING_BIN_DESCRIPTION = [ - '【统计范围】只统计拣选位(bin_group 为空的仓位);存货位不纳入动销分析。', + '【统计范围】只统计真实拣选仓位(pack_type=Bin 且 bin_group 为空);箱记录(pack_type=Pack)和存货位不纳入动销分析。', '', '【粒度】一行 = 一个「仓位 × 商品编码」组合。一个仓位放了 N 个商品就显示 N 行,列表与导出一致。', ' 库存数量 = 该商品在这个仓位上的实际库存(同一仓位多行相加 = 仓位总库存)。', @@ -86,5 +86,5 @@ export const PICKING_BIN_DESCRIPTION = [ '', '数据来源:库存取 rpt.jst_packInventoryInfo(按仓库 + 仓位 + 商品汇总);', '销量取 rpt.jst_Orders × rpt.jst_OrderItem(按 send_date,排除 Delete/Cancelled),与「每日进销存」发货口径一致。', - '统计范围:只取 bin_group 为空的拣选位,存货位(bin_group=存货)不参与统计。', + '统计范围:只取 pack_type=Bin 且 bin_group 为空的真实拣选仓位,箱记录(pack_type=Pack)和存货位(bin_group=存货)不参与统计。', ];