Introdução
Nesse sétimo post vou mostrar como utilizar alguns eventos do JARCH, BeanValidation, não permitir excluir registro com relacionamento em outra entidade, auto-incremento, formatação do código do lookup e totalizador de coluna na tela de lista.
Totalização Valores na Lista

Normalmente quando temos campos de valores é interessante mostrar o total desse valor. Na tela de lista atual de Lançamento (Conta Pagar e Conta Recebe) temos a coluna valor no grid:
Posso colocar um totalizar nesse grid bastando somente adicionar a propriedade showColumnsTotalizer como true no componente e:divListDatatable do lancamentoList.xhml:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:e="http://jarch.com.br/ui/extension">
<ui:composition template="/paginas/templates/templateListaV2.xhtml">
<ui:define name="panelBodyTemplateLista">
<e:form>
<e:divTitle
title="#{e:bundle('label.lista')} - #{lancamentoListAction.tipoLancamento.description}"
description="#{e:bundle('label.manter')} #{lancamentoListAction.tipoLancamento.description}"/>
<e:divListFilter
title="#{e:bundle('label.filtro')} - #{lancamentoListAction.tipoLancamento.description}"
actionList="#{lancamentoListAction}"
update="@(.list-datatable)"/>
<e:divListDatatable id="listEntityDataTableLancamento"
title="#{e:bundle('label.lista')} - #{lancamentoListAction.tipoLancamento.description}"
actionList="#{lancamentoListAction}" showColumnsTotalizer="true" />
</e:form>
</ui:define>
</ui:composition>
</html>
Com essa alteração a tela de lista aparecerá o total da coluna valor:
Validações
Nossa tela de lançamento está permitindo digitar e gravar valores negativos, então vou utilizar o BeanValidation para impedir isso, anotando o atributo valor do LancamentoEntity:
@Column(name = "vl_lancamento", nullable = false, scale = 2, precision = 12)
@JArchValidRequired("label.valor")
@Positive(message = "message.valorDeveSerMaiorZero")
private BigDecimal valor;
E no ValidationMessages_pt_BR.properties:
message.valorDeveSerMaiorZero=Valor deve ser maior que ZERO
Agora se tentarmos gravar o lançamento com o valor inferior a ZERO:
Agora vou implementar a seguinte a regra de validação, nenhuma conta poderá ser lançada com vencimento inferior ao últimos 10 anos, por exemplo se a data do sistema for dia 27/06/2020 e tentarmos lançar uma conta inferior ao dia 01/01/2010 devemos dar a seguinte mensagem de erro: "Vencimento deve ser a partir de 01/01/2010". Para isso vamos utilizar o evento @JArchValidInsertChange no LancamentoObserver para interceptar o momento de validação para inclusão e alteração:
public class LancamentoObserver {
private void loadCrud(@Observes @JArchEventLoadCrud LancamentoEntity entity) {
InitializeUtils.initializeCollectionLazy(entity);
}
private void validaDataInferiorDezAnos(@Observes @JArchEventValidInsertChange LancamentoEntity lancamento) {
LocalDate vencimentoMinimo = LocalDate.now().minusYears(10).withMonth(1).withDayOfMonth(1);
if (lancamento.getVencimento().isBefore(vencimentoMinimo)) {
throw new ValidationException(BundleUtils.messageBundle("message.vencimentoPartirDe",
DateUtils.formatddMMyyyy(vencimentoMinimo)));
}
}
}
E adicionar a chave no bundle_pt_BR.properties:
message.vencimentoPartirDe=Vencimento deve ser a partir de %s
E como resultado:
Agora vou implementar uma validação para não permitir excluir um Banco, Centro Custo, Categoria ou Pessoa caso exista algum lançamento. Por exemplo nesse momento não poderia deixar excluir o Banco 237 Bradesco porque já existe uma conta lançada para esse banco e essa mesma regra também para os demais (Centro Custo, Categoria e Pessoa). Uma solução seria colocar um evento de @JArchEventValidDelete em cada um deles e verificar se existe uma lancamento atrelado, como por exemplo no BancoObserver:
public class BancoObserver {
private void loadCrud(@Observes @JArchEventLoadCrud BancoEntity entity) {
InitializeUtils.initializeCollectionLazy(entity);
}
private void validaExistenciaLancamento(@Observes @JArchEventValidDelete BancoEntity banco, LancamentoFacade lancamentoFacade) {
if (lancamentoFacade.existeLancamentoBanco(banco)) {
throw new ValidationException(BundleUtils.messageBundle("message.bancoLancadoContaPagarReceber"));
}
}
}
LancamentoFacade:
public class LancamentoFacade extends CrudFacade<LancamentoEntity, ILancamentoManager> {
public boolean existeLancamentoBanco(BancoEntity banco) {
return getManager().existeLancamentoBanco(banco);
}
}
ILancamentoManager:
public interface ILancamentoManager extends ICrudManager<LancamentoEntity> {
boolean existeLancamentoBanco(BancoEntity banco);
}
LancamentoManager:
public class LancamentoManager extends CrudManager<LancamentoEntity> implements ILancamentoManager {
@Override
public boolean existeLancamentoBanco(BancoEntity banco) {
TypedQuery<LancamentoEntity> query = getEntityManager().createQuery(
"FROM lancamento l WHERE l.banco = :banco", LancamentoEntity.class);
query.setParameter("banco", banco);
return !query.getResultList().isEmpty();
}
}
bundle_pt_BR.properties:
message.bancoLancadoContaPagarReceber=Banco j\u00E1 lan\u00E7ado para conta a pagar/receber
E replicaríamos isso para os demais (Centro Custo, Categoria e Pessoa). Isso funcionaria, mas se tivesse outros casos de usos que utiliza-se também essas entidades ? Teríamos que implementar para todas as situações possíveis de relacionamento, isso funcionaria mas além de ser trabalhoso, mas esse tipo de verificação provavelmente passará desapercebido pelo desenvolvedor/analista. E o que pode acontecer no final é termos registros inválidos ou receber um erro do banco de dados (caso exista uma chave estrangeira configurada). Mas tenho uma boa notícia, o desenvolvimento desse tipo de validação pelo programador é desnecessário, porque o JARCH já faz esse tipo automaticamente. Vamos fazer o teste, vamos desfazer as implementações anteriores:
BancoObserver:
public class BancoObserver {
private void loadCrud(@Observes @JArchEventLoadCrud BancoEntity entity) {
InitializeUtils.initializeCollectionLazy(entity);
}
}
LancamentoFacade:
public class LancamentoFacade extends CrudFacade<LancamentoEntity, ILancamentoManager> {
}
ILancamentoManager:
public interface ILancamentoManager extends ICrudManager<LancamentoEntity> {
}
LancamentoManager:
public class LancamentoManager extends CrudManager<LancamentoEntity> implements ILancamentoManager {
}
Ao tentar excluir o Banco 237 - Bradesco:
Ao tentar excluir o Centro Custo CAR - Carro:
E para os demais vai acontecer a mesma coisa, o JARCH já faz esse trabalho pra nós.
Coluna Incremental
Agora vou mostrar como implementar a seguinte necessidade. O cliente que utiliza nossa aplicação solicitou que o campo de código fosse preenchido automaticamente com um sequencial por Pessoa. Exemplo: a primeira conta cadastrada para o Posto Shell seria o código 0000000001, a segunda conta seria o código 0000000002 e assim por diante. Essa sequência tem que ser por Pessoa. Então poderíamos adotar a seguinte estratégia, interceptar o evento @JArchEventInsert e fazer uma busca pelo maior código daquela Pessoa, somar 1 e formatar com zeros a esquerda. Algo parecido com isso:
LancamentoObserver.java
LancamentoObserver.java
private void geraCodigoLancamentoPessoa(@Observes @JArchEventInsert LancamentoEntity lancamento, LancamentoFacade lancamentoFacade) {
String proximoCodigo = lancamentoFacade.proximoCodigoPessoa(lancamento.getPessoa());
lancamento.setCodigo(proximoCodigo);
}
LancamentoFacade.java
public class LancamentoFacade extends CrudFacade<LancamentoEntity, ILancamentoManager> {
public String proximoCodigoPessoa(PessoaEntity pessoa) {
return getManager().proximoCodigoPessoa(pessoa);
}
}
ILancamentoManager.java
public interface ILancamentoManager extends ICrudManager<LancamentoEntity> {
String proximoCodigoPessoa(PessoaEntity pessoa);
}
LancamentoManager.java
public class LancamentoManager extends CrudManager<LancamentoEntity> implements ILancamentoManager {
@Override
public String proximoCodigoPessoa(PessoaEntity pessoa) {
TypedQuery<String> query = getEntityManager().createQuery(
"SELECT MAX(l.codigo) FROM lancamento l WHERE l.pessoa = :pessoa", String.class);
query.setParameter("pessoa", pessoa);
String ultimoCodigo = query.getSingleResult();
String proximoCodigo = NumberUtils.formatZeroLeft(Long.valueOf(ultimoCodigo) + 1, 10);
return proximoCodigo;
}
}
Apesar do código acima estar bem enxuto e de fácil leitura foi necessário implementar um método no LacamentoObserver para interceptar a inclusão, implementar o método no LancamentoFacade e LancamentoManager e ILancamentoManager. Mas todo esse código poderia ser substituído pela anotação @JArchAutoIncrement no atributo codigo do LancamentoEntity:
@Column(name = "cd_lancamento", nullable = false, length = 20)
@Size(max = 20, message = "{message.maxSizeExceeded}")
@JArchAutoIncrement(fieldGroups = {"tipoLancamento", "pessoa"}, size = 10)
@JArchNoClone
private String codigo;
No exemplo acima foi utilizado a anotação @JArchAutoIncrement, setando os atributos fieldGroups e size, como se trata de um campo String ele vai preencher com ZEROS a esquerda até completar o que foi definido no atributo size. Depos de alguns lançamentos para o Posto Shell temos o seguinte cenário:
E para finalizar esse assunto foi ajustar o lancamentoData.xhtml para não mostrar o campo Código na inclusão e clonagem, e deixar desabilitado:
<a:panelGrid columns="2">
<h:panelGroup rendered="#{not lancamentoDataAction.stateInsert and not lancamentoDataAction.stateClone}">
<a:outputLabel value="#{e:bundle('label.codigo')}" for="idLancamentoCodigo"/>
<br/>
<a:inputText id="idLancamentoCodigo" styleClass="input-code"
disabled="true"
label="#{e:bundle('label.codigo')}" value="#{lancamentoDataAction.entity.codigo}"
required="true"/>
</h:panelGroup>
Ordenação no Grid
No print anterior verificamos que os registros não estão na ordem, o código 0000000002 aparece antes do código 0000000001. Isso pelo fato de não configurar qual ordem deve ser apresentado esses dados. Vou definir que a ordem é por id e decrescente, isso é definido pela anotação @JArchOrderBy na entidade LancamentoEntity:
@JArchOrderBy(fields = @JArchOrderByField(value = "id", desc = true))
@JArchSearchWhereJpa(id = LancamentoEntity.FILTRO_TIPO_LANCAMENTO,
conditionWhereJpa = "lancamento.tipoLancamento = :tipoLancamento")
@JArchLookup(codeAttribute = "codigo", descriptionAttribute = "descricao")
@Audited
@Table(name = "tb_lancamento",
indexes = {
@Index(columnList = "cd_lancamento", name = "dx_lancamentocdlan"),
@Index(columnList = "ds_lancamento", name = "dx_lancamentodslan"),
@Index(columnList = "dt_vencimento", name = "dx_lancamentodtven"),
@Index(columnList = "vl_lancamento", name = "dx_lancamentovllan")
})
@Entity(name = "lancamento")
@SequenceGenerator(name = "LancamentoIdSequence", sequenceName = "sq_idlancamento", allocationSize = 1)
public class LancamentoEntity extends CrudMultiTenantEntity {
public static final String FILTRO_TIPO_LANCAMENTO = "lancamentoEntity.filtroTipoLancamento";
E analisando o resultado no grid:
Ajustando Formatação Lookup
Na tela de dados do lançamento ao digitar o campo de código do lookup para a pessoa, devido ao tamanho default os últimos digitos do CNPJ ficam ocultos, conforme print:
Para resolvermos isso podemos alterar a propriedade withCode do e:lookup conforme exemplo:
<a:panelGrid columns="1">
<h:panelGroup>
<e:lookup id="idLancamentoPessoa" labelUnique="#{e:bundle('label.pessoa')}"
header="#{e:bundle('label.pessoa')}" value="#{lancamentoDataAction.entity.pessoa}"
actionFilterSelect="#{pessoaFilterSelectAction}"
disabled="#{lancamentoDataAction.blockedMaster}" required="true"
createExtensionInternal="true" widthCode="120"/>
</h:panelGroup>
</a:panelGrid>
E agora ficará assim:
Mas ainda pode ficar melhor, como se trata de um campo do tipo CPF/CNPJ podemos usar a propriedade typeCode do e:lookup para setar como cpf-cnpj:
<a:panelGrid columns="1">
<h:panelGroup>
<e:lookup id="idLancamentoPessoa" labelUnique="#{e:bundle('label.pessoa')}"
header="#{e:bundle('label.pessoa')}" value="#{lancamentoDataAction.entity.pessoa}"
actionFilterSelect="#{pessoaFilterSelectAction}"
disabled="#{lancamentoDataAction.blockedMaster}" required="true"
createExtensionInternal="true" widthCode="120" typeCode="cpf-cnpj"/>
</h:panelGroup>
</a:panelGrid>
E agora com essa nova configuração:
Conclusão
Nesse post mostrei como utilizar alguns eventos do JARCH, validação via BeanValidation, validação de exclusão com registro com relacionamento e totalizar coluna na tela de lista. Mostrei também como gerar um valor auto-incremento e como formatar o campo de código de lookup com CPF/CNPJ. No próximo post vou explorar ainda mais esses recursos.
Segue o link dessa video aula: https://youtu.be/9msPS2zGk4M
Até mais,
Nenhum comentário:
Postar um comentário