vendaweb-api/docs/api.md

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

  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