Product Group Service
Gerencia operações relacionadas aos grupos de produtos, permitindo buscar hierarquias de categorização de produtos.
Visão Geral
O ProductGroupService é responsável por:
- Buscar grupos de produtos da API remota
- Fornecer estrutura hierárquica de categorização
- Listar grupos pai e seus grupos filhos
- Filtrar grupos analíticos e sintéticos
- Buscar imagens de grupos de produtos
- Sincronizar grupos com banco de dados local
Os grupos de produtos organizam o catálogo em categorias hierárquicas, facilitando a navegação e gestão de produtos. Cada grupo pode ter subgrupos (filhos) formando uma árvore de categorias. O serviço também suporta cache local para melhor performance e operação offline.
Localização: services/product-group/product-group.service.ts
Endpoints Utilizados
Base URL
Todos os endpoints são relativos à base URL configurada em services/api.service.ts
Métodos
fetchRemote(params)
Busca grupos de produtos remotamente com paginação.
Endpoint: GET /grupoProdutos
Parâmetros:
type FetchProductGroupsParams = {
page?: number; // Número da página (padrão: 0)
size?: number; // Quantidade de itens por página (padrão: 999)
dataHora?: string;
};
Query Params enviados:
page: número da páginasize: tamanho da páginadataHora: timestamp (opcional)
Retorno:
Promise<ProductGroup[]>;
Exemplo de uso:
import { productGroupService } from "@/services/product-group/product-group.service.instance";
// Buscar todos os grupos de produtos
const allGroups = await productGroupService.fetchRemote();
// Buscar com paginação
const groupsPage = await productGroupService.fetchRemote({
page: 0,
size: 50,
});
// Buscar para sincronização
const groups = await productGroupService.fetchRemote({
page: 0,
size: 999,
dataHora: "2026-02-25T10:00:00Z",
});
Tratamento de erros:
- Lança
Errorse o status da resposta não for 2xx - Mensagem:
ProductGroupService.fetchRemote failed: {status}
fetchProductGroupImageRemote(externalId)
Busca a imagem de um grupo de produtos remotamente.
Endpoint: GET /api/v1/product-group/{externalId}/image
Base URL: Configurada em AUTH_CONFIG.API_MIDDLEWARE_URL
Parâmetros:
externalId: string; // ID externo do grupo de produtos
Retorno:
Promise<Uint8Array>; // Bytes da imagem
Exemplo de uso:
import { productGroupService } from "@/services/product-group/product-group.service.instance";
// Buscar imagem de um grupo
const imageBytes =
await productGroupService.fetchProductGroupImageRemote("pg-001");
console.log(`Imagem carregada: ${imageBytes.length} bytes`);
// Converter para Blob para exibir em <img>
const blob = new Blob([imageBytes], { type: "image/jpeg" });
const url = URL.createObjectURL(blob);
document.getElementById("group-image").src = url;
Tratamento de erros:
- Lança
Errorse o status da resposta não for 2xx - Mensagem:
ProductGroupService.fetchProductGroupImageRemote failed: {status}
sync(pageSize, onProgress)
Sincroniza todos os grupos de produtos com o banco de dados local, limpando dados antigos e salvando novos dados em lote.
Parâmetros:
pageSize?: number; // Tamanho de página para sincronização (padrão: 2500)
onProgress?: (progress: number) => void; // Callback para progresso (0-100)
Retorno:
Promise<void>;
Exemplo de uso:
import { productGroupService } from "@/services/product-group/product-group.service.instance";
// Sincronizar com callback de progresso
await productGroupService.sync(2500, (progress) => {
console.log(`Sincronização: ${progress}%`);
});
console.log("Sincronização concluída!");
Fluxo de Sincronização:
- Garante que o banco de dados está pronto
- Limpa todos os grupos locais existentes
- Busca grupos remotamente com paginação
- Salva grupos em lote no banco de dados local
- Repete até que menos itens que o
pageSizesejam retornados - Chama callback de progresso periodicamente
Tratamento de erros:
- Pode lançar erros do
fetchRemotedurante a sincronização
clearLocal()
Limpa todos os grupos de produtos do banco de dados local.
Retorno:
Promise<void>;
Exemplo de uso:
// Limpar cache local
await productGroupService.clearLocal();
console.log("Cache local limpo");
Estruturas de Dados
Interface ProductGroup
interface ProductGroup {
id: string;
organizationId: string;
idExterno: string | null;
idGrupoPai: string | null;
organization: string;
nome: string;
grupoPai: string | null;
analitico: "S" | "N";
ativo: "S" | "N";
grupoDeProdutosFilho: BaseProductGroup[] | null;
}
Interface BaseProductGroup
interface BaseProductGroup {
id: string;
organizationId: string;
idExterno: string | null;
idGrupoPai: string | null;
organization: string;
nome: string;
grupoPai: string | null;
analitico: "S" | "N";
ativo: "S" | "N";
}
Componentes Relacionados
buildQueryParams Helper
Função utilitária que converte objeto de parâmetros em URLSearchParams.
Localização: helpers/buildQueryParams.ts
PageResponse Interface
Interface compartilhada para respostas paginadas.
Localização: services/client/client.service.ts
interface PageResponse<T> {
content: T[]; // Array de itens da página
page?: number; // Número da página atual
size?: number; // Tamanho da página
totalPages?: number; // Total de páginas
totalElements?: number; // Total de elementos
}
Fluxo de Integração
Fluxo de Busca de Grupos
1. App precisa exibir categorias de produtos
2. App chama productGroupService.fetchRemote()
3. GET /grupoProdutos é enviado com query params
4. API retorna grupos com estrutura hierárquica
5. Grupos incluem array de grupos filhos (grupoDeProdutosFilho)
6. App pode renderizar árvore de categorias
Fluxo de Navegação Hierárquica
1. Obter todos os grupos
2. Filtrar grupos raiz (idGrupoPai === null)
3. Para cada grupo raiz:
a. Renderizar como categoria principal
b. Verificar se tem grupoDeProdutosFilho
c. Renderizar subgrupos recursivamente
4. Aplicar filtros (apenas ativos, apenas analíticos, etc.)
Casos de Uso
Buscar e Listar Grupos
// Buscar todos os grupos
const groups = await productGroupService.fetchRemote();
console.log(`Total de grupos: ${groups.length}`);
// Separar grupos raiz e subgrupos
const rootGroups = groups.filter((g) => g.idGrupoPai === null);
const childGroups = groups.filter((g) => g.idGrupoPai !== null);
console.log(`Grupos raiz: ${rootGroups.length}`);
console.log(`Subgrupos: ${childGroups.length}`);
Filtrar Grupos Analíticos
// Buscar grupos
const groups = await productGroupService.fetchRemote();
// Filtrar apenas grupos analíticos (que podem ter produtos)
const analyticGroups = groups.filter(
(g) => g.analitico === "S" && g.ativo === "S",
);
console.log("Grupos que podem ter produtos:");
analyticGroups.forEach((group) => {
console.log(`- ${group.nome} (Pai: ${group.grupoPai || "Nenhum"})`);
});
Renderizar Select de Categorias
// Buscar grupos
const groups = await productGroupService.fetchRemote();
// Filtrar apenas ativos e analíticos (podem ter produtos)
const selectableGroups = groups.filter(
(g) => g.ativo === "S" && g.analitico === "S",
);
// Gerar opções para select
const options = selectableGroups.map((group) => ({
value: group.id,
label: group.grupoPai ? `${group.grupoPai} > ${group.nome}` : group.nome,
group: group,
}));
// Ordenar alfabeticamente
options.sort((a, b) => a.label.localeCompare(b.label));
console.log("Opções para select:");
options.forEach((opt) => {
console.log(`<option value="${opt.value}">${opt.label}</option>`);
});
---
## Testes
### Localização
`services/product-group/product-group.service.test.ts`
### Executar Testes
```bash
# Teste específico do serviço
npx jest services/product-group/product-group.service.test.ts
# Modo watch
npx jest services/product-group/product-group.service.test.ts --watch
# Com coverage
npx jest services/product-group/product-group.service.test.ts --coverage
Casos de Teste
- ✅
fetchRemote: busca de grupos com paginação - ✅
fetchRemote: retorna content em status 2xx - ✅
fetchRemote: lança erro para status não-2xx - ✅
fetchProductGroupImageRemote: busca imagem com sucesso (status 2xx) - ✅
fetchProductGroupImageRemote: lança erro para status não-2xx - ✅
sync: busca remoto, salva em lote e completa com sucesso
Mocks
Localização
__mocks__/product-group.mock.ts
Dados Disponíveis
Mock completo com estrutura hierárquica de grupos:
- Electronics (
pg-001): Grupo pai com Mobile Phones e Laptops - Home Appliances (
pg-002): Grupo pai com Refrigerators - Clothing (
pg-003): Grupo pai com Men, Women e Kids - Food & Beverage (
pg-005): Grupo pai com Beverages - Sports (
pg-007): Grupo pai com Fitness Equipment e Apparel - E mais grupos de exemplo...
Exemplo de uso em testes:
import { productGroupResponseMock } from "@/__mocks__/product-group.mock";
// Mock da API para fetchRemote
const apiMock = {
get: jest.fn(async () => ({
status: 200,
data: productGroupResponseMock,
})),
};
const service = new ProductGroupService({ apiWithoutAccessToken: apiMock });
const result = await service.fetchRemote();
expect(result).toEqual(productGroupResponseMock.content);
expect(result[0]).toHaveProperty("grupoDeProdutosFilho");
// Mock da API para fetchProductGroupImageRemote
const imageData = new Uint8Array([1, 2, 3, 4, 5]);
const apiImageMock = {
get: jest.fn(async () => ({
status: 200,
data: imageData,
})),
};
const serviceImage = new ProductGroupService({
apiWithoutAccessToken: apiImageMock,
});
const image = await serviceImage.fetchProductGroupImageRemote("pg-001");
expect(image).toEqual(imageData);
// Mock para sync
const saveBatch = jest.fn(async () => undefined);
const serviceSync = new ProductGroupService({
repo: { saveBatch } as any,
apiWithoutAccessToken: apiMock as any,
});
await serviceSync.sync(10); // pageSize pequeno para teste
expect(saveBatch).toHaveBeenCalledWith(productGroupResponseMock.content);
Helpers
toImageBytes
Função utilitária que converte vários formatos de dados para Uint8Array.
Localização: helpers/to-image-bytes.ts
Suporta:
Uint8Array- retorna como estáArrayBuffer- converte para Uint8ArrayArrayBuffer.isView(TypedArray) - extrai bytes- Objetos com propriedade
data- convertedatapara Uint8Array
Exemplo:
import { toImageBytes } from "@/helpers/to-image-bytes";
const buffer = new ArrayBuffer(10);
const bytes = toImageBytes(buffer);
// bytes é Uint8Array