Módulos do guia

Nível 3 · Documentos e fluxo · Módulo 10

Experiência do usuário

Duração estimada: 2h a 3h

Você vai sair sabendo

  • →Cadastrar um cliente sem sair da visita, com a entrada rápida
  • →Destacar informação importante com estilos condicionais
  • →Colocar um indicador no painel inicial do usuário
  • →Criar um formulário ZK próprio dentro do plugin
  • →Saber quando vale escrever tela em Java e quando o Dicionário basta

Checkpoint: 0 de 6

ver .md

Antes de começar

  • Módulo 09 concluído: a visita é um documento, e "em aberto" é Processed='N'.
  • Este módulo tem três partes de Dicionário (passos 1 a 3) e uma de Java (passo 4). Se o tempo for curto, faça as três primeiras: são as que mais melhoram o dia do usuário.

O que você vai construir

Um sistema que funciona não é o mesmo que um sistema bom de usar. Os técnicos da GardenWorld reclamam de quatro coisas:

  • "Para agendar um cliente novo, tenho de sair da visita e ir ao cadastro."
  • "Não vejo, batendo o olho, quais visitas são urgentes."
  • "Quando entro no sistema, não sei quanto trabalho tenho pendente."
  • "Queria uma tela só com a minha agenda, sem filtros."

Cada uma tem uma resposta diferente no iDempiere, e só a última exige Java.

Passo a passo

1. Entrada rápida: cliente novo sem sair da visita

Conceito: a Quick Entry (entrada rápida) abre um formulário pequeno para criar ou alterar o registro apontado por um campo, sem sair da janela. Os campos do formulário são os marcados como Quick Entry na janela de destino.

O cadastro de parceiros já vem com entrada rápida. Teste: como GardenAdmin, numa visita nova, clique com o botão direito no campo Cliente e escolha New. Aparece um formulário com Search Key, Name, Customer e mais alguns campos.

Falta o essencial no Brasil: o CNPJ ou CPF. Como System, na janela Window, Tab and Field, abra a janela Business Partner, aba Business Partner, e na aba Field marque Quick Entry no campo Tax ID.

Faça logout e login, e teste de novo: o formulário agora pede o Tax ID. Crie um cliente pela entrada rápida. Ele já fica selecionado na visita.

2. Estilo condicional: urgente em vermelho

Conceito: um CSS Style é um conjunto de linhas de CSS, cada uma com uma Display Logic. A linha só vale quando a lógica é verdadeira. O estilo é aplicado a campos (rótulo ou valor) e a colunas de Info Window.

Como System, na janela CSS Style, crie:

CampoValor
NameEDU Urgente
Entity TypeEDU

Na aba Style Line:

Line NoInline StyleDisplay Logic
10color: #b3261e; font-weight: bold;@EDU_IsUrgente@=Y

Agora aplique. Na janela Window, Tab and Field, janela Visita Técnica, aba Visita, aba Field, abra o campo Urgente e preencha Label Style com EDU Urgente. Faça o mesmo no campo Motivo da Urgência.

Teste: abra uma visita urgente e uma normal. Marque e desmarque Urgente e veja o rótulo mudar na hora: a lógica é reavaliada a cada mudança, como no módulo 02.

Na Info Window do módulo 08, a coluna Concluída ganha o mesmo recurso pelo campo Field Style da coluna. Crie um estilo EDU Em aberto, com cor diferente, para @EDU_IsConcluida@=N, e aplique.

3. Um indicador no painel inicial

Conceito: o painel inicial do iDempiere é feito de Dashboard Content: pequenos blocos com gráfico, relatório, HTML ou uma Status Line. Você já tem uma Status Line do módulo 08; aqui vai uma variação com o usuário logado.

Crie a mensagem (janela Message, Entity Type EDU):

Search KeyMessage TypeMessage Text
EDU_MinhasVisitasEmAbertoInformationVocê tem {0} visita(s) técnica(s) em aberto.

Crie a Status Line (janela Status Line, Entity Type EDU):

CampoValor
NameEDU Minhas visitas em aberto
MessageEDU_MinhasVisitasEmAberto
SQL Expression/StatementSELECT COUNT(*) FROM EDU_Visita WHERE SalesRep_ID=@#AD_User_ID@ AND Processed='N' AND IsActive='Y' AND AD_Client_ID=@#AD_Client_ID@

Sem aba Used In: ela não pertence a janela nenhuma. O @#AD_User_ID@ é o usuário logado, da mesma forma nas lógicas do módulo 02.

