接单平台分类管理完善:抢单大厅分类筛选与分类 CRUD
- 抢单大厅新增分类 Tab 筛选(仅显示启用分类,支持按分类查询工单) - 管理端分类面板支持编辑、启用/停用切换、删除(被工单/规则引用时禁止删除) - 后端工单列表支持 categoryId 筛选,大厅接口返回分类列表
This commit is contained in:
@@ -166,6 +166,7 @@ type ListInput = {
|
||||
status?: string
|
||||
keyword?: string
|
||||
workerId?: number
|
||||
categoryId?: number
|
||||
}
|
||||
|
||||
type ProductRuleListInput = {
|
||||
@@ -912,6 +913,14 @@ export async function getWorkCategoryByKey(categoryKey: string): Promise<WorkCat
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
export async function getWorkCategoryById(categoryId: number | string): Promise<WorkCategoryRow | null> {
|
||||
const result = await query<WorkCategoryRow>(
|
||||
'SELECT * FROM work_categories WHERE id = $1 LIMIT 1',
|
||||
[Number(categoryId)],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
export async function listWorkCategories(): Promise<WorkCategoryRow[]> {
|
||||
const result = await query<WorkCategoryRow>(
|
||||
`SELECT * FROM work_categories WHERE status = 'active' ORDER BY sort_order ASC, id ASC`,
|
||||
@@ -946,6 +955,36 @@ export async function upsertWorkCategory(input: {
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
export async function countWorkCategoryUsages(categoryId: number | string): Promise<{
|
||||
workOrderCount: number
|
||||
productRuleCount: number
|
||||
}> {
|
||||
const [orders, rules] = await Promise.all([
|
||||
query<{ total: number }>(
|
||||
'SELECT COUNT(*)::int AS total FROM work_orders WHERE category_id = $1',
|
||||
[Number(categoryId)],
|
||||
),
|
||||
query<{ total: number }>(
|
||||
'SELECT COUNT(*)::int AS total FROM work_product_rules WHERE category_id = $1',
|
||||
[Number(categoryId)],
|
||||
),
|
||||
])
|
||||
return {
|
||||
workOrderCount: Number(orders.rows[0]?.total || 0),
|
||||
productRuleCount: Number(rules.rows[0]?.total || 0),
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteWorkCategory(
|
||||
categoryId: number | string,
|
||||
): Promise<{ deleted: boolean }> {
|
||||
const result = await query<{ id: number }>(
|
||||
'DELETE FROM work_categories WHERE id = $1 RETURNING id',
|
||||
[Number(categoryId)],
|
||||
)
|
||||
return { deleted: Boolean(result.rows[0]) }
|
||||
}
|
||||
|
||||
export async function listWorkProductRules({
|
||||
enabled = null,
|
||||
keyword = '',
|
||||
@@ -1123,8 +1162,14 @@ export async function listWorkOrders({
|
||||
status = '',
|
||||
keyword = '',
|
||||
workerId = 0,
|
||||
categoryId = 0,
|
||||
}: ListInput = {}): Promise<{ items: WorkOrderRow[]; total: number }> {
|
||||
const { whereClause, params } = buildWorkOrderWhere({ status, keyword, workerId })
|
||||
const { whereClause, params } = buildWorkOrderWhere({
|
||||
status,
|
||||
keyword,
|
||||
workerId,
|
||||
categoryId,
|
||||
})
|
||||
const totalResult = await query<{ total: number }>(
|
||||
`
|
||||
SELECT COUNT(*)::int AS total
|
||||
@@ -1713,7 +1758,12 @@ function buildWorkerUserWhere({
|
||||
}
|
||||
}
|
||||
|
||||
function buildWorkOrderWhere({ status = '', keyword = '', workerId = 0 }: ListInput) {
|
||||
function buildWorkOrderWhere({
|
||||
status = '',
|
||||
keyword = '',
|
||||
workerId = 0,
|
||||
categoryId = 0,
|
||||
}: ListInput) {
|
||||
const filters: string[] = []
|
||||
const params: unknown[] = []
|
||||
if (status) {
|
||||
@@ -1730,6 +1780,10 @@ function buildWorkOrderWhere({ status = '', keyword = '', workerId = 0 }: ListIn
|
||||
params.push(workerId)
|
||||
filters.push(`wo.assigned_worker_id = $${params.length}`)
|
||||
}
|
||||
if (categoryId) {
|
||||
params.push(categoryId)
|
||||
filters.push(`wo.category_id = $${params.length}`)
|
||||
}
|
||||
return {
|
||||
whereClause: filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '',
|
||||
params,
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
acceptAdminWorkOrder,
|
||||
creditAdminWorkerWallet,
|
||||
createAdminMockWorkOrder,
|
||||
deleteAdminWorkCategory,
|
||||
getAdminWorkerFinanceConfig,
|
||||
getAdminWorkerPlatformSummary,
|
||||
listAdminWorkerFinanceRequests,
|
||||
@@ -92,6 +93,22 @@ router.post(
|
||||
}),
|
||||
)
|
||||
|
||||
router.delete(
|
||||
'/worker-platform/categories/:categoryId',
|
||||
requireAdminRoles(['admin', 'operator']),
|
||||
createJsonHandler((req) => deleteAdminWorkCategory(String(req.params.categoryId || '')), {
|
||||
successMessage: '接单分类已删除',
|
||||
errorMessage: '删除接单分类失败',
|
||||
scope: '[admin/worker-platform/categories/:categoryId]',
|
||||
audit: (_req, data) => ({
|
||||
action: 'work_category_deleted',
|
||||
targetType: 'work_category',
|
||||
targetId: String(_req.params.categoryId || ''),
|
||||
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/worker-platform/product-rules',
|
||||
requireAdminRoles(['admin', 'operator', 'support']),
|
||||
|
||||
@@ -9,12 +9,15 @@ import {
|
||||
addWorkerWalletCredit,
|
||||
countWorkerAcceptedOrders,
|
||||
countWorkerActiveOrders,
|
||||
countWorkCategoryUsages,
|
||||
createWorkOrder,
|
||||
createWorkOrderEvent,
|
||||
createWorkerFinanceRequest,
|
||||
createWorkerUser,
|
||||
deleteWorkCategory,
|
||||
getWorkerFinanceRequestById,
|
||||
getWorkerFinanceRequestSummary,
|
||||
getWorkCategoryById,
|
||||
getWorkerLevelById,
|
||||
getWorkerLevelByKey,
|
||||
getWorkerUserByInviteCode,
|
||||
@@ -28,6 +31,7 @@ import {
|
||||
listPendingMaterialWorkOrdersByPlatformOrderId,
|
||||
listWorkerWalletLedgers,
|
||||
grabWorkOrder,
|
||||
listWorkCategories,
|
||||
listWorkOrders,
|
||||
listWorkProductRules,
|
||||
listWorkerLevels,
|
||||
@@ -627,17 +631,21 @@ export async function listWorkerHallOrders(query: JsonObject = {}, session: Work
|
||||
requireActiveWorkerSession(session)
|
||||
const page = normalizePage(query.page)
|
||||
const pageSize = normalizePageSize(query.pageSize)
|
||||
const categoryId = normalizeOptionalId(query.categoryId)
|
||||
const { items, total } = await listWorkOrders({
|
||||
page,
|
||||
pageSize,
|
||||
status: WORK_ORDER_STATUS.OPEN,
|
||||
keyword: String(query.keyword || '').trim(),
|
||||
categoryId: categoryId || 0,
|
||||
})
|
||||
const worker = await getRequiredWorker(session.workerId)
|
||||
const permissions = resolveWorkerPermissions(worker)
|
||||
const categories = (await listWorkCategories()).map(mapWorkCategory)
|
||||
return {
|
||||
items: items.map((item) => mapWorkOrderForWorker(item, permissions)),
|
||||
pagination: { page, pageSize, total },
|
||||
categories,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -880,6 +888,28 @@ export async function saveAdminWorkCategory(payload: JsonObject = {}) {
|
||||
return { category: mapWorkCategory(category) }
|
||||
}
|
||||
|
||||
export async function deleteAdminWorkCategory(categoryId: number | string) {
|
||||
const category = await getWorkCategoryById(Number(categoryId))
|
||||
if (!category) {
|
||||
throw createHttpError('分类不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'work_category_not_found',
|
||||
})
|
||||
}
|
||||
const usages = await countWorkCategoryUsages(Number(categoryId))
|
||||
if (usages.workOrderCount > 0 || usages.productRuleCount > 0) {
|
||||
throw createHttpError(
|
||||
`该分类已被 ${usages.workOrderCount} 个工单和 ${usages.productRuleCount} 条物品规则使用,无法删除,请先停用`,
|
||||
{
|
||||
statusCode: 409,
|
||||
errorCode: 'work_category_in_use',
|
||||
},
|
||||
)
|
||||
}
|
||||
const { deleted } = await deleteWorkCategory(Number(categoryId))
|
||||
return { deleted }
|
||||
}
|
||||
|
||||
export async function saveAdminWorkerLevel(payload: JsonObject = {}) {
|
||||
const now = nowIso()
|
||||
const levelKey = String(payload.levelKey || payload.level_key || '').trim() || DEFAULT_LEVEL_KEY
|
||||
|
||||
Reference in New Issue
Block a user