sábado, 1 de agosto de 2020

Contas Pagar/Receber - Parte XXVI - BPM Comunicando Operador da Reprovação

Introdução
Nesse vigésimo sexto post vou criar um subprocesso de evento, onde será informado ao operador que o lançamento foi reprovado e na sequência esse Lançamento será excluído. 

SubProcesso Evento
Vou adicionar uma nova Lane para indicar o operador, nessa Lane vou criar um subprocesso de reprovação. Vou alterar também o evento de inicialização do fluxo para ficar nessa nova Lane

O
Sub Process de evento precisa estar com a seguinte configuração:

 
No componente de Escalation dentro do Sub Process vou colocar o atributo Name como Escalation_Reprovado
      
No componente Message vou configurar o seguinte Delegate #{mensagemReprovacaoDelegate}:
      
No componente Service Task vou atribuir o Delegate Expression #{apagaLancamentoDelegate}
      
ApagaLancamentoDelegate.java:

@Named
public class ApagaLancamentoDelegate implements JavaDelegate {

@Inject
private LancamentoFacade lancamentoFacade;

@Override
public void execute(DelegateExecution execution) {
CDI.current().select(UserInformation.class).get().setUserSystem();
CDI.current().select(MultiTenant.class).get().set((Long) execution.getVariable(Constant.ID_MULTITENANT));
Long idLancamento = Long.valueOf(execution.getBusinessKey());
lancamentoFacade.delete(idLancamento);
}
}

Disparando Evento Escalation
Nas Lanes de Supervisor, Gerente e Diretor vou alterar os Sequences Flow de Reprovação para disparar o evento de Escalation, e vou ajustar as tarefas de Aprovar Conta para finalizar o fluxo: 

Implementando MensagemReprovacaoDelegate
Agora vou implementar o Delegate MensagemAprovacaoDelegate para gerar uma mensagem para o Operador informando que a conta não foi aprovada. Vou utilizar a biblioteca de Communication do JARCH que serve para armazenar e gerenciar mensagens
MensagemReprovacaoDelegate.java:

@Named
public class MensagemReprovacaoDelegate implements JavaDelegate {

@Inject
private CommunicationFacade communicationFacade;

@Inject
private LancamentoFacade lancamentoFacade;

@Override
public void execute(DelegateExecution execution) {
CDI.current().select(UserInformation.class).get().setUserSystem();
CDI.current().select(MultiTenant.class).get().set((Long) execution.getVariable(Constant.ID_MULTITENANT));

Long idOperador = (Long) execution.getVariable(Constant.ID_OPERADOR);
Long idLancamento = Long.valueOf(execution.getBusinessKey());
LancamentoEntity lancamento = lancamentoFacade.find(idLancamento);

communicationFacade.insert(new CommunicationEntity(MessageBuilder
.newInstance()
.type(0)
.to(idOperador)
.code(lancamento.getId())
.title(BundleUtils.messageBundle("message.lancamentoReprovado"))
.body(BundleUtils.messageBundle("message.corpoMensagemReprovacao", lancamento.getCodigo(),
lancamento.getDescricao(), DateUtils.formatddMMyyyy(lancamento.getVencimento()),
NumberUtils.formatMoney(lancamento.getValor())))
.build()));
}
}
Regularizando Banco Dados
Vou apagar a tabela TB_COMUNICACAOMENSAGEM, para que na próxima publicação seja regularizado.
DROP TABLE TB_COMUNICACAOMENSAGEM;

Ajustes Finais
Vou adicionar mais duas constantes no Constant:
Constant.java:

public final class Constant {

public static final String PROCESS_AVALIACAO_CONTA = "process-avaliacao-conta";
public static final String ID_AVALIAR_LANCAMENTO = "idAvaliarLancamento";
public static final String ID_CANCELAR_BAIXA_LANCAMENTO = "idCancelarBaixaLancamento";
public static final String ID_OPERADOR = "idOperador";
public static final String ID_MULTITENANT = "idMultitenant"
;

private Constant() {

}
}
Vou ajustar o método do observer de Lançamento para adicionar o ID do multitenant e o ID do operador dentro da instância do fluxo BPM:
LancamentoObserver.java:

