# 07 · Relatórios

Fonte: https://muriloht.com/idempiere/07-relatorios (Guia iDempiere, Murilo H. Torquato). Versão alvo: iDempiere 14.

Objetivos:
- Criar uma view pelo Dicionário, para que ela viaje no 2Pack
- Montar um relatório padrão com Report View e Print Format, sem Java
- Filtrar o relatório com parâmetros que batem com as colunas da view
- Criar um relatório Jasper com o arquivo dentro do plugin
- Saber quando usar cada tipo de relatório

## Antes de começar

- Módulo 06 concluído: o processo gerou visitas de manutenção, então há dados para listar.
- Para a parte 2 (Jasper), instale o **Jaspersoft Studio 6.21**. A versão importa: o iDempiere 14 usa a biblioteca JasperReports 6.21.0, e um relatório salvo por uma versão mais nova do Studio pode não abrir.

## O que você vai construir

O supervisor da GardenWorld quer saber quantas visitas cada técnico fez no mês, de que tipo e quantos produtos foram usados. Você vai entregar isso de dois jeitos:

- **Relatório padrão** do iDempiere: uma view, um Report View e um formato de impressão gerado sozinho. Nenhuma linha de Java, e o usuário pode reorganizar colunas, agrupar e exportar para Excel.
- **Relatório Jasper**: layout livre, com cabeçalho e logotipo, para quando o documento sai da empresa.

## Os tipos de relatório

| Tipo | Como funciona | Use quando |
|---|---|---|
| **Padrão (Report View)** | Uma view ou tabela + um Print Format | Listagens e totais para uso interno. É o primeiro a tentar |
| **Jasper** | Arquivo `.jrxml` desenhado no Jaspersoft Studio | O layout importa: documento para cliente, etiqueta, formulário |
| **Cubo calculado** | Um processo Java grava numa tabela temporária `T_`, e o relatório lê dela | A lógica é complexa demais para uma view |

## Parte 1: o relatório padrão

### 1. A view, pelo Dicionário

Você poderia criar a view com `CREATE VIEW` no `psql`. O problema: ela não iria no 2Pack, e o próximo ambiente não teria a view. Criando pelo Dicionário, a definição fica no banco como dado e o Pack In recria a view no destino.

Como **System**, na janela **Table and Column**, crie a tabela:

| Campo | Valor |
|---|---|
| DB Table Name | `EDU_RV_Visita` |
| Name | `Visitas (relatório)` |
| View | marcado |
| Data Access Level | `Client+Organization` |
| Entity Type | `EDU` |

O prefixo `RV_` é o costume do core para views de relatório.

Na aba **View Component**, crie um componente:

| Campo | Valor |
|---|---|
| Name | `EDU_RV_Visita` |
| Sql FROM | `FROM EDU_Visita v` |
| Entity Type | `EDU` |

Na aba **View Column** do componente, crie as colunas. **DB Column Name** é o nome na view; **Column SQL** é a expressão:

| Seq | DB Column Name | Column SQL |
|---|---|---|
| 10 | `AD_Client_ID` | `v.AD_Client_ID` |
| 20 | `AD_Org_ID` | `v.AD_Org_ID` |
| 30 | `EDU_Visita_ID` | `v.EDU_Visita_ID` |
| 40 | `EDU_DataVisita` | `v.EDU_DataVisita` |
| 50 | `SalesRep_ID` | `v.SalesRep_ID` |
| 60 | `C_BPartner_ID` | `v.C_BPartner_ID` |
| 70 | `EDU_TipoVisita` | `v.EDU_TipoVisita` |
| 80 | `EDU_IsUrgente` | `v.EDU_IsUrgente` |
| 90 | `EDU_IsConcluida` | `v.EDU_IsConcluida` |
| 100 | `Qty` | `(SELECT COALESCE(SUM(l.Qty),0) FROM EDU_VisitaLinha l WHERE l.EDU_Visita_ID=v.EDU_Visita_ID)` |

> **`AD_Client_ID` e `AD_Org_ID` são obrigatórios.** É por elas que o iDempiere aplica a segurança do perfil: sem elas, o relatório falha ou mostra dados de todas as empresas.

Volte à aba **Table** e clique em **View Validate**. O iDempiere monta o `CREATE OR REPLACE VIEW` a partir dos componentes e roda no banco. Confira:

```sql
SELECT * FROM EDU_RV_Visita LIMIT 5;
```

Agora clique em **Create Columns from DB**: o iDempiere lê as colunas da view e cria os registros de coluna. Revise duas referências que ele não tem como adivinhar:

| Coluna | Reference | Reference Key |
|---|---|---|
| `SalesRep_ID` | Table | `AD_User - Internal` |
| `EDU_TipoVisita` | List | `EDU_TipoVisita` |

Isso faz o relatório mostrar o nome do técnico e o nome do tipo, e não o ID e o código.

### 2. O Report View

**Conceito:** o **Report View** liga um relatório a uma tabela ou view. Pode ter um filtro fixo (`Sql WHERE`) que vale sempre.

