feat(worker): support batch sharing cancellation
This commit is contained in:
@@ -61,3 +61,57 @@ export function resolveWorkOrderShareCancellationStatus(
|
|||||||
if (!['joined', 'submitted'].includes(shareStatus)) return null
|
if (!['joined', 'submitted'].includes(shareStatus)) return null
|
||||||
return 'open'
|
return 'open'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验母单级拼单撤单。退回大厅只影响尚未验收的份额;取消整单则不能存在已验收份额,
|
||||||
|
* 否则必须经售后处理已结算的报酬和订单退款。
|
||||||
|
*/
|
||||||
|
export function resolveWorkOrderSharingCancellationPlan(
|
||||||
|
workOrderStatus: string,
|
||||||
|
shareStatuses: string[],
|
||||||
|
action: 'return_to_hall' | 'cancel_order',
|
||||||
|
): {
|
||||||
|
nextOrderStatus: 'open' | 'cancelled' | null
|
||||||
|
cancellableShareCount: number
|
||||||
|
acceptedShareCount: number
|
||||||
|
failureReason:
|
||||||
|
| 'work_order_not_cancellable'
|
||||||
|
| 'no_cancellable_shares'
|
||||||
|
| 'accepted_shares_present'
|
||||||
|
| null
|
||||||
|
} {
|
||||||
|
if (!['open', 'pending_acceptance', 'in_progress'].includes(workOrderStatus)) {
|
||||||
|
return {
|
||||||
|
nextOrderStatus: null,
|
||||||
|
cancellableShareCount: 0,
|
||||||
|
acceptedShareCount: 0,
|
||||||
|
failureReason: 'work_order_not_cancellable',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const cancellableShareCount = shareStatuses.filter((status) =>
|
||||||
|
['joined', 'submitted'].includes(status),
|
||||||
|
).length
|
||||||
|
const acceptedShareCount = shareStatuses.filter((status) => status === 'accepted').length
|
||||||
|
if (cancellableShareCount === 0) {
|
||||||
|
return {
|
||||||
|
nextOrderStatus: null,
|
||||||
|
cancellableShareCount,
|
||||||
|
acceptedShareCount,
|
||||||
|
failureReason: 'no_cancellable_shares',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (action === 'cancel_order' && acceptedShareCount > 0) {
|
||||||
|
return {
|
||||||
|
nextOrderStatus: null,
|
||||||
|
cancellableShareCount,
|
||||||
|
acceptedShareCount,
|
||||||
|
failureReason: 'accepted_shares_present',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
nextOrderStatus: action === 'return_to_hall' ? 'open' : 'cancelled',
|
||||||
|
cancellableShareCount,
|
||||||
|
acceptedShareCount,
|
||||||
|
failureReason: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -473,6 +473,7 @@ export type WorkOrderCapabilities = {
|
|||||||
canReopen: boolean
|
canReopen: boolean
|
||||||
canPublish: boolean
|
canPublish: boolean
|
||||||
canCancel: boolean
|
canCancel: boolean
|
||||||
|
canCancelSharing: boolean
|
||||||
canResolveProblem: boolean
|
canResolveProblem: boolean
|
||||||
canDeduct: boolean
|
canDeduct: boolean
|
||||||
canConfirmFriend: boolean
|
canConfirmFriend: boolean
|
||||||
|
|||||||
@@ -28,4 +28,5 @@ export {
|
|||||||
acceptWorkOrderAndSettle,
|
acceptWorkOrderAndSettle,
|
||||||
acceptWorkOrderShareAndSettle,
|
acceptWorkOrderShareAndSettle,
|
||||||
cancelWorkOrderShare,
|
cancelWorkOrderShare,
|
||||||
|
cancelWorkOrderSharing,
|
||||||
} from './work-order-settlement-repo.js'
|
} from './work-order-settlement-repo.js'
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
getWorkerWalletWithClient,
|
getWorkerWalletWithClient,
|
||||||
WORK_ORDER_SHARE_SELECT,
|
WORK_ORDER_SHARE_SELECT,
|
||||||
resolveWorkOrderShareCancellationStatus,
|
resolveWorkOrderShareCancellationStatus,
|
||||||
|
resolveWorkOrderSharingCancellationPlan,
|
||||||
} from './shared.js'
|
} from './shared.js'
|
||||||
import { createWorkOrderEventWithClient } from './work-order-event-repo.js'
|
import { createWorkOrderEventWithClient } from './work-order-event-repo.js'
|
||||||
import { getWorkOrderByIdWithClient } from './work-order-query-repo.js'
|
import { getWorkOrderByIdWithClient } from './work-order-query-repo.js'
|
||||||
@@ -740,3 +741,165 @@ export async function cancelWorkOrderShare(input: {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 母单级拼单撤单。只撤回尚未验收的 joined/submitted 份额;已验收份额保留,
|
||||||
|
* 取消整单时则要求不存在已验收份额,避免绕过售后和已结算资金处理。
|
||||||
|
*/
|
||||||
|
export async function cancelWorkOrderSharing(input: {
|
||||||
|
workOrderId: number
|
||||||
|
action: 'return_to_hall' | 'cancel_order'
|
||||||
|
now: string
|
||||||
|
reason?: string
|
||||||
|
actorName?: string
|
||||||
|
}): Promise<{
|
||||||
|
order: WorkOrderRow | null
|
||||||
|
cancelledShareIds: number[]
|
||||||
|
cancelledWorkerIds: number[]
|
||||||
|
cancelledShareCount: number
|
||||||
|
releasedDepositAmount: number
|
||||||
|
failureReason:
|
||||||
|
| 'work_order_not_cancellable'
|
||||||
|
| 'no_cancellable_shares'
|
||||||
|
| 'accepted_shares_present'
|
||||||
|
| 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 || workOrder.sharing_enabled !== true) {
|
||||||
|
return {
|
||||||
|
order: workOrder,
|
||||||
|
cancelledShareIds: [],
|
||||||
|
cancelledWorkerIds: [],
|
||||||
|
cancelledShareCount: 0,
|
||||||
|
releasedDepositAmount: 0,
|
||||||
|
failureReason: 'work_order_not_cancellable',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const sharesResult = await client.query<WorkOrderShareRow>(
|
||||||
|
`SELECT * FROM work_order_shares
|
||||||
|
WHERE work_order_id = $1 AND status != 'cancelled'
|
||||||
|
ORDER BY id ASC
|
||||||
|
FOR UPDATE`,
|
||||||
|
[input.workOrderId],
|
||||||
|
)
|
||||||
|
const shares = sharesResult.rows
|
||||||
|
const plan = resolveWorkOrderSharingCancellationPlan(
|
||||||
|
workOrder.status,
|
||||||
|
shares.map((share) => share.status),
|
||||||
|
input.action,
|
||||||
|
)
|
||||||
|
if (!plan.nextOrderStatus) {
|
||||||
|
return {
|
||||||
|
order: workOrder,
|
||||||
|
cancelledShareIds: [],
|
||||||
|
cancelledWorkerIds: [],
|
||||||
|
cancelledShareCount: plan.cancellableShareCount,
|
||||||
|
releasedDepositAmount: 0,
|
||||||
|
failureReason: plan.failureReason,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancellableShares = shares.filter((share) => ['joined', 'submitted'].includes(share.status))
|
||||||
|
let releasedDepositAmount = 0
|
||||||
|
for (const share of cancellableShares) {
|
||||||
|
const workerId = Number(share.worker_id || 0)
|
||||||
|
if (workerId <= 0) continue
|
||||||
|
await ensureWorkerWalletWithClient(client, workerId, input.now)
|
||||||
|
const wallet = await getWorkerWalletWithClient(client, workerId)
|
||||||
|
const releaseAmount = Math.min(
|
||||||
|
Number(share.share_deposit || 0),
|
||||||
|
Number(wallet?.frozen_deposit_amount || 0),
|
||||||
|
)
|
||||||
|
if (releaseAmount <= 0) continue
|
||||||
|
const nextAvailable = Number(wallet?.available_amount || 0) + releaseAmount
|
||||||
|
const nextFrozen = Math.max(0, Number(wallet?.frozen_deposit_amount || 0) - releaseAmount)
|
||||||
|
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,
|
||||||
|
releaseAmount,
|
||||||
|
nextAvailable,
|
||||||
|
nextFrozen,
|
||||||
|
input.workOrderId,
|
||||||
|
JSON.stringify({
|
||||||
|
workOrderId: input.workOrderId,
|
||||||
|
shareId: Number(share.id),
|
||||||
|
action: input.action,
|
||||||
|
reason: String(input.reason || '').trim(),
|
||||||
|
}),
|
||||||
|
input.now,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
releasedDepositAmount += releaseAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelledShareIds = cancellableShares.map((share) => Number(share.id))
|
||||||
|
const cancelledWorkerIds = [
|
||||||
|
...new Set(cancellableShares.map((share) => Number(share.worker_id || 0)).filter(Boolean)),
|
||||||
|
]
|
||||||
|
await client.query(
|
||||||
|
`UPDATE work_order_shares
|
||||||
|
SET status = 'cancelled', updated_at = $1
|
||||||
|
WHERE id = ANY($2::bigint[])`,
|
||||||
|
[input.now, cancelledShareIds],
|
||||||
|
)
|
||||||
|
if (input.action === 'return_to_hall') {
|
||||||
|
await client.query(
|
||||||
|
`UPDATE work_orders
|
||||||
|
SET status = 'open', submitted_at = NULL, published_at = COALESCE(published_at, $1),
|
||||||
|
hall_queued_at = $1, updated_at = $1
|
||||||
|
WHERE id = $2`,
|
||||||
|
[input.now, input.workOrderId],
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
await client.query(
|
||||||
|
`UPDATE work_orders
|
||||||
|
SET status = 'cancelled', assigned_worker_id = NULL, assigned_at = NULL, deadline_at = NULL,
|
||||||
|
submitted_at = NULL, published_at = NULL, pinned_at = NULL, updated_at = $1
|
||||||
|
WHERE id = $2`,
|
||||||
|
[input.now, input.workOrderId],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
await createWorkOrderEventWithClient(client, {
|
||||||
|
workOrderId: input.workOrderId,
|
||||||
|
actorType: 'admin',
|
||||||
|
actorId: input.actorName || '',
|
||||||
|
eventType:
|
||||||
|
input.action === 'return_to_hall'
|
||||||
|
? 'sharing_unaccepted_shares_returned_to_hall_by_admin'
|
||||||
|
: 'sharing_order_cancelled_by_admin',
|
||||||
|
fromStatus: workOrder.status,
|
||||||
|
toStatus: plan.nextOrderStatus,
|
||||||
|
payloadJson: JSON.stringify({
|
||||||
|
cancelledShareIds,
|
||||||
|
cancelledShareCount: cancelledShareIds.length,
|
||||||
|
acceptedShareCount: plan.acceptedShareCount,
|
||||||
|
releasedDepositAmount,
|
||||||
|
reason: String(input.reason || '').trim(),
|
||||||
|
}),
|
||||||
|
now: input.now,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
order: await getWorkOrderByIdWithClient(client, input.workOrderId),
|
||||||
|
cancelledShareIds,
|
||||||
|
cancelledWorkerIds,
|
||||||
|
cancelledShareCount: cancelledShareIds.length,
|
||||||
|
releasedDepositAmount,
|
||||||
|
failureReason: null,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import {
|
|||||||
acceptAdminWorkOrder,
|
acceptAdminWorkOrder,
|
||||||
acceptAdminWorkOrderShare,
|
acceptAdminWorkOrderShare,
|
||||||
cancelAdminWorkOrderShare,
|
cancelAdminWorkOrderShare,
|
||||||
|
cancelAdminWorkOrderSharing,
|
||||||
deductAdminWorkOrderPendingDeposit,
|
deductAdminWorkOrderPendingDeposit,
|
||||||
markAdminWorkOrderProblem,
|
markAdminWorkOrderProblem,
|
||||||
resolveAdminProblemWorkOrder,
|
resolveAdminProblemWorkOrder,
|
||||||
} from '../../../services/worker-platform/index.js'
|
} from '../../../services/worker-platform/index.js'
|
||||||
import { createJsonHandler, requireAdminPermission } from '../session.js'
|
import { createJsonHandler, requireAdminPermission, requireAnyAdminPermission } from '../session.js'
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
@@ -104,6 +105,34 @@ router.post(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/worker-platform/orders/:workOrderId/sharing/cancel',
|
||||||
|
requireAnyAdminPermission(['worker_order.return_to_hall', 'worker_order.cancel']),
|
||||||
|
createJsonHandler(
|
||||||
|
(req) =>
|
||||||
|
cancelAdminWorkOrderSharing(
|
||||||
|
String(req.params.workOrderId || ''),
|
||||||
|
req.body || {},
|
||||||
|
req.adminSession?.username || '',
|
||||||
|
req.adminSession?.permissions || [],
|
||||||
|
),
|
||||||
|
{
|
||||||
|
successMessage: '拼单撤单处理完成',
|
||||||
|
errorMessage: '拼单撤单失败',
|
||||||
|
scope: '[admin/worker-platform/orders/:workOrderId/sharing/cancel]',
|
||||||
|
audit: (req, data) => ({
|
||||||
|
action:
|
||||||
|
String(req.body?.action || '').trim() === 'return_to_hall'
|
||||||
|
? 'work_order_sharing_returned_to_hall'
|
||||||
|
: 'work_order_sharing_cancelled',
|
||||||
|
targetType: 'work_order',
|
||||||
|
targetId: String(req.params.workOrderId || ''),
|
||||||
|
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/worker-platform/orders/:workOrderId/shares/:shareId/cancel',
|
'/worker-platform/orders/:workOrderId/shares/:shareId/cancel',
|
||||||
requireAdminPermission('worker_order.cancel_share'),
|
requireAdminPermission('worker_order.cancel_share'),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
acceptWorkOrderAndSettle,
|
acceptWorkOrderAndSettle,
|
||||||
acceptWorkOrderShareAndSettle,
|
acceptWorkOrderShareAndSettle,
|
||||||
cancelWorkOrderShare,
|
cancelWorkOrderShare,
|
||||||
|
cancelWorkOrderSharing,
|
||||||
countWorkOrderPendingSharingSubmissions,
|
countWorkOrderPendingSharingSubmissions,
|
||||||
updateWorkOrder,
|
updateWorkOrder,
|
||||||
} from '../../repositories/worker-platform/index.js'
|
} from '../../repositories/worker-platform/index.js'
|
||||||
@@ -157,6 +158,81 @@ export async function cancelAdminWorkOrderShare(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function cancelAdminWorkOrderSharing(
|
||||||
|
workOrderId: number | string,
|
||||||
|
payload: JsonObject = {},
|
||||||
|
actorName = '',
|
||||||
|
actorPermissions: string[] = [],
|
||||||
|
) {
|
||||||
|
const action = String(payload.action || '').trim()
|
||||||
|
if (!['return_to_hall', 'cancel_order'].includes(action)) {
|
||||||
|
throw createHttpError('请选择拼单撤单处理方式', {
|
||||||
|
statusCode: 400,
|
||||||
|
errorCode: 'work_order_sharing_cancel_action_required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const requiredPermission =
|
||||||
|
action === 'return_to_hall' ? 'worker_order.return_to_hall' : 'worker_order.cancel'
|
||||||
|
if (!actorPermissions.includes(requiredPermission)) {
|
||||||
|
throw createHttpError('当前账号没有此拼单撤单权限', {
|
||||||
|
statusCode: 403,
|
||||||
|
errorCode: 'admin_permission_denied',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const reason = String(payload.reason || '').trim()
|
||||||
|
if (action === 'cancel_order' && !reason) {
|
||||||
|
throw createHttpError('取消拼单订单时请填写退款或撤单原因', {
|
||||||
|
statusCode: 400,
|
||||||
|
errorCode: 'work_order_sharing_cancel_reason_required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const result = await cancelWorkOrderSharing({
|
||||||
|
workOrderId: Number(workOrderId),
|
||||||
|
action: action as 'return_to_hall' | 'cancel_order',
|
||||||
|
reason,
|
||||||
|
now: nowIso(),
|
||||||
|
actorName,
|
||||||
|
})
|
||||||
|
if (result.failureReason === 'work_order_not_cancellable') {
|
||||||
|
throw createHttpError('当前订单不是可撤单的拼单', {
|
||||||
|
statusCode: 409,
|
||||||
|
errorCode: 'work_order_sharing_cancel_status_invalid',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (result.failureReason === 'no_cancellable_shares') {
|
||||||
|
throw createHttpError('没有可撤销的未验收拼单份额', {
|
||||||
|
statusCode: 409,
|
||||||
|
errorCode: 'work_order_sharing_cancel_no_cancellable_shares',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (result.failureReason === 'accepted_shares_present') {
|
||||||
|
throw createHttpError('存在已验收份额,不能取消整单;请通过售后处理退款', {
|
||||||
|
statusCode: 409,
|
||||||
|
errorCode: 'work_order_sharing_cancel_accepted_shares_present',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (!result.order) {
|
||||||
|
throw createHttpError('拼单撤单失败,订单状态可能已变化', {
|
||||||
|
statusCode: 409,
|
||||||
|
errorCode: 'work_order_sharing_cancel_conflict',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const activeWorkerIds = await resolveWorkOrderRealtimeWorkerIds(result.order)
|
||||||
|
const workerIds = [...new Set([...activeWorkerIds, ...result.cancelledWorkerIds])]
|
||||||
|
publishWorkOrderRealtimeChange({
|
||||||
|
workOrderId: Number(workOrderId),
|
||||||
|
workerIds,
|
||||||
|
hallChanged: action === 'return_to_hall',
|
||||||
|
})
|
||||||
|
for (const workerId of result.cancelledWorkerIds) publishWorkerWalletRealtimeChange(workerId)
|
||||||
|
return {
|
||||||
|
order: mapWorkOrderAdmin(result.order),
|
||||||
|
action,
|
||||||
|
cancelledShareCount: result.cancelledShareCount,
|
||||||
|
releasedDepositAmount: result.releasedDepositAmount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function acceptAdminWorkOrders(payload: JsonObject = {}, actorName = '') {
|
export async function acceptAdminWorkOrders(payload: JsonObject = {}, actorName = '') {
|
||||||
const workOrderIds = [
|
const workOrderIds = [
|
||||||
...new Set(
|
...new Set(
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export {
|
|||||||
acceptAdminWorkOrderShare,
|
acceptAdminWorkOrderShare,
|
||||||
acceptAdminWorkOrders,
|
acceptAdminWorkOrders,
|
||||||
cancelAdminWorkOrderShare,
|
cancelAdminWorkOrderShare,
|
||||||
|
cancelAdminWorkOrderSharing,
|
||||||
} from './admin-work-order-acceptance-service.js'
|
} from './admin-work-order-acceptance-service.js'
|
||||||
export * from './worker-product-match-config-service.js'
|
export * from './worker-product-match-config-service.js'
|
||||||
export * from './worker-announcement-config-service.js'
|
export * from './worker-announcement-config-service.js'
|
||||||
|
|||||||
@@ -174,6 +174,10 @@ export function mapWorkOrderAdmin(workOrder: WorkOrderRow, shares?: WorkOrderSha
|
|||||||
canPublish:
|
canPublish:
|
||||||
workOrder.status === WORK_ORDER_STATUS.UNASSIGNED && Number(workOrder.reward_amount || 0) > 0,
|
workOrder.status === WORK_ORDER_STATUS.UNASSIGNED && Number(workOrder.reward_amount || 0) > 0,
|
||||||
canCancel: status === WORK_ORDER_STATUS.IN_PROGRESS && Boolean(workOrder.assigned_worker_id),
|
canCancel: status === WORK_ORDER_STATUS.IN_PROGRESS && Boolean(workOrder.assigned_worker_id),
|
||||||
|
canCancelSharing:
|
||||||
|
workOrder.sharing_enabled === true &&
|
||||||
|
hasActiveShares &&
|
||||||
|
['open', 'pending_acceptance', 'in_progress'].includes(workOrder.status),
|
||||||
canResolveProblem: workOrder.status === WORK_ORDER_STATUS.PROBLEM,
|
canResolveProblem: workOrder.status === WORK_ORDER_STATUS.PROBLEM,
|
||||||
canDeduct: workOrder.status === WORK_ORDER_STATUS.ACCEPTED,
|
canDeduct: workOrder.status === WORK_ORDER_STATUS.ACCEPTED,
|
||||||
canConfirmFriend:
|
canConfirmFriend:
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type {
|
|||||||
import {
|
import {
|
||||||
buildWorkOrderWhere,
|
buildWorkOrderWhere,
|
||||||
resolveWorkOrderShareCancellationStatus,
|
resolveWorkOrderShareCancellationStatus,
|
||||||
|
resolveWorkOrderSharingCancellationPlan,
|
||||||
resolveWorkOrderShareJoinQuantity,
|
resolveWorkOrderShareJoinQuantity,
|
||||||
resolveOpenSharingSyncReward,
|
resolveOpenSharingSyncReward,
|
||||||
} from '../../repositories/worker-platform/index.js'
|
} from '../../repositories/worker-platform/index.js'
|
||||||
@@ -991,6 +992,7 @@ test('后台拼单聚合状态同时收敛筛选语义和操作能力', () => {
|
|||||||
assert.equal(partial.capabilities.canConfigureSharing, false)
|
assert.equal(partial.capabilities.canConfigureSharing, false)
|
||||||
assert.equal(partial.capabilities.canAssign, false)
|
assert.equal(partial.capabilities.canAssign, false)
|
||||||
assert.equal(partial.capabilities.canUpdateMaterial, true)
|
assert.equal(partial.capabilities.canUpdateMaterial, true)
|
||||||
|
assert.equal(partial.capabilities.canCancelSharing, true)
|
||||||
|
|
||||||
const filled = mapWorkOrderAdmin(
|
const filled = mapWorkOrderAdmin(
|
||||||
buildWorkOrderRow({
|
buildWorkOrderRow({
|
||||||
@@ -1005,6 +1007,7 @@ test('后台拼单聚合状态同时收敛筛选语义和操作能力', () => {
|
|||||||
assert.equal(filled.hallAvailable, false)
|
assert.equal(filled.hallAvailable, false)
|
||||||
assert.equal(filled.capabilities.canAccept, true)
|
assert.equal(filled.capabilities.canAccept, true)
|
||||||
assert.equal(filled.capabilities.canUnpublish, false)
|
assert.equal(filled.capabilities.canUnpublish, false)
|
||||||
|
assert.equal(filled.capabilities.canCancelSharing, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('已取消工单可重新启用,恢复未分配后可编辑和发布', () => {
|
test('已取消工单可重新启用,恢复未分配后可编辑和发布', () => {
|
||||||
@@ -1051,6 +1054,44 @@ test('拼单份额撤单只允许未验收份额并让母工单回到大厅状
|
|||||||
assert.equal(resolveWorkOrderShareCancellationStatus('open', 'cancelled'), null)
|
assert.equal(resolveWorkOrderShareCancellationStatus('open', 'cancelled'), null)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('拼单母单撤单区分退回大厅和取消整单的验收边界', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
resolveWorkOrderSharingCancellationPlan(
|
||||||
|
'pending_acceptance',
|
||||||
|
['submitted', 'accepted'],
|
||||||
|
'return_to_hall',
|
||||||
|
),
|
||||||
|
{
|
||||||
|
nextOrderStatus: 'open',
|
||||||
|
cancellableShareCount: 1,
|
||||||
|
acceptedShareCount: 1,
|
||||||
|
failureReason: null,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert.equal(
|
||||||
|
resolveWorkOrderSharingCancellationPlan(
|
||||||
|
'pending_acceptance',
|
||||||
|
['submitted', 'accepted'],
|
||||||
|
'cancel_order',
|
||||||
|
).failureReason,
|
||||||
|
'accepted_shares_present',
|
||||||
|
)
|
||||||
|
assert.deepEqual(
|
||||||
|
resolveWorkOrderSharingCancellationPlan('open', ['joined', 'submitted'], 'cancel_order'),
|
||||||
|
{
|
||||||
|
nextOrderStatus: 'cancelled',
|
||||||
|
cancellableShareCount: 2,
|
||||||
|
acceptedShareCount: 0,
|
||||||
|
failureReason: null,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert.equal(
|
||||||
|
resolveWorkOrderSharingCancellationPlan('accepted', ['submitted'], 'return_to_hall')
|
||||||
|
.failureReason,
|
||||||
|
'work_order_not_cancellable',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test('问题单退回打手按标记前状态恢复', () => {
|
test('问题单退回打手按标记前状态恢复', () => {
|
||||||
assert.equal(resolveProblemReturnToWorkerStatus('pending_acceptance'), 'pending_acceptance')
|
assert.equal(resolveProblemReturnToWorkerStatus('pending_acceptance'), 'pending_acceptance')
|
||||||
assert.equal(resolveProblemReturnToWorkerStatus('open'), 'open')
|
assert.equal(resolveProblemReturnToWorkerStatus('open'), 'open')
|
||||||
|
|||||||
@@ -313,6 +313,22 @@ export function formatWorkOrderEvent(event: WorkOrderEvent): EventNodeStyle {
|
|||||||
color: 'red',
|
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':
|
case 'pending_deposit_deducted':
|
||||||
return { title: '扣减了待解冻押金', color: 'default' }
|
return { title: '扣减了待解冻押金', color: 'default' }
|
||||||
case 'voucher_backfilled':
|
case 'voucher_backfilled':
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import {
|
|||||||
assignAdminWorkOrderToWorker,
|
assignAdminWorkOrderToWorker,
|
||||||
cancelAdminWorkOrder,
|
cancelAdminWorkOrder,
|
||||||
cancelAdminWorkOrderShare,
|
cancelAdminWorkOrderShare,
|
||||||
|
cancelAdminWorkOrderSharing,
|
||||||
confirmAdminWorkOrderFriend,
|
confirmAdminWorkOrderFriend,
|
||||||
deductAdminWorkOrderPendingDeposit,
|
deductAdminWorkOrderPendingDeposit,
|
||||||
deleteAdminWorkOrder,
|
deleteAdminWorkOrder,
|
||||||
@@ -146,6 +147,11 @@ type CancelShareFormValues = {
|
|||||||
reason?: string
|
reason?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CancelSharingFormValues = {
|
||||||
|
action?: 'return_to_hall' | 'cancel_order'
|
||||||
|
reason?: string
|
||||||
|
}
|
||||||
|
|
||||||
export default function WorkOrdersPanel() {
|
export default function WorkOrdersPanel() {
|
||||||
const { message, modal } = App.useApp()
|
const { message, modal } = App.useApp()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@@ -221,6 +227,7 @@ export default function WorkOrdersPanel() {
|
|||||||
const [materialPasteText, setMaterialPasteText] = useState('')
|
const [materialPasteText, setMaterialPasteText] = useState('')
|
||||||
const [sharingOrder, setSharingOrder] = useState<WorkOrder | null>(null)
|
const [sharingOrder, setSharingOrder] = useState<WorkOrder | null>(null)
|
||||||
const [cancelShare, setCancelShare] = useState<WorkOrderShare | null>(null)
|
const [cancelShare, setCancelShare] = useState<WorkOrderShare | null>(null)
|
||||||
|
const [cancelSharingOrder, setCancelSharingOrder] = useState<WorkOrder | null>(null)
|
||||||
const [assignOrder, setAssignOrder] = useState<WorkOrder | null>(null)
|
const [assignOrder, setAssignOrder] = useState<WorkOrder | null>(null)
|
||||||
const [assignWorkerId, setAssignWorkerId] = useState<number | undefined>(undefined)
|
const [assignWorkerId, setAssignWorkerId] = useState<number | undefined>(undefined)
|
||||||
const [editOrder, setEditOrder] = useState<WorkOrder | null>(null)
|
const [editOrder, setEditOrder] = useState<WorkOrder | null>(null)
|
||||||
@@ -232,6 +239,7 @@ export default function WorkOrdersPanel() {
|
|||||||
const [materialForm] = Form.useForm<MaterialFormValues>()
|
const [materialForm] = Form.useForm<MaterialFormValues>()
|
||||||
const [cancelForm] = Form.useForm<CancelOrderFormValues>()
|
const [cancelForm] = Form.useForm<CancelOrderFormValues>()
|
||||||
const [cancelShareForm] = Form.useForm<CancelShareFormValues>()
|
const [cancelShareForm] = Form.useForm<CancelShareFormValues>()
|
||||||
|
const [cancelSharingForm] = Form.useForm<CancelSharingFormValues>()
|
||||||
const [deductForm] = Form.useForm<{ amount?: number; note?: string }>()
|
const [deductForm] = Form.useForm<{ amount?: number; note?: string }>()
|
||||||
const [editForm] = Form.useForm<{
|
const [editForm] = Form.useForm<{
|
||||||
productName?: string
|
productName?: string
|
||||||
@@ -335,6 +343,11 @@ export default function WorkOrdersPanel() {
|
|||||||
queryFn: () => fetchAdminWorkOrderSharing(Number(sharingOrder?.workOrderId)),
|
queryFn: () => fetchAdminWorkOrderSharing(Number(sharingOrder?.workOrderId)),
|
||||||
enabled: Boolean(sharingOrder),
|
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({
|
const assignWorkersQuery = useQuery({
|
||||||
queryKey: ['admin-worker-platform-workers', 'assign', assignWorkerKeyword],
|
queryKey: ['admin-worker-platform-workers', 'assign', assignWorkerKeyword],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
@@ -498,6 +511,28 @@ export default function WorkOrdersPanel() {
|
|||||||
cancelShareForm.resetFields()
|
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() {
|
async function refreshAll() {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
@@ -1097,7 +1132,7 @@ export default function WorkOrdersPanel() {
|
|||||||
(row.capabilities?.canConfigureSharing && canConfigureSharing) ? (
|
(row.capabilities?.canConfigureSharing && canConfigureSharing) ? (
|
||||||
<Button icon={<TeamOutlined />} onClick={() => openSharingConfigModal(row)}>
|
<Button icon={<TeamOutlined />} onClick={() => openSharingConfigModal(row)}>
|
||||||
{row.sharing?.enabled && !row.capabilities?.canConfigureSharing
|
{row.sharing?.enabled && !row.capabilities?.canConfigureSharing
|
||||||
? '拼单明细'
|
? '拼单管理'
|
||||||
: '拼单配置'}
|
: '拼单配置'}
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -2148,8 +2183,10 @@ export default function WorkOrdersPanel() {
|
|||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setSharingOrder(null)
|
setSharingOrder(null)
|
||||||
setCancelShare(null)
|
setCancelShare(null)
|
||||||
|
setCancelSharingOrder(null)
|
||||||
sharingForm.resetFields()
|
sharingForm.resetFields()
|
||||||
cancelShareForm.resetFields()
|
cancelShareForm.resetFields()
|
||||||
|
cancelSharingForm.resetFields()
|
||||||
}}
|
}}
|
||||||
onOk={() => sharingForm.submit()}
|
onOk={() => sharingForm.submit()}
|
||||||
footer={
|
footer={
|
||||||
@@ -2237,114 +2274,152 @@ export default function WorkOrdersPanel() {
|
|||||||
) : (sharingQuery.data?.data.shares || []).length === 0 ? (
|
) : (sharingQuery.data?.data.shares || []).length === 0 ? (
|
||||||
<Typography.Text type="secondary">暂无打手参与拼单</Typography.Text>
|
<Typography.Text type="secondary">暂无打手参与拼单</Typography.Text>
|
||||||
) : (
|
) : (
|
||||||
<Table<WorkOrderShare>
|
<>
|
||||||
rowKey="shareId"
|
{sharingOrder.capabilities?.canCancelSharing &&
|
||||||
size="small"
|
hasCancellableSharingShares &&
|
||||||
dataSource={sharingQuery.data?.data.shares || []}
|
(canReturnToHall || canCancelOrder) ? (
|
||||||
loading={sharingQuery.isFetching}
|
<Space
|
||||||
pagination={false}
|
direction="vertical"
|
||||||
columns={[
|
size={8}
|
||||||
{
|
className="full-width"
|
||||||
title: '打手',
|
style={{ marginBottom: 12 }}
|
||||||
render: (_, share) =>
|
>
|
||||||
share.worker?.displayName || share.worker?.username || '-',
|
<Alert
|
||||||
},
|
showIcon
|
||||||
{ title: '数量', dataIndex: 'quantity' },
|
type="warning"
|
||||||
{
|
message="拼单撤单会批量处理所有未验收份额"
|
||||||
title: '份额报酬',
|
description={
|
||||||
render: (_, share) => formatMoney(share.shareReward),
|
hasAcceptedSharingShares
|
||||||
},
|
? '已验收份额会保留;如需退款,请从售后处理。'
|
||||||
{
|
: '可选择把未验收份额退回大厅,或取消整个拼单订单。'
|
||||||
title: '状态',
|
}
|
||||||
render: (_, share) => (
|
/>
|
||||||
<Tag color={resolveSharingShareStatusColor(share.status)}>
|
<div>
|
||||||
{formatSharingShareStatus(share.status)}
|
<Button
|
||||||
</Tag>
|
danger
|
||||||
),
|
onClick={() => {
|
||||||
},
|
setCancelSharingOrder(sharingOrder)
|
||||||
{
|
cancelSharingForm.setFieldsValue({
|
||||||
title: '提交时间',
|
action: canReturnToHall ? 'return_to_hall' : 'cancel_order',
|
||||||
render: (_, share) =>
|
reason: '',
|
||||||
share.submittedAt ? formatAdminDateTime(share.submittedAt) : '-',
|
})
|
||||||
},
|
}}
|
||||||
{
|
>
|
||||||
title: '验收图',
|
拼单撤单处理
|
||||||
width: 150,
|
</Button>
|
||||||
render: (_, share) => (
|
</div>
|
||||||
<ImagePreviewList
|
</Space>
|
||||||
files={
|
) : null}
|
||||||
Array.isArray(share.acceptance?.files) ? share.acceptance.files : []
|
<Table<WorkOrderShare>
|
||||||
}
|
rowKey="shareId"
|
||||||
imageUrls={
|
size="small"
|
||||||
Array.isArray(share.acceptance?.imageUrls)
|
dataSource={sharingShares}
|
||||||
? share.acceptance.imageUrls
|
loading={sharingQuery.isFetching}
|
||||||
: []
|
pagination={false}
|
||||||
}
|
columns={[
|
||||||
size={44}
|
{
|
||||||
maxVisible={2}
|
title: '打手',
|
||||||
/>
|
render: (_, share) =>
|
||||||
),
|
share.worker?.displayName || share.worker?.username || '-',
|
||||||
},
|
},
|
||||||
{
|
{ title: '数量', dataIndex: 'quantity' },
|
||||||
title: '备注',
|
{
|
||||||
render: (_, share) => String(share.acceptance?.note || '').trim() || '-',
|
title: '份额报酬',
|
||||||
},
|
render: (_, share) => formatMoney(share.shareReward),
|
||||||
{
|
},
|
||||||
title: '操作',
|
{
|
||||||
width: 190,
|
title: '状态',
|
||||||
render: (_, share) => {
|
render: (_, share) => (
|
||||||
if (share.status === 'accepted') return <Tag color="green">已验收</Tag>
|
<Tag color={resolveSharingShareStatusColor(share.status)}>
|
||||||
if (share.status === 'cancelled') return <Tag>已撤单</Tag>
|
{formatSharingShareStatus(share.status)}
|
||||||
return (
|
</Tag>
|
||||||
<Space size={6}>
|
),
|
||||||
{share.status === 'submitted' ? (
|
},
|
||||||
<Button
|
{
|
||||||
type="primary"
|
title: '提交时间',
|
||||||
size="small"
|
render: (_, share) =>
|
||||||
onClick={() => {
|
share.submittedAt ? formatAdminDateTime(share.submittedAt) : '-',
|
||||||
void runAction(
|
},
|
||||||
() =>
|
{
|
||||||
acceptAdminWorkOrderShare(
|
title: '验收图',
|
||||||
sharingOrder.workOrderId,
|
width: 150,
|
||||||
share.shareId,
|
render: (_, share) => (
|
||||||
),
|
<ImagePreviewList
|
||||||
'该拼单参与者已验收',
|
files={
|
||||||
).then((succeeded) => {
|
Array.isArray(share.acceptance?.files) ? share.acceptance.files : []
|
||||||
if (succeeded) {
|
}
|
||||||
void queryClient.invalidateQueries({
|
imageUrls={
|
||||||
queryKey: [
|
Array.isArray(share.acceptance?.imageUrls)
|
||||||
'admin-worker-platform-order-sharing',
|
? 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,
|
sharingOrder.workOrderId,
|
||||||
],
|
share.shareId,
|
||||||
})
|
),
|
||||||
}
|
'该拼单参与者已验收',
|
||||||
})
|
).then((succeeded) => {
|
||||||
}}
|
if (succeeded) {
|
||||||
>
|
void queryClient.invalidateQueries({
|
||||||
单独验收
|
queryKey: [
|
||||||
</Button>
|
'admin-worker-platform-order-sharing',
|
||||||
) : (
|
sharingOrder.workOrderId,
|
||||||
<Typography.Text type="secondary">待提交</Typography.Text>
|
],
|
||||||
)}
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
单独验收
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Typography.Text type="secondary">待提交</Typography.Text>
|
||||||
|
)}
|
||||||
{canCancelShare ? (
|
{canCancelShare ? (
|
||||||
<Button
|
<Button
|
||||||
danger
|
danger
|
||||||
size="small"
|
size="small"
|
||||||
icon={<UndoOutlined />}
|
icon={<UndoOutlined />}
|
||||||
|
title="只撤销当前参与者的拼单份额,其他参与者不受影响"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCancelShare(share)
|
setCancelShare(share)
|
||||||
cancelShareForm.setFieldsValue({ reason: '' })
|
cancelShareForm.setFieldsValue({ reason: '' })
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
撤单
|
单人撤单
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
</Space>
|
</Space>
|
||||||
)
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
]}
|
||||||
]}
|
/>
|
||||||
/>
|
</>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</Space>
|
</Space>
|
||||||
@@ -2381,6 +2456,66 @@ export default function WorkOrdersPanel() {
|
|||||||
</Form>
|
</Form>
|
||||||
) : null}
|
) : null}
|
||||||
</Modal>
|
</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>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -728,6 +728,21 @@ export function cancelAdminWorkOrderShare(
|
|||||||
}>(`/api/v1/admin/worker-platform/orders/${workOrderId}/shares/${shareId}/cancel`, payload)
|
}>(`/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[]) {
|
export function acceptAdminWorkOrders(workOrderIds: number[]) {
|
||||||
return apiPost<{ orders: WorkOrder[] }>('/api/v1/admin/worker-platform/orders/accept-batch', {
|
return apiPost<{ orders: WorkOrder[] }>('/api/v1/admin/worker-platform/orders/accept-batch', {
|
||||||
workOrderIds,
|
workOrderIds,
|
||||||
|
|||||||
@@ -550,6 +550,7 @@ export type WorkOrder = {
|
|||||||
canReopen: boolean
|
canReopen: boolean
|
||||||
canPublish: boolean
|
canPublish: boolean
|
||||||
canCancel: boolean
|
canCancel: boolean
|
||||||
|
canCancelSharing?: boolean
|
||||||
canResolveProblem: boolean
|
canResolveProblem: boolean
|
||||||
canDeduct: boolean
|
canDeduct: boolean
|
||||||
canConfirmFriend?: boolean
|
canConfirmFriend?: boolean
|
||||||
|
|||||||
Reference in New Issue
Block a user