Ir para o conteúdo principal

Feito - Estudo Técnico para separar a Carta Remessa em PDF por Identificador

Data de elaboração 17/07/2023
Responsável pelo estudo

Ádrian Rabelo Mendes (Assessor)
Diego Gonçalves de Almeida (Assessor)
Jônatas Legal (Técnico em Tecnologia da Informação e Comunicação) (Assessor)
Rodrigo Stefano Sales Nascimento (Assessor)

Equipe do estudo CAOS

Alvo

Sistema Governa

Origem

Objetivo estratégico: Separar Carta Remessa na sua geração de relatório por Identificador.

Objetivo para que seja atendida a demanda de agilizar a demanda diária de desbloqueios de pagamentos e a respectiva disponibilização das cartas remessas em cada processo SEI (demanda Nº: 0031.072694/2022-33)

Glossário
FEBRABAN – Federação Brasileira de Bancos
CNAB240 – Centro Nacional de Automação Bancária

1. Introdução

A carta remessa é um documento ou arquivo eletrônico contendo detalhes de uma transação ou série de transações a serem processadas por um banco ou outra instituição financeira. Estes detalhes podem incluir informações sobre o remetente e o destinatário do pagamento, o valor da transação, a data da transação, entre outros.

Atualmente, as Cartas Remessa são produzidas em um formato padrão definido pela Federação Brasileira de Bancos (FEBRABAN), conhecido como CNAB240. Este formato estruturado facilita o processamento automatizado das transações pelos bancos e é amplamente aceito em todo o setor bancário brasileiro.

No sistema Governa, a geração da carta remessa é feita por um processo que envolve várias etapas. Neste estudo, iremos explorar estas etapas em detalhes, e fornecer recomendações para a implementação da separação dos relatórios nela gerados.

2. Desenvolvimento

2.1 Geração da Carta Remessa

A carta remessa pode ser gerada através do seguinte menu no sistema Governa, módulo de Recursos Humanos:  
Movimentação Mensal > Controle Bancário > Carta Remessa > Emitir


Figura 1: Menu de emissão de carta remessa

Na figura 2, na tela de emissão de Carta Remessa, podemos observar diversas opções para que o usuário possa escolher no evento da geração da mesma.

Para gerar a Carta remessa, o usuário precisará preencher todos os campos obrigatórios, marcados com asterisco * e acionar o botão Executar. Se a carta remessa nas condições que o usuário escolheu já foi gerada, o botão Baixar Relatório estará ativo, permitindo assim, que o usuário faça download do arquivo gerado.  

Ao clicar no botão Executar, o sistema começará a realizar as etapas do método executar() na classe CartaRemessaMB.java, conforme podemos ver nos métodos comentados do código abaixo.

CartaRemessaMB.java - Método executar

public void executar() {
try {
  // Verifica se há permissão para o usuário inserir sem validar o mês fechado.
  isPermissaoBotaoInserirSemValidarMesFechado();

  // Define a lista de verbas selecionadas nas verbas da carta remessa
  this.cartaRemessa.setVerbas(verbaSelecionadaList);
			
  // Define o mês de referência buscando da classe pai			
  String mesRefLogado = super.getMesReferenciaFormatado();

  // Define a lista de cartaRemessaid de acordo com o retorno do método 
  // executar(cartaremessa, mesRefLogado) da instância da classe        
  // CartaRemessaBO. Essa por sua vez, é responsável por gerar as cartas
  // remessas e retorna uma lista com as id’s caso sejam geradas.
  this.listaCartaRemessaid = cartaRemessaBO.executar(this.cartaRemessa,mesRefLogado);
  			
  // Se gerou carta, entao gera o relatório
  if (!listaCartaRemessaid.isEmpty()) {
  
  // Define o modelo de acordo com o modelo da carta remessa enviado pelo usuário
  setModelo(this.cartaRemessa.getModelo());
  
  // Gera o arquivo zip de saída contendo os relatórios
  file = this.gerarRelatorioCartaRemessa("", CartaRemessaModeloEnum.parse(
    cartaRemessa.getModelo()).getDescricaoFormatada(), MesReferenciaUtil
    .converterMesAnoParaAnoMesReferencia(
    cartaRemessa.getMesReferencia()).toString(), ""
  );

  // Envia uma mensagem para o usuário informando que o registro foi //cadastrado com sucesso
  super.enviarMensagemInfo(msgServico.getMsgGenericaRegistroCadastradoComSucesso(EntidadeRhEnum.CARTA_REMESSA));

  // Envia uma mensagem para o usuário informando que o relatório foi gerado
  super.enviarMensagemInfo(msgServico.getMensagem(MensagemChaveRh.CARTA_REMESSA_IMPRIMIR_RELATORIO));
  
  } else {
    
  // Envia uma mensagem para o usuário informando que não foi gerado carta remessa
  super.enviarMensagemInfo(msgServico.getMensagem(MensagemChaveRh.CARTA_REMESSA_NAO_GERADAS));
  }

// Captura os erros caso ocorram e retornam ao usuário a mensagem do erro
} catch (RestricaoNegocioExcecao e) {
      log.warn(e.getMessage());
      super.enviarMensagemErro(e.getMessage());
  } catch (CampoObrigatorioNaoInformadoExcecao e) {
      log.warn(e.getMessage());
      super.enviarMensagemErro(msgServico.getMsgGenericaCampoObrigatorioNaoInformado(
        e.getCamposNaoInformadosFormatado()));
  } catch (RelatorioGeracaoExcecao e) {
      log.error(e.getMessage(), e);
      super.enviarMensagemErro(msgServico.getMsgGenericaErroInesperado(e.getMessage()));
  } finally {
      System.gc();
  }
}