private void instanciaFluxoLancamentoOperador(@Observes @JArchEventInsert(momentPersist = MomentType.AFTER)
LancamentoEntity lancamento
, UserInformation userInformation, MultiTenant multiTenant,
RuntimeService runtimeService) {

if (userInformation.get(UsuarioEntity.class).getCargo() != CargoType.OPERADOR) {
return;
}

if (!parametroUtilizaBpmAutorizacaoLancamento.getValue()) {
return;
}

runtimeService
.createProcessInstanceByKey(Constant.
PROCESS_AVALIACAO_CONTA)
.businessKey(lancamento.getId().toString())
.setVariable(Constant.ID_MULTITENANT, multiTenant.get())
.setVariable(Constant.ID_OPERADOR, userInformation.get().getId())
.setVariable("descricao", lancamento.getDescricao())
.setVariable(
"valor", lancamento.getValor().doubleValue())
.setVariable(
"vencimento", lancamento.getVencimento())
.execute()
;
}

E vou ajustar o AutorizacaoObserver para verificar o usuário logado e permitir manutenção na entidade CommunicationEntity:
AutorizacaoObserver.java:

public class AutorizacaoObserver {

private void validaAlteracaoViaOperador(@Observes @JArchEventValidChange CrudMultiTenantEntity entity, UserInformation userInformation) {
if (CommunicationEntity.class.isAssignableFrom(entity.getClass())) {
return;
}

UsuarioEntity usuarioSistema = userInformation.get(UsuarioEntity.class);

if (UsuarioEntity.class.isAssignableFrom(entity.getClass()) && usuarioSistema.equals(entity)) {
return;
}

if (usuarioSistema.getCargo() == CargoType.OPERADOR) {
throw new ValidationException(BundleUtils.messageBundle("message.naoAutorizadoEntreContatoSupervisor"));
}
}

private void validaExclusaoViaOperador(@Observes @JArchEventValidDelete CrudMultiTenantEntity entity, UserInformation userInformation) {
if (CommunicationEntity.class.isAssignableFrom(entity.getClass())) {
return;
}

if (userInformation.isSystem()) {
return;
}

UsuarioEntity usuarioSistema = userInformation.get(UsuarioEntity.class);

if (usuarioSistema != null && usuarioSistema.getCargo() == CargoType.OPERADOR) {
throw new ValidationException(BundleUtils.messageBundle("message.naoAutorizadoEntreContatoSupervisor"));
}
}
}
Agora vou alterar a aplicação para utilizar o GlobalInformation no lugar do SessionInformation:
AlertaFacade.java:

public class AlertaFacade {

@Inject
private LancamentoFacade lancamentoFacade;

@Inject
private GlobalInformation globalInformation
;

public Alerts getAlerts() {
Alerts alerts =
new Alerts();

long contasPagarVencido = lancamentoFacade.contaPagarAbertoAte(LocalDate.now().minusDays(1)).size();

if (contasPagarVencido > 0) {
alerts.add(AlertBuilder
.
newInstance()
.danger()
.title(BundleUtils.
messageBundle("label.pagarVencido"))
.description(contasPagarVencido +
" " + BundleUtils.messageBundle("label.contaPagar"))
.link(
"../lancamento/lancamentoList.jsf?tipo=" + TipoLancamentoType.PAGAR + "&status=" + StatusLancamentoType.VENCIDO)
.build())
;
}

long contasPagarVencendo = lancamentoFacade.contaPagarAbertoEm(LocalDate.now()).size();

if (contasPagarVencendo > 0) {
alerts.add(AlertBuilder
.
newInstance()
.warning()
.title(BundleUtils.
messageBundle("label.pagarVencendo"))
.description(contasPagarVencendo +
" " + BundleUtils.messageBundle("label.contaPagar"))
.link(
"../lancamento/lancamentoList.jsf?tipo=" + TipoLancamentoType.PAGAR + "&status=" + StatusLancamentoType.VENCENDO)
.build())
;
}

return alerts;
}

public void atualizaAlertaSessao() {
globalInformation.set("alerts", getAlerts());
}
}
Agora vou alterar a aplicação para utilizar o GlobalInformation no lugar do SessionInformation:
MessagesProduces.java:

public class MessagesProduces {

@Produces
public Messages messages(GlobalInformation globalInformation) {
return (Messages) globalInformation.get("messages");
}
}
Agora vou alterar a aplicação para utilizar o GlobalInformation no lugar do SessionInformation e ajustar para levantar as mensagens gravadas na entidade CommunicationEntity do usuário logado:
MensagemFacade.java:

