diff --git a/apps/backend/src/repositories/worker-platform/work-order-cancel-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-cancel-repo.ts index 2b26656b..8df97d13 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-cancel-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-cancel-repo.ts @@ -6,7 +6,7 @@ import { ensureWorkerWalletWithClient, getWorkerWalletWithClient } from './share import { createWorkOrderEventWithClient } from './work-order-event-repo.js' import { getWorkOrderByIdWithClient } from './work-order-query-repo.js' import { getOutstandingDepositAmountWithClient } from './work-order-deposit-repo.js' -import { RESET_GIFT_FLOW_SQL } from './work-order-gift-repo.js' +import { CLEAR_DELIVERY_RESIDUE_SQL, RESET_GIFT_FLOW_SQL } from './work-order-gift-repo.js' import type { WorkOrderRow } from './types.js' export async function cancelWorkerWorkOrder(input: { @@ -51,6 +51,7 @@ export async function cancelWorkerWorkOrder(input: { assigned_at = NULL, deadline_at = NULL, hall_queued_at = $1, + ${CLEAR_DELIVERY_RESIDUE_SQL}, ${RESET_GIFT_FLOW_SQL}, updated_at = $1 WHERE id = $2 @@ -158,6 +159,7 @@ export async function unassignWorkOrder(input: { assigned_worker_id = NULL, assigned_at = NULL, deadline_at = NULL, + ${CLEAR_DELIVERY_RESIDUE_SQL}, ${RESET_GIFT_FLOW_SQL}, updated_at = $1 WHERE id = $2 @@ -281,6 +283,7 @@ export async function returnAssignedWorkOrderToHall(input: { deadline_at = NULL, published_at = $1, hall_queued_at = $1, + ${CLEAR_DELIVERY_RESIDUE_SQL}, ${RESET_GIFT_FLOW_SQL}, updated_at = $1 WHERE id = $2 @@ -404,6 +407,7 @@ export async function cancelAssignedWorkOrder(input: { assigned_at = NULL, deadline_at = NULL, published_at = NULL, + ${CLEAR_DELIVERY_RESIDUE_SQL}, ${RESET_GIFT_FLOW_SQL}, updated_at = $1 WHERE id = $2 @@ -518,11 +522,7 @@ export async function reopenCancelledWorkOrder(input: { assigned_worker_id = NULL, assigned_at = NULL, deadline_at = NULL, - acceptance_json = '{}'::jsonb, - draft_acceptance_json = '{}'::jsonb, - submitted_at = NULL, - accepted_at = NULL, - problem_note = '', + ${CLEAR_DELIVERY_RESIDUE_SQL}, published_at = NULL, pinned_at = NULL, ${RESET_GIFT_FLOW_SQL}, diff --git a/apps/backend/src/repositories/worker-platform/work-order-gift-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-gift-repo.ts index 8e58e12e..27993257 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-gift-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-gift-repo.ts @@ -38,6 +38,19 @@ export const RESET_GIFT_FLOW_SQL = ` gift_available_at = NULL ` +/** + * 脱离上一手交付(撤单、退回大厅、取消指派、超时、重新接单)时清掉验收与备注痕迹。 + * 买家下单资料(material.collect)和寄售履约留痕(consignmentHistory)不在这里动。 + */ +export const CLEAR_DELIVERY_RESIDUE_SQL = ` + submitted_at = NULL, + accepted_at = NULL, + acceptance_json = '{}'::jsonb, + draft_acceptance_json = '{}'::jsonb, + problem_note = '', + worker_note = '' +` + export function parseWorkOrderMaterial(workOrder: { material_json?: unknown }): Record { diff --git a/apps/backend/src/repositories/worker-platform/work-order-grab-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-grab-repo.ts index 21c9247f..cff7767b 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-grab-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-grab-repo.ts @@ -1,4 +1,5 @@ import { withTransaction } from '../../db/client.js' +import { CLEAR_DELIVERY_RESIDUE_SQL } from './work-order-gift-repo.js' import { ensureWorkerWalletWithClient, getWorkerWalletWithClient } from './shared.js' import { createWorkOrderEventWithClient } from './work-order-event-repo.js' import { @@ -66,6 +67,7 @@ export async function grabWorkOrder(input: { booster_material_json = '{}'::jsonb, friend_added_at = NULL, gift_available_at = NULL, + ${CLEAR_DELIVERY_RESIDUE_SQL}, updated_at = $2 WHERE id = $4 AND status = 'open' @@ -190,6 +192,7 @@ export async function assignWorkOrderToWorker(input: { booster_material_json = '{}'::jsonb, friend_added_at = NULL, gift_available_at = NULL, + ${CLEAR_DELIVERY_RESIDUE_SQL}, updated_at = $2 WHERE id = $4 `, 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 c31ba6e6..a02cff63 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 @@ -6,7 +6,7 @@ import { ensureWorkerWalletWithClient, getWorkerWalletWithClient } from './share import { createWorkOrderEventWithClient } from './work-order-event-repo.js' import { getWorkOrderByIdWithClient } from './work-order-query-repo.js' import { getOutstandingDepositAmountWithClient } from './work-order-deposit-repo.js' -import { RESET_GIFT_FLOW_SQL } from './work-order-gift-repo.js' +import { CLEAR_DELIVERY_RESIDUE_SQL, RESET_GIFT_FLOW_SQL } from './work-order-gift-repo.js' import type { ProblemWorkOrderResolutionAction, WorkOrderRow } from './types.js' /** 问题单退回打手时,按标记前状态恢复;异常历史数据回退单人单进行中。 */ @@ -172,9 +172,12 @@ export async function resolveProblemWorkOrder(input: { status = $1, assigned_worker_id = CASE WHEN $2 THEN NULL ELSE assigned_worker_id END, assigned_at = CASE WHEN $2 THEN NULL ELSE assigned_at END, - acceptance_json = CASE WHEN $3 THEN '{}'::jsonb ELSE acceptance_json END, - submitted_at = CASE WHEN $3 THEN NULL ELSE submitted_at END, - problem_note = CASE WHEN $2 THEN '' ELSE problem_note END, + acceptance_json = CASE WHEN $1 IN ('open', 'cancelled') OR $3 THEN '{}'::jsonb ELSE acceptance_json END, + draft_acceptance_json = CASE WHEN $1 IN ('open', 'cancelled') THEN '{}'::jsonb ELSE draft_acceptance_json END, + submitted_at = CASE WHEN $1 IN ('open', 'cancelled') OR $3 THEN NULL ELSE submitted_at END, + accepted_at = CASE WHEN $1 IN ('open', 'cancelled') THEN NULL ELSE accepted_at END, + problem_note = CASE WHEN $2 OR $1 IN ('open', 'cancelled') THEN '' ELSE problem_note END, + worker_note = CASE WHEN $1 IN ('open', 'cancelled') THEN '' ELSE worker_note END, deadline_at = CASE WHEN $1 = 'in_progress' AND timeout_minutes > 0 THEN CASE @@ -348,9 +351,7 @@ export async function settleOverdueWorkOrder(input: { assigned_worker_id = NULL, assigned_at = NULL, deadline_at = NULL, - acceptance_json = CASE WHEN $1 = 'open' THEN '{}'::jsonb ELSE acceptance_json END, - submitted_at = CASE WHEN $1 = 'open' THEN NULL ELSE submitted_at END, - problem_note = CASE WHEN $1 = 'open' THEN '' ELSE problem_note END, + ${CLEAR_DELIVERY_RESIDUE_SQL}, published_at = CASE WHEN $1 = 'open' THEN $2 ELSE published_at END, hall_queued_at = CASE WHEN $1 = 'open' THEN $2 ELSE hall_queued_at END, ${RESET_GIFT_FLOW_SQL}, diff --git a/apps/backend/src/repositories/worker-platform/work-order-settlement-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-settlement-repo.ts index ddb7cf48..e044958f 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-settlement-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-settlement-repo.ts @@ -7,6 +7,7 @@ import { resolveWorkOrderShareCancellationStatus, resolveWorkOrderSharingCancellationPlan, } from './shared.js' +import { CLEAR_DELIVERY_RESIDUE_SQL, RESET_GIFT_FLOW_SQL } from './work-order-gift-repo.js' import { createWorkOrderEventWithClient } from './work-order-event-repo.js' import { getWorkOrderByIdWithClient } from './work-order-query-repo.js' import { @@ -807,7 +808,12 @@ export async function cancelWorkOrderShare(input: { await client.query( `UPDATE work_order_shares - SET status = 'cancelled', updated_at = $1 + SET status = 'cancelled', + acceptance_json = '{}'::jsonb, + draft_acceptance_json = '{}'::jsonb, + submitted_at = NULL, + accepted_at = NULL, + updated_at = $1 WHERE id = $2`, [input.now, input.shareId], ) @@ -963,14 +969,22 @@ export async function cancelWorkOrderSharing(input: { ] await client.query( `UPDATE work_order_shares - SET status = 'cancelled', updated_at = $1 + SET status = 'cancelled', + acceptance_json = '{}'::jsonb, + draft_acceptance_json = '{}'::jsonb, + submitted_at = NULL, + accepted_at = NULL, + updated_at = $1 WHERE id = ANY($2::bigint[])`, [input.now, cancelledShareIds], ) if (input.action === 'return_to_hall') { await client.query( `UPDATE work_orders - SET status = 'open', submitted_at = NULL, published_at = COALESCE(published_at, $1), + SET status = 'open', + ${CLEAR_DELIVERY_RESIDUE_SQL}, + ${RESET_GIFT_FLOW_SQL}, + published_at = COALESCE(published_at, $1), hall_queued_at = $1, updated_at = $1 WHERE id = $2`, [input.now, input.workOrderId], @@ -979,7 +993,9 @@ export async function cancelWorkOrderSharing(input: { await client.query( `UPDATE work_orders SET status = 'cancelled', assigned_worker_id = NULL, assigned_at = NULL, deadline_at = NULL, - submitted_at = NULL, published_at = NULL, pinned_at = NULL, updated_at = $1 + ${CLEAR_DELIVERY_RESIDUE_SQL}, + ${RESET_GIFT_FLOW_SQL}, + published_at = NULL, pinned_at = NULL, updated_at = $1 WHERE id = $2`, [input.now, input.workOrderId], ) diff --git a/apps/backend/src/repositories/worker-platform/worker-cancel-request-repo.ts b/apps/backend/src/repositories/worker-platform/worker-cancel-request-repo.ts index 26805382..f4b3c5bd 100644 --- a/apps/backend/src/repositories/worker-platform/worker-cancel-request-repo.ts +++ b/apps/backend/src/repositories/worker-platform/worker-cancel-request-repo.ts @@ -9,6 +9,7 @@ import { getWorkerWalletWithClient, resolveWorkOrderShareCancellationStatus, } from './shared.js' +import { CLEAR_DELIVERY_RESIDUE_SQL, RESET_GIFT_FLOW_SQL } from './work-order-gift-repo.js' import { createWorkOrderEventWithClient } from './work-order-event-repo.js' import { getOutstandingDepositAmountWithClient, @@ -531,13 +532,24 @@ export async function reviewWorkerCancelRequest(input: { ) } await client.query( - `UPDATE work_order_shares SET status = 'cancelled', updated_at = $1 WHERE id = $2`, + `UPDATE work_order_shares + SET status = 'cancelled', + acceptance_json = '{}'::jsonb, + draft_acceptance_json = '{}'::jsonb, + submitted_at = NULL, + accepted_at = NULL, + updated_at = $1 + WHERE id = $2`, [input.now, share.id], ) await client.query( `UPDATE work_orders - SET status = 'open', submitted_at = NULL, published_at = COALESCE(published_at, $1), - hall_queued_at = COALESCE(hall_queued_at, $1), updated_at = $1 + SET status = 'open', + ${CLEAR_DELIVERY_RESIDUE_SQL}, + ${RESET_GIFT_FLOW_SQL}, + published_at = COALESCE(published_at, $1), + hall_queued_at = COALESCE(hall_queued_at, $1), + updated_at = $1 WHERE id = $2`, [input.now, workOrder.id], ) @@ -565,18 +577,20 @@ export async function reviewWorkerCancelRequest(input: { ), Number(wallet?.frozen_deposit_amount || 0), ) - await client.query( - `UPDATE work_orders - SET status = 'open', assigned_worker_id = NULL, assigned_at = NULL, deadline_at = NULL, - published_at = $1, hall_queued_at = $1, updated_at = $1 - WHERE id = $2`, - [input.now, workOrder.id], - ) - // 打手退单申请通过:工单回到大厅,被本工单占用的寄卖现货同时放回在库池。 + // 先按当前 friend_added_at 解绑现货,再清交付痕迹。清空之后再解绑会丢掉「已加好友」判断。 await releaseConsignmentInventoryBindingForWorkOrderWithClient(client, workOrder.id, { now: input.now, reason: '打手退单申请通过', }) + await client.query( + `UPDATE work_orders + SET status = 'open', assigned_worker_id = NULL, assigned_at = NULL, deadline_at = NULL, + ${CLEAR_DELIVERY_RESIDUE_SQL}, + ${RESET_GIFT_FLOW_SQL}, + published_at = $1, hall_queued_at = $1, updated_at = $1 + WHERE id = $2`, + [input.now, workOrder.id], + ) if (releasedDepositAmount > 0) { const nextAvailable = Number(wallet?.available_amount || 0) + (forfeitGiftDeposit ? 0 : releasedDepositAmount) diff --git a/apps/backend/src/services/worker-platform/consignment-admin-service.ts b/apps/backend/src/services/worker-platform/consignment-admin-service.ts index 8aaa081b..17cc20bd 100644 --- a/apps/backend/src/services/worker-platform/consignment-admin-service.ts +++ b/apps/backend/src/services/worker-platform/consignment-admin-service.ts @@ -156,6 +156,10 @@ function mapConsignmentEvidence(source: { } export function mapConsignmentInventory(row: ConsignmentInventoryRow) { + // 买家资料和赠送凭证都挂在「当前绑定的工单」上。退库后现货回到卖家仓库, + // 只保留卖家自己的提报信息,不再把上一单买家角色、主页截图、赠送凭证带出来。 + const showingMatchedBuyer = row.status === 'consumed' + const evidence = mapConsignmentEvidence(row) return { id: Number(row.id), inventoryNo: row.inventory_no, @@ -170,7 +174,7 @@ export function mapConsignmentInventory(row: ConsignmentInventoryRow) { brillianceCount: Number(row.brilliance_count || 0), costPriceFen: Number(row.cost_price_fen || 0), buyerName: row.buyer_name || '', - shopName: row.shop_name || '', + shopName: showingMatchedBuyer ? row.shop_name || '' : '', inboundBatch: row.inbound_batch || '', sellerContact: row.seller_contact || '', sellerGameId: row.seller_game_id || '', @@ -191,17 +195,32 @@ export function mapConsignmentInventory(row: ConsignmentInventoryRow) { allowMatching: Boolean(row.allow_matching), status: row.status, allocationStatus: row.allocation_status || '待安排', - boundWorkOrderId: row.bound_work_order_id ? Number(row.bound_work_order_id) : null, - boundPlatformOrderId: row.bound_platform_order_id || '', + boundWorkOrderId: + showingMatchedBuyer && row.bound_work_order_id ? Number(row.bound_work_order_id) : null, + boundPlatformOrderId: showingMatchedBuyer ? row.bound_platform_order_id || '' : '', sourceType: row.source_type, sourceSubmissionId: row.source_submission_id ? Number(row.source_submission_id) : null, /** 唯一锚点:卖家端看到的「寄卖单号」就是它,客服端也用同一个号 */ sourceSubmissionNo: row.source_submission_no || '', - ...mapConsignmentEvidence(row), - buyerInfo: extractConsignmentBuyerInfo({ - materialJson: row.bound_work_order_material_json, - requirementJson: row.bound_work_order_requirement_json, - }), + ...evidence, + ...(showingMatchedBuyer + ? {} + : { + giftEvidenceImages: [], + giftEvidenceNote: '', + giftEvidenceSubmittedAt: '', + giftWorkOrderNo: '', + giftWorkOrderStatus: '', + giftPhase: '', + giftFriendAddedAt: '', + giftEvidenceReviewStatus: 'none' as const, + }), + buyerInfo: showingMatchedBuyer + ? extractConsignmentBuyerInfo({ + materialJson: row.bound_work_order_material_json, + requirementJson: row.bound_work_order_requirement_json, + }) + : null, workerId: row.worker_id ? Number(row.worker_id) : null, workerName: row.worker_name || '', workerPhone: row.worker_phone || '', diff --git a/apps/backend/src/services/worker-platform/consignment-evidence.test.ts b/apps/backend/src/services/worker-platform/consignment-evidence.test.ts index b04f47b6..f92cd6d8 100644 --- a/apps/backend/src/services/worker-platform/consignment-evidence.test.ts +++ b/apps/backend/src/services/worker-platform/consignment-evidence.test.ts @@ -27,6 +27,37 @@ function inventoryRow(overrides: Record = {}) { } as never } +test('退库后不再带出上一单买家资料和赠送凭证,卖家提报截图还在', () => { + const mapped = mapConsignmentInventory( + inventoryRow({ + status: 'in_stock', + bound_work_order_id: 99, + bound_platform_order_id: '260930-371164485221051', + shop_name: '木塔网游专营店', + gift_evidence_json: { + note: '已赠送', + imageUrls: ['https://cdn.example.com/gift.png'], + }, + gift_work_order_no: 'WO1', + gift_work_order_status: 'in_progress', + bound_work_order_material_json: { + collect: { + fields: { gameNickname: '低陀小情在快手', gameId: '3489287354' }, + screenshots: ['https://cdn.example.com/buyer.png'], + }, + }, + }), + ) + + assert.equal(mapped.buyerInfo, null) + assert.deepEqual(mapped.giftEvidenceImages, []) + assert.equal(mapped.giftWorkOrderNo, '') + assert.equal(mapped.boundWorkOrderId, null) + assert.equal(mapped.boundPlatformOrderId, '') + assert.equal(mapped.shopName, '') + assert.deepEqual(mapped.submissionScreenshots, ['https://cdn.example.com/publish-1.png']) +}) + test('赠送凭证从工单验收资料的 imageUrls 与 files 两处取图并去重', () => { const mapped = mapConsignmentInventory( inventoryRow({ diff --git a/apps/backend/src/services/worker-platform/consignment-fulfillment-service.ts b/apps/backend/src/services/worker-platform/consignment-fulfillment-service.ts index 930ba81b..e379dfc8 100644 --- a/apps/backend/src/services/worker-platform/consignment-fulfillment-service.ts +++ b/apps/backend/src/services/worker-platform/consignment-fulfillment-service.ts @@ -17,6 +17,7 @@ import { } from '../../repositories/worker-platform/consignment-deposit-repo.js' import { CONSIGNMENT_FULFILLMENT_TYPE, + CLEAR_DELIVERY_RESIDUE_SQL, RESET_GIFT_FLOW_SQL, createWorkOrderEvent, createWorkOrderEventWithClient, @@ -1103,11 +1104,7 @@ export async function revokeWorkOrderConsignmentFulfillment(input: { assigned_worker_id = NULL, assigned_at = NULL, deadline_at = NULL, - submitted_at = NULL, - accepted_at = NULL, - acceptance_json = '{}'::jsonb, - draft_acceptance_json = '{}'::jsonb, - problem_note = '', + ${CLEAR_DELIVERY_RESIDUE_SQL}, published_at = NULL, pinned_at = NULL, ${RESET_GIFT_FLOW_SQL}, diff --git a/apps/backend/src/services/worker-platform/consignment-worker-service.ts b/apps/backend/src/services/worker-platform/consignment-worker-service.ts index 5cdbced9..2eeeb982 100644 --- a/apps/backend/src/services/worker-platform/consignment-worker-service.ts +++ b/apps/backend/src/services/worker-platform/consignment-worker-service.ts @@ -433,13 +433,17 @@ export async function listWorkerConsignments( ? workOrderById.get(Number(inv.bound_work_order_id)) : undefined const stage = resolveStage(String(sub.status || ''), inv) - const evidence = extractConsignmentGiftEvidenceFromWorkOrder(boundWo) + // 只有仍被本单占用的现货才展示买家。退回仓库后这件货还是卖家的,上一单买家资料不再跟着走。 + const showingMatchedBuyer = inv?.status === 'consumed' && Boolean(boundWo) + const evidence = showingMatchedBuyer + ? extractConsignmentGiftEvidenceFromWorkOrder(boundWo) + : { giftEvidenceImages: [], giftEvidenceNote: '', giftEvidenceSubmittedAt: '' } return { /** 唯一锚点:提报单号贯穿发布→审核→在库→匹配→冷却→赠送→结算 */ consignmentNo: sub.submission_no, submissionId: sub.id, inventoryId: inv?.id ?? null, - workOrderId: inv?.bound_work_order_id ?? null, + workOrderId: showingMatchedBuyer ? (inv?.bound_work_order_id ?? null) : null, itemName: sub.item_name, system: sub.system, serverZone: sub.server_zone, @@ -459,10 +463,10 @@ export async function listWorkerConsignments( createdAt: sub.created_at, ...stage, /** 冷却截止时间,前端拿它显示倒计时 */ - giftAvailableAt: boundWo?.gift_available_at ?? null, - friendAddedAt: boundWo?.friend_added_at ?? null, + giftAvailableAt: showingMatchedBuyer ? (boundWo?.gift_available_at ?? null) : null, + friendAddedAt: showingMatchedBuyer ? (boundWo?.friend_added_at ?? null) : null, sellerInfo: extractConsignmentSellerInfoFromSubmission(sub), - buyerInfo: extractConsignmentBuyerInfoFromWorkOrder(boundWo), + buyerInfo: showingMatchedBuyer ? extractConsignmentBuyerInfoFromWorkOrder(boundWo) : null, } } @@ -472,7 +476,10 @@ export async function listWorkerConsignments( ? workOrderById.get(Number(inv.bound_work_order_id)) : undefined const stage = resolveStage('approved', inv) - const evidence = extractConsignmentGiftEvidenceFromWorkOrder(boundWo) + const showingMatchedBuyer = inv.status === 'consumed' && Boolean(boundWo) + const evidence = showingMatchedBuyer + ? extractConsignmentGiftEvidenceFromWorkOrder(boundWo) + : { giftEvidenceImages: [], giftEvidenceNote: '', giftEvidenceSubmittedAt: '' } return { consignmentNo: '客服手工入库', ...(stage.canConfirmFriend || stage.canSubmitEvidence @@ -480,7 +487,7 @@ export async function listWorkerConsignments( : {}), submissionId: null, inventoryId: inv.id, - workOrderId: inv.bound_work_order_id ?? null, + workOrderId: showingMatchedBuyer ? inv.bound_work_order_id : null, itemName: inv.item_name, system: inv.system, serverZone: inv.server_zone, @@ -496,10 +503,10 @@ export async function listWorkerConsignments( giftEvidenceSubmittedAt: evidence.giftEvidenceSubmittedAt, createdAt: inv.created_at, ...stage, - giftAvailableAt: boundWo?.gift_available_at ?? null, - friendAddedAt: boundWo?.friend_added_at ?? null, + giftAvailableAt: showingMatchedBuyer ? (boundWo?.gift_available_at ?? null) : null, + friendAddedAt: showingMatchedBuyer ? (boundWo?.friend_added_at ?? null) : null, sellerInfo: extractConsignmentSellerInfoFromInventory(inv), - buyerInfo: extractConsignmentBuyerInfoFromWorkOrder(boundWo), + buyerInfo: showingMatchedBuyer ? extractConsignmentBuyerInfoFromWorkOrder(boundWo) : null, } }