Na janela **Report View**, crie:

| Campo | Valor |
|---|---|
| Name | `EDU_RV_Visita` |
| Table | `EDU_RV_Visita` |
| Entity Type | `EDU` |

### 3. O relatório e os parâmetros

Na janela **Report and Process**, crie:

| Campo | Valor |
|---|---|
| Search Key | `EDU_VisitasPorTecnico` |
| Name | `Visitas por Técnico` |
| Data Access Level | `Client+Organization` |
| Entity Type | `EDU` |
| Report | marcado |
| Report View | `EDU_RV_Visita` |

Sem **Classname**: quem roda é o motor de relatórios do core.

Na aba **Parameter**:

| Seq | Name | DB Column Name | Reference | Detalhes |
|---|---|---|---|---|
| 10 | Técnico | `SalesRep_ID` | Table | Reference Key: `AD_User - Internal` |
| 20 | Data da Visita | `EDU_DataVisita` | Date | **Range** marcado |
| 30 | Tipo de Visita | `EDU_TipoVisita` | List | Reference Key: `EDU_TipoVisita` |

É aqui que o relatório padrão economiza código: **cada parâmetro vira um filtro na coluna da view com o mesmo DB Column Name**. Parâmetro vazio não filtra. Com **Range**, a tela pede "de" e "até" e o filtro vira um intervalo.

Crie a entrada de menu com **Action** `Report` e rode **Role Access Update** como GardenAdmin.

### 4. Rode e ajuste o formato

Como GardenAdmin, rode **Visitas por Técnico** sem filtros. Na primeira execução, o iDempiere cria sozinho um **Print Format** com todas as colunas do Report View.

Agora deixe o relatório útil:

1. No visualizador, abra o **Report Wizard** (botão de personalização na barra do relatório).
2. Esconda `AD_Client_ID`, `AD_Org_ID` e `EDU_Visita_ID`.
3. Ordene e agrupe por **Técnico**, e peça a soma de **Qty**.
4. Salve. O formato vale para todos os usuários desse cliente.

Rode de novo filtrando por um técnico e pelo mês atual. Teste também a exportação para Excel e PDF, que vem pronta.

> O formato fica na janela **Print Format** e pertence ao cliente (GardenWorld), não ao Dicionário. Para levar um formato pronto para outro ambiente, inclua-o no Pack Out com o tipo `PrintFormat`.

## Parte 2: o mesmo relatório em Jasper

### 5. Desenhe no Jaspersoft Studio

Crie um relatório em branco `VisitasPorTecnico.jrxml` conectado ao seu banco de desenvolvimento, com esta consulta:

```sql
SELECT v.EDU_DataVisita, u.Name AS Tecnico, bp.Name AS Cliente,
       v.EDU_TipoVisita, v.Qty
FROM EDU_RV_Visita v
JOIN AD_User u ON u.AD_User_ID = v.SalesRep_ID
JOIN C_BPartner bp ON bp.C_BPartner_ID = v.C_BPartner_ID
WHERE v.AD_Client_ID = $P{AD_CLIENT_ID}
  AND ($P{SalesRep_ID} IS NULL OR v.SalesRep_ID = $P{SalesRep_ID})
  AND ($P{EDU_DataVisita1} IS NULL OR v.EDU_DataVisita >= $P{EDU_DataVisita1})
  AND ($P{EDU_DataVisita2} IS NULL OR v.EDU_DataVisita <= $P{EDU_DataVisita2})
ORDER BY u.Name, v.EDU_DataVisita
```

Declare os parâmetros no relatório com estes nomes e tipos:

| Parâmetro | Tipo | De onde vem |
|---|---|---|
| `AD_CLIENT_ID` | `java.lang.Integer` | O iDempiere envia sempre (cliente logado) |
| `SalesRep_ID` | `java.lang.Integer` | Parâmetro do processo |
| `EDU_DataVisita1` | `java.sql.Timestamp` | "De" do parâmetro com Range |
| `EDU_DataVisita2` | `java.sql.Timestamp` | "Até" do parâmetro com Range |

A regra dos nomes: o parâmetro chega com o **DB Column Name**; num Range, chega como dois, com sufixo `1` e `2`. Além dos seus, o iDempiere sempre envia `AD_CLIENT_ID`, `AD_ORG_ID`, `AD_USER_ID`, `AD_ROLE_ID`, `AD_PINSTANCE_ID` e `RECORD_ID`.

> **O filtro por `AD_CLIENT_ID` é responsabilidade sua.** O Jasper roda o SQL que você escreveu, sem a segurança automática do relatório padrão. Esquecer esse filtro mostra dados de outras empresas.

Monte o layout: agrupe por `Tecnico`, com a soma de `Qty` no rodapé do grupo.

### 6. O arquivo mora no plugin

Crie a pasta `reports` no plugin, salve o `.jrxml` nela e inclua a pasta no `build.properties`:

```
bin.includes = META-INF/,\
               .,\
               OSGI-INF/,\
               reports/
```

