All files / src/lib/support notification-utils.ts

0% Statements 0/286
100% Branches 0/0
0% Functions 0/1
0% Lines 0/286

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             

/**
 * Support Notification Utility Functions
 */

import { prisma } from '@/lib/prisma';
import { TicketStatus, TicketPriority, SupportTicketWithRelations } from '@/types/support';
import { SUPPORT_NOTIFICATION_TYPES, TICKET_STATUS_CONFIG, TICKET_PRIORITY_CONFIG } from '@/constants/support';

interface NotificationData {
  userId: number;
  type: string;
  title: string;
  message: string;
  link?: string;
}

/**
 * Create a notification in the database
 */
async function createNotification(data: NotificationData): Promise<void> {
  await prisma.notification.create({
    data: {
      userId: data.userId,
      type: data.type,
      title: data.title,
      message: data.message,
      link: data.link}});
}

/**
 * Notify customer when their ticket is created
 */
export async function notifyTicketCreated(
  ticket: SupportTicketWithRelations
): Promise<void> {
  if (!ticket.userId) {
    return; // Guest ticket, no notification needed
  }

  await createNotification({
    userId: ticket.userId,
    type: SUPPORT_NOTIFICATION_TYPES.TICKET_CREATED,
    title: 'Support Ticket Created',
    message: `Your ticket #${ticket.ticketNumber} has been created. Our team will respond shortly.`,
    link: `/support/tickets/${ticket.id}`});
}

/**
 * Notify customer when their ticket status changes
 */
export async function notifyTicketStatusChanged(
  ticket: SupportTicketWithRelations,
  oldStatus: TicketStatus,
  newStatus: TicketStatus
): Promise<void> {
  if (!ticket.userId) {
    return;
  }

  const statusConfig = TICKET_STATUS_CONFIG[newStatus];

  await createNotification({
    userId: ticket.userId,
    type: SUPPORT_NOTIFICATION_TYPES.TICKET_UPDATED,
    title: 'Ticket Status Updated',
    message: `Your ticket #${ticket.ticketNumber} status has been updated to "${statusConfig.label}".`,
    link: `/support/tickets/${ticket.id}`});
}

/**
 * Notify customer when they receive a new message on their ticket
 */
export async function notifyNewMessage(
  ticket: SupportTicketWithRelations,
  senderName: string,
  isInternal: boolean = false
): Promise<void> {
  // Don't notify for internal messages
  if (isInternal) {
    return;
  }

  if (!ticket.userId) {
    return;
  }

  await createNotification({
    userId: ticket.userId,
    type: SUPPORT_NOTIFICATION_TYPES.MESSAGE_RECEIVED,
    title: 'New Message on Your Ticket',
    message: `${senderName} replied to your ticket #${ticket.ticketNumber}.`,
    link: `/support/tickets/${ticket.id}`});
}

/**
 * Notify customer when their ticket is resolved
 */
export async function notifyTicketResolved(
  ticket: SupportTicketWithRelations
): Promise<void> {
  if (!ticket.userId) {
    return;
  }

  await createNotification({
    userId: ticket.userId,
    type: SUPPORT_NOTIFICATION_TYPES.TICKET_RESOLVED,
    title: 'Ticket Resolved',
    message: `Your ticket #${ticket.ticketNumber} has been resolved. Please let us know if you need further assistance.`,
    link: `/support/tickets/${ticket.id}`});
}

/**
 * Notify agent when they are assigned a ticket
 */
export async function notifyAgentAssigned(
  ticket: SupportTicketWithRelations,
  agentId: number
): Promise<void> {
  const priorityConfig = TICKET_PRIORITY_CONFIG[ticket.priority];

  await createNotification({
    userId: agentId,
    type: SUPPORT_NOTIFICATION_TYPES.TICKET_ASSIGNED,
    title: 'New Ticket Assigned',
    message: `You have been assigned ticket #${ticket.ticketNumber} (${priorityConfig.label} priority): "${ticket.subject}"`,
    link: `/admin/support/tickets/${ticket.id}`});
}

/**
 * Notify agent when customer replies to their assigned ticket
 */
export async function notifyAgentOfCustomerReply(
  ticket: SupportTicketWithRelations,
  customerName: string
): Promise<void> {
  if (!ticket.assignedToId) {
    return;
  }

  await createNotification({
    userId: ticket.assignedToId,
    type: SUPPORT_NOTIFICATION_TYPES.MESSAGE_RECEIVED,
    title: 'Customer Reply',
    message: `${customerName} replied to ticket #${ticket.ticketNumber}: "${ticket.subject}"`,
    link: `/admin/support/tickets/${ticket.id}`});
}