CartaRemessaBO.java - Método executar

public List<Long> executar(CartaRemessa cartaRemessa, String mesRefLogado) 
  throws RestricaoNegocioExcecao, CampoObrigatorioNaoInformadoExcecao {
  
  // Gera logs
  log.info("Gerando carta remessa [{}]", cartaRemessa.getMesReferencia());
  log.info("Gerando carta remessa mesReferencia Logado [{}]", mesRefLogado);		
  
  // Valida se os campos obrigatórios foram preenchidos
  validarCamposObrigatorios(cartaRemessa);

  // realiza uma série de verificações de regras de negócios em um objeto CartaRemessa. 
  // Primeiro, verifica se o modelo de CartaRemessa é uma "Carta Acerto" e se certas condições são cumpridas. 
  // Se não forem, uma exceção é lançada. Em seguida, se o objeto CartaRemessa tiver um número, o método 
  // verifica se já existe um arquivo bancário associado a esse número e modelo. Se existir, outra exceção
  // é lançada. Depois, procura uma CartaRemessa existente com o mesmo número e, se encontrada, atualiza os
  // valores de lotacaoInicial e lotacaoFinal e o modelo do objeto CartaRemessa original, e então exclui 
  // a CartaRemessa encontrada.
  validarRegraNegocio(cartaRemessa);

  // Instancia a interface ICartaRemessaServiço de acordo com o modelo da carta remessa
  ICartaRemessaServico cartaRemessaServico = cartaRemessaServicoFabrica.getServico(cartaRemessa.getCartaRemessaModeloEnum());

  // Chama o método gerarCartaRemessa(cartaRemessa, mesRefLogado do serviço cartaRemessaServiço
  return cartaRemessaServico.gerarCartaRemessa(cartaRemessa, mesRefLogado);
}

CartaRemessaAcertoSalarioLiquidoServico.java - Método gerarCartaRemessa

public List<Long> gerarCartaRemessa(CartaRemessa cartaRemessa, String mesRefLogado) throws RestricaoNegocioExcecao {
	// Log
    log.debug("Gerando o Acerto da Carta Remessa do Tipo {}", cartaRemessa.getCartaRemessaModeloEnum().getDescricaoFormatada());

    // Retorna o resultado da função gerarAcerto(cartaRemessa, mesRefLogado)
    return gerarAcerto(cartaRemessa, mesRefLogado);
}

AbstractCartaRemessaServico.java - Método gerarAcerto

