# 02 · Janelas inteligentes: contexto e lógicas

Fonte: https://muriloht.com/idempiere/02-contexto-e-logicas (Guia iDempiere, Murilo H. Torquato). Versão alvo: iDempiere 14.

Objetivos:
- Entender o que é o contexto e de onde vêm as variáveis @...@
- Preencher campos automaticamente com Default Logic
- Mostrar, esconder, travar e exigir campos conforme o que o usuário faz
- Travar uma aba inteira a partir de um campo da aba pai

## Antes de começar

- Módulo 01 concluído: a janela **Visita Técnica** abre e salva uma visita com linhas.
- Entre como **System** para mexer no Dicionário e deixe uma segunda aba do navegador logada como **GardenAdmin** para testar. As mudanças de lógica valem ao reabrir a janela; não precisa reiniciar o servidor.

## O que você vai construir

A mesma janela do módulo 01, agora reagindo ao que o usuário faz:

- **Visita nova já vem preenchida:** data de hoje, tipo Manutenção e o usuário logado como técnico.
- **Urgente pede justificativa:** ao marcar Urgente, aparece o campo **Motivo da Urgência**, obrigatório.
- **Concluída trava a visita:** cabeçalho e linhas deixam de aceitar alterações.

Tudo sem Java. São quatro campos do Dicionário: **Default Logic**, **Display Logic**, **Mandatory Logic** e **Read Only Logic**.

## O contexto, em 1 minuto

Enquanto você usa o iDempiere, ele mantém um conjunto de variáveis na memória da sessão: quem está logado, em qual empresa e organização, que dia é hoje, e o valor de cada campo de cada janela aberta. Esse conjunto é o **contexto**.

As lógicas do Dicionário leem o contexto com a sintaxe `@Nome@`. Três formas resolvem quase tudo:

| Sintaxe | Lê | Exemplo |
|---|---|---|
| `@Campo@` | Um campo da janela: primeiro na aba atual, depois nas abas acima | `@C_BPartner_ID@` |
| `@#Variavel@` | Uma variável de login, a mesma em qualquer janela | `@#AD_User_ID@`, `@#Date@` |
| `@~Campo@` | Um campo **só** da aba atual, sem subir para a aba pai | `@~Description@` |

