feat(consignment): 支持多数量拆分与份额级赠送,并对齐待抢单看板
- 匹配引擎支持一条库存按剩余数量拆给多张整单/拼单,份额数量、报酬、分配数量按实际占用计算 - 「我的寄卖」改为按占用单/份额一行 + 剩余库存行,卖家按 allocation/share/workOrder 定位确认加好友与提交凭证 - 解绑/撤销/超时按 allocation 精确回补,修复单绑定字段在多占用下被误清的问题 - 拼单好友赠送改为份额级 friend_added_at / gift_available_at(迁移 146),新增后台按份额确认加好友入口与寄卖赠送列 - 待抢单匹配看板改用新引擎:覆盖全部寄卖分类、拼单余量、精确/非精确/冲突明细,去掉手枪品类写死 - 发布押金改为该提报单全部售出/作废后才释放,避免多件库存分多单时提前退款 - 补齐集成测试与单测:拆分、多占用、份额级独立冷却与凭证、看板新旧引擎对齐
This commit is contained in:
@@ -95,6 +95,11 @@ export default function ConsignmentPendingMatchModal({
|
||||
<Text type="secondary" style={{ fontSize: 11 }}>
|
||||
录单 {record.workOrderCreatedAt ? formatDateTime(record.workOrderCreatedAt) : '-'}
|
||||
</Text>
|
||||
{Number(record.totalQuantity || 1) > 1 ? (
|
||||
<Text type="secondary" style={{ fontSize: 11 }}>
|
||||
拼单剩余 {record.remainingQuantity ?? 0} / {record.totalQuantity} 份
|
||||
</Text>
|
||||
) : null}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
@@ -166,6 +171,15 @@ export default function ConsignmentPendingMatchModal({
|
||||
{record.conflicts.join(';')}
|
||||
</Text>
|
||||
) : null}
|
||||
{(record.matchDetails || [])
|
||||
.filter((detail) => detail.status !== 'match' && detail.status !== 'ignored')
|
||||
.slice(0, 4)
|
||||
.map((detail) => (
|
||||
<Text key={detail.key} type="secondary" style={{ fontSize: 11 }}>
|
||||
{detail.label}:订单「{detail.orderValue || '-'}」/ 库存「
|
||||
{detail.inventoryValue || '-'}」
|
||||
</Text>
|
||||
))}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
@@ -173,7 +187,7 @@ export default function ConsignmentPendingMatchModal({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="匹配 · 待抢单手枪 × 在库现货"
|
||||
title="匹配 · 待抢单 × 在库现货"
|
||||
open={open}
|
||||
width="min(1320px, 94vw)"
|
||||
footer={null}
|
||||
@@ -184,8 +198,8 @@ export default function ConsignmentPendingMatchModal({
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
message="只查询非拼单的待抢单手枪单 × 在库待匹配现货,不直接改单。"
|
||||
description="分数 0-100:系统 20 / 区服 20 / 系列 20 / 钻级 25 / 璀璨 15。非精确项仍需按现有规则确认;不按录单、入库时间先后过滤。"
|
||||
message="按寄卖配置匹配全部待抢单 × 在库待匹配现货,不直接改单。"
|
||||
description="有寄卖配置的订单按该分类的规格逐项判定(精确 / 非精确 / 不匹配);拼单订单按剩余份数展示。未配置寄卖的订单仍按旧手枪口径兜底。"
|
||||
/>
|
||||
|
||||
<Space wrap size={12}>
|
||||
|
||||
@@ -52,6 +52,7 @@ import VideoPreview, { formatAcceptanceVideoDuration } from '@/components/files/
|
||||
import {
|
||||
acceptAdminWorkOrder,
|
||||
acceptAdminWorkOrderShare,
|
||||
confirmAdminWorkOrderShareFriendAdded,
|
||||
acceptAdminWorkOrders,
|
||||
assignAdminWorkOrderToWorker,
|
||||
cancelAdminWorkOrder,
|
||||
@@ -3734,6 +3735,22 @@ export default function WorkOrdersPanel() {
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '寄卖赠送',
|
||||
width: 120,
|
||||
render: (_, share) => {
|
||||
if (!share.consignmentInventoryId) return '-'
|
||||
if (share.status === 'accepted') return <Tag color="green">已完成</Tag>
|
||||
if (share.status === 'submitted')
|
||||
return <Tag color="processing">待审核</Tag>
|
||||
if (!share.friendAddedAt) return <Tag>待加好友</Tag>
|
||||
const availableAt = Date.parse(String(share.giftAvailableAt || ''))
|
||||
if (Number.isFinite(availableAt) && availableAt > Date.now()) {
|
||||
return <Tag color="orange">冷却中</Tag>
|
||||
}
|
||||
return <Tag color="green">待赠送</Tag>
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '问题',
|
||||
width: 120,
|
||||
@@ -3906,6 +3923,29 @@ export default function WorkOrdersPanel() {
|
||||
)
|
||||
) : null
|
||||
|
||||
const consignmentGiftAction =
|
||||
sharingOrder?.acceptanceMode === 'friend_gift' &&
|
||||
share.consignmentInventoryId &&
|
||||
share.status === 'joined' &&
|
||||
!share.friendAddedAt ? (
|
||||
<Button
|
||||
size="small"
|
||||
title="只确认该卖家的份额,其他卖家的冷却不受影响"
|
||||
onClick={() => {
|
||||
void runAction(
|
||||
() =>
|
||||
confirmAdminWorkOrderShareFriendAdded(
|
||||
sharingOrder.workOrderId,
|
||||
share.shareId,
|
||||
),
|
||||
'已确认该份额加好友,赠送冷却开始',
|
||||
)
|
||||
}}
|
||||
>
|
||||
确认加好友
|
||||
</Button>
|
||||
) : null
|
||||
|
||||
return (
|
||||
<Space size={6} className="admin-share-actions">
|
||||
{share.status === 'submitted' ? (
|
||||
@@ -3969,6 +4009,7 @@ export default function WorkOrdersPanel() {
|
||||
撤份额
|
||||
</Button>
|
||||
) : null}
|
||||
{consignmentGiftAction}
|
||||
{problemAction}
|
||||
</Space>
|
||||
)
|
||||
|
||||
@@ -530,6 +530,14 @@ 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
|
||||
@@ -554,7 +562,7 @@ export default function WorkerConsignmentPage() {
|
||||
centered: true,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await confirmWorkerConsignmentFriendAdded(submissionId)
|
||||
await confirmWorkerConsignmentFriendAdded(submissionId, resolveActionTarget(item))
|
||||
message.success('已确认加好友,赠送冷却开始')
|
||||
await myConsignmentsQuery.refetch()
|
||||
} catch (err: any) {
|
||||
@@ -578,7 +586,8 @@ export default function WorkerConsignmentPage() {
|
||||
}
|
||||
|
||||
async function handleEvidenceSubmit() {
|
||||
const submissionId = evidenceItem ? resolveActionSubmissionId(evidenceItem) : null
|
||||
if (!evidenceItem) return
|
||||
const submissionId = resolveActionSubmissionId(evidenceItem)
|
||||
if (submissionId == null) return
|
||||
const urls = evidenceFiles.map((file) => file.url).filter(Boolean)
|
||||
if (urls.length === 0) {
|
||||
@@ -594,7 +603,10 @@ export default function WorkerConsignmentPage() {
|
||||
imageUrls: urls,
|
||||
...(note ? { note } : {}),
|
||||
}
|
||||
await submitWorkerConsignmentGiftEvidence(submissionId, payload)
|
||||
await submitWorkerConsignmentGiftEvidence(submissionId, {
|
||||
...resolveActionTarget(evidenceItem),
|
||||
...payload,
|
||||
})
|
||||
message.success('赠送截图已提交,等待客服验收')
|
||||
closeEvidenceModal()
|
||||
await myConsignmentsQuery.refetch()
|
||||
@@ -1137,7 +1149,7 @@ export default function WorkerConsignmentPage() {
|
||||
<div className="worker-consignment-record-list">
|
||||
{records.map((record) => (
|
||||
<ConsignmentRecordCard
|
||||
key={record.consignmentNo}
|
||||
key={`${record.submissionId ?? 'x'}-${record.inventoryId ?? 'x'}-${record.allocationId ?? 'stock'}-${record.workOrderId ?? 0}`}
|
||||
record={record}
|
||||
now={cooldownNow}
|
||||
onConfirmFriend={handleConfirmFriend}
|
||||
@@ -1526,6 +1538,11 @@ function ConsignmentRecordCard({
|
||||
寄售价{' '}
|
||||
<strong>¥{((record.salePriceFen ?? record.askingPriceFen) / 100).toFixed(2)}</strong>
|
||||
</span>
|
||||
{record.allocationId && Number(record.allocatedQuantity || 0) > 0 ? (
|
||||
<span>
|
||||
本单占用 <strong>{record.allocatedQuantity} 件</strong>
|
||||
</span>
|
||||
) : null}
|
||||
{Number(record.quantity || 1) > 1 ? (
|
||||
<span>
|
||||
数量{' '}
|
||||
|
||||
@@ -981,6 +981,14 @@ export function acceptAdminWorkOrderShare(workOrderId: number, shareId: number)
|
||||
)
|
||||
}
|
||||
|
||||
/** 寄卖拼单:按份额确认已加好友,只影响该卖家的冷却。 */
|
||||
export function confirmAdminWorkOrderShareFriendAdded(workOrderId: number, shareId: number) {
|
||||
return apiPost<{ share: WorkOrderShare | null }>(
|
||||
`/api/v1/admin/worker-platform/orders/${workOrderId}/shares/${shareId}/confirm-friend`,
|
||||
{},
|
||||
)
|
||||
}
|
||||
|
||||
export function cancelAdminWorkOrderShare(
|
||||
workOrderId: number,
|
||||
shareId: number,
|
||||
|
||||
@@ -447,7 +447,13 @@ export type WorkerConsignmentRecord = {
|
||||
consignmentNo: string
|
||||
submissionId: number | null
|
||||
inventoryId: number | null
|
||||
/** 一库存拆给多张订单时,本行对应的占用记录 */
|
||||
allocationId?: number | null
|
||||
/** 拼单份额 id(本行对应的卖家份额) */
|
||||
shareId?: number | null
|
||||
workOrderId: number | null
|
||||
/** 本行占用的数量;库存剩余行为剩余数量 */
|
||||
allocatedQuantity?: number
|
||||
itemName: string
|
||||
system: string
|
||||
serverZone: string
|
||||
@@ -558,21 +564,30 @@ export function fetchWorkerConsignments(params: WorkerConsignmentListParams = {}
|
||||
)
|
||||
}
|
||||
|
||||
export type WorkerConsignmentActionTarget = {
|
||||
workOrderId?: number | null
|
||||
shareId?: number | null
|
||||
allocationId?: number | null
|
||||
}
|
||||
|
||||
/**
|
||||
* 卖家确认已在游戏内加好买家好友 → 进入赠送冷却期。
|
||||
* 注意:确认后若未完成赠送,履约押金不退。
|
||||
*/
|
||||
export function confirmWorkerConsignmentFriendAdded(submissionId: number | string) {
|
||||
export function confirmWorkerConsignmentFriendAdded(
|
||||
submissionId: number | string,
|
||||
target: WorkerConsignmentActionTarget = {},
|
||||
) {
|
||||
return apiPost<{ order: WorkOrder }>(
|
||||
`/api/v1/worker/consignment/items/${submissionId}/confirm-friend`,
|
||||
{},
|
||||
target,
|
||||
)
|
||||
}
|
||||
|
||||
/** 卖家提交赠送完成截图(后端复用打手验收链路,工单转「待验收」交客服审核)。 */
|
||||
export function submitWorkerConsignmentGiftEvidence(
|
||||
submissionId: number | string,
|
||||
payload: { imageUrls: string[]; note?: string },
|
||||
payload: { imageUrls: string[]; note?: string } & WorkerConsignmentActionTarget,
|
||||
) {
|
||||
return apiPost<{ order: WorkOrder }>(
|
||||
`/api/v1/worker/consignment/items/${submissionId}/gift-evidence`,
|
||||
|
||||
@@ -300,7 +300,7 @@ export type WorkOrderInventoryMatchCheck = {
|
||||
matches: ConsignmentMatchResult[]
|
||||
}
|
||||
|
||||
/** 后台「寄售仓库 → 匹配」:待抢单手枪 × 在库待匹配现货的配对明细 */
|
||||
/** 后台「寄售仓库 → 匹配」:待抢单 × 在库待匹配现货的配对明细 */
|
||||
export type PendingPistolMatchPair = {
|
||||
workOrderId: number
|
||||
workOrderNo: string
|
||||
@@ -316,6 +316,20 @@ export type PendingPistolMatchPair = {
|
||||
score: number
|
||||
reason: string
|
||||
conflicts: string[]
|
||||
/** 自定义规格逐字段对比;内置五字段为空,沿用旧评分原因 */
|
||||
matchDetails?: Array<{
|
||||
key: string
|
||||
label: string
|
||||
orderValue: string
|
||||
inventoryValue: string
|
||||
status: 'match' | 'partial' | 'conflict' | 'unknown' | 'ignored'
|
||||
}>
|
||||
/** 拼单订单还差多少份;非拼单为 1 */
|
||||
remainingQuantity?: number
|
||||
/** 拼单订单总份数;非拼单为 1 */
|
||||
totalQuantity?: number
|
||||
/** 命中的寄卖配置 id;无配置时为 null(旧手枪兜底) */
|
||||
profileId?: number | null
|
||||
}
|
||||
|
||||
export type PendingPistolMatchResult = {
|
||||
|
||||
@@ -608,6 +608,10 @@ export type WorkOrderShare = {
|
||||
cancelNote?: string
|
||||
/** 被撤掉的份数(撤份额累计 / 单人撤单为整份份数;0 = 未撤过) */
|
||||
cancelQuantity?: number
|
||||
/** 寄卖拼单:本份额对应的库存与加好友/赠送冷却状态 */
|
||||
consignmentInventoryId?: number | null
|
||||
friendAddedAt?: string | null
|
||||
giftAvailableAt?: string | null
|
||||
/** 份额级问题单:只影响该份额持有人,母单状态不变 */
|
||||
problemMarked?: boolean
|
||||
problemNote?: string
|
||||
|
||||
Reference in New Issue
Block a user