配件库存监控:已配置MKH改为"被MKH模块化成品引用"判定
- backend/src/services/modularStockMonitorService.js
- 「已配置MKH」判定口径切回 jst_comboProductItem 组合装引用:
该 SKU 出现在 jst_comboProductItem.src_sku_id 且 combine_sku_id 以 'MKH' 开头
即视为被 MKH 模块化成品(组合装/成品档案)引用为配件。
- 移除产品资料系统数据库覆盖清单逻辑(resolveProductCoverage / productCoveredCsv),
产品资料库仅保留作图片来源。
- 实测目标仓 794 SKU 中 29 个命中(原产品资料库口径 661 已废弃)。
- frontend/src/pages/ModularStockMonitor/index.jsx
- 状态列 tooltip 文案同步为「是否被 MKH 模块化成品作为配件引用」。
注:本改动会令已配置MKH数量从 661 降至 29(数据使然,非 bug)。
This commit is contained in:
parent
75a8795723
commit
e6ea1246f9
@ -50,27 +50,18 @@ const WARNING_LEVEL = Object.freeze({
|
||||
NORMAL: '正常',
|
||||
});
|
||||
|
||||
// 模块化配置状态:
|
||||
// 「已配置MKH」= 该 SKU 已在「产品资料系统数据库」中建立商品档案
|
||||
// (external.product_catalog_wide_base_v1 按 barcode 匹配)
|
||||
// 「未配置MKH」= 产品资料系统数据库中查不到该 SKU 档案
|
||||
//
|
||||
// 口径变更说明(2026-09-15,按业务确认):
|
||||
// 原先用业务库 jst_comboProductItem.src_sku_id 判定「是否被 MKH 组合 BOM 引用」,
|
||||
// 实测目标仓 794 个 SKU 中只有 32 个命中——因为目标仓(半成品包装仓)与
|
||||
// MKH 组合 BOM 的配件(159 个 SKU / 14 个款号)是两批基本不重叠的编码集合,
|
||||
// 按款号(SPU)放宽也只有 3 个款号重叠。
|
||||
// 业务确认改用「产品资料系统数据库是否有商品档案」作为判定口径,
|
||||
// 实测目标仓 661/794 命中。
|
||||
// 模块化配置状态(2026-09-17 业务确认口径):
|
||||
// 「已配置MKH」= 该 SKU 作为配件被某个 MKH 模块化成品引用
|
||||
// (业务库 jst_comboProductItem 中 src_sku_id = 该 SKU
|
||||
// 且 combine_sku_id 以 'MKH' 开头,即 MKH 组合装 / 成品档案)
|
||||
// 「未配置MKH」= 未被任何 MKH 模块化成品引用
|
||||
// 实测目标仓 794 个 SKU 中 29 个命中(32 个被任意组合装引用,其中 29 个属 MKH 体系)。
|
||||
// ⚠️ 之前的「产品资料系统数据库商品档案」口径(661/794)已废弃,改用组合装引用口径。
|
||||
const MKH_STATUS = Object.freeze({
|
||||
CONFIGURED: '已配置MKH',
|
||||
UNCONFIGURED: '未配置MKH',
|
||||
});
|
||||
|
||||
// 产品资料覆盖度缓存:避免每次列表请求都回查产品资料系统数据库
|
||||
const PRODUCT_COVERAGE_TTL_MS = 10 * 60 * 1000;
|
||||
let productCoverageCache = null;
|
||||
|
||||
const SORTABLE_FIELDS = Object.freeze({
|
||||
skuId: 's.sku_id',
|
||||
productName: 's.name',
|
||||
@ -85,21 +76,19 @@ const SORTABLE_FIELDS = Object.freeze({
|
||||
});
|
||||
|
||||
/**
|
||||
* 「已配置MKH」判定表达式。
|
||||
* available = true —— 用产品资料库覆盖清单(@productCoveredCsv,逗号分隔的 SKU 编码)
|
||||
* available = false —— 产品资料库不可用时回退到业务库 MKH BOM 判定,保证页面可用
|
||||
* 「已配置MKH」判定表达式(2026-09-17 口径):
|
||||
* 该 SKU 作为配件被某个 MKH 模块化成品引用,即业务库 jst_comboProductItem 中
|
||||
* src_sku_id = 该 SKU 且 combine_sku_id 以 'MKH' 开头(MKH 组合装 / 成品档案)。
|
||||
*/
|
||||
const buildConfiguredExistsSql = (coverage = {}) => (
|
||||
coverage.available
|
||||
? `EXISTS (
|
||||
SELECT 1 FROM STRING_SPLIT(@productCoveredCsv, N',') pc WHERE pc.value = s.sku_id
|
||||
)`
|
||||
: `EXISTS (
|
||||
SELECT 1 FROM JSDataMiddlePlatform.dbo.jst_comboProductItem x WHERE x.src_sku_id = s.sku_id
|
||||
)`
|
||||
const buildConfiguredExistsSql = () => (
|
||||
`EXISTS (
|
||||
SELECT 1 FROM dbo.jst_comboProductItem x
|
||||
WHERE x.src_sku_id = s.sku_id
|
||||
AND x.combine_sku_id LIKE N'MKH%'
|
||||
)`
|
||||
);
|
||||
|
||||
const buildBaseSql = (coverage = {}) => `
|
||||
const buildBaseSql = () => `
|
||||
FROM dbo.jst_sku s
|
||||
OUTER APPLY (
|
||||
SELECT TOP 1 i.qty
|
||||
@ -127,9 +116,9 @@ OUTER APPLY (
|
||||
WHERE UPPER(LTRIM(RTRIM(TRY_CONVERT(NVARCHAR(100), c.sku)))) = UPPER(LTRIM(RTRIM(TRY_CONVERT(NVARCHAR(100), s.sku_id))))
|
||||
ORDER BY c.createTime DESC
|
||||
) cost
|
||||
-- 模块化配置状态:见文件头部「口径变更说明」
|
||||
-- 模块化配置状态:见文件头部「已配置MKH」口径说明
|
||||
OUTER APPLY (
|
||||
SELECT CAST(CASE WHEN ${buildConfiguredExistsSql(coverage)} THEN 1 ELSE 0 END AS BIT) AS configured
|
||||
SELECT CAST(CASE WHEN ${buildConfiguredExistsSql()} THEN 1 ELSE 0 END AS BIT) AS configured
|
||||
) cfg
|
||||
WHERE s.other_3 = @warehouseName`;
|
||||
|
||||
@ -180,7 +169,7 @@ async function resolveMonitorDate() {
|
||||
return Number.isNaN(d.getTime()) ? null : d.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
const buildFilters = (query = {}, monitorDate = null, coverage = {}) => {
|
||||
const buildFilters = (query = {}, monitorDate = null) => {
|
||||
const conditions = [];
|
||||
const params = {
|
||||
wmsCoId: WAREHOUSE_WMS_CO_ID,
|
||||
@ -188,11 +177,6 @@ const buildFilters = (query = {}, monitorDate = null, coverage = {}) => {
|
||||
monitorDate,
|
||||
};
|
||||
|
||||
// 「已配置MKH」判定依赖产品资料库覆盖清单(逗号分隔的 SKU 编码)
|
||||
if (coverage.available) {
|
||||
params.productCoveredCsv = coverage.csv || '';
|
||||
}
|
||||
|
||||
const keyword = toText(query.keyword);
|
||||
if (keyword) {
|
||||
params.keyword = `%${keyword}%`;
|
||||
@ -284,53 +268,11 @@ async function attachProductImages(rows = []) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析目标仓 SKU 的「产品资料库覆盖清单」,用于「已配置MKH」判定。
|
||||
* 返回 { available, csv, set, total, coveredCount },结果按 TTL 缓存。
|
||||
* 产品资料库不可用时 available = false,SQL 侧自动回退到业务库 MKH BOM 判定。
|
||||
* 「已配置MKH」判定说明(2026-09-17):
|
||||
* 直接由 buildBaseSql 的 cfg 子查询判定(jst_comboProductItem.src_sku_id
|
||||
* 命中且 combine_sku_id 以 'MKH' 开头),不再依赖产品资料系统数据库。
|
||||
* 产品资料库仅用于补齐商品图片(见 attachProductImages)。
|
||||
*/
|
||||
async function resolveProductCoverage() {
|
||||
const now = Date.now();
|
||||
if (
|
||||
productCoverageCache
|
||||
&& productCoverageCache.warehouse === WAREHOUSE_NAME
|
||||
&& now - productCoverageCache.at < PRODUCT_COVERAGE_TTL_MS
|
||||
) {
|
||||
return productCoverageCache;
|
||||
}
|
||||
|
||||
const skuRows = await cloudDatabase.query(
|
||||
`SELECT s.sku_id AS skuId FROM dbo.jst_sku s WHERE s.other_3 = @warehouseName`,
|
||||
{ warehouseName: WAREHOUSE_NAME },
|
||||
{ requestTimeout: 180000 }
|
||||
);
|
||||
const codes = skuRows.map((row) => toText(row.skuId)).filter(Boolean);
|
||||
|
||||
let covered = [];
|
||||
let available = true;
|
||||
try {
|
||||
const rows = await productDb.query(
|
||||
`SELECT DISTINCT barcode
|
||||
FROM external.product_catalog_wide_base_v1
|
||||
WHERE barcode = ANY($1)`,
|
||||
[codes]
|
||||
);
|
||||
covered = rows.map((row) => toText(row.barcode)).filter(Boolean);
|
||||
} catch (error) {
|
||||
available = false;
|
||||
console.warn('[ModularStock] 产品资料库覆盖清单查询失败,回退业务库 MKH BOM 判定:', error.message);
|
||||
}
|
||||
|
||||
productCoverageCache = {
|
||||
warehouse: WAREHOUSE_NAME,
|
||||
available,
|
||||
csv: covered.join(','),
|
||||
set: new Set(covered),
|
||||
total: codes.length,
|
||||
coveredCount: covered.length,
|
||||
at: now,
|
||||
};
|
||||
return productCoverageCache;
|
||||
}
|
||||
|
||||
const mapRow = (row = {}) => {
|
||||
const realtimeStock = Number(row.realtimeStock || 0);
|
||||
@ -390,12 +332,9 @@ async function getModularStockList(query = {}) {
|
||||
const page = Math.max(1, Number.parseInt(query.page, 10) || 1);
|
||||
const requestedSize = Number.parseInt(query.pageSize, 10) || DEFAULT_PAGE_SIZE;
|
||||
const pageSize = Math.min(MAX_PAGE_SIZE, Math.max(1, requestedSize));
|
||||
const [monitorDate, coverage] = await Promise.all([
|
||||
resolveMonitorDate(),
|
||||
resolveProductCoverage(),
|
||||
]);
|
||||
const { conditions, params } = buildFilters(query, monitorDate, coverage);
|
||||
const baseSql = buildBaseSql(coverage);
|
||||
const monitorDate = await resolveMonitorDate();
|
||||
const { conditions, params } = buildFilters(query, monitorDate);
|
||||
const baseSql = buildBaseSql();
|
||||
const whereSql = conditions.length ? `${baseSql} AND ${conditions.join(' AND ')}` : baseSql;
|
||||
|
||||
const sortField = SORTABLE_FIELDS[query.sortField] ? query.sortField : 'realtimeStock';
|
||||
@ -474,22 +413,16 @@ async function buildSummary(whereSql, params) {
|
||||
}
|
||||
|
||||
async function getModularStockFilters() {
|
||||
const [monitorDate, coverage] = await Promise.all([
|
||||
resolveMonitorDate(),
|
||||
resolveProductCoverage(),
|
||||
]);
|
||||
const monitorDate = await resolveMonitorDate();
|
||||
const params = {
|
||||
wmsCoId: WAREHOUSE_WMS_CO_ID,
|
||||
warehouseName: WAREHOUSE_NAME,
|
||||
monitorDate,
|
||||
};
|
||||
if (coverage.available) {
|
||||
params.productCoveredCsv = coverage.csv || '';
|
||||
}
|
||||
|
||||
const rows = await cloudDatabase.query(
|
||||
`SELECT DISTINCT s.category
|
||||
${buildBaseSql(coverage)} AND s.category IS NOT NULL AND LTRIM(RTRIM(s.category)) <> N''
|
||||
${buildBaseSql()} AND s.category IS NOT NULL AND LTRIM(RTRIM(s.category)) <> N''
|
||||
ORDER BY s.category`,
|
||||
params,
|
||||
{ requestTimeout: 180000 }
|
||||
|
||||
@ -157,7 +157,7 @@ const AccessoryStockPanel = () => {
|
||||
{
|
||||
title: <TipTitle
|
||||
title="状态"
|
||||
tip="该商品是否已在「产品资料系统数据库」中建立商品档案。已配置MKH = 产品资料库中能查到该商品编码的商品资料;未配置MKH = 产品资料库中查不到该商品档案"
|
||||
tip="该商品是否被 MKH 模块化成品作为配件引用。已配置MKH = 业务库 jst_comboProductItem 中 src_sku_id 命中该商品编码且 combine_sku_id 以 MKH 开头(被某 MKH 组合装/成品引用为配件);未配置MKH = 未被任何 MKH 模块化成品引用"
|
||||
/>,
|
||||
dataIndex: 'mkhStatus',
|
||||
width: 110,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user