feat(接单): 支持客服手动结束赠送冷却

This commit is contained in:
yml2213
2026-08-28 20:39:41 +08:00
parent e680792161
commit 5d5d63bb59
11 changed files with 255 additions and 2 deletions
@@ -1,4 +1,5 @@
import {
completeWorkOrderGiftCooldown,
confirmWorkOrderFriendAdded,
resetWorkOrderGiftMaterial,
} from '../../repositories/worker-platform/index.js'
@@ -39,6 +40,51 @@ export async function confirmAdminWorkOrderFriendAdded(
return { order: mapWorkOrderAdmin(order) }
}
/** 客服手动结束好友赠送冷却,让打手立即可以提交赠送凭证。 */
export async function completeAdminWorkOrderGiftCooldown(
workOrderId: number | string,
payload: { note?: unknown } = {},
actorName = '',
) {
const workOrder = await getRequiredWorkOrder(workOrderId)
const { order, failureReason } = await completeWorkOrderGiftCooldown({
workOrderId: Number(workOrder.id),
note: String(payload.note || '').trim(),
actorName,
now: nowIso(),
})
if (failureReason === 'work_order_gift_phase_invalid') {
throw createHttpError('当前订单不在好友赠送冷却中,请刷新后重试', {
statusCode: 409,
errorCode: 'work_order_gift_cooldown_phase_invalid',
})
}
if (failureReason === 'work_order_gift_cooldown_completed') {
throw createHttpError('好友赠送冷却已结束,无需手动操作', {
statusCode: 409,
errorCode: 'work_order_gift_cooldown_already_completed',
})
}
if (failureReason === 'work_order_status_invalid') {
throw createHttpError('当前订单状态不能结束赠送冷却', {
statusCode: 409,
errorCode: 'work_order_gift_cooldown_status_invalid',
})
}
if (!order) {
throw createHttpError('结束赠送冷却失败,订单可能已变化', {
statusCode: 409,
errorCode: 'work_order_gift_cooldown_complete_failed',
})
}
const workerId = Number(order.assigned_worker_id || 0)
publishWorkOrderRealtimeChange({
workOrderId: Number(order.id),
workerIds: workerId > 0 ? [workerId] : [],
})
return { order: mapWorkOrderAdmin(order) }
}
/** 打手资料有误或误确认好友时,重置资料让打手重新提交(冷却与好友时间清零)。 */
export async function resetAdminWorkOrderGiftMaterial(
workOrderId: number | string,
@@ -1,6 +1,7 @@
import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js'
import {
isFriendGiftAcceptanceMode,
isWorkOrderGiftAvailable,
normalizeWorkOrderAcceptanceMode,
normalizeWorkOrderGiftCooldownHours,
normalizeWorkOrderGiftCooldownMinutes,
@@ -185,6 +186,13 @@ export function mapWorkOrderAdmin(workOrder: WorkOrderRow, shares?: WorkOrderSha
isFriendGift &&
workOrder.status === WORK_ORDER_STATUS.IN_PROGRESS &&
giftPhase === WORK_ORDER_GIFT_PHASE.MATERIAL_SUBMITTED,
canCompleteGiftCooldown:
isFriendGift &&
[WORK_ORDER_STATUS.IN_PROGRESS, WORK_ORDER_STATUS.PROBLEM].includes(
workOrder.status as never,
) &&
giftPhase === WORK_ORDER_GIFT_PHASE.FRIEND_COUNTDOWN &&
!isWorkOrderGiftAvailable(workOrder),
canResetGiftFlow:
isFriendGift &&
[WORK_ORDER_STATUS.IN_PROGRESS, WORK_ORDER_STATUS.PROBLEM].includes(
@@ -971,6 +971,42 @@ test('打手订单状态筛选使用个人拼单份额状态', () => {
assert.match(whereClause, /= ANY\(\$2::text\[\]\)/)
})
test('好友赠送待打手提交资料筛选排除待抢单订单', () => {
const { whereClause, params } = buildWorkOrderWhere({ giftPhase: 'material_required' })
assert.deepEqual(params, ['material_required'])
assert.match(whereClause, /wo\.acceptance_mode = 'friend_gift'/)
assert.match(whereClause, /wo\.gift_phase = \$1/)
assert.match(whereClause, /wo\.assigned_worker_id IS NOT NULL/)
assert.match(whereClause, /wo\.status IN \('in_progress', 'problem'\)/)
})
test('好友赠送冷却中才允许客服手动结束冷却', () => {
const cooling = mapWorkOrderAdmin(
buildWorkOrderRow({
status: 'in_progress',
assigned_worker_id: 1,
acceptance_mode: 'friend_gift',
gift_phase: 'friend_countdown',
gift_available_at: new Date(Date.now() + 60_000).toISOString(),
}),
)
assert.equal(cooling.gift?.phase, 'friend_countdown')
assert.equal(cooling.capabilities.canCompleteGiftCooldown, true)
const completed = mapWorkOrderAdmin(
buildWorkOrderRow({
status: 'in_progress',
assigned_worker_id: 1,
acceptance_mode: 'friend_gift',
gift_phase: 'friend_countdown',
gift_available_at: new Date(Date.now() - 60_000).toISOString(),
}),
)
assert.equal(completed.gift?.phase, 'gift_ready')
assert.equal(completed.capabilities.canCompleteGiftCooldown, false)
})
test('后台拼单聚合状态同时收敛筛选语义和操作能力', () => {
const waitingForClaim = mapWorkOrderAdmin(buildWorkOrderRow({ status: 'open' }))
assert.equal(waitingForClaim.status, 'open')