protected List<Long> gerarAcerto(CartaRemessa cartaRemessa, String mesRefLogado) throws RestricaoNegocioExcecao {

 // cria lista de objetos do tipo CartaRemessaDetalhe
List<CartaRemessaDetalhe> cartaRemessaDetalheList = cartaRemessaDetalhePersistencia.buscarCartaRemessaDetalheGerarCartaRemessaDoisAcerto(
QueryUtil.prepararParametro(cartaRemessa.getLotacaoInicial()), cartaRemessa.getLotacaoFinal(),
QueryUtil.prepararParametro(cartaRemessa.getMatriculaInicial()), QueryUtil.prepararParametro(cartaRemessa.getMatriculaFinal()),
QueryUtil.prepararParametro(cartaRemessa.getIdentificadorUnidadeGestora()), QueryUtil.prepararParametro(cartaRemessa.getRotina()),
MesReferenciaUtil.converterMesAnoParaAnoMesReferencia(cartaRemessa.getMesReferencia()),
QueryUtil.prepararParametro(cartaRemessa.getCartaRemessaModeloEnum().getDuasPosicoesFinais()));

// Adiciona diversos parâmetros ao final da lista cartaRemessaDetalheList
cartaRemessaDetalheList.addAll(cartaRemessaDetalhePersistencia.buscarCartaRemessaDetalheGerarCartaRemessaAcerto(
QueryUtil.prepararParametro(cartaRemessa.getLotacaoInicial()), cartaRemessa.getLotacaoFinal(),
QueryUtil.prepararParametro(cartaRemessa.getMatriculaInicial()), QueryUtil.prepararParametro(cartaRemessa.getMatriculaFinal()),
QueryUtil.prepararParametro(cartaRemessa.getIdentificadorUnidadeGestora()), QueryUtil.prepararParametro(cartaRemessa.getRotina()),
MesReferenciaUtil.converterMesAnoParaAnoMesReferencia(cartaRemessa.getMesReferencia()),
QueryUtil.prepararParametro(cartaRemessa.getCartaRemessaModeloEnum().getDuasPosicoesFinais())));

  // retorna o resultado da função montarCartaAcerto(cartaRemessa, cartaRemessaDetalheList, mesRefLogado)
  return montarCartaAcerto(cartaRemessa, cartaRemessaDetalheList, mesRefLogado);
}

AbstractCartaRemessaServico.java - Método montarCartaAcerto

