重构:统一商品渠道投影并收敛权威文档
This commit is contained in:
@@ -2146,15 +2146,6 @@ async function ensureDraftProductForChannel(
|
||||
const productId = Number(product?.id || 0)
|
||||
if (!productId || product?.status !== 'active') return null
|
||||
|
||||
await query(
|
||||
`
|
||||
INSERT INTO product_channel_capabilities (
|
||||
product_id, channel, priority, is_fallback, enabled, source
|
||||
) VALUES ($1, $2, 10, FALSE, TRUE, 'auto')
|
||||
ON CONFLICT (product_id, channel) DO NOTHING
|
||||
`,
|
||||
[productId, channel],
|
||||
)
|
||||
return productId
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 内部 SKU 与渠道路由兼容层集成测试(需要真实 PostgreSQL)。
|
||||
*
|
||||
* INTERNAL_SKU_ROUTING_TEST_DATABASE_URL=postgres://... \
|
||||
* npx tsx --test src/services/fulfillment/internal-sku-routing.integration.test.ts
|
||||
*/
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
const TEST_DATABASE_URL = String(process.env.INTERNAL_SKU_ROUTING_TEST_DATABASE_URL || '').trim()
|
||||
|
||||
test(
|
||||
'旧 internal_sku_channels 写入会同步路由,商品来源与主渠道投影保持分离',
|
||||
{ skip: !TEST_DATABASE_URL ? '未设置 INTERNAL_SKU_ROUTING_TEST_DATABASE_URL' : false },
|
||||
async () => {
|
||||
process.env.DATABASE_URL = TEST_DATABASE_URL
|
||||
const { closeDb, query } = await import('../../db/client.js')
|
||||
const suffix = `${Date.now()}_${Math.floor(Math.random() * 1_000_000)}`
|
||||
const internalSku = `路由集成测试-${suffix}`
|
||||
const canonicalSku = `man:${internalSku}`
|
||||
let internalSkuId = 0
|
||||
let productId = 0
|
||||
|
||||
try {
|
||||
const createdSku = await query<{ id: string }>(
|
||||
'SELECT ensure_canonical_internal_sku($1) AS id',
|
||||
[internalSku],
|
||||
)
|
||||
internalSkuId = Number(createdSku.rows[0]?.id || 0)
|
||||
assert.ok(internalSkuId > 0)
|
||||
|
||||
const createdProduct = await query<{ id: string; source_kind: string }>(
|
||||
`
|
||||
INSERT INTO product_catalog (
|
||||
canonical_sku, canonical_name, category, unit_name, status, attributes_json,
|
||||
created_at, updated_at
|
||||
) VALUES ($1, $2, '集成测试', '件', 'active', '{}'::jsonb, NOW(), NOW())
|
||||
RETURNING id, source_kind
|
||||
`,
|
||||
[canonicalSku, internalSku],
|
||||
)
|
||||
productId = Number(createdProduct.rows[0]?.id || 0)
|
||||
assert.ok(productId > 0)
|
||||
assert.equal(createdProduct.rows[0]?.source_kind, 'manual')
|
||||
|
||||
const beforeRoute = await query<{ active_primary_channel: string | null }>(
|
||||
'SELECT active_primary_channel FROM v_product_current WHERE id = $1',
|
||||
[productId],
|
||||
)
|
||||
assert.equal(
|
||||
beforeRoute.rows[0]?.active_primary_channel,
|
||||
null,
|
||||
'man: 来源没有主路由时必须显示未路由',
|
||||
)
|
||||
|
||||
await query(
|
||||
`
|
||||
INSERT INTO internal_sku_channels (
|
||||
internal_sku, channel, priority, is_fallback, enabled, canonical_product_id, source
|
||||
) VALUES ($1, 'manual', 10, FALSE, TRUE, $2, 'integration_test')
|
||||
`,
|
||||
[internalSku, productId],
|
||||
)
|
||||
|
||||
const primary = await query<{ route_role: string; channel: string }>(
|
||||
`
|
||||
SELECT route_role, channel
|
||||
FROM sku_channel_routes
|
||||
WHERE internal_sku_id = $1 AND enabled = TRUE
|
||||
`,
|
||||
[internalSkuId],
|
||||
)
|
||||
assert.deepEqual(primary.rows, [{ route_role: 'primary', channel: 'manual' }])
|
||||
|
||||
const projection = await query<{
|
||||
source_kind: string
|
||||
active_primary_channel: string
|
||||
internal_sku_id: string
|
||||
}>(
|
||||
`
|
||||
SELECT source_kind, active_primary_channel, internal_sku_id
|
||||
FROM v_product_current
|
||||
WHERE id = $1
|
||||
`,
|
||||
[productId],
|
||||
)
|
||||
assert.equal(projection.rows[0]?.source_kind, 'manual')
|
||||
assert.equal(projection.rows[0]?.active_primary_channel, 'manual')
|
||||
assert.equal(Number(projection.rows[0]?.internal_sku_id), internalSkuId)
|
||||
|
||||
const { getAdminCostCenterOverview } =
|
||||
await import('../cost-center/cost-center-overview-service.js')
|
||||
const { listAdminProducts } = await import('../cost-center/product-catalog-service.js')
|
||||
const overview = await getAdminCostCenterOverview({ scope: 'manual' })
|
||||
const products = await listAdminProducts({ channel: 'manual', page: 1, pageSize: 100 })
|
||||
assert.equal(products.pagination.total, overview.productCount)
|
||||
assert.ok(products.items.every((product) => product.activePrimaryChannel === 'manual'))
|
||||
|
||||
await query(
|
||||
`
|
||||
UPDATE internal_sku_channels
|
||||
SET is_fallback = TRUE, updated_at = NOW()
|
||||
WHERE internal_sku = $1 AND channel = 'manual'
|
||||
`,
|
||||
[internalSku],
|
||||
)
|
||||
const fallback = await query<{ route_role: string }>(
|
||||
'SELECT route_role FROM sku_channel_routes WHERE internal_sku_id = $1',
|
||||
[internalSkuId],
|
||||
)
|
||||
assert.deepEqual(fallback.rows, [{ route_role: 'fallback' }])
|
||||
|
||||
await query(
|
||||
`
|
||||
UPDATE internal_sku_channels
|
||||
SET is_fallback = FALSE, updated_at = NOW()
|
||||
WHERE internal_sku = $1 AND channel = 'manual'
|
||||
`,
|
||||
[internalSku],
|
||||
)
|
||||
|
||||
await assert.rejects(
|
||||
() =>
|
||||
query(
|
||||
`
|
||||
INSERT INTO sku_channel_routes (
|
||||
internal_sku_id, channel, route_role, priority, enabled
|
||||
) VALUES ($1, 'work_order', 'primary', 1, TRUE)
|
||||
`,
|
||||
[internalSkuId],
|
||||
),
|
||||
/uq_sku_channel_routes_primary|duplicate key/,
|
||||
)
|
||||
} finally {
|
||||
if (productId > 0) {
|
||||
await query('DELETE FROM product_catalog WHERE id = $1', [productId])
|
||||
}
|
||||
if (internalSkuId > 0) {
|
||||
await query('DELETE FROM internal_sku_channels WHERE internal_sku = $1', [internalSku])
|
||||
await query('DELETE FROM internal_skus WHERE id = $1', [internalSkuId])
|
||||
}
|
||||
await closeDb()
|
||||
}
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user