feat(pipeline): 渠道判定后立即归一商品并算成本
- 新增按订单项定向归一,避免新判定订单等冷通道 - 判定/指定渠道后立即归一 → 算成本 → 按业务日回填事实与总账 - 提取 refreshFactsForDates 供管道与渠道服务复用
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import { query } from '../../db/client.js'
|
||||
import { rebuildChannelSalesDailyFacts } from './sales-facts-channel-service.js'
|
||||
import { syncFinanceLedgerFromFacts } from './finance-ledger-service.js'
|
||||
|
||||
/**
|
||||
* 按业务日回填销售事实与总账。
|
||||
*
|
||||
* - 日期较多(>31)时按 min~max 整段重算,避免截断导致部分日期永远不更新;
|
||||
* - 已关账期间会被跳过,返回 skippedClosed 供调用方提示。
|
||||
*/
|
||||
export async function refreshFactsForDates(
|
||||
dates: string[],
|
||||
): Promise<{ days: number; skippedClosed: number }> {
|
||||
const unique = [...new Set(dates.filter(Boolean))].sort()
|
||||
if (unique.length === 0) return { days: 0, skippedClosed: 0 }
|
||||
|
||||
if (unique.length > 31) {
|
||||
const dateFrom = unique[0]!
|
||||
const dateTo = unique[unique.length - 1]!
|
||||
try {
|
||||
const facts = await rebuildChannelSalesDailyFacts({ dateFrom, dateTo })
|
||||
await syncFinanceLedgerFromFacts({ dateFrom, dateTo })
|
||||
if (facts.skippedClosedPeriods.length > 0) {
|
||||
console.warn('[finance] 部分日期已关账,事实未回填', {
|
||||
dateFrom,
|
||||
dateTo,
|
||||
skipped: facts.skippedClosedPeriods,
|
||||
})
|
||||
}
|
||||
return { days: unique.length, skippedClosed: facts.skippedClosedPeriods.length }
|
||||
} catch {
|
||||
return { days: 0, skippedClosed: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
let done = 0
|
||||
let skippedClosed = 0
|
||||
for (const date of unique) {
|
||||
try {
|
||||
const facts = await rebuildChannelSalesDailyFacts({ dateFrom: date, dateTo: date })
|
||||
if (facts.skippedClosedPeriods.length > 0) {
|
||||
skippedClosed += facts.skippedClosedPeriods.length
|
||||
console.warn('[finance] 日期已关账,事实未回填', { date })
|
||||
continue
|
||||
}
|
||||
await syncFinanceLedgerFromFacts({ dateFrom: date, dateTo: date })
|
||||
done += 1
|
||||
} catch {
|
||||
// 单日失败不阻断调用方,下一轮或人工重跑会再处理。
|
||||
}
|
||||
}
|
||||
return { days: done, skippedClosed }
|
||||
}
|
||||
|
||||
/** 批量取订单项所属业务日(Asia/Shanghai)。 */
|
||||
export async function orderItemBasisDates(itemIds: number[]): Promise<string[]> {
|
||||
const ids = [...new Set(itemIds.filter((id) => Number.isInteger(id) && id > 0))]
|
||||
if (ids.length === 0) return []
|
||||
const rows = await query<{ basis_date: string }>(
|
||||
`
|
||||
SELECT DISTINCT
|
||||
(COALESCE(o.paid_at, o.created_at) AT TIME ZONE 'Asia/Shanghai')::date::text
|
||||
AS basis_date
|
||||
FROM order_items oi
|
||||
JOIN orders o ON o.id = oi.order_id
|
||||
WHERE oi.id = ANY($1::bigint[])
|
||||
`,
|
||||
[ids],
|
||||
)
|
||||
return rows.rows.map((row) => row.basis_date)
|
||||
}
|
||||
Reference in New Issue
Block a user