fix(consignment): 兼容历史整单履约的赠送操作

无占用记录时按库存绑定工单兜底;前端无 allocation/share 时不再传 workOrderId,修复卖家提交赠送截图的订单占用报错。
This commit is contained in:
2026-11-13 15:09:40 +08:00
parent 1f3c98c071
commit dd0c56c265
6 changed files with 162 additions and 11 deletions
@@ -0,0 +1,44 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { isLegacyBoundConsignmentTarget } from './consignment-action-target.js'
function baseInput() {
return {
inventoryStatus: 'consumed',
boundWorkOrderId: 204855,
allocationId: 0,
shareId: 0,
workOrderId: 204855,
hasActiveAllocation: false,
}
}
test('历史整单履约:没有占用记录且指向库存绑定工单时允许继续', () => {
assert.equal(isLegacyBoundConsignmentTarget(baseInput()), true)
})
test('历史兜底只接受 consumed 库存', () => {
assert.equal(
isLegacyBoundConsignmentTarget({ ...baseInput(), inventoryStatus: 'in_stock' }),
false,
)
})
test('有新的有效占用时不得回退到库存绑定工单', () => {
assert.equal(isLegacyBoundConsignmentTarget({ ...baseInput(), hasActiveAllocation: true }), false)
})
test('显式指定 allocation/share 但没有匹配记录时不得兜底', () => {
assert.equal(isLegacyBoundConsignmentTarget({ ...baseInput(), allocationId: 11 }), false)
assert.equal(isLegacyBoundConsignmentTarget({ ...baseInput(), shareId: 22 }), false)
})
test('工单号与库存绑定工单不一致时不得兜底', () => {
assert.equal(isLegacyBoundConsignmentTarget({ ...baseInput(), workOrderId: 204856 }), false)
assert.equal(isLegacyBoundConsignmentTarget({ ...baseInput(), boundWorkOrderId: null }), false)
})
test('没有传工单号时不需要历史兜底', () => {
assert.equal(isLegacyBoundConsignmentTarget({ ...baseInput(), workOrderId: 0 }), false)
})
@@ -0,0 +1,21 @@
/**
* 历史整单履约兼容判断。
*
* 旧的后台「内部库存履约」只写 consignment_inventories.bound_work_order_id,
* 不会生成 consignment_inventory_allocations。卖家端按绑定工单发起操作时,
* 只要库存仍是 consumed 且没有新的有效占用,就允许按整单继续。
*/
export function isLegacyBoundConsignmentTarget(input: {
inventoryStatus: string
boundWorkOrderId: number | string | null | undefined
allocationId: number
shareId: number
workOrderId: number
hasActiveAllocation: boolean
}): boolean {
if (input.hasActiveAllocation) return false
if (input.allocationId > 0 || input.shareId > 0) return false
if (input.workOrderId <= 0) return false
if (String(input.inventoryStatus || '').trim() !== 'consumed') return false
return Number(input.boundWorkOrderId || 0) === input.workOrderId
}
@@ -32,6 +32,7 @@ import {
resolvePriceDropSuggestion,
} from './consignment-profile-service.js'
import { suggestConsignmentPriceFromRules } from './consignment-matching-service.js'
import { isLegacyBoundConsignmentTarget } from './consignment-action-target.js'
import {
getEnabledWorkProductRuleByKey,
parseTemplateSpecs,
@@ -1010,7 +1011,19 @@ async function resolveSellerConsignmentWorkOrder(
errorCode: 'consignment_allocation_selection_required',
})
}
if ((allocationIdInput > 0 || shareIdInput > 0 || workOrderIdInput > 0) && !allocation) {
const isLegacyBoundTarget = isLegacyBoundConsignmentTarget({
inventoryStatus: inventory.status,
boundWorkOrderId: inventory.bound_work_order_id,
allocationId: allocationIdInput,
shareId: shareIdInput,
workOrderId: workOrderIdInput,
hasActiveAllocation: allocations.length > 0,
})
if (
(allocationIdInput > 0 || shareIdInput > 0 || workOrderIdInput > 0) &&
!allocation &&
!isLegacyBoundTarget
) {
throw createHttpError('未找到该寄卖单对应的订单占用,请刷新后重试', {
statusCode: 404,
errorCode: 'consignment_allocation_not_found',
@@ -63,6 +63,7 @@ import type {
} from '@/types/consignment'
import type { UploadedFile } from '@/types/worker-platform'
import { DEFAULT_CONSIGNMENT_INTAKE_FIELDS, buildSpecItemName } from '@/utils/consignment-spec'
import { resolveConsignmentActionTarget } from '@/utils/consignment-action-target'
import { buildConsignmentCategoryGroups } from '@/utils/consignment-category-tree'
import { formatConsignmentContactSupporter } from '@/utils/consignment-contact'
import { formatGiftCooldownRemaining } from '@/utils/consignment-cooldown'
@@ -757,14 +758,6 @@ export default function WorkerConsignmentPage() {
return null
}
function resolveActionTarget(item: WorkerConsignmentRecord) {
return {
workOrderId: item.workOrderId,
shareId: item.shareId ?? null,
allocationId: item.allocationId ?? null,
}
}
function handleConfirmFriend(item: WorkerConsignmentRecord) {
const submissionId = resolveActionSubmissionId(item)
if (submissionId == null) return
@@ -789,7 +782,10 @@ export default function WorkerConsignmentPage() {
centered: true,
onOk: async () => {
try {
await confirmWorkerConsignmentFriendAdded(submissionId, resolveActionTarget(item))
await confirmWorkerConsignmentFriendAdded(
submissionId,
resolveConsignmentActionTarget(item),
)
message.success('已确认加好友,赠送冷却开始')
await myConsignmentsQuery.refetch()
} catch (err: any) {
@@ -831,7 +827,7 @@ export default function WorkerConsignmentPage() {
...(note ? { note } : {}),
}
await submitWorkerConsignmentGiftEvidence(submissionId, {
...resolveActionTarget(evidenceItem),
...resolveConsignmentActionTarget(evidenceItem),
...payload,
})
message.success('赠送截图已提交,等待客服验收')
@@ -0,0 +1,29 @@
export type ConsignmentActionTargetInput = {
workOrderId?: number | null
shareId?: number | null
allocationId?: number | null
}
export type ConsignmentActionTarget = {
workOrderId?: number | null
shareId?: number | null
allocationId?: number | null
}
/**
* 卖家侧操作定位参数。
*
* 有 allocation/share 时按新占用模型传参;历史整单履约没有占用记录,
* 这时不要传 workOrderId,让后端回退到库存自身的 bound_work_order_id。
*/
export function resolveConsignmentActionTarget(
item: ConsignmentActionTargetInput,
): ConsignmentActionTarget {
const hasAllocationTarget = Number(item.allocationId || 0) > 0 || Number(item.shareId || 0) > 0
if (!hasAllocationTarget) return {}
return {
workOrderId: item.workOrderId ?? null,
shareId: item.shareId ?? null,
allocationId: item.allocationId ?? null,
}
}
@@ -0,0 +1,48 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { resolveConsignmentActionTarget } from '../src/utils/consignment-action-target.ts'
test('历史整单履约没有占用/份额时不传 workOrderId', () => {
assert.deepEqual(
resolveConsignmentActionTarget({
workOrderId: 204855,
shareId: null,
allocationId: null,
}),
{},
)
})
test('allocationId/shareId 为 0 也按无占用处理', () => {
assert.deepEqual(
resolveConsignmentActionTarget({
workOrderId: 204855,
shareId: 0,
allocationId: 0,
}),
{},
)
})
test('有占用记录时携带完整定位参数', () => {
assert.deepEqual(
resolveConsignmentActionTarget({
workOrderId: 204855,
shareId: null,
allocationId: 99,
}),
{ workOrderId: 204855, shareId: null, allocationId: 99 },
)
})
test('拼单份额同样保留定位参数', () => {
assert.deepEqual(
resolveConsignmentActionTarget({
workOrderId: 204855,
shareId: 88,
allocationId: null,
}),
{ workOrderId: 204855, shareId: 88, allocationId: null },
)
})