API backend desenvolvida em Java com Spring Boot para processamento de arquivos CSV/XLSX e geração de relatórios analíticos de vendas.
Este projeto faz parte do Projeto Hanami, uma iniciativa de impacto social voltada ao uso de tecnologia para análise de dados.
- Prazo total: 40 dias
- Metodologia: Desenvolvimento incremental por sprints
- Sprint atual: Sprint 1 – Fundação e Setup do Projeto
- Status atual: Finalizada
Desenvolver uma API robusta capaz de:
- Receber arquivos CSV/XLSX
- Processar dados de vendas
- Armazenar informações em banco de dados
- Gerar relatórios analíticos
| Foco Principal | Entregas |
|---|---|
| Setup do projeto e arquitetura base | Estrutura inicial do projeto |
| Configuração de ambiente | Perfis dev e prod |
| Início do backend | Parser de dados e endpoint de upload |
| Persistência | Entidades e repositórios iniciais |
| Foco Principal | Entregas |
|---|---|
| Finalização da lógica de análise | Algoritmos completos |
| Relatórios | Geração de relatórios PDF |
| Documentação | README final e instruções de uso |
| Deploy | Ambiente produtivo |
🔎 Observação: A Sprint 2 será detalhada após a conclusão da Sprint 1.
- Java 17
- Spring Boot
- MySQL
- Maven
src/main/java/com/hanami/iurydev/apiHanami
├── controller # Camada de controle (endpoints REST)
├── dto # Objetos de transferência de dados
├── entity
│ ├── embeddable # Objetos incorporáveis
│ ├── enums # Enumerações do domínio
│ └── Venda # Entidade principal de vendas
├── repository # Interfaces JPA
├── service # Regras de negócio
└── ApiHanamiApplication
spring.datasource.url=jdbc:mysql://localhost:3306/hanamiapidb
spring.datasource.username=seu-login-mysql
spring.datasource.password=sua-senha-mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
# Aumenta o limite de tamanho do arquivo individual
spring.servlet.multipart.max-file-size=50MB
# Aumenta o limite total da requisição (arquivo + dados extras)
spring.servlet.multipart.max-request-size=50MB
spring.jackson.date-format=yyyy-MM-dd
spring.jackson.time-zone=America/Sao_Paulo
spring.datasource.url=jdbc:mysql://${MYSQLHOST}:${MYSQLPORT}/${MYSQLDATABASE}
spring.datasource.username=${MYSQLUSER}
spring.datasource.password=${MYSQLPASSWORD}
spring.jpa.hibernate.ddl-auto=update
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true
spring.jackson.date-format=yyyy-MM-dd
spring.jackson.time-zone=America/Sao_Paulo
spring.profiles.active=dev
- Java 17
- MySQL
Passo a passo
- Clone o repositório
git clone https://github.com/IuryDevJava/api-hanami.git- Entre no diretório
cd api-hanami- Execute a aplicação
./mvnw spring-boot:run <!-- Leitura de arquivos XLSX -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<!-- Leitura de arquivos CSV -->
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.5.2</version>
</dependency>Endpoint responsável por receber arquivos CSV ou XLSX, validar os dados, processar as vendas e persistir no banco de dados
Post /vendas/upload
- Valida a estrutura do arquivo
- Valida regras de negócio campo a campo
- Evita duplicidade por id_transacao
- Persiste apenas registros válidos
- Marca registros inválidos com observações
- URL: /vendas/upload
- Método: POST
- Content-Type: multipart/form-data
| Nome | Tipo | descrição |
|---|---|---|
| file | File | Arquivo CSV ou XLSX com os dados de vendas |
Body
- Type: form-data
- Key: file
- Type: File
- Value: vendas_ficticias_10000_linhas.csv
{
"Status": "sucesso",
"Linhas_processadas": 10000
} {
"Status": "Aviso: Nenhuma nova linha processada",
"Linhas_processadas": 0
} {
"Status": "erro",
"Linhas_processadas": 0
} {
"Status": "Coluna obrigatória ausente: id_transacao",
"Linhas_processadas": 0
} {
"Receita_liquida": 5243176617.89,
"Lucro_bruto": 3099751358.11,
"Total_vendas": 8342927976.00,
"Media_por_transacao": 928849.70,
"Custo_total": 5243176617.89,
"Numero_transacoes": 8982
} [
{
"Nome_produto": "Carregador Wireless",
"Quatidade_vendida": 1006,
"Total_arrecadado": 288235871.00
},
{
"Nome_produto": "iPhone 15",
"Quatidade_vendida": 787,
"Total_arrecadado": 246201201.00
},
{
"Nome_produto": "Apple Watch",
"Quatidade_vendida": 858,
"Total_arrecadado": 249837283.00
}
]{{base_url}}/vendas/reports/product-analysis?sort_by=quantidade - Retorna os produtos de forma ordenada e por quantidade.
[
{
"Nome_produto": "Cabo USB-C",
"Quatidade_vendida": 1061,
"Total_arrecadado": 339386041.00
},
{
"Nome_produto": "Webcam HD",
"Quatidade_vendida": 1026,
"Total_arrecadado": 319378902.00
},
{
"Nome_produto": "Carregador Wireless",
"Quatidade_vendida": 1006,
"Total_arrecadado": 288235871.00
}
]{{base_url}}/vendas/reports/product-analysis?sort_by=valor - Retorna os produtos de forma ordenada e por valor.
[
{
"Nome_produto": "Cabo USB-C",
"Quatidade_vendida": 1061,
"Total_arrecadado": 339386041.00
},
{
"Nome_produto": "Webcam HD",
"Quatidade_vendida": 1026,
"Total_arrecadado": 319378902.00
},
{
"Nome_produto": "Chromecast",
"Quatidade_vendida": 934,
"Total_arrecadado": 294529780.00
}
]{{base_url}}/vendas/reports/financial-metrics - Retorna um JSON com lucro_bruto, receita_liquida e custo_total.
{
"Receita_liquida": 5243176617.89,
"Lucro_bruto": 3099751358.11,
"Custo_total": 5243176617.89
}- Formato do id_transacao (ex: TXN12345678)
- Margem de lucro mínima e máxima
- Idade do cliente
- Formato de IDs de cliente, produto e vendedor
- Datas válidas
- Campos obrigatórios não nulos
- Enumerações normalizadas (canal de venda, forma de pagamento, região, status de entrega)
A aplicação utiliza o modelo de persistência onde os dados do Produto são tratados como objetos incorporáveis (@Embeddable), resultando em uma tabela única de vendas para otimização de performance analítica.
Criar e usar o banco (não esqueça que o nome do banco precisa ser o mesmo no arquivo properties em spring.datasource.url=jdbc:mysql://localhost:3306/hanamiapidb)
CREATE DATABASE hanamiapidb;
USE hanamiapidb; SHOW TABLES; SELECT COUNT(*) FROM vendas; SELECT * FROM vendas LIMIT 10; SELECT id_transacao, observacao_validada
FROM vendas
WHERE processado_sucesso = false; SELECT processado_sucesso, COUNT(*)
FROM vendas
GROUP BY processado_sucesso; DROP TABLE hanamiapidb.vendas; SELECT
SUM(valor_final) AS total_vendas,
SUM(valor_final * (margem_lucro / 100)) AS lucro_bruto,
SUM(valor_final) - SUM(valor_final * (margem_lucro / 100)) AS receita_liquida,
SUM(valor_final - (valor_final * (margem_lucro / 100))) AS custo_total,
AVG(valor_final) AS media_por_transacao,
COUNT(*) AS numero_transacoes
FROM vendas
WHERE processado_sucesso = 1; SELECT
nome_produto,
COUNT(*) AS quantidade_vendida,
SUM(valor_final) AS total_arrecadado
FROM vendas
WHERE processado_sucesso = 1
GROUP BY nome_produto
ORDER BY total_arrecadado DESC; SELECT
SUM(valor_final * (margem_lucro / 100)) AS lucro_bruto,
SUM(valor_final) - SUM(valor_final * (margem_lucro / 100)) AS receita_liquida,
SUM(valor_final - (valor_final * (margem_lucro / 100))) AS custo_total
FROM vendas
WHERE processado_sucesso = 1;A API utiliza logging estruturado para registrar eventos importantes durante o processamento de arquivos, facilitando:
- Monitoramento da aplicação
- Debug de erros
- Auditoria de processamento
- Análise de falhas em produção
@Slf4j
@RestController
@RequestMapping("/vendas")
public class VendaController {
} 200 OK. Arquivo 'vendas_ficticias_10000_linhas.csv' foi processado com sucesso. Total: 10000 linhas
- Upload válido
- Arquivo lido corretamente
- Processamento finalizado sem erros
Erro 400. Ao tentar fazer o upload sem arquivo foi retornado um erro
- Parâmetro file não enviado
- Arquivo vazio
Erro 422. Arquivo enviado não contém uma ou mais colunas obrigatórias Coluna obrigatória ausente: id_transacao
- CSV/XLSX não possui colunas obrigatórias
- Estrutura incompatível com o parser
Erro crítico durante o processamento de upload
- Exceções inesperadas
- Falhas de I/O, parsing ou banco de dados
[X] Código: O projeto compila sem erros? (Sim)
[X] Testes: Os endpoints no Postman batem com os resultados do SQL? (Sim)
[X] Documentação: O README reflete a realidade do código? (Sim)