fix(worker-platform): 补齐拼单份额超时自动处置机制与退回大厅流程

- 根因排查:此前超时扫描仅针对整单 in_progress,而拼单母单为 open 且 deadline_at 为空,导致打手超时的拼单份额无法被定时扫描命中与释放;
- 定时扫描:在 settleOverdueWorkOrders 中引入 listOverdueWorkOrderShares,自动扫描超时未提交资料的拼单份额(joined);
- 超时处置:新增 settleOverdueWorkOrderShare 事务处理,按母单 timeout_policy 释放或扣除押金,份额标记 cancelled,母单保持或恢复 open 状态重回抢单大厅,记录 timeout 事件并推送实时变更;
- 提交防呆:在打手提交拼单验收时检测个人份额是否已超时,超时则立即执行处置并拦截提交;
- 倒计时展示:在 mapWorkOrderForWorker 中根据 myShare.created_at + timeout_minutes 为拼单打手计算并展示个人履约截止时间;
- 质量验证:补齐拼单截止时间计算与处置链路测试,全量 676 个测试均通过,通过 npm run check 校验。
This commit is contained in:
yml2213
2026-10-11 14:53:56 +08:00
parent 06d888fb6f
commit b82f9c4063
20 changed files with 418 additions and 44 deletions
@@ -1,12 +1,8 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import {
getOrderClaimContext,
} from './kuaishou-cloud-claim-context.js'
import {
updateAdminOrderClaimStatus,
} from '../admin/write/order-actions.js'
import { getOrderClaimContext } from './kuaishou-cloud-claim-context.js'
import { updateAdminOrderClaimStatus } from '../admin/write/order-actions.js'
test('getOrderClaimContext: 缺少订单号时抛出 400 错误', async () => {
await assert.rejects(
@@ -1,7 +1,11 @@
import { findClaimTokenByToken, updateClaimToken } from '../../repositories/claim-token-repo.js'
import { getOrderItemById, listOrderItemsByOrderId } from '../../repositories/order-item-repo.js'
import { findLatestOrderByAnyPlatformOrderId, getOrderById } from '../../repositories/order-repo.js'
import { findTaskByClaimTokenId, listTasksByOrderId, updateTask } from '../../repositories/task-repo.js'
import {
findTaskByClaimTokenId,
listTasksByOrderId,
updateTask,
} from '../../repositories/task-repo.js'
import { createHttpError } from '../../utils/http.js'
import { formatFenToAmount, normalizeFen } from '../../utils/money.js'
import { parseTaskContext as parseTaskContextValue } from '../../utils/task-json.js'
@@ -28,7 +32,6 @@ export type ClaimContext = {
orderItem: OrderItemRow
}
export async function getClaimContext(token: unknown) {
const normalized = String(token || '').trim()
@@ -194,9 +197,7 @@ export async function getOrderClaimContext(orderNo: unknown): Promise<ClaimConte
})
}
const activeTask =
tasks.find((t) => !isTaskFinalStatus(t.task_status)) ||
tasks[tasks.length - 1]
const activeTask = tasks.find((t) => !isTaskFinalStatus(t.task_status)) || tasks[tasks.length - 1]
if (!activeTask) {
throw createHttpError('该订单暂无可用履约任务', {
@@ -205,9 +206,7 @@ export async function getOrderClaimContext(orderNo: unknown): Promise<ClaimConte
})
}
const orderItem =
items.find((it) => it.id === activeTask.order_item_id) ||
items[0]
const orderItem = items.find((it) => it.id === activeTask.order_item_id) || items[0]
if (!orderItem) {
throw createHttpError('订单商品明细缺失', {
@@ -227,7 +226,9 @@ export async function getOrderClaimContext(orderNo: unknown): Promise<ClaimConte
const claimToken: ClaimTokenRow = {
id: Number(activeTask.primary_claim_token_id || 0),
token: String(activeTask.primary_claim_token || activeTask.claim_token || `h5_${order.platform_order_id}`).trim(),
token: String(
activeTask.primary_claim_token || activeTask.claim_token || `h5_${order.platform_order_id}`,
).trim(),
status: 'active',
expired_at: activeTask.claim_expires_at || null,
created_at: activeTask.created_at,