diff --git a/apps/backend/src/repositories/worker-platform/work-order-issue-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-issue-repo.ts index 3c776e0e..c31ba6e6 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-issue-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-issue-repo.ts @@ -143,17 +143,25 @@ export async function resolveProblemWorkOrder(input: { input.action === 'reopen' || (input.action === 'return_to_worker' && targetStatus === 'in_progress') - // 取消处置 = 工单终止:被本工单占用的寄卖现货必须解绑(回库;已确认加好友则作废下架), - // 并同步摘掉 material 上的寄售标记。否则会出现两个后果: - // 1) 列表「打手」列继续显示「供货打手 / 内部现货」,看着像打手还挂在这张已取消的单上; - // 2) 现货永久停在 consumed 占库不还,既不参与匹配也没有押金释放入口。 - // ⚠️ 必须放在下面的 UPDATE 之前:那条 UPDATE 在取消时会把 friend_added_at 清空, + // 问题单处置把工单带离履约态(取消 / 退回大厅)时,被本工单占用的寄卖现货必须解绑 + // (回库;已确认加好友则作废下架),并同步摘掉 material 上的寄售标记。 + // 不解绑会出现两个后果: + // 1) 卖家端「我的寄卖」把这张单显示成「履约中 + 关联订单状态已变化,请联系客服核对」 + // ——状态机只认 in_progress / problem / pending_acceptance,open 落到兜底分支; + // 2) 现货永久停在 consumed 占库不还,既不再参与匹配,也没有押金释放入口。 + // 「退回大厅」也必须解绑:后台退回大厅、超时重开、打手退单通过这三条同义路径都会解绑, + // 只有这里漏了(口径不一致,且订单回到大厅后本单已不承担交付)。 + // 退回打手重做(in_progress / pending_acceptance)保持占用:货还在这一单上履约。 + // ⚠️ 必须放在下面的 UPDATE 之前:那条 UPDATE 在 open/cancelled 时会把 friend_added_at 清空, // 而解绑是否需要改成「作废」正是看这个字段——清空后再解绑会把已加好友的货退回在库池, // 等于二次出货。 - if (targetStatus === 'cancelled') { + if (targetStatus === 'cancelled' || targetStatus === 'open') { await releaseConsignmentInventoryBindingForWorkOrderWithClient(client, input.workOrderId, { now: input.now, - reason: `问题单处置取消(${effectiveAction})`, + reason: + targetStatus === 'cancelled' + ? `问题单处置取消(${effectiveAction})` + : `问题单处置退回大厅(${effectiveAction})`, }) } diff --git a/apps/backend/src/services/worker-platform/worker-platform-service.test.ts b/apps/backend/src/services/worker-platform/worker-platform-service.test.ts index f528fe15..22d7110f 100644 --- a/apps/backend/src/services/worker-platform/worker-platform-service.test.ts +++ b/apps/backend/src/services/worker-platform/worker-platform-service.test.ts @@ -240,7 +240,7 @@ test('没解绑到现货时也必须摘掉工单上的寄售标记(老帖 / assert.equal(detach!.values[3], null, '无现货可解绑时应按工单整体摘标记') }) -test('问题单处置取消:先解绑寄卖现货,再改工单状态', async () => { +test('问题单处置离开履约态(取消 / 退回大厅)都要先解绑寄卖现货', async () => { const readFile = await import('node:fs/promises') const src = await readFile.readFile( new URL('../../repositories/worker-platform/work-order-issue-repo.ts', import.meta.url), @@ -253,13 +253,18 @@ test('问题单处置取消:先解绑寄卖现货,再改工单状态', async const releaseIndex = body.indexOf( 'releaseConsignmentInventoryBindingForWorkOrderWithClient(client, input.workOrderId', ) - assert.ok(releaseIndex > 0, '取消问题单必须解绑被本工单占用的寄卖现货,否则现货永久占库不还') - assert.ok( - body.includes("if (targetStatus === 'cancelled')"), - '只对「取消」处置解绑:退回打手重做 / 退回大厅时货还在履约,不能解绑', - ) + assert.ok(releaseIndex > 0, '处置问题单必须解绑被本工单占用的寄卖现货,否则现货永久占库不还') - // 顺序很重要:那条 UPDATE 会在取消时把 friend_added_at 清空,而"是否改判作废"正是看这个字段。 + // 「退回大厅」也必须解绑:后台退回大厅 / 超时重开 / 打手退单通过这三条同义路径都会解绑, + // 只有问题单这条漏了,导致卖家端一直显示「履约中 + 关联订单状态已变化,请联系客服核对」。 + assert.ok( + body.includes("if (targetStatus === 'cancelled' || targetStatus === 'open')"), + '取消与退回大厅都要解绑', + ) + // 退回打手重做必须保持占用:货还在这一单上履约 + assert.ok(body.includes('return_to_worker'), '未找到退回打手重做的分支(该分支不能解绑)') + + // 顺序很重要:那条 UPDATE 在 open/cancelled 时会把 friend_added_at 清空,而"是否改判作废"正是看这个字段。 // 清空之后再解绑,会把已加好友的现货退回在库池 → 二次出货。 const updateIndex = body.indexOf('UPDATE work_orders') assert.ok(updateIndex > 0, '未找到工单状态更新语句')