initial: standalone repo from monorepo split
This commit is contained in:
186
src/ticket/ticket.service.ts
Normal file
186
src/ticket/ticket.service.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
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';
|
||||
import { NotificationService } from '../notification/notification.service';
|
||||
|
||||
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,
|
||||
private readonly notificationService: NotificationService,
|
||||
) {}
|
||||
|
||||
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 }),
|
||||
},
|
||||
}).then(async (ticket) => {
|
||||
// Notify on status change
|
||||
if (dto.status) {
|
||||
this.notificationService.create(tenantId, {
|
||||
type: 'in_app',
|
||||
channel: 'ticket_update',
|
||||
title: 'Ticket Updated',
|
||||
message: `Ticket "${existing.title}" status changed to ${dto.status.replace(/_/g, ' ')}`,
|
||||
}).catch(() => {});
|
||||
}
|
||||
return ticket;
|
||||
});
|
||||
}
|
||||
|
||||
async resolve(tenantId: string, id: string, resolvedById: string, coords?: { latitude?: number; longitude?: number }) {
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
// Update client coordinates if this is an installation ticket with coords
|
||||
if (coords?.latitude !== undefined && coords?.longitude !== undefined && ticket.clientId && ticket.type === 'installation') {
|
||||
await this.prisma.client.update({
|
||||
where: { id: ticket.clientId },
|
||||
data: { latitude: coords.latitude, longitude: coords.longitude },
|
||||
});
|
||||
}
|
||||
|
||||
// Fire event for workflow automation
|
||||
if (this.onTicketResolved && ticket.clientId) {
|
||||
await this.onTicketResolved({
|
||||
ticketId: id,
|
||||
tenantId,
|
||||
clientId: ticket.clientId,
|
||||
type: ticket.type,
|
||||
});
|
||||
}
|
||||
|
||||
// Notify on ticket resolution
|
||||
this.notificationService.create(tenantId, {
|
||||
type: 'in_app',
|
||||
channel: 'ticket_update',
|
||||
title: 'Ticket Resolved',
|
||||
message: `Ticket "${ticket.title}" has been resolved`,
|
||||
}).catch(() => {});
|
||||
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user