private List<Long> montarCartaAcerto(CartaRemessa cartaRemessa, List<CartaRemessaDetalhe> detalhes, String mesRefLogado)
throws RestricaoNegocioExcecao {
  
  // Verifica se a lista de detalhes da carta de remessa está vazia. Se estiver, retorna uma lista vazia.
  if (detalhes.isEmpty()) {
      return Collections.emptyList();
  }
  
  // Verifica se ja tem algum arquivo bancário gerado sem retorno
  List<Long> servidoresParaAcerto = new ArrayList<>();
  detalhes.forEach(item -> servidoresParaAcerto.add(item.getServidorMensal().getId()));
  
  // A lista de chave sera utilizada para verificar se o servidor/pensao
  // judicial/pensao vitalicia ja tiveram acerto gerado, ou carta sem retorno
  List<String> detalhesChaves = new ArrayList<>();
  detalhes.forEach(item -> detalhesChaves.add(item.getChave()));
  
  // Busca os servidor que ja tem acerto
  List<ArquivoBancario> arquivoJaGerado = new ArrayList<>();
  int cont = 0;
  int it = servidoresParaAcerto.size();
  List<Long> idsServidorTemp = new ArrayList<>();
  for (Long id : servidoresParaAcerto) {
      idsServidorTemp.add(id);
      cont++;
      if (idsServidorTemp.size() == 2000 || cont == it) {
          arquivoJaGerado.addAll(cartaRemessaDetalhePersistencia.buscarServidorQueJaTenhamAcerto(idsServidorTemp,
                  MesReferenciaUtil.converterMesAnoParaAnoMesReferencia(cartaRemessa.getMesReferencia()), cartaRemessa.getModelo(),
                  cartaRemessa.getRotina()));
          idsServidorTemp.clear();
      }
  }
  
  // Verifica se ja teve algum retorno com sucesso, caso tenha o mesmo sera
  // retirado da nova carta
  List<String> retirar = new ArrayList<>();
  for (String chave : detalhesChaves) {
      arquivoJaGerado.stream().forEach(arq -> {
          if (chave.equals(arq.getChave())
                  && ("00".equals(arq.getStatusRetorno()) || "BD".equals(arq.getStatusRetorno()) || "".equals(arq.getStatusRetorno()))) {
              retirar.add(arq.getChave());
          }
      });
  }
  
  // Remove os servidores que ja teve sucesso
  detalhes = detalhes.stream().filter(item -> !retirar.contains(item.getChave())).collect(Collectors.toList());
  
  // Remover os servidores que estao sem arquivo, ou seja, ja estao em carta mais
  // não tem arquivo gerado
  arquivoJaGerado.clear();
  retirar.clear();
  
  List<CartaRemessaDetalhe> servidoresSemArquivo = new ArrayList<>();
  cont = 0;
  it = servidoresParaAcerto.size();
  idsServidorTemp = new ArrayList<>();
  for (Long id : servidoresParaAcerto) {
      idsServidorTemp.add(id);
      cont++;
      if (idsServidorTemp.size() == 2000 || cont == it) {
          servidoresSemArquivo.addAll(cartaRemessaDetalhePersistencia.buscarServidorQueJaTenhamAcertoENaoTermArquivoBancario(idsServidorTemp,
                  MesReferenciaUtil.converterMesAnoParaAnoMesReferencia(cartaRemessa.getMesReferencia()), cartaRemessa.getModelo(),
                  cartaRemessa.getRotina()));
          idsServidorTemp.clear();
      }
  }
  
  servidoresSemArquivo.forEach(item -> retirar.add(item.getChave()));
  detalhes = detalhes.stream().filter(item -> !retirar.contains(item.getChave())).collect(Collectors.toList());
  
  // Se não tiver resultado envia mensagem
  if (detalhes.isEmpty()) {
      throw new RestricaoNegocioExcecao(mensagemServicoRh.getMensagem(MensagemChaveRh.MENSAGEM_GERACAO_CARTA_REMESSA_SEM_RESULTADO));
  }

  // Agrupa os detalhes da carta remessa restantes por lotacaoMensalId e armazena em um mapa.
  Map<Long, List<CartaRemessaDetalhe>> cartaRemessaDetalhePorLotacaoMensalId = new HashMap<>();
  detalhes.forEach(item -> {
      Long lotacaoMensalId = item.getCartaRemessa().getLotacaoMensal().getId();
      if (cartaRemessaDetalhePorLotacaoMensalId.containsKey(lotacaoMensalId)) {
          cartaRemessaDetalhePorLotacaoMensalId.get(lotacaoMensalId).add(item);
      } else {
          cartaRemessaDetalhePorLotacaoMensalId.put(lotacaoMensalId, new ArrayList<CartaRemessaDetalhe>(Arrays.asList(item)));
      }
  });
  
  List<Long> cartaRemessaGeradaIdList = new ArrayList<>();
  List<String> matriculasJaEnviadas = new ArrayList<>();

  // Para cada lotacaoMensalId, cria uma nova carta remessa. Calcula o valor total da nova 
  // carta remessa somando os valores de todos os detalhes da carta remessa que pertencem a essa lotacaoMensalId.
  for (Long chave : cartaRemessaDetalhePorLotacaoMensalId.keySet()) {
      List<CartaRemessaDetalhe> cartaRemessaDetalheMapList = cartaRemessaDetalhePorLotacaoMensalId.get(chave);
  
      // Soma Total da Carta Remessa
      Fracao valorTotalCarta = new Fracao(0d);
  
      // Lista para armazenas os detalhes da carta remessa
      List<CartaRemessaDetalhe> cartaRemessaDetalheList = new ArrayList<>();
  
      // Cria a Carta Remessa
      CartaRemessa novaCartaRemessa = criarCartaRemessa(cartaRemessa.getRotinaCalculoEnum(), cartaRemessa.getCartaRemessaModeloEnum(),
              cartaRemessaDetalheMapList.get(0).getCartaRemessa().getLotacaoMensal().getContaDebito().getContaDebito(),
              cartaRemessaDetalheMapList.get(0).getCartaRemessa().getLotacaoMensal().getContaDebito().getConvenio(),
              cartaRemessaDetalheMapList.get(0).getCartaRemessa().getLotacaoMensal().getContaDebito().getAgencia(),
              cartaRemessaDetalheMapList.get(0).getCartaRemessa().getLotacaoMensal(), cartaRemessa.getNumero());
  
      for (CartaRemessaDetalhe item : cartaRemessaDetalheMapList) {
          if (item.getValor() > 0d) {
              if (!matriculasJaEnviadas.contains(item.getChave())) {
                  matriculasJaEnviadas.add(item.getChave());
                  valorTotalCarta = valorTotalCarta.somarCom(new Fracao(item.getValor()));
  
                  cartaRemessaDetalheList.add(item);
              }
          }
      }

    // Atualiza os dados bancários para os detalhes da carta de remessa, 
    // salva a nova carta de remessa, e salva os detalhes da carta de remessa.
      if (!cartaRemessaDetalheList.isEmpty()) {
          atualizarDadosBancarios(cartaRemessaDetalheList);
          // Valor Total da Carta
          novaCartaRemessa.setValor(valorTotalCarta.doubleValor());
          // Salva a Carta Remessa
          salvarCartaRemessa(novaCartaRemessa);
          // Salva os Itens da Carta Remessa Detalhe
          salvarCartaRemessaDetalhe(novaCartaRemessa, cartaRemessaDetalheList, mesRefLogado);
          // preenche a lista com todos os ids das carta remessas geradas
          cartaRemessaGeradaIdList.add(novaCartaRemessa.getId());
      }
  }

  // Retorna uma lista de IDs de todas as cartas de remessa que foram geradas
  return cartaRemessaGeradaIdList;
}

