Módulos do guia

Nível 1 · Código · Módulo 06

Processos

Duração estimada: 2h a 3h

Você vai sair sabendo

  • →Cadastrar um processo com parâmetros no Dicionário
  • →Escrever o processo em Java e registrá-lo por anotação
  • →Entender a transação de um processo: tudo ou nada
  • →Deixar um log que leva o usuário aos registros criados
  • →Chamar um processo a partir do código

Checkpoint: 0 de 6

ver .md

Antes de começar

  • Módulo 05 concluído: a visita tem endereço, e a MVisita preenche o endereço padrão no beforeSave.
  • Servidor em modo Debug.

O que você vai construir

A GardenWorld quer visitar todos os clientes periodicamente. Hoje isso significaria criar uma visita por vez, à mão.

Você vai criar o processo Gerar Visitas de Manutenção: o usuário escolhe a data, o técnico e, se quiser, um grupo de clientes. O processo cria uma visita de manutenção para cada cliente que ainda não tem visita em aberto, e mostra no fim a lista do que criou.

É o primeiro código do guia que cria registros em vez de reagir a eles. E é aqui que as regras dos módulos 04 e 05 se pagam: como o processo usa a MVisita, elas valem sem nenhuma linha a mais.

O que é um processo

Um processo é uma ação que o usuário dispara: pelo menu, por um botão na janela ou por agendamento. No iDempiere ele tem duas metades:

MetadeOnde ficaO que define
DicionárioJanela Report and ProcessNome, parâmetros, quem pode rodar, onde aparece
JavaClasse que herda de SvrProcessO que acontece quando roda

A tela de parâmetros, a execução em segundo plano, o log e o controle de acesso vêm prontos. Você escreve só a regra.

Passo a passo

1. Cadastre o processo

Como System, abra a janela Report and Process e crie:

CampoValor
Search KeyEDU_GerarVisitasManutencao
NameGerar Visitas de Manutenção
Data Access LevelClient+Organization
Entity TypeEDU
Reportdesmarcado
Classnamecom.gardenworld.visitas.process.GerarVisitasManutencao

O Classname é o elo entre as duas metades: precisa ser exatamente o nome completo da classe que você vai criar.

2. Os parâmetros

Na aba Parameter, crie:

SeqNameDB Column NameReferenceDetalhes
10Data da VisitaEDU_DataVisitaDateMandatory. Default Logic: @#Date@
20TécnicoSalesRep_IDTableReference Key: AD_User - Internal. Mandatory. Default Logic: @#AD_User_ID@
30Grupo de ClientesC_BP_Group_IDTable DirectOpcional

Em cada parâmetro, escolha em System Element o elemento com o mesmo nome da coluna. É o mesmo raciocínio das colunas do módulo 01: o elemento dá o significado, e a referência dá o tipo e o componente de tela.

3. Menu e acesso

Na janela Menu, crie uma entrada com Action Process e Process Gerar Visitas de Manutenção, arraste para perto da Visita Técnica na árvore e salve.

Como GardenAdmin, rode Role Access Update, como no módulo 01.

4. A classe do processo

Crie com.gardenworld.visitas.process.GerarVisitasManutencao:

package com.gardenworld.visitas.process;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;

import org.adempiere.base.annotation.Parameter;
import org.adempiere.base.annotation.Process;
import org.compiere.model.MBPartner;
import org.compiere.model.Query;
import org.compiere.process.SvrProcess;

import com.gardenworld.visitas.model.MVisita;

@Process
public class GerarVisitasManutencao extends SvrProcess {

    @Parameter
    private Timestamp p_EDU_DataVisita;

    @Parameter
    private int p_SalesRep_ID;

    @Parameter
    private int p_C_BP_Group_ID;

    @Override
    protected void prepare() {
        // os parâmetros já chegam preenchidos pelas anotações @Parameter
    }

