fix(cost): 快照新单优先、费用口径与回填健壮性
- 成本快照拆热/冷双通道:新单走热通道分钟级入账,历史项冷通道轮询重算 - 通用成本只允许平台费/支付费自动全量,售后损失/其他必须按商品配置 - 比例费用按扣除已确认退款后的净收入计算 - 已入账 actual 快照不再被后续预估覆盖 - 受影响日期超过 31 天时按整段重算,不再静默丢弃;关账跳过会提示 - 通用费用解析不到时计数告警;已入队的成本缺失不再重复 upsert - 快照替换与写入放入同一事务,避免事实任务读到空窗
This commit is contained in:
@@ -52,9 +52,11 @@ export async function runChannelAssignJob(job: JsonObject = {}) {
|
||||
status: 'ok',
|
||||
message:
|
||||
`渠道判定:扫描 ${result.scanned},已定 ${result.assigned},` +
|
||||
`冲突 ${result.conflicted},仍待定 ${result.stillPending},回填 ${refreshed} 天`,
|
||||
`冲突 ${result.conflicted},仍待定 ${result.stillPending},回填 ${refreshed.days} 天` +
|
||||
(refreshed.skippedClosed > 0 ? `(${refreshed.skippedClosed} 天已关账跳过)` : ''),
|
||||
...result,
|
||||
refreshedDays: refreshed,
|
||||
refreshedDays: refreshed.days,
|
||||
skippedClosedDays: refreshed.skippedClosed,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,19 +66,49 @@ export async function runChannelAssignJob(job: JsonObject = {}) {
|
||||
* 事实任务只回看最近几天;补配价格、迟到分类这类变更必须按受影响日期重算,
|
||||
* 否则旧日期的毛利会一直停在缺失成本/未分类。
|
||||
*/
|
||||
async function refreshFactsForDates(dates: string[]): Promise<number> {
|
||||
const unique = [...new Set(dates.filter(Boolean))].sort().slice(0, 31)
|
||||
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-pipeline] 部分日期已关账,事实未回填', {
|
||||
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 {
|
||||
await rebuildChannelSalesDailyFacts({ dateFrom: date, dateTo: date })
|
||||
const facts = await rebuildChannelSalesDailyFacts({ dateFrom: date, dateTo: date })
|
||||
if (facts.skippedClosedPeriods.length > 0) {
|
||||
skippedClosed += facts.skippedClosedPeriods.length
|
||||
console.warn('[finance-pipeline] 日期已关账,事实未回填', { date })
|
||||
continue
|
||||
}
|
||||
await syncFinanceLedgerFromFacts({ dateFrom: date, dateTo: date })
|
||||
done += 1
|
||||
} catch {
|
||||
// 单日失败不影响管道本身,下一轮或人工重跑会再处理。
|
||||
}
|
||||
}
|
||||
return done
|
||||
return { days: done, skippedClosed }
|
||||
}
|
||||
|
||||
export async function runCarrierDispatchJob(job: JsonObject = {}) {
|
||||
@@ -368,14 +400,18 @@ export async function runCostSnapshotJob(job: JsonObject = {}) {
|
||||
})
|
||||
|
||||
await touchWatermark('cost_snapshot', { lastItemId: result.nextCursor })
|
||||
await touchWatermark('cost_snapshot_hot', { lastItemId: result.hotCursor })
|
||||
const refreshed = await refreshFactsForDates(result.affectedDates)
|
||||
return {
|
||||
status: 'ok',
|
||||
message:
|
||||
`成本快照:扫描 ${result.scanned},生成 ${result.created},` +
|
||||
`替换 ${result.superseded},缺失 ${result.missing},回填 ${refreshed} 天`,
|
||||
`替换 ${result.superseded},缺失 ${result.missing},回填 ${refreshed.days} 天` +
|
||||
(result.genericMissing > 0 ? `,通用费用未解析 ${result.genericMissing} 项` : '') +
|
||||
(refreshed.skippedClosed > 0 ? `(${refreshed.skippedClosed} 天已关账跳过)` : ''),
|
||||
...result,
|
||||
refreshedDays: refreshed,
|
||||
refreshedDays: refreshed.days,
|
||||
skippedClosedDays: refreshed.skippedClosed,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user