#!/usr/bin/env tsx /** * 商品来源、内部 SKU 与履约路由只读审计。 * * 用法: * npm run audit:product-routing * npm run audit:product-routing -- --strict */ import { closeDb, query } from '../src/db/client.js' const strict = process.argv.includes('--strict') async function main() { const [ sourceChannel, routeCounts, manualConfigRoutes, orphanManualProducts, multiplePrimaryRoutes, missingInternalSku, unroutedProducts, invalidSourceKinds, ] = await Promise.all([ query<{ source_kind: string; primary_channel: string | null; total: number }>( ` SELECT source_kind, active_primary_channel AS primary_channel, COUNT(*)::int AS total FROM v_product_current WHERE status = 'active' GROUP BY 1, 2 ORDER BY 1, 2 NULLS FIRST `, ), query<{ channel: string; route_role: string; total: number }>( ` SELECT channel, route_role, COUNT(*)::int AS total FROM sku_channel_routes WHERE enabled = TRUE GROUP BY 1, 2 ORDER BY 1, 2 `, ), query<{ route_state: string total: number }>( ` SELECT CASE WHEN primary_route.channel = 'manual' THEN 'primary_manual' WHEN primary_route.channel IS NOT NULL AND manual_route.route_role = 'fallback' THEN 'manual_fallback' WHEN manual_route.route_role = 'fallback' THEN 'fallback_without_primary' ELSE 'missing_manual_route' END AS route_state, COUNT(*)::int AS total FROM manual_fulfillment_configs config LEFT JOIN LATERAL ( SELECT route.channel FROM sku_channel_routes route WHERE route.internal_sku_id = config.internal_sku_id AND route.enabled = TRUE AND route.route_role = 'primary' ORDER BY route.priority, route.channel LIMIT 1 ) primary_route ON TRUE LEFT JOIN sku_channel_routes manual_route ON manual_route.internal_sku_id = config.internal_sku_id AND manual_route.channel = 'manual' AND manual_route.enabled = TRUE WHERE config.enabled = TRUE GROUP BY 1 ORDER BY 1 `, ), query<{ product_id: number canonical_sku: string canonical_name: string }>( ` SELECT product.id AS product_id, product.canonical_sku, product.canonical_name FROM product_catalog product WHERE product.status = 'active' AND product.source_kind = 'manual' AND NOT EXISTS ( SELECT 1 FROM sku_channel_routes route WHERE route.internal_sku_id = product.internal_sku_id AND route.enabled = TRUE AND route.route_role = 'primary' ) AND NOT EXISTS ( SELECT 1 FROM manual_fulfillment_configs config WHERE config.enabled = TRUE AND ( config.canonical_product_id = product.id OR config.internal_sku_id = product.internal_sku_id OR canonicalize_internal_sku(config.sku_key) = canonicalize_internal_sku(substring(product.canonical_sku from 5)) ) ) ORDER BY product.id `, ), query<{ internal_sku_id: number; total: number }>( ` SELECT internal_sku_id, COUNT(*)::int AS total FROM sku_channel_routes WHERE enabled = TRUE AND route_role = 'primary' GROUP BY 1 HAVING COUNT(*) > 1 ORDER BY 2 DESC, 1 `, ), query<{ source_kind: string; total: number }>( ` SELECT source_kind, COUNT(*)::int AS total FROM product_catalog WHERE status = 'active' AND source_kind IN ('manual', 'third_party_voucher') AND internal_sku_id IS NULL GROUP BY 1 ORDER BY 1 `, ), query<{ product_id: number canonical_sku: string canonical_name: string source_kind: string }>( ` SELECT id AS product_id, canonical_sku, canonical_name, source_kind FROM v_product_current WHERE status = 'active' AND active_primary_channel IS NULL ORDER BY source_kind, id `, ), query<{ source_kind: string; total: number }>( ` SELECT source_kind, COUNT(*)::int AS total FROM product_catalog WHERE status = 'active' AND source_kind NOT IN ( 'affiliate_dash', 'work_order', 'third_party_voucher', 'manual', 'unclassified' ) GROUP BY 1 ORDER BY 1 `, ), ]) const critical = { orphanManualProducts: orphanManualProducts.rows.length, multiplePrimaryRoutes: multiplePrimaryRoutes.rows.length, missingInternalSku: missingInternalSku.rows.reduce((sum, row) => sum + Number(row.total), 0), invalidSourceKinds: invalidSourceKinds.rows.reduce((sum, row) => sum + Number(row.total), 0), invalidManualConfigRoutes: manualConfigRoutes.rows .filter( (row) => row.route_state !== 'primary_manual' && row.route_state !== 'manual_fallback', ) .reduce((sum, row) => sum + Number(row.total), 0), } console.info('[product-routing-audit] 商品来源 × 当前渠道') console.table(sourceChannel.rows) console.info('[product-routing-audit] 路由分布') console.table(routeCounts.rows) console.info('[product-routing-audit] 人工配置路由状态') console.table(manualConfigRoutes.rows) console.info('[product-routing-audit] 无主路由且无人工配置的 man 商品') console.table(orphanManualProducts.rows) console.info('[product-routing-audit] 多主路由内部 SKU') console.table(multiplePrimaryRoutes.rows) console.info('[product-routing-audit] 缺少内部 SKU 的商品') console.table(missingInternalSku.rows) console.info('[product-routing-audit] 非法商品来源') console.table(invalidSourceKinds.rows) console.info('[product-routing-audit] 未路由活动商品(待人工分类)') console.table(unroutedProducts.rows) console.info('[product-routing-audit] 关键异常', critical) if (strict && Object.values(critical).some((count) => count > 0)) { process.exitCode = 1 } } main() .catch((error) => { console.error(error) process.exitCode = 1 }) .finally(() => closeDb())