Como podemos perceber no código acima, dentro do método montarCartaAcerto, nas linhas 78 a 86 , a organização de cartas remessa por lotação mensal já é executada de forma integral. Isso é feito por meio da construção de um mapa, no qual o ID da lotação mensal atua como chave, e os detalhes da carta remessa que correspondem àquela lotação mensal se tornam os valores associados a essa chave. Essa estruturação permite que todas as cartas remessa sejam automaticamente categorizadas de acordo com a respectiva lotação mensal.

2.2 Geração dos Relatórios

Atualmente, o sistema Governa gera os relatórios da Carta Remessa em 3 etapas: image.png

Na Etapa 1, a função buscarListaCartaremessaDetalheVisao() é chamada para buscar os detalhes da Carta Remessa. Os detalhes são retornados como uma lista de objetos CartaRemessaDetalheVisao.

Na Etapa 2, a função gerarRelatorioCartaRemessa() é chamada com a lista de detalhes da Carta Remessa obtida na etapa 1. Esta função gera um relatório baseado nos detalhes fornecidos e retorna um ByteArrayOutputStream que contém os dados do relatório.

Na Etapa 3, a função gerarStreamZip() é chamada com o ByteArrayOutputStream gerado na etapa 2. Esta função define o nome do arquivo zip, prepara um fluxo de download com o arquivo para o relatório, que pode ser enviado para o cliente para download.

Etapa 1 - CartaRemessaBO - Método - BuscarListaCartaRemessaDetalheVisao

public List<CartaRemessaDetalheVisao> buscarListaCartaremessaDetalheVisao(CartaRemessa cartaRemessa, List<Long> idsCarta) {
  
  // Verifica se a lista de idsCarta possui menos de 2000 elementos
  if (idsCarta.size() < 2000) {
      // Verifica o tipo de modelo de carta remessa
      if (CartaRemessaModeloEnum.eCartaConsignacao(cartaRemessa.getCartaRemessaModeloEnum())) {
          // Busca os detalhes da carta remessa consignação pelo idCartaRemessaDetalhe
          return cartaRemessaDetalheConsignacaoRepositorio
                  .findCartaRemessaDetalheVisaoByIdCartaRemessaDetalhe(QueryUtil.preparaParametroListaNaoVazia(idsCarta));
      } else {
          // Busca os detalhes da carta remessa pelo idCartaRemessaDetalhe
          return cartaRemessaDetalheRepositorio
                  .findCartaRemessaDetalheVisaoByIdCartaRemessaDetalhe(QueryUtil.preparaParametroListaNaoVazia(idsCarta));
      }
  }
  
  List<CartaRemessaDetalheVisao> lista = new ArrayList<>();
  List<Long> idsCartaTemp = new ArrayList<>();
  
  // Itera sobre os idsCarta
  for (int i = 0; i < idsCarta.size(); i++) {
      
      // Adiciona o idCarta atual na lista temporária idsCartaTemp
      idsCartaTemp.add(idsCarta.get(i));
      
      // Verifica se atingiu o limite de 2000 elementos na lista temporária
      if (i % 2000 == 0) {
          // Verifica o tipo de modelo de carta remessa
          if (CartaRemessaModeloEnum.eCartaConsignacao(cartaRemessa.getCartaRemessaModeloEnum())) {
              // Busca os detalhes da carta remessa consignação pelo idCartaRemessaDetalhe na lista temporária
              lista.addAll(cartaRemessaDetalheConsignacaoRepositorio
                      .findCartaRemessaDetalheVisaoByIdCartaRemessaDetalhe(QueryUtil.preparaParametroListaNaoVazia(idsCartaTemp)));
              // Limpa a lista temporária para os próximos elementos
              idsCartaTemp.clear();
          } else {
              // Busca os detalhes da carta remessa pelo idCartaRemessaDetalhe na lista temporária
              lista.addAll(cartaRemessaDetalheRepositorio
                      .findCartaRemessaDetalheVisaoByIdCartaRemessaDetalhe(QueryUtil.preparaParametroListaNaoVazia(idsCartaTemp)));
              // Limpa a lista temporária para os próximos elementos
              idsCartaTemp.clear();
          }
      }
  }
  
  // Busca os detalhes restantes que não foram buscados na iteração anterior
  if (CartaRemessaModeloEnum.eCartaConsignacao(cartaRemessa.getCartaRemessaModeloEnum())) {
      // Busca os detalhes da carta remessa consignação pelo idCartaRemessaDetalhe na lista temporária
      lista.addAll(cartaRemessaDetalheConsignacaoRepositorio
              .findCartaRemessaDetalheVisaoByIdCartaRemessaDetalhe(QueryUtil.preparaParametroListaNaoVazia(idsCartaTemp)));
  } else {
      // Busca os detalhes da carta remessa pelo idCartaRemessaDetalhe na lista temporária
      lista.addAll(cartaRemessaDetalheRepositorio
              .findCartaRemessaDetalheVisaoByIdCartaRemessaDetalhe(QueryUtil.preparaParametroListaNaoVazia(idsCartaTemp)));
  }
  
  return lista;
}

