/** * Product Service * Serviço para gerenciar operações relacionadas a produtos, departamentos e filtros */ import { env } from '../config/env'; import { authService } from './auth.service'; const API_URL = env.API_URL; export interface Categoria { codigoSecao: number; codigoCategoria: number; descricaoCategoria: string; tituloECommerce: string; url: string; } export interface Secao { codigoSecao: number; codigoDepartamento: number; descricaoSecao: string; tituloEcommerce: string; url: string; categorias: Categoria[]; } export interface ClasseMercadologica { codigoDepartamento: number; descricaoDepartamento: string; tituloEcommerce: string; url: string; secoes: Secao[]; } export interface FilterProduct { brands?: string[]; text?: string; urlCategory?: string; outLine?: boolean; campaign?: boolean; onlyWithStock?: boolean; promotion?: boolean; oportunity?: boolean; markdown?: boolean; productPromotion?: boolean; offers?: boolean; storeStock?: string; orderBy?: string; percentOffMin?: number; percentOffMax?: number; } export interface SaleProduct { id: number; brand: string; category: string; [key: string]: any; } export interface StoreERP { id: string; name: string; shortName: string; } class ProductService { /** * Obtém o token de autenticação */ private getAuthHeaders(): HeadersInit { const token = authService.getToken(); const headers: HeadersInit = { 'Content-Type': 'application/json', }; if (token) { headers['Authorization'] = `Bearer ${token}`; } return headers; } /** * Obtém os headers com informações da loja */ private getStoreHeaders(store?: string): HeadersInit { const headers = this.getAuthHeaders(); const storeName = store || authService.getStore() || ''; if (!storeName || storeName.trim() === '') { throw new Error('Loja não informada. É necessário informar a loja para buscar produtos.'); } headers['x-store'] = storeName; return headers; } /** * Carrega as classes mercadológicas (departamentos, seções e categorias) */ async getClasseMercadologica(): Promise { try { const response = await fetch(`${API_URL}sales/departments`, { method: 'GET', headers: this.getAuthHeaders(), }); if (!response.ok) { throw new Error(`Erro ao carregar departamentos: ${response.statusText}`); } const data: ClasseMercadologica[] = await response.json(); return data; } catch (error) { console.error('Erro ao carregar departamentos:', error); throw error; } } /** * Busca produtos com filtros */ async getProductByFilter( store: string, page: number, size: number, filterProduct?: FilterProduct ): Promise { try { // Validar se a loja foi fornecida if (!store || store.trim() === '') { throw new Error('Loja não informada. É necessário informar a loja para buscar produtos.'); } const headers = this.getStoreHeaders(store); headers['x-page'] = page.toString(); headers['x-size-count'] = size.toString(); let response: Response; const url = `${API_URL}sales/products`; if (filterProduct) { response = await fetch(url, { method: 'POST', headers, body: JSON.stringify(filterProduct), }); } else { // GET sem body, apenas headers response = await fetch(url, { method: 'GET', headers, }); } if (!response.ok) { let errorMessage = response.statusText; let errorDetails: any = null; try { errorDetails = await response.json(); errorMessage = errorDetails?.message || errorDetails?.error || errorMessage; } catch { // Se não conseguir parsear o JSON, usa a mensagem padrão } console.error('Erro na API:', { status: response.status, statusText: response.statusText, errorDetails, }); throw new Error(`Erro ao buscar produtos: ${errorMessage}`); } let result: any; try { result = await response.json(); } catch (error) { console.error('Erro ao parsear resposta JSON:', error); throw new Error('Resposta da API não é um JSON válido'); } // A API pode retornar diretamente o array ou dentro de um wrapper { data: [...] } let data: SaleProduct[] = []; // Verificar se result existe e é válido if (!result) { console.warn('Resposta da API é nula ou indefinida'); return []; } if (Array.isArray(result)) { data = result; } else if (result && typeof result === 'object' && result.data !== undefined) { // Verificar se result.data é um array antes de atribuir if (Array.isArray(result.data)) { data = result.data; } else { console.warn('result.data não é um array:', result.data); return []; } } else if (result && typeof result === 'object') { // Se for um objeto mas não tiver data, pode ser um erro ou formato inesperado console.warn('Resposta da API em formato inesperado:', result); return []; } else { console.warn('Resposta da API não é um array ou objeto válido:', result); return []; } return data; } catch (error) { console.error('Erro ao buscar produtos:', error); throw error; } } /** * Busca produtos por departamento/categoria * Segue o padrão do Angular: getProductByDepartment(store, page, size, urlDepartment) * Endpoint: GET /sales/product/category/{urlDepartment} */ async getProductByDepartment( store: string, page: number, size: number, urlDepartment: string ): Promise { try { // Validar se a loja foi fornecida if (!store || store.trim() === '') { throw new Error('Loja não informada. É necessário informar a loja para buscar produtos.'); } const headers = this.getStoreHeaders(store); headers['x-page'] = page.toString(); headers['x-size-count'] = size.toString(); // No Angular, sempre usa 'category/' mesmo que seja department // Se urlDepartment contém '/', já está no formato correto // Caso contrário, usa 'category/' + urlDepartment let url = ''; if (urlDepartment.includes('/')) { url = `category/${urlDepartment}`; } else { url = `category/${urlDepartment}`; } const response = await fetch(`${API_URL}sales/product/${url}`, { method: 'GET', headers, }); if (!response.ok) { let errorMessage = response.statusText; let errorDetails: any = null; try { errorDetails = await response.json(); errorMessage = errorDetails?.message || errorDetails?.error || errorMessage; } catch { // Se não conseguir parsear o JSON, usa a mensagem padrão } console.error('Erro na API:', { status: response.status, statusText: response.statusText, errorDetails, }); throw new Error(`Erro ao buscar produtos por departamento: ${errorMessage}`); } let result: any; try { result = await response.json(); } catch (error) { console.error('Erro ao parsear resposta JSON:', error); throw new Error('Resposta da API não é um JSON válido'); } // A API pode retornar diretamente o array ou dentro de um wrapper let data: SaleProduct[] = []; if (!result) { console.warn('Resposta da API é nula ou indefinida'); return []; } if (Array.isArray(result)) { data = result; } else if (result && typeof result === 'object' && result.data !== undefined) { if (Array.isArray(result.data)) { data = result.data; } else { console.warn('result.data não é um array:', result.data); return []; } } else if (result && typeof result === 'object') { console.warn('Resposta da API em formato inesperado:', result); return []; } else { console.warn('Resposta da API não é um array ou objeto válido:', result); return []; } return data; } catch (error) { console.error('Erro ao buscar produtos por departamento:', error); throw error; } } /** * Busca produtos por termo de pesquisa */ async searchProduct( store: string, page: number, size: number, search: string ): Promise { try { const headers = this.getStoreHeaders(store); headers['x-page'] = page.toString(); headers['x-size-count'] = size.toString(); const response = await fetch(`${API_URL}sales/products/${encodeURIComponent(search)}`, { method: 'GET', headers, }); if (!response.ok) { let errorMessage = response.statusText; let errorDetails: any = null; try { errorDetails = await response.json(); errorMessage = errorDetails?.message || errorDetails?.error || errorMessage; } catch { // Se não conseguir parsear o JSON, usa a mensagem padrão } console.error('Erro na API:', { status: response.status, statusText: response.statusText, errorDetails, }); throw new Error(`Erro ao buscar produtos: ${errorMessage}`); } let result: any; try { result = await response.json(); } catch (error) { console.error('Erro ao parsear resposta JSON:', error); throw new Error('Resposta da API não é um JSON válido'); } // A API pode retornar diretamente o array ou dentro de um wrapper let data: SaleProduct[] = []; if (!result) { console.warn('Resposta da API é nula ou indefinida'); return []; } if (Array.isArray(result)) { data = result; } else if (result && typeof result === 'object' && result.data !== undefined) { if (Array.isArray(result.data)) { data = result.data; } else { console.warn('result.data não é um array:', result.data); return []; } } else if (result && typeof result === 'object') { console.warn('Resposta da API em formato inesperado:', result); return []; } else { console.warn('Resposta da API não é um array ou objeto válido:', result); return []; } return data; } catch (error) { console.error('Erro ao buscar produtos:', error); throw error; } } /** * Extrai marcas únicas de uma lista de produtos */ extractBrandsFromProducts(products: SaleProduct[]): string[] { const brandsSet = new Set(); products.forEach((product) => { if (product.brand) { const brand = product.brand.replace('#', '').trim(); if (brand) { brandsSet.add(brand.toUpperCase()); } } }); return Array.from(brandsSet).sort(); } /** * Carrega as filiais disponíveis para o usuário * Segue o mesmo padrão do Angular: lookupService.getStore() */ async getStores(): Promise { try { const user = authService.getUser(); if (!user || !user.id) { throw new Error('Usuário não encontrado. É necessário estar autenticado.'); } // No Angular: lookupService.getStore() chama lists/store/user/${this.authService.getUser()} // onde getUser() retorna apenas o ID do usuário const userId = user.id; const response = await fetch(`${API_URL}lists/store/user/${userId}`, { method: 'GET', headers: this.getAuthHeaders(), }); if (!response.ok) { const errorData = await response.json().catch(() => ({ message: response.statusText })); throw new Error(`Erro ao carregar filiais: ${errorData.message || response.statusText}`); } // A API pode retornar diretamente o array ou dentro de um wrapper const result = await response.json(); // Se retornar dentro de um wrapper { data: [...] }, extrair o data // Caso contrário, retornar diretamente const data: StoreERP[] = Array.isArray(result) ? result : (result.data || result); if (!Array.isArray(data)) { throw new Error('Formato de resposta inválido da API'); } return data; } catch (error) { console.error('Erro ao carregar filiais:', error); throw error; } } /** * Obtém detalhes completos de um produto de venda * Endpoint: GET /sales/product/:id */ async getProductDetail(store: string, id: number): Promise { try { if (!store || store.trim() === "") { throw new Error("Loja não informada. É necessário informar a loja."); } const headers = this.getStoreHeaders(store); const response = await fetch(`${API_URL}sales/product/${id}`, { method: "GET", headers, }); if (!response.ok) { const errorData = await response.json().catch(() => ({ message: response.statusText })); throw new Error(`Erro ao buscar detalhes do produto: ${errorData.message || response.statusText}`); } const data: SaleProduct = await response.json(); return data; } catch (error) { console.error("Erro ao buscar detalhes do produto:", error); throw error; } } /** * Obtém produtos que compram junto * Endpoint: GET /sales/product/bytogether/:id */ async getProductsBuyTogether(store: string, id: number): Promise { try { if (!store || store.trim() === "") { throw new Error("Loja não informada. É necessário informar a loja."); } const headers = this.getStoreHeaders(store); const response = await fetch(`${API_URL}sales/product/bytogether/${id}`, { method: "GET", headers, }); if (!response.ok) { const errorData = await response.json().catch(() => ({ message: response.statusText })); throw new Error(`Erro ao buscar produtos compre junto: ${errorData.message || response.statusText}`); } const result = await response.json(); const data: SaleProduct[] = Array.isArray(result) ? result : (result.data || []); return data; } catch (error) { console.error("Erro ao buscar produtos compre junto:", error); throw error; } } /** * Obtém produtos similares * Endpoint: GET /sales/product/simil/:id */ async getProductsSimilar(store: string, id: number): Promise { try { if (!store || store.trim() === "") { throw new Error("Loja não informada. É necessário informar a loja."); } const headers = this.getStoreHeaders(store); const response = await fetch(`${API_URL}sales/product/simil/${id}`, { method: "GET", headers, }); if (!response.ok) { const errorData = await response.json().catch(() => ({ message: response.statusText })); throw new Error(`Erro ao buscar produtos similares: ${errorData.message || response.statusText}`); } const result = await response.json(); const data: SaleProduct[] = Array.isArray(result) ? result : (result.data || []); return data; } catch (error) { console.error("Erro ao buscar produtos similares:", error); throw error; } } /** * Obtém estoques de um produto em todas as filiais * Endpoint: GET /sales/stock/:storeId/:id */ async getProductStocks(store: string, id: number): Promise> { try { if (!store || store.trim() === "") { throw new Error("Loja não informada. É necessário informar a loja."); } const headers = this.getAuthHeaders(); const response = await fetch(`${API_URL}sales/stock/${store}/${id}`, { method: "GET", headers, }); if (!response.ok) { const errorData = await response.json().catch(() => ({ message: response.statusText })); throw new Error(`Erro ao buscar estoques: ${errorData.message || response.statusText}`); } const result = await response.json(); const data = Array.isArray(result) ? result : (result.data || []); return data; } catch (error) { console.error("Erro ao buscar estoques:", error); throw error; } } /** * Obtém opções de parcelamento de um produto * Endpoint: GET /sales/installment/:id?quantity=:quantity */ async getProductInstallments( store: string, id: number, quantity: number = 1 ): Promise> { try { if (!store || store.trim() === "") { throw new Error("Loja não informada. É necessário informar a loja."); } const headers = this.getStoreHeaders(store); const url = new URL(`${API_URL}sales/installment/${id}`); url.searchParams.append("quantity", quantity.toString()); const response = await fetch(url.toString(), { method: "GET", headers, }); if (!response.ok) { const errorData = await response.json().catch(() => ({ message: response.statusText })); throw new Error(`Erro ao buscar parcelamento: ${errorData.message || response.statusText}`); } const result = await response.json(); const data = Array.isArray(result) ? result : (result.data || []); return data; } catch (error) { console.error("Erro ao buscar parcelamento:", error); throw error; } } } export const productService = new ProductService();