增加接单分类和大厅分类配额
This commit is contained in:
@@ -101,5 +101,21 @@ test('大厅容量先确定全局候选集合,再应用页面筛选条件', ()
|
||||
assert.match(query.whereClause, /LIMIT \$4/)
|
||||
const candidateSql = query.whereClause.slice(query.whereClause.indexOf('candidate.status'))
|
||||
assert.doesNotMatch(candidateSql, /candidate\.category_id|candidate.*测试商品/)
|
||||
assert.deepEqual(query.params, [['open'], '%测试商品%', 3, 50, '2026-08-21T00:00:00.000Z'])
|
||||
assert.deepEqual(query.params, [['open'], '%测试商品%', 3, 50, '{}', '2026-08-21T00:00:00.000Z'])
|
||||
})
|
||||
|
||||
test('后台接单工单支持按接单分类筛选', () => {
|
||||
const query = buildWorkOrderWhere({ categoryId: 7 })
|
||||
assert.match(query.whereClause, /wo\.category_id = \$1/)
|
||||
assert.deepEqual(query.params, [7])
|
||||
})
|
||||
|
||||
test('大厅候选按分类上限取队首后再受全局上限限制', () => {
|
||||
const query = buildWorkOrderWhere({
|
||||
hallCandidateLimit: 20,
|
||||
hallCategoryLimits: { '1': 3, '2': 5 },
|
||||
})
|
||||
assert.match(query.whereClause, /ROW_NUMBER\(\) OVER/)
|
||||
assert.match(query.whereClause, /category_rank/)
|
||||
assert.deepEqual(query.params, [20, '{"1":3,"2":5}'])
|
||||
})
|
||||
|
||||
@@ -431,6 +431,7 @@ export type ListInput = {
|
||||
workerSharingId?: number
|
||||
/** 大厅候选集合的总展示上限;筛选与分页在候选集合内执行。 */
|
||||
hallCandidateLimit?: number
|
||||
hallCategoryLimits?: Record<string, number>
|
||||
visibleAfterIso?: string
|
||||
/** 抢单大厅隐藏已被拼完的工单。 */
|
||||
excludeFilledSharing?: boolean
|
||||
|
||||
@@ -14,6 +14,7 @@ export async function grabWorkOrder(input: {
|
||||
depositAmount: number
|
||||
maxActiveOrders: number
|
||||
hallCandidateLimit?: number
|
||||
hallCategoryLimits?: Record<string, number>
|
||||
visibleAfterIso?: string
|
||||
deadlineAt: string | null
|
||||
now: string
|
||||
@@ -42,6 +43,7 @@ export async function grabWorkOrder(input: {
|
||||
!(await isWorkOrderWithinHallCapacityWithClient(client, {
|
||||
workOrderId: input.workOrderId,
|
||||
hallCandidateLimit: input.hallCandidateLimit,
|
||||
...(input.hallCategoryLimits ? { hallCategoryLimits: input.hallCategoryLimits } : {}),
|
||||
visibleAfterIso: input.visibleAfterIso,
|
||||
}))
|
||||
) {
|
||||
|
||||
@@ -226,6 +226,7 @@ export async function listWorkOrders({
|
||||
categoryId = 0,
|
||||
workerSharingId = 0,
|
||||
hallCandidateLimit = 0,
|
||||
hallCategoryLimits = {},
|
||||
visibleAfterIso = '',
|
||||
excludeFilledSharing = false,
|
||||
vipEvidencePending = false,
|
||||
@@ -247,6 +248,7 @@ export async function listWorkOrders({
|
||||
categoryId,
|
||||
workerSharingId,
|
||||
hallCandidateLimit,
|
||||
hallCategoryLimits,
|
||||
visibleAfterIso,
|
||||
excludeFilledSharing,
|
||||
vipEvidencePending,
|
||||
@@ -652,6 +654,7 @@ export function buildWorkOrderWhere({
|
||||
categoryId = 0,
|
||||
workerSharingId = 0,
|
||||
hallCandidateLimit = 0,
|
||||
hallCategoryLimits = {},
|
||||
visibleAfterIso = '',
|
||||
excludeFilledSharing = false,
|
||||
vipEvidencePending = false,
|
||||
@@ -843,7 +846,9 @@ export function buildWorkOrderWhere({
|
||||
}
|
||||
if (hallCandidateLimit > 0) {
|
||||
params.push(hallCandidateLimit)
|
||||
filters.push(`wo.id IN (${buildHallCandidateIdsSql(`$${params.length}`)})`)
|
||||
const limitPlaceholder = `$${params.length}`
|
||||
params.push(JSON.stringify(hallCategoryLimits))
|
||||
filters.push(`wo.id IN (${buildHallCandidateIdsSql(limitPlaceholder, `$${params.length}`)})`)
|
||||
}
|
||||
if (visibleAfterIso) {
|
||||
params.push(visibleAfterIso)
|
||||
@@ -863,23 +868,34 @@ export function buildWorkOrderWhere({
|
||||
}
|
||||
|
||||
/** 大厅容量先选出全局队首,分类、关键词和 VIP 延迟只能在该集合内继续筛选。 */
|
||||
function buildHallCandidateIdsSql(limitPlaceholder: string) {
|
||||
function buildHallCandidateIdsSql(limitPlaceholder: string, categoryLimitsPlaceholder: string) {
|
||||
return `
|
||||
SELECT candidate.id
|
||||
FROM work_orders candidate
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
work_order_id,
|
||||
SUM(quantity) FILTER (WHERE status != 'cancelled') AS active_share_quantity
|
||||
FROM work_order_shares
|
||||
GROUP BY work_order_id
|
||||
) candidate_shares ON candidate_shares.work_order_id = candidate.id
|
||||
WHERE candidate.status = 'open'
|
||||
AND (
|
||||
candidate.sharing_enabled IS NOT TRUE
|
||||
OR COALESCE(candidate_shares.active_share_quantity, 0) < candidate.sharing_total_quantity
|
||||
)
|
||||
ORDER BY candidate.hall_queued_at ASC NULLS LAST, candidate.id ASC
|
||||
SELECT ranked.id
|
||||
FROM (
|
||||
SELECT candidate.id, candidate.category_id, candidate.hall_queued_at,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY candidate.category_id
|
||||
ORDER BY candidate.hall_queued_at ASC NULLS LAST, candidate.id ASC
|
||||
) AS category_rank
|
||||
FROM work_orders candidate
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
work_order_id,
|
||||
SUM(quantity) FILTER (WHERE status != 'cancelled') AS active_share_quantity
|
||||
FROM work_order_shares
|
||||
GROUP BY work_order_id
|
||||
) candidate_shares ON candidate_shares.work_order_id = candidate.id
|
||||
WHERE candidate.status = 'open'
|
||||
AND (
|
||||
candidate.sharing_enabled IS NOT TRUE
|
||||
OR COALESCE(candidate_shares.active_share_quantity, 0) < candidate.sharing_total_quantity
|
||||
)
|
||||
) ranked
|
||||
WHERE ranked.category_rank <= COALESCE(
|
||||
NULLIF(${categoryLimitsPlaceholder}::jsonb ->> ranked.category_id::text, '')::int,
|
||||
${limitPlaceholder}
|
||||
)
|
||||
ORDER BY ranked.hall_queued_at ASC NULLS LAST, ranked.id ASC
|
||||
LIMIT ${limitPlaceholder}
|
||||
`
|
||||
}
|
||||
@@ -887,7 +903,12 @@ function buildHallCandidateIdsSql(limitPlaceholder: string) {
|
||||
/** 抢单操作在事务中复核容量与当前打手的可见延迟,避免通过订单 ID 绕过大厅队列。 */
|
||||
export async function isWorkOrderWithinHallCapacityWithClient(
|
||||
client: PoolClient,
|
||||
input: { workOrderId: number; hallCandidateLimit: number; visibleAfterIso?: string | undefined },
|
||||
input: {
|
||||
workOrderId: number
|
||||
hallCandidateLimit: number
|
||||
hallCategoryLimits?: Record<string, number>
|
||||
visibleAfterIso?: string | undefined
|
||||
},
|
||||
) {
|
||||
const result = await client.query<{ visible: boolean }>(
|
||||
`
|
||||
@@ -895,13 +916,14 @@ export async function isWorkOrderWithinHallCapacityWithClient(
|
||||
SELECT 1
|
||||
FROM work_orders wo
|
||||
WHERE wo.id = $1
|
||||
AND wo.id IN (${buildHallCandidateIdsSql('$2')})
|
||||
AND ($3::timestamptz IS NULL OR wo.hall_queued_at IS NULL OR wo.hall_queued_at <= $3)
|
||||
AND wo.id IN (${buildHallCandidateIdsSql('$2', '$3')})
|
||||
AND ($4::timestamptz IS NULL OR wo.hall_queued_at IS NULL OR wo.hall_queued_at <= $4)
|
||||
) AS visible
|
||||
`,
|
||||
[
|
||||
input.workOrderId,
|
||||
input.hallCandidateLimit,
|
||||
JSON.stringify(input.hallCategoryLimits || {}),
|
||||
input.visibleAfterIso ? input.visibleAfterIso : null,
|
||||
],
|
||||
)
|
||||
|
||||
@@ -103,6 +103,7 @@ export async function joinWorkOrderShare(input: {
|
||||
takeRemaining?: boolean
|
||||
maxActiveOrders: number
|
||||
hallCandidateLimit?: number
|
||||
hallCategoryLimits?: Record<string, number>
|
||||
visibleAfterIso?: string
|
||||
now: string
|
||||
}): Promise<{
|
||||
@@ -137,6 +138,7 @@ export async function joinWorkOrderShare(input: {
|
||||
!(await isWorkOrderWithinHallCapacityWithClient(client, {
|
||||
workOrderId: input.workOrderId,
|
||||
hallCandidateLimit: input.hallCandidateLimit,
|
||||
...(input.hallCategoryLimits ? { hallCategoryLimits: input.hallCategoryLimits } : {}),
|
||||
visibleAfterIso: input.visibleAfterIso,
|
||||
}))
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user