import { ConflictException, Injectable, NotFoundException } from '@nestjs/common'; import { Prisma } from '@prisma/client'; import { PrismaService } from '../prisma/prisma.service'; import { CreateJobDto, QueryJobsDto, UpdateJobDto } from './dto/jobs.dto'; const JOB_SELECT = { id: true, ticketNumber: true, title: true, description: true, address: true, status: true, source: true, dueAt: true, startedAt: true, completedAt: true, createdAt: true, updatedAt: true, assignedTo: { select: { id: true, name: true, email: true } }, createdBy: { select: { id: true, name: true, email: true } }, createdByDeviceId: true, _count: { select: { points: true } }, } satisfies Prisma.JobSelect; @Injectable() export class JobsService { constructor(private readonly prisma: PrismaService) {} async list(orgId: string, query: QueryJobsDto) { const where: Prisma.JobWhereInput = { orgId, ...(query.status && { status: query.status }), ...(query.assignedToId && { assignedToId: query.assignedToId }), ...(query.q && { OR: [ { ticketNumber: { contains: query.q, mode: 'insensitive' } }, { title: { contains: query.q, mode: 'insensitive' } }, { address: { contains: query.q, mode: 'insensitive' } }, ], }), }; const [jobs, total] = await this.prisma.$transaction([ this.prisma.job.findMany({ where, select: JOB_SELECT, orderBy: { createdAt: 'desc' }, take: query.limit ?? 50, skip: query.offset ?? 0, }), this.prisma.job.count({ where }), ]); return { jobs, total }; } async get(orgId: string, jobId: string) { const job = await this.prisma.job.findFirst({ where: { id: jobId, orgId }, select: JOB_SELECT, }); if (!job) { throw new NotFoundException('Job not found'); } return job; } async create(orgId: string, dto: CreateJobDto, createdById: string | null) { const existing = await this.prisma.job.findUnique({ where: { orgId_ticketNumber: { orgId, ticketNumber: dto.ticketNumber } }, }); if (existing) { throw new ConflictException(`Ticket ${dto.ticketNumber} already exists in this organization`); } return this.prisma.job.create({ data: { orgId, ticketNumber: dto.ticketNumber, title: dto.title, description: dto.description, address: dto.address, status: dto.status, assignedToId: dto.assignedToId, dueAt: dto.dueAt ? new Date(dto.dueAt) : undefined, createdById, }, select: JOB_SELECT, }); } async update(orgId: string, jobId: string, dto: UpdateJobDto) { await this.get(orgId, jobId); const statusTimestamps: Prisma.JobUncheckedUpdateInput = dto.status === 'IN_PROGRESS' ? { startedAt: new Date() } : dto.status === 'COMPLETED' ? { completedAt: new Date() } : {}; return this.prisma.job.update({ where: { id: jobId }, data: { ...(dto.title !== undefined && { title: dto.title }), ...(dto.description !== undefined && { description: dto.description }), ...(dto.address !== undefined && { address: dto.address }), ...(dto.status !== undefined && { status: dto.status }), ...(dto.assignedToId !== undefined && { assignedToId: dto.assignedToId }), ...(dto.dueAt !== undefined && { dueAt: dto.dueAt ? new Date(dto.dueAt) : null }), ...statusTimestamps, }, select: JOB_SELECT, }); } async remove(orgId: string, jobId: string) { await this.get(orgId, jobId); await this.prisma.job.delete({ where: { id: jobId } }); return { ok: true }; } }