import { Injectable, NotFoundException } from '@nestjs/common'; import { CrmPrismaService } from '../prisma/crm-prisma.service'; import { CreateDealDto } from './dto/create-deal.dto'; import { UpdateDealDto } from './dto/update-deal.dto'; import { QueryDealsDto } from './dto/query-deals.dto'; import { Prisma } from '.prisma/crm-client'; @Injectable() export class DealsService { constructor(private readonly prisma: CrmPrismaService) {} async create(tenantId: string, userId: string, dto: CreateDealDto) { // Pipeline und Stage validieren const pipeline = await this.prisma.pipeline.findFirst({ where: { id: dto.pipelineId, tenantId }, }); if (!pipeline) { throw new NotFoundException('Pipeline nicht gefunden'); } const stage = await this.prisma.pipelineStage.findFirst({ where: { id: dto.stageId, pipelineId: dto.pipelineId }, }); if (!stage) { throw new NotFoundException('Pipeline-Stufe nicht gefunden'); } // Kontakt validieren (optional) if (dto.contactId) { const contact = await this.prisma.contact.findFirst({ where: { id: dto.contactId, tenantId }, }); if (!contact) { throw new NotFoundException('Kontakt nicht gefunden'); } } // Unternehmen validieren (optional) if (dto.companyId) { const company = await this.prisma.company.findFirst({ where: { id: dto.companyId, tenantId }, }); if (!company) { throw new NotFoundException('Unternehmen nicht gefunden'); } } return this.prisma.deal.create({ data: { tenantId, pipelineId: dto.pipelineId, stageId: dto.stageId, contactId: dto.contactId, companyId: dto.companyId, title: dto.title, value: dto.value, currency: dto.currency ?? 'EUR', status: dto.status ?? 'OPEN', expectedCloseDate: dto.expectedCloseDate ? new Date(dto.expectedCloseDate) : undefined, notes: dto.notes, createdBy: userId, }, include: { pipeline: { select: { id: true, name: true } }, stage: { select: { id: true, name: true, color: true } }, contact: { select: { id: true, firstName: true, lastName: true, companyName: true, }, }, company: { select: { id: true, name: true, }, }, }, }); } async findAll(tenantId: string, query: QueryDealsDto) { const page = query.page ?? 1; const pageSize = query.pageSize ?? 25; const where: Prisma.DealWhereInput = { tenantId }; if (query.pipelineId) { where.pipelineId = query.pipelineId; } if (query.stageId) { where.stageId = query.stageId; } if (query.contactId) { where.contactId = query.contactId; } if (query.companyId) { where.companyId = query.companyId; } if (query.status) { where.status = query.status; } if (query.search) { where.title = { contains: query.search, mode: 'insensitive' }; } const allowedSortFields = [ 'createdAt', 'updatedAt', 'title', 'value', 'expectedCloseDate', ]; const sortField = allowedSortFields.includes(query.sort ?? '') ? (query.sort as string) : 'createdAt'; const [data, total] = await Promise.all([ this.prisma.deal.findMany({ where, skip: (page - 1) * pageSize, take: pageSize, orderBy: { [sortField]: query.order ?? 'desc' }, include: { pipeline: { select: { id: true, name: true } }, stage: { select: { id: true, name: true, color: true } }, contact: { select: { id: true, firstName: true, lastName: true, companyName: true, }, }, company: { select: { id: true, name: true, }, }, }, }), this.prisma.deal.count({ where }), ]); return { data, total, page, pageSize }; } async findOne(tenantId: string, id: string) { const deal = await this.prisma.deal.findFirst({ where: { id, tenantId }, include: { pipeline: { include: { stages: { orderBy: { sortOrder: 'asc' } } } }, stage: true, contact: true, company: true, dealVouchers: { include: { voucher: { select: { id: true, voucherType: true, voucherNumber: true, voucherDate: true, voucherStatus: true, totalGrossAmount: true, currency: true, title: true, lexwareDeepLink: true, }, }, }, orderBy: { linkedAt: 'desc' }, }, }, }); if (!deal) { throw new NotFoundException('Vorgang nicht gefunden'); } return deal; } async update( tenantId: string, id: string, userId: string, dto: UpdateDealDto, ) { await this.findOne(tenantId, id); // Stage validieren wenn geaendert if (dto.stageId) { const deal = await this.prisma.deal.findUnique({ where: { id } }); const pipelineId = dto.pipelineId ?? deal?.pipelineId; const stage = await this.prisma.pipelineStage.findFirst({ where: { id: dto.stageId, pipelineId }, }); if (!stage) { throw new NotFoundException('Pipeline-Stufe nicht gefunden'); } } const updateData: Prisma.DealUpdateInput = { ...dto, expectedCloseDate: dto.expectedCloseDate ? new Date(dto.expectedCloseDate) : undefined, updatedBy: userId, }; // Wenn Deal gewonnen/verloren, closedAt setzen if (dto.status === 'WON' || dto.status === 'LOST') { updateData.closedAt = new Date(); } return this.prisma.deal.update({ where: { id }, data: updateData, include: { pipeline: { select: { id: true, name: true } }, stage: { select: { id: true, name: true, color: true } }, contact: { select: { id: true, firstName: true, lastName: true, companyName: true, }, }, }, }); } async remove(tenantId: string, id: string) { await this.findOne(tenantId, id); return this.prisma.deal.delete({ where: { id } }); } }