Vendaweb-portal/src/services/product.service.ts

618 lines
18 KiB
TypeScript

/**
* 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<ClasseMercadologica[]> {
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<SaleProduct[]> {
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<SaleProduct[]> {
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<SaleProduct[]> {
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<string>();
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<StoreERP[]> {
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<SaleProduct> {
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<SaleProduct[]> {
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<SaleProduct[]> {
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<Array<{
store: string;
storeName: string;
quantity: number;
work: boolean;
blocked: string;
breakdown: number;
transfer: number;
allowDelivery: number;
}>> {
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<Array<{
installment: number;
installmentValue: number;
}>> {
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();