新增好友赠送验收模式支持打手资料提交加好友冷却与模板配置
This commit is contained in:
@@ -12,6 +12,7 @@ export * from './work-order-management-repo.js'
|
||||
export * from './work-order-grab-repo.js'
|
||||
export * from './work-order-cancel-repo.js'
|
||||
export * from './work-order-issue-repo.js'
|
||||
export * from './work-order-gift-repo.js'
|
||||
export * from './work-order-deposit-query-repo.js'
|
||||
export * from './work-order-deposit-repo.js'
|
||||
export * from './work-order-event-repo.js'
|
||||
|
||||
@@ -262,6 +262,8 @@ export type WorkProductRuleRow = {
|
||||
sharing_auto_from_order: boolean
|
||||
timeout_minutes: number
|
||||
timeout_policy: string
|
||||
acceptance_mode: string
|
||||
gift_cooldown_hours: number
|
||||
requirement_json: string | Record<string, unknown>
|
||||
match_json: string | Record<string, unknown>
|
||||
sort_order: number
|
||||
@@ -360,6 +362,15 @@ export type WorkOrderRow = {
|
||||
deadline_at: string | null
|
||||
timeout_minutes: number
|
||||
timeout_policy: string
|
||||
/** 验收模式:standard / friend_gift;friend_gift 建单时从接单模板拷贝。 */
|
||||
acceptance_mode: string
|
||||
/** 好友赠送阶段:material_required / material_submitted / friend_countdown;gift_ready 实时计算。 */
|
||||
gift_phase: string
|
||||
/** 打手本人资料(昵称/ID/区服/系统 + 主页截图),仅 friend_gift 模式使用。 */
|
||||
booster_material_json: string | Record<string, unknown>
|
||||
friend_added_at: string | null
|
||||
gift_available_at: string | null
|
||||
gift_cooldown_hours: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
/** 拼单聚合字段,由工单查询统一计算。 */
|
||||
@@ -411,6 +422,10 @@ export type ListInput = {
|
||||
excludeFilledSharing?: boolean
|
||||
/** VIP 免审核后仍在补图期限内的订单;与状态筛选取并集 */
|
||||
vipEvidencePending?: boolean
|
||||
/** 验收模式筛选:standard / friend_gift。 */
|
||||
acceptanceMode?: string
|
||||
/** 好友赠送阶段筛选:material_required / material_submitted / friend_countdown / gift_ready。 */
|
||||
giftPhase?: string
|
||||
/** 置顶订单优先展示(抢单大厅使用) */
|
||||
pinnedFirst?: boolean
|
||||
sort?: 'id_desc' | 'worker_claimed_at_desc'
|
||||
@@ -444,6 +459,8 @@ export type WorkOrderCapabilities = {
|
||||
canCancel: boolean
|
||||
canResolveProblem: boolean
|
||||
canDeduct: boolean
|
||||
canConfirmFriend: boolean
|
||||
canResetGiftFlow: boolean
|
||||
}
|
||||
|
||||
export type ProductRuleListInput = {
|
||||
@@ -510,6 +527,8 @@ export type CreateWorkOrderInput = {
|
||||
sharingUnitReward?: number
|
||||
timeoutMinutes?: number
|
||||
timeoutPolicy?: string
|
||||
acceptanceMode?: string
|
||||
giftCooldownHours?: number
|
||||
materialJson: string
|
||||
requirementJson: string
|
||||
now: string
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ensureWorkerWalletWithClient, getWorkerWalletWithClient } from './share
|
||||
import { createWorkOrderEventWithClient } from './work-order-event-repo.js'
|
||||
import { getWorkOrderByIdWithClient } from './work-order-query-repo.js'
|
||||
import { getOutstandingDepositAmountWithClient } from './work-order-deposit-repo.js'
|
||||
import { RESET_GIFT_FLOW_SQL } from './work-order-gift-repo.js'
|
||||
import type { WorkOrderRow } from './types.js'
|
||||
|
||||
export async function cancelWorkerWorkOrder(input: {
|
||||
@@ -47,6 +48,7 @@ export async function cancelWorkerWorkOrder(input: {
|
||||
assigned_at = NULL,
|
||||
deadline_at = NULL,
|
||||
hall_queued_at = $1,
|
||||
${RESET_GIFT_FLOW_SQL},
|
||||
updated_at = $1
|
||||
WHERE id = $2
|
||||
`,
|
||||
@@ -137,6 +139,7 @@ export async function unassignWorkOrder(input: {
|
||||
assigned_worker_id = NULL,
|
||||
assigned_at = NULL,
|
||||
deadline_at = NULL,
|
||||
${RESET_GIFT_FLOW_SQL},
|
||||
updated_at = $1
|
||||
WHERE id = $2
|
||||
`,
|
||||
@@ -228,6 +231,7 @@ export async function returnAssignedWorkOrderToHall(input: {
|
||||
deadline_at = NULL,
|
||||
published_at = $1,
|
||||
hall_queued_at = $1,
|
||||
${RESET_GIFT_FLOW_SQL},
|
||||
updated_at = $1
|
||||
WHERE id = $2
|
||||
`,
|
||||
@@ -424,6 +428,7 @@ export async function reopenCancelledWorkOrder(input: {
|
||||
problem_note = '',
|
||||
published_at = NULL,
|
||||
pinned_at = NULL,
|
||||
${RESET_GIFT_FLOW_SQL},
|
||||
updated_at = $1
|
||||
WHERE id = $2
|
||||
`,
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
import { withTransaction } from '../../db/client.js'
|
||||
import { createWorkOrderEventWithClient } from './work-order-event-repo.js'
|
||||
import { getWorkOrderByIdWithClient } from './work-order-query-repo.js'
|
||||
import type { WorkOrderRow } from './types.js'
|
||||
import {
|
||||
normalizeWorkOrderAcceptanceMode,
|
||||
normalizeWorkOrderGiftCooldownHours,
|
||||
WORK_ORDER_ACCEPTANCE_MODE,
|
||||
WORK_ORDER_GIFT_PHASE,
|
||||
} from '../../domain/work-order-acceptance-mode.js'
|
||||
|
||||
/**
|
||||
* 工单脱离打手(回大厅、取消指派、恢复未分配等)时重置好友赠送进度,
|
||||
* 下一个打手重新走“提交资料 → 确认好友 → 冷却”流程。
|
||||
* 标准模式工单这些字段本就为空,重置无副作用。
|
||||
*/
|
||||
export const RESET_GIFT_FLOW_SQL = `
|
||||
gift_phase = CASE WHEN acceptance_mode = 'friend_gift' THEN 'material_required' ELSE '' END,
|
||||
booster_material_json = '{}'::jsonb,
|
||||
friend_added_at = NULL,
|
||||
gift_available_at = NULL
|
||||
`
|
||||
|
||||
/** 打手提交本人资料(昵称/ID/区服/系统 + 主页截图),进入待确认好友阶段。 */
|
||||
export async function submitWorkOrderGiftMaterial(input: {
|
||||
workOrderId: number
|
||||
workerId: number
|
||||
boosterMaterialJson: string
|
||||
now: string
|
||||
}): Promise<{
|
||||
order: WorkOrderRow | null
|
||||
failureReason:
|
||||
| 'work_order_not_found'
|
||||
| 'work_order_mode_invalid'
|
||||
| 'work_order_owner_required'
|
||||
| 'work_order_status_invalid'
|
||||
| 'work_order_gift_phase_invalid'
|
||||
| null
|
||||
}> {
|
||||
return withTransaction(async (client) => {
|
||||
const currentResult = await client.query<WorkOrderRow>(
|
||||
'SELECT * FROM work_orders WHERE id = $1 FOR UPDATE',
|
||||
[input.workOrderId],
|
||||
)
|
||||
const workOrder = currentResult.rows[0] || null
|
||||
if (!workOrder) return { order: null, failureReason: 'work_order_not_found' }
|
||||
if (
|
||||
normalizeWorkOrderAcceptanceMode(workOrder.acceptance_mode) !==
|
||||
WORK_ORDER_ACCEPTANCE_MODE.FRIEND_GIFT
|
||||
) {
|
||||
return { order: null, failureReason: 'work_order_mode_invalid' }
|
||||
}
|
||||
if (Number(workOrder.assigned_worker_id || 0) !== input.workerId) {
|
||||
return { order: null, failureReason: 'work_order_owner_required' }
|
||||
}
|
||||
if (!['in_progress', 'problem'].includes(workOrder.status)) {
|
||||
return { order: null, failureReason: 'work_order_status_invalid' }
|
||||
}
|
||||
if (workOrder.gift_phase !== WORK_ORDER_GIFT_PHASE.MATERIAL_REQUIRED) {
|
||||
return { order: null, failureReason: 'work_order_gift_phase_invalid' }
|
||||
}
|
||||
|
||||
await client.query(
|
||||
`
|
||||
UPDATE work_orders
|
||||
SET
|
||||
gift_phase = 'material_submitted',
|
||||
booster_material_json = $1::jsonb,
|
||||
friend_added_at = NULL,
|
||||
gift_available_at = NULL,
|
||||
updated_at = $2
|
||||
WHERE id = $3
|
||||
`,
|
||||
[input.boosterMaterialJson, input.now, input.workOrderId],
|
||||
)
|
||||
await createWorkOrderEventWithClient(client, {
|
||||
workOrderId: input.workOrderId,
|
||||
actorType: 'worker',
|
||||
actorId: String(input.workerId),
|
||||
eventType: 'gift_material_submitted',
|
||||
fromStatus: workOrder.status,
|
||||
toStatus: workOrder.status,
|
||||
payloadJson: input.boosterMaterialJson,
|
||||
now: input.now,
|
||||
})
|
||||
return {
|
||||
order: await getWorkOrderByIdWithClient(client, input.workOrderId),
|
||||
failureReason: null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 后台确认已加好友:记录冷却起点并顺延任务时限到冷却结束后再起算。 */
|
||||
export async function confirmWorkOrderFriendAdded(input: {
|
||||
workOrderId: number
|
||||
actorName?: string
|
||||
now: string
|
||||
}): Promise<{
|
||||
order: WorkOrderRow | null
|
||||
failureReason:
|
||||
| 'work_order_not_found'
|
||||
| 'work_order_mode_invalid'
|
||||
| 'work_order_gift_phase_invalid'
|
||||
| null
|
||||
}> {
|
||||
return withTransaction(async (client) => {
|
||||
const currentResult = await client.query<WorkOrderRow>(
|
||||
'SELECT * FROM work_orders WHERE id = $1 FOR UPDATE',
|
||||
[input.workOrderId],
|
||||
)
|
||||
const workOrder = currentResult.rows[0] || null
|
||||
if (!workOrder) return { order: null, failureReason: 'work_order_not_found' }
|
||||
if (
|
||||
normalizeWorkOrderAcceptanceMode(workOrder.acceptance_mode) !==
|
||||
WORK_ORDER_ACCEPTANCE_MODE.FRIEND_GIFT
|
||||
) {
|
||||
return { order: null, failureReason: 'work_order_mode_invalid' }
|
||||
}
|
||||
if (workOrder.gift_phase !== WORK_ORDER_GIFT_PHASE.MATERIAL_SUBMITTED) {
|
||||
return { order: null, failureReason: 'work_order_gift_phase_invalid' }
|
||||
}
|
||||
|
||||
const cooldownHours = normalizeWorkOrderGiftCooldownHours(workOrder.gift_cooldown_hours)
|
||||
// 参数必须显式转型:未知类型参数与 interval 做运算时 PostgreSQL 无法唯一解析操作符。
|
||||
await client.query(
|
||||
`
|
||||
UPDATE work_orders
|
||||
SET
|
||||
gift_phase = 'friend_countdown',
|
||||
friend_added_at = $1::timestamptz,
|
||||
gift_available_at = $1::timestamptz + ($2::int * INTERVAL '1 hour'),
|
||||
deadline_at = CASE
|
||||
WHEN timeout_minutes > 0
|
||||
THEN $1::timestamptz + ($2::int * INTERVAL '1 hour') + (timeout_minutes * INTERVAL '1 minute')
|
||||
ELSE NULL
|
||||
END,
|
||||
updated_at = $1::timestamptz
|
||||
WHERE id = $3
|
||||
`,
|
||||
[input.now, cooldownHours, input.workOrderId],
|
||||
)
|
||||
await createWorkOrderEventWithClient(client, {
|
||||
workOrderId: input.workOrderId,
|
||||
actorType: 'admin',
|
||||
actorId: input.actorName || '',
|
||||
eventType: 'friend_confirmed',
|
||||
fromStatus: workOrder.status,
|
||||
toStatus: workOrder.status,
|
||||
payloadJson: JSON.stringify({
|
||||
cooldownHours,
|
||||
friendAddedAt: input.now,
|
||||
}),
|
||||
now: input.now,
|
||||
})
|
||||
return {
|
||||
order: await getWorkOrderByIdWithClient(client, input.workOrderId),
|
||||
failureReason: null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 后台重置打手资料(资料有误或误确认好友):清空好友状态并让打手重新提交。 */
|
||||
export async function resetWorkOrderGiftMaterial(input: {
|
||||
workOrderId: number
|
||||
note?: string
|
||||
actorName?: string
|
||||
now: string
|
||||
}): Promise<{
|
||||
order: WorkOrderRow | null
|
||||
failureReason:
|
||||
| 'work_order_not_found'
|
||||
| 'work_order_mode_invalid'
|
||||
| 'work_order_status_invalid'
|
||||
| 'work_order_gift_phase_invalid'
|
||||
| null
|
||||
}> {
|
||||
return withTransaction(async (client) => {
|
||||
const currentResult = await client.query<WorkOrderRow>(
|
||||
'SELECT * FROM work_orders WHERE id = $1 FOR UPDATE',
|
||||
[input.workOrderId],
|
||||
)
|
||||
const workOrder = currentResult.rows[0] || null
|
||||
if (!workOrder) return { order: null, failureReason: 'work_order_not_found' }
|
||||
if (
|
||||
normalizeWorkOrderAcceptanceMode(workOrder.acceptance_mode) !==
|
||||
WORK_ORDER_ACCEPTANCE_MODE.FRIEND_GIFT
|
||||
) {
|
||||
return { order: null, failureReason: 'work_order_mode_invalid' }
|
||||
}
|
||||
if (!['in_progress', 'problem'].includes(workOrder.status)) {
|
||||
return { order: null, failureReason: 'work_order_status_invalid' }
|
||||
}
|
||||
if (
|
||||
workOrder.gift_phase !== WORK_ORDER_GIFT_PHASE.MATERIAL_SUBMITTED &&
|
||||
workOrder.gift_phase !== WORK_ORDER_GIFT_PHASE.FRIEND_COUNTDOWN
|
||||
) {
|
||||
return { order: null, failureReason: 'work_order_gift_phase_invalid' }
|
||||
}
|
||||
|
||||
await client.query(
|
||||
`
|
||||
UPDATE work_orders
|
||||
SET
|
||||
gift_phase = 'material_required',
|
||||
booster_material_json = '{}'::jsonb,
|
||||
friend_added_at = NULL,
|
||||
gift_available_at = NULL,
|
||||
deadline_at = CASE
|
||||
WHEN timeout_minutes > 0
|
||||
THEN $1::timestamptz + (timeout_minutes * INTERVAL '1 minute')
|
||||
ELSE NULL
|
||||
END,
|
||||
updated_at = $1::timestamptz
|
||||
WHERE id = $2
|
||||
`,
|
||||
[input.now, input.workOrderId],
|
||||
)
|
||||
await createWorkOrderEventWithClient(client, {
|
||||
workOrderId: input.workOrderId,
|
||||
actorType: 'admin',
|
||||
actorId: input.actorName || '',
|
||||
eventType: 'gift_material_reset',
|
||||
fromStatus: workOrder.status,
|
||||
toStatus: workOrder.status,
|
||||
payloadJson: JSON.stringify({
|
||||
previousPhase: workOrder.gift_phase,
|
||||
note: String(input.note || '').trim(),
|
||||
}),
|
||||
now: input.now,
|
||||
})
|
||||
return {
|
||||
order: await getWorkOrderByIdWithClient(client, input.workOrderId),
|
||||
failureReason: null,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -60,6 +60,10 @@ export async function grabWorkOrder(input: {
|
||||
last_assigned_worker_id = $1,
|
||||
assigned_at = $2,
|
||||
deadline_at = $3,
|
||||
gift_phase = CASE WHEN acceptance_mode = 'friend_gift' THEN 'material_required' ELSE '' END,
|
||||
booster_material_json = '{}'::jsonb,
|
||||
friend_added_at = NULL,
|
||||
gift_available_at = NULL,
|
||||
updated_at = $2
|
||||
WHERE id = $4
|
||||
AND status = 'open'
|
||||
@@ -180,6 +184,10 @@ export async function assignWorkOrderToWorker(input: {
|
||||
last_assigned_worker_id = $1,
|
||||
assigned_at = $2,
|
||||
deadline_at = $3,
|
||||
gift_phase = CASE WHEN acceptance_mode = 'friend_gift' THEN 'material_required' ELSE '' END,
|
||||
booster_material_json = '{}'::jsonb,
|
||||
friend_added_at = NULL,
|
||||
gift_available_at = NULL,
|
||||
updated_at = $2
|
||||
WHERE id = $4
|
||||
`,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ensureWorkerWalletWithClient, getWorkerWalletWithClient } from './share
|
||||
import { createWorkOrderEventWithClient } from './work-order-event-repo.js'
|
||||
import { getWorkOrderByIdWithClient } from './work-order-query-repo.js'
|
||||
import { getOutstandingDepositAmountWithClient } from './work-order-deposit-repo.js'
|
||||
import { RESET_GIFT_FLOW_SQL } from './work-order-gift-repo.js'
|
||||
import type { ProblemWorkOrderResolutionAction, WorkOrderRow } from './types.js'
|
||||
|
||||
export async function resolveProblemWorkOrder(input: {
|
||||
@@ -100,11 +101,34 @@ export async function resolveProblemWorkOrder(input: {
|
||||
submitted_at = CASE WHEN $3 THEN NULL ELSE submitted_at END,
|
||||
problem_note = CASE WHEN $2 THEN '' ELSE problem_note END,
|
||||
deadline_at = CASE
|
||||
WHEN $1 = 'in_progress' AND timeout_minutes > 0
|
||||
THEN NOW() + (timeout_minutes * INTERVAL '1 minute')
|
||||
WHEN $1 = 'in_progress' AND timeout_minutes > 0 THEN
|
||||
CASE
|
||||
WHEN acceptance_mode = 'friend_gift'
|
||||
AND gift_available_at IS NOT NULL
|
||||
AND gift_available_at > NOW()
|
||||
THEN gift_available_at + (timeout_minutes * INTERVAL '1 minute')
|
||||
ELSE NOW() + (timeout_minutes * INTERVAL '1 minute')
|
||||
END
|
||||
WHEN $1 = 'open' THEN NULL
|
||||
ELSE deadline_at
|
||||
END,
|
||||
gift_phase = CASE
|
||||
WHEN $1 = 'open' OR $1 = 'cancelled'
|
||||
THEN CASE WHEN acceptance_mode = 'friend_gift' THEN 'material_required' ELSE '' END
|
||||
ELSE gift_phase
|
||||
END,
|
||||
booster_material_json = CASE
|
||||
WHEN $1 = 'open' OR $1 = 'cancelled' THEN '{}'::jsonb
|
||||
ELSE booster_material_json
|
||||
END,
|
||||
friend_added_at = CASE
|
||||
WHEN $1 = 'open' OR $1 = 'cancelled' THEN NULL
|
||||
ELSE friend_added_at
|
||||
END,
|
||||
gift_available_at = CASE
|
||||
WHEN $1 = 'open' OR $1 = 'cancelled' THEN NULL
|
||||
ELSE gift_available_at
|
||||
END,
|
||||
updated_at = $4
|
||||
WHERE id = $5
|
||||
`,
|
||||
@@ -237,6 +261,7 @@ export async function settleOverdueWorkOrder(input: {
|
||||
problem_note = CASE WHEN $1 = 'open' THEN '' ELSE problem_note END,
|
||||
published_at = CASE WHEN $1 = 'open' THEN $2 ELSE published_at END,
|
||||
hall_queued_at = CASE WHEN $1 = 'open' THEN $2 ELSE hall_queued_at END,
|
||||
${RESET_GIFT_FLOW_SQL},
|
||||
updated_at = $2
|
||||
WHERE id = $3
|
||||
`,
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import { query } from '../../db/client.js'
|
||||
import { toJsonString, toPositiveInteger } from './shared.js'
|
||||
import { getWorkOrderById } from './work-order-query-repo.js'
|
||||
import {
|
||||
normalizeWorkOrderAcceptanceMode,
|
||||
normalizeWorkOrderGiftCooldownHours,
|
||||
WORK_ORDER_ACCEPTANCE_MODE,
|
||||
WORK_ORDER_GIFT_PHASE,
|
||||
} from '../../domain/work-order-acceptance-mode.js'
|
||||
import type { CreateWorkOrderInput, WorkOrderRow } from './types.js'
|
||||
|
||||
export async function createWorkOrder(input: CreateWorkOrderInput): Promise<WorkOrderRow | null> {
|
||||
const acceptanceMode = normalizeWorkOrderAcceptanceMode(input.acceptanceMode)
|
||||
const isFriendGift = acceptanceMode === WORK_ORDER_ACCEPTANCE_MODE.FRIEND_GIFT
|
||||
const result = await query<{ id: number }>(
|
||||
`
|
||||
INSERT INTO work_orders (
|
||||
@@ -11,10 +19,13 @@ export async function createWorkOrder(input: CreateWorkOrderInput): Promise<Work
|
||||
product_name, category_id, status, reward_amount, required_deposit_amount,
|
||||
deposit_threshold_amount, sharing_enabled, sharing_total_quantity,
|
||||
sharing_unit_reward, timeout_minutes, timeout_policy,
|
||||
acceptance_mode, gift_phase, gift_cooldown_hours,
|
||||
material_json, requirement_json, created_at, updated_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11,
|
||||
$12, $13, $14, $15, $16, $17, $18::jsonb, $19::jsonb, $20, $21
|
||||
$12, $13, $14, $15, $16, $17,
|
||||
$18, $19, $20,
|
||||
$21::jsonb, $22::jsonb, $23, $24
|
||||
)
|
||||
RETURNING id
|
||||
`,
|
||||
@@ -36,6 +47,9 @@ export async function createWorkOrder(input: CreateWorkOrderInput): Promise<Work
|
||||
toPositiveInteger(input.sharingUnitReward, 0),
|
||||
toPositiveInteger(input.timeoutMinutes, 0),
|
||||
String(input.timeoutPolicy || 'reopen').trim() || 'reopen',
|
||||
acceptanceMode,
|
||||
isFriendGift ? WORK_ORDER_GIFT_PHASE.MATERIAL_REQUIRED : '',
|
||||
normalizeWorkOrderGiftCooldownHours(input.giftCooldownHours),
|
||||
input.materialJson,
|
||||
input.requirementJson,
|
||||
input.now,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { query } from '../../db/client.js'
|
||||
import type { ListInput, WorkOrderRow, WorkOrderStatistics } from './types.js'
|
||||
import type { PoolClient } from 'pg'
|
||||
import {
|
||||
normalizeWorkOrderAcceptanceMode,
|
||||
WORK_ORDER_GIFT_PHASE,
|
||||
} from '../../domain/work-order-acceptance-mode.js'
|
||||
export const WORK_ORDER_SELECT = `
|
||||
SELECT
|
||||
wo.*,
|
||||
@@ -167,6 +171,8 @@ export async function listWorkOrders({
|
||||
visibleAfterIso = '',
|
||||
excludeFilledSharing = false,
|
||||
vipEvidencePending = false,
|
||||
acceptanceMode = '',
|
||||
giftPhase = '',
|
||||
useOperationalStatus = false,
|
||||
pinnedFirst = false,
|
||||
sort = 'id_desc',
|
||||
@@ -186,6 +192,8 @@ export async function listWorkOrders({
|
||||
visibleAfterIso,
|
||||
excludeFilledSharing,
|
||||
vipEvidencePending,
|
||||
acceptanceMode,
|
||||
giftPhase,
|
||||
useOperationalStatus,
|
||||
createdFrom,
|
||||
createdTo,
|
||||
@@ -382,7 +390,7 @@ export async function getWorkerOrderOverview(workerId: number | string): Promise
|
||||
}))
|
||||
}
|
||||
|
||||
/** 查询已超过接单时限、仍待处置的工单。 */
|
||||
/** 查询已超过接单时限、仍待处置的工单。好友赠送单在“待确认好友”阶段等待后台操作,不计打手超时。 */
|
||||
export async function listOverdueWorkOrders({
|
||||
limit = 50,
|
||||
workerId = 0,
|
||||
@@ -399,6 +407,7 @@ export async function listOverdueWorkOrders({
|
||||
WHERE wo.status = 'in_progress'
|
||||
AND wo.deadline_at IS NOT NULL
|
||||
AND wo.deadline_at < NOW()
|
||||
AND NOT (wo.acceptance_mode = 'friend_gift' AND wo.gift_phase = 'material_submitted')
|
||||
${workerClause}
|
||||
ORDER BY wo.deadline_at ASC
|
||||
LIMIT $${params.length}`,
|
||||
@@ -522,6 +531,8 @@ export function buildWorkOrderWhere({
|
||||
visibleAfterIso = '',
|
||||
excludeFilledSharing = false,
|
||||
vipEvidencePending = false,
|
||||
acceptanceMode = '',
|
||||
giftPhase = '',
|
||||
useOperationalStatus = false,
|
||||
createdFrom = '',
|
||||
createdTo = '',
|
||||
@@ -685,6 +696,25 @@ export function buildWorkOrderWhere({
|
||||
params.push(categoryId)
|
||||
filters.push(`wo.category_id = $${params.length}`)
|
||||
}
|
||||
if (acceptanceMode) {
|
||||
params.push(normalizeWorkOrderAcceptanceMode(acceptanceMode))
|
||||
filters.push(`wo.acceptance_mode = $${params.length}`)
|
||||
}
|
||||
if (giftPhase) {
|
||||
const normalizedGiftPhase = String(giftPhase).trim()
|
||||
if (normalizedGiftPhase === WORK_ORDER_GIFT_PHASE.GIFT_READY) {
|
||||
// gift_ready 是冷却到点后的实时阶段,由时间条件计算而不是落库状态。
|
||||
filters.push(`(
|
||||
wo.acceptance_mode = 'friend_gift'
|
||||
AND wo.gift_phase = 'friend_countdown'
|
||||
AND wo.gift_available_at IS NOT NULL
|
||||
AND wo.gift_available_at <= NOW()
|
||||
)`)
|
||||
} else if (Object.values(WORK_ORDER_GIFT_PHASE).includes(normalizedGiftPhase as never)) {
|
||||
params.push(normalizedGiftPhase)
|
||||
filters.push(`wo.acceptance_mode = 'friend_gift' AND wo.gift_phase = $${params.length}`)
|
||||
}
|
||||
}
|
||||
if (hallCandidateLimit > 0) {
|
||||
params.push(hallCandidateLimit)
|
||||
filters.push(`wo.id IN (${buildHallCandidateIdsSql(`$${params.length}`)})`)
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { query, withTransaction } from '../../db/client.js'
|
||||
import { toPositiveInteger } from './shared.js'
|
||||
import {
|
||||
normalizeWorkOrderAcceptanceMode,
|
||||
normalizeWorkOrderGiftCooldownHours,
|
||||
} from '../../domain/work-order-acceptance-mode.js'
|
||||
import type {
|
||||
ProductRuleListInput,
|
||||
WorkOrderRow,
|
||||
@@ -336,6 +340,8 @@ export async function upsertWorkProductRule(input: {
|
||||
sharingAutoFromOrder?: boolean
|
||||
timeoutMinutes?: number
|
||||
timeoutPolicy?: string
|
||||
acceptanceMode?: string
|
||||
giftCooldownHours?: number
|
||||
requirementJson: string
|
||||
matchJson?: string
|
||||
sortOrder: number
|
||||
@@ -350,6 +356,7 @@ export async function upsertWorkProductRule(input: {
|
||||
required_deposit_amount, deposit_threshold_amount,
|
||||
sharing_enabled, sharing_total_quantity, sharing_unit_reward, sharing_auto_from_order,
|
||||
timeout_minutes, timeout_policy,
|
||||
acceptance_mode, gift_cooldown_hours,
|
||||
requirement_json, match_json,
|
||||
sort_order, created_at, updated_at
|
||||
) VALUES (
|
||||
@@ -359,8 +366,9 @@ export async function upsertWorkProductRule(input: {
|
||||
$13, $14,
|
||||
$15, $16, $17, $18,
|
||||
$19, $20,
|
||||
$21::jsonb, $22::jsonb,
|
||||
$23, $24, $25
|
||||
$21, $22,
|
||||
$23::jsonb, $24::jsonb,
|
||||
$25, $26, $27
|
||||
)
|
||||
ON CONFLICT (rule_key) DO UPDATE
|
||||
SET
|
||||
@@ -383,6 +391,8 @@ export async function upsertWorkProductRule(input: {
|
||||
sharing_auto_from_order = EXCLUDED.sharing_auto_from_order,
|
||||
timeout_minutes = EXCLUDED.timeout_minutes,
|
||||
timeout_policy = EXCLUDED.timeout_policy,
|
||||
acceptance_mode = EXCLUDED.acceptance_mode,
|
||||
gift_cooldown_hours = EXCLUDED.gift_cooldown_hours,
|
||||
requirement_json = EXCLUDED.requirement_json,
|
||||
match_json = EXCLUDED.match_json,
|
||||
sort_order = EXCLUDED.sort_order,
|
||||
@@ -410,6 +420,8 @@ export async function upsertWorkProductRule(input: {
|
||||
input.sharingAutoFromOrder === true,
|
||||
toPositiveInteger(input.timeoutMinutes, 0),
|
||||
String(input.timeoutPolicy || 'reopen').trim(),
|
||||
normalizeWorkOrderAcceptanceMode(input.acceptanceMode),
|
||||
normalizeWorkOrderGiftCooldownHours(input.giftCooldownHours),
|
||||
input.requirementJson,
|
||||
input.matchJson || '{}',
|
||||
input.sortOrder,
|
||||
|
||||
Reference in New Issue
Block a user