Ir para o conteúdo principal

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árioEBRABAN – 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 Separaçã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 {
      
    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);
      
      ByteArrayOutputStream relatorioCartaRemessaViaFinanceiro = geradorCartaRemessa.gerarRelatorioCartaRemessa(
              super.getEmpresaAutenticada(), this.cartaRemessa, listaCartaRemessaDetalheVisao,
              IRelatorioCartaRemessaGerador.ViaCartaRemessa.FINANCEIRO);
      
      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);
}

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

WORK IN PROGRESS....


3. Possíveis Soluções

WORK IN PROGRESS....

História

O quê: Eu, dev, preciso criar xxxxxxxxxxxxxxxxxxxxxx

Por quê: para xxxxxxxxxxxxxxxxxxxxxxxxx

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


4. Conclusão

Ao estudar o código fonte do Governa, foi possível identificar 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.