Sem essa linha, o arquivo funciona no Eclipse e some quando o plugin vira um jar (módulo 12).

### 7. Cadastre o processo Jasper

Na janela **Report and Process**, crie outro registro:

| Campo | Valor |
|---|---|
| Search Key | `EDU_VisitasPorTecnicoJasper` |
| Name | `Visitas por Técnico (PDF)` |
| Data Access Level | `Client+Organization` |
| Entity Type | `EDU` |
| Report | marcado |
| Jasper Report | `bundle:com.gardenworld.visitas:reports/VisitasPorTecnico.jrxml` |

Copie os parâmetros **Técnico** e **Data da Visita** do relatório anterior (mesmos DB Column Names). Preenchido o **Jasper Report**, o iDempiere passa a execução para o motor do Jasper, sem Classname.

O prefixo `bundle:` diz: procure o arquivo dentro do plugin `com.gardenworld.visitas`. O iDempiere compila o `.jrxml` na primeira execução. Outros prefixos aceitos:

| Prefixo | Onde está o arquivo |
|---|---|
| `bundle:plugin:caminho` | Dentro de um plugin (o recomendado) |
| `attachment:arquivo.jrxml` | Anexado ao próprio registro do processo |
| `/caminho/absoluto` | No sistema de arquivos do servidor |

Crie o menu, rode **Role Access Update** e teste.

### 8. Commit

Inclua no Pack Out, na ordem: a tabela `EDU_RV_Visita` (o Pack In recria a view no destino), os dois relatórios (**Process/Report** já leva o Report View junto) e os dois itens de menu. Gere o `2Pack_1.0.3.zip`.

```bash
git add .
git commit -m "Visitas 1.0.3: relatórios padrão e Jasper de visitas por técnico"
```

## Checkpoint

- [ ] A view EDU_RV_Visita existe no banco e foi criada pelo botão View Validate
- [ ] O relatório Visitas por Técnico aparece no menu do GardenAdmin
- [ ] Filtrar por técnico e período muda o resultado
- [ ] Agrupei o relatório por técnico com o Report Wizard
- [ ] O relatório Jasper abre com os mesmos filtros
- [ ] O pacote 1.0.3 leva a view, o relatório e o menu

## Erros comuns

| Sintoma | Causa provável | Correção |
|---|---|---|
| **View Validate** falha | Erro de SQL em alguma **Column SQL** | Monte o `SELECT` à mão no `psql` com as mesmas expressões e corrija |
| O relatório não filtra | DB Column Name do parâmetro diferente do nome da coluna na view | Tem de ser idêntico |
| Relatório mostra IDs em vez de nomes | Referência da coluna da view ficou como número | Passo 1: ajuste Reference e Reference Key |
| Relatório vazio ou erro de acesso | Faltou `AD_Client_ID` ou `AD_Org_ID` na view | Inclua as duas colunas e rode **View Validate** de novo |
| Jasper: "report not found" | Caminho do `bundle:` errado ou pasta fora do `build.properties` | Confira nome do plugin, pasta e arquivo |
| Jasper: parâmetro sempre nulo | Nome ou tipo diferente no `.jrxml` | Range chega como `Nome1` e `Nome2`; `_ID` chega como `Integer` |
| Jasper abre dados de outra empresa | Consulta sem filtro por `AD_CLIENT_ID` | Passo 5 |

## Desafio

Crie o relatório **Produtos mais usados em visitas**: por produto, a quantidade total e em quantas visitas apareceu, com filtro por período. Use o relatório padrão. Você vai precisar de uma segunda view, com `GROUP BY` na **Other SQL Clause** do componente.

## Referência

### Cubo calculado

Quando a lógica não cabe numa view, o relatório lê de uma tabela temporária preenchida por um processo Java:

- A tabela começa com `T_` e tem as colunas `AD_Client_ID`, `AD_Org_ID` e `AD_PInstance_ID`.
- O processo (módulo 06) grava as linhas com o `getAD_PInstance_ID()` da execução.
- O processo é marcado como **Report**, com **Classname** e um **Report View** apontando para a tabela `T_`.
- Para tabelas `T_`, o motor de relatórios filtra só pela execução (`AD_PInstance_ID`), sem transformar os parâmetros em filtro: quem usa os parâmetros é o seu Java.

### Onde cada parte do relatório vive

| Parte | Onde fica | Vai no 2Pack? |
|---|---|---|
| View (componentes e colunas) | Dicionário (Table and Column) | Sim, no tipo Table |
| Report View | Dicionário | Sim, junto do processo |
| Processo e parâmetros | Dicionário | Sim |
| Print Format | Dados do cliente | Só se incluído com o tipo PrintFormat |
| `.jrxml` | Plugin | Vai no jar, não no 2Pack |

## Próximo módulo

[08 · Consulta e navegação](https://muriloht.com/idempiere/08-consulta-e-navegacao): Info Window, mensagens traduzíveis, índices, quick info, configurações do sistema e auditoria.
