完善接单平台通知配置
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
import { query } from '../db/client.js'
|
||||
|
||||
export type AdminNotificationRow = {
|
||||
id: number
|
||||
notification_type: string
|
||||
priority: string
|
||||
entity_type: string
|
||||
entity_id: number
|
||||
dedupe_key: string
|
||||
title: string
|
||||
body: string
|
||||
action_path: string
|
||||
payload_json: string | Record<string, unknown>
|
||||
visible: boolean
|
||||
sound_enabled: boolean
|
||||
reminder_count: number
|
||||
last_reminded_at: string | null
|
||||
status: string
|
||||
resolved_at: string | null
|
||||
resolution_reason: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export async function createAdminNotification(input: {
|
||||
notificationType: string
|
||||
priority: string
|
||||
entityType: string
|
||||
entityId: number
|
||||
dedupeKey: string
|
||||
title: string
|
||||
body: string
|
||||
actionPath: string
|
||||
payloadJson?: string
|
||||
visible?: boolean
|
||||
soundEnabled?: boolean
|
||||
now: string
|
||||
}): Promise<AdminNotificationRow | null> {
|
||||
const result = await query<AdminNotificationRow>(
|
||||
`
|
||||
INSERT INTO admin_notifications (
|
||||
notification_type, priority, entity_type, entity_id, dedupe_key,
|
||||
title, body, action_path, payload_json, visible, sound_enabled, created_at, updated_at
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10, $11, $12, $12)
|
||||
ON CONFLICT (dedupe_key) DO NOTHING
|
||||
RETURNING *
|
||||
`,
|
||||
[
|
||||
input.notificationType,
|
||||
input.priority,
|
||||
input.entityType,
|
||||
input.entityId,
|
||||
input.dedupeKey,
|
||||
input.title,
|
||||
input.body,
|
||||
input.actionPath,
|
||||
input.payloadJson || '{}',
|
||||
input.visible !== false,
|
||||
input.soundEnabled !== false,
|
||||
input.now,
|
||||
],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
export async function getPendingAdminNotificationByDedupeKey(
|
||||
dedupeKey: string,
|
||||
): Promise<AdminNotificationRow | null> {
|
||||
const result = await query<AdminNotificationRow>(
|
||||
`SELECT * FROM admin_notifications WHERE dedupe_key = $1 AND status = 'pending' LIMIT 1`,
|
||||
[dedupeKey],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
export async function bumpAdminNotificationReminder(input: {
|
||||
dedupeKey: string
|
||||
notificationType: string
|
||||
title: string
|
||||
visible: boolean
|
||||
soundEnabled: boolean
|
||||
now: string
|
||||
}): Promise<AdminNotificationRow | null> {
|
||||
const result = await query<AdminNotificationRow>(
|
||||
`
|
||||
UPDATE admin_notifications
|
||||
SET
|
||||
notification_type = $1,
|
||||
title = $2,
|
||||
priority = 'high',
|
||||
visible = visible OR $3,
|
||||
sound_enabled = $4,
|
||||
reminder_count = reminder_count + 1,
|
||||
last_reminded_at = $5,
|
||||
updated_at = $5
|
||||
WHERE dedupe_key = $6
|
||||
AND status = 'pending'
|
||||
AND reminder_count < 3
|
||||
AND (
|
||||
last_reminded_at IS NULL
|
||||
OR last_reminded_at <= $5::timestamptz - INTERVAL '30 minutes'
|
||||
)
|
||||
RETURNING *
|
||||
`,
|
||||
[
|
||||
input.notificationType,
|
||||
input.title,
|
||||
input.visible,
|
||||
input.soundEnabled,
|
||||
input.now,
|
||||
input.dedupeKey,
|
||||
],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
export async function resolveAdminNotificationsByEntity(input: {
|
||||
entityType: string
|
||||
entityId: number
|
||||
reason: string
|
||||
now: string
|
||||
}): Promise<void> {
|
||||
await query(
|
||||
`
|
||||
UPDATE admin_notifications
|
||||
SET status = 'resolved', resolved_at = $1, resolution_reason = $2, updated_at = $1
|
||||
WHERE entity_type = $3 AND entity_id = $4 AND status = 'pending'
|
||||
`,
|
||||
[input.now, input.reason, input.entityType, input.entityId],
|
||||
)
|
||||
}
|
||||
|
||||
export async function listPendingAdminNotifications(
|
||||
limit: number,
|
||||
): Promise<AdminNotificationRow[]> {
|
||||
const result = await query<AdminNotificationRow>(
|
||||
`
|
||||
SELECT *
|
||||
FROM admin_notifications
|
||||
WHERE status = 'pending' AND visible = TRUE
|
||||
ORDER BY
|
||||
CASE priority WHEN 'urgent' THEN 3 WHEN 'high' THEN 2 ELSE 1 END DESC,
|
||||
updated_at DESC,
|
||||
id DESC
|
||||
LIMIT $1
|
||||
`,
|
||||
[limit],
|
||||
)
|
||||
return result.rows
|
||||
}
|
||||
|
||||
export async function countPendingAdminNotifications(): Promise<number> {
|
||||
const result = await query<{ total: number }>(
|
||||
`SELECT COUNT(*)::int AS total FROM admin_notifications WHERE status = 'pending' AND visible = TRUE`,
|
||||
)
|
||||
return Number(result.rows[0]?.total || 0)
|
||||
}
|
||||
@@ -221,6 +221,8 @@ export type ListInput = {
|
||||
status?: string
|
||||
/** 支持多个状态同时筛选(空数组表示全部状态) */
|
||||
statuses?: string[]
|
||||
/** 后台待办跳转时按工单主键精确定位 */
|
||||
workOrderId?: number
|
||||
keyword?: string
|
||||
workerId?: number
|
||||
categoryId?: number
|
||||
@@ -248,6 +250,8 @@ export type WalletLedgerListInput = {
|
||||
export type FinanceRequestListInput = {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
/** 后台待办跳转时按资金申请主键精确定位 */
|
||||
requestId?: number
|
||||
workerId?: number
|
||||
status?: string
|
||||
requestType?: string
|
||||
|
||||
@@ -767,6 +767,7 @@ export async function listWorkOrders({
|
||||
pageSize = 20,
|
||||
statuses = [],
|
||||
status = '',
|
||||
workOrderId = 0,
|
||||
keyword = '',
|
||||
workerId = 0,
|
||||
categoryId = 0,
|
||||
@@ -779,6 +780,7 @@ export async function listWorkOrders({
|
||||
const effectiveStatuses = statuses.length > 0 ? statuses : status.trim() ? [status.trim()] : []
|
||||
const { whereClause, params } = buildWorkOrderWhere({
|
||||
statuses: effectiveStatuses,
|
||||
workOrderId,
|
||||
keyword,
|
||||
workerId,
|
||||
categoryId,
|
||||
@@ -2665,6 +2667,7 @@ async function getWorkOrderByIdWithClient(
|
||||
|
||||
function buildWorkOrderWhere({
|
||||
statuses = [],
|
||||
workOrderId = 0,
|
||||
keyword = '',
|
||||
workerId = 0,
|
||||
categoryId = 0,
|
||||
@@ -2694,6 +2697,10 @@ function buildWorkOrderWhere({
|
||||
params.push(normalizedStatuses)
|
||||
filters.push(`wo.status = ANY($${params.length}::text[])`)
|
||||
}
|
||||
if (workOrderId) {
|
||||
params.push(workOrderId)
|
||||
filters.push(`wo.id = $${params.length}`)
|
||||
}
|
||||
if (keyword) {
|
||||
params.push(`%${keyword}%`)
|
||||
filters.push(
|
||||
|
||||
@@ -562,12 +562,14 @@ export async function countWorkerWithdrawRequestsOnDay(
|
||||
export async function listWorkerFinanceRequests({
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
requestId = 0,
|
||||
workerId = 0,
|
||||
status = '',
|
||||
requestType = '',
|
||||
keyword = '',
|
||||
}: FinanceRequestListInput = {}): Promise<{ items: WorkerFinanceRequestRow[]; total: number }> {
|
||||
const { whereClause, params } = buildWorkerFinanceRequestWhere({
|
||||
requestId,
|
||||
workerId,
|
||||
status,
|
||||
requestType,
|
||||
@@ -1018,6 +1020,7 @@ function buildWorkerWalletLedgerWhere({ workerId = 0, ledgerType = '' }: WalletL
|
||||
}
|
||||
|
||||
function buildWorkerFinanceRequestWhere({
|
||||
requestId = 0,
|
||||
workerId = 0,
|
||||
status = '',
|
||||
requestType = '',
|
||||
@@ -1025,6 +1028,10 @@ function buildWorkerFinanceRequestWhere({
|
||||
}: FinanceRequestListInput) {
|
||||
const filters: string[] = []
|
||||
const params: unknown[] = []
|
||||
if (requestId) {
|
||||
params.push(requestId)
|
||||
filters.push(`wfr.id = $${params.length}`)
|
||||
}
|
||||
if (workerId) {
|
||||
params.push(workerId)
|
||||
filters.push(`wfr.worker_id = $${params.length}`)
|
||||
|
||||
Reference in New Issue
Block a user