vendaweb-api/docs/api.md

378 lines
9.3 KiB
Markdown
Raw Normal View History

# APIs - DRE Gerencial
## Visão Geral
O sistema possui duas APIs principais construídas com Next.js App Router, utilizando Drizzle ORM para interação com PostgreSQL.
## Estrutura das APIs
### 1. **API DRE Gerencial** (`/api/dre/route.ts`)
#### Endpoint
```
GET /api/dre
```
#### Descrição
Retorna dados consolidados da view `view_dre_gerencial` para construção da interface hierárquica.
#### Implementação
```typescript
import db from '@/db';
import { sql } from 'drizzle-orm';
import { NextResponse } from 'next/server';
export async function GET() {
try {
const data = await db.execute(sql`SELECT * FROM view_dre_gerencial`);
return NextResponse.json(data.rows);
} catch (error) {
console.error('Erro ao buscar dados da view:', error);
return NextResponse.json(
{ error: 'Erro ao carregar dados' },
{ status: 500 }
);
}
}
```
#### Response
```typescript
interface DREItem {
codfilial: string;
data_competencia: string;
data_caixa: string;
grupo: string;
subgrupo: string;
centro_custo: string;
codigo_conta: number;
conta: string;
valor: string;
}
```
#### Casos de Uso
- Carregamento inicial da interface DRE
- Construção da hierarquia Grupo → Subgrupo → Centro de Custo → Conta
- Cálculo de totais e percentuais
---
### 2. **API Analítica** (`/api/analitico/route.ts`)
#### Endpoint
```
GET /api/analitico?dataInicio=YYYY-MM&dataFim=YYYY-MM&[filtros]
```
#### Parâmetros
| Parâmetro | Tipo | Obrigatório | Descrição |
|-----------|------|-------------|-----------|
| `dataInicio` | string | ✅ | Período inicial (YYYY-MM) |
| `dataFim` | string | ✅ | Período final (YYYY-MM) |
| `centroCusto` | string | ❌ | Filtro por centro de custo |
| `codigoGrupo` | string | ❌ | Filtro por código do grupo |
| `codigoSubgrupo` | string | ❌ | Filtro por código do subgrupo |
| `codigoConta` | string | ❌ | Filtro por código da conta |
#### Implementação
```typescript
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const dataInicio = searchParams.get('dataInicio');
const dataFim = searchParams.get('dataFim');
const centroCusto = searchParams.get('centroCusto');
const codigoGrupo = searchParams.get('codigoGrupo');
const codigoSubgrupo = searchParams.get('codigoSubgrupo');
const codigoConta = searchParams.get('codigoConta');
if (!dataInicio || !dataFim) {
return NextResponse.json(
{ message: 'Parâmetros obrigatórios: dataInicio, dataFim' },
{ status: 400 }
);
}
// Construção dinâmica da query baseada nos filtros
let query;
if (centroCusto || codigoGrupo || codigoSubgrupo || codigoConta) {
// Query com filtros específicos
query = buildFilteredQuery(dataInicio, dataFim, filtros);
} else {
// Query simples por período
query = buildSimpleQuery(dataInicio, dataFim);
}
const data = await db.execute(query);
return NextResponse.json(data.rows);
} catch (error) {
console.error('Erro ao buscar dados analíticos:', error);
return NextResponse.json(
{
message: 'Erro ao buscar dados analíticos',
error: error instanceof Error ? error.message : String(error),
},
{ status: 500 }
);
}
}
```
#### Response
```typescript
interface AnaliticoItem {
codigo_grupo: string;
codigo_subgrupo: string;
codigo_fornecedor: string;
nome_fornecedor: string;
id: number;
codfilial: string;
recnum: number;
data_competencia: string;
data_vencimento: string;
data_pagamento: string;
data_caixa: string;
codigo_conta: string;
conta: string;
codigo_centrocusto: string;
valor: number;
historico: string;
historico2: string;
created_at: string;
updated_at: string;
}
```
## Estratégias de Query
### 1. **Query Simples (Sem Filtros Específicos)**
```sql
SELECT
ffa.codigo_fornecedor,
ffa.nome_fornecedor,
ffa.id,
ffa.codfilial,
ffa.recnum,
ffa.data_competencia,
ffa.data_vencimento,
ffa.data_pagamento,
ffa.data_caixa,
ffa.codigo_conta,
ffa.conta,
ffa.codigo_centrocusto,
ffa.valor,
ffa.historico,
ffa.historico2,
ffa.created_at,
ffa.updated_at
FROM fato_financeiro_analitico AS ffa
WHERE to_char(ffa.data_competencia, 'YYYY-MM') BETWEEN $1 AND $2
```
### 2. **Query com Filtros de Centro de Custo e Conta**
```sql
SELECT ffa.*
FROM fato_financeiro_analitico AS ffa
WHERE to_char(ffa.data_competencia, 'YYYY-MM') BETWEEN $1 AND $2
AND ffa.codigo_centrocusto = $3
AND ffa.codigo_conta = $4
```
### 3. **Query com Filtros de Grupo/Subgrupo**
```sql
SELECT ffa.*
FROM fato_financeiro_analitico AS ffa
WHERE EXISTS (
SELECT 1 FROM public.view_dre_gerencial AS dre
WHERE ffa.codigo_conta = dre.codigo_conta::text
AND ffa.codigo_centrocusto = dre.centro_custo
AND to_char(ffa.data_competencia, 'YYYY-MM') = to_char(dre.data_competencia, 'YYYY-MM')
AND SUBSTRING(dre.grupo FROM '^\\s*(\\d+)\\s*\\.') = $1
AND SUBSTRING(dre.subgrupo FROM '^\\s*(\\d+(?:\\.\\d+)+)\\s*-') = $2
)
AND to_char(ffa.data_competencia, 'YYYY-MM') BETWEEN $3 AND $4
```
## Tratamento de Erros
### 1. **Validação de Parâmetros**
```typescript
if (!dataInicio || !dataFim) {
return NextResponse.json(
{ message: 'Parâmetros obrigatórios: dataInicio, dataFim' },
{ status: 400 }
);
}
```
### 2. **Tratamento de Erros de Banco**
```typescript
try {
const data = await db.execute(query);
return NextResponse.json(data.rows);
} catch (error) {
console.error('Erro ao buscar dados:', error);
return NextResponse.json(
{
message: 'Erro ao buscar dados',
error: error instanceof Error ? error.message : String(error),
},
{ status: 500 }
);
}
```
### 3. **Códigos de Status HTTP**
| Status | Cenário | Response |
|--------|---------|----------|
| 200 | Sucesso | Dados solicitados |
| 400 | Parâmetros inválidos | Mensagem de erro |
| 500 | Erro interno | Detalhes do erro |
## Performance e Otimização
### 1. **Índices Recomendados**
```sql
-- Para filtros por data
CREATE INDEX idx_fato_financeiro_data_competencia
ON fato_financeiro_analitico (data_competencia);
-- Para filtros por centro de custo
CREATE INDEX idx_fato_financeiro_centro_custo
ON fato_financeiro_analitico (codigo_centrocusto);
-- Para filtros por conta
CREATE INDEX idx_fato_financeiro_conta
ON fato_financeiro_analitico (codigo_conta);
```
### 2. **Estratégias de Cache**
- **Client-side**: React Query para cache de dados
- **Server-side**: Cache de views materializadas
- **CDN**: Para assets estáticos
### 3. **Paginação (Futuro)**
```typescript
interface PaginatedResponse<T> {
data: T[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}
```
## Segurança
### 1. **Validação de Input**
- Sanitização de parâmetros de query
- Validação de tipos de dados
- Escape de caracteres especiais
### 2. **SQL Injection Prevention**
- Uso de prepared statements via Drizzle
- Parâmetros tipados
- Validação de entrada
### 3. **Rate Limiting (Futuro)**
```typescript
// Implementação de rate limiting
const rateLimit = new Map();
export async function GET(request: NextRequest) {
const ip = request.ip;
const now = Date.now();
const windowMs = 15 * 60 * 1000; // 15 minutos
const maxRequests = 100;
// Lógica de rate limiting
}
```
## Monitoramento
### 1. **Logs Estruturados**
```typescript
console.log({
timestamp: new Date().toISOString(),
endpoint: '/api/analitico',
method: 'GET',
params: { dataInicio, dataFim, centroCusto },
duration: Date.now() - startTime,
status: 'success'
});
```
### 2. **Métricas de Performance**
- Tempo de resposta por endpoint
- Número de requisições por minuto
- Taxa de erro por endpoint
- Uso de memória e CPU
### 3. **Health Check (Futuro)**
```typescript
// GET /api/health
export async function GET() {
try {
await db.execute(sql`SELECT 1`);
return NextResponse.json({ status: 'healthy', timestamp: new Date() });
} catch (error) {
return NextResponse.json({ status: 'unhealthy', error: error.message }, { status: 500 });
}
}
```
## Testes
### 1. **Testes Unitários**
```typescript
// Exemplo de teste para API DRE
describe('/api/dre', () => {
it('should return DRE data', async () => {
const response = await fetch('/api/dre');
const data = await response.json();
expect(response.status).toBe(200);
expect(Array.isArray(data)).toBe(true);
});
});
```
### 2. **Testes de Integração**
```typescript
// Teste com filtros
describe('/api/analitico', () => {
it('should filter by date range', async () => {
const params = new URLSearchParams({
dataInicio: '2024-01',
dataFim: '2024-12'
});
const response = await fetch(`/api/analitico?${params}`);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.every(item =>
item.data_competencia.startsWith('2024')
)).toBe(true);
});
});
```
## Próximos Passos
1. **Implementar Autenticação** JWT
2. **Adicionar Rate Limiting** por IP
3. **Implementar Cache Redis** para queries frequentes
4. **Adicionar Paginação** para grandes volumes
5. **Implementar Webhooks** para notificações
6. **Adicionar Documentação OpenAPI** (Swagger)
7. **Implementar Versionamento** de API
8. **Adicionar Monitoramento** com Prometheus/Grafana