import { Injectable } from '@nestjs/common'; import { ClsService } from 'nestjs-cls'; import type { CreateOportunidadeDto, UpdateOportunidadeDto, FunilResponse, EtapaFunil, Oportunidade, } from '@sar/api-interface'; type OportRow = { id: number; idCliente: number | null; nomeProspect: string | null; empresaProspect: string | null; titulo: string; etapa: string; valorEstimado: unknown; observacoes: string | null; idPedido: string | null; createdAt: Date; updatedAt: Date; }; type PrismaCtx = { oportunidade: { findMany: (args: object) => Promise; create: (args: object) => Promise; update: (args: object) => Promise; delete: (args: object) => Promise; }; $queryRawUnsafe: (sql: string, ...args: unknown[]) => Promise; }; type ClienteRow = { id_cliente: number; nome: string | null; razao: string | null }; @Injectable() export class FunilService { constructor(private readonly cls: ClsService) {} private ctx(): { prisma: PrismaCtx; idEmpresa: number; codVendedor: number } { const prisma = this.cls.get('prisma') as PrismaCtx; if (!prisma) throw new Error('prisma não disponível no CLS'); const idEmpresa = this.cls.get('idEmpresa') as number; const userId = this.cls.get('userId') as string | undefined; const codVendedor = userId ? parseInt(userId, 10) : 0; return { prisma, idEmpresa, codVendedor }; } private toOp(r: OportRow, nomeMap: Map): Oportunidade { return { id: r.id, idCliente: r.idCliente, nomeProspect: r.nomeProspect, empresaProspect: r.empresaProspect, titulo: r.titulo, etapa: r.etapa as EtapaFunil, valorEstimado: r.valorEstimado != null ? String(r.valorEstimado) : null, observacoes: r.observacoes, idPedido: r.idPedido, createdAt: r.createdAt.toISOString(), updatedAt: r.updatedAt.toISOString(), nomeCliente: r.idCliente ? (nomeMap.get(r.idCliente) ?? null) : null, }; } async listar(): Promise { const { prisma, idEmpresa, codVendedor } = this.ctx(); const rows = await prisma.oportunidade.findMany({ where: { idEmpresa, codVendedor }, orderBy: { updatedAt: 'desc' }, }); const idClientes = [ ...new Set(rows.filter((r) => r.idCliente).map((r) => r.idCliente as number)), ]; const nomeMap = new Map(); if (idClientes.length > 0) { const ids = idClientes.join(','); const clientes = await prisma.$queryRawUnsafe( `SELECT id_cliente, TRIM(nome) AS nome, TRIM(razao) AS razao FROM vw_clientes WHERE id_cliente IN (${ids}) AND id_empresa = ${idEmpresa}`, ); for (const c of clientes) { nomeMap.set(c.id_cliente, c.razao ?? c.nome ?? `Cód. ${c.id_cliente}`); } } const byEtapa = (etapa: EtapaFunil) => rows.filter((r) => r.etapa === etapa).map((r) => this.toOp(r, nomeMap)); return { lead: byEtapa('lead'), proposta: byEtapa('proposta'), negociacao: byEtapa('negociacao'), ganho: byEtapa('ganho'), perdido: byEtapa('perdido'), }; } async criar(dto: CreateOportunidadeDto): Promise { const { prisma, idEmpresa, codVendedor } = this.ctx(); const row = await prisma.oportunidade.create({ data: { idEmpresa, codVendedor, idCliente: dto.idCliente ?? null, nomeProspect: dto.nomeProspect ?? null, empresaProspect: dto.empresaProspect ?? null, titulo: dto.titulo, etapa: dto.etapa ?? 'lead', valorEstimado: dto.valorEstimado ?? null, observacoes: dto.observacoes ?? null, }, }); return this.toOp(row, new Map()); } async atualizar(id: number, dto: UpdateOportunidadeDto): Promise { const { prisma, idEmpresa, codVendedor } = this.ctx(); const row = await prisma.oportunidade.update({ where: { id, idEmpresa, codVendedor }, data: { ...(dto.idCliente !== undefined && { idCliente: dto.idCliente }), ...(dto.nomeProspect !== undefined && { nomeProspect: dto.nomeProspect }), ...(dto.empresaProspect !== undefined && { empresaProspect: dto.empresaProspect }), ...(dto.titulo !== undefined && { titulo: dto.titulo }), ...(dto.etapa !== undefined && { etapa: dto.etapa }), ...(dto.valorEstimado !== undefined && { valorEstimado: dto.valorEstimado }), ...(dto.observacoes !== undefined && { observacoes: dto.observacoes }), ...(dto.idPedido !== undefined && { idPedido: dto.idPedido }), }, }); return this.toOp(row, new Map()); } async deletar(id: number): Promise { const { prisma, idEmpresa, codVendedor } = this.ctx(); await prisma.oportunidade.delete({ where: { id, idEmpresa, codVendedor } }); } }