Etapa 2 - AbstractCartaRemessaMB.java - Método gerarRelatorioCartaRemessa

public StreamedContent gerarRelatorioCartaRemessa(String numeroCarta, String nomeCartaRemessa, String mesReferencia, String lotacaoCodigo)
throws RelatorioGeracaoExcecao {
  try {

    // recebe uma carta remessa e uma lista de identificadores de carta. Ela divide os identificadores em lotes de até 2000 elementos e realiza consultas aos repositórios correspondentes para buscar os detalhes da carta remessa para cada lote. Os detalhes encontrados são agregados em uma lista final, que é retornada pela função. Dessa forma, a função permite buscar os detalhes da carta remessa de forma eficiente, trabalhando com lotes de identificadores em vez de processar todos os elementos de uma só vez.
    List<CartaRemessaDetalheVisao> listaCartaRemessaDetalheVisao = this.buscarListaCartaRemessaDetalheVisao();
  
      // Removida a geração do arquivo para o banco a pedido do cliente
      // ByteArrayOutputStream relatorioCartaRemessaViaBanco = geradorCartaRemessa.gerarRelatorioCartaRemessa(
      // super.getEmpresaAutenticada(),
      // this.cartaRemessa, listaCartaRemessaDetalheVisao, IRelatorioCartaRemessaGerador.ViaCartaRemessa.BANCO);

      // Converte em buffer de saída o relatorio retornado pelo método gerarRelatorioCartaRemessa
      ByteArrayOutputStream relatorioCartaRemessaViaFinanceiro = geradorCartaRemessa.gerarRelatorioCartaRemessa(
              super.getEmpresaAutenticada(), this.cartaRemessa, listaCartaRemessaDetalheVisao,
              IRelatorioCartaRemessaGerador.ViaCartaRemessa.FINANCEIRO);

      //  retorna o zip gerado pelo método gerarStreamZip
      return this.gerarStreamZip(relatorioCartaRemessaViaFinanceiro, numeroCarta, nomeCartaRemessa,
              mesReferencia, lotacaoCodigo);
      
  } catch (Exception e) {
      throw new RelatorioGeracaoExcecao(e);
  }
}

Etapa 3 - AbstractCartaRemessaMB.java - Método gerarStreamZip