A lista completa está na [Referência](#referência), no fim do módulo.

## Passo a passo

### 1. Dois campos novos

Entre como **System**. Em **Element**, crie dois elementos com Entity Type `EDU`:

| DB Column Name | Name |
|---|---|
| `EDU_MotivoUrgencia` | Motivo da Urgência |
| `EDU_IsConcluida` | Concluída |

Na tabela `EDU_Visita` (janela **Table and Column**), crie as colunas e sincronize cada uma:

| DB Column Name | Reference | Detalhes |
|---|---|---|
| `EDU_MotivoUrgencia` | Text | Length: `2000` |
| `EDU_IsConcluida` | Yes-No | Default Logic: `N` |

Depois, na aba **Visita** da janela **Window, Tab and Field**, clique em **Create Fields** para os campos novos aparecerem, e ajuste a posição deles no **Tab Editor**.

### 2. Default Logic: a visita já nasce preenchida

**Conceito:** a **Default Logic** define o valor inicial de um campo num registro novo. Pode ser uma constante (`MA`), uma variável de contexto (`@#Date@`) ou uma consulta (`@SQL=SELECT ...`).

Na aba **Column** da tabela `EDU_Visita`, preencha:

| Coluna | Default Logic | O que faz |
|---|---|---|
| `EDU_TipoVisita` | `MA` | Toda visita nova começa como Manutenção |
| `SalesRep_ID` | `@#AD_User_ID@` | O técnico começa como o usuário logado |

A data você já configurou no módulo 01 com `@#Date@`.

Teste como GardenAdmin: abra a janela e crie uma visita nova. Data, tipo e técnico já vêm preenchidos.

> Se o técnico vier vazio, é porque o usuário logado não está na lista `AD_User - Internal` (não é funcionário nem representante de vendas). A Default Logic não força um valor que a referência não aceita.

> A Default Logic aceita alternativas separadas por `;`. Em `@#AD_User_ID@;100`, o iDempiere tenta a primeira e, se vier vazia, usa a segunda.

### 3. Display Logic: o campo aparece quando faz sentido

**Conceito:** a **Display Logic** é uma expressão verdadeira ou falsa. Quando é falsa, o campo some da tela. Ela fica no **Field** (aba Field da janela Window, Tab and Field), porque é um comportamento da tela, não do dado.

Abra o campo **Motivo da Urgência** na aba **Field** e preencha:

| Campo | Valor |
|---|---|
| Display Logic | `@EDU_IsUrgente@=Y` |

Checkbox no contexto vale `Y` ou `N`. Teste: marque e desmarque **Urgente** e veja o campo aparecer e sumir sem salvar nada.

### 4. Mandatory Logic: obrigatório só às vezes

**Conceito:** o **Mandatory** da coluna torna o campo sempre obrigatório. A **Mandatory Logic** torna obrigatório só quando a expressão é verdadeira.

Na coluna `EDU_MotivoUrgencia` (aba **Column**), preencha:

| Campo | Valor |
|---|---|
| Mandatory Logic | `@EDU_IsUrgente@=Y` |

Teste: marque Urgente, deixe o motivo em branco e tente salvar. O iDempiere recusa.

> Não marque **Mandatory** nessa coluna. O `NOT NULL` no banco bloquearia todas as visitas sem urgência. Mandatory Logic é validada na tela, não no banco.

### 5. Read Only Logic: travar o que já foi concluído

**Conceito:** a **Read Only Logic** deixa o campo visível, mas sem edição, quando a expressão é verdadeira. Ela existe na coluna (vale em todas as janelas) e no campo (vale só naquela aba).

Nas colunas `C_BPartner_ID`, `SalesRep_ID`, `EDU_DataVisita` e `EDU_TipoVisita` da tabela `EDU_Visita`, preencha:

| Campo | Valor |
|---|---|
| Read Only Logic | `@EDU_IsConcluida@=Y` |

Teste: marque **Concluída** e salve. Os quatro campos ficam cinza. Desmarque e eles voltam a ser editáveis.

> Coloque a lógica em cada campo, e não na aba inteira. Se a aba **Visita** ficasse somente leitura com `@EDU_IsConcluida@=Y`, o próprio checkbox Concluída travaria, e ninguém conseguiria reabrir a visita.

### 6. Travar uma aba filha a partir da aba pai

**Conceito:** a aba também tem **Read Only Logic**. Como `@Campo@` procura na aba atual e depois nas abas acima, a aba Linhas consegue ler um campo do cabeçalho.

Na janela **Window, Tab and Field**, abra a aba **Linhas** e preencha:

| Campo | Valor |
|---|---|
| Read Only Logic | `@EDU_IsConcluida@=Y` |

Teste: com a visita concluída, vá para **Linhas**. O botão de novo registro fica desabilitado e as linhas existentes não aceitam edição.

## Checkpoint

- [ ] Uma visita nova já vem com a data de hoje, o tipo Manutenção e o técnico preenchido
- [ ] O campo Motivo da Urgência só aparece quando Urgente está marcado
- [ ] Com Urgente marcado, a visita não salva sem o motivo
- [ ] Marcar Concluída trava cliente, técnico, data e tipo
- [ ] Com a visita concluída, a aba Linhas não aceita inclusão nem edição
- [ ] Sei explicar a diferença entre @Campo@, @#Variavel@ e @~Campo@

## Erros comuns

| Sintoma | Causa provável | Correção |
|---|---|---|
| A lógica não faz efeito | A janela já estava aberta | Feche e abra a janela de novo |
| Display Logic nunca mostra o campo | Nome da coluna errado ou com maiúscula/minúscula diferente | O nome dentro de `@...@` é exatamente o **DB Column Name**, respeitando maiúsculas |
| `@EDU_IsUrgente@=true` não funciona | Checkbox vale `Y` ou `N` no contexto | Use `=Y` |
| Campo `_ID` vazio não bate com `=''` | Campos `_ID` vazios valem `0` no contexto | Use `@C_BPartner_ID@=0` |
| A aba filha não trava | Lógica colocada no campo, e não na aba | Read Only Logic da **Tab**, não do Field |
| Salvar dá erro de `NOT NULL` no motivo | **Mandatory** marcado na coluna | Desmarque Mandatory e sincronize; use só Mandatory Logic |

## Desafio

Faça o campo **Tipo de Visita** esconder a opção de urgência: quando o tipo for **Orçamento** (`OR`), o checkbox **Urgente** não deve aparecer, e o **Motivo da Urgência** também não.

Dica: a Display Logic aceita `&` (e), `|` (ou) e parênteses.

## Referência

### Formas de ler o contexto

| Sintaxe | Lê |
|---|---|
| `@Campo@` | Campo da aba atual; se não achar, das abas acima |
| `@~Campo@` | Campo só da aba atual |
| `@1\|Campo@` | Campo da aba de número 1 (0 é a primeira aba) |
| `@#Variavel@` | Variável de login (`#AD_Client_ID`, `#AD_Org_ID`, `#AD_User_ID`, `#AD_Role_ID`, `#Date`) |
| `@$Variavel@` | Variável contábil de login (`$C_Currency_ID`, `$C_AcctSchema_ID`) |
| `@P\|Variavel@` | Preferência do usuário |
| `@+Variavel@` | Variável predefinida no menu, na janela ou no perfil (veja abaixo) |
| `@Campo:valor@` | Usa `valor` quando o campo não existe no contexto |
| `@Campo_ID.Coluna@` | Busca uma coluna do registro referenciado: `@C_BPartner_ID.Name@` |

### Operadores das lógicas

| Operador | Significado | Exemplo |
|---|---|---|
| `=` | igual | `@EDU_TipoVisita@=MA` |
| `!` ou `^` | diferente | `@EDU_TipoVisita@!OR` |
| `>` `<` `>=` `<=` | comparação | `@Qty@>=10` |
| `~` | corresponde à expressão regular | `@Description@~'^URG.*'` |
| `&` | e | `@EDU_IsUrgente@=Y & @EDU_TipoVisita@=IN` |
| `\|` | ou | `@EDU_TipoVisita@=IN \| @EDU_TipoVisita@=MA` |
| `( )` | agrupa | `(@A@=Y \| @B@=Y) & @C@=N` |
| `$!` | negação | `$!(@EDU_IsConcluida@=Y)` |
| `'a','b'` | igual a qualquer um da lista | `@EDU_TipoVisita@='IN','MA'` |

Texto entre aspas simples é comparado literalmente. Sem aspas também funciona para valores simples (`Y`, `MA`, `100`).

### Lógicas com SQL

Onde a lógica espera verdadeiro ou falso, `@SQL=SELECT ...` é verdadeiro quando a consulta devolve alguma linha. Na Default Logic, a consulta deve devolver um único valor. Use com parcimônia: cada lógica com SQL é uma ida ao banco sempre que a tela é redesenhada.

### Onde cada lógica pode ficar

| Lógica | Column | Field | Tab |
|---|---|---|---|
| Default Logic | sim | sim (sobrescreve a da coluna) | não |
| Display Logic | não | sim | sim |
| Read Only Logic | sim | sim | sim |
| Mandatory Logic | sim | sim | não |

### Para ir além: variáveis predefinidas no menu

Uma entrada de menu pode injetar variáveis no contexto quando abre a janela, no campo **Predefined Context Variables**, uma por linha, no formato `NOME=valor`. Elas são lidas com `@+NOME@`. É assim que o mesmo cadastro vira duas entradas de menu com comportamentos diferentes, por exemplo uma que abre já filtrada. Elas não são injetadas quando a janela é aberta por zoom.

## Próximo módulo

[03 · Levando mudanças para outro ambiente](https://muriloht.com/idempiere/03-migracao): tudo o que você fez até aqui está só no seu banco. O módulo 03 mostra como empacotar e levar para outro ambiente.