    @Override
    protected String doIt() throws Exception {
        StringBuilder where = new StringBuilder("IsCustomer='Y'")
            .append(" AND NOT EXISTS (SELECT 1 FROM EDU_Visita v")
            .append("  WHERE v.C_BPartner_ID=C_BPartner.C_BPartner_ID AND v.EDU_IsConcluida='N')");
        List<Object> params = new ArrayList<>();
        if (p_C_BP_Group_ID > 0) {
            where.append(" AND C_BP_Group_ID=?");
            params.add(p_C_BP_Group_ID);
        }

        List<MBPartner> clientes = new Query(getCtx(), MBPartner.Table_Name, where.toString(), get_TrxName())
            .setParameters(params)
            .setClient_ID()
            .setOnlyActiveRecords(true)
            .setOrderBy(MBPartner.COLUMNNAME_Name)
            .list();

        for (MBPartner cliente : clientes) {
            MVisita visita = new MVisita(getCtx(), 0, get_TrxName());
            visita.setC_BPartner_ID(cliente.getC_BPartner_ID());
            visita.setSalesRep_ID(p_SalesRep_ID);
            visita.setEDU_DataVisita(p_EDU_DataVisita);
            visita.setEDU_TipoVisita("MA"); // Manutenção
            visita.setEDU_IsUrgente(false);
            visita.setEDU_IsConcluida(false);
            visita.setDescription("Manutenção periódica");
            visita.saveEx();

            addBufferLog(visita.getEDU_Visita_ID(), p_EDU_DataVisita, null,
                cliente.getName(), MVisita.Table_ID, visita.getEDU_Visita_ID());
        }

        return "@Created@ = " + clientes.size();
    }
}

Linha por linha, o que importa:

  • @Process marca a classe para a factory do próximo passo encontrar.
  • @Parameter preenche o campo com o parâmetro de mesmo nome, antes do prepare(). O prefixo p_ é opcional; o nome depois dele é o DB Column Name do passo 2. Parâmetro vazio chega como 0 (número) ou null (data, texto).
  • NOT EXISTS é o que torna o processo seguro para rodar de novo: cliente com visita em aberto fica de fora.
  • setClient_ID() filtra pelo cliente logado (GardenWorld). Sem isso, a consulta pegaria parceiros de todos os clientes do banco.
  • Tudo é preenchido explicitamente. A Default Logic do Dicionário é aplicada pela tela. Um objeto criado em Java nasce só com os campos padrão (cliente, organização, ativo). O endereço é a exceção, porque a MVisita cuida dele no beforeSave.
  • saveEx() e não save(): se algo der errado, a exceção interrompe o processo em vez de seguir em silêncio.
  • addBufferLog grava uma linha de log com a tabela e o registro. É isso que torna a linha clicável.
  • @Created@ entre arroba é traduzido pelo iDempiere para o idioma do usuário.

5. A factory de processos

Crie com.gardenworld.visitas.VisitasProcessFactory, a terceira factory do plugin:

package com.gardenworld.visitas;

import org.adempiere.base.AnnotationBasedProcessFactory;
import org.adempiere.base.IProcessFactory;
import org.osgi.service.component.annotations.Component;

@Component(immediate = true, service = IProcessFactory.class,
           property = {"service.ranking:Integer=1"})
public class VisitasProcessFactory extends AnnotationBasedProcessFactory {

    @Override
    protected String[] getPackages() {
        return new String[] {"com.gardenworld.visitas.process"};
    }
}

A essa altura o padrão deve estar claro: uma factory por tipo de extensão, cada uma apontando para um pacote do plugin. Modelo, callout, evento e processo seguem a mesma ideia.

6. A transação: tudo ou nada

Um processo roda dentro de uma transação, e o get_TrxName() é ela. O iDempiere faz o commit quando o doIt() termina sem erro, e o rollback se ele lançar uma exceção.

Consequência prática: se o processo criar 30 visitas e falhar na 31ª, nenhuma fica gravada. Para uma geração em lote, é o que você quer: rodar de novo não deixa metade feita.

7. Teste

Reinicie o servidor (factory nova). Como GardenAdmin:

  1. Conclua ou apague as visitas de teste dos módulos anteriores, para ver o efeito com clareza.
  2. Abra Gerar Visitas de Manutenção no menu. A data e o técnico já vêm preenchidos.
  3. Rode sem grupo. O resultado mostra quantas visitas foram criadas e uma linha por cliente. O GardenWorld vem com 7 clientes ativos: sem visitas abertas, são 7 visitas.
  4. Clique numa linha do log: a visita abre na janela Visita Técnica, com tipo Manutenção e endereço preenchido.
  5. Rode de novo. O resultado é 0: todos os clientes já têm visita em aberto.
  6. Coloque um breakpoint no saveEx() e rode com um grupo de clientes para acompanhar uma visita sendo criada. Siga com Step Into até o beforeSave da MVisita.

