Files
order_site/apps/backend/scripts/audit-fulfillment-state.ts
T

141 lines
4.3 KiB
TypeScript

#!/usr/bin/env tsx
/**
* 履约状态只读审计。
*
* 用法:
* npm run audit:fulfillment
* npm run audit:fulfillment -- --strict
*
* --strict 会在关键异常大于 0 时返回失败,适合部署后和生产巡检。
*/
import { closeDb, query } from '../src/db/client.js'
const strict = process.argv.includes('--strict')
type CountRow = {
label: string
count: number
}
async function loadCounts(): Promise<CountRow[]> {
const result = await query<{
pending_items: number
classification: number
configuration: number
carrier: number
manual_review: number
after_sales_pending: number
state_inconsistent: number
assigned_missing_carrier: number
}>(
`
SELECT
COUNT(*) FILTER (WHERE issue_code <> '')::int AS pending_items,
COUNT(*) FILTER (WHERE issue_category = 'classification')::int
AS classification,
COUNT(*) FILTER (WHERE issue_category = 'configuration')::int
AS configuration,
COUNT(*) FILTER (WHERE issue_category = 'carrier')::int AS carrier,
COUNT(*) FILTER (WHERE issue_category = 'manual_review')::int
AS manual_review,
COUNT(*) FILTER (
WHERE issue_category = 'after_sales' AND fulfillment_status = 'pending'
)::int AS after_sales_pending,
COUNT(*) FILTER (WHERE issue_code = 'state_inconsistent')::int
AS state_inconsistent,
COUNT(*) FILTER (
WHERE fulfillment_status = 'assigned' AND carrier_status = 'missing'
)::int AS assigned_missing_carrier
FROM v_fulfillment_item_state
`,
)
const row = result.rows[0]
return [
{ label: '待处理订单项', count: Number(row?.pending_items || 0) },
{ label: '未分类', count: Number(row?.classification || 0) },
{ label: '配置待补', count: Number(row?.configuration || 0) },
{ label: '载体异常', count: Number(row?.carrier || 0) },
{ label: '人工复核', count: Number(row?.manual_review || 0) },
{ label: '售后关闭仍进入履约队列', count: Number(row?.after_sales_pending || 0) },
{ label: '状态不一致', count: Number(row?.state_inconsistent || 0) },
{ label: '已分配但无有效载体', count: Number(row?.assigned_missing_carrier || 0) },
]
}
async function loadIssueBreakdown(): Promise<CountRow[]> {
const result = await query<{ label: string; count: number }>(
`
SELECT issue_code AS label, COUNT(*)::int AS count
FROM v_fulfillment_item_state
WHERE issue_code <> ''
GROUP BY issue_code
ORDER BY count DESC, issue_code
`,
)
return result.rows
}
async function loadRecentDuplicateAttempts(): Promise<CountRow[]> {
const result = await query<{ label: string; count: number }>(
`
SELECT
'近 24 小时重复尝试订单项' AS label,
COUNT(*)::int AS count
FROM (
SELECT order_item_id, unit_index
FROM fulfillment_tasks
WHERE created_at >= NOW() - INTERVAL '24 hours'
GROUP BY order_item_id, unit_index
HAVING COUNT(*) > 1
) duplicate
`,
)
return result.rows
}
async function main(): Promise<void> {
const [counts, issues, duplicates] = await Promise.all([
loadCounts(),
loadIssueBreakdown(),
loadRecentDuplicateAttempts(),
])
console.info('[fulfillment-audit] 总览')
for (const row of counts) {
console.info(` ${row.label}: ${row.count}`)
}
console.info('[fulfillment-audit] 异常分布')
if (issues.length === 0) {
console.info(' 无')
} else {
for (const row of issues) {
console.info(` ${row.label}: ${row.count}`)
}
}
console.info('[fulfillment-audit] 尝试审计')
for (const row of duplicates) {
console.info(` ${row.label}: ${row.count}`)
}
const critical = new Map(counts.map((row) => [row.label, Number(row.count || 0)]))
const duplicateCount = Number(duplicates[0]?.count || 0)
const hasCritical =
(critical.get('状态不一致') || 0) > 0 ||
(critical.get('售后关闭仍进入履约队列') || 0) > 0 ||
(critical.get('已分配但无有效载体') || 0) > 0 ||
duplicateCount > 0
if (strict && hasCritical) {
process.exitCode = 1
console.error('[fulfillment-audit] strict 模式检测到关键异常')
}
}
try {
await main()
} finally {
await closeDb()
}