fix(consignment): 撤销寄售履约按选择回库,不再因加好友强制作废扣押金

买家退款时应能把现货退回仓库继续匹配;卖家押金只在客服显式选择扣除时才没收。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yml2213
2026-10-05 14:28:38 +08:00
co-authored by Cursor
parent 64110aa214
commit dbdafd3dc4
6 changed files with 45 additions and 75 deletions
@@ -27,7 +27,6 @@ import {
type WorkOrderRow,
} from '../../repositories/worker-platform/index.js'
import { createHttpError } from '../../utils/http.js'
import { isWorkOrderGiftDepositForfeitable } from '../../domain/work-order-acceptance-mode.js'
import {
normalizeConsignmentDepositHandling,
normalizeWorkerDepositHandling,
@@ -1000,17 +999,16 @@ export async function revokeWorkOrderConsignmentFulfillment(input: {
// 1. 处理关联库存解绑与状态流转。
// 必须带「仍被本工单占用」条件:否则会把别的工单正在使用的现货抢过来解绑,
// 造成同一件现货被多张工单履约。
// 已确认加好友 = 买家好友已加上、货随时可能送出去:不论运营选的是回库还是作废,
// 都只能作废下架,否则这件现货会回到在库池被下一个工单再次占用(二次出货)。
const autoForfeit = isWorkOrderGiftDepositForfeitable(lockedOrder)
const archiveOnly = autoForfeit || input.action === 'archive_inventory'
// 现货去向只看运营选择:回库 / 取消工单都退回在库继续匹配,只有显式「报废该现货」才作废。
// 已确认加好友不再强制作废。买家退款时货还在,回库才能继续卖;二次出货由运营确认后再选回库。
const archiveOnly = input.action === 'archive_inventory'
// SQL 与参数必须一起构造:作废分支要 5 个参数,回库分支只用 4 个。
// 之前两条分支共用 5 元参数数组,回库时 PostgreSQL 直接报
// "bind message supplies 5 parameters, but prepared statement requires 4",
// 于是「退单回库」这条最常用的路径必定 500。
const { sql: invSql, params: invParams } = buildConsignmentRevokeInventoryUpdate({
archiveOnly,
autoForfeit,
autoForfeit: false,
reason,
now,
inventoryId,
@@ -1025,10 +1023,9 @@ export async function revokeWorkOrderConsignmentFulfillment(input: {
})
}
// 卖家发布押金:默认走规则(已确认加好友→没收;作废→退还;回库→保持冻结),
// 运营可以显式覆盖成"保留冻结"或"扣除没收"。
// 卖家发布押金按选择走:auto 时回库保持冻结、作废退还;只有显式「扣除没收」才扣。
// 买家退款不因已确认加好友而没收卖家押金。
const consignmentDepositAction = resolveConsignmentDepositAction(input.consignmentDeposit, {
autoForfeit,
archive: archiveOnly,
})
const submissionId = consignmentInfo.sourceSubmissionId || inv.source_submission_id
@@ -1054,7 +1051,7 @@ export async function revokeWorkOrderConsignmentFulfillment(input: {
// 打手履约押金:撤销寄售履约会把工单打回未指派/已取消,必须同时结算打手押金,
// 否则这笔钱会一直冻着(以前这里只清 assigned_worker_id,押金没有出口)。
const revokedWorkerId = Number(lockedOrder.assigned_worker_id || 0)
const workerDepositMode = resolveWorkerDepositMode(input.workerDeposit, autoForfeit)
const workerDepositMode = resolveWorkerDepositMode(input.workerDeposit)
let workerDepositAmount = 0
if (revokedWorkerId > 0) {
workerDepositAmount = await settleWorkOrderDepositWithClient(client, {
@@ -17,13 +17,13 @@ test('撤销寄售履约必须结算打手履约押金,不能只清 assigned_w
assert.ok(source.includes('撤销寄售履约扣除履约押金'))
})
test('撤销寄售履约的现货去向由押金规则与运营选择共同决定', async () => {
test('撤销寄售履约的现货去向和押金都按运营选择,已确认加好友不再强制', async () => {
const source = await readServiceSource()
assert.ok(source.includes('const autoForfeit = isWorkOrderGiftDepositForfeitable(lockedOrder)'))
assert.ok(
source.includes("const archiveOnly = autoForfeit || input.action === 'archive_inventory'"),
)
assert.ok(source.includes("const archiveOnly = input.action === 'archive_inventory'"))
assert.ok(!source.includes('isWorkOrderGiftDepositForfeitable'))
assert.ok(!source.includes('autoForfeit || input.action'))
assert.ok(source.includes('resolveConsignmentDepositAction(input.consignmentDeposit'))
assert.ok(source.includes('resolveWorkerDepositMode(input.workerDeposit)'))
// 押金去向要写进工单事件,方便对账
assert.ok(
source.includes('workerDeposit: { mode: workerDepositMode, amount: workerDepositAmount }'),
@@ -2489,7 +2489,11 @@ test('确认加好友后的取消/退单/作废路径都要没收卖家发布押
)
assert.ok(
revokeBody.includes('forfeitConsignmentPublishDepositBySubmissionWithClient('),
'撤销寄售履约在已确认加好友后不能再退卖家押金',
'撤销寄售履约在运营显式选择扣除时仍要没收卖家押金',
)
assert.ok(
revokeBody.includes("const archiveOnly = input.action === 'archive_inventory'"),
'撤销寄售履约不能再因已确认加好友强制作废',
)
})