/**
 * Notify all admins of high/urgent priority tickets
 */
export async function notifyAdminsOfUrgentTicket(
  ticket: SupportTicketWithRelations
): Promise<void> {
  // Only notify for HIGH or URGENT priority
  if (ticket.priority !== TicketPriority.HIGH && ticket.priority !== TicketPriority.URGENT) {
    return;
  }

  // Get all admin users
  const admins = await prisma.user.findMany({
    where: { role: 'ADMIN' },
    select: { id: true }});

  const priorityConfig = TICKET_PRIORITY_CONFIG[ticket.priority];

  // Create notification for each admin
  await Promise.all(
    admins.map((admin) =>
      createNotification({
        userId: admin.id,
        type: SUPPORT_NOTIFICATION_TYPES.TICKET_CREATED,
        title: `${priorityConfig.label} Priority Ticket`,
        message: `New ${priorityConfig.label.toLowerCase()} priority ticket #${ticket.ticketNumber}: "${ticket.subject}"`,
        link: `/admin/support/tickets/${ticket.id}`})
    )
  );
}

/**
 * Get unread support notification count for a user
 */
export async function getUnreadSupportNotificationCount(userId: number): Promise<number> {
  const count = await prisma.notification.count({
    where: {
      userId,
      read: false,
      type: {
        in: Object.values(SUPPORT_NOTIFICATION_TYPES)}}});

  return count;
}

/**
 * Mark all support notifications as read for a user
 */
export async function markSupportNotificationsAsRead(userId: number): Promise<void> {
  await prisma.notification.updateMany({
    where: {
      userId,
      read: false,
      type: {
        in: Object.values(SUPPORT_NOTIFICATION_TYPES)}},
    data: {
      read: true}});
}

/**
 * Send SLA warning notification to agent
 */
export async function notifySLAWarning(
  ticket: SupportTicketWithRelations,
  type: 'firstResponse' | 'resolution',
  hoursRemaining: number
): Promise<void> {
  if (!ticket.assignedToId) {
    return;
  }

  const warningType = type === 'firstResponse' ? 'First Response' : 'Resolution';

  await createNotification({
    userId: ticket.assignedToId,
    type: SUPPORT_NOTIFICATION_TYPES.TICKET_UPDATED,
    title: 'SLA Warning',
    message: `${warningType} SLA for ticket #${ticket.ticketNumber} expires in ${hoursRemaining} hour(s).`,
    link: `/admin/support/tickets/${ticket.id}`});
}

/**
 * Send SLA breach notification to agent and admins
 */
export async function notifySLABreach(
  ticket: SupportTicketWithRelations,
  type: 'firstResponse' | 'resolution'
): Promise<void> {
  const warningType = type === 'firstResponse' ? 'First Response' : 'Resolution';
  const message = `${warningType} SLA has been breached for ticket #${ticket.ticketNumber}: "${ticket.subject}"`;

  // Notify assigned agent
  if (ticket.assignedToId) {
    await createNotification({
      userId: ticket.assignedToId,
      type: SUPPORT_NOTIFICATION_TYPES.TICKET_UPDATED,
      title: 'SLA Breach',
      message,
      link: `/admin/support/tickets/${ticket.id}`});
  }

  // Notify all admins
  const admins = await prisma.user.findMany({
    where: { role: 'ADMIN' },
    select: { id: true }});

  await Promise.all(
    admins
      .filter((admin) => admin.id !== ticket.assignedToId) // Don't duplicate for assigned agent
      .map((admin) =>
        createNotification({
          userId: admin.id,
          type: SUPPORT_NOTIFICATION_TYPES.TICKET_UPDATED,
          title: 'SLA Breach Alert',
          message,
          link: `/admin/support/tickets/${ticket.id}`})
      )
  );
}

/**
 * Notify customer to complete satisfaction survey
 */
export async function notifySurveyRequest(
  ticket: SupportTicketWithRelations
): Promise<void> {
  if (!ticket.userId) {
    return;
  }

  await createNotification({
    userId: ticket.userId,
    type: SUPPORT_NOTIFICATION_TYPES.TICKET_RESOLVED,
    title: 'How Was Your Experience?',
    message: `Your ticket #${ticket.ticketNumber} has been resolved. We'd love to hear your feedback!`,
    link: `/support/tickets/${ticket.id}?survey=true`});
}