Métricas do Painel (GET /stats)

Detalhamento do endpoint GET /stats/ do backend-store: o que cada métrica significa, como é calculada e — ponto que costuma gerar dúvida — por que nenhuma delas usa janela de tempo.

Visão geral

GET /stats/ alimenta o "Painel da Lojinha" (dashboard inicial) do frontend fuji-loja. Como qualquer rota do backend-store, exige autenticação via Keycloak JWT e é sempre filtrado por saas_id (multi-tenant) — nunca agrega dados entre tenants.

A rota (app/api/routes/stats.py) delega o cálculo ao StatsService.obter_dashboard (app/domain/services/stats_service.py), que busca as métricas em paralelo lógico nos repositórios de venda, estoque, produto, categoria e fornecedor, e devolve um StatsOut (app/api/schemas/stats.py).

O endpoint retorna total_fornecedores e total_categorias, mas o dashboard atual do fuji-loja (dashboard.service.ts) não exibe esses dois campos — só usa produtos, pendentes, receita, ticket médio, estoque baixo e últimos pedidos. Os dois campos existem na API para consumo futuro ou por outros clientes.

Janela de tempo

Nenhuma métrica de /stats é filtrada por período. Todos os totais e somas são calculados sobre o histórico completo do tenant, desde o primeiro registro — não existe parâmetro de data na rota nem cláusula WHERE por intervalo nas queries dos repositórios.

  • receita_total e ticket_medio: soma/média acumulada de todas as vendas pagas desde sempre, não "vendas do mês" ou "dos últimos 30 dias".

  • total_pedidos e pedidos_pendentes: contam sobre o histórico completo de vendas do tenant.

  • ultimos_pedidos: não usa janela de data — limita por quantidade (10 registros mais recentes por data desc), então pode trazer um pedido de meses atrás se o tenant tiver poucas vendas.

Isso contrasta com GET /estoque/movimentacoes/, endpoint diferente e não relacionado a /stats, que lista movimentações de estoque restritas aos últimos 60 dias (MOVIMENTACAO_HISTORICO_DIAS em app/domain/services/estoque_service.py). Se um consumidor da API espera um comportamento de janela parecido em /stats, é um engano — os dois endpoints têm semânticas diferentes.

Se no futuro for necessário um /stats por período (ex.: "receita do mês"), isso exige um novo parâmetro de data na rota e novas queries com filtro por Venda.data — não existe hoje.

Campos e como são calculados

Campo Cálculo Fonte

total_produtos

Conta produtos do tenant com status = Ativo. Produtos inativos não entram na contagem.

ProdutoRepository.contar_ativos

total_fornecedores

Conta fornecedores do tenant com status = Ativo.

FornecedorRepository.contar_ativos

total_categorias

Conta categorias do tenant com status = Ativo.

CategoriaRepository.contar_ativas

total_pedidos

Conta todas as vendas do tenant, em qualquer estado (inclusive Criado e Cancelado).

VendaRepository.contar_total

pedidos_pendentes

Conta vendas cujo status ainda não chegou a um estado final — ou seja, está fora de {Retirado, Cancelado} (cobre Criado, Pago e Aguardando retirada). Ver Máquina de Estados da Venda para a máquina de estados completa.

VendaRepository.contar_pendentes

receita_total

Soma de valor_final das vendas cujo status é Pago, Aguardando retirada ou Retirado — ou seja, vendas cujo pagamento já foi confirmado, independentemente de já terem sido retiradas. Vendas Criado (ainda não pagas) e Cancelado não entram.

VendaRepository.obter_metricas_pagamento

ticket_medio

receita_total / alunos_únicos, onde alunos_únicos é a contagem de aluno_id distintos entre as mesmas vendas pagas usadas em receita_total. É 0 quando não há nenhum aluno pagante (evita divisão por zero) — não é a média por pedido, é a média por aluno.

StatsService.obter_dashboard (usa o resultado de obter_metricas_pagamento)

estoque_baixo

Pares (Estoque, Produto) onde o produto tem flag_estoque = True (controla estoque) e quantidade_atual ⇐ estoque_minimo. Cada linha é por combinação produto + academia — uma rede com várias academias pode ter o mesmo produto listado mais de uma vez, uma por academia abaixo do mínimo.

EstoqueRepository.abaixo_do_minimo

ultimos_pedidos

10 vendas mais recentes do tenant por data decrescente, em qualquer status. Enriquecidas na rota (não no service) com nome do produto (busca local) e nome do aluno (chamada à API do SaaS principal); ambos ficam null se a busca falhar.

VendaRepository.ultimos + _pedidos_enriquecidos em app/api/routes/stats.py

total_pedidos e pedidos_pendentes contam todas as vendas, inclusive as ainda não pagas (Criado). Já receita_total e ticket_medio só consideram vendas com pagamento confirmado. São bases diferentes — não espere que pedidos_pendentes bata com "pedidos sem receita".

entrada, saída e movimentação: não fazem parte de /stats

/stats não calcula nem expõe totais de entrada, saída ou movimentação de estoque. Esses termos pertencem a um domínio separado — o de movimentação de estoque — servido por outros endpoints do backend-store:

Endpoint O que faz

POST /estoque/entrada

Registra uma entrada de estoque (compra de fornecedor ou outra origem) e incrementa o snapshot (Estoque.quantidade_atual).

POST /estoque/saida

Registra uma baixa manual de estoque (perda ou inventário, com motivo obrigatório) e decrementa o snapshot.

GET /estoque/movimentacoes/

Lista o histórico de movimentações (Movimentacao, com tipo Entrada/Saida) dos últimos 60 dias do tenant — essa é a única rota do módulo de estoque com janela de tempo.

No frontend fuji-loja, esses três endpoints alimentam o módulo Inventory (projects/store/src/inventory/), uma tela separada do "Painel da Lojinha" que consome /stats.

Cada Movimentacao tem tipo (Entrada ou Saida) e origem (Compra, Venda, Perda ou Inventario). Não existe hoje um endpoint que some "total de entradas" ou "total de saídas" — a única agregação sobre o histórico de movimentações é interna, em EstoqueRepository.calcular_saldo (soma quantidades de entrada menos saída de todo o histórico, sem limite de 60 dias), usada pelo comando de ressincronismo (make task sync-estoque), não pela API.

Se o painel precisar futuramente exibir "entradas/saídas do período" como métrica, isso não existe em /stats nem em nenhum outro endpoint atual — seria uma nova agregação a implementar sobre Movimentacao, com sua própria decisão de janela de tempo (a mesma questão detalhada acima).

Referências

  • app/api/routes/stats.py, app/domain/services/stats_service.py, app/api/schemas/stats.py — implementação do endpoint.

  • app/infrastructure/repositories/venda_repository.py, estoque_repository.py — queries por trás de cada métrica.

  • app/domain/services/estoque_service.py — janela de 60 dias das movimentações (MOVIMENTACAO_HISTORICO_DIAS).

  • Máquina de Estados da Venda — estados de venda usados em pedidos_pendentes e receita_total.