feat(pipeline): 渠道判定后立即归一商品并算成本
- 新增按订单项定向归一,避免新判定订单等冷通道 - 判定/指定渠道后立即归一 → 算成本 → 按业务日回填事实与总账 - 提取 refreshFactsForDates 供管道与渠道服务复用
This commit is contained in:
@@ -319,3 +319,69 @@ async function loadMappingIndex(): Promise<MappingIndex> {
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
/** 指定订单项立即归一(渠道判定后调用,保证成本能马上算出来)。 */
|
||||
export async function normalizeProductsForItems(itemIds: number[]): Promise<{
|
||||
matched: number
|
||||
unmatched: number
|
||||
}> {
|
||||
const ids = [
|
||||
...new Set(itemIds.map(Number).filter((id) => Number.isInteger(id) && id > 0)),
|
||||
].slice(0, 2000)
|
||||
if (ids.length === 0) return { matched: 0, unmatched: 0 }
|
||||
const index = await loadMappingIndex()
|
||||
const rows = await query<ItemRow>(
|
||||
`
|
||||
SELECT
|
||||
oi.id,
|
||||
o.provider,
|
||||
o.shop_id,
|
||||
oi.sku_code,
|
||||
oi.sku_name,
|
||||
oi.normalized_sku_name,
|
||||
oi.normalized_sku_series,
|
||||
oi.fulfillment_channel,
|
||||
wpr.rule_key AS work_rule_key,
|
||||
cfg.canonical_product_id AS manual_config_product_id
|
||||
FROM order_items oi
|
||||
JOIN orders o ON o.id = oi.order_id
|
||||
LEFT JOIN work_orders wo ON wo.order_item_id = oi.id
|
||||
LEFT JOIN work_product_rules wpr ON wpr.id = wo.product_rule_id
|
||||
LEFT JOIN manual_fulfillment_configs cfg ON cfg.id = oi.fulfillment_config_id
|
||||
WHERE oi.fulfillment_status = 'assigned'
|
||||
AND oi.canonical_product_id IS NULL
|
||||
AND oi.id = ANY($1::bigint[])
|
||||
`,
|
||||
[ids],
|
||||
)
|
||||
const matched: Array<{ id: number; productId: number }> = []
|
||||
let unmatched = 0
|
||||
for (const item of rows.rows) {
|
||||
const productId = resolveProductId(item, index)
|
||||
if (!productId) {
|
||||
unmatched += 1
|
||||
await upsertAggregatedException({
|
||||
reason: 'product_mapping_missing',
|
||||
scopeKey: `product_mapping_missing|${item.provider}|${item.shop_id}|${item.sku_code}|${item.fulfillment_channel || '-'}`,
|
||||
orderItemId: item.id,
|
||||
detail: { skuName: item.sku_name, channel: item.fulfillment_channel },
|
||||
})
|
||||
continue
|
||||
}
|
||||
matched.push({ id: item.id, productId })
|
||||
}
|
||||
if (matched.length > 0) {
|
||||
await query(
|
||||
`
|
||||
UPDATE order_items oi
|
||||
SET canonical_product_id = input.product_id, updated_at = NOW()
|
||||
FROM unnest($1::bigint[], $2::bigint[]) AS input(id, product_id)
|
||||
WHERE oi.id = input.id
|
||||
AND oi.canonical_product_id IS NULL
|
||||
`,
|
||||
[matched.map((row) => row.id), matched.map((row) => row.productId)],
|
||||
)
|
||||
await resolveExceptionsForItems(matched.map((row) => row.id))
|
||||
}
|
||||
return { matched: matched.length, unmatched }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user