完善接单平台通知配置

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,211 @@
import {
bumpAdminNotificationReminder,
countPendingAdminNotifications,
createAdminNotification,
getPendingAdminNotificationByDedupeKey,
listPendingAdminNotifications,
resolveAdminNotificationsByEntity,
type AdminNotificationRow,
} from '../../repositories/admin-notification-repo.js'
import { notifyInternalSafely } from '../notification/domain-notifications.js'
import {
getWorkerPlatformNotificationConfig,
type WorkerPlatformNotificationEventKey,
} from '../worker-platform/worker-platform-notification-config-service.js'
const ADMIN_WORKER_PLATFORM_PATH = '/admin/worker-platform'
export async function createWorkerWithdrawAdminNotification(input: {
requestId: number
workerName: string
amountFen: number
channel: string
}) {
return createConfiguredWorkerPlatformNotification('withdraw_requested', {
notificationType: 'worker_withdraw_requested',
priority: 'high',
entityType: 'worker_finance_request',
entityId: input.requestId,
dedupeKey: `worker_withdraw_request:${input.requestId}`,
title: '有新的提现申请',
body: `打手:${input.workerName || '-'};金额:${formatAmount(input.amountFen)};渠道:${formatChannel(input.channel)}`,
actionPath: `${ADMIN_WORKER_PLATFORM_PATH}?tab=finance`,
now: new Date().toISOString(),
})
}
export async function createWorkerRechargeAdminNotification(input: {
requestId: number
workerName: string
amountFen: number
}) {
return createConfiguredWorkerPlatformNotification('recharge_requested', {
notificationType: 'worker_recharge_requested',
priority: 'normal',
entityType: 'worker_finance_request',
entityId: input.requestId,
dedupeKey: `worker_recharge_request:${input.requestId}`,
title: '有新的充值申请',
body: `打手:${input.workerName || '-'};金额:${formatAmount(input.amountFen)}`,
actionPath: `${ADMIN_WORKER_PLATFORM_PATH}?tab=finance`,
now: new Date().toISOString(),
})
}
export async function createWorkOrderMaterialAdminNotification(input: {
workOrderId: number
workOrderNo: string
productName: string
}) {
return createConfiguredWorkerPlatformNotification('material_required', {
notificationType: 'work_order_material_required',
priority: 'normal',
entityType: 'work_order',
entityId: input.workOrderId,
dedupeKey: `work_order_material:${input.workOrderId}`,
title: '有工单待补资料',
body: `订单:${input.workOrderNo || input.workOrderId};商品:${input.productName || '-'}`,
actionPath: `${ADMIN_WORKER_PLATFORM_PATH}?tab=orders`,
now: new Date().toISOString(),
})
}
export async function createWorkerAcceptanceAdminNotification(input: {
workOrderId: number
workOrderNo: string
productName: string
workerName: string
imageCount: number
}) {
return createConfiguredWorkerPlatformNotification('acceptance_submitted', {
notificationType: 'worker_acceptance_submitted',
priority: 'normal',
entityType: 'work_order',
entityId: input.workOrderId,
dedupeKey: acceptanceNotificationKey(input.workOrderId),
title: '有订单待验收',
body: [
`订单:${input.workOrderNo || input.workOrderId}`,
`商品:${input.productName || '-'}`,
`打手:${input.workerName || '-'}`,
`验收图片:${input.imageCount}`,
].join(''),
actionPath: `${ADMIN_WORKER_PLATFORM_PATH}?tab=orders`,
now: new Date().toISOString(),
})
}
export async function remindWorkerAcceptanceAdminNotification(workOrderId: number) {
const now = new Date().toISOString()
const eventConfig = getWorkerPlatformNotificationConfig().events.acceptance_reminded
const notification = await bumpAdminNotificationReminder({
dedupeKey: acceptanceNotificationKey(workOrderId),
notificationType: 'worker_acceptance_reminded',
title: '有打手催验收',
visible: eventConfig.todoEnabled,
soundEnabled: eventConfig.todoEnabled && eventConfig.soundEnabled,
now,
})
if (notification && eventConfig.externalPushEnabled) {
void notifyInternalSafely({
title: '打手催促验收',
body: `${notification.body};第 ${notification.reminder_count} 次催验收`,
category: 'worker_acceptance_reminded',
url: notification.action_path,
cooldownKey: `worker_acceptance_reminded:${workOrderId}:${notification.reminder_count}`,
cooldownMs: 30 * 60 * 1000,
})
}
return notification
}
export async function getWorkerAcceptanceReminder(workOrderId: number) {
return getPendingAdminNotificationByDedupeKey(acceptanceNotificationKey(workOrderId))
}
export async function resolveAdminNotificationEntity(
entityType: string,
entityId: number,
reason: string,
) {
await resolveAdminNotificationsByEntity({
entityType,
entityId,
reason,
now: new Date().toISOString(),
})
}
export async function listAdminPendingNotifications(limit = 20) {
const normalizedLimit = Math.min(50, Math.max(1, Math.floor(Number(limit) || 20)))
const [items, pendingCount] = await Promise.all([
listPendingAdminNotifications(normalizedLimit),
countPendingAdminNotifications(),
])
return {
pendingCount,
items: items.map(mapAdminNotification),
}
}
function mapAdminNotification(row: AdminNotificationRow) {
return {
notificationId: Number(row.id),
notificationType: row.notification_type,
priority: row.priority,
entityType: row.entity_type,
entityId: Number(row.entity_id),
title: row.title,
body: row.body,
actionPath: row.action_path,
reminderCount: Number(row.reminder_count || 0),
soundEnabled: row.sound_enabled !== false,
lastRemindedAt: row.last_reminded_at,
createdAt: row.created_at,
updatedAt: row.updated_at,
}
}
async function createConfiguredWorkerPlatformNotification(
eventKey: WorkerPlatformNotificationEventKey,
input: {
notificationType: string
priority: string
entityType: string
entityId: number
dedupeKey: string
title: string
body: string
actionPath: string
now: string
},
) {
const eventConfig = getWorkerPlatformNotificationConfig().events[eventKey]
const notification = await createAdminNotification({
...input,
visible: eventConfig.todoEnabled,
soundEnabled: eventConfig.todoEnabled && eventConfig.soundEnabled,
})
if (notification && eventConfig.externalPushEnabled) {
void notifyInternalSafely({
title: notification.title,
body: notification.body,
category: notification.notification_type,
url: notification.action_path,
cooldownKey: notification.dedupe_key,
})
}
return notification
}
function acceptanceNotificationKey(workOrderId: number) {
return `work_order_acceptance:${workOrderId}`
}
function formatAmount(amountFen: number) {
return `${(Math.max(0, Number(amountFen) || 0) / 100).toFixed(2)}`
}
function formatChannel(channel: string) {
return channel === 'wechat' ? '微信' : channel === 'alipay' ? '支付宝' : channel || '-'
}