fix(consignment): 撤销寄售履约回库分支必 500 —— SQL 参数比占位符多一个
线上客服 kf_13 撤销 WO26092609475746 时返回 500 internal_error, 真实库报:bind message supplies 5 parameters, but prepared statement "" requires 4。 根因:解绑现货的两条分支共用一个 5 元参数数组,但回库/取消分支的 SQL 只用到 $1..$4(第 5 个「备注前缀」只有作废分支才用)。于是运营只要选 「退单回库并恢复工单为未指派」或「退单回库并取消工单」就必定 500, 而假 client 的单测与类型检查都看不到这类错。 修复: - 解绑 SQL 与参数抽到 consignment-revoke-inventory-sql.ts 一起构造, 回库/取消分支只传 4 个参数,作废分支才多带备注前缀($5); - 单测锁定「占位符个数 == 参数个数」,覆盖 reopen / archive_inventory / 已确认加好友自动作废三条分支; - db 层加参数个数自检(db/sql-arity.ts + db/client.ts):参数比占位符多时 提前抛出带 SQL 片段的错误,不再让 pg 的 bind 报错裸奔, 并跳过字符串/注释/dollar-quote 里的 $n,避免误杀。 验证:本地影子库端到端跑通三条分支(工单 100140 + 现货 INV26091917439822), 并复现修复前的 bind 报错;全量单测 588 通过,typecheck/lint/build 均通过。
This commit is contained in:
@@ -39,6 +39,7 @@ import type {
|
||||
WorkerDepositHandling,
|
||||
} from '../../domain/consignment-deposit-policy.js'
|
||||
import { settleWorkOrderDepositWithClient } from '../../repositories/worker-platform/work-order-deposit-repo.js'
|
||||
import { buildConsignmentRevokeInventoryUpdate } from '../../repositories/worker-platform/consignment-revoke-inventory-sql.js'
|
||||
import { nowIso } from '../../utils/time.js'
|
||||
import { safeParseJson } from '../admin/admin-query-utils.js'
|
||||
import { publishWorkOrderRealtimeChange } from '../realtime/realtime-event-service.js'
|
||||
@@ -1003,44 +1004,19 @@ export async function revokeWorkOrderConsignmentFulfillment(input: {
|
||||
// 都只能作废下架,否则这件现货会回到在库池被下一个工单再次占用(二次出货)。
|
||||
const autoForfeit = isWorkOrderGiftDepositForfeitable(lockedOrder)
|
||||
const archiveOnly = autoForfeit || input.action === 'archive_inventory'
|
||||
const invSql = archiveOnly
|
||||
? `
|
||||
UPDATE consignment_inventories
|
||||
SET
|
||||
status = 'archived',
|
||||
allow_matching = FALSE,
|
||||
bound_order_id = NULL,
|
||||
bound_work_order_id = NULL,
|
||||
bound_platform_order_id = '',
|
||||
shop_name = '',
|
||||
remark = remark || ($5::text || $1 || ']'),
|
||||
updated_at = $2
|
||||
WHERE id = $3 AND status = 'consumed' AND bound_work_order_id = $4
|
||||
RETURNING *
|
||||
`
|
||||
: `
|
||||
UPDATE consignment_inventories
|
||||
SET
|
||||
status = 'in_stock',
|
||||
allocation_status = '待安排',
|
||||
assigned_worker_id = NULL,
|
||||
bound_order_id = NULL,
|
||||
bound_work_order_id = NULL,
|
||||
bound_platform_order_id = '',
|
||||
shop_name = '',
|
||||
consumed_at = NULL,
|
||||
remark = remark || ' [撤销回库: ' || $1 || ']',
|
||||
updated_at = $2
|
||||
WHERE id = $3 AND status = 'consumed' AND bound_work_order_id = $4
|
||||
RETURNING *
|
||||
`
|
||||
const invRes = await client.query<ConsignmentInventoryRow>(invSql, [
|
||||
// SQL 与参数必须一起构造:作废分支要 5 个参数,回库分支只用 4 个。
|
||||
// 之前两条分支共用 5 元参数数组,回库时 PostgreSQL 直接报
|
||||
// "bind message supplies 5 parameters, but prepared statement requires 4",
|
||||
// 于是「退单回库」这条最常用的路径必定 500。
|
||||
const { sql: invSql, params: invParams } = buildConsignmentRevokeInventoryUpdate({
|
||||
archiveOnly,
|
||||
autoForfeit,
|
||||
reason,
|
||||
now,
|
||||
inventoryId,
|
||||
lockedOrder.id,
|
||||
autoForfeit ? ' [已确认加好友作废下架: ' : ' [交付异常作废: ',
|
||||
])
|
||||
workOrderId: lockedOrder.id,
|
||||
})
|
||||
const invRes = await client.query<ConsignmentInventoryRow>(invSql, invParams)
|
||||
inv = invRes.rows[0] || null
|
||||
if (!inv) {
|
||||
throw createHttpError('该现货已被其他操作解绑或占用,请刷新工单后重试', {
|
||||
|
||||
@@ -34,6 +34,15 @@ test('撤销寄售履约的现货去向由押金规则与运营选择共同决
|
||||
|
||||
test('已确认加好友后作废现货:必须关掉匹配开关并留痕', async () => {
|
||||
const source = await readServiceSource()
|
||||
assert.ok(source.includes('allow_matching = FALSE'))
|
||||
assert.ok(source.includes('已确认加好友作废下架'))
|
||||
// 解绑 SQL 已抽到 consignment-revoke-inventory-sql.ts(参数个数由该模块自己的单测锁定)
|
||||
const sqlSource = await readFile(
|
||||
new URL(
|
||||
'../../repositories/worker-platform/consignment-revoke-inventory-sql.ts',
|
||||
import.meta.url,
|
||||
),
|
||||
'utf8',
|
||||
)
|
||||
assert.ok(source.includes('buildConsignmentRevokeInventoryUpdate('))
|
||||
assert.ok(sqlSource.includes('allow_matching = FALSE'))
|
||||
assert.ok(sqlSource.includes('已确认加好友作废下架'))
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user