378 lines
9.3 KiB
Markdown
378 lines
9.3 KiB
Markdown
# 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
|