feat(category): 接单平台支持二级分类目录与接单大厅联动展示及限额优化
This commit is contained in:
@@ -256,6 +256,8 @@ export type WorkCategoryRow = {
|
||||
id: number
|
||||
category_key: string
|
||||
name: string
|
||||
parent_id?: number | null
|
||||
icon_url?: string | null
|
||||
sort_order: number
|
||||
status: string
|
||||
created_at: string
|
||||
@@ -402,6 +404,8 @@ export type WorkOrderRow = {
|
||||
active_share_quantity?: number
|
||||
pending_share_count?: number
|
||||
category_name?: string
|
||||
parent_category_id?: number | null
|
||||
parent_category_name?: string
|
||||
worker_username?: string
|
||||
worker_display_name?: string
|
||||
/** 取消信息,由工单查询从取消类事件推导,仅已取消工单有值。 */
|
||||
|
||||
@@ -36,19 +36,30 @@ export async function listAllWorkCategories(): Promise<WorkCategoryRow[]> {
|
||||
export async function upsertWorkCategory(input: {
|
||||
categoryKey: string
|
||||
name: string
|
||||
parentId?: number | null
|
||||
iconUrl?: string | null
|
||||
sortOrder: number
|
||||
status: string
|
||||
now: string
|
||||
}): Promise<WorkCategoryRow | null> {
|
||||
const result = await query<WorkCategoryRow>(
|
||||
`
|
||||
INSERT INTO work_categories (category_key, name, sort_order, status, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
INSERT INTO work_categories (category_key, name, parent_id, icon_url, sort_order, status, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
ON CONFLICT (category_key) DO UPDATE
|
||||
SET name = EXCLUDED.name, sort_order = EXCLUDED.sort_order, status = EXCLUDED.status, updated_at = EXCLUDED.updated_at
|
||||
SET name = EXCLUDED.name, parent_id = EXCLUDED.parent_id, icon_url = EXCLUDED.icon_url, sort_order = EXCLUDED.sort_order, status = EXCLUDED.status, updated_at = EXCLUDED.updated_at
|
||||
RETURNING *
|
||||
`,
|
||||
[input.categoryKey, input.name, input.sortOrder, input.status, input.now, input.now],
|
||||
[
|
||||
input.categoryKey,
|
||||
input.name,
|
||||
input.parentId ?? null,
|
||||
input.iconUrl ?? null,
|
||||
input.sortOrder,
|
||||
input.status,
|
||||
input.now,
|
||||
input.now,
|
||||
],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
}
|
||||
@@ -56,8 +67,9 @@ export async function upsertWorkCategory(input: {
|
||||
export async function countWorkCategoryUsages(categoryId: number | string): Promise<{
|
||||
workOrderCount: number
|
||||
productRuleCount: number
|
||||
childCategoryCount: number
|
||||
}> {
|
||||
const [orders, rules] = await Promise.all([
|
||||
const [orders, rules, children] = await Promise.all([
|
||||
query<{ total: number }>(
|
||||
'SELECT COUNT(*)::int AS total FROM work_orders WHERE category_id = $1',
|
||||
[Number(categoryId)],
|
||||
@@ -66,10 +78,15 @@ export async function countWorkCategoryUsages(categoryId: number | string): Prom
|
||||
'SELECT COUNT(*)::int AS total FROM work_product_rules WHERE category_id = $1',
|
||||
[Number(categoryId)],
|
||||
),
|
||||
query<{ total: number }>(
|
||||
'SELECT COUNT(*)::int AS total FROM work_categories WHERE parent_id = $1',
|
||||
[Number(categoryId)],
|
||||
),
|
||||
])
|
||||
return {
|
||||
workOrderCount: Number(orders.rows[0]?.total || 0),
|
||||
productRuleCount: Number(rules.rows[0]?.total || 0),
|
||||
childCategoryCount: Number(children.rows[0]?.total || 0),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ export const WORK_ORDER_SELECT = `
|
||||
COALESCE(share_summary.active_share_quantity, 0)::int AS active_share_quantity,
|
||||
COALESCE(share_summary.pending_share_count, 0)::int AS pending_share_count,
|
||||
wc.name AS category_name,
|
||||
parent_wc.id AS parent_category_id,
|
||||
parent_wc.name AS parent_category_name,
|
||||
wu.username AS worker_username,
|
||||
wu.display_name AS worker_display_name,
|
||||
cancel_info.cancel_reason,
|
||||
@@ -20,6 +22,7 @@ export const WORK_ORDER_SELECT = `
|
||||
cancel_info.cancelled_at
|
||||
FROM work_orders wo
|
||||
LEFT JOIN work_categories wc ON wc.id = wo.category_id
|
||||
LEFT JOIN work_categories parent_wc ON parent_wc.id = wc.parent_id
|
||||
LEFT JOIN worker_users wu ON wu.id = wo.assigned_worker_id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
@@ -862,7 +865,9 @@ export function buildWorkOrderWhere({
|
||||
}
|
||||
if (categoryId) {
|
||||
params.push(categoryId)
|
||||
filters.push(`wo.category_id = $${params.length}`)
|
||||
filters.push(
|
||||
`(wo.category_id = $${params.length} OR wo.category_id IN (SELECT id FROM work_categories WHERE parent_id = $${params.length}))`,
|
||||
)
|
||||
}
|
||||
if (acceptanceMode) {
|
||||
params.push(normalizeWorkOrderAcceptanceMode(acceptanceMode))
|
||||
@@ -917,12 +922,21 @@ function buildHallCandidateIdsSql(limitPlaceholder: string, categoryLimitsPlaceh
|
||||
return `
|
||||
SELECT ranked.id
|
||||
FROM (
|
||||
SELECT candidate.id, candidate.category_id, candidate.hall_queued_at,
|
||||
SELECT
|
||||
candidate.id,
|
||||
candidate.category_id,
|
||||
candidate.hall_queued_at,
|
||||
COALESCE(candidate_wc.parent_id, candidate.category_id) AS primary_category_id,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY COALESCE(candidate_wc.parent_id, candidate.category_id)
|
||||
ORDER BY candidate.hall_queued_at ASC NULLS LAST, candidate.id ASC
|
||||
) AS primary_category_rank,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY candidate.category_id
|
||||
ORDER BY candidate.hall_queued_at ASC NULLS LAST, candidate.id ASC
|
||||
) AS category_rank
|
||||
) AS sub_category_rank
|
||||
FROM work_orders candidate
|
||||
LEFT JOIN work_categories candidate_wc ON candidate_wc.id = candidate.category_id
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
work_order_id,
|
||||
@@ -936,10 +950,15 @@ function buildHallCandidateIdsSql(limitPlaceholder: string, categoryLimitsPlaceh
|
||||
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,
|
||||
WHERE ranked.primary_category_rank <= COALESCE(
|
||||
NULLIF(${categoryLimitsPlaceholder}::jsonb ->> ranked.primary_category_id::text, '')::int,
|
||||
${limitPlaceholder}
|
||||
)
|
||||
AND (
|
||||
NULLIF(${categoryLimitsPlaceholder}::jsonb ->> ranked.category_id::text, '') IS NULL
|
||||
OR ranked.category_id = ranked.primary_category_id
|
||||
OR ranked.sub_category_rank <= NULLIF(${categoryLimitsPlaceholder}::jsonb ->> ranked.category_id::text, '')::int
|
||||
)
|
||||
ORDER BY ranked.hall_queued_at ASC NULLS LAST, ranked.id ASC
|
||||
LIMIT ${limitPlaceholder}
|
||||
`
|
||||
|
||||
Reference in New Issue
Block a user