Agora, como GardenAdmin, na janela Dashboard Content, crie:

CampoValor
NameMinhas visitas
Status LineEDU Minhas visitas em aberto
Column No / Line No1 / 1
Show in Dashboardmarcado

Volte ao painel inicial (ou faça logout e login). O bloco aparece, com a contagem do usuário logado. Entre como GardenUser e veja outro número.

4. Um formulário ZK: Minha Agenda

Conceito: quando nenhuma janela, Info Window ou relatório resolve, você escreve uma tela em Java. O iDempiere usa o ZK na interface web, e uma tela própria é um Form: uma classe que herda de ADForm e monta os componentes no initForm().

A regra para decidir: escreva um Form quando a tela não é um cadastro nem uma busca. Um painel de agenda, uma conferência de estoque com leitor de código de barras, um assistente passo a passo. Para listar e filtrar registros, a Info Window do módulo 08 é mais barata e o usuário já sabe usar.

Primeiro, o plugin passa a depender da interface web. No MANIFEST.MF, acrescente ao Require-Bundle:

Require-Bundle: org.adempiere.base;bundle-version="14.0.0",
 org.adempiere.plugin.utils;bundle-version="14.0.0",
 org.adempiere.ui.zk;bundle-version="14.0.0",
 zk;bundle-version="10.0.1",
 zul;bundle-version="10.0.1",
 zcommon;bundle-version="10.0.1"

Crie com.gardenworld.visitas.form.AgendaTecnico:

package com.gardenworld.visitas.form;

import java.util.List;

import org.adempiere.webui.apps.AEnv;
import org.adempiere.webui.panel.ADForm;
import org.compiere.model.Query;
import org.compiere.util.DisplayType;
import org.compiere.util.Env;
import org.compiere.util.Msg;
import org.idempiere.ui.zk.annotation.Form;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.Button;
import org.zkoss.zul.Column;
import org.zkoss.zul.Columns;
import org.zkoss.zul.Grid;
import org.zkoss.zul.Label;
import org.zkoss.zul.Row;
import org.zkoss.zul.Rows;

import com.gardenworld.visitas.model.MVisita;

@Form
public class AgendaTecnico extends ADForm {

    private static final long serialVersionUID = 1L;

    @Override
    protected void initForm() {
        Grid grid = new Grid();
        grid.setHflex("1");
        grid.setVflex("1");
        appendChild(grid);

        Columns colunas = new Columns();
        colunas.appendChild(new Column(Msg.getElement(Env.getCtx(), "EDU_DataVisita")));
        colunas.appendChild(new Column(Msg.getElement(Env.getCtx(), "DocumentNo")));
        colunas.appendChild(new Column(Msg.getElement(Env.getCtx(), "C_BPartner_ID")));
        colunas.appendChild(new Column(Msg.getElement(Env.getCtx(), "EDU_TipoVisita")));
        colunas.appendChild(new Column(""));
        grid.appendChild(colunas);

        Rows linhas = new Rows();
        grid.appendChild(linhas);

        List<MVisita> visitas = new Query(Env.getCtx(), MVisita.Table_Name,
                "SalesRep_ID=? AND Processed='N'", null)
            .setParameters(Env.getAD_User_ID(Env.getCtx()))
            .setClient_ID()
            .setOnlyActiveRecords(true)
            .setOrderBy(MVisita.COLUMNNAME_EDU_DataVisita)
            .list();

        for (MVisita visita : visitas) {
            Row linha = new Row();
            linha.appendChild(new Label(DisplayType.getDateFormat().format(visita.getEDU_DataVisita())));
            linha.appendChild(new Label(visita.getDocumentNo()));
            linha.appendChild(new Label(visita.get_DisplayValue(MVisita.COLUMNNAME_C_BPartner_ID, true)));
            linha.appendChild(new Label(visita.get_DisplayValue(MVisita.COLUMNNAME_EDU_TipoVisita, true)));

            Button abrir = new Button(Msg.getMsg(Env.getCtx(), "Zoom"));
            int visitaId = visita.getEDU_Visita_ID();
            abrir.addEventListener(Events.ON_CLICK, e -> AEnv.zoom(MVisita.Table_ID, visitaId));
            linha.appendChild(abrir);

            linhas.appendChild(linha);
        }
    }
}

