feat:拼单明细管理加一个撤份额的功能,指定数量
This commit is contained in:
@@ -34,6 +34,18 @@ function formatStatusLabel(status: string) {
|
||||
return STATUS_LABELS[status] || status || '未知'
|
||||
}
|
||||
|
||||
/** 份额级问题单处置动作中文名(与后台弹窗的四个选项一一对应) */
|
||||
const SHARE_PROBLEM_ACTION_LABELS: Record<string, string> = {
|
||||
return_to_worker: '退回打手继续处理',
|
||||
reopen: '取消该份额并退回接单大厅',
|
||||
cancel_release: '取消该份额并退还押金',
|
||||
cancel_deduct: '取消该份额并扣除押金',
|
||||
}
|
||||
|
||||
function formatShareProblemAction(action: string) {
|
||||
return SHARE_PROBLEM_ACTION_LABELS[action] || ''
|
||||
}
|
||||
|
||||
function readPayload(event: WorkOrderEvent, key: string): string {
|
||||
const value = event.payload?.[key]
|
||||
if (value === undefined || value === null) return ''
|
||||
@@ -311,6 +323,88 @@ export function formatWorkOrderEvent(event: WorkOrderEvent): EventNodeStyle {
|
||||
}
|
||||
case 'sharing_config_updated':
|
||||
return { title: '更新了拼单配置', color: 'default' }
|
||||
case 'sharing_joined': {
|
||||
const workerLabel = resolveEventWorkerLabel(event)
|
||||
const quantity = readPayloadAmount(event, 'quantity')
|
||||
const deadlineAt = readPayload(event, 'deadlineAt')
|
||||
const timeoutMinutes = readPayloadAmount(event, 'timeoutMinutes')
|
||||
const deadlineText = deadlineAt
|
||||
? `截止 ${dayjs(deadlineAt).format('MM-DD HH:mm')}`
|
||||
: timeoutMinutes > 0
|
||||
? `限时 ${Math.round(timeoutMinutes)} 分钟`
|
||||
: '不限时'
|
||||
return {
|
||||
title: workerLabel ? `打手参与拼单(${workerLabel})` : '打手参与拼单',
|
||||
description: [quantity > 0 ? `参与 ${quantity} 份` : '', deadlineText]
|
||||
.filter(Boolean)
|
||||
.join(' · '),
|
||||
color: 'purple',
|
||||
}
|
||||
}
|
||||
case 'sharing_share_quantity_reduced': {
|
||||
const workerLabel = resolveEventWorkerLabel(event)
|
||||
const fromQuantity = readPayloadAmount(event, 'fromQuantity')
|
||||
const toQuantity = readPayloadAmount(event, 'toQuantity')
|
||||
const reduceQuantity = readPayloadAmount(event, 'reduceQuantity')
|
||||
const details = [
|
||||
fromQuantity > 0 && toQuantity > 0 ? `${fromQuantity} 份 → ${toQuantity} 份` : '',
|
||||
reduceQuantity > 0 ? `撤销 ${reduceQuantity} 份` : '',
|
||||
note ? `原因:${note}` : '',
|
||||
'撤掉的份数已退回接单大厅,押金保持冻结',
|
||||
].filter(Boolean)
|
||||
return {
|
||||
title: workerLabel ? `撤销部分拼单份额(${workerLabel})` : '撤销部分拼单份额',
|
||||
description: details.join(';'),
|
||||
color: 'orange',
|
||||
}
|
||||
}
|
||||
case 'sharing_share_marked_problem': {
|
||||
const workerLabel = resolveEventWorkerLabel(event)
|
||||
return {
|
||||
title: workerLabel ? `标记拼单份额问题单(${workerLabel})` : '标记拼单份额问题单',
|
||||
description: note
|
||||
? `原因:${note};仅该份额受影响,母单状态不变`
|
||||
: '未填备注;仅该份额受影响,母单状态不变',
|
||||
color: 'red',
|
||||
}
|
||||
}
|
||||
case 'sharing_share_problem_disposed': {
|
||||
const workerLabel = resolveEventWorkerLabel(event)
|
||||
const actionLabel = formatShareProblemAction(readPayload(event, 'action'))
|
||||
const depositAmount = readPayloadAmount(event, 'depositAmount')
|
||||
const details = [
|
||||
actionLabel ? `处理方式:${actionLabel}` : '',
|
||||
depositAmount > 0 ? `涉及押金 ${formatFenAmount(depositAmount)}` : '',
|
||||
note ? `备注:${note}` : '',
|
||||
].filter(Boolean)
|
||||
return {
|
||||
title: workerLabel ? `处置拼单份额问题单(${workerLabel})` : '处置拼单份额问题单',
|
||||
description: details.length > 0 ? details.join(';') : undefined,
|
||||
color: 'orange',
|
||||
}
|
||||
}
|
||||
case 'sharing_acceptance_draft_saved':
|
||||
case 'sharing_acceptance_updated': {
|
||||
const workerLabel = resolveEventWorkerLabel(event)
|
||||
const imageCount = readPayloadAmount(event, 'imageCount')
|
||||
const hasVideo = Boolean(event.payload?.hasVideo)
|
||||
const details = [
|
||||
imageCount > 0 ? `验收图 ${imageCount} 张` : '',
|
||||
hasVideo ? '含验收视频' : '',
|
||||
].filter(Boolean)
|
||||
return {
|
||||
title:
|
||||
type === 'sharing_acceptance_draft_saved'
|
||||
? workerLabel
|
||||
? `保存了拼单验收草稿(${workerLabel})`
|
||||
: '保存了拼单验收草稿'
|
||||
: workerLabel
|
||||
? `更新了拼单验收资料(${workerLabel})`
|
||||
: '更新了拼单验收资料',
|
||||
description: details.length > 0 ? details.join(' · ') : undefined,
|
||||
color: 'purple',
|
||||
}
|
||||
}
|
||||
case 'price_synced_from_rule': {
|
||||
const rewardAmount = Number(readPayload(event, 'rewardAmount') || 0)
|
||||
const unitReward = Number(readPayload(event, 'sharingUnitReward') || 0)
|
||||
|
||||
@@ -72,6 +72,7 @@ import {
|
||||
markAdminWorkOrderShareProblem,
|
||||
pinAdminWorkOrder,
|
||||
publishAdminWorkOrder,
|
||||
reduceAdminWorkOrderShareQuantity,
|
||||
reopenAdminCancelledWorkOrder,
|
||||
resetAdminWorkOrderGiftMaterial,
|
||||
resolveAdminProblemWorkOrder,
|
||||
@@ -306,6 +307,7 @@ export default function WorkOrdersPanel() {
|
||||
const [materialPasteText, setMaterialPasteText] = useState('')
|
||||
const [sharingOrder, setSharingOrder] = useState<WorkOrder | null>(null)
|
||||
const [cancelShare, setCancelShare] = useState<WorkOrderShare | null>(null)
|
||||
const [reduceShare, setReduceShare] = useState<WorkOrderShare | null>(null)
|
||||
const [problemShare, setProblemShare] = useState<WorkOrderShare | null>(null)
|
||||
const [disposeShare, setDisposeShare] = useState<WorkOrderShare | null>(null)
|
||||
const [cancelSharingOrder, setCancelSharingOrder] = useState<WorkOrder | null>(null)
|
||||
@@ -320,6 +322,7 @@ export default function WorkOrdersPanel() {
|
||||
const [materialForm] = Form.useForm<MaterialFormValues>()
|
||||
const [cancelForm] = Form.useForm<CancelOrderFormValues>()
|
||||
const [cancelShareForm] = Form.useForm<CancelShareFormValues>()
|
||||
const [reduceShareForm] = Form.useForm<{ reduceQuantity?: number; note?: string }>()
|
||||
const [problemShareForm] = Form.useForm<{ note?: string }>()
|
||||
const [disposeShareForm] = Form.useForm<{
|
||||
action?: 'return_to_worker' | 'reopen' | 'cancel_release' | 'cancel_deduct'
|
||||
@@ -725,6 +728,32 @@ export default function WorkOrdersPanel() {
|
||||
cancelShareForm.resetFields()
|
||||
}
|
||||
|
||||
/** 按份数撤销部分份额:减少的份数回到大厅,押金保持冻结。 */
|
||||
async function submitReduceShare(values: { reduceQuantity?: number; note?: string }) {
|
||||
if (!sharingOrder || !reduceShare) return
|
||||
const workOrderId = sharingOrder.workOrderId
|
||||
const reduceQuantity = Math.floor(Number(values.reduceQuantity || 0))
|
||||
if (!Number.isFinite(reduceQuantity) || reduceQuantity <= 0) {
|
||||
message.warning('请填写要撤掉的份数')
|
||||
return
|
||||
}
|
||||
const keepQuantity = Math.max(0, reduceShare.quantity - reduceQuantity)
|
||||
const succeeded = await runAction(
|
||||
() =>
|
||||
reduceAdminWorkOrderShareQuantity(workOrderId, reduceShare.shareId, {
|
||||
reduceQuantity,
|
||||
note: String(values.note || '').trim(),
|
||||
}),
|
||||
`已撤掉 ${reduceQuantity} 份,保留 ${keepQuantity} 份继续履约`,
|
||||
)
|
||||
if (!succeeded) return
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ['admin-worker-platform-order-sharing', workOrderId],
|
||||
})
|
||||
setReduceShare(null)
|
||||
reduceShareForm.resetFields()
|
||||
}
|
||||
|
||||
/** 按份额标记问题单:只影响该打手,母单状态不变。备注选填。 */
|
||||
async function submitProblemShare(values: { note?: string }) {
|
||||
if (!sharingOrder || !problemShare) return
|
||||
@@ -3083,9 +3112,11 @@ export default function WorkOrdersPanel() {
|
||||
onCancel={() => {
|
||||
setSharingOrder(null)
|
||||
setCancelShare(null)
|
||||
setReduceShare(null)
|
||||
setCancelSharingOrder(null)
|
||||
sharingForm.resetFields()
|
||||
cancelShareForm.resetFields()
|
||||
reduceShareForm.resetFields()
|
||||
cancelSharingForm.resetFields()
|
||||
}}
|
||||
onOk={() => sharingForm.submit()}
|
||||
@@ -3218,7 +3249,7 @@ export default function WorkOrdersPanel() {
|
||||
loading={sharingQuery.isFetching}
|
||||
pagination={false}
|
||||
tableLayout="fixed"
|
||||
scroll={{ x: 1624 }}
|
||||
scroll={{ x: 1794 }}
|
||||
rowClassName={(share) =>
|
||||
share.problemMarked
|
||||
? 'admin-share-row-problem'
|
||||
@@ -3363,6 +3394,27 @@ export default function WorkOrdersPanel() {
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '撤单记录',
|
||||
width: 170,
|
||||
render: (_, share) => {
|
||||
// 份数前缀由展示层拼接:接口只给结构化的份数与原因文本
|
||||
const cancelledQuantity = Math.max(
|
||||
0,
|
||||
Math.floor(Number(share.cancelQuantity || 0)),
|
||||
)
|
||||
const note = String(share.cancelNote || '').trim()
|
||||
if (!cancelledQuantity && !note) return '-'
|
||||
const text = cancelledQuantity
|
||||
? `撤 ${cancelledQuantity} 份${note ? ` · ${note}` : ''}`
|
||||
: note
|
||||
return (
|
||||
<Typography.Text style={{ fontSize: 12 }} ellipsis={{ tooltip: text }}>
|
||||
{text}
|
||||
</Typography.Text>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 300,
|
||||
@@ -3451,6 +3503,24 @@ export default function WorkOrdersPanel() {
|
||||
单人撤单
|
||||
</Button>
|
||||
) : null}
|
||||
{(share.status === 'joined' || share.status === 'submitted') &&
|
||||
share.quantity >= 2 ? (
|
||||
<Button
|
||||
danger
|
||||
size="small"
|
||||
icon={<UndoOutlined />}
|
||||
title="按份数撤销部分份额:减少的份数立即回到大厅,押金保持冻结,验收资料保留"
|
||||
onClick={() => {
|
||||
setReduceShare(share)
|
||||
reduceShareForm.setFieldsValue({
|
||||
reduceQuantity: 1,
|
||||
note: '',
|
||||
})
|
||||
}}
|
||||
>
|
||||
撤份额
|
||||
</Button>
|
||||
) : null}
|
||||
{problemAction}
|
||||
</Space>
|
||||
)
|
||||
@@ -3496,6 +3566,75 @@ export default function WorkOrdersPanel() {
|
||||
) : null}
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={`撤份额 · ${reduceShare?.worker?.displayName || reduceShare?.worker?.username || ''}`}
|
||||
open={Boolean(reduceShare)}
|
||||
okText="确认撤销"
|
||||
cancelText="取消"
|
||||
okButtonProps={{ danger: true }}
|
||||
onCancel={() => {
|
||||
setReduceShare(null)
|
||||
reduceShareForm.resetFields()
|
||||
}}
|
||||
onOk={() => reduceShareForm.submit()}
|
||||
destroyOnHidden
|
||||
>
|
||||
{reduceShare ? (
|
||||
<Form
|
||||
form={reduceShareForm}
|
||||
layout="vertical"
|
||||
initialValues={{ reduceQuantity: 1 }}
|
||||
onFinish={submitReduceShare}
|
||||
>
|
||||
<Alert
|
||||
showIcon
|
||||
type="warning"
|
||||
message={`将从该打手的 ${reduceShare.quantity} 份中撤掉指定份数`}
|
||||
description="撤掉的份数会立刻回到抢单大厅;押金保持原冻结额不退还;验收资料保留,之后仍可单独验收剩余份数。"
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<Form.Item
|
||||
label={`撤掉份数(1 ~ ${Math.max(1, reduceShare.quantity - 1)} 份)`}
|
||||
name="reduceQuantity"
|
||||
rules={[{ required: true, message: '请填写要撤掉的份数' }]}
|
||||
>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={Math.max(1, reduceShare.quantity - 1)}
|
||||
precision={0}
|
||||
addonAfter="份"
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item noStyle shouldUpdate>
|
||||
{() => {
|
||||
const reduceQuantity = Math.floor(
|
||||
Number(reduceShareForm.getFieldValue('reduceQuantity') || 0),
|
||||
)
|
||||
const boundedReduce = Math.max(
|
||||
0,
|
||||
Math.min(Math.max(0, reduceShare.quantity - 1), reduceQuantity),
|
||||
)
|
||||
const keepQuantity = Math.max(0, reduceShare.quantity - boundedReduce)
|
||||
return (
|
||||
<Typography.Text type="secondary">
|
||||
调整后保留 {keepQuantity} 份,回到大厅 {boundedReduce} 份;剩余份额报酬{' '}
|
||||
{formatMoney(keepQuantity * Number(reduceShare.unitReward || 0))}
|
||||
</Typography.Text>
|
||||
)
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item label="撤销原因(选填)" name="note">
|
||||
<Input.TextArea
|
||||
rows={3}
|
||||
maxLength={200}
|
||||
placeholder="如:打手账号实际只能赠送 5 份"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
) : null}
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={`标记份额问题单 · ${
|
||||
problemShare?.worker?.displayName || problemShare?.worker?.username || ''
|
||||
|
||||
@@ -892,6 +892,18 @@ export function cancelAdminWorkOrderShare(
|
||||
}>(`/api/v1/admin/worker-platform/orders/${workOrderId}/shares/${shareId}/cancel`, payload)
|
||||
}
|
||||
|
||||
/** 按份数撤销某个打手的部分份额:减少的份数回到大厅,押金保持原冻结额。 */
|
||||
export function reduceAdminWorkOrderShareQuantity(
|
||||
workOrderId: number,
|
||||
shareId: number,
|
||||
payload: { reduceQuantity: number; note?: string },
|
||||
) {
|
||||
return apiPost<{ order: WorkOrder; share: WorkOrderShare }>(
|
||||
`/api/v1/admin/worker-platform/orders/${workOrderId}/shares/${shareId}/reduce-quantity`,
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
/** 按份额标记问题单:只影响该份额持有人,母单状态不变。备注选填。 */
|
||||
export function markAdminWorkOrderShareProblem(workOrderId: number, shareId: number, note = '') {
|
||||
return apiPost<{ order: WorkOrder; share: WorkOrderShare }>(
|
||||
|
||||
@@ -572,6 +572,10 @@ export type WorkOrderShare = {
|
||||
timeoutMinutes?: number
|
||||
timeoutPolicy?: string
|
||||
cancelledReason?: string
|
||||
/** 后台撤单/撤份额时填写的原因(自由文本,选填) */
|
||||
cancelNote?: string
|
||||
/** 被撤掉的份数(撤份额累计 / 单人撤单为整份份数;0 = 未撤过) */
|
||||
cancelQuantity?: number
|
||||
/** 份额级问题单:只影响该份额持有人,母单状态不变 */
|
||||
problemMarked?: boolean
|
||||
problemNote?: string
|
||||
|
||||
Reference in New Issue
Block a user