interface ProdutoVenda { id: string; catalogo_id: string; preco_venda: number; preco_final?: number; // ✅ Preço final calculado pela API observacoes?: string; data_validade: string; qtdade_estoque?: number; // ✅ Agora incluído no endpoint unificado empresa_id?: string; // ✅ ID da empresa que está vendendo o produto createdAt: string; updatedAt: string; } interface ProdutoEstoque { id: string; catalogo_id: string; venda_id: string; quantidade: number; createdAt: string; updatedAt: string; } interface ProdutoCatalogo { $id: string; codigo_ean: string; codigo_interno: string; nome: string; descricao?: string; preco_base: number; preco_fabrica?: number; pmc?: number; desconto_comercial: number; laboratorio?: string; categoria?: string; subcategoria?: string; lab_nome?: string; // ✅ Nome do laboratório cat_nome?: string; // ✅ Nome da categoria } interface ProdutoCompleto { id: string; $id?: string; // ✅ Para compatibilidade com Appwrite catalogo_id: string; nome: string; descricao?: string; preco_venda: number; preco_base: number; preco_final?: number; // ✅ Preço final para exibição na listagem quantidade_estoque: number; observacoes?: string; data_validade: string; codigo_ean: string; codigo_interno: string; laboratorio?: string; categoria?: string; lab_nome?: string; // ✅ Nome do laboratório cat_nome?: string; // ✅ Nome da categoria empresa_id?: string; // ✅ ID da empresa que está vendendo o produto } interface ResponseVenda { items: ProdutoVenda[]; total: number; } interface ResponseEstoque { items: ProdutoEstoque[]; total: number; } interface ResponseCatalogo { documents: ProdutoCatalogo[]; total: number; } class ProdutosVendaService { private baseUrl = process.env.NEXT_PUBLIC_BFF_API_URL!; private getAuthHeaders(): Record { const token = localStorage.getItem('access_token'); if (!token) { console.warn("⚠️ Nenhum token de acesso encontrado. Usuário pode não estar autenticado."); } return { "accept": "application/json", "Content-Type": "application/json", ...(token && { "Authorization": `Bearer ${token}` }) }; } /** * Busca dados do usuário logado via endpoint /auth/me * @returns Promise com os dados do usuário ou null se erro */ private async buscarUsuarioLogado(): Promise { try { const token = localStorage.getItem('access_token'); if (!token) { console.error('🔑 Token de acesso não encontrado'); return null; } const response = await fetch(`${this.baseUrl}/auth/me`, { method: 'GET', headers: this.getAuthHeaders(), }); if (!response.ok) { console.error('❌ Erro ao buscar dados do usuário:', response.status, response.statusText); return null; } const userData = await response.json(); return userData; } catch (error) { console.error('❌ Erro exception ao buscar dados do usuário:', error); return null; } } /** * Extrai o IDs de empresa dos dados do usuário * @param userData - Dados do usuário do endpoint /auth/me * @returns Array de string com os IDs das empresas ou array vazio se não encontrado */ private extrairEmpresasIds(userData: any | null): string[] { if (!userData) { return []; } // Buscar o campo empresasDados const empresasDados = userData.empresasDados || userData["empresas-dados"] || userData.empresas_dados; if (Array.isArray(empresasDados)) { return empresasDados; } return []; } async buscarProdutosVenda(page: number = 1): Promise { try { const response = await fetch(`${this.baseUrl}/produtos-venda?page=${page}&limit=100`, { method: "GET", headers: this.getAuthHeaders(), }); if (!response.ok) { if (response.status === 401) { throw new Error("Não autorizado. Por favor, faça login novamente."); } throw new Error(`Erro ao buscar produtos de venda: ${response.status}`); } return await response.json(); } catch (error) { console.error("Erro ao buscar produtos de venda:", error); throw error; } } async buscarProdutoVendaPorId(id: string): Promise { try { const response = await fetch(`${this.baseUrl}/produtos-venda/${id}`, { method: "GET", headers: this.getAuthHeaders(), }); if (!response.ok) { if (response.status === 404) { console.warn(`⚠️ [ProdutosVenda] Produto de venda não encontrado: ${id}`); return null; } if (response.status === 401) { throw new Error("Não autorizado. Por favor, faça login novamente."); } throw new Error(`Erro ao buscar produto de venda: ${response.status}`); } const produto = await response.json(); return produto; } catch (error) { console.error(`❌ [ProdutosVenda] Erro ao buscar produto de venda ${id}:`, error); throw error; } } // NOTA: Método removido pois agora usamos apenas o endpoint unificado /produtos-venda para estoque // Mantemos buscarProdutosCatalogo() pois ainda precisamos dos dados de laboratório e categoria async buscarProdutosCatalogo(page: number = 1): Promise { try { const response = await fetch(`${this.baseUrl}/produtos-catalogo?page=${page}`, { method: "GET", headers: this.getAuthHeaders(), }); if (!response.ok) { if (response.status === 401) { throw new Error("Não autorizado. Por favor, faça login novamente."); } throw new Error(`Erro ao buscar produtos do catálogo: ${response.status}`); } return await response.json(); } catch (error) { console.error("Erro ao buscar produtos do catálogo:", error); throw error; } } async buscarProdutosCompletos(page: number = 1): Promise { try { // Buscar dados de venda (que agora inclui estoque) e catálogo em paralelo const [vendaResponse, catalogoResponse] = await Promise.all([ this.buscarProdutosVenda(page), this.buscarProdutosCatalogo(page), ]); // Criar um mapa de produtos do catálogo por id para busca rápida const catalogoMap = new Map(); catalogoResponse.documents.forEach(catalogo => { catalogoMap.set(catalogo.$id, catalogo); }); // Combinar os dados const produtosCompletos: ProdutoCompleto[] = vendaResponse.items.map(venda => { const catalogo = catalogoMap.get(venda.catalogo_id); return { id: venda.id, $id: venda.id, // ✅ Para compatibilidade catalogo_id: venda.catalogo_id, nome: catalogo?.nome || `Produto ${venda.catalogo_id}`, descricao: catalogo?.descricao || venda.observacoes, preco_venda: venda.preco_venda, preco_final: venda.preco_final, // ✅ Incluir preco_final da API preco_base: catalogo?.preco_base || 0, quantidade_estoque: venda.qtdade_estoque || 0, // ✅ Agora vem do endpoint /produtos-venda observacoes: venda.observacoes, data_validade: venda.data_validade, codigo_ean: catalogo?.codigo_ean || "", codigo_interno: catalogo?.codigo_interno || "", laboratorio: catalogo?.laboratorio, categoria: catalogo?.categoria, lab_nome: catalogo?.lab_nome, // ✅ Incluir lab_nome do catálogo cat_nome: catalogo?.cat_nome, // ✅ Incluir cat_nome do catálogo empresa_id: venda.empresa_id, // ✅ Incluir empresa_id do produto de venda }; }); return produtosCompletos; } catch (error) { console.error("❌ Erro ao buscar produtos completos:", error); throw error; } } /** * Busca produtos completos excluindo os da própria empresa do usuário logado * @param page - Número da página * @returns Promise - Lista de produtos filtrados */ async buscarProdutosCompletosParaCompra(page: number = 1): Promise { try { // Buscar dados do usuário logado em paralelo com os produtos const [produtosCompletos, usuarioLogado] = await Promise.all([ this.buscarProdutosCompletos(page), this.buscarUsuarioLogado() ]); // Se não conseguir buscar dados do usuário, retornar todos os produtos if (!usuarioLogado) { console.warn("⚠️ Não foi possível buscar dados do usuário, retornando todos os produtos"); return produtosCompletos; } // Extrair IDs das empresas do usuário const empresasUsuario = this.extrairEmpresasIds(usuarioLogado); if (empresasUsuario.length === 0) { console.warn("⚠️ Usuário não possui empresas associadas, retornando todos os produtos"); return produtosCompletos; } // Filtrar produtos que NÃO são da empresa do usuário const produtosFiltrados = produtosCompletos.filter(produto => { // Se o produto não tem empresa_id, manter na lista if (!produto.empresa_id) { return true; } // Excluir produtos das empresas do usuário const isProdutoDaPropriaEmpresa = empresasUsuario.includes(produto.empresa_id); return !isProdutoDaPropriaEmpresa; }); return produtosFiltrados; } catch (error) { console.error("❌ Erro ao buscar produtos para compra:", error); // Em caso de erro, retornar produtos sem filtro para não quebrar a aplicação console.warn("⚠️ Retornando produtos sem filtro devido ao erro"); return this.buscarProdutosCompletos(page); } } async buscarTodosProdutosCompletos(): Promise { try { let todosProdutos: ProdutoCompleto[] = []; let page = 1; let hasMore = true; while (hasMore) { const produtosPagina = await this.buscarProdutosCompletosParaCompra(page); if (produtosPagina.length > 0) { todosProdutos.push(...produtosPagina); page++; // Se retornou menos que o esperado, provavelmente é a última página if (produtosPagina.length < 20) { // assumindo que API retorna ~20 por página hasMore = false; } } else { hasMore = false; } } return todosProdutos; } catch (error) { console.error("❌ Erro ao buscar todos os produtos:", error); throw error; } } async buscarProdutosPorNome(nome: string, page: number = 1): Promise { try { // Por enquanto, vamos buscar todos os produtos e filtrar pelo nome // Em uma implementação real, a API deveria suportar filtros const produtos = await this.buscarProdutosCompletosParaCompra(page); if (!nome.trim()) { return produtos; } const termoBusca = nome.toLowerCase().trim(); return produtos.filter(produto => produto.nome.toLowerCase().includes(termoBusca) || produto.descricao?.toLowerCase().includes(termoBusca) || produto.codigo_ean.toLowerCase().includes(termoBusca) || produto.codigo_interno.toLowerCase().includes(termoBusca) ); } catch (error) { console.error("❌ Erro ao buscar produtos por nome:", error); throw error; } } /** * Atualiza a quantidade de estoque de um produto */ async atualizarQuantidadeEstoque(produtoId: string, novaQuantidade: number): Promise<{ success: boolean, data?: any, error?: string }> { try { const response = await fetch(`${this.baseUrl}/produtos-venda/${produtoId}`, { method: "PUT", headers: this.getAuthHeaders(), body: JSON.stringify({ qtdade_estoque: novaQuantidade }), }); if (!response.ok) { if (response.status === 401) { throw new Error("Não autorizado. Por favor, faça login novamente."); } const errorData = await response.json(); throw new Error(`Erro ao atualizar estoque: ${errorData.message || response.status}`); } const data = await response.json(); return { success: true, data }; } catch (error: any) { console.error(`❌ Erro ao atualizar estoque do produto ${produtoId}:`, error); return { success: false, error: error.message }; } } /** * Reduz a quantidade de estoque de um produto */ async reduzirEstoque(produtoId: string, quantidadeReduzir: number): Promise<{ success: boolean, data?: any, error?: string }> { try { // Primeiro buscar o produto atual para obter a quantidade atual const produtoAtual = await this.buscarProdutoVendaPorId(produtoId); if (!produtoAtual) { return { success: false, error: "Produto não encontrado" }; } // Buscar dados completos para obter quantidade atual de estoque const produtosCompletos = await this.buscarProdutosCompletos(); const produtoCompleto = produtosCompletos.find(p => p.id === produtoId); if (!produtoCompleto) { return { success: false, error: "Produto completo não encontrado" }; } const quantidadeAtual = produtoCompleto.quantidade_estoque; const novaQuantidade = Math.max(0, quantidadeAtual - quantidadeReduzir); return await this.atualizarQuantidadeEstoque(produtoId, novaQuantidade); } catch (error: any) { console.error(`❌ Erro ao reduzir estoque do produto ${produtoId}:`, error); return { success: false, error: error.message }; } } /** * Processa ajuste de estoque para múltiplos produtos (para checkout) */ async processarAjusteEstoquePedido(itens: Array<{ produto: any, quantidade: number }>): Promise<{ sucessos: number, erros: number, detalhes: Array<{ produto: string, sucesso: boolean, erro?: string }> }> { let sucessos = 0; let erros = 0; const detalhes: Array<{ produto: string, sucesso: boolean, erro?: string }> = []; for (const item of itens) { try { const produtoId = item.produto.id; const quantidadeReduzir = item.quantidade; const resultado = await this.reduzirEstoque(produtoId, quantidadeReduzir); if (resultado.success) { sucessos++; detalhes.push({ produto: item.produto.nome, sucesso: true }); } else { erros++; detalhes.push({ produto: item.produto.nome, sucesso: false, erro: resultado.error }); console.error(`❌ Erro ao ajustar estoque: ${item.produto.nome} - ${resultado.error}`); } } catch (error: any) { erros++; detalhes.push({ produto: item.produto.nome, sucesso: false, erro: error.message }); console.error(`💥 Erro ao processar estoque do produto ${item.produto.nome}:`, error); } } return { sucessos, erros, detalhes }; } } export const produtosVendaService = new ProdutosVendaService(); export type { ProdutoCompleto, ProdutoVenda, ProdutoEstoque, ProdutoCatalogo };