完善接单平台通知配置

This commit is contained in:
yml2213
2026-08-17 13:54:01 +08:00
parent bca2e4a801
commit 5330b55b75
27 changed files with 1390 additions and 10 deletions
@@ -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}`)