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
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:
| Campo | Valor |
|---|---|
| Name | EDU Urgente |
| Entity Type | EDU |
Na aba Style Line:
| Line No | Inline Style | Display Logic |
|---|---|---|
| 10 | color: #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 Key | Message Type | Message Text |
|---|---|---|
EDU_MinhasVisitasEmAberto | Information | Você tem {0} visita(s) técnica(s) em aberto. |
Crie a Status Line (janela Status Line, Entity Type EDU):
| Campo | Valor |
|---|---|
| Name | EDU Minhas visitas em aberto |
| Message | EDU_MinhasVisitasEmAberto |
| SQL Expression/Statement | SELECT 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:
| Campo | Valor |
|---|---|
| Name | Minhas visitas |
| Status Line | EDU Minhas visitas em aberto |
| Column No / Line No | 1 / 1 |
| Show in Dashboard | marcado |
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_DisplayValuedevolve 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.zoomabre 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:
| Campo | Valor |
|---|---|
| Name | Minha Agenda |
| Classname | com.gardenworld.visitas.form.AgendaTecnico |
| Data Access Level | Client+Organization |
| Entity Type | EDU |
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
Erros comuns
| Sintoma | Causa provável | Correção |
|---|---|---|
| Não aparece New no botão direito do campo | O campo não é do tipo Search para C_BPartner, ou o perfil não pode criar parceiros | Confira a referência da coluna e o acesso do perfil à janela Business Partner |
| A entrada rápida não salva | Coluna obrigatória fora do formulário | Marque Quick Entry também nela, ou dê um valor padrão |
| O estilo não aparece | Label Style no lugar errado, ou Display Logic falsa | Confira o campo e a lógica com @EDU_IsUrgente@=Y |
| O bloco não aparece no painel | Show in Dashboard desmarcado, ou o perfil não tem acesso ao conteúdo | Passo 3; confira a aba Dashboard Content Access |
O bloco mostra {0} | Mensagem sem marcador ou SQL que não devolve valor | Teste o SQL no psql, trocando as variáveis por valores |
Plugin INSTALLED depois do passo 4 | Faltou algum bundle do ZK no Require-Bundle | diag <id> no console |
| O menu abre uma tela em branco ou erro | Classname diferente da classe, ou factory não registrada | Compare letra por letra; confira OSGI-INF/ |
| A agenda aparece vazia | O usuário não é técnico de nenhuma visita em aberto | Abra 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
| Necessidade | Recurso | Código? |
|---|---|---|
| Criar registro relacionado sem sair da tela | Quick Entry | Não |
| Destacar valores | CSS Style | Não |
| Esconder, travar, exigir campos | Lógicas (módulo 02) | Não |
| Buscar e filtrar | Info Window (módulo 08) | Não |
| Número ou aviso de contexto | Status Line (módulo 08) | Não |
| Bloco no painel inicial | Dashboard Content | Não |
| Tela que não é cadastro nem busca | Form ZK | Sim |
Onde o CSS Style se aplica
| Lugar | Campo |
|---|---|
| Rótulo de um campo | Label Style na aba Field |
| Valor de um campo | Field Style na aba Field |
| Coluna de Info Window | Field 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.