feat(pipeline): 渠道判定后立即归一商品并算成本

- 新增按订单项定向归一,避免新判定订单等冷通道
- 判定/指定渠道后立即归一 → 算成本 → 按业务日回填事实与总账
- 提取 refreshFactsForDates 供管道与渠道服务复用
This commit is contained in:
yml2213
2026-11-01 15:21:53 +08:00
parent 9e154cb16d
commit fd6d2d561b
4 changed files with 174 additions and 55 deletions
@@ -3,6 +3,9 @@ import { createHttpError } from '../../utils/http.js'
import { nowIso } from '../../utils/time.js'
import { upsertAggregatedException } from '../cost-center/cost-exception-service.js'
import { readWatermarkItemId } from '../watermark-store.js'
import { syncCostSnapshotsForItems } from '../cost-center/cost-snapshot-service.js'
import { normalizeProductsForItems } from '../cost-center/product-normalize-service.js'
import { orderItemBasisDates, refreshFactsForDates } from '../finance/refresh-facts.js'
import {
FULFILLMENT_CHANNELS,
MATCH_KEY_TYPES,
@@ -145,6 +148,8 @@ export type AssignChannelsResult = {
byChannel: Record<string, number>
/** 本轮被判定的订单所属业务日,供事实/总账按日回填。 */
affectedDates: string[]
/** 本轮新判定的订单项(供立即计算成本,最多 2000 条)。 */
assignedItemIds: number[]
}
/** 批量判定:默认只扫 pending 且未锁定的订单项。 */
@@ -169,6 +174,7 @@ export async function assignPendingOrderItemChannels(
nextCursor: cursor,
byChannel: {},
affectedDates: [],
assignedItemIds: [],
}
const affectedDates = new Set<string>()
@@ -262,6 +268,7 @@ export async function assignPendingOrderItemChannels(
],
)
result.assigned += updated.rowCount || 0
result.assignedItemIds = assignments.map((row) => row.id).slice(0, 2000)
}
}
@@ -305,7 +312,17 @@ export async function assignChannelToOrderItems(input: {
[ids, input.channel, input.adminUserId, String(input.note || '').slice(0, 200)],
)
return { updated: result.rowCount || 0 }
const updated = result.rowCount || 0
if (updated > 0) {
// 运营指定后先归一商品,再立即算成本并回填事实,避免旧单等冷通道。
await normalizeProductsForItems(ids).catch(() => undefined)
const cost = await syncCostSnapshotsForItems(ids).catch(() => null)
const dates = [
...new Set([...(await orderItemBasisDates(ids)), ...(cost?.affectedDates || [])]),
]
await refreshFactsForDates(dates).catch(() => undefined)
}
return { updated }
}
/** 运营解锁:允许自动判定重新接管。 */