fix(worker-platform): 修复寄售匹配工单详情中卖家上架截图丢失问题,并收敛手动匹配为内部现货直接履约
1. 修复文件归一化器 normalizeUploadedFiles 遇到纯字符串 URL 数组被误过滤为空的问题,自动提取 objectKey 并构造规范 UploadedFile 结构; 2. 优化工单展示映射器 resolveWorkOrderEffectiveBoosterMaterial:当工单资料中截图为空时,自动回退并加载寄售卖家上架/主页截图; 3. 前端管理后台详情抽屉与打手端详情抽屉支持读取 detailOrder.gift.boosterMaterial.screenshots 与 consignmentInfo.screenshots 降级兜底渲染; 4. 手动匹配弹窗与发布拦截弹窗全面收敛为单一标准的【内部现货直接履约】,废除旧版指派打手带来的重复扣押金与二次填资料问题; 5. 新增 consignment-booster-material 单元测试,更新寄卖库存优化现状与进度追踪文档,前后端全套测试通过。
This commit is contained in:
@@ -16,9 +16,9 @@ import {
|
||||
import { SearchOutlined, TeamOutlined } from '@ant-design/icons'
|
||||
|
||||
import {
|
||||
assignWorkOrderWithConsignmentWorker,
|
||||
fetchInventoryMatchableOrders,
|
||||
fetchWorkOrderConsignmentMatchOptions,
|
||||
fulfillWorkOrderWithConsignment,
|
||||
} from '@/services/admin/consignment'
|
||||
import type { ConsignmentMatchOptionView, ConsignmentMatchableOrder } from '@/types/consignment'
|
||||
import {
|
||||
@@ -39,7 +39,7 @@ type Selected = {
|
||||
inventoryLabel: string
|
||||
matchType: ManualMatchType
|
||||
conflicts: string[]
|
||||
/** 供货打手(接单平台指派履约对象) */
|
||||
/** 供货打手(用于回显供货来源) */
|
||||
workerId?: number | null
|
||||
workerName?: string
|
||||
}
|
||||
@@ -57,7 +57,7 @@ export type ConsignmentManualMatchModalProps = {
|
||||
/**
|
||||
* 「手动指定现货」弹窗:订单侧与库存侧共用一套交互与落地逻辑。
|
||||
*
|
||||
* 统一走接单平台指派供货打手履约(自动冻结押金、开启交付倒计时与履约监控)。
|
||||
* 统一走内部现货直接履约(原子占库、免外派佣金、不冻结重复押金、跳过打手资料直接进入待加好友)。
|
||||
*/
|
||||
export default function ConsignmentManualMatchModal({
|
||||
open,
|
||||
@@ -106,7 +106,6 @@ export default function ConsignmentManualMatchModal({
|
||||
? `${res.data.inventory.itemName}(${res.data.inventory.sourceSubmissionNo || res.data.inventory.inventoryNo})`
|
||||
: inventory.label,
|
||||
)
|
||||
// 指派线需要现货的供货打手:库存侧入口从响应里记下来,选中订单时带上
|
||||
setInventoryWorker({
|
||||
workerId: res.data?.inventory?.workerId ?? null,
|
||||
workerName: res.data?.inventory?.workerName || '',
|
||||
@@ -165,7 +164,6 @@ export default function ConsignmentManualMatchModal({
|
||||
const requirement = resolveManualMatchRequirement(selected?.matchType || 'exact')
|
||||
const canSubmit =
|
||||
Boolean(selected) &&
|
||||
Boolean(selected?.workerId) &&
|
||||
canSubmitManualMatch({
|
||||
matchType: selected?.matchType || 'exact',
|
||||
confirmedPartial,
|
||||
@@ -175,16 +173,11 @@ export default function ConsignmentManualMatchModal({
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!selected) return
|
||||
if (!selected.workerId) {
|
||||
message.error('该现货未关联供货打手,无法走平台指派履约;请先关联打手')
|
||||
return
|
||||
}
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const res = await assignWorkOrderWithConsignmentWorker({
|
||||
const res = await fulfillWorkOrderWithConsignment({
|
||||
workOrderId: selected.workOrderId,
|
||||
inventoryId: selected.inventoryId,
|
||||
workerId: selected.workerId,
|
||||
allowPartial: selected.matchType !== 'exact' || confirmedPartial,
|
||||
allowSpecMismatch: selected.matchType === 'conflict' || confirmedMismatch,
|
||||
matchNote: matchNote.trim(),
|
||||
@@ -194,7 +187,7 @@ export default function ConsignmentManualMatchModal({
|
||||
const voucher = (res.data as any)?.voucherConsume
|
||||
notifyVoucherConsume(voucher)
|
||||
if (!voucher || voucher.ok) {
|
||||
message.success('已手动匹配并指派供货打手履约,履约押金已冻结')
|
||||
message.success('已使用内部寄售现货直接履约,已进入待加好友阶段')
|
||||
}
|
||||
onMatched?.()
|
||||
resetState()
|
||||
@@ -221,7 +214,7 @@ export default function ConsignmentManualMatchModal({
|
||||
void load()
|
||||
return
|
||||
}
|
||||
message.error(err.message || '手动匹配指派失败')
|
||||
message.error(err.message || '内部现货履约失败')
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
@@ -232,7 +225,7 @@ export default function ConsignmentManualMatchModal({
|
||||
title={mode === 'order' ? `手动指定现货 · ${workOrder?.label || ''}` : '手动匹配订单'}
|
||||
open={open}
|
||||
width={860}
|
||||
okText="确认指派履约"
|
||||
okText="确认内部现货履约"
|
||||
okButtonProps={{
|
||||
disabled: !canSubmit,
|
||||
loading: submitting,
|
||||
@@ -252,13 +245,13 @@ export default function ConsignmentManualMatchModal({
|
||||
>
|
||||
<Space direction="vertical" size={12} style={{ width: '100%' }}>
|
||||
<Alert
|
||||
type="warning"
|
||||
type="info"
|
||||
showIcon
|
||||
message="手动匹配将直接占用现货并指派供货打手履约(冻结履约押金并纳入交付监控)。"
|
||||
message="手动匹配将直接选用内部寄售现货履约(原子占库、免外派佣金、不冻结重复押金、自动跳过打手资料)。"
|
||||
description={
|
||||
mode === 'order'
|
||||
? '下面是该订单的全部在库现货(含规格不一致的),请核对差异后再决定。'
|
||||
: `当前现货:${inventoryLabel || inventory?.label || ''}。下面是可匹配的订单列表,请核对规格后指派。`
|
||||
? '下面是该订单的全部在库现货(含规格不一致的),核对差异后直接内部履约。'
|
||||
: `当前现货:${inventoryLabel || inventory?.label || ''}。下面是可匹配的订单列表,核对后直接内部履约。`
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -442,18 +435,18 @@ export default function ConsignmentManualMatchModal({
|
||||
<Descriptions size="small" column={1} style={{ marginBottom: 8 }}>
|
||||
<Descriptions.Item label="订单">{selected.workOrderLabel || '—'}</Descriptions.Item>
|
||||
<Descriptions.Item label="现货">{selected.inventoryLabel || '—'}</Descriptions.Item>
|
||||
<Descriptions.Item label="指派打手">
|
||||
<Descriptions.Item label="供货来源">
|
||||
{selected.workerId ? (
|
||||
<Space size={6}>
|
||||
<Tag icon={<TeamOutlined />} color="blue">
|
||||
{selected.workerName || `打手 #${selected.workerId}`}
|
||||
{selected.workerName || `供货打手 #${selected.workerId}`}
|
||||
</Tag>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
(工单将进入该打手“我的接单”并冻结履约押金)
|
||||
(验收后将自动为该打手发放货款)
|
||||
</Text>
|
||||
</Space>
|
||||
) : (
|
||||
<Text type="danger">该现货未关联供货打手,无法指派</Text>
|
||||
<Tag color="default">内部库存现货(无打手关联)</Tag>
|
||||
)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="匹配判定">
|
||||
@@ -469,15 +462,6 @@ export default function ConsignmentManualMatchModal({
|
||||
</Descriptions>
|
||||
|
||||
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||
{!selected.workerId ? (
|
||||
<Alert
|
||||
type="error"
|
||||
showIcon
|
||||
message="无法指派打手"
|
||||
description="该现货未绑定任何供货打手,无法在接单平台中执行指派与押金冻结流程。请先在寄卖管理中关联打手。"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{requirement.needsPartialConfirm ? (
|
||||
<Checkbox
|
||||
checked={confirmedPartial}
|
||||
@@ -516,7 +500,7 @@ export default function ConsignmentManualMatchModal({
|
||||
) : null}
|
||||
|
||||
<Paragraph type="secondary" style={{ fontSize: 12, marginBottom: 0 }}>
|
||||
确认后现货立即被占用:内部现货履约进入「待加好友」,指派打手会冻结履约押金。
|
||||
确认后现货立即被占用并进入「待加好友」,双方加好买家好友后点确认加好友进入赠送冷却期。
|
||||
如需退回请使用「撤销寄售履约」。
|
||||
</Paragraph>
|
||||
</Space>
|
||||
|
||||
@@ -808,6 +808,7 @@ export default function WorkOrdersPanel() {
|
||||
consignmentNo: string
|
||||
workerName?: string
|
||||
depositFen?: number | null
|
||||
costPriceFen?: number | null
|
||||
onOk: () => void
|
||||
}) {
|
||||
const isPartial = input.matchType !== 'exact'
|
||||
@@ -2413,6 +2414,14 @@ export default function WorkOrdersPanel() {
|
||||
files={detailOrder.gift.boosterMaterial.screenshots}
|
||||
size={72}
|
||||
/>
|
||||
) : Array.isArray(consignmentInfo?.screenshots) &&
|
||||
consignmentInfo.screenshots.length > 0 ? (
|
||||
<ImagePreviewList
|
||||
imageUrls={(consignmentInfo.screenshots as string[]).filter(
|
||||
(item) => typeof item === 'string' && item.trim(),
|
||||
)}
|
||||
size={72}
|
||||
/>
|
||||
) : (
|
||||
<Typography.Text type="secondary">未提交</Typography.Text>
|
||||
)}
|
||||
@@ -3429,49 +3438,36 @@ export default function WorkOrdersPanel() {
|
||||
backgroundColor: '#fff',
|
||||
}}
|
||||
>
|
||||
{inv.workerId ? (
|
||||
<>
|
||||
<Button
|
||||
block
|
||||
type="primary"
|
||||
icon={<TeamOutlined />}
|
||||
style={{ backgroundColor: '#722ed1', borderColor: '#722ed1' }}
|
||||
loading={consignmentInterceptModal.submitting}
|
||||
onClick={() =>
|
||||
confirmConsignmentMatchAction({
|
||||
kind: 'assign_worker',
|
||||
matchType: m.matchType,
|
||||
consignmentNo: inv.sourceSubmissionNo || '客服手工入库',
|
||||
workerName: inv.workerName,
|
||||
depositFen: inv.depositAmountFen,
|
||||
onOk: () => {
|
||||
void handleAssignWithConsignmentWorker(
|
||||
inv.id,
|
||||
inv.workerId!,
|
||||
!isExact,
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
走接单平台指派打手
|
||||
</Button>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 11,
|
||||
color: '#8c8c8c',
|
||||
marginTop: 6,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
指派供货打手履约 · 自动冻结履约押金
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div style={{ fontSize: 12, color: '#cf1322', textAlign: 'center' }}>
|
||||
该现货未关联供货打手,无法走平台指派;请先关联打手
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
block
|
||||
type="primary"
|
||||
icon={<PushpinOutlined />}
|
||||
style={{ backgroundColor: '#52c41a', borderColor: '#52c41a' }}
|
||||
loading={consignmentInterceptModal.submitting}
|
||||
onClick={() =>
|
||||
confirmConsignmentMatchAction({
|
||||
kind: 'internal_stock',
|
||||
matchType: m.matchType,
|
||||
consignmentNo: inv.sourceSubmissionNo || '客服手工入库',
|
||||
costPriceFen: inv.costPriceFen,
|
||||
onOk: () => {
|
||||
void handleFulfillWithConsignment(inv.id, !isExact)
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
内部现货直接履约
|
||||
</Button>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 11,
|
||||
color: '#8c8c8c',
|
||||
marginTop: 6,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
免外派佣金 · 不扣打手押金 · 自动进冷静期
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -245,6 +245,17 @@ function GiftProgressCard({ order }: { order: WorkOrder }) {
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<ImagePreviewList files={gift.boosterMaterial.screenshots} size={64} />
|
||||
</div>
|
||||
) : Array.isArray(
|
||||
(order.material as Record<string, any> | undefined)?.consignment?.screenshots,
|
||||
) &&
|
||||
((order.material as Record<string, any>).consignment.screenshots as unknown[])
|
||||
.length > 0 ? (
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<ImagePreviewList
|
||||
imageUrls={(order.material as Record<string, any>).consignment.screenshots}
|
||||
size={64}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user