8. Commit

O Dicionário mudou (processo, parâmetros e menu). Inclua o processo no Pack Out e gere o 2Pack_1.0.2.zip:

SeqTypeObjeto
80Process/ReportGerar Visitas de Manutenção
90Application or Moduleo item de menu Gerar Visitas de Manutenção
cd ~/sources/guia-idempiere-projeto
git add .
git commit -m "Visitas 1.0.2: processo de geração de visitas de manutenção"

Checkpoint

0 de 6 itens

Erros comuns

SintomaCausa provávelCorreção
ClassNotFoundException ou "processo não encontrado" ao rodarClassname diferente do nome da classe, ou factory não registradaCompare letra por letra; confira OSGI-INF/ e reinicie
Parâmetro sempre 0 ou nullNome do campo não bate com o DB Column Namep_ + DB Column Name, ou exatamente o DB Column Name
O processo não aparece no menuFaltou o Role Access Update, ou o Data Access Level não permite o perfilRode o processo como GardenAdmin; confira o nível
Cria visitas de clientes de outra empresaFaltou setClient_ID()Passo 4
Erro de coluna obrigatória ao salvarAlgum campo obrigatório não foi preenchido no códigoPreencha tudo explicitamente; Default Logic é só da tela
Rodar duas vezes duplica visitasFaltou o NOT EXISTSPasso 4

Desafio

Crie um segundo processo, Concluir Visita, que roda a partir da própria janela, sobre a visita aberta, em vez do menu:

  1. Cadastre o processo sem parâmetros.
  2. Adicione-o como botão na aba Visita da janela Visita Técnica (aba Toolbar Button da janela Window, Tab and Field, com Action Process e o processo escolhido).
  3. No Java, pegue a visita com getRecord_ID(), marque como concluída e salve com saveEx().

Teste numa visita sem linhas. A regra do módulo 04 barra a conclusão, e a mensagem chega ao usuário, sem nenhum código a mais no processo.

Referência

Campos importantes do processo

CampoPara quê
Data Access LevelQuais perfis podem rodar (mesma lógica da tabela)
ClassnameClasse Java do processo
ReportMarcado quando o processo gera um relatório (módulo 07)
Allow Concurrent ExecutionControla se o processo pode ser disparado de novo enquanto uma execução anterior ainda está rodando
Show HelpMostra ou não a ajuda na tela de parâmetros

Parâmetros especiais

RecursoComo
Intervalo (de/até)Marque Range no parâmetro; declare um segundo campo com sufixo _To (p_DateDoc e p_DateDoc_To)
Valor padrãoDefault Logic, igual à coluna (módulo 02)
Lógica de exibiçãoDisplay Logic no parâmetro, com @NomeDoParametro@
Ler sem anotaçãofor (ProcessInfoParameter p : getParameter()) no prepare(), como no código do core

Chamando um processo pelo código

int processId = MProcess.getProcess_ID("EDU_GerarVisitasManutencao", null);
ProcessInfo pi = new ProcessInfo("Gerar Visitas de Manutenção", processId);
pi.setAD_Client_ID(Env.getAD_Client_ID(getCtx()));
pi.setAD_User_ID(Env.getAD_User_ID(getCtx()));
pi.setParameter(new ProcessInfoParameter[] {
    new ProcessInfoParameter("EDU_DataVisita", dataVisita, null, null, null),
    new ProcessInfoParameter("SalesRep_ID", tecnicoId, null, null, null)
});
ServerProcessCtl.process(pi, null);
if (pi.isError())
    throw new AdempiereException(pi.getSummary());

Com null como transação, o processo chamado roda na transação dele e faz o próprio commit. Busque sempre pelo Search Key, nunca pelo ID: o ID muda de um banco para outro, pelo mesmo motivo do módulo 03.

Agendamento

Um processo pode rodar sozinho, sem usuário, pela janela Scheduler: escolha o processo, os parâmetros e a frequência. É o caminho natural para a geração de visitas rodar todo mês.

Próximo módulo

07 · Relatórios: visitas por técnico e por período, com views e Jasper.