private StreamedContent gerarStreamZip(ByteArrayOutputStream relatorioCartaRemessaViaFinanceiro, String numeroCarta, String modeloArquivo, String mesReferencia, String lotacaoCodigo) throws IOException {

//		Removido geração do arquivo para o banco a pedido do cliente
//		ArquivoOutputStream relatorioOutBanco = new ArquivoOutputStream(relatorioCartaRemessaViaBanco,
//				String.format(IRelatorioCartaRemessaGerador.ARQUIVO_PDF_BANCO, numeroCarta, modeloArquivo, mesReferencia, lotacaoCodigo));

  // recebe o relatorioCartaRemessaViaFinanceiro como entrada e utiliza a formatação adequada para o nome do arquivo PDF financeiro, utilizando os parâmetros fornecidos. 
  ArquivoOutputStream relatorioOutFinanceiro = new ArquivoOutputStream(relatorioCartaRemessaViaFinanceiro,
	String.format(IRelatorioCartaRemessaGerador.ARQUIVO_PDF_FINANCEIRO, numeroCarta, modeloArquivo, mesReferencia, lotacaoCodigo));

  // Aqui, é criado um ByteArrayOutputStream chamado out para armazenar os dados do arquivo ZIP. A função ZipUtil.zipar() é chamada para compactar o relatorioOutFinanceiro e armazenar o resultado no out.
  ByteArrayOutputStream out = new ByteArrayOutputStream();
  ZipUtil.zipar(out, relatorioOutFinanceiro);

  // permite que os dados do arquivo ZIP sejam lidos a partir de um fluxo de entrada.
  InputStream is = new ByteArrayInputStream(out.toByteArray());

  gerouRelatorio = true;

  // O nome do arquivo ZIP é formatado e é removido qualquer espaço em branco ou acentuação indesejada utilizando funções auxiliares.
  String zipNome = String.format(IRelatorioCartaRemessaGerador.ARQUIVO_ZIP, numeroCarta, modeloArquivo, mesReferencia, lotacaoCodigo)
          .replace(" ", "");
  zipNome = StringUtil.removerAcentos(zipNome);

  // retorna o arquivo zip para download com nome definido
  return new DefaultStreamedContent(is, "application/zip", zipNome);
}

Na Etapa 1, é feita a busca dos detalhes da carta remessa com base em uma lista de identificadores. Caso essa lista possua menos de 2000 elementos, os detalhes são buscados diretamente nos repositórios correspondentes. Caso contrário, os identificadores são divididos em lotes de até 2000 elementos e consultas são realizadas em cada lote, agregando os resultados em uma lista final.

Na Etapa 2, é invocada a função gerarRelatorioCartaRemessa para gerar o relatório em formato PDF. Essa função recebe como parâmetros o número da carta, o nome da carta remessa, o mês de referência e o código da lotação. É realizada a busca dos detalhes da carta remessa e, em seguida, é gerado o relatório utilizando o gerador apropriado.

Na Etapa 3, é executada a função gerarStreamZip para criar um arquivo ZIP contendo o relatório em formato PDF financeiro. O relatório é convertido em um ByteArrayOutputStream, que é compactado utilizando a função ZipUtil.zipar. Em seguida, o arquivo ZIP é disponibilizado para download com um nome formatado e sem caracteres indesejados.

Em conjunto, essas três etapas permitem a geração eficiente de um relatório de carta remessa, buscando os detalhes necessários, gerando o relatório no formato adequado e fornecendo-o como um arquivo ZIP para download.

3. Possíveis Soluções

3.1 Separação dos Relatórios por Lotação

Considerando o conhecimento das regras de geração dos arquivos de relatório da carta remessa obtidos por meio da análise aprofundada do código fonte e de todo o fluxo seguido para a atual geração da Carta Remessa e de seus relatórios, observamos que uma possível solução seria modificar o método gerarRelatorioCartaRemessade forma que funcione de acordo com as seguintes etapas:  

image.png

Na Etapa 1, é realizada a busca dos detalhes da carta remessa, levando em consideração a divisão por lotações. Essa abordagem permite a obtenção dos detalhes de forma segmentada, melhorando o desempenho e a organização dos dados.

Na Etapa 2, os relatórios são gerados para cada lotação. Utilizando um laço de repetição, o relatório é gerado individualmente para cada lotação, garantindo que cada um seja devidamente processado e formatado.

Por fim, na Etapa 3, os relatórios gerados são compactados em arquivos ZIP. Utilizando novamente um laço de repetição, cada relatório é adicionado ao arquivo ZIP correspondente à sua lotação. Essa abordagem permite a organização dos relatórios em lotações separadas, facilitando a posterior consulta e análise dos dados.

