sábado, 27 de junho de 2020

Contas Pagar/Receber - Parte VII - Validações, AutoIncrement e Lookup

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

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

Versão 23.3.0-Final

      Introdução Nesse post vou mostrar as principais novidades da versão 23.3.0, algumas correções e pequenas alterações. Alterações Além d...