Merge pull request 'fix: 动销分析销量补齐组合装子件与全民客户(B2B)渠道' (#64) from codex/picking-bin-combo-qmcust-1014 into main
This commit is contained in:
commit
1d4eecb03c
@ -13,7 +13,10 @@
|
|||||||
* 库存数量:rpt.jst_packInventoryInfo 按 wms_co_id + 仓位 + sku_id 汇总 qty
|
* 库存数量:rpt.jst_packInventoryInfo 按 wms_co_id + 仓位 + sku_id 汇总 qty
|
||||||
* 统计范围:只统计真实拣选仓位(pack_type=Bin 且 bin_group 为空);
|
* 统计范围:只统计真实拣选仓位(pack_type=Bin 且 bin_group 为空);
|
||||||
* 箱记录(pack_type=Pack)与存货位(bin_group=存货)不纳入
|
* 箱记录(pack_type=Pack)与存货位(bin_group=存货)不纳入
|
||||||
* 销量: rpt.jst_Orders × rpt.jst_OrderItem,按 send_date,排除 Delete/Cancelled
|
* 销量: 4 个来源分支,均按 send_date 窗口(详见 services/pickingBinSalesAnalysis.js):
|
||||||
|
* ① jst_Orders 普通行;② jst_Orders 组合装行 → jst_comboProductItem 展开为子件;
|
||||||
|
* ③ jst_qmcust_orderlist(全民客户 B2B 分销)普通行(排除拆单父单 Split);
|
||||||
|
* ④ qmcust 组合装行 → 同样展开。
|
||||||
* 日销: 商品窗口销量 / 窗口天数(整档,不按仓位分摊)
|
* 日销: 商品窗口销量 / 窗口天数(整档,不按仓位分摊)
|
||||||
* 等级: 严格按商品日销判定,同一商品占多少个仓位都是同一等级
|
* 等级: 严格按商品日销判定,同一商品占多少个仓位都是同一等级
|
||||||
*
|
*
|
||||||
|
|||||||
@ -6,7 +6,12 @@
|
|||||||
* 库存:rpt.jst_packInventoryInfo 按 wms_co_id + 仓位 + sku_id 汇总 qty(仓位为空的行不参与)
|
* 库存:rpt.jst_packInventoryInfo 按 wms_co_id + 仓位 + sku_id 汇总 qty(仓位为空的行不参与)
|
||||||
* 范围:只统计真实拣选仓位(pack_type=Bin 且 bin_group 为空);
|
* 范围:只统计真实拣选仓位(pack_type=Bin 且 bin_group 为空);
|
||||||
* 箱记录(pack_type=Pack)与存货位(bin_group=存货)不纳入
|
* 箱记录(pack_type=Pack)与存货位(bin_group=存货)不纳入
|
||||||
* 销量:rpt.jst_Orders × rpt.jst_OrderItem,按 send_date,排除 Delete/Cancelled
|
* 销量(4 个来源分支,均按 send_date 窗口):
|
||||||
|
* 1) rpt.jst_Orders × jst_OrderItem 普通行(sku_type<>combine),排除 Delete/Cancelled
|
||||||
|
* 2) rpt.jst_Orders 组合装行(sku_id 为 ZH-... 拼接串)→ JOIN jst_comboProductItem 展开为子件×数量
|
||||||
|
* 3) rpt.jst_qmcust_orderlist(全民客户 B2B 分销)普通行,排除 Delete/Cancelled/Split/Question
|
||||||
|
* (Split 是拆单父单,与拆出的子单重复,必须排除)
|
||||||
|
* 4) rpt.jst_qmcust_orderlist 组合装行 → 同样按映射表展开
|
||||||
* 粒度:仓位 × 商品编码(一个仓位有 N 个商品就输出 N 行),列表与导出共用
|
* 粒度:仓位 × 商品编码(一个仓位有 N 个商品就输出 N 行),列表与导出共用
|
||||||
*
|
*
|
||||||
* 【日销与动销等级口径】按「商品日销」判定,不做仓位分摊:
|
* 【日销与动销等级口径】按「商品日销」判定,不做仓位分摊:
|
||||||
@ -110,6 +115,7 @@ async function fetchPickingBinSkuRows(cloudDb, wmsCoIds, options = {}) {
|
|||||||
GROUP BY wmsCoId, skuId, binType
|
GROUP BY wmsCoId, skuId, binType
|
||||||
),
|
),
|
||||||
ship AS (
|
ship AS (
|
||||||
|
-- 1) 线上店铺订单:普通商品行(sku_type<>combine,组合装行在下一分支展开)
|
||||||
SELECT o.wms_co_id AS wmsCoId,
|
SELECT o.wms_co_id AS wmsCoId,
|
||||||
oi.sku_id AS skuId,
|
oi.sku_id AS skuId,
|
||||||
SUM(CAST(ISNULL(oi.qty, 0) AS DECIMAL(18, 2))) AS salesQty
|
SUM(CAST(ISNULL(oi.qty, 0) AS DECIMAL(18, 2))) AS salesQty
|
||||||
@ -119,7 +125,49 @@ async function fetchPickingBinSkuRows(cloudDb, wmsCoIds, options = {}) {
|
|||||||
AND o.send_date >= @shipFrom
|
AND o.send_date >= @shipFrom
|
||||||
AND o.send_date < @shipTo
|
AND o.send_date < @shipTo
|
||||||
AND ISNULL(o.status, '') NOT IN ('Delete', 'Cancelled')
|
AND ISNULL(o.status, '') NOT IN ('Delete', 'Cancelled')
|
||||||
|
AND ISNULL(oi.sku_type, 'normal') <> 'combine'
|
||||||
GROUP BY o.wms_co_id, oi.sku_id
|
GROUP BY o.wms_co_id, oi.sku_id
|
||||||
|
UNION ALL
|
||||||
|
-- 2) 线上店铺订单:组合装行(sku_id 为 ZH-子件A-N-子件B-M 拼接串)→ 按映射表展开为子件
|
||||||
|
SELECT o.wms_co_id AS wmsCoId,
|
||||||
|
ci.src_sku_id AS skuId,
|
||||||
|
SUM(CAST(ISNULL(oi.qty, 0) * ISNULL(ci.qty, 0) AS DECIMAL(18, 2))) AS salesQty
|
||||||
|
FROM rpt.jst_OrderItem oi
|
||||||
|
JOIN rpt.jst_Orders o ON o.o_id = oi.o_id
|
||||||
|
JOIN rpt.jst_comboProductItem ci ON ci.combine_sku_id = oi.sku_id
|
||||||
|
WHERE o.wms_co_id IN (${shipIn.clause})
|
||||||
|
AND o.send_date >= @shipFrom
|
||||||
|
AND o.send_date < @shipTo
|
||||||
|
AND ISNULL(o.status, '') NOT IN ('Delete', 'Cancelled')
|
||||||
|
AND oi.sku_type = 'combine'
|
||||||
|
GROUP BY o.wms_co_id, ci.src_sku_id
|
||||||
|
UNION ALL
|
||||||
|
-- 3) 全民客户(B2B 分销)订单:普通商品行(拆单父单 Split 与子单重复,需排除)
|
||||||
|
SELECT o.wms_co_id AS wmsCoId,
|
||||||
|
oi.sku_id AS skuId,
|
||||||
|
SUM(CAST(ISNULL(oi.qty, 0) AS DECIMAL(18, 2))) AS salesQty
|
||||||
|
FROM rpt.jst_qmcust_orderlist_item oi
|
||||||
|
JOIN rpt.jst_qmcust_orderlist o ON o.o_id = oi.o_id
|
||||||
|
WHERE o.wms_co_id IN (${shipIn.clause})
|
||||||
|
AND o.send_date >= @shipFrom
|
||||||
|
AND o.send_date < @shipTo
|
||||||
|
AND ISNULL(o.status, '') NOT IN ('Delete', 'Cancelled', 'Split', 'Question')
|
||||||
|
AND ISNULL(oi.sku_type, 'normal') <> 'combine'
|
||||||
|
GROUP BY o.wms_co_id, oi.sku_id
|
||||||
|
UNION ALL
|
||||||
|
-- 4) 全民客户订单:组合装行展开为子件
|
||||||
|
SELECT o.wms_co_id AS wmsCoId,
|
||||||
|
ci.src_sku_id AS skuId,
|
||||||
|
SUM(CAST(ISNULL(oi.qty, 0) * ISNULL(ci.qty, 0) AS DECIMAL(18, 2))) AS salesQty
|
||||||
|
FROM rpt.jst_qmcust_orderlist_item oi
|
||||||
|
JOIN rpt.jst_qmcust_orderlist o ON o.o_id = oi.o_id
|
||||||
|
JOIN rpt.jst_comboProductItem ci ON ci.combine_sku_id = oi.sku_id
|
||||||
|
WHERE o.wms_co_id IN (${shipIn.clause})
|
||||||
|
AND o.send_date >= @shipFrom
|
||||||
|
AND o.send_date < @shipTo
|
||||||
|
AND ISNULL(o.status, '') NOT IN ('Delete', 'Cancelled', 'Split', 'Question')
|
||||||
|
AND oi.sku_type = 'combine'
|
||||||
|
GROUP BY o.wms_co_id, ci.src_sku_id
|
||||||
),
|
),
|
||||||
binAgg AS (
|
binAgg AS (
|
||||||
SELECT wmsCoId,
|
SELECT wmsCoId,
|
||||||
|
|||||||
@ -137,6 +137,25 @@ test('fetchPickingBinSkuRows 只统计真实拣选仓位,排除 Pack 箱记录
|
|||||||
assert.equal(captured.params.binTypes, undefined);
|
assert.equal(captured.params.binTypes, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('销量覆盖 4 个来源:线上普通行 + 组合装展开 + 全民客户普通行 + 全民客户组合装展开', async () => {
|
||||||
|
let captured = null;
|
||||||
|
const db = {
|
||||||
|
query: async (sqlText, params) => { captured = { sqlText, params }; return []; },
|
||||||
|
};
|
||||||
|
await fetchPickingBinSkuRows(db, [101], { shipFrom: 'a', shipTo: 'b', days: 30 });
|
||||||
|
|
||||||
|
// ① 线上店铺订单普通行
|
||||||
|
assert.ok(captured.sqlText.includes('FROM rpt.jst_OrderItem'), '缺少 jst_OrderItem 普通行分支');
|
||||||
|
// ② 线上订单组合装行按映射表展开为子件
|
||||||
|
assert.ok(captured.sqlText.includes('JOIN rpt.jst_comboProductItem'), '缺少组合装展开分支');
|
||||||
|
assert.ok(captured.sqlText.includes("oi.sku_type = 'combine'"), '缺少组合装行识别条件');
|
||||||
|
// ③ 全民客户(B2B 分销)渠道普通行,排除拆单父单 Split 与问题单
|
||||||
|
assert.ok(captured.sqlText.includes('FROM rpt.jst_qmcust_orderlist_item'), '缺少 qmcust 分支');
|
||||||
|
assert.ok(captured.sqlText.includes("'Split', 'Question'"), 'qmcust 未排除拆单父单/问题单');
|
||||||
|
// 四个分支共用同一发货窗口
|
||||||
|
assert.equal(captured.sqlText.split('o.send_date >= @shipFrom').length, 5);
|
||||||
|
});
|
||||||
|
|
||||||
test('gradeKey 缺失时兜底为 zero', async () => {
|
test('gradeKey 缺失时兜底为 zero', async () => {
|
||||||
const db = { query: async () => [{ wmsCoId: 101, binName: 'X', binType: '拣选', skuId: 'S1', stockQty: 5 }] };
|
const db = { query: async () => [{ wmsCoId: 101, binName: 'X', binType: '拣选', skuId: 'S1', stockQty: 5 }] };
|
||||||
const rows = await fetchPickingBinSkuRows(db, [101], { shipFrom: 'a', shipTo: 'b', days: 15 });
|
const rows = await fetchPickingBinSkuRows(db, [101], { shipFrom: 'a', shipTo: 'b', days: 15 });
|
||||||
|
|||||||
@ -29,7 +29,7 @@ export const PICKING_BIN_COLUMNS = [
|
|||||||
{
|
{
|
||||||
key: 'salesQty',
|
key: 'salesQty',
|
||||||
label: '商品销量',
|
label: '商品销量',
|
||||||
tip: '近 N 天该商品的销量(整档,不按仓位分摊):同一商品占几个仓位,这几行显示的销量都一样,所以这一列不能直接按行求和',
|
tip: '近 N 天该商品的销量(整档,不按仓位分摊):含线上订单、组合装子件拆分、全民客户(B2B分销)渠道;同一商品占几个仓位,这几行显示的销量都一样,所以这一列不能直接按行求和',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'dailySales',
|
key: 'dailySales',
|
||||||
@ -85,6 +85,7 @@ export const PICKING_BIN_DESCRIPTION = [
|
|||||||
' 注意它小于全仓销量:jst_packInventoryInfo 只覆盖部分 SKU 的仓位数据,无仓位记录的 SKU 销量不计入任何库位。',
|
' 注意它小于全仓销量:jst_packInventoryInfo 只覆盖部分 SKU 的仓位数据,无仓位记录的 SKU 销量不计入任何库位。',
|
||||||
'',
|
'',
|
||||||
'数据来源:库存取 rpt.jst_packInventoryInfo(按仓库 + 仓位 + 商品汇总);',
|
'数据来源:库存取 rpt.jst_packInventoryInfo(按仓库 + 仓位 + 商品汇总);',
|
||||||
'销量取 rpt.jst_Orders × rpt.jst_OrderItem(按 send_date,排除 Delete/Cancelled),与「每日进销存」发货口径一致。',
|
'销量按 send_date 窗口统计 4 个来源:① jst_Orders 普通商品行;② jst_Orders 组合装行(ZH-拼接编码)按组合装映射展开为子件;',
|
||||||
|
'③ jst_qmcust_orderlist 全民客户(B2B分销)普通行(排除拆单父单 Split);④ qmcust 组合装行同样展开。',
|
||||||
'统计范围:只取 pack_type=Bin 且 bin_group 为空的真实拣选仓位,箱记录(pack_type=Pack)和存货位(bin_group=存货)不参与统计。',
|
'统计范围:只取 pack_type=Bin 且 bin_group 为空的真实拣选仓位,箱记录(pack_type=Pack)和存货位(bin_group=存货)不参与统计。',
|
||||||
];
|
];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user