public StreamedContent gerarRelatorioCartaRemessa(String numeroCarta, String nomeCartaRemessa, String mesReferencia, String lotacaoCodigo)
throws RelatorioGeracaoExcecao {
  try {

    //  ### ETAPA 1
    // recebe uma carta remessa e uma lista de identificadores de carta. Ela divide os identificadores em lotes de até 2000 elementos e realiza consultas aos repositórios correspondentes para buscar os detalhes da carta remessa para cada lote. Os detalhes encontrados são agregados em uma lista final, que é retornada pela função. Dessa forma, a função permite buscar os detalhes da carta remessa de forma eficiente, trabalhando com lotes de identificadores em vez de processar todos os elementos de uma só vez.
    List<CartaRemessaDetalheVisao> listaCartaRemessaDetalheVisao = this.buscarListaCartaRemessaDetalheVisao();

      // ### ETAPA 2
      // Criar método para obter a lista de lotações

      // Criar um mapa para armazenar os relatórios por lotação
      
      // Gerar o relatório para cada lotação  
        // Armazenar no mapa cada relatório gerado usando lotação como chave
      
      // Criar um mapa de arquivo ZIP para cada lotação 
      // (Cada lotação terá um zip contendo seus relatórios gerados)

      // Usa um laço para pegar as chaves dos relatórios armazenados no mapa, gera os pdf's com os nomes corretos, zipa e adiciona no mapa de arquivos ZIP usando a lotação como chave

      // ### ETAPA 3 
      // Junta todos os arquivos ZIP em um único arquivo

      // converte o zip final em ByteArrayOutputStream para permitir o download   

      // Gera o nome do arquivo final de acordo com o método gerarStreamZip
     
      // definir a variável gerouRelatorio como true
       gerouRelatorio = true;      
    
      //  retorna o zip final gerado
      
  } catch (Exception e) {
      throw new RelatorioGeracaoExcecao(e);
  }
}

História

O quê: Eu, dev, preciso criar um método para obter  uma lista de Lotação

Por quê: para que seja possível separar os relatórios por Lotação.

Regras e Validações --
Pontuação 3

História

O quê: Eu, dev, preciso criar um mapa para armazenar os arquivos ZIP por lotação

Por quê: para que seja possível separar os relatórios por Lotação.

Regras e Validações --
Pontuação 2
História

O quê: Eu, dev, preciso criar um laço para gerar o relatório para cada lotação

Por quê: para que seja possível separar os relatórios por Lotação.

Regras e Validações --
Pontuação 3

História

O quê: Eu, dev, preciso armazenar o relatório gerado no mapa utilizando a lotação como chave

Por quê: para que seja possível separar os relatórios por Lotação.

Regras e Validações --
Pontuação 2

História

O quê: Eu, dev, preciso criar um mapa de arquivo ZIP para adicioná-los separados por lotação

Por quê: para que seja possível separar os relatórios por Lotação.

Regras e Validações --
Pontuação 2

História

O quê: Eu, dev, preciso criar um laço para montar o arquivo zip,
gerando os pdf's com nome formatado,
adicionando ao mapa de arquivo ZIP.

Por quê: para que seja possível separar os relatórios por Lotação.

Regras e Validações --
Pontuação 3

História

O quê: Eu, dev, preciso combinar todos os arquivos ZIP em um único arquivo ZIP final

Por quê: para que seja possível separar os relatórios por Lotação.

Regras e Validações --
Pontuação 3

História

O quê: Eu, dev, preciso montar o nome do arquivo zip final

Por quê: para que seja possível separar os relatórios por Lotação.

Regras e Validações --
Pontuação 2

História

O quê: Eu, dev, preciso retornar o arquivo zip gerado disponível para download

Por quê: para que seja possível separar os relatórios por Lotação.

Regras e Validações --
Pontuação 2

4. Conclusão

Ao estudar o código fonte do Governa, foi possível compreender a funcionalidade de geração de Carta Remessa e de seus relatórios em PDF. O processo atual  consiste em buscar os detalhes da carta remessa, gerar os relatórios em PDF, criar um arquivo ZIP contendo os relatórios. No entanto, o código não separa os relatórios por lotação, o que pode demonstrar ser uma limitação em cenários em que é necessário agrupar os relatórios por essa informação.

Com base nessa análise, é possível concluir que é necessário fazer modificações no código fonte para separar os relatórios PDF por lotação.