public class MensagemFacade {

@Inject
private LancamentoFacade lancamentoFacade;

@Inject
private CommunicationFacade communicationFacade;

@Inject
private GlobalInformation globalInformation;

@Inject
private UserInformation userInformation;

public Messages getMessages() {
Messages messages =
new Messages();

Collection<LancamentoEntity> contasPagar = lancamentoFacade.contaPagarAbertoAte(LocalDate.now());

contasPagar
.stream()
.filter(l -> l.isVencendo())
.sorted(Comparator.
comparing(LancamentoEntity::getVencimento).reversed())
.forEach(l ->
messages.add(MessageBuilder.newInstance()
.code(l.getId())
.warning()
.title(BundleUtils.
messageBundle("label.pagarVencendo"))
.description(DateUtils.
formatddMMyyyy(l.getVencimento()) + " " + l.getDescricao())
.link(
"../lancamento/lancamentoList.jsf?tipo=" + TipoLancamentoType.PAGAR + "&id=" + l.getId())
.build()))
;

contasPagar
.stream()
.filter(l -> l.isVencido())
.sorted(Comparator.
comparing(LancamentoEntity::getVencimento).reversed())
.forEach(l ->
messages.add(MessageBuilder.newInstance()
.code(l.getId())
.danger()
.title(BundleUtils.
messageBundle("label.pagarVencido"))
.description(DateUtils.
formatddMMyyyy(l.getVencimento()) + " " + l.getDescricao())
.link(
"../lancamento/lancamentoList.jsf?tipo=" + TipoLancamentoType.PAGAR + "&id=" + l.getId())
.build()))
;

if (userInformation.exists()) {
communicationFacade
.searchAllFilter("to", userInformation.get().getId())
.forEach(c ->
messages.add(MessageBuilder.newInstance()
.code(c.getCode())
.warning()
.title(c.getTitle())
.description(c.getTitle())
.link("../communication/communicationList.jsf?type=" + c.getType() + "&code=" + c.getCode())
.build()));
}

return messages;
}

public void atualizaMensagemSessao() {
globalInformation.set("messages", getMessages());
}
}
Agora vou alterar a aplicação para utilizar o GlobalInformation no lugar do SessionInformation:
AlertsProduces.java:

public class AlertsProduces {

@Produces
public Alerts alerts(GlobalInformation globalInformation) {
return (Alerts) globalInformation.get("alerts");
}
}
E pra finalizar vou criar o pacote comunicacao no contas-client e implementar um observer no CommunicatioEntity para atualizar a lista de mensagens:
ComunicacaoObserver.java:

public class ComunicacaoObserver {

private void inclusaoAlteracao(@Observes @JArchEventInsertChange(momentPersistMerge = MomentType.AFTER) CommunicationEntity communicationEntity,
MensagemFacade mensagemFacade) {
mensagemFacade.atualizaMensagemSessao();
}

private void exclusao(@Observes @JArchEventDelete(momentRemove = MomentType.AFTER) CommunicationEntity communicationEntity,
MensagemFacade mensagemFacade) {
mensagemFacade.atualizaMensagemSessao();
}
}
bundle_pt_BR.properties:

message.lancamentoReprovado=Lan\u00E7amento Reprovado
message.corpoMensagemReprovacao=C\u00F3digo: %s<br/>Descri\u00E7\u00E3o: %s<br/>Vencimento: %s<br/>Valor: %s \ <br/><br/><h1>N\u00E3o foi aprovado</h1>

Agora compilando e publicando vou lançar uma conta como Operador:

Agora vou logar como Supervisor e reprovar a conta:

Reprovando o lançamento:

Agora vou logar novamente como o
Operador e verificar que a mensagem foi enviada:

E acessando o link da mensagem:


Conclusão
Nesse post criei um subprocesso de evento que gera a mensagem de reprovação para o operador e apaga o lançamento. Para o envio da mensagem para o operador eu utilizei a entidade CommunicationEntity do JARCH, para que essa mensagem fique gravada até a leitura e exclusão pelo Operador.

Segue o link dessa video aula: https://youtu.be/oNu_vifnkOA

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...