feat(worker): support batch sharing cancellation
This commit is contained in:
@@ -313,6 +313,22 @@ export function formatWorkOrderEvent(event: WorkOrderEvent): EventNodeStyle {
|
||||
color: 'red',
|
||||
}
|
||||
}
|
||||
case 'sharing_unaccepted_shares_returned_to_hall_by_admin': {
|
||||
const count = readPayload(event, 'cancelledShareCount')
|
||||
return {
|
||||
title: `批量撤回未验收拼单份额${count ? `(${count} 个)` : ''}`,
|
||||
description: note ? `原因:${note}` : '对应数量已重新开放到接单大厅',
|
||||
color: 'orange',
|
||||
}
|
||||
}
|
||||
case 'sharing_order_cancelled_by_admin': {
|
||||
const count = readPayload(event, 'cancelledShareCount')
|
||||
return {
|
||||
title: `取消拼单订单${count ? `(已撤销 ${count} 个参与份额)` : ''}`,
|
||||
description: note ? `原因:${note}` : undefined,
|
||||
color: 'red',
|
||||
}
|
||||
}
|
||||
case 'pending_deposit_deducted':
|
||||
return { title: '扣减了待解冻押金', color: 'default' }
|
||||
case 'voucher_backfilled':
|
||||
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
assignAdminWorkOrderToWorker,
|
||||
cancelAdminWorkOrder,
|
||||
cancelAdminWorkOrderShare,
|
||||
cancelAdminWorkOrderSharing,
|
||||
confirmAdminWorkOrderFriend,
|
||||
deductAdminWorkOrderPendingDeposit,
|
||||
deleteAdminWorkOrder,
|
||||
@@ -146,6 +147,11 @@ type CancelShareFormValues = {
|
||||
reason?: string
|
||||
}
|
||||
|
||||
type CancelSharingFormValues = {
|
||||
action?: 'return_to_hall' | 'cancel_order'
|
||||
reason?: string
|
||||
}
|
||||
|
||||
export default function WorkOrdersPanel() {
|
||||
const { message, modal } = App.useApp()
|
||||
const navigate = useNavigate()
|
||||
@@ -221,6 +227,7 @@ export default function WorkOrdersPanel() {
|
||||
const [materialPasteText, setMaterialPasteText] = useState('')
|
||||
const [sharingOrder, setSharingOrder] = useState<WorkOrder | null>(null)
|
||||
const [cancelShare, setCancelShare] = useState<WorkOrderShare | null>(null)
|
||||
const [cancelSharingOrder, setCancelSharingOrder] = useState<WorkOrder | null>(null)
|
||||
const [assignOrder, setAssignOrder] = useState<WorkOrder | null>(null)
|
||||
const [assignWorkerId, setAssignWorkerId] = useState<number | undefined>(undefined)
|
||||
const [editOrder, setEditOrder] = useState<WorkOrder | null>(null)
|
||||
@@ -232,6 +239,7 @@ export default function WorkOrdersPanel() {
|
||||
const [materialForm] = Form.useForm<MaterialFormValues>()
|
||||
const [cancelForm] = Form.useForm<CancelOrderFormValues>()
|
||||
const [cancelShareForm] = Form.useForm<CancelShareFormValues>()
|
||||
const [cancelSharingForm] = Form.useForm<CancelSharingFormValues>()
|
||||
const [deductForm] = Form.useForm<{ amount?: number; note?: string }>()
|
||||
const [editForm] = Form.useForm<{
|
||||
productName?: string
|
||||
@@ -335,6 +343,11 @@ export default function WorkOrdersPanel() {
|
||||
queryFn: () => fetchAdminWorkOrderSharing(Number(sharingOrder?.workOrderId)),
|
||||
enabled: Boolean(sharingOrder),
|
||||
})
|
||||
const sharingShares = sharingQuery.data?.data.shares || []
|
||||
const hasCancellableSharingShares = sharingShares.some((share) =>
|
||||
['joined', 'submitted'].includes(share.status),
|
||||
)
|
||||
const hasAcceptedSharingShares = sharingShares.some((share) => share.status === 'accepted')
|
||||
const assignWorkersQuery = useQuery({
|
||||
queryKey: ['admin-worker-platform-workers', 'assign', assignWorkerKeyword],
|
||||
queryFn: () =>
|
||||
@@ -498,6 +511,28 @@ export default function WorkOrdersPanel() {
|
||||
cancelShareForm.resetFields()
|
||||
}
|
||||
|
||||
async function submitCancelSharing(values: CancelSharingFormValues) {
|
||||
if (!cancelSharingOrder || !values.action) return
|
||||
const action = values.action
|
||||
const succeeded = await runAction(
|
||||
() =>
|
||||
cancelAdminWorkOrderSharing(cancelSharingOrder.workOrderId, {
|
||||
action,
|
||||
reason: String(values.reason || '').trim(),
|
||||
}),
|
||||
(result) =>
|
||||
action === 'return_to_hall'
|
||||
? `已撤回 ${result.data.cancelledShareCount} 个未验收份额并退回大厅`
|
||||
: `拼单订单已取消,已撤销 ${result.data.cancelledShareCount} 个参与份额`,
|
||||
)
|
||||
if (!succeeded) return
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ['admin-worker-platform-order-sharing', cancelSharingOrder.workOrderId],
|
||||
})
|
||||
setCancelSharingOrder(null)
|
||||
cancelSharingForm.resetFields()
|
||||
}
|
||||
|
||||
async function refreshAll() {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({
|
||||
@@ -1097,7 +1132,7 @@ export default function WorkOrdersPanel() {
|
||||
(row.capabilities?.canConfigureSharing && canConfigureSharing) ? (
|
||||
<Button icon={<TeamOutlined />} onClick={() => openSharingConfigModal(row)}>
|
||||
{row.sharing?.enabled && !row.capabilities?.canConfigureSharing
|
||||
? '拼单明细'
|
||||
? '拼单管理'
|
||||
: '拼单配置'}
|
||||
</Button>
|
||||
) : null}
|
||||
@@ -2148,8 +2183,10 @@ export default function WorkOrdersPanel() {
|
||||
onCancel={() => {
|
||||
setSharingOrder(null)
|
||||
setCancelShare(null)
|
||||
setCancelSharingOrder(null)
|
||||
sharingForm.resetFields()
|
||||
cancelShareForm.resetFields()
|
||||
cancelSharingForm.resetFields()
|
||||
}}
|
||||
onOk={() => sharingForm.submit()}
|
||||
footer={
|
||||
@@ -2237,114 +2274,152 @@ export default function WorkOrdersPanel() {
|
||||
) : (sharingQuery.data?.data.shares || []).length === 0 ? (
|
||||
<Typography.Text type="secondary">暂无打手参与拼单</Typography.Text>
|
||||
) : (
|
||||
<Table<WorkOrderShare>
|
||||
rowKey="shareId"
|
||||
size="small"
|
||||
dataSource={sharingQuery.data?.data.shares || []}
|
||||
loading={sharingQuery.isFetching}
|
||||
pagination={false}
|
||||
columns={[
|
||||
{
|
||||
title: '打手',
|
||||
render: (_, share) =>
|
||||
share.worker?.displayName || share.worker?.username || '-',
|
||||
},
|
||||
{ title: '数量', dataIndex: 'quantity' },
|
||||
{
|
||||
title: '份额报酬',
|
||||
render: (_, share) => formatMoney(share.shareReward),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
render: (_, share) => (
|
||||
<Tag color={resolveSharingShareStatusColor(share.status)}>
|
||||
{formatSharingShareStatus(share.status)}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '提交时间',
|
||||
render: (_, share) =>
|
||||
share.submittedAt ? formatAdminDateTime(share.submittedAt) : '-',
|
||||
},
|
||||
{
|
||||
title: '验收图',
|
||||
width: 150,
|
||||
render: (_, share) => (
|
||||
<ImagePreviewList
|
||||
files={
|
||||
Array.isArray(share.acceptance?.files) ? share.acceptance.files : []
|
||||
}
|
||||
imageUrls={
|
||||
Array.isArray(share.acceptance?.imageUrls)
|
||||
? share.acceptance.imageUrls
|
||||
: []
|
||||
}
|
||||
size={44}
|
||||
maxVisible={2}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
render: (_, share) => String(share.acceptance?.note || '').trim() || '-',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 190,
|
||||
render: (_, share) => {
|
||||
if (share.status === 'accepted') return <Tag color="green">已验收</Tag>
|
||||
if (share.status === 'cancelled') return <Tag>已撤单</Tag>
|
||||
return (
|
||||
<Space size={6}>
|
||||
{share.status === 'submitted' ? (
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
void runAction(
|
||||
() =>
|
||||
acceptAdminWorkOrderShare(
|
||||
sharingOrder.workOrderId,
|
||||
share.shareId,
|
||||
),
|
||||
'该拼单参与者已验收',
|
||||
).then((succeeded) => {
|
||||
if (succeeded) {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [
|
||||
'admin-worker-platform-order-sharing',
|
||||
<>
|
||||
{sharingOrder.capabilities?.canCancelSharing &&
|
||||
hasCancellableSharingShares &&
|
||||
(canReturnToHall || canCancelOrder) ? (
|
||||
<Space
|
||||
direction="vertical"
|
||||
size={8}
|
||||
className="full-width"
|
||||
style={{ marginBottom: 12 }}
|
||||
>
|
||||
<Alert
|
||||
showIcon
|
||||
type="warning"
|
||||
message="拼单撤单会批量处理所有未验收份额"
|
||||
description={
|
||||
hasAcceptedSharingShares
|
||||
? '已验收份额会保留;如需退款,请从售后处理。'
|
||||
: '可选择把未验收份额退回大厅,或取消整个拼单订单。'
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
<Button
|
||||
danger
|
||||
onClick={() => {
|
||||
setCancelSharingOrder(sharingOrder)
|
||||
cancelSharingForm.setFieldsValue({
|
||||
action: canReturnToHall ? 'return_to_hall' : 'cancel_order',
|
||||
reason: '',
|
||||
})
|
||||
}}
|
||||
>
|
||||
拼单撤单处理
|
||||
</Button>
|
||||
</div>
|
||||
</Space>
|
||||
) : null}
|
||||
<Table<WorkOrderShare>
|
||||
rowKey="shareId"
|
||||
size="small"
|
||||
dataSource={sharingShares}
|
||||
loading={sharingQuery.isFetching}
|
||||
pagination={false}
|
||||
columns={[
|
||||
{
|
||||
title: '打手',
|
||||
render: (_, share) =>
|
||||
share.worker?.displayName || share.worker?.username || '-',
|
||||
},
|
||||
{ title: '数量', dataIndex: 'quantity' },
|
||||
{
|
||||
title: '份额报酬',
|
||||
render: (_, share) => formatMoney(share.shareReward),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
render: (_, share) => (
|
||||
<Tag color={resolveSharingShareStatusColor(share.status)}>
|
||||
{formatSharingShareStatus(share.status)}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '提交时间',
|
||||
render: (_, share) =>
|
||||
share.submittedAt ? formatAdminDateTime(share.submittedAt) : '-',
|
||||
},
|
||||
{
|
||||
title: '验收图',
|
||||
width: 150,
|
||||
render: (_, share) => (
|
||||
<ImagePreviewList
|
||||
files={
|
||||
Array.isArray(share.acceptance?.files) ? share.acceptance.files : []
|
||||
}
|
||||
imageUrls={
|
||||
Array.isArray(share.acceptance?.imageUrls)
|
||||
? share.acceptance.imageUrls
|
||||
: []
|
||||
}
|
||||
size={44}
|
||||
maxVisible={2}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
render: (_, share) => String(share.acceptance?.note || '').trim() || '-',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 190,
|
||||
render: (_, share) => {
|
||||
if (share.status === 'accepted') return <Tag color="green">已验收</Tag>
|
||||
if (share.status === 'cancelled') return <Tag>已撤单</Tag>
|
||||
return (
|
||||
<Space size={6}>
|
||||
{share.status === 'submitted' ? (
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
void runAction(
|
||||
() =>
|
||||
acceptAdminWorkOrderShare(
|
||||
sharingOrder.workOrderId,
|
||||
],
|
||||
})
|
||||
}
|
||||
})
|
||||
}}
|
||||
>
|
||||
单独验收
|
||||
</Button>
|
||||
) : (
|
||||
<Typography.Text type="secondary">待提交</Typography.Text>
|
||||
)}
|
||||
share.shareId,
|
||||
),
|
||||
'该拼单参与者已验收',
|
||||
).then((succeeded) => {
|
||||
if (succeeded) {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [
|
||||
'admin-worker-platform-order-sharing',
|
||||
sharingOrder.workOrderId,
|
||||
],
|
||||
})
|
||||
}
|
||||
})
|
||||
}}
|
||||
>
|
||||
单独验收
|
||||
</Button>
|
||||
) : (
|
||||
<Typography.Text type="secondary">待提交</Typography.Text>
|
||||
)}
|
||||
{canCancelShare ? (
|
||||
<Button
|
||||
danger
|
||||
size="small"
|
||||
icon={<UndoOutlined />}
|
||||
title="只撤销当前参与者的拼单份额,其他参与者不受影响"
|
||||
onClick={() => {
|
||||
setCancelShare(share)
|
||||
cancelShareForm.setFieldsValue({ reason: '' })
|
||||
}}
|
||||
>
|
||||
撤单
|
||||
单人撤单
|
||||
</Button>
|
||||
) : null}
|
||||
</Space>
|
||||
)
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
</Space>
|
||||
@@ -2381,6 +2456,66 @@ export default function WorkOrdersPanel() {
|
||||
</Form>
|
||||
) : null}
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={`拼单撤单处理 · ${getDisplayOrderNo(cancelSharingOrder)}`}
|
||||
open={Boolean(cancelSharingOrder)}
|
||||
onCancel={() => {
|
||||
setCancelSharingOrder(null)
|
||||
cancelSharingForm.resetFields()
|
||||
}}
|
||||
onOk={() => cancelSharingForm.submit()}
|
||||
destroyOnHidden
|
||||
>
|
||||
{cancelSharingOrder ? (
|
||||
<Form form={cancelSharingForm} layout="vertical" onFinish={submitCancelSharing}>
|
||||
<Alert
|
||||
showIcon
|
||||
type="warning"
|
||||
message="将批量处理所有未验收的拼单份额"
|
||||
description="退回大厅会释放未验收份额的押金并重新开放对应数量;取消整单仅在不存在已验收份额时可用。"
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<Form.Item
|
||||
label="处理方式"
|
||||
name="action"
|
||||
rules={[{ required: true, message: '请选择拼单撤单处理方式' }]}
|
||||
>
|
||||
<Radio.Group>
|
||||
<Space direction="vertical">
|
||||
{canReturnToHall ? (
|
||||
<Radio value="return_to_hall">
|
||||
退回大厅:撤回所有未验收份额,空出数量重新开放给其他打手
|
||||
</Radio>
|
||||
) : null}
|
||||
{canCancelOrder ? (
|
||||
<Radio value="cancel_order">
|
||||
取消整单:终止订单;存在已验收份额时不可用,需转售后处理
|
||||
</Radio>
|
||||
) : null}
|
||||
</Space>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="处理原因"
|
||||
name="reason"
|
||||
dependencies={['action']}
|
||||
rules={[
|
||||
({ getFieldValue }) => ({
|
||||
validator(_, value) {
|
||||
if (getFieldValue('action') !== 'cancel_order' || String(value || '').trim()) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
return Promise.reject(new Error('取消整单时请填写退款或撤单原因'))
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input.TextArea rows={3} placeholder="取消整单时请填写退款原因;退回大厅可选填" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
) : null}
|
||||
</Modal>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -728,6 +728,21 @@ export function cancelAdminWorkOrderShare(
|
||||
}>(`/api/v1/admin/worker-platform/orders/${workOrderId}/shares/${shareId}/cancel`, payload)
|
||||
}
|
||||
|
||||
export function cancelAdminWorkOrderSharing(
|
||||
workOrderId: number,
|
||||
payload: {
|
||||
action: 'return_to_hall' | 'cancel_order'
|
||||
reason?: string
|
||||
},
|
||||
) {
|
||||
return apiPost<{
|
||||
order: WorkOrder
|
||||
action: 'return_to_hall' | 'cancel_order'
|
||||
cancelledShareCount: number
|
||||
releasedDepositAmount: number
|
||||
}>(`/api/v1/admin/worker-platform/orders/${workOrderId}/sharing/cancel`, payload)
|
||||
}
|
||||
|
||||
export function acceptAdminWorkOrders(workOrderIds: number[]) {
|
||||
return apiPost<{ orders: WorkOrder[] }>('/api/v1/admin/worker-platform/orders/accept-batch', {
|
||||
workOrderIds,
|
||||
|
||||
@@ -550,6 +550,7 @@ export type WorkOrder = {
|
||||
canReopen: boolean
|
||||
canPublish: boolean
|
||||
canCancel: boolean
|
||||
canCancelSharing?: boolean
|
||||
canResolveProblem: boolean
|
||||
canDeduct: boolean
|
||||
canConfirmFriend?: boolean
|
||||
|
||||
Reference in New Issue
Block a user