feat(finance): 统一内部 SKU 驱动的履约与财务分流
- 新增 internal_sku_mappings / internal_sku_channels,平台字段统一映射到内部中文 SKU - 下单入口按内部 SKU 渠道决策修正默认人工路由,自动发货补全 dash sku - 财务 channel_assign 读取统一渠道表;卡券/人工能力兜底,自动发货/接单要求真实载体 - carrier reconciliation 自动补自动发货任务和接单工单 - pending 落明确原因:无内部 SKU / 无渠道能力 / 无履约载体 - 合并、重判、旧 sku_code 规则清理、部署后 preflight 核对脚本 - 待分类队列按内部 SKU 聚合并展示原因
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 履约/财务统一分流部署后核对(只读)。
|
||||
*
|
||||
* 用法:
|
||||
* npx tsx scripts/preflight-fulfillment-routing.ts
|
||||
* npx tsx scripts/preflight-fulfillment-routing.ts --strict
|
||||
*/
|
||||
import { closeDb, query } from '../src/db/client.js'
|
||||
|
||||
type CountRow = { label: string; total: number }
|
||||
|
||||
async function main() {
|
||||
const strict = process.argv.includes('--strict')
|
||||
const checks: Record<string, unknown> = {}
|
||||
|
||||
checks.migrations = (
|
||||
await query<{ name: string }>(`SELECT name FROM schema_migrations ORDER BY id DESC LIMIT 5`)
|
||||
).rows.map((row) => row.name)
|
||||
|
||||
checks.pendingReasons = (
|
||||
await query<CountRow>(
|
||||
`SELECT COALESCE(fulfillment_pending_reason, '(null)') AS label, count(*)::int AS total
|
||||
FROM order_items WHERE fulfillment_status = 'pending'
|
||||
GROUP BY 1 ORDER BY 2 DESC`,
|
||||
)
|
||||
).rows
|
||||
|
||||
checks.renameCardChannels = (
|
||||
await query<CountRow>(
|
||||
`SELECT COALESCE(fulfillment_channel, '(pending)') AS label, count(*)::int AS total
|
||||
FROM order_items
|
||||
WHERE sku_name = '11月改名卡'
|
||||
GROUP BY 1 ORDER BY 2 DESC`,
|
||||
)
|
||||
).rows
|
||||
|
||||
checks.rules = (
|
||||
await query<CountRow>(
|
||||
`SELECT match_key_type || '/' || channel || '/' ||
|
||||
CASE WHEN enabled THEN 'on' ELSE 'off' END AS label, count(*)::int AS total
|
||||
FROM sku_channel_rules GROUP BY 1 ORDER BY 2 DESC`,
|
||||
)
|
||||
).rows
|
||||
|
||||
checks.internalChannels = (
|
||||
await query<CountRow>(
|
||||
`SELECT channel AS label, count(*)::int AS total
|
||||
FROM internal_sku_channels WHERE enabled GROUP BY 1 ORDER BY 2 DESC`,
|
||||
)
|
||||
).rows
|
||||
|
||||
checks.pendingWithoutChannel = (
|
||||
await query<CountRow>(
|
||||
`SELECT 'pending_no_internal_channel' AS label, count(*)::int AS total
|
||||
FROM order_items oi
|
||||
WHERE oi.fulfillment_status = 'pending'
|
||||
AND btrim(COALESCE(oi.manual_sku_key, '')) <> ''
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM internal_sku_channels c
|
||||
WHERE c.enabled AND btrim(c.internal_sku) = btrim(oi.manual_sku_key)
|
||||
)`,
|
||||
)
|
||||
).rows[0]
|
||||
|
||||
checks.channelCarrier = (
|
||||
await query<{
|
||||
channel: string
|
||||
total: number
|
||||
work_orders: number
|
||||
affiliate_tasks: number
|
||||
manual_tasks: number
|
||||
}>(
|
||||
`SELECT COALESCE(fulfillment_channel, '(pending)') AS channel,
|
||||
count(*)::int AS total,
|
||||
count(*) FILTER (WHERE EXISTS (SELECT 1 FROM work_orders wo WHERE wo.order_item_id = oi.id))::int AS work_orders,
|
||||
count(*) FILTER (WHERE EXISTS (SELECT 1 FROM fulfillment_tasks ft WHERE ft.order_item_id = oi.id AND ft.executor_key = 'affiliate_dash'))::int AS affiliate_tasks,
|
||||
count(*) FILTER (WHERE EXISTS (SELECT 1 FROM fulfillment_tasks ft WHERE ft.order_item_id = oi.id AND ft.executor_key = 'manual_dispatch'))::int AS manual_tasks
|
||||
FROM order_items oi
|
||||
WHERE oi.fulfillment_status = 'assigned'
|
||||
GROUP BY 1 ORDER BY 2 DESC`,
|
||||
)
|
||||
).rows
|
||||
|
||||
const failed: string[] = []
|
||||
const rename = checks.renameCardChannels as CountRow[]
|
||||
if (rename.some((row) => row.label === 'pending')) failed.push('11月改名卡仍有 pending')
|
||||
if ((checks.pendingWithoutChannel as CountRow).total > 0)
|
||||
failed.push('存在有内部 SKU 但无渠道能力的 pending')
|
||||
|
||||
console.log(JSON.stringify({ strict, failed, ...checks }, null, 2))
|
||||
if (strict && failed.length > 0) process.exitCode = 2
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
process.exitCode = 1
|
||||
})
|
||||
.finally(() => closeDb())
|
||||
Reference in New Issue
Block a user