9.3 KiB
9.3 KiB
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
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
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
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
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)
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
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
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
if (!dataInicio || !dataFim) {
return NextResponse.json(
{ message: 'Parâmetros obrigatórios: dataInicio, dataFim' },
{ status: 400 }
);
}
2. Tratamento de Erros de Banco
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
-- 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)
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)
// 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
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)
// 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
// 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
// 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
- Implementar Autenticação JWT
- Adicionar Rate Limiting por IP
- Implementar Cache Redis para queries frequentes
- Adicionar Paginação para grandes volumes
- Implementar Webhooks para notificações
- Adicionar Documentação OpenAPI (Swagger)
- Implementar Versionamento de API
- Adicionar Monitoramento com Prometheus/Grafana