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' import { publishRealtimeEvent } from '../realtime/realtime-event-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 createWorkerCancelRequestAdminNotification(input: { requestId: number workOrderId: number workOrderNo: string productName: string workerName: string reason: string }) { return createConfiguredWorkerPlatformNotification('cancel_requested', { notificationType: 'worker_cancel_requested', priority: 'high', entityType: 'worker_cancel_request', entityId: input.requestId, dedupeKey: `worker_cancel_request:${input.requestId}`, title: '有打手撤单申请待审核', body: [ `订单:${input.workOrderNo || input.workOrderId}`, `商品:${input.productName || '-'}`, `打手:${input.workerName || '-'}`, `原因:${input.reason || '-'}`, ].join(';'), actionPath: `${ADMIN_WORKER_PLATFORM_PATH}?tab=cancel-requests&cancelRequestId=${input.requestId}`, now: new Date().toISOString(), }) } export async function createWorkerOrderFeedbackAdminNotification(input: { feedbackId: number workOrderId: number workOrderNo: string productName: string workerName: string content: string }) { return createConfiguredWorkerPlatformNotification('feedback_submitted', { notificationType: 'worker_order_feedback_submitted', priority: 'high', entityType: 'worker_order_feedback', entityId: input.feedbackId, dedupeKey: `worker_order_feedback:${input.feedbackId}`, title: '有打手问题反馈待处理', body: [ `订单:${input.workOrderNo || input.workOrderId}`, `商品:${input.productName || '-'}`, `打手:${input.workerName || '-'}`, `反馈:${input.content || '-'}`, ].join(';'), actionPath: `${ADMIN_WORKER_PLATFORM_PATH}?tab=feedbacks&feedbackId=${input.feedbackId}`, 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, }) } if (notification?.visible) { publishRealtimeEvent({ type: 'admin_notification.changed', entityId: Number(notification.id), scopes: ['admin_notifications'], operation: 'upsert', data: mapAdminNotification(notification), admin: true, }) } 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(), }) publishRealtimeEvent({ type: 'admin_notification.changed', entityId, scopes: ['admin_notifications'], operation: 'remove', data: { notificationId: entityId }, admin: true, }) } 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), } } export 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, }) } if (notification?.visible) { publishRealtimeEvent({ type: 'admin_notification.changed', entityId: Number(notification.id), scopes: ['admin_notifications'], operation: 'upsert', data: mapAdminNotification(notification), admin: true, }) } 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 || '-' }