fix: use pack inventory for BOM locations
This commit is contained in:
parent
6c0097753f
commit
3a87d8ecba
@ -39,9 +39,8 @@
|
|||||||
//
|
//
|
||||||
// ⑤ 配件仓位 —— 业务库 rpt.jst_packInventoryInfo;
|
// ⑤ 配件仓位 —— 业务库 rpt.jst_packInventoryInfo;
|
||||||
// sku_id 可能带“-包装序号”后缀,需按基础配件编码匹配。优先显示 bin_group / bin,
|
// sku_id 可能带“-包装序号”后缀,需按基础配件编码匹配。优先显示 bin_group / bin,
|
||||||
// 仓位为空时显示净数量大于 0 的 pack_id(箱号)与实际仓库名称。
|
// 仓位为空时显示净数量大于 0 的 pack_id(箱号)与 wh_id(仓库)。
|
||||||
// 尚未分箱时使用 rpt.jst_inventoryInfo 的正库存仓库;连库存记录也没有时,
|
// 该列只使用箱及仓位库存表,不用库存仓库或商品档案仓库冒充仓位。
|
||||||
// 回退到 jst_sku.other_3 的商品档案仓库,并明确标注没有箱号,避免误报具体仓位。
|
|
||||||
//
|
//
|
||||||
// ⑥ 配件库存 —— 业务库 rpt.jst_inventoryInfo 按 sku_id 汇总。
|
// ⑥ 配件库存 —— 业务库 rpt.jst_inventoryInfo 按 sku_id 汇总。
|
||||||
|
|
||||||
@ -555,37 +554,6 @@ function buildPackLocationMap(rows, componentCodes) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildInventoryWarehouseMap(rows, componentCodes) {
|
|
||||||
const positiveWarehouses = new Map(componentCodes.map((code) => [code, new Set()]));
|
|
||||||
const defaultWarehouses = new Map();
|
|
||||||
|
|
||||||
rows.forEach((row) => {
|
|
||||||
const componentCode = componentCodes.find((code) => normalizeCode(code) === normalizeCode(row.skuId));
|
|
||||||
if (!componentCode) return;
|
|
||||||
|
|
||||||
const defaultWarehouse = toText(row.defaultWarehouse);
|
|
||||||
if (defaultWarehouse) defaultWarehouses.set(componentCode, defaultWarehouse);
|
|
||||||
|
|
||||||
const warehouseName = toText(row.warehouseName);
|
|
||||||
if (warehouseName && (toNumber(row.availableQty) ?? 0) > 0) {
|
|
||||||
positiveWarehouses.get(componentCode).add(warehouseName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = new Map();
|
|
||||||
componentCodes.forEach((code) => {
|
|
||||||
const warehouses = [...positiveWarehouses.get(code)];
|
|
||||||
if (warehouses.length > 0) {
|
|
||||||
result.set(code, `${warehouses.join('、')}(未分箱)`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultWarehouse = defaultWarehouses.get(code);
|
|
||||||
if (defaultWarehouse) result.set(code, `${defaultWarehouse}(暂无箱号)`);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 列表:指定仓库的 MKH 模块化成品 + BOM 配件种类/数量。
|
* 列表:指定仓库的 MKH 模块化成品 + BOM 配件种类/数量。
|
||||||
* BOM 数据只做明细匹配,不向商品列表追加无仓库档案的编码。
|
* BOM 数据只做明细匹配,不向商品列表追加无仓库档案的编码。
|
||||||
@ -725,18 +693,10 @@ async function getMkhBomDetail(mkhSkuId) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const stockRows = await cloudDatabase.query(
|
const stockRows = await cloudDatabase.query(
|
||||||
`SELECT s.sku_id AS skuId,
|
`SELECT i.sku_id AS skuId, SUM(i.qty) AS qty
|
||||||
s.other_3 AS defaultWarehouse,
|
FROM rpt.jst_inventoryInfo i WITH (NOLOCK)
|
||||||
w.name AS warehouseName,
|
WHERE i.sku_id IN (${placeholders})
|
||||||
SUM(COALESCE(i.qty, 0)) AS qty,
|
GROUP BY i.sku_id`,
|
||||||
SUM(COALESCE(i.qty, 0)
|
|
||||||
- COALESCE(i.order_lock, 0)
|
|
||||||
- COALESCE(i.pick_lock, 0)) AS availableQty
|
|
||||||
FROM dbo.jst_sku s WITH (NOLOCK)
|
|
||||||
LEFT JOIN rpt.jst_inventoryInfo i WITH (NOLOCK) ON i.sku_id = s.sku_id
|
|
||||||
LEFT JOIN rpt.jst_warehouseInfo w WITH (NOLOCK) ON w.wms_co_id = i.wms_co_id
|
|
||||||
WHERE s.sku_id IN (${placeholders})
|
|
||||||
GROUP BY s.sku_id, s.other_3, i.wms_co_id, w.name`,
|
|
||||||
codeParams,
|
codeParams,
|
||||||
{ requestTimeout: 180000 }
|
{ requestTimeout: 180000 }
|
||||||
);
|
);
|
||||||
@ -745,9 +705,6 @@ async function getMkhBomDetail(mkhSkuId) {
|
|||||||
if (!code) return;
|
if (!code) return;
|
||||||
stockMap.set(code, (stockMap.get(code) || 0) + (toNumber(row.qty) ?? 0));
|
stockMap.set(code, (stockMap.get(code) || 0) + (toNumber(row.qty) ?? 0));
|
||||||
});
|
});
|
||||||
buildInventoryWarehouseMap(stockRows, uncachedCodes).forEach((location, code) => {
|
|
||||||
locationMap.set(code, location);
|
|
||||||
});
|
|
||||||
stockQuerySucceeded = true;
|
stockQuerySucceeded = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[MkhBom] 配件库存查询降级:', error.message);
|
console.warn('[MkhBom] 配件库存查询降级:', error.message);
|
||||||
@ -763,15 +720,13 @@ async function getMkhBomDetail(mkhSkuId) {
|
|||||||
const locRows = await cloudDatabase.query(
|
const locRows = await cloudDatabase.query(
|
||||||
`SELECT p.sku_id AS skuId,
|
`SELECT p.sku_id AS skuId,
|
||||||
p.pack_id AS packId,
|
p.pack_id AS packId,
|
||||||
p.wms_co_id AS warehouseId,
|
p.wh_id AS warehouseId,
|
||||||
w.name AS warehouseName,
|
|
||||||
p.bin AS bin,
|
p.bin AS bin,
|
||||||
p.bin_group AS binGroup,
|
p.bin_group AS binGroup,
|
||||||
SUM(p.qty) AS qty
|
SUM(p.qty) AS qty
|
||||||
FROM rpt.jst_packInventoryInfo p WITH (NOLOCK)
|
FROM rpt.jst_packInventoryInfo p WITH (NOLOCK)
|
||||||
LEFT JOIN rpt.jst_warehouseInfo w WITH (NOLOCK) ON w.wms_co_id = p.wms_co_id
|
|
||||||
WHERE ${locationConditions.join(' OR ')}
|
WHERE ${locationConditions.join(' OR ')}
|
||||||
GROUP BY p.sku_id, p.pack_id, p.wms_co_id, w.name, p.bin, p.bin_group
|
GROUP BY p.sku_id, p.pack_id, p.wh_id, p.bin, p.bin_group
|
||||||
HAVING SUM(p.qty) > 0`,
|
HAVING SUM(p.qty) > 0`,
|
||||||
locationParams,
|
locationParams,
|
||||||
{ requestTimeout: 120000 }
|
{ requestTimeout: 120000 }
|
||||||
@ -831,6 +786,5 @@ module.exports = {
|
|||||||
matchesPackSku,
|
matchesPackSku,
|
||||||
formatPackLocation,
|
formatPackLocation,
|
||||||
buildPackLocationMap,
|
buildPackLocationMap,
|
||||||
buildInventoryWarehouseMap,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -155,27 +155,17 @@ test('pack inventory locations match a BOM component and its package suffixes',
|
|||||||
test('pack inventory location prefers bins and falls back to positive-stock box ids', () => {
|
test('pack inventory location prefers bins and falls back to positive-stock box ids', () => {
|
||||||
const locations = _private.buildPackLocationMap([
|
const locations = _private.buildPackLocationMap([
|
||||||
{
|
{
|
||||||
skuId: 'PART-1-1', warehouseId: 1, warehouseName: '半成品仓', binGroup: '成品区', bin: 'A-01', packId: 'BOX-1', qty: 2,
|
skuId: 'PART-1-1', warehouseId: 1, binGroup: '成品区', bin: 'A-01', packId: 'BOX-1', qty: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
skuId: 'PART-1-2', warehouseId: 1, binGroup: null, bin: '', packId: 'BOX-2', qty: 1,
|
skuId: 'PART-1-2', warehouseId: 1, binGroup: null, bin: '', packId: 'BOX-2', qty: 1,
|
||||||
},
|
},
|
||||||
], ['PART-1']);
|
], ['PART-1']);
|
||||||
|
|
||||||
assert.equal(locations.get('PART-1'), '半成品仓 · 成品区 / A-01、仓库 1 · 箱号 BOX-2');
|
assert.equal(locations.get('PART-1'), '仓库 1 · 成品区 / A-01、仓库 1 · 箱号 BOX-2');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('inventory warehouse fallback distinguishes unboxed stock from a warehouse with no box record', () => {
|
test('components without pack inventory rows do not receive a fallback warehouse location', () => {
|
||||||
const locations = _private.buildInventoryWarehouseMap([
|
const locations = _private.buildPackLocationMap([], ['PART-1']);
|
||||||
{
|
assert.equal(locations.has('PART-1'), false);
|
||||||
skuId: 'PART-1', defaultWarehouse: '默认半成品仓', warehouseName: '实际库存仓', availableQty: 12,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
skuId: 'PART-2', defaultWarehouse: '默认半成品仓', warehouseName: '实际库存仓', availableQty: 0,
|
|
||||||
},
|
|
||||||
], ['PART-1', 'PART-2', 'PART-3']);
|
|
||||||
|
|
||||||
assert.equal(locations.get('PART-1'), '实际库存仓(未分箱)');
|
|
||||||
assert.equal(locations.get('PART-2'), '默认半成品仓(暂无箱号)');
|
|
||||||
assert.equal(locations.has('PART-3'), false);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -84,7 +84,7 @@ const PrintPreview = ({ data, onClose }) => {
|
|||||||
<div className="mkh-print-foot">
|
<div className="mkh-print-foot">
|
||||||
<span>
|
<span>
|
||||||
MKH 商品编码取自指定仓库商品主档;配件优先取聚水潭商品 BOM,业务组合 BOM 与产品资料系统作为补充;
|
MKH 商品编码取自指定仓库商品主档;配件优先取聚水潭商品 BOM,业务组合 BOM 与产品资料系统作为补充;
|
||||||
配件仓位优先取包装箱与具体仓位,尚未分箱时显示库存仓库或商品档案仓库。
|
配件仓位只取箱及仓位库存表;没有箱号或仓位记录时显示「未维护」。
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -317,7 +317,7 @@ const MkhBomData = () => {
|
|||||||
dataIndex: 'location',
|
dataIndex: 'location',
|
||||||
width: 140,
|
width: 140,
|
||||||
render: (v) => (v ? text(v) : (
|
render: (v) => (v ? text(v) : (
|
||||||
<Tooltip title="上游未回传库位数据">
|
<Tooltip title="箱及仓位库存表中没有该配件的有效记录">
|
||||||
<span className="mkh-muted">未维护</span>
|
<span className="mkh-muted">未维护</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)),
|
)),
|
||||||
@ -339,8 +339,8 @@ const MkhBomData = () => {
|
|||||||
<Text type="secondary" className="mkh-note">
|
<Text type="secondary" className="mkh-note">
|
||||||
数据口径:MKH 商品编码仅取自业务库 jst_sku 中“简尚-崔桥工厂品牌仓”的成品(当前 4408 条);
|
数据口径:MKH 商品编码仅取自业务库 jst_sku 中“简尚-崔桥工厂品牌仓”的成品(当前 4408 条);
|
||||||
配件明细与数量优先取自聚水潭商品 BOM,业务中台组合商品 BOM 与产品资料系统数据库中维护的配件作为补充;
|
配件明细与数量优先取自聚水潭商品 BOM,业务中台组合商品 BOM 与产品资料系统数据库中维护的配件作为补充;
|
||||||
无指定仓库商品档案的 BOM 组合编码不会进入列表;配件仓位优先显示箱号或具体仓位,
|
无指定仓库商品档案的 BOM 组合编码不会进入列表;配件仓位只取 rpt.jst_packInventoryInfo,
|
||||||
尚未分箱时显示实际库存仓库,没有库存记录时显示商品档案仓库。
|
优先显示具体仓位,没有具体仓位时显示箱号,表中没有有效记录时显示「未维护」。
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user