fix(consignment): 寄卖改价上限按单价计算并补充单价/合计提示

This commit is contained in:
2026-11-08 12:12:01 +08:00
parent e03abc3a8a
commit 1773fe7837
6 changed files with 204 additions and 23 deletions
@@ -0,0 +1,123 @@
#!/usr/bin/env node
/**
* 修复寄卖「改价上限」被寄卖数量放大的历史数据。
*
* 背景:模板驱动规格曾是 unit 计价模板「模板单价 × 寄卖数量」写进 price_cap_fen,
* 但售价/库存/匹配引擎都是单价口径,导致改价弹窗提示的上限是单价的 N 倍。
* 取价逻辑已改为按单价返回;本脚本把历史脏数据修正为单价。
*
* 只处理命中 bug 特征的行:price_cap_fen === 单价 × quantity 且 quantity > 1。
* 默认 dry-run(只打印),加 --apply 才写库。
*
* 用法(apps/backend 目录下):
* npx tsx scripts/fix-consignment-price-cap.ts
* npx tsx scripts/fix-consignment-price-cap.ts --apply
*/
import process from 'node:process'
import { closeDb, query } from '../src/db/client.js'
import { parseConsignmentSpecValues } from '../src/services/worker-platform/consignment-spec-service.js'
import { getWorkerConsignmentPriceSuggestion } from '../src/services/worker-platform/consignment-worker-service.js'
type CandidateRow = {
id: number
profile_id: number
quantity: number
price_fen: number
price_cap_fen: number
spec_values_json: unknown
}
type Fix = {
table: 'submissions' | 'inventories'
id: number
quantity: number
beforeFen: number
afterFen: number
}
const apply = process.argv.includes('--apply')
async function loadCandidates() {
const submissions = await query<CandidateRow>(
`SELECT id, profile_id, quantity, asking_price_fen AS price_fen, price_cap_fen, spec_values_json
FROM consignment_submissions
WHERE profile_id IS NOT NULL AND quantity > 1 AND price_cap_fen > 0
AND status IN ('pending_review', 'approved')
ORDER BY id`,
)
const inventories = await query<CandidateRow>(
`SELECT id, profile_id, quantity, sale_price_fen AS price_fen, price_cap_fen, spec_values_json
FROM consignment_inventories
WHERE profile_id IS NOT NULL AND quantity > 1 AND price_cap_fen > 0
ORDER BY id`,
)
return { submissions: submissions.rows, inventories: inventories.rows }
}
async function resolveFixes(table: Fix['table'], rows: CandidateRow[]): Promise<Fix[]> {
const fixes: Fix[] = []
for (const row of rows) {
const suggestion = await getWorkerConsignmentPriceSuggestion({
profileId: Number(row.profile_id),
values: parseConsignmentSpecValues(row.spec_values_json),
})
const unitCapFen = Math.max(0, Math.round(Number(suggestion.capFen || 0)))
// pg 的 BIGINT 默认返回字符串,这里统一转数字再比较
const quantity = Math.max(1, Math.round(Number(row.quantity || 1)))
const beforeFen = Math.max(0, Math.round(Number(row.price_cap_fen || 0)))
const priceFen = Math.max(0, Math.round(Number(row.price_fen || 0)))
// bug 特征:上限 = 单价 × 寄卖数量。用「售价」和「当前建议价」两种口径各判一次,
// 避免运营改过折扣后当前建议价与历史值不一致导致漏修。
const inflatedByPrice = priceFen > 0 && beforeFen === Math.round(priceFen * quantity)
const inflatedBySuggestion = unitCapFen > 0 && beforeFen === Math.round(unitCapFen * quantity)
if (!inflatedByPrice && !inflatedBySuggestion) continue
const afterFen = Math.max(0, Math.round(beforeFen / quantity))
if (afterFen <= 0 || afterFen === beforeFen) continue
fixes.push({
table,
id: Number(row.id),
quantity,
beforeFen,
afterFen,
})
}
return fixes
}
const yuan = (fen: number) => `¥${(fen / 100).toFixed(2)}`
try {
const { submissions, inventories } = await loadCandidates()
const fixes = [
...(await resolveFixes('submissions', submissions)),
...(await resolveFixes('inventories', inventories)),
]
console.log(`扫描:提报 ${submissions.length} 条 / 库存 ${inventories.length} 条(数量 > 1)`)
if (fixes.length === 0) {
console.log('没有命中需要修复的数据。')
} else {
console.log('需要修复:')
for (const fix of fixes) {
console.log(
` [${fix.table}] id=${fix.id} 数量=${fix.quantity} 上限 ${yuan(fix.beforeFen)} → ${yuan(fix.afterFen)}`,
)
}
}
if (!apply) {
console.log('\n当前为预览模式。确认后追加 `--apply` 执行写库。')
} else if (fixes.length > 0) {
for (const fix of fixes) {
const table =
fix.table === 'submissions' ? 'consignment_submissions' : 'consignment_inventories'
await query(`UPDATE ${table} SET price_cap_fen = $1 WHERE id = $2`, [fix.afterFen, fix.id])
}
console.log(`\n已修正 ${fixes.length} 条记录。`)
}
} finally {
await closeDb()
}