From 439888631f3103520641a9dce59514a1961d7f87 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 19 Sep 2026 10:22:09 +0800 Subject: [PATCH] =?UTF-8?q?fix(worker-platform):=20=E5=86=85=E9=83=A8?= =?UTF-8?q?=E5=AF=84=E5=94=AE=E7=8E=B0=E8=B4=A7=E5=B1=A5=E7=BA=A6=E5=8F=91?= =?UTF-8?q?=E5=8D=95=E9=87=91=E9=A2=9D=E5=AF=B9=E9=BD=90=E5=AF=84=E5=8D=96?= =?UTF-8?q?=E6=88=90=E6=9C=AC=E4=BB=B7=E5=B9=B6=E8=A1=A5=E5=85=85=E7=95=99?= =?UTF-8?q?=E7=97=95=E8=BF=BD=E6=BA=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 内部现货履约时原子更新工单 reward_amount 为寄卖结算成本价,解决工单列表显示旧规则价与寄卖成本冲突问题; 2. 工单资料 material.consignment 与工单事件 flow 补充 previousRewardAmountFen/updatedRewardAmountFen/costPriceFen 等价格变更留痕; 3. 撤销寄售履约且退回未指派状态时支持自动还原原工单发单金额并在撤销事件中记录还原明细; 4. 新增 consignment-fulfillment-price-trace.test.ts 专项单测并通过全量测试回归。 --- .../worker-platform/work-order-gift-repo.ts | 16 +++++- ...onsignment-fulfillment-price-trace.test.ts | 38 ++++++++++++++ .../consignment-fulfillment-service.ts | 51 ++++++++++++++++--- 3 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 apps/backend/src/services/worker-platform/consignment-fulfillment-price-trace.test.ts 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 49493d97..8e58e12e 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 @@ -99,6 +99,7 @@ export async function parkWorkOrderWaitingFriendAddWithClient( actorName?: string | undefined eventType?: string | undefined materialJson?: unknown + rewardAmountFen?: number | null | undefined extraPayload?: Record }, ): Promise { @@ -121,6 +122,10 @@ export async function parkWorkOrderWaitingFriendAddWithClient( : JSON.stringify(input.materialJson) const materialForInternalCheck = input.materialJson === undefined ? workOrder.material_json : input.materialJson + const targetRewardAmountFen = + input.rewardAmountFen !== undefined && input.rewardAmountFen !== null + ? Math.max(0, Math.round(Number(input.rewardAmountFen))) + : null await client.query( ` UPDATE work_orders @@ -134,10 +139,19 @@ export async function parkWorkOrderWaitingFriendAddWithClient( deadline_at = NULL, accepted_at = CASE WHEN $4 THEN NULL ELSE accepted_at END, material_json = COALESCE($5::jsonb, material_json), + reward_amount = COALESCE($7, reward_amount), updated_at = $2::timestamptz WHERE id = $6 `, - [nextStatus, input.now, cooldownHours, clearAcceptedAt, materialJson, workOrder.id], + [ + nextStatus, + input.now, + cooldownHours, + clearAcceptedAt, + materialJson, + workOrder.id, + targetRewardAmountFen, + ], ) await createWorkOrderEventWithClient(client, { workOrderId: Number(workOrder.id), diff --git a/apps/backend/src/services/worker-platform/consignment-fulfillment-price-trace.test.ts b/apps/backend/src/services/worker-platform/consignment-fulfillment-price-trace.test.ts new file mode 100644 index 00000000..36f029a2 --- /dev/null +++ b/apps/backend/src/services/worker-platform/consignment-fulfillment-price-trace.test.ts @@ -0,0 +1,38 @@ +import assert from 'node:assert/strict' +import test from 'node:test' +import { readFile } from 'node:fs/promises' + +const FULFILLMENT_SERVICE = new URL('./consignment-fulfillment-service.ts', import.meta.url) +const GIFT_REPO = new URL('../../repositories/worker-platform/work-order-gift-repo.ts', import.meta.url) + +test('内部现货履约必须将工单发单金额更新为寄卖成本价并记录留痕', async () => { + const source = await readFile(FULFILLMENT_SERVICE, 'utf8') + assert.ok(source.includes('const previousRewardAmountFen = Number(lockedOrder.reward_amount || 0)')) + assert.ok(source.includes('const costPriceFen = Math.max(0, Math.round(Number(inv.cost_price_fen || 0)))')) + assert.ok(source.includes('const effectiveRewardAmountFen = costPriceFen > 0 ? costPriceFen : previousRewardAmountFen')) + assert.ok(source.includes('previousRewardAmountFen,')) + assert.ok(source.includes('rewardAmountFen: effectiveRewardAmountFen,')) + assert.ok(source.includes('updatedRewardAmountFen: effectiveRewardAmountFen,')) + assert.ok(source.includes("priceAdjustReason: '寄卖内部现货履约按现货结算成本价更新发单金额'")) +}) + +test('parkWorkOrderWaitingFriendAddWithClient 支持原子更新 reward_amount', async () => { + const source = await readFile(GIFT_REPO, 'utf8') + assert.ok(source.includes('rewardAmountFen?: number | null | undefined')) + assert.ok(source.includes('reward_amount = COALESCE($7, reward_amount)')) +}) + +test('指派供货打手履约记录原始发单金额并写入事件留痕', async () => { + const source = await readFile(FULFILLMENT_SERVICE, 'utf8') + assert.ok(source.includes('const previousRewardAmountFen = Number(workOrder.reward_amount || 0)')) + assert.ok(source.includes("priceAdjustReason: '指派供货打手履约按现货结算成本价更新发单金额'")) +}) + +test('撤销寄售履约退回未分配时必须恢复原始发单金额并记录留痕', async () => { + const source = await readFile(FULFILLMENT_SERVICE, 'utf8') + assert.ok(source.includes('const previousRewardAmountFen =')) + assert.ok(source.includes('const restoreRewardAmount =')) + assert.ok(source.includes('reward_amount = CASE WHEN $6::boolean THEN $7 ELSE reward_amount END')) + assert.ok(source.includes('restoredRewardAmountFen: previousRewardAmountFen')) + assert.ok(source.includes("priceRestoreReason: '撤销寄售履约退回未分配恢复原始发单金额'")) +}) 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 8f5485d4..87ab2dc3 100644 --- a/apps/backend/src/services/worker-platform/consignment-fulfillment-service.ts +++ b/apps/backend/src/services/worker-platform/consignment-fulfillment-service.ts @@ -446,6 +446,10 @@ export async function fulfillWorkOrderWithConsignment(input: { }) } + const previousRewardAmountFen = Number(lockedOrder.reward_amount || 0) + const costPriceFen = Math.max(0, Math.round(Number(inv.cost_price_fen || 0))) + const effectiveRewardAmountFen = costPriceFen > 0 ? costPriceFen : previousRewardAmountFen + const updatedMaterial = { ...material, consignment: { @@ -466,6 +470,8 @@ export async function fulfillWorkOrderWithConsignment(input: { fulfillmentType: CONSIGNMENT_FULFILLMENT_TYPE.INTERNAL_STOCK, fulfilledAt: now, fulfilledBy: input.actorName, + previousRewardAmountFen, + rewardAmountFen: effectiveRewardAmountFen, ...(matchNote ? { matchNote } : {}), ...(input.allowSpecMismatch === true ? { @@ -482,11 +488,15 @@ export async function fulfillWorkOrderWithConsignment(input: { actorName: input.actorName, eventType: 'consignment_fulfilled', materialJson: updatedMaterial, + rewardAmountFen: effectiveRewardAmountFen, extraPayload: { inventoryId: inv.id, inventoryNo: inv.inventory_no, itemName: inv.item_name, costPriceFen: inv.cost_price_fen, + previousRewardAmountFen, + updatedRewardAmountFen: effectiveRewardAmountFen, + priceAdjustReason: '寄卖内部现货履约按现货结算成本价更新发单金额', buyerName: inv.buyer_name, shopName, matchType: @@ -662,6 +672,15 @@ export async function assignWorkOrderWithConsignmentWorker(input: { const material = orderMaterial + const previousRewardAmountFen = Number(workOrder.reward_amount || 0) + const rewardAmountFen = resolveConsignmentAssignRewardFen({ + costPriceFen: claimed.cost_price_fen, + orderRewardFen: workOrder.reward_amount, + // 指派对象是供货卖家本人 → 报酬改写为结算价(货款走工单报酬渠道); + // 指派了别的打手 → 报酬保持原值(货款走验收结算的供货货款渠道),两条渠道互斥防双付 + assigneeIsSupplier: Number(targetWorkerId) === Number(inventory.worker_id || 0), + }) + // 3. 将寄售手枪信息写入工单 material const updatedMaterial = { ...material, @@ -683,6 +702,8 @@ export async function assignWorkOrderWithConsignmentWorker(input: { fulfillmentType: 'worker_deposit', assignedAt: now, assignedBy: input.actorName, + previousRewardAmountFen, + rewardAmountFen, ...(matchNote ? { matchNote } : {}), ...(input.allowSpecMismatch === true ? { @@ -694,14 +715,6 @@ export async function assignWorkOrderWithConsignmentWorker(input: { }, } - const rewardAmountFen = resolveConsignmentAssignRewardFen({ - costPriceFen: claimed.cost_price_fen, - orderRewardFen: workOrder.reward_amount, - // 指派对象是供货卖家本人 → 报酬改写为结算价(货款走工单报酬渠道); - // 指派了别的打手 → 报酬保持原值(货款走验收结算的供货货款渠道),两条渠道互斥防双付 - assigneeIsSupplier: Number(targetWorkerId) === Number(inventory.worker_id || 0), - }) - await updateWorkOrder(workOrder.id, { material_json: updatedMaterial, reward_amount: rewardAmountFen, @@ -724,6 +737,9 @@ export async function assignWorkOrderWithConsignmentWorker(input: { ...(matchNote ? { matchNote } : {}), workerId: targetWorkerId, costPriceFen: claimed.cost_price_fen, + previousRewardAmountFen, + updatedRewardAmountFen: rewardAmountFen, + priceAdjustReason: '指派供货打手履约按现货结算成本价更新发单金额', sourceSubmissionId: claimed.source_submission_id ? Number(claimed.source_submission_id) : null, @@ -1038,6 +1054,14 @@ export async function revokeWorkOrderConsignmentFulfillment(input: { // 工单回到「未指派 / 已取消」前,要把上一次交付留下的指派、时限、验收草稿与 // 问题单痕迹一并清干净;否则重新指派时新打手会看到上一手的 deadline 与验收内容。 + const previousRewardAmountFen = + consignmentInfo.previousRewardAmountFen !== undefined && + consignmentInfo.previousRewardAmountFen !== null + ? Number(consignmentInfo.previousRewardAmountFen) + : null + const restoreRewardAmount = + nextOrderStatus === WORK_ORDER_STATUS.UNASSIGNED && previousRewardAmountFen !== null + const woSql = ` UPDATE work_orders SET @@ -1054,6 +1078,7 @@ export async function revokeWorkOrderConsignmentFulfillment(input: { published_at = NULL, pinned_at = NULL, ${RESET_GIFT_FLOW_SQL}, + reward_amount = CASE WHEN $6::boolean THEN $7 ELSE reward_amount END, updated_at = $3 WHERE id = $4 AND status = $5 RETURNING * @@ -1064,6 +1089,8 @@ export async function revokeWorkOrderConsignmentFulfillment(input: { now, lockedOrder.id, lockedOrder.status, + restoreRewardAmount, + restoreRewardAmount ? previousRewardAmountFen : lockedOrder.reward_amount, ]) const wo = woRes.rows[0] if (!wo) { @@ -1093,6 +1120,14 @@ export async function revokeWorkOrderConsignmentFulfillment(input: { workerDeposit: { mode: workerDepositMode, amount: workerDepositAmount }, consignmentDeposit: { action: consignmentDepositAction }, inventoryArchived: archiveOnly, + ...(restoreRewardAmount + ? { + restoredRewardAmountFen: previousRewardAmountFen, + previousConsignmentCostFen: + consignmentInfo.costPriceFen || inv.cost_price_fen || null, + priceRestoreReason: '撤销寄售履约退回未分配恢复原始发单金额', + } + : {}), }), now, })