import { Injectable, NotFoundException, BadRequestException, } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { CreateTicketDto } from './dto/create-ticket.dto'; import { UpdateTicketDto } from './dto/update-ticket.dto'; export interface TicketResolvedEvent { ticketId: string; tenantId: string; clientId: string | null; type: string; } @Injectable() export class TicketService { // Event handler for ticket resolution — set by SubscriptionService onTicketResolved: ((event: TicketResolvedEvent) => Promise) | null = null; constructor(private readonly prisma: PrismaService) {} async findAll(tenantId: string, filters?: { clientId?: string; status?: string; type?: string }) { const db = this.prisma.forTenant(tenantId); return db.ticket.findMany({ where: { ...(filters?.clientId && { clientId: filters.clientId }), ...(filters?.status && { status: filters.status }), ...(filters?.type && { type: filters.type }), }, include: { client: { select: { id: true, firstName: true, lastName: true, accountNumber: true } }, assignee: { select: { id: true, firstName: true, lastName: true } }, createdBy: { select: { id: true, firstName: true, lastName: true } }, }, orderBy: { createdAt: 'desc' }, }); } async findById(tenantId: string, id: string) { const db = this.prisma.forTenant(tenantId); const ticket = await db.ticket.findFirst({ where: { id }, include: { client: true, assignee: { select: { id: true, firstName: true, lastName: true } }, createdBy: { select: { id: true, firstName: true, lastName: true } }, }, }); if (!ticket) { throw new NotFoundException('Ticket not found'); } return ticket; } async create(tenantId: string, createdById: string, dto: CreateTicketDto) { return this.prisma.ticket.create({ data: { tenantId, clientId: dto.clientId, createdById, assigneeId: dto.assigneeId, type: dto.type, title: dto.title, description: dto.description, priority: dto.priority ?? 'normal', }, }); } async createSystemTicket( tenantId: string, systemUserId: string, data: { clientId: string; type: string; title: string; description?: string; }, ) { return this.prisma.ticket.create({ data: { tenantId, clientId: data.clientId, createdById: systemUserId, type: data.type, title: data.title, description: data.description, priority: 'high', }, }); } async update(tenantId: string, id: string, dto: UpdateTicketDto) { const db = this.prisma.forTenant(tenantId); const existing = await db.ticket.findFirst({ where: { id } }); if (!existing) { throw new NotFoundException('Ticket not found'); } return this.prisma.ticket.update({ where: { id }, data: { ...(dto.assigneeId !== undefined && { assigneeId: dto.assigneeId }), ...(dto.title && { title: dto.title }), ...(dto.description !== undefined && { description: dto.description }), ...(dto.priority && { priority: dto.priority }), ...(dto.status && { status: dto.status }), ...(dto.latitude !== undefined && { latitude: dto.latitude }), ...(dto.longitude !== undefined && { longitude: dto.longitude }), }, }); } async resolve(tenantId: string, id: string, resolvedById: string) { const db = this.prisma.forTenant(tenantId); const ticket = await db.ticket.findFirst({ where: { id } }); if (!ticket) { throw new NotFoundException('Ticket not found'); } if (ticket.status === 'resolved') { throw new BadRequestException('Ticket is already resolved'); } if (ticket.status === 'cancelled') { throw new BadRequestException('Cannot resolve a cancelled ticket'); } const resolved = await this.prisma.ticket.update({ where: { id }, data: { status: 'resolved', resolvedAt: new Date(), assigneeId: resolvedById, }, }); // Fire event for workflow automation if (this.onTicketResolved && ticket.clientId) { await this.onTicketResolved({ ticketId: id, tenantId, clientId: ticket.clientId, type: ticket.type, }); } return resolved; } }