O que vale observar:

  • Os títulos vêm do Dicionário (Msg.getElement), não do código. Se alguém traduzir o elemento, a agenda acompanha.
  • get_DisplayValue devolve o que o usuário veria na janela: o nome do cliente em vez do ID, o nome do tipo em vez do código.
  • AEnv.zoom abre o registro na janela configurada para a tabela, como o duplo clique da Info Window.
  • A consulta usa o usuário do contexto e setClient_ID(). Um Form não tem a segurança automática de uma janela: filtrar por empresa e por usuário é responsabilidade sua.

A quarta factory do plugin, com.gardenworld.visitas.VisitasFormFactory:

package com.gardenworld.visitas;

import org.adempiere.webui.factory.AnnotationBasedFormFactory;
import org.adempiere.webui.factory.IFormFactory;
import org.osgi.service.component.annotations.Component;

@Component(immediate = true, service = IFormFactory.class,
           property = {"service.ranking:Integer=1"})
public class VisitasFormFactory extends AnnotationBasedFormFactory {

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

Cadastre o Form. Como System, na janela Form:

CampoValor
NameMinha Agenda
Classnamecom.gardenworld.visitas.form.AgendaTecnico
Data Access LevelClient+Organization
Entity TypeEDU

Crie a entrada de menu com Action Form e Special Form Minha Agenda, e rode Role Access Update como GardenAdmin.

Reinicie o servidor. Como GardenAdmin, abra Minha Agenda: aparecem as visitas em aberto em que ele é o técnico. Clique no botão de uma linha para abrir a visita.

5. Commit

No Pack Out: a janela Business Partner não é sua, então não entra inteira. A mudança de Quick Entry no campo Tax ID vai como Data da tabela AD_Field, com SELECT * FROM AD_Field WHERE AD_Field_UU='<UUID do campo>' (o UUID está no próprio registro do campo). Inclua também o estilo (Data, tabelas AD_Style e AD_StyleLine), a janela Visita Técnica (os estilos nos campos), a mensagem nova, a Status Line (Data), o Form (tipo Form) e o menu. Gere o 2Pack_1.0.6.zip.

git add .
git commit -m "Visitas 1.0.6: entrada rápida, estilos, indicador no painel e agenda do técnico"

Checkpoint

0 de 6 itens

Erros comuns

SintomaCausa provávelCorreção
Não aparece New no botão direito do campoO campo não é do tipo Search para C_BPartner, ou o perfil não pode criar parceirosConfira a referência da coluna e o acesso do perfil à janela Business Partner
A entrada rápida não salvaColuna obrigatória fora do formulárioMarque Quick Entry também nela, ou dê um valor padrão
O estilo não apareceLabel Style no lugar errado, ou Display Logic falsaConfira o campo e a lógica com @EDU_IsUrgente@=Y
O bloco não aparece no painelShow in Dashboard desmarcado, ou o perfil não tem acesso ao conteúdoPasso 3; confira a aba Dashboard Content Access
O bloco mostra {0}Mensagem sem marcador ou SQL que não devolve valorTeste o SQL no psql, trocando as variáveis por valores
Plugin INSTALLED depois do passo 4Faltou algum bundle do ZK no Require-Bundlediag <id> no console
O menu abre uma tela em branco ou erroClassname diferente da classe, ou factory não registradaCompare letra por letra; confira OSGI-INF/
A agenda aparece vaziaO usuário não é técnico de nenhuma visita em abertoAbra uma visita em aberto e troque o técnico para o usuário logado

Desafio

Acrescente à agenda um botão Iniciar rota, que abre o endereço da visita no mapa. Dica: o endereço completo está em C_BPartner_Location → C_Location, e a ZK abre uma URL em nova aba com Executions.getCurrent().sendRedirect(url, "_blank").

Referência

Onde cada tipo de melhoria mora

NecessidadeRecursoCódigo?
Criar registro relacionado sem sair da telaQuick EntryNão
Destacar valoresCSS StyleNão
Esconder, travar, exigir camposLógicas (módulo 02)Não
Buscar e filtrarInfo Window (módulo 08)Não
Número ou aviso de contextoStatus Line (módulo 08)Não
Bloco no painel inicialDashboard ContentNão
Tela que não é cadastro nem buscaForm ZKSim

Onde o CSS Style se aplica

LugarCampo
Rótulo de um campoLabel Style na aba Field
Valor de um campoField Style na aba Field
Coluna de Info WindowField Style na coluna

A Display Logic de cada linha usa a mesma sintaxe do módulo 02. Uma linha sem lógica vale sempre.

Próximo módulo

11 · Integrações: a visita conversa com outros sistemas, pela API REST e por notificações de saída.