Files
fiberops-api/src/ticket/ticket.service.ts
2026-05-04 13:22:34 +08:00

158 lines
4.4 KiB
TypeScript

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<void>) | 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;
}
}