Sunday, November 25, 2018

Máscaras no JasperReport: o jeito certo

Durante o desenvolvimento de relatórios, um trabalho para o qual geralmente é relegado menor prioriade e que, portanto, ao final costumam ser feitos na base da gambiarra, são as máscaras dos campos.

É bastante comum soluções que colocam toda lógica de formatação do valor do campo de texto dentro do próprio campo de texto, como no screenshot abaixo tirado do editor de expressão do JasperStudio:


O código da expressão na imagem é o que segue:
 ($F{telefoneEmpresaAdm} != null && $F{telefoneEmpresaAdm}.length() == 13) ? 
 ("(" + ($F{telefoneEmpresaAdm}.substring(0,4).trim()) + ") " + $F{telefoneEmpresaAdm}.substring(4,9) + "-" + $F{telefoneEmpresaAdm}.substring(9,13)) : 
 ($F{telefoneEmpresaAdm} != null && $F{telefoneEmpresaAdm}.length() == 12) ? 
 "(" + (($F{telefoneEmpresaAdm}.substring(0,4).trim())+ ") " + $F{telefoneEmpresaAdm}.substring(4,8) + "-" + $F{telefoneEmpresaAdm}.substring(8,12)) : 
 ${telefoneEmpresaAdm}
    
Tudo isso dentro de um mísero campo de texto!

Esse código representa a lógica para criar a máscara para um campo de texto cujo conteúdo é um número de telefone. Esta lógica supostamente atende aos diversos cenários em que um número de telefone pode ocorrer: celular ou fixo? contém DDD? em algumas cidades do Brasil o número do  telefone fixo tem mais dígitos do que outras, etc.

Essa abordagem pode até resolver o problema no curto prazo, mas obviamente não é uma boa solução em termos de manutenção do projeto porque a lógica de formatação fica espalhada nos campos. Em projetos com dezenas ou até centenas de relatórios, essa estratégia se torna inviável.

A solução ideal é centralizar tais tarefas em um único local, de modo que todos os relatórios busquem alí a máscara para um respectivo campo. Esse local é uma classe com dezenas de métodos estáticos cada um contendo uma lógica de formatação para um determinado tipo de campo e cenário. Podemos inclusive invocar esses métodos a partir da template .jrxml, seja em produção ou em teste.

Projeto de Exemplo

Para exemplificar esta abordagem criamos um projeto que chama um relatório cuja fonte de dados é um banco de dados relacional. Para criar e popular o banco de dados, execute o seguinte script SQL (o banco de dados que utilizo neste exemplo é o MySQL):
create database if not exists bancoDeDados;
use bancoDeDados;
create table if not exists algumaEntidade(cep varchar(255), 
            telefone varchar(255), 
            data date, 
            valor decimal(5,2), 
            cpf varchar(255));
                                 
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');    
Nesse script criamos colunas com tipos de dados que aparecem com frequência em qualquer relatório e sempre requerem algum tipo de máscaras formatação.

A classe Mascaras assume a responsabilidade de controlar toda lógica de geração de máscaras para todos os relatórios do projeto:
package main;

import java.math.BigDecimal;
import java.sql.Date;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

import javax.swing.text.MaskFormatter;

public class Mascaras {

 public static String getDataAtualCompleta() {
  
  DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd 'de' MMMM 'de' yyyy", new Locale("pt", "BR"));
  
  LocalDate hoje = LocalDate.now();
  return dtf.format(hoje);
 }
 
 public static String getCEP(String cep) {
  
  try {
   MaskFormatter mask = new MaskFormatter("#####-###");
   mask.setValueContainsLiteralCharacters(false);
   return mask.valueToString(cep);
  }
  catch (ParseException ex) {
   ex.printStackTrace();
   return null;
  }
 }
 
 public static String getTelefone(String telefone) {
  
  try {
   String pattern = null;
   
   if(telefone.length() == 10) 
    pattern = "(##) ####-####";  //fixo com ddd   
   
   else if(telefone.length() == 11)
    pattern = "(##) #-####-####"; // celular com ddd
   //else if... outros formatos
   
   MaskFormatter mask = new MaskFormatter(pattern);
   mask.setValueContainsLiteralCharacters(false);
   return mask.valueToString(telefone);
  }
  catch (ParseException ex) {
   ex.printStackTrace();
   return null;
  }
 }
 
 public static String getDataSimples(Date date) {
  
  DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd/MM/yyyy", new Locale("pt", "BR"));  
  LocalDate hoje = date.toLocalDate();
  return dtf.format(date);
 }
 
 public static String getCPF(String cpf) {
  
  try {
   MaskFormatter mask = new MaskFormatter("###.###.###-##");
   mask.setValueContainsLiteralCharacters(false);
   return mask.valueToString(cpf);
  }
  catch (ParseException ex) {
   ex.printStackTrace();
   return null;
  }
 }
 
 public static String getValorMonetario(BigDecimal valor, boolean usaSimboloMoeda) {
  
  NumberFormat format = NumberFormat.getCurrencyInstance(new Locale("pt", "BR"));
  
  DecimalFormatSymbols dfs = ((DecimalFormat)format).getDecimalFormatSymbols();
  
  if(!usaSimboloMoeda)
   dfs.setCurrencySymbol("");
  
  ((DecimalFormat)format).setDecimalFormatSymbols(dfs);
  
  return format.format(valor);  
 }
}    
Em seguida temos a template jrxml que representa o modelo do relatório. Ela contém a query que busca os dados no banco de dados que criamos:
<?xml version="1.0" encoding="UTF-8"?>

<jasperReport name="TesteMascaras">
  
 <queryString language="SQL">
 
  SELECT algumaEntidade.cep,
   algumaEntidade.telefone,
   algumaEntidade.data,
   algumaEntidade.valor,
   algumaEntidade.cpf
  FROM algumaEntidade
 
 </queryString>
 
 <field name="cep" class="java.lang.String"/>
 <field name="telefone" class="java.lang.String"/>
 <field name="data" class="java.sql.Date"/>
 <field name="valor" class="java.math.BigDecimal"/>
 <field name="cpf" class="java.lang.String"/>
 
 <variable name="somaValor" class="java.math.BigDecimal" calculation="Sum">
  <variableExpression>$F{valor}</variableExpression>
 </variable>
 
 <title>
  <band height="46"  >
   <staticText>
    <reportElement x="29" y="8" width="180" height="30" />
    <textElement>
     <font isBold="true"/>
    </textElement>
    <text>MÁSCARAS</text>
   </staticText>
   <textField>
    <reportElement x="370" y="8" width="175" height="30" />
    <textFieldExpression>main.Mascaras.getDataAtualCompleta()</textFieldExpression>
   </textField>
  </band>
 </title>
 <columnHeader>
  <band height="61"  >
   <staticText>
    <reportElement x="0" y="0" width="80" height="30" />
    <text>cep</text>
   </staticText>
   <staticText>
    <reportElement x="90" y="0" width="78" height="30" />
    <text>telefone</text>
   </staticText>
   <staticText>
    <reportElement x="180" y="0" width="100" height="30" />
    <text>data</text>
   </staticText>
   <staticText>
    <reportElement x="422" y="0" width="100" height="30" />
    <text>valor</text>
   </staticText>
   <staticText>
    <reportElement x="297" y="0" width="100" height="30" />
    <text>cpf</text>
   </staticText>
  </band>
 </columnHeader>
 <detail>
  <band height="40"  >
   <textField>
    <reportElement x="0" y="0" width="80" height="30" />
    <textFieldExpression>main.Mascaras.getCEP($F{cep})</textFieldExpression>
   </textField>
   <textField>
    <reportElement x="90" y="0" width="85" height="30" />
    <textFieldExpression>main.Mascaras.getTelefone($F{telefone})</textFieldExpression>
   </textField>
   <textField>
    <reportElement x="180" y="0" width="100" height="30" />
    <textFieldExpression>main.Mascaras.getDataSimples($F{data})</textFieldExpression>
   </textField>
   <textField>
    <reportElement x="422" y="0" width="100" height="30" />
<!--     podmeos inclusive passar parametros que não sejam o campo $F, neste caso passomos um boolean literal -->
    <textFieldExpression>main.Mascaras.getValorMonetario($F{valor}, false)</textFieldExpression>
   </textField>
   <textField>
    <reportElement x="297" y="0" width="100" height="30" />
    <textFieldExpression>main.Mascaras.getCPF($F{cpf})</textFieldExpression>
   </textField>
  </band>
 </detail>
 <summary>
  <band height="44"  >
   <textField>
    <reportElement x="410" y="7" width="134" height="30" />
    <textFieldExpression>main.Mascaras.getValorMonetario($V{somaValor}, true)</textFieldExpression>
   </textField>
  </band>
 </summary>
</jasperReport>
    
E a classe Main que inicia a aplicação e chama o relatório:
package main;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;

import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.view.JasperViewer;

public class Main {

 public static void main(String[] args) throws Exception {

  String url = "jdbc:mysql://localhost/bancoDeDados?useSSL=true&serverTimezone=UTC&user=root&password=root";

  Class.forName("com.mysql.cj.jdbc.Driver");
  
  try (Connection conn = DriverManager.getConnection(url);) {

   InputStream stream = Main.class.getResourceAsStream("TesteMascaras.jxml");
   
   JasperReport jasper = JasperCompileManager.compileReport(stream);
     
   JasperPrint print = JasperFillManager.fillReport(jasper, null, conn);
   
   JasperViewer viewer = new JasperViewer(print);
   viewer.setVisible(true);
  }
 }
}    
O resultado é o relatório com os campos devidamente formatados abaixo.


Esse é o padrão correto para criação de máscaras em relatórios no Java.

"Assim diz Javé dos exércitos: Neste lugar agora arruinado, sem gente e sem criações, e também em todas as suas cidades haverá pastagens, onde os pastores farão suas ovelhas repousar."
Jr 33,12


       

No comments:

Post a Comment