fix: read BOM locations from pack inventory

This commit is contained in:
lunanting 2026-09-16 17:52:49 +08:00
parent 3fe0e55ad6
commit 65d009dcdc
2 changed files with 86 additions and 9 deletions

View File

@ -30,8 +30,9 @@
// payload 里的 bomDetails / factoryRows / pricingRows 属成本与工厂行,结构不同
// (无 SKU 条码),也不能直接当配件 BOM。
//
// ④ 配件仓位 —— 业务库 SKU 档案中的库位字段;
// 业务库 jst_sku.bin / other_bin 全库为空,故多数配件显示「未维护」。
// ④ 配件仓位 —— 业务库 rpt.jst_packInventoryInfo;
// sku_id 可能带“-包装序号”后缀,需按基础配件编码匹配。优先显示 bin_group / bin,
// 仓位为空时显示净数量大于 0 的 pack_id(箱号)与 wh_id(仓库)。
//
// ⑤ 配件库存 —— 业务库 rpt.jst_inventoryInfo 按 sku_id 汇总。
@ -335,6 +336,46 @@ function countDistinctBomProducts(...boms) {
const sumQty = (components) => components.reduce((sum, item) => sum + (toNumber(item.qty) || 0), 0);
function matchesPackSku(componentCode, packSkuCode) {
const component = toText(componentCode)?.toUpperCase();
const packSku = toText(packSkuCode)?.toUpperCase();
if (!component || !packSku) return false;
return packSku === component || packSku.startsWith(`${component}-`);
}
function formatPackLocation(row) {
const warehouseId = toText(row.warehouseId);
const binGroup = toText(row.binGroup);
const bin = toText(row.bin);
const packId = toText(row.packId);
const warehouse = warehouseId ? `仓库 ${warehouseId}` : '';
const position = [binGroup, bin].filter(Boolean).join(' / ')
|| (packId ? `箱号 ${packId}` : '');
return [warehouse, position].filter(Boolean).join(' · ');
}
function buildPackLocationMap(rows, componentCodes) {
const locations = new Map(componentCodes.map((code) => [code, new Set()]));
rows.forEach((row) => {
const componentCode = componentCodes.find((code) => matchesPackSku(code, row.skuId));
if (!componentCode) return;
const location = formatPackLocation(row);
if (location) locations.get(componentCode).add(location);
});
const result = new Map();
locations.forEach((values, code) => {
const all = [...values];
if (!all.length) return;
const visible = all.slice(0, 10);
result.set(code, all.length > visible.length
? `${visible.join('、')} 等 ${all.length} 个位置`
: visible.join('、'));
});
return result;
}
/**
* 找出「资料库里已维护 BOM、但业务库列表里没有」的模块化商品,
* 作为追加行放在列表最前面,保证已维护的数据一定能在页面上看到。
@ -594,17 +635,31 @@ async function getMkhBomDetail(mkhSkuId) {
}
try {
const locationParams = {};
const locationConditions = codes.map((code, index) => {
locationParams[`lc${index}`] = code;
locationParams[`lp${index}`] = `${code}-%`;
return `(p.sku_id = @lc${index} OR p.sku_id LIKE @lp${index})`;
});
const locRows = await cloudDatabase.query(
`SELECT sku_id AS skuId,
COALESCE(NULLIF(LTRIM(RTRIM(bin)), N''), NULLIF(LTRIM(RTRIM(other_bin)), N'')) AS location
FROM dbo.jst_sku
WHERE sku_id IN (${placeholders})`,
codeParams,
`SELECT p.sku_id AS skuId,
p.pack_id AS packId,
p.wh_id AS warehouseId,
p.bin AS bin,
p.bin_group AS binGroup,
SUM(p.qty) AS qty
FROM rpt.jst_packInventoryInfo p
WHERE ${locationConditions.join(' OR ')}
GROUP BY p.sku_id, p.pack_id, p.wh_id, p.bin, p.bin_group
HAVING SUM(p.qty) > 0`,
locationParams,
{ requestTimeout: 120000 }
);
locRows.forEach((row) => locationMap.set(toText(row.skuId), toText(row.location)));
buildPackLocationMap(locRows, codes).forEach((location, code) => {
locationMap.set(code, location);
});
} catch (error) {
console.warn('[MkhBom] 配件仓位查询降级:', error.message);
console.warn('[MkhBom] 配件箱号/仓位查询降级:', error.message);
}
}
@ -642,5 +697,8 @@ module.exports = {
buildBusinessBomExtraRows,
mergeExtraRows,
filterExtraRows,
matchesPackSku,
formatPackLocation,
buildPackLocationMap,
},
};

View File

@ -93,3 +93,22 @@ test('BOM-only rows honor the same keyword filter as jst_sku rows', () => {
assert.deepEqual(rows.map((row) => row.skuId), ['MKH-B-1']);
});
test('pack inventory locations match a BOM component and its package suffixes', () => {
assert.equal(_private.matchesPackSku('PART-1', 'PART-1'), true);
assert.equal(_private.matchesPackSku('PART-1', 'PART-1-10'), true);
assert.equal(_private.matchesPackSku('PART-1', 'PART-10'), false);
});
test('pack inventory location prefers bins and falls back to positive-stock box ids', () => {
const locations = _private.buildPackLocationMap([
{
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,
},
], ['PART-1']);
assert.equal(locations.get('PART-1'), '仓库 1 · 成品区 / A-01、仓库 1 · 箱号 BOX-2');
});