修复拼单明细并支持参与者撤单
This commit is contained in:
@@ -199,6 +199,16 @@ export function resolveWorkOrderShareJoinQuantity(input: {
|
||||
return input.takeRemaining ? Math.max(0, input.remaining) : input.quantity
|
||||
}
|
||||
|
||||
/** 校验单个拼单份额能否撤单,并返回母工单应回退到的状态。 */
|
||||
export function resolveWorkOrderShareCancellationStatus(
|
||||
workOrderStatus: string,
|
||||
shareStatus: string,
|
||||
): 'open' | null {
|
||||
if (!['open', 'pending_acceptance'].includes(workOrderStatus)) return null
|
||||
if (!['joined', 'submitted'].includes(shareStatus)) return null
|
||||
return 'open'
|
||||
}
|
||||
|
||||
export async function joinWorkOrderShare(input: {
|
||||
workOrderId: number
|
||||
workerId: number
|
||||
@@ -2038,7 +2048,9 @@ export async function acceptWorkOrderAndSettle(input: {
|
||||
)
|
||||
|
||||
if (shares.length > 0) {
|
||||
for (const share of shares) {
|
||||
// 单独验收过的份额已经完成资金结算,统一验收时只处理尚未结算的份额。
|
||||
const unsettledShares = shares.filter((share) => share.status !== 'accepted')
|
||||
for (const share of unsettledShares) {
|
||||
const shareWorkerId = Number(share.worker_id || 0)
|
||||
if (!shareWorkerId) continue
|
||||
await ensureWorkerWalletWithClient(client, shareWorkerId, input.now)
|
||||
@@ -2263,7 +2275,10 @@ export async function acceptWorkOrderAndSettle(input: {
|
||||
eventType: 'accepted',
|
||||
fromStatus: 'pending_acceptance',
|
||||
toStatus: 'accepted',
|
||||
payloadJson: JSON.stringify({ shareCount: shares.length }),
|
||||
payloadJson: JSON.stringify({
|
||||
shareCount: shares.length,
|
||||
settledShareCount: shares.filter((share) => share.status !== 'accepted').length,
|
||||
}),
|
||||
now: input.now,
|
||||
})
|
||||
|
||||
@@ -2437,6 +2452,149 @@ export async function acceptWorkOrderShareAndSettle(input: {
|
||||
})
|
||||
}
|
||||
|
||||
/** 后台撤销单个拼单份额,释放该打手押金并把份数重新放回大厅。 */
|
||||
export async function cancelWorkOrderShare(input: {
|
||||
workOrderId: number
|
||||
shareId: number
|
||||
now: string
|
||||
reason?: string
|
||||
actorName?: string
|
||||
}): Promise<{
|
||||
order: WorkOrderRow | null
|
||||
share: WorkOrderShareRow | null
|
||||
releasedDepositAmount: number
|
||||
failureReason:
|
||||
| 'work_order_not_cancellable'
|
||||
| 'share_not_found'
|
||||
| 'share_status_not_cancellable'
|
||||
| null
|
||||
}> {
|
||||
return withTransaction(async (client) => {
|
||||
const orderResult = await client.query<WorkOrderRow>(
|
||||
`SELECT * FROM work_orders WHERE id = $1 FOR UPDATE`,
|
||||
[input.workOrderId],
|
||||
)
|
||||
const workOrder = orderResult.rows[0] || null
|
||||
if (!workOrder || !['open', 'pending_acceptance'].includes(workOrder.status)) {
|
||||
return {
|
||||
order: workOrder,
|
||||
share: null,
|
||||
releasedDepositAmount: 0,
|
||||
failureReason: 'work_order_not_cancellable',
|
||||
}
|
||||
}
|
||||
|
||||
const shareResult = await client.query<WorkOrderShareRow>(
|
||||
`SELECT * FROM work_order_shares WHERE id = $1 AND work_order_id = $2 FOR UPDATE`,
|
||||
[input.shareId, input.workOrderId],
|
||||
)
|
||||
const share = shareResult.rows[0] || null
|
||||
if (!share) {
|
||||
return {
|
||||
order: workOrder,
|
||||
share: null,
|
||||
releasedDepositAmount: 0,
|
||||
failureReason: 'share_not_found',
|
||||
}
|
||||
}
|
||||
const nextOrderStatus = resolveWorkOrderShareCancellationStatus(workOrder.status, share.status)
|
||||
if (!nextOrderStatus) {
|
||||
return {
|
||||
order: workOrder,
|
||||
share,
|
||||
releasedDepositAmount: 0,
|
||||
failureReason: 'share_status_not_cancellable',
|
||||
}
|
||||
}
|
||||
|
||||
const workerId = Number(share.worker_id || 0)
|
||||
let releasedDepositAmount = 0
|
||||
if (workerId > 0) {
|
||||
await ensureWorkerWalletWithClient(client, workerId, input.now)
|
||||
const wallet = await getWorkerWalletWithClient(client, workerId)
|
||||
releasedDepositAmount = Math.min(
|
||||
Number(share.share_deposit || 0),
|
||||
Number(wallet?.frozen_deposit_amount || 0),
|
||||
)
|
||||
if (releasedDepositAmount > 0) {
|
||||
const nextAvailable = Number(wallet?.available_amount || 0) + releasedDepositAmount
|
||||
const nextFrozen = Math.max(
|
||||
0,
|
||||
Number(wallet?.frozen_deposit_amount || 0) - releasedDepositAmount,
|
||||
)
|
||||
await client.query(
|
||||
`UPDATE worker_wallets
|
||||
SET available_amount = $1, frozen_deposit_amount = $2, updated_at = $3
|
||||
WHERE worker_id = $4`,
|
||||
[nextAvailable, nextFrozen, input.now, workerId],
|
||||
)
|
||||
await client.query(
|
||||
`INSERT INTO worker_wallet_ledgers (
|
||||
worker_id, ledger_type, amount, balance_after, frozen_after,
|
||||
related_work_order_id, note, payload_json, created_at
|
||||
) VALUES ($1, 'deposit_release', $2, $3, $4, $5, '后台撤销拼单份额退还押金', $6::jsonb, $7)`,
|
||||
[
|
||||
workerId,
|
||||
releasedDepositAmount,
|
||||
nextAvailable,
|
||||
nextFrozen,
|
||||
input.workOrderId,
|
||||
JSON.stringify({
|
||||
workOrderId: input.workOrderId,
|
||||
shareId: input.shareId,
|
||||
reason: String(input.reason || '').trim(),
|
||||
}),
|
||||
input.now,
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
await client.query(
|
||||
`UPDATE work_order_shares
|
||||
SET status = 'cancelled', updated_at = $1
|
||||
WHERE id = $2`,
|
||||
[input.now, input.shareId],
|
||||
)
|
||||
await client.query(
|
||||
`UPDATE work_orders
|
||||
SET status = $1,
|
||||
submitted_at = NULL,
|
||||
published_at = COALESCE(published_at, $2),
|
||||
updated_at = $2
|
||||
WHERE id = $3`,
|
||||
[nextOrderStatus, input.now, input.workOrderId],
|
||||
)
|
||||
await createWorkOrderEventWithClient(client, {
|
||||
workOrderId: input.workOrderId,
|
||||
actorType: 'admin',
|
||||
actorId: input.actorName || '',
|
||||
eventType: 'sharing_share_cancelled_by_admin',
|
||||
fromStatus: workOrder.status,
|
||||
toStatus: nextOrderStatus,
|
||||
payloadJson: JSON.stringify({
|
||||
shareId: input.shareId,
|
||||
workerId,
|
||||
quantity: Number(share.quantity || 0),
|
||||
releasedDepositAmount,
|
||||
reason: String(input.reason || '').trim(),
|
||||
}),
|
||||
now: input.now,
|
||||
})
|
||||
|
||||
const updatedShareResult = await client.query<WorkOrderShareRow>(
|
||||
`${WORK_ORDER_SHARE_SELECT} WHERE wos.id = $1 LIMIT 1`,
|
||||
[input.shareId],
|
||||
)
|
||||
return {
|
||||
order: await getWorkOrderByIdWithClient(client, input.workOrderId),
|
||||
share: updatedShareResult.rows[0] || null,
|
||||
releasedDepositAmount,
|
||||
failureReason: null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function resolveProblemWorkOrder(input: {
|
||||
workOrderId: number
|
||||
action: ProblemWorkOrderResolutionAction
|
||||
@@ -3230,7 +3388,16 @@ function buildWorkOrderWhere({
|
||||
}
|
||||
if (workerId) {
|
||||
params.push(workerId)
|
||||
filters.push(`wo.assigned_worker_id = $${params.length}`)
|
||||
filters.push(`(
|
||||
wo.assigned_worker_id = $${params.length}
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM work_order_shares wos
|
||||
WHERE wos.work_order_id = wo.id
|
||||
AND wos.worker_id = $${params.length}
|
||||
AND wos.status != 'cancelled'
|
||||
)
|
||||
)`)
|
||||
}
|
||||
if (categoryId) {
|
||||
params.push(categoryId)
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
acceptAdminWorkOrders,
|
||||
assignAdminWorkOrderToWorker,
|
||||
cancelAdminWorkOrder,
|
||||
cancelAdminWorkOrderShare,
|
||||
unassignAdminWorkOrder,
|
||||
creditAdminWorkerWallet,
|
||||
createAdminMockWorkOrder,
|
||||
@@ -854,6 +855,31 @@ router.post(
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/worker-platform/orders/:workOrderId/shares/:shareId/cancel',
|
||||
requireAdminRoles(['admin', 'operator', 'support']),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
cancelAdminWorkOrderShare(
|
||||
String(req.params.workOrderId || ''),
|
||||
String(req.params.shareId || ''),
|
||||
req.body || {},
|
||||
req.adminSession?.username || '',
|
||||
),
|
||||
{
|
||||
successMessage: '拼单参与者已撤单,份数已退回大厅',
|
||||
errorMessage: '撤销拼单参与者失败',
|
||||
scope: '[admin/worker-platform/orders/:workOrderId/shares/:shareId/cancel]',
|
||||
audit: (req, data) => ({
|
||||
action: 'work_order_share_cancelled',
|
||||
targetType: 'work_order_share',
|
||||
targetId: String(req.params.shareId || ''),
|
||||
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
||||
}),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/worker-platform/orders/:workOrderId/deduct-pending-deposit',
|
||||
requireAdminRoles(['admin', 'operator']),
|
||||
|
||||
@@ -310,11 +310,13 @@ async function publishWorkOrderSnapshots(input: {
|
||||
const viewedAt =
|
||||
views.find((item) => Number(item.work_order_id) === workOrderId)?.viewed_at || null
|
||||
const share = shares.find((item) => Number(item.worker_id) === workerId)
|
||||
const isAssignedWorker = Number(workOrder.assigned_worker_id || 0) === workerId
|
||||
const workerOrderVisible = isAssignedWorker || Boolean(share && share.status !== 'cancelled')
|
||||
publishRealtimeEvent({
|
||||
type: 'work_order.changed',
|
||||
entityId: workOrderId,
|
||||
scopes: ['worker_my_orders'],
|
||||
operation: 'upsert',
|
||||
operation: workerOrderVisible ? 'upsert' : 'remove',
|
||||
data: mapWorkOrderForWorker(
|
||||
workOrder,
|
||||
resolveWorkerPermissions(worker),
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
addWorkerWalletCredit,
|
||||
assignWorkOrderToWorker,
|
||||
cancelAssignedWorkOrder,
|
||||
cancelWorkOrderShare,
|
||||
returnAssignedWorkOrderToHall,
|
||||
unassignWorkOrder,
|
||||
countWorkerActiveOrders,
|
||||
@@ -2040,6 +2041,52 @@ export async function acceptAdminWorkOrderShare(
|
||||
}
|
||||
}
|
||||
|
||||
export async function cancelAdminWorkOrderShare(
|
||||
workOrderId: number | string,
|
||||
shareId: number | string,
|
||||
payload: JsonObject = {},
|
||||
actorName = '',
|
||||
) {
|
||||
const result = await cancelWorkOrderShare({
|
||||
workOrderId: Number(workOrderId),
|
||||
shareId: Number(shareId),
|
||||
now: nowIso(),
|
||||
reason: String(payload.reason || '').trim(),
|
||||
actorName,
|
||||
})
|
||||
if (result.failureReason === 'work_order_not_cancellable') {
|
||||
throw createHttpError('当前拼单状态不能撤销参与者', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_sharing_cancel_status_invalid',
|
||||
})
|
||||
}
|
||||
if (result.failureReason === 'share_not_found') {
|
||||
throw createHttpError('未找到该拼单参与记录', {
|
||||
statusCode: 404,
|
||||
errorCode: 'work_order_sharing_share_not_found',
|
||||
})
|
||||
}
|
||||
if (result.failureReason === 'share_status_not_cancellable' || !result.share || !result.order) {
|
||||
throw createHttpError('只有已参与或已提交验收的拼单份额可以撤单', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_sharing_share_cancel_status_invalid',
|
||||
})
|
||||
}
|
||||
|
||||
const workerId = Number(result.share.worker_id || 0)
|
||||
publishWorkOrderRealtimeChange({
|
||||
workOrderId: Number(workOrderId),
|
||||
workerIds: await resolveWorkOrderRealtimeWorkerIds(result.order),
|
||||
hallChanged: true,
|
||||
})
|
||||
if (workerId > 0) publishWorkerWalletRealtimeChange(workerId)
|
||||
return {
|
||||
order: mapWorkOrderAdmin(result.order),
|
||||
share: mapWorkOrderShare(result.share),
|
||||
releasedDepositAmount: result.releasedDepositAmount,
|
||||
}
|
||||
}
|
||||
|
||||
export async function acceptAdminWorkOrders(payload: JsonObject = {}, actorName = '') {
|
||||
const workOrderIds = [
|
||||
...new Set(
|
||||
|
||||
@@ -8,7 +8,10 @@ import type {
|
||||
WorkProductRuleRow,
|
||||
WorkerUserRow,
|
||||
} from '../../repositories/worker-platform/index.js'
|
||||
import { resolveWorkOrderShareJoinQuantity } from '../../repositories/worker-platform/index.js'
|
||||
import {
|
||||
resolveWorkOrderShareCancellationStatus,
|
||||
resolveWorkOrderShareJoinQuantity,
|
||||
} from '../../repositories/worker-platform/index.js'
|
||||
import type { OrderItemRow, OrderRow } from '../../types/repository/rows.js'
|
||||
import {
|
||||
assertWithdrawChannelAllowedForAmount,
|
||||
@@ -729,6 +732,15 @@ test('后台拼单聚合状态同时收敛筛选语义和操作能力', () => {
|
||||
assert.equal(filled.capabilities.canUnpublish, false)
|
||||
})
|
||||
|
||||
test('拼单份额撤单只允许未验收份额并让母工单回到大厅状态', () => {
|
||||
assert.equal(resolveWorkOrderShareCancellationStatus('open', 'joined'), 'open')
|
||||
assert.equal(resolveWorkOrderShareCancellationStatus('open', 'submitted'), 'open')
|
||||
assert.equal(resolveWorkOrderShareCancellationStatus('pending_acceptance', 'submitted'), 'open')
|
||||
assert.equal(resolveWorkOrderShareCancellationStatus('pending_acceptance', 'accepted'), null)
|
||||
assert.equal(resolveWorkOrderShareCancellationStatus('accepted', 'submitted'), null)
|
||||
assert.equal(resolveWorkOrderShareCancellationStatus('open', 'cancelled'), null)
|
||||
})
|
||||
|
||||
test('resolveCollectSubmitTargetWorkOrder returns the only pending work order when no id is provided', () => {
|
||||
const workOrder = buildWorkOrderRow()
|
||||
const selected = resolveCollectSubmitTargetWorkOrder('ORDER-001', [workOrder], '')
|
||||
|
||||
@@ -182,6 +182,15 @@ export function formatWorkOrderEvent(event: WorkOrderEvent): EventNodeStyle {
|
||||
return { title: '更改了订单信息', color: 'default' }
|
||||
case 'sharing_config_updated':
|
||||
return { title: '更新了拼单配置', color: 'default' }
|
||||
case 'sharing_share_cancelled_by_admin': {
|
||||
const workerId = readPayload(event, 'workerId')
|
||||
const quantity = readPayload(event, 'quantity')
|
||||
return {
|
||||
title: `撤销拼单参与者${workerId ? `(打手 #${workerId})` : ''}`,
|
||||
description: `${quantity ? `撤销 ${quantity} 份;` : ''}${note ? `原因:${note}` : '份数已退回接单大厅'}`,
|
||||
color: 'red',
|
||||
}
|
||||
}
|
||||
case 'pending_deposit_deducted':
|
||||
return { title: '扣减了待解冻押金', color: 'default' }
|
||||
case 'voucher_backfilled':
|
||||
|
||||
@@ -349,13 +349,17 @@ function applyAdminRealtimeEvent(
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
event.type === 'work_order.changed' &&
|
||||
event.scopes.includes('admin_work_orders') &&
|
||||
isRecord(event.data)
|
||||
) {
|
||||
const data = event.data
|
||||
patchAdminWorkOrderCaches(queryClient, { ...event, data })
|
||||
if (event.type === 'work_order.changed' && event.scopes.includes('admin_work_orders')) {
|
||||
// 拼单明细是独立查询,工单列表快照更新时也要同步刷新当前打开的明细。
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: ['admin-worker-platform-order-sharing', event.entityId],
|
||||
})
|
||||
if (isRecord(event.data)) {
|
||||
const data = event.data
|
||||
patchAdminWorkOrderCaches(queryClient, { ...event, data })
|
||||
} else {
|
||||
void queryClient.invalidateQueries({ queryKey: ['admin-worker-platform-orders'] })
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
acceptAdminWorkOrders,
|
||||
assignAdminWorkOrderToWorker,
|
||||
cancelAdminWorkOrder,
|
||||
cancelAdminWorkOrderShare,
|
||||
createAdminMockWorkOrder,
|
||||
deductAdminWorkOrderPendingDeposit,
|
||||
deleteAdminWorkOrder,
|
||||
@@ -120,6 +121,10 @@ type CancelOrderFormValues = {
|
||||
reason?: string
|
||||
}
|
||||
|
||||
type CancelShareFormValues = {
|
||||
reason?: string
|
||||
}
|
||||
|
||||
export default function WorkOrdersPanel() {
|
||||
const { message, modal } = App.useApp()
|
||||
const navigate = useNavigate()
|
||||
@@ -142,6 +147,7 @@ export default function WorkOrdersPanel() {
|
||||
const [materialScreenshots, setMaterialScreenshots] = useState<UploadedFile[]>([])
|
||||
const [materialPasteText, setMaterialPasteText] = useState('')
|
||||
const [sharingOrder, setSharingOrder] = useState<WorkOrder | null>(null)
|
||||
const [cancelShare, setCancelShare] = useState<WorkOrderShare | null>(null)
|
||||
const [assignOrder, setAssignOrder] = useState<WorkOrder | null>(null)
|
||||
const [assignWorkerId, setAssignWorkerId] = useState<number | undefined>(undefined)
|
||||
const [editOrder, setEditOrder] = useState<WorkOrder | null>(null)
|
||||
@@ -151,6 +157,7 @@ export default function WorkOrdersPanel() {
|
||||
const [resolutionForm] = Form.useForm()
|
||||
const [materialForm] = Form.useForm<MaterialFormValues>()
|
||||
const [cancelForm] = Form.useForm<CancelOrderFormValues>()
|
||||
const [cancelShareForm] = Form.useForm<CancelShareFormValues>()
|
||||
const [deductForm] = Form.useForm<{ amount?: number; note?: string }>()
|
||||
const [editForm] = Form.useForm<{
|
||||
productName?: string
|
||||
@@ -349,6 +356,24 @@ export default function WorkOrdersPanel() {
|
||||
sharingForm.resetFields()
|
||||
}
|
||||
|
||||
async function submitCancelShare(values: CancelShareFormValues) {
|
||||
if (!sharingOrder || !cancelShare) return
|
||||
const workOrderId = sharingOrder.workOrderId
|
||||
const succeeded = await runAction(
|
||||
() =>
|
||||
cancelAdminWorkOrderShare(workOrderId, cancelShare.shareId, {
|
||||
reason: String(values.reason || '').trim(),
|
||||
}),
|
||||
'该参与者已撤单,押金和拼单份数已恢复',
|
||||
)
|
||||
if (!succeeded) return
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ['admin-worker-platform-order-sharing', workOrderId],
|
||||
})
|
||||
setCancelShare(null)
|
||||
cancelShareForm.resetFields()
|
||||
}
|
||||
|
||||
async function refreshAll() {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({
|
||||
@@ -816,7 +841,9 @@ export default function WorkOrdersPanel() {
|
||||
) : null}
|
||||
{row.sharing?.enabled || row.capabilities?.canConfigureSharing ? (
|
||||
<Button icon={<TeamOutlined />} onClick={() => openSharingConfigModal(row)}>
|
||||
{row.sharing?.enabled && !row.capabilities?.canConfigureSharing ? '拼单明细' : '拼单配置'}
|
||||
{row.sharing?.enabled && !row.capabilities?.canConfigureSharing
|
||||
? '拼单明细'
|
||||
: '拼单配置'}
|
||||
</Button>
|
||||
) : null}
|
||||
{row.capabilities?.canAssign ? (
|
||||
@@ -1589,7 +1616,9 @@ export default function WorkOrdersPanel() {
|
||||
open={Boolean(sharingOrder)}
|
||||
onCancel={() => {
|
||||
setSharingOrder(null)
|
||||
setCancelShare(null)
|
||||
sharingForm.resetFields()
|
||||
cancelShareForm.resetFields()
|
||||
}}
|
||||
onOk={() => sharingForm.submit()}
|
||||
footer={sharingOrder?.capabilities?.canConfigureSharing ? undefined : null}
|
||||
@@ -1679,6 +1708,7 @@ export default function WorkOrdersPanel() {
|
||||
rowKey="shareId"
|
||||
size="small"
|
||||
dataSource={sharingQuery.data?.data.shares || []}
|
||||
loading={sharingQuery.isFetching}
|
||||
pagination={false}
|
||||
columns={[
|
||||
{
|
||||
@@ -1709,8 +1739,14 @@ export default function WorkOrdersPanel() {
|
||||
width: 150,
|
||||
render: (_, share) => (
|
||||
<ImagePreviewList
|
||||
files={Array.isArray(share.acceptance?.files) ? share.acceptance.files : []}
|
||||
imageUrls={Array.isArray(share.acceptance?.imageUrls) ? share.acceptance.imageUrls : []}
|
||||
files={
|
||||
Array.isArray(share.acceptance?.files) ? share.acceptance.files : []
|
||||
}
|
||||
imageUrls={
|
||||
Array.isArray(share.acceptance?.imageUrls)
|
||||
? share.acceptance.imageUrls
|
||||
: []
|
||||
}
|
||||
size={44}
|
||||
maxVisible={2}
|
||||
/>
|
||||
@@ -1722,35 +1758,55 @@ export default function WorkOrdersPanel() {
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 110,
|
||||
render: (_, share) =>
|
||||
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.workOrderId,
|
||||
],
|
||||
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.workOrderId,
|
||||
],
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}}
|
||||
>
|
||||
单独验收
|
||||
</Button>
|
||||
) : share.status === 'accepted' ? (
|
||||
<Tag color="green">已验收</Tag>
|
||||
) : (
|
||||
<Typography.Text type="secondary">待提交</Typography.Text>
|
||||
),
|
||||
}}
|
||||
>
|
||||
单独验收
|
||||
</Button>
|
||||
) : (
|
||||
<Typography.Text type="secondary">待提交</Typography.Text>
|
||||
)}
|
||||
<Button
|
||||
danger
|
||||
size="small"
|
||||
icon={<UndoOutlined />}
|
||||
onClick={() => {
|
||||
setCancelShare(share)
|
||||
cancelShareForm.setFieldsValue({ reason: '' })
|
||||
}}
|
||||
>
|
||||
撤单
|
||||
</Button>
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1759,6 +1815,37 @@ export default function WorkOrdersPanel() {
|
||||
</Space>
|
||||
) : null}
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={`撤销拼单参与者 · ${
|
||||
cancelShare?.worker?.displayName || cancelShare?.worker?.username || ''
|
||||
}`}
|
||||
open={Boolean(cancelShare)}
|
||||
okText="确认撤单"
|
||||
cancelText="取消"
|
||||
okButtonProps={{ danger: true }}
|
||||
onCancel={() => {
|
||||
setCancelShare(null)
|
||||
cancelShareForm.resetFields()
|
||||
}}
|
||||
onOk={() => cancelShareForm.submit()}
|
||||
destroyOnHidden
|
||||
>
|
||||
{cancelShare ? (
|
||||
<Form form={cancelShareForm} layout="vertical" onFinish={submitCancelShare}>
|
||||
<Alert
|
||||
showIcon
|
||||
type="warning"
|
||||
message={`将撤销该打手承接的 ${cancelShare.quantity} 份拼单`}
|
||||
description="撤单后会退还该份额冻结的押金,空出的份数重新回到接单大厅;已提交的验收资料会保留在后台记录中。"
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<Form.Item label="撤单原因" name="reason">
|
||||
<Input.TextArea rows={3} placeholder="选填,用于后台流转记录" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
) : null}
|
||||
</Modal>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -470,6 +470,18 @@ export function acceptAdminWorkOrderShare(workOrderId: number, shareId: number)
|
||||
)
|
||||
}
|
||||
|
||||
export function cancelAdminWorkOrderShare(
|
||||
workOrderId: number,
|
||||
shareId: number,
|
||||
payload: { reason?: string },
|
||||
) {
|
||||
return apiPost<{
|
||||
order: WorkOrder
|
||||
share: WorkOrderShare
|
||||
releasedDepositAmount: number
|
||||
}>(`/api/v1/admin/worker-platform/orders/${workOrderId}/shares/${shareId}/cancel`, payload)
|
||||
}
|
||||
|
||||
export function acceptAdminWorkOrders(workOrderIds: number[]) {
|
||||
return apiPost<{ orders: WorkOrder[] }>('/api/v1/admin/worker-platform/orders/accept-batch', {
|
||||
workOrderIds,
|
||||
|
||||
Reference in New Issue
Block a user