PC SOFT

FOROS PROFESIONALES
WINDEVWEBDEV y WINDEV Mobile

Inicio → WINDEV 25 → Manual Completo: Relatórios Programados no WX (Windev/Webdev/Windev Mobile)
Manual Completo: Relatórios Programados no WX (Windev/Webdev/Windev Mobile)
Iniciado por Boller, 02,jul. 2025 15:07 - 8 respuestas
Miembro registrado
4.520 mensajes
Publicado el 02,julio 2025 - 15:07
# Manual Completo: Relatórios Programados no WX (Windev/Webdev/Windev Mobile)

## Introdução

Este manual aborda a técnica de criação de relatórios totalmente programados usando WLanguage, onde o preenchimento dos dados é controlado inteiramente via código, proporcionando máxima flexibilidade e controle sobre a geração de relatórios.

## 1. Configuração Inicial do Relatório

### 1.1 Criação do Relatório

1. Crie um novo relatório no editor
1. **IMPORTANTE**: Configure a fonte de dados como “**Preenchimento por programação**”
1. Adicione os controles necessários (campos de texto, imagens, etc.)
1. Nomeie os controles de forma clara (ex: STC_NOME, STC_VALOR, etc.)

### 1.2 Estrutura Base do Código

```wlanguage
// Variáveis globais
dsMinhaConsulta is Data Source
sSQL is string
bPrimeiroRegistro is boolean = True

// Configuração da consulta SQL
sSQL = [
SELECT
campo1,
campo2,
campo3
FROM
minha_tabela
WHERE
condicao = 'valor'
ORDER BY
campo1
]
```

## 2. Implementação dos Blocos de Código

### 2.1 Bloco BEFORE PRINTING REPORT

```wlanguage
// Executar a consulta SQL
IF NOT HExecuteSQLQuery(dsMinhaConsulta, MyConnection, hQueryWithoutCorrection, sSQL) THEN
Info(HErrorInfo(hErrMessage))
gsERRO is string = HErrorInfo(hErrFullDetails)
RETURN False
ELSE
HReadFirst(dsMinhaConsulta)
END
```

### 2.2 Bloco BEFORE PRINTING BODY (Mais Importante)

```wlanguage
// Verificar se há dados para processar
IF HOut(dsMinhaConsulta) THEN
RESULT NOT_HOut(dsMinhaConsulta)
END

// Preencher os campos do relatório com dados do Data Source
STC_CAMPO1 = dsMinhaConsulta.campo1
STC_CAMPO2 = dsMinhaConsulta.campo2
STC_CAMPO3 = dsMinhaConsulta.campo3

// Tratamento de valores nulos
IF dsMinhaConsulta.valor = Null THEN
STC_VALOR = "0,00"
ELSE
STC_VALOR = NumToString(dsMinhaConsulta.valor, "99999.99")
END

// Cálculos e formatações específicas
IF dsMinhaConsulta.tipo = "ENTRADA" THEN
STC_TIPO.Color = DarkGreen
ELSE
STC_TIPO.Color = DarkRed
END
```

### 2.3 Bloco AFTER PRINTING BODY

```wlanguage
// Avançar para o próximo registro
HReadNext(dsMinhaConsulta)
```

### 2.4 Bloco AFTER PRINTING REPORT

```wlanguage
// Liberar a memória da consulta
HFreeQuery(dsMinhaConsulta)
```

## 3. Exemplo Completo: Relatório de Movimentação de Estoque

### 3.1 Código Completo do Relatório​​​​​​​​​​​​​​​​

## 4. Técnicas Avançadas

### 4.1 Tratamento de Erros

```wlanguage
// Verificação de erros na execução da consulta
IF NOT HExecuteSQLQuery(dsConsulta, MinhaConexao, hQueryWithoutCorrection, sSQL) THEN
sErro is string = HErrorInfo(hErrFullDetails)

// Log do erro
Trace("Erro SQL: " + sErro)

// Notificar usuário
Error("Erro ao gerar relatório. Contate o suporte.")

RETURN False
END
```

### 4.2 Otimização de Performance

```wlanguage
// Usar índices nas consultas
sSQL = [
SELECT /*+ INDEX(tabela, indice_data) */
campos
FROM tabela
WHERE data_campo BETWEEN %1 AND %2
]

// Limitar resultados se necessário
sSQL += " LIMIT 1000"
```

### 4.3 Formatação Condicional Avançada

```wlanguage
// No BEFORE PRINTING BODY
SWITCH dsConsulta.status
CASE "ATIVO"
STC_STATUS.BackgroundColor = LightGreen
STC_STATUS.Font.Bold = True

CASE "INATIVO"
STC_STATUS.BackgroundColor = LightRed
STC_STATUS.Font.Strikethrough = True

CASE "PENDENTE"
STC_STATUS.BackgroundColor = LightYellow
STC_STATUS.Font.Italic = True
END
```

## 5. Boas Práticas

### 5.1 Nomenclatura

- Use prefixos claros: `STC_` para campos de texto, `IMG_` para imagens
- Nomeie Data Sources com prefixo `ds`: `dsRelatorioVendas`
- Use nomes descritivos: `dsMovimentacaoEstoque` ao invés de `ds1`

### 5.2 Gerenciamento de Memória

```wlanguage
// SEMPRE usar HFreeQuery ao final
HFreeQuery(dsConsulta)

// Para múltiplas consultas
HFreeQuery(dsConsulta1)
HFreeQuery(dsConsulta2)
HFreeQuery(dsConsulta3)
```

### 5.3 Validações

```wlanguage
// Validar dados antes de processar
IF HOut(dsConsulta) THEN
Info("Nenhum registro encontrado.")
RETURN False
END

// Validar campos críticos
IF dsConsulta.valor = Null OR dsConsulta.valor < 0 THEN
STC_VALOR = "R$ 0,00"
STC_VALOR.Color = Red
END
```

## 6. Troubleshooting

### 6.1 Problemas Comuns

**Problema**: Relatório não exibe dados
**Solução**: Verificar se `HReadFirst()` foi chamado no `BEFORE PRINTING REPORT`

**Problema**: Erro de memória
**Solução**: Sempre usar `HFreeQuery()` no `AFTER PRINTING REPORT`

**Problema**: Campos não preenchidos
**Solução**: Verificar se o preenchimento está no `BEFORE PRINTING BODY`

**Problema**: Loop infinito
**Solução**: Certificar que `HReadNext()` está no `AFTER PRINTING BODY`

### 6.2 Debug

```wlanguage
// Adicionar traces para debug
Trace("Registro atual: " + dsConsulta.id)
Trace("Valor: " + dsConsulta.valor)

// Verificar posição do cursor
Trace("HOut: " + HOut(dsConsulta))
Trace("Número do registro: " + HRecNum(dsConsulta))
```

## 7. Conclusão

A técnica de relatórios programados oferece controle total sobre a apresentação dos dados, permitindo:

- **Flexibilidade máxima** na formatação
- **Cálculos complexos** em tempo real
- **Formatação condicional** avançada
- **Controle total** sobre a fonte de dados
- **Performance otimizada** com consultas específicas

**Lembre-se sempre**: Configure como “Preenchimento por programação”, use `HExecuteSQLQuery` no início, preencha os campos no `BEFORE PRINTING BODY`, avance com `HReadNext` no `AFTER PRINTING BODY`, e finalize com `HFreeQuery` no `AFTER PRINTING REPORT`.

Esta técnica é essencial para relatórios corporativos complexos e deve fazer parte do arsenal de todo desenvolvedor WX.​​​​​​​​​​​​​​​​

Link exemplo
https://repository.windev.com/resource.awp…

Relatórios do PEDROSAO


Help.Windev.com
Para maiores detalhes

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Miembro registrado
4.520 mensajes
Publicado el 02,julio 2025 - 15:12
Aqui está um manual detalhado e aprimorado, com foco em relatórios condicionados e programados no WX (WinDev/WebDev/Windev Mobile), integrando técnicas avançadas do help.windev.com:



1. Criação Inicial do Relatório

1.1 Passo a passo no Assistente de Relatórios
1. No editor, clique em Criar → Relatório.
2. Escolha o tipo (tabela, formulário, rótulo, etc.).
3. Em “Fonte de dados”, selecione Outro (programming, …)
4. Marque “Programo a leitura dos dados” .
5. Defina parâmetros específicos ao tipo:
• Tabela: número de colunas, cabeçalhos, tipos.
• Formulário: imagem, informações de visualização.
• Rótulo: formato e quantidade de cópias.
6. Configure o layout da página (A4, paisagem, margens).
7. Escolha um theme ou skin.
8. Dê nome/título ao .WDE e finalize.
9. Ajuste layout se colunas “vazarem” .



2. Eventos e Seqüência de Execução

Use o help.windev.com para entender a ordem dos eventos:

Evento Bloco? Objetivo principal
Report Opening — HReadFirst(dto) ()
Block Initializing Header/Body… Inicializações gerais ()
Data Query / Reading Data — HOut → parar ou continuar ()
Before Printing Body Body Atribuir valores, formatações, HReadNext ()
After Printing Body Body Quebra de página, reset de variáveis ()
After Report — HFreeQuery para liberar memória ()

Fluxo completo resumido ():
1. Report Opening: leitura da primeira linha.
2. Reading Data: verifica HOut → RESULT True/False.
3. Before Printing Body: preenche controles e lê o próximo.
4. After Printing Body: aplica iPageEnd se ainda houver dados.
5. After Report: uso de HFreeQuery.



3. Exemplo Avançado (Movimentação Estoque)

3.1 Variáveis iniciais

dsMovStock is Data Source
sSQL is string

3.2 Report Opening

sSQL = [
SELECT id, produto, tipo, quantidade, data
FROM Movimentacao
WHERE condicao = 'X'
ORDER BY data
]
IF NOT HExecuteSQLQuery(dsMovStock, HConnection, hQueryWithoutCorrection, sSQL) THEN
Error("Erro SQL: "+HErrorInfo(hErrFullDetails))
RESULT False
END
HReadFirst(dsMovStock)

3.3 Reading Data

RESULT NOT HOut(dsMovStock)

3.4 Before Printing Body (condições e formatação)

STC_ID = dsMovStock.id
STC_PROD = dsMovStock.produto
STC_DATA = DateToString(dsMovStock.data, "DD/MM/YYYY")

IF dsMovStock.quantidade = Null THEN
STC_QTD = "0"
ELSE
STC_QTD = NumToString(dsMovStock.quantidade, "###,##0")
END

IF dsMovStock.tipo = "ENTRADA" THEN
STC_TIPO.Color = DarkGreen
ELSE
STC_TIPO.Color = DarkRed
END

HReadNext(dsMovStock)

3.5 After Printing Body

IF NOT HOut(dsMovStock) THEN
iPageEnd()
END

3.6 After Report

HFreeQuery(dsMovStock)




4. Técnicas Especiais e Recursos

4.1 Gráficos e Planilhas
• Table/Spreadsheet: podem ser populados por código e ajustados em eventos “Before printing cell”  .
• Gráficos: controle Chart pode ser preenchido via WLanguage automaticamente.

4.2 Formatação Condicional e controles
• Use SWITCH ou IF em Before Printing Body para cores, tipos de fonte, estilos (riscar, negrito, itálico) .
• Controle de blocos: visibilidade, substituições com iSubstBlock, imprimir blocos iterativos com iPrintBlock .

4.3 Funções de impressão úteis

É possível usar funções como iPageEnd, iCloseReport, iParameterPDF, iMargin, entre outras, conforme documentação .



5. Otimização e Boas Práticas
• Erros SQL: log com Trace, exibir mensagens claras ao usuário.
• Performance: usar índices e limitar linhas com LIMIT ().
• Memória: sempre HFreeQuery ao final.
• Nomenclatura: STC_, IMG_, dsNome, qryNome para clareza.
• Null e validação: tratar em corpo do relatório para evitar campos em branco ou erros.



6. Exemplo Completo em WLanguage

PROCEDURE RelMovEstoque()
REPORT_OPENING
... // Report Opening conforme §3.2

REPORT_READING
RESULT NOT HOut(dsMovStock)

BODY_BEFORE_PRINTING
... // Corpo conforme §3.4

BODY_AFTER_PRINTING
... // Quebra de página conforme §3.5

REPORT_AFTER
HFreeQuery(dsMovStock)
END




✅ Resumo Final
• Crie com fonte “Preenchimento por programação” 
• Use HExecuteSQLQuery e HReadFirst em Report Opening
• No Reading Data, use RESULT NOT HOut(...)
• Em Body Before, preencha controles, formate e leia próximo
• Após corpo, use iPageEnd se houver mais registros
• Finalize com HFreeQuery



Este manual integra o exemplo básico inicial com as boas práticas do help.windev.com, detalhando eventos, funções avançadas e formatação condicional programada — ideal para relatórios dinâmicos e robustos no WX. Se quiser ampliar com planilhas, gráficos ou relatórios aninhados, posso detalhar em seguida!

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Miembro registrado
4.520 mensajes
Publicado el 02,julio 2025 - 15:17
Novo Exemplo: Relatório de Vendas por Cliente (WLanguage)

Objetivo

Criar um relatório que exiba as vendas de cada cliente, incluindo nome, total de vendas, data da última compra e status (ativo/inativo). O relatório terá formatação condicional (cores baseadas no status) e tratamento de valores nulos, ideal para um desenvolvedor júnior.
Passo a Passo para um Desenvolvedor Júnior
1. Criando o Relatório no Editor
1 Abra o WinDev e clique em Arquivo → Novo → Relatório.
2 Escolha o tipo Tabela (para listar vendas).
3 Na tela de fonte de dados, selecione Outro (programação, …) e marque “Programo a leitura dos dados”.
4 Adicione controles no layout:
◦ STC_NOME_CLIENTE: Campo de texto para o nome do cliente.
◦ STC_TOTAL_VENDAS: Campo de texto para o total de vendas (formato monetário).
◦ STC_ULTIMA_COMPRA: Campo de texto para a data da última compra.
◦ STC_STATUS: Campo de texto para o status (ativo/inativo).
5 Configure o layout (ex.: A4, margens de 1 cm, tema moderno).
6 Salve o relatório com o nome REL_VendasPorCliente.
2. Estrutura do Banco de Dados
Para este exemplo, assumimos uma tabela simples no banco de dados chamada Vendas com a seguinte estrutura:
CREATE TABLE Vendas (
id_venda INT PRIMARY KEY,
nome_cliente VARCHAR(100),
total_vendas DECIMAL(10,2),
data_ultima_compra DATE,
status VARCHAR(20) -- 'ATIVO' ou 'INATIVO'
);
3. Código WLanguage Completo
Abaixo está o código comentado e organizado para gerar o relatório programado. Cada bloco é explicado para facilitar a compreensão.
// Procedimento principal do relatório
PROCEDURE REL_VendasPorCliente()

// === Declaração de Variáveis ===
dsVendas is Data Source // Fonte de dados para a consulta
sSQL is string // String para a query SQL
bErro is boolean = False // Controle de erro

// === Bloco: REPORT_OPENING (Início do relatório) ===
// Configura e executa a consulta SQL
sSQL = [
SELECT id_venda, nome_cliente, total_vendas, data_ultima_compra, status
FROM Vendas
WHERE data_ultima_compra >= %1
ORDER BY nome_cliente
]

// Substitui %1 por uma data inicial (ex.: últimos 30 dias)
sSQL = StringBuild(sSQL, DateToString(DateSys() - 30, "YYYYMMDD"))

// Executa a consulta
IF NOT HExecuteSQLQuery(dsVendas, hDefaultConnection, hQueryWithoutCorrection, sSQL) THEN
Error("Erro ao consultar dados: " + HErrorInfo(hErrMessage))
Trace("Detalhes do erro: " + HErrorInfo(hErrFullDetails)) // Log para debug
bErro = True
RETURN False
END

// Lê o primeiro registro
HReadFirst(dsVendas)

// === Bloco: READING_DATA (Verifica se há dados) ===
// Retorna False se não houver registros
IF HOut(dsVendas) THEN
Info("Nenhum registro de vendas encontrado.")
RETURN False
END
RESULT True

// === Bloco: BEFORE_PRINTING_BODY (Preenche os campos do relatório) ===
// Preenche os controles com os dados do registro atual
STC_NOME_CLIENTE = dsVendas.nome_cliente

// Trata valores nulos para o total de vendas
IF dsVendas.total_vendas = Null THEN
STC_TOTAL_VENDAS = "R$ 0,00"
ELSE
STC_TOTAL_VENDAS = "R$ " + NumToString(dsVendas.total_vendas, "#,##0.00")
END

// Formata a data da última compra
IF dsVendas.data_ultima_compra = Null THEN
STC_ULTIMA_COMPRA = "Sem compras"
ELSE
STC_ULTIMA_COMPRA = DateToString(dsVendas.data_ultima_compra, "DD/MM/YYYY")
END

// Formatação condicional para o status
SWITCH dsVendas.status
CASE "ATIVO"
STC_STATUS = "Ativo"
STC_STATUS.BackgroundColor = LightGreen
STC_STATUS.Font.Bold = True
CASE "INATIVO"
STC_STATUS = "Inativo"
STC_STATUS.BackgroundColor = LightRed
STC_STATUS.Font.Strikethrough = True
OTHER CASE
STC_STATUS = "Desconhecido"
STC_STATUS.BackgroundColor = LightYellow
END

// Avança para o próximo registro
HReadNext(dsVendas)

// === Bloco: AFTER_PRINTING_BODY (Controle de página) ===
// Se ainda houver registros, continua imprimindo
IF NOT HOut(dsVendas) THEN
iPageEnd() // Força a impressão do registro atual e continua
END

// === Bloco: AFTER_REPORT (Finalização) ===
// Libera a memória da consulta
HFreeQuery(dsVendas)

// Se houve erro, não imprime o relatório
IF bErro THEN
iCloseReport()
END
4. Explicação dos Blocos
• REPORT_OPENING: Configura a consulta SQL para buscar vendas dos últimos 30 dias, executa-a e lê o primeiro registro. Inclui tratamento de erro com mensagens claras.
• READING_DATA: Verifica se há registros. Se não houver, exibe uma mensagem amigável e interrompe o relatório.
• BEFORE_PRINTING_BODY: Preenche os controles do relatório com os dados do registro atual, trata valores nulos e aplica formatação condicional (cores e estilos de fonte).
• AFTER_PRINTING_BODY: Avança para o próximo registro e usa iPageEnd() para garantir que o relatório continue imprimindo enquanto houver dados.
• AFTER_REPORT: Libera a memória com HFreeQuery e fecha o relatório se houve erro.
5. Recursos Inovadores Adicionados
1 Filtro Dinâmico: A consulta usa StringBuild para incluir um parâmetro de data (últimos 30 dias), tornando o relatório reutilizável.
2 Formatação Condicional Moderna: Usa SWITCH para aplicar cores e estilos de fonte (negrito, riscado) com base no status do cliente.
3 Mensagens Amigáveis: Inclui mensagens de erro claras para o usuário e logs detalhados com Trace para depuração.
4 Tratamento de Nulos: Garante que valores nulos sejam exibidos de forma legível (ex.: “R$ 0,00” ou “Sem compras”).
5 Controle de Erro Global: A variável bErro evita que o relatório seja impresso se ocorrer um erro na consulta.
6. Boas Práticas Aplicadas
• Nomenclatura Clara: Controles com prefixo STC_ e fonte de dados com ds.
• Gerenciamento de Memória: Uso de HFreeQuery para liberar recursos.
• Validações: Tratamento de nulos e verificação de HOut para evitar erros.
• Performance: Consulta com ORDER BY para organizar os dados e filtro de data para limitar resultados.
• Debug: Uso de Trace para facilitar a identificação de problemas.
7. Como Testar
1 Crie a tabela Vendas no seu banco de dados e insira alguns registros de teste.
2 Crie o relatório no editor do WinDev conforme descrito.
3 Copie e cole o código acima nos eventos correspondentes do relatório (clique com o botão direito no relatório → “Código”).
4 Execute o relatório com iPrintReport(REL_VendasPorCliente) em uma janela ou procedimento.
5 Verifique o resultado em PDF ou na tela. Use a função Trace para depurar se necessário.
8. Dicas para o Desenvolvedor Júnior
• Teste Incrementalmente: Comece com uma consulta simples e adicione formatações aos poucos.
• Use o Help: Consulte o help.windev.com para funções como iPrintBlock ou iParameterPDF se quiser personalizar a saída.
• Depure com Trace: Adicione Trace em pontos críticos para entender o fluxo do relatório.
• Valide o Layout: Certifique-se de que os controles não “vazam” para fora da página no editor.
• Experimente: Adicione gráficos ou planilhas (ex.: controle Chart) para tornar o relatório mais visual.
9. Exemplo de Dados de Teste
Insira no banco para testar:
INSERT INTO Vendas (id_venda, nome_cliente, total_vendas, data_ultima_compra, status) VALUES
(1, 'João Silva', 1500.50, '2025-06-15', 'ATIVO'),
(2, 'Maria Oliveira', NULL, NULL, 'INATIVO'),
(3, 'Pedro Santos', 750.25, '2025-07-01', 'ATIVO');
10. Resultado Esperado
O relatório exibirá uma tabela com:
• Nome do cliente (ex.: “João Silva”).
• Total de vendas formatado (ex.: “R$ 1.500,50” ou “R$ 0,00” para nulos).
• Data da última compra (ex.: “15/06/2025” ou “Sem compras”).
• Status com cores (verde para “Ativo”, vermelho para “Inativo”, amarelo para “Desconhecido”).

Por que este Exemplo é Inovador?
1 Simplicidade para Juniores: O código é comentado e estruturado em etapas claras, com explicações para cada bloco.
2 Funcionalidades Modernas: Inclui formatação condicional, tratamento de nulos e filtros dinâmicos, que são comuns em relatórios corporativos.
3 Robusto e Reutilizável: Pode ser facilmente adaptado para outros tipos de relatórios (ex.: estoque, financeiro) alterando a consulta SQL e os controles.
4 Foco em Boas Práticas: Segue as recomendações do manual original, como nomenclatura, gerenciamento de memória e tratamento de erros.

Ok

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Miembro registrado
4.520 mensajes
Publicado el 02,julio 2025 - 15:24
// ==============================================
// RELATÓRIO: RPT_DASHBOARD_EXECUTIVO
// TIPO: Dashboard com múltiplos gráficos programados
// CONTROLES: CHART_VENDAS_MES, CHART_PRODUTOS_TOP, CHART_REGIOES, TABLE_RESUMO
// ==============================================

// ==============================================
// DECLARAÇÕES GLOBAIS
// ==============================================
dsVendasMes is Data Source
dsProdutosTop is Data Source
dsRegioes is Data Source
dsResumoGeral is Data Source

nTotalVendas is numeric = 0
nMetaMensal is numeric = 500000
arrCoresGrafico is array of 12 strings = [”#FF6B6B”, “#4ECDC4”, “#45B7D1”, “#96CEB4”,
“#FFEAA7”, “#DDA0DD”, “#98D8C8”, “#F06292”, “#AED581”, “#FFB74D”, “#A1887F”, “#90A4AE”]

// ==============================================
// BEFORE PRINTING REPORT
// ==============================================

// ===== CONSULTA 1: Vendas por Mês =====
sSQL_VendasMes is string = [
SELECT
EXTRACT(MONTH FROM data_venda) as mes,
EXTRACT(YEAR FROM data_venda) as ano,
SUM(valor_total) as total_vendas,
COUNT(*) as qtd_vendas,
AVG(valor_total) as ticket_medio
FROM vendas
WHERE data_venda >= CURRENT_DATE - INTERVAL ‘12 months’
AND status_venda = ‘FINALIZADA’
GROUP BY EXTRACT(YEAR FROM data_venda), EXTRACT(MONTH FROM data_venda)
ORDER BY ano DESC, mes DESC
LIMIT 12
]

IF NOT HExecuteSQLQuery(dsVendasMes, MyConnection, hQueryWithoutCorrection, sSQL_VendasMes) THEN
Error(“Erro ao carregar dados de vendas por mês: “ + HErrorInfo(hErrMessage))
RETURN False
END

// ===== CONSULTA 2: Top 10 Produtos =====
sSQL_ProdutosTop is string = [
SELECT
p.nome_produto,
p.codigo_produto,
SUM(iv.quantidade) as qtd_vendida,
SUM(iv.valor_total) as valor_total,
AVG(iv.valor_unitario) as preco_medio,
COUNT(DISTINCT v.id_venda) as num_vendas
FROM produtos p
INNER JOIN itens_venda iv ON p.id_produto = iv.id_produto
INNER JOIN vendas v ON iv.id_venda = v.id_venda
WHERE v.data_venda >= CURRENT_DATE - INTERVAL ‘3 months’
AND v.status_venda = ‘FINALIZADA’
GROUP BY p.id_produto, p.nome_produto, p.codigo_produto
ORDER BY valor_total DESC
LIMIT 10
]

IF NOT HExecuteSQLQuery(dsProdutosTop, MyConnection, hQueryWithoutCorrection, sSQL_ProdutosTop) THEN
Error(“Erro ao carregar top produtos: “ + HErrorInfo(hErrMessage))
RETURN False
END

// ===== CONSULTA 3: Vendas por Região =====
sSQL_Regioes is string = [
SELECT
r.nome_regiao,
r.sigla_regiao,
COUNT(v.id_venda) as qtd_vendas,
SUM(v.valor_total) as total_vendas,
AVG(v.valor_total) as ticket_medio,
COUNT(DISTINCT c.id_cliente) as clientes_ativos
FROM regioes r
INNER JOIN clientes c ON r.id_regiao = c.id_regiao
INNER JOIN vendas v ON c.id_cliente = v.id_cliente
WHERE v.data_venda >= CURRENT_DATE - INTERVAL ‘1 month’
AND v.status_venda = ‘FINALIZADA’
GROUP BY r.id_regiao, r.nome_regiao, r.sigla_regiao
ORDER BY total_vendas DESC
]

IF NOT HExecuteSQLQuery(dsRegioes, MyConnection, hQueryWithoutCorrection, sSQL_Regioes) THEN
Error(“Erro ao carregar dados por região: “ + HErrorInfo(hErrMessage))
RETURN False
END

// ===== CONSULTA 4: Resumo Geral =====
sSQL_Resumo is string = [
SELECT
‘VENDAS_HOJE’ as indicador,
COALESCE(SUM(valor_total), 0) as valor,
COUNT(*) as quantidade
FROM vendas
WHERE DATE(data_venda) = CURRENT_DATE
AND status_venda = ‘FINALIZADA’
UNION ALL
SELECT
‘VENDAS_MES’ as indicador,
COALESCE(SUM(valor_total), 0) as valor,
COUNT(*) as quantidade
FROM vendas
WHERE EXTRACT(MONTH FROM data_venda) = EXTRACT(MONTH FROM CURRENT_DATE)
AND EXTRACT(YEAR FROM data_venda) = EXTRACT(YEAR FROM CURRENT_DATE)
AND status_venda = ‘FINALIZADA’
UNION ALL
SELECT
‘CLIENTES_NOVOS’ as indicador,
COUNT(*) as valor,
0 as quantidade
FROM clientes
WHERE DATE(data_cadastro) >= CURRENT_DATE - INTERVAL ‘30 days’
UNION ALL
SELECT
‘PRODUTOS_ESTOQUE_BAIXO’ as indicador,
COUNT(*) as valor,
0 as quantidade
FROM produtos
WHERE estoque_atual <= estoque_minimo
]

IF NOT HExecuteSQLQuery(dsResumoGeral, MyConnection, hQueryWithoutCorrection, sSQL_Resumo) THEN
Error(“Erro ao carregar resumo geral: “ + HErrorInfo(hErrMessage))
RETURN False
END

// ==============================================
// BEFORE PRINTING HEADER
// ==============================================

// Configurar título dinâmico com período
STC_TITULO = “Dashboard Executivo - “ + DateToString(DateSys(), “MMMM YYYY”)
STC_PERIODO = “Período de Análise: “ + DateToString(DateSys() - 90, “DD/MM/YYYY”) + “ a “ + DateToString(DateSys(), “DD/MM/YYYY”)
STC_DATA_GERACAO = “Gerado em: “ + DateTimeToString(Now(), “DD/MM/YYYY às HH:MM”)

// ==============================================
// BEFORE PRINTING BODY
// ==============================================

// ===== CONFIGURAR GRÁFICO DE VENDAS POR MÊS =====
// Limpar gráfico
grDeleteAll(CHART_VENDAS_MES)

// Configurar propriedades do gráfico
grType(CHART_VENDAS_MES, grColumn3D)
grTitle(CHART_VENDAS_MES, “Evolução de Vendas - Últimos 12 Meses”)
grAxisTitle(CHART_VENDAS_MES, grXCoordinate, “Meses”)
grAxisTitle(CHART_VENDAS_MES, grYCoordinate, “Valor em R$”)
grColor(CHART_VENDAS_MES, 1, RGB(70, 130, 180))
grGradient(CHART_VENDAS_MES, 1, RGB(135, 206, 250), RGB(70, 130, 180), 45)

// Adicionar dados do gráfico
HReadFirst(dsVendasMes)
nContadorMes is int = 1
WHILE NOT HOut(dsVendasMes)
sMesAno is string = Right(“0” + dsVendasMes.mes, 2) + “/” + Right(dsVendasMes.ano, 2)
grAddData(CHART_VENDAS_MES, 1, dsVendasMes.total_vendas)
grCategoryLabel(CHART_VENDAS_MES, nContadorMes, sMesAno)

```
// Adicionar meta como linha
grAddData(CHART_VENDAS_MES, 2, nMetaMensal)

nTotalVendas += dsVendasMes.total_vendas
nContadorMes++
HReadNext(dsVendasMes)
```

END

// Configurar segunda série (meta)
grSeriesLabel(CHART_VENDAS_MES, 1, “Vendas Realizadas”)
grSeriesLabel(CHART_VENDAS_MES, 2, “Meta Mensal”)
grSeriesType(CHART_VENDAS_MES, 2, grLine)
grColor(CHART_VENDAS_MES, 2, RGB(255, 99, 71))

// ===== CONFIGURAR GRÁFICO DE TOP PRODUTOS =====
grDeleteAll(CHART_PRODUTOS_TOP)
grType(CHART_PRODUTOS_TOP, grPie)
grTitle(CHART_PRODUTOS_TOP, “Top 10 Produtos por Faturamento”)
grLegend(CHART_PRODUTOS_TOP, grAtBottom)

HReadFirst(dsProdutosTop)
nContadorProd is int = 1
WHILE NOT HOut(dsProdutosTop) AND nContadorProd <= 10
sNomeProduto is string = Left(dsProdutosTop.nome_produto, 20)
nValorProduto is numeric = dsProdutosTop.valor_total

```
grAddData(CHART_PRODUTOS_TOP, 1, nValorProduto)
grCategoryLabel(CHART_PRODUTOS_TOP, nContadorProd, sNomeProduto)

// Usar cores do array
IF nContadorProd <= Dimension(arrCoresGrafico) THEN
grColor(CHART_PRODUTOS_TOP, nContadorProd, HTML(arrCoresGrafico[nContadorProd]))
END

nContadorProd++
HReadNext(dsProdutosTop)
```

END

// ===== CONFIGURAR GRÁFICO DE REGIÕES =====
grDeleteAll(CHART_REGIOES)
grType(CHART_REGIOES, grBarHorizontal)
grTitle(CHART_REGIOES, “Vendas por Região - Último Mês”)
grAxisTitle(CHART_REGIOES, grXCoordinate, “Valor em R$”)
grAxisTitle(CHART_REGIOES, grYCoordinate, “Regiões”)

HReadFirst(dsRegioes)
nContadorReg is int = 1
WHILE NOT HOut(dsRegioes)
grAddData(CHART_REGIOES, 1, dsRegioes.total_vendas)
grCategoryLabel(CHART_REGIOES, nContadorReg, dsRegioes.nome_regiao)

```
// Gradiente baseado no valor
IF dsRegioes.total_vendas > 100000 THEN
grColor(CHART_REGIOES, nContadorReg, RGB(34, 139, 34)) // Verde forte
ELSE IF dsRegioes.total_vendas > 50000 THEN
grColor(CHART_REGIOES, nContadorReg, RGB(255, 165, 0)) // Laranja
ELSE
grColor(CHART_REGIOES, nContadorReg, RGB(220, 20, 60)) // Vermelho
END

nContadorReg++
HReadNext(dsRegioes)
```

END

// ===== PREENCHER TABELA DE RESUMO =====
TableDeleteAll(TABLE_RESUMO)

HReadFirst(dsResumoGeral)
WHILE NOT HOut(dsResumoGeral)
nLinha is int = TableAddLine(TABLE_RESUMO)

```
SWITCH dsResumoGeral.indicador
CASE "VENDAS_HOJE"
TABLE_RESUMO[nLinha][1] = "Vendas Hoje"
TABLE_RESUMO[nLinha][2] = NumToString(dsResumoGeral.valor, "R$ 999,999.99")
TABLE_RESUMO[nLinha][3] = dsResumoGeral.quantidade + " vendas"

// Colorir baseado na performance
IF dsResumoGeral.valor > 10000 THEN
TABLE_RESUMO[nLinha].BackgroundColor = LightGreen
ELSE IF dsResumoGeral.valor > 5000 THEN
TABLE_RESUMO[nLinha].BackgroundColor = LightYellow
ELSE
TABLE_RESUMO[nLinha].BackgroundColor = LightRed
END

CASE "VENDAS_MES"
TABLE_RESUMO[nLinha][1] = "Vendas do Mês"
TABLE_RESUMO[nLinha][2] = NumToString(dsResumoGeral.valor, "R$ 999,999.99")
TABLE_RESUMO[nLinha][3] = dsResumoGeral.quantidade + " vendas"

// Comparar com meta
nPercentualMeta is numeric = (dsResumoGeral.valor / nMetaMensal) * 100
TABLE_RESUMO[nLinha][4] = NumToString(nPercentualMeta, "999.9") + "% da meta"

CASE "CLIENTES_NOVOS"
TABLE_RESUMO[nLinha][1] = "Novos Clientes (30d)"
TABLE_RESUMO[nLinha][2] = dsResumoGeral.valor + " clientes"
TABLE_RESUMO[nLinha][3] = "Crescimento da base"

CASE "PRODUTOS_ESTOQUE_BAIXO"
TABLE_RESUMO[nLinha][1] = "Estoque Baixo"
TABLE_RESUMO[nLinha][2] = dsResumoGeral.valor + " produtos"
TABLE_RESUMO[nLinha][3] = "Requer atenção"

IF dsResumoGeral.valor > 50 THEN
TABLE_RESUMO[nLinha].Color = Red
TABLE_RESUMO[nLinha].Font.Bold = True
END
END

HReadNext(dsResumoGeral)
```

END

// ===== INDICADORES VISUAIS (CARDS) =====
// Card de Performance Geral
nPerformanceGeral is numeric = (nTotalVendas / (nMetaMensal * 12)) * 100

IF nPerformanceGeral >= 100 THEN
STC_PERFORMANCE.BackgroundColor = RGB(76, 175, 80) // Verde
STC_PERFORMANCE.Color = White
STC_PERFORMANCE_ICON = “✓”
ELSE IF nPerformanceGeral >= 80 THEN
STC_PERFORMANCE.BackgroundColor = RGB(255, 193, 7) // Amarelo
STC_PERFORMANCE.Color = Black
STC_PERFORMANCE_ICON = “⚠”
ELSE
STC_PERFORMANCE.BackgroundColor = RGB(244, 67, 54) // Vermelho
STC_PERFORMANCE.Color = White
STC_PERFORMANCE_ICON = “✗”
END

STC_PERFORMANCE = NumToString(nPerformanceGeral, “999.9”) + “%”

// Redimensionar gráficos dinamicamente
grSaveBMP(CHART_VENDAS_MES, CompleteDir(fDataDir()) + “temp_vendas_mes.bmp”)
grSaveBMP(CHART_PRODUTOS_TOP, CompleteDir(fDataDir()) + “temp_produtos_top.bmp”)
grSaveBMP(CHART_REGIOES, CompleteDir(fDataDir()) + “temp_regioes.bmp”)

// ==============================================
// AFTER PRINTING BODY
// ==============================================
// Não há loop de registros neste relatório, é um relatório único

// ==============================================
// BEFORE PRINTING FOOTER
// ==============================================

// Configurar informações de rodapé
STC_TOTAL_VENDAS_FOOTER = “Total Geral: “ + NumToString(nTotalVendas, “R$ 999,999,999.99”)
STC_META_FOOTER = “Meta Anual: “ + NumToString(nMetaMensal * 12, “R$ 999,999,999.99”)
STC_USUARIO_FOOTER = “Gerado por: “ + gsUsuarioLogado
STC_SISTEMA_FOOTER = “Sistema: “ + gsNomeSistema + “ v” + gsVersaoSistema

// ==============================================
// AFTER PRINTING REPORT
// ==============================================

// Liberar todas as consultas
HFreeQuery(dsVendasMes)
HFreeQuery(dsProdutosTop)
HFreeQuery(dsRegioes)
HFreeQuery(dsResumoGeral)

// Limpar arquivos temporários
fDelete(CompleteDir(fDataDir()) + “temp_vendas_mes.bmp”)
fDelete(CompleteDir(fDataDir()) + “temp_produtos_top.bmp”)
fDelete(CompleteDir(fDataDir()) + “temp_regioes.bmp”)

// Resetar variáveis
nTotalVendas = 0

Trace(“Dashboard executivo gerado com sucesso!”)

// ==============================================
// PROCEDIMENTOS AUXILIARES
// ==============================================

// Procedimento para configurar cores dinâmicas do gráfico
PROCEDURE ConfigurarCoresGrafico(LOCAL nomeGrafico is string, LOCAL nNumSeries is int)

arrCoresPadrao is array of strings = [
“#FF6B6B”, “#4ECDC4”, “#45B7D1”, “#96CEB4”, “#FFEAA7”,
“#DDA0DD”, “#98D8C8”, “#F06292”, “#AED581”, “#FFB74D”
]

FOR i = 1 TO nNumSeries
IF i <= Dimension(arrCoresPadrao) THEN
grColor(nomeGrafico, i, HTML(arrCoresPadrao[i]))
END
END

// Procedimento para adicionar efeitos 3D nos gráficos
PROCEDURE ConfigurarEfeitos3D(LOCAL nomeGrafico is string)

grParameter(nomeGrafico, grElevation, 30)
grParameter(nomeGrafico, grRotation, 45)
grParameter(nomeGrafico, grPerspective, 50)
grSmoothing(nomeGrafico, True)

// ==============================================
// EXEMPLO DE CHAMADA
// ==============================================
/*
// Configurar parâmetros globais
gsUsuarioLogado = “João Silva”
gsNomeSistema = “ERP Corporativo”
gsVersaoSistema = “2.1.5”

// Configurar destino
iDestination(iViewer)

// Gerar o relatório
iPrintReport(RPT_DASHBOARD_EXECUTIVO)
*/

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Miembro registrado
4.520 mensajes
Publicado el 02,julio 2025 - 15:28
// ==============================================
// RELATÓRIO: RPT_MOVIMENTACAO_ESTOQUE
// FONTE DE DADOS: Preenchimento por programação
// ==============================================

// ==============================================
// DECLARAÇÕES GLOBAIS
// ==============================================
dsMovimentoEstoque is Data Source
sSQL is string
nTotalEntradas is numeric = 0
nTotalSaidas is numeric = 0
nTotalGeral is numeric = 0

// ==============================================
// BEFORE PRINTING REPORT
// ==============================================
// Construir a consulta SQL
sSQL = [
SELECT
mov.id_movimento,
mov.data_movimento,
mov.tipo_movimento,
mov.quantidade,
mov.valor_unitario,
mov.valor_total,
prod.codigo_produto,
prod.descricao_produto,
prod.unidade_medida,
est.estoque_anterior,
est.estoque_atual
FROM
movimentos_estoque mov
INNER JOIN produtos prod ON mov.id_produto = prod.id_produto
INNER JOIN estoque est ON mov.id_estoque = est.id_estoque
WHERE
mov.data_movimento BETWEEN ‘%1’ AND ‘%2’
AND mov.ativo = 1
ORDER BY
mov.data_movimento DESC,
prod.descricao_produto
]

// Substituir parâmetros da consulta
sSQL = StringBuild(sSQL, DateToString(gdDataInicio, “YYYY-MM-DD”), DateToString(gdDataFim, “YYYY-MM-DD”))

// Executar a consulta
IF NOT HExecuteSQLQuery(dsMovimentoEstoque, MyConnection_Postgres, hQueryWithoutCorrection, sSQL) THEN
Info(“Erro ao executar consulta: “ + HErrorInfo(hErrMessage))
gsERRO is string = HErrorInfo(hErrFullDetails)
Error(“Detalhes do erro:”, gsERRO)
RETURN False
ELSE
// Posicionar no primeiro registro
HReadFirst(dsMovimentoEstoque)

```
// Verificar se existem dados
IF HOut(dsMovimentoEstoque) THEN
Info("Nenhum dado encontrado para o período selecionado.")
RETURN False
END
```

END

// ==============================================
// BEFORE PRINTING BODY
// ==============================================
// Verificar se ainda há registros para processar
IF HOut(dsMovimentoEstoque) THEN
RESULT NOT_HOut(dsMovimentoEstoque)
END

// PREENCHER OS CAMPOS DO RELATÓRIO
// Dados do movimento
STC_ID = dsMovimentoEstoque.id_movimento
STC_DATA = DateToString(dsMovimentoEstoque.data_movimento, “DD/MM/YYYY”)
STC_TIPO = Upper(dsMovimentoEstoque.tipo_movimento)

// Dados do produto
STC_CODIGO = dsMovimentoEstoque.codigo_produto
STC_DESCRICAO = dsMovimentoEstoque.descricao_produto
STC_UNIDADE = dsMovimentoEstoque.unidade_medida

// Tratamento de valores nulos e formatação
// Quantidade
IF dsMovimentoEstoque.quantidade = Null OR dsMovimentoEstoque.quantidade = 0 THEN
STC_QUANTIDADE = “0”
ELSE
STC_QUANTIDADE = NumToString(dsMovimentoEstoque.quantidade, “999999”)
END

// Valor unitário
IF dsMovimentoEstoque.valor_unitario = Null THEN
STC_VALOR_UNITARIO = “R$ 0,00”
ELSE
STC_VALOR_UNITARIO = “R$ “ + NumToString(dsMovimentoEstoque.valor_unitario, “999999.99”)
END

// Valor total
IF dsMovimentoEstoque.valor_total = Null THEN
STC_VALOR_TOTAL = “R$ 0,00”
ELSE
STC_VALOR_TOTAL = “R$ “ + NumToString(dsMovimentoEstoque.valor_total, “999999.99”)
END

// Estoque anterior e atual
STC_ESTOQUE_ANTERIOR = NumToString(dsMovimentoEstoque.estoque_anterior, “999999”)
STC_ESTOQUE_ATUAL = NumToString(dsMovimentoEstoque.estoque_atual, “999999”)

// FORMATAÇÃO CONDICIONAL
// Colorir conforme o tipo de movimento
SWITCH dsMovimentoEstoque.tipo_movimento
CASE “ENTRADA”
STC_TIPO.Color = DarkGreen
STC_QUANTIDADE.Color = DarkGreen
STC_VALOR_TOTAL.Color = DarkGreen

```
// Acumular totais
nTotalEntradas += dsMovimentoEstoque.valor_total

CASE "SAIDA"
STC_TIPO.Color = DarkRed
STC_QUANTIDADE.Color = DarkRed
STC_VALOR_TOTAL.Color = DarkRed

// Acumular totais
nTotalSaidas += dsMovimentoEstoque.valor_total

OTHER CASE
STC_TIPO.Color = Black
STC_QUANTIDADE.Color = Black
STC_VALOR_TOTAL.Color = Black
```

END

// Destacar produtos com estoque baixo
IF dsMovimentoEstoque.estoque_atual < 10 THEN
STC_ESTOQUE_ATUAL.Color = Orange
STC_ESTOQUE_ATUAL.Font.Bold = True
END

// Calcular giro de estoque
nGiroEstoque is numeric
IF dsMovimentoEstoque.estoque_anterior > 0 THEN
nGiroEstoque = (dsMovimentoEstoque.quantidade / dsMovimentoEstoque.estoque_anterior) * 100
IF nGiroEstoque > 50 THEN
STC_GIRO_ESTOQUE = NumToString(nGiroEstoque, “999.9”) + “%”
STC_GIRO_ESTOQUE.Color = Red
ELSE
STC_GIRO_ESTOQUE = NumToString(nGiroEstoque, “999.9”) + “%”
STC_GIRO_ESTOQUE.Color = Black
END
ELSE
STC_GIRO_ESTOQUE = “N/A”
END

// Calcular porcentagem do movimento
nPercentual is numeric
IF nTotalEntradas + nTotalSaidas > 0 THEN
nPercentual = (dsMovimentoEstoque.valor_total / (nTotalEntradas + nTotalSaidas)) * 100
STC_PORCENTAGEM = NumToString(nPercentual, “999.99”) + “%”
ELSE
STC_PORCENTAGEM = “0,00%”
END

// ==============================================
// AFTER PRINTING BODY
// ==============================================
// Avançar para o próximo registro
HReadNext(dsMovimentoEstoque)

// ==============================================
// BEFORE PRINTING END_OF_DOCUMENT
// ==============================================
// Calcular totais finais
nTotalGeral = nTotalEntradas - nTotalSaidas

// Preencher campos de totais
STC_TOTAL_ENTRADAS = “R$ “ + NumToString(nTotalEntradas, “999,999.99”)
STC_TOTAL_SAIDAS = “R$ “ + NumToString(nTotalSaidas, “999,999.99”)
STC_TOTAL_GERAL = “R$ “ + NumToString(nTotalGeral, “999,999.99”)

// Colorir total geral conforme saldo
IF nTotalGeral >= 0 THEN
STC_TOTAL_GERAL.Color = DarkGreen
ELSE
STC_TOTAL_GERAL.Color = DarkRed
END

// Informações do rodapé
STC_DATA_GERACAO = “Relatório gerado em: “ + DateTimeToString(Now(), “DD/MM/YYYY às HH:MM:SS”)
STC_USUARIO = “Usuário: “ + gsNomeUsuario
STC_PERIODO = “Período: “ + DateToString(gdDataInicio, “DD/MM/YYYY”) + “ a “ + DateToString(gdDataFim, “DD/MM/YYYY”)

// ==============================================
// AFTER PRINTING REPORT
// ==============================================
// IMPORTANTE: Liberar a memória da consulta
HFreeQuery(dsMovimentoEstoque)

// Resetar variáveis globais
nTotalEntradas = 0
nTotalSaidas = 0
nTotalGeral = 0

// Log de finalização
Info(“Relatório gerado com sucesso!”)

// ==============================================
// PROCEDIMENTOS AUXILIARES
// ==============================================

// Procedimento para validar dados antes da impressão
PROCEDURE ValidarDadosRelatorio()

// Verificar se as datas são válidas
IF gdDataInicio = InvalidDate OR gdDataFim = InvalidDate THEN
Error(“Datas inválidas selecionadas!”)
RETURN False
END

// Verificar se a data inicial não é maior que a final
IF gdDataInicio > gdDataFim THEN
Error(“A data inicial não pode ser maior que a data final!”)
RETURN False
END

// Verificar conexão com banco
IF NOT HConnectionStatusPostgres(MyConnection_Postgres) THEN
Error(“Erro de conexão com o banco de dados!”)
RETURN False
END

RETURN True

// Procedimento para configurar parâmetros do relatório
PROCEDURE ConfigurarParametrosRelatorio(LOCAL dDataIni is Date, LOCAL dDataFim is Date, LOCAL sUsuario is string)

gdDataInicio = dDataIni
gdDataFim = dDataFim
gsNomeUsuario = sUsuario

// ==============================================
// EXEMPLO DE CHAMADA DO RELATÓRIO
// ==============================================
/*
// No código que chama o relatório:

// Configurar parâmetros
ConfigurarParametrosRelatorio(EDT_DATA_INICIO, EDT_DATA_FIM, gsUsuarioLogado)

// Validar dados
IF NOT ValidarDadosRelatorio() THEN
RETURN
END

// Imprimir relatório
iDestination(iViewer)
iPrintReport(RPT_MOVIMENTACAO_ESTOQUE)
*/



# Manual Completo: Relatórios Programados no WX (Windev/Webdev/Windev Mobile)

## Introdução

Este manual aborda a técnica de criação de relatórios totalmente programados usando WLanguage, onde o preenchimento dos dados é controlado inteiramente via código, proporcionando máxima flexibilidade e controle sobre a geração de relatórios.

## 1. Configuração Inicial do Relatório

### 1.1 Criação do Relatório

1. Crie um novo relatório no editor
1. **IMPORTANTE**: Configure a fonte de dados como “**Preenchimento por programação**”
1. Adicione os controles necessários (campos de texto, imagens, etc.)
1. Nomeie os controles de forma clara (ex: STC_NOME, STC_VALOR, etc.)

### 1.2 Estrutura Base do Código

```wlanguage
// Variáveis globais
dsMinhaConsulta is Data Source
sSQL is string
bPrimeiroRegistro is boolean = True

// Configuração da consulta SQL
sSQL = [
SELECT
campo1,
campo2,
campo3
FROM
minha_tabela
WHERE
condicao = 'valor'
ORDER BY
campo1
]
```

## 2. Implementação dos Blocos de Código

### 2.1 Bloco BEFORE PRINTING REPORT

```wlanguage
// Executar a consulta SQL
IF NOT HExecuteSQLQuery(dsMinhaConsulta, MyConnection, hQueryWithoutCorrection, sSQL) THEN
Info(HErrorInfo(hErrMessage))
gsERRO is string = HErrorInfo(hErrFullDetails)
RETURN False
ELSE
HReadFirst(dsMinhaConsulta)
END
```

### 2.2 Bloco BEFORE PRINTING BODY (Mais Importante)

```wlanguage
// Verificar se há dados para processar
IF HOut(dsMinhaConsulta) THEN
RESULT NOT_HOut(dsMinhaConsulta)
END

// Preencher os campos do relatório com dados do Data Source
STC_CAMPO1 = dsMinhaConsulta.campo1
STC_CAMPO2 = dsMinhaConsulta.campo2
STC_CAMPO3 = dsMinhaConsulta.campo3

// Tratamento de valores nulos
IF dsMinhaConsulta.valor = Null THEN
STC_VALOR = "0,00"
ELSE
STC_VALOR = NumToString(dsMinhaConsulta.valor, "99999.99")
END

// Cálculos e formatações específicas
IF dsMinhaConsulta.tipo = "ENTRADA" THEN
STC_TIPO.Color = DarkGreen
ELSE
STC_TIPO.Color = DarkRed
END
```

### 2.3 Bloco AFTER PRINTING BODY

```wlanguage
// Avançar para o próximo registro
HReadNext(dsMinhaConsulta)
```

### 2.4 Bloco AFTER PRINTING REPORT

```wlanguage
// Liberar a memória da consulta
HFreeQuery(dsMinhaConsulta)
```

## 3. Exemplo Completo: Relatório de Movimentação de Estoque

### 3.1 Código Completo do Relatório​​​​​​​​​​​​​​​​

## 4. Técnicas Avançadas

### 4.1 Tratamento de Erros

```wlanguage
// Verificação de erros na execução da consulta
IF NOT HExecuteSQLQuery(dsConsulta, MinhaConexao, hQueryWithoutCorrection, sSQL) THEN
sErro is string = HErrorInfo(hErrFullDetails)

// Log do erro
Trace("Erro SQL: " + sErro)

// Notificar usuário
Error("Erro ao gerar relatório. Contate o suporte.")

RETURN False
END
```

### 4.2 Otimização de Performance

```wlanguage
// Usar índices nas consultas
sSQL = [
SELECT /*+ INDEX(tabela, indice_data) */
campos
FROM tabela
WHERE data_campo BETWEEN %1 AND %2
]

// Limitar resultados se necessário
sSQL += " LIMIT 1000"
```

### 4.3 Formatação Condicional Avançada

```wlanguage
// No BEFORE PRINTING BODY
SWITCH dsConsulta.status
CASE "ATIVO"
STC_STATUS.BackgroundColor = LightGreen
STC_STATUS.Font.Bold = True

CASE "INATIVO"
STC_STATUS.BackgroundColor = LightRed
STC_STATUS.Font.Strikethrough = True

CASE "PENDENTE"
STC_STATUS.BackgroundColor = LightYellow
STC_STATUS.Font.Italic = True
END
```

## 5. Boas Práticas

### 5.1 Nomenclatura

- Use prefixos claros: `STC_` para campos de texto, `IMG_` para imagens
- Nomeie Data Sources com prefixo `ds`: `dsRelatorioVendas`
- Use nomes descritivos: `dsMovimentacaoEstoque` ao invés de `ds1`

### 5.2 Gerenciamento de Memória

```wlanguage
// SEMPRE usar HFreeQuery ao final
HFreeQuery(dsConsulta)

// Para múltiplas consultas
HFreeQuery(dsConsulta1)
HFreeQuery(dsConsulta2)
HFreeQuery(dsConsulta3)
```

### 5.3 Validações

```wlanguage
// Validar dados antes de processar
IF HOut(dsConsulta) THEN
Info("Nenhum registro encontrado.")
RETURN False
END

// Validar campos críticos
IF dsConsulta.valor = Null OR dsConsulta.valor < 0 THEN
STC_VALOR = "R$ 0,00"
STC_VALOR.Color = Red
END
```

## 6. Troubleshooting

### 6.1 Problemas Comuns

**Problema**: Relatório não exibe dados
**Solução**: Verificar se `HReadFirst()` foi chamado no `BEFORE PRINTING REPORT`

**Problema**: Erro de memória
**Solução**: Sempre usar `HFreeQuery()` no `AFTER PRINTING REPORT`

**Problema**: Campos não preenchidos
**Solução**: Verificar se o preenchimento está no `BEFORE PRINTING BODY`

**Problema**: Loop infinito
**Solução**: Certificar que `HReadNext()` está no `AFTER PRINTING BODY`

### 6.2 Debug

```wlanguage
// Adicionar traces para debug
Trace("Registro atual: " + dsConsulta.id)
Trace("Valor: " + dsConsulta.valor)

// Verificar posição do cursor
Trace("HOut: " + HOut(dsConsulta))
Trace("Número do registro: " + HRecNum(dsConsulta))
```

## 7. Conclusão

A técnica de relatórios programados oferece controle total sobre a apresentação dos dados, permitindo:

- **Flexibilidade máxima** na formatação
- **Cálculos complexos** em tempo real
- **Formatação condicional** avançada
- **Controle total** sobre a fonte de dados
- **Performance otimizada** com consultas específicas

**Lembre-se sempre**: Configure como “Preenchimento por programação”, use `HExecuteSQLQuery` no início, preencha os campos no `BEFORE PRINTING BODY`, avance com `HReadNext` no `AFTER PRINTING BODY`, e finalize com `HFreeQuery` no `AFTER PRINTING REPORT`.

Esta técnica é essencial para relatórios corporativos complexos e deve fazer parte do arsenal de todo desenvolvedor WX.​​​​​​​​​​​​​​​​

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Miembro registrado
4.520 mensajes
Publicado el 02,julio 2025 - 15:29
Manual Avançado: Técnicas Especiais de Relatórios Programados WX
1. Relatório com Gráficos Dinâmicos Integrados
1.1 Relatório de Dashboard Executivo com Múltiplos Gráficos​​​​​​​​​​​​​​​​

// ==============================================
// RELATÓRIO: RPT_DASHBOARD_EXECUTIVO
// TIPO: Dashboard com múltiplos gráficos programados
// CONTROLES: CHART_VENDAS_MES, CHART_PRODUTOS_TOP, CHART_REGIOES, TABLE_RESUMO
// ==============================================

// ==============================================
// DECLARAÇÕES GLOBAIS
// ==============================================
dsVendasMes is Data Source
dsProdutosTop is Data Source
dsRegioes is Data Source
dsResumoGeral is Data Source

nTotalVendas is numeric = 0
nMetaMensal is numeric = 500000
arrCoresGrafico is array of 12 strings = [”#FF6B6B”, “#4ECDC4”, “#45B7D1”, “#96CEB4”,
“#FFEAA7”, “#DDA0DD”, “#98D8C8”, “#F06292”, “#AED581”, “#FFB74D”, “#A1887F”, “#90A4AE”]

// ==============================================
// BEFORE PRINTING REPORT
// ==============================================

// ===== CONSULTA 1: Vendas por Mês =====
sSQL_VendasMes is string = [
SELECT
EXTRACT(MONTH FROM data_venda) as mes,
EXTRACT(YEAR FROM data_venda) as ano,
SUM(valor_total) as total_vendas,
COUNT(*) as qtd_vendas,
AVG(valor_total) as ticket_medio
FROM vendas
WHERE data_venda >= CURRENT_DATE - INTERVAL ‘12 months’
AND status_venda = ‘FINALIZADA’
GROUP BY EXTRACT(YEAR FROM data_venda), EXTRACT(MONTH FROM data_venda)
ORDER BY ano DESC, mes DESC
LIMIT 12
]

IF NOT HExecuteSQLQuery(dsVendasMes, MyConnection, hQueryWithoutCorrection, sSQL_VendasMes) THEN
Error(“Erro ao carregar dados de vendas por mês: “ + HErrorInfo(hErrMessage))
RETURN False
END

// ===== CONSULTA 2: Top 10 Produtos =====
sSQL_ProdutosTop is string = [
SELECT
p.nome_produto,
p.codigo_produto,
SUM(iv.quantidade) as qtd_vendida,
SUM(iv.valor_total) as valor_total,
AVG(iv.valor_unitario) as preco_medio,
COUNT(DISTINCT v.id_venda) as num_vendas
FROM produtos p
INNER JOIN itens_venda iv ON p.id_produto = iv.id_produto
INNER JOIN vendas v ON iv.id_venda = v.id_venda
WHERE v.data_venda >= CURRENT_DATE - INTERVAL ‘3 months’
AND v.status_venda = ‘FINALIZADA’
GROUP BY p.id_produto, p.nome_produto, p.codigo_produto
ORDER BY valor_total DESC
LIMIT 10
]

IF NOT HExecuteSQLQuery(dsProdutosTop, MyConnection, hQueryWithoutCorrection, sSQL_ProdutosTop) THEN
Error(“Erro ao carregar top produtos: “ + HErrorInfo(hErrMessage))
RETURN False
END

// ===== CONSULTA 3: Vendas por Região =====
sSQL_Regioes is string = [
SELECT
r.nome_regiao,
r.sigla_regiao,
COUNT(v.id_venda) as qtd_vendas,
SUM(v.valor_total) as total_vendas,
AVG(v.valor_total) as ticket_medio,
COUNT(DISTINCT c.id_cliente) as clientes_ativos
FROM regioes r
INNER JOIN clientes c ON r.id_regiao = c.id_regiao
INNER JOIN vendas v ON c.id_cliente = v.id_cliente
WHERE v.data_venda >= CURRENT_DATE - INTERVAL ‘1 month’
AND v.status_venda = ‘FINALIZADA’
GROUP BY r.id_regiao, r.nome_regiao, r.sigla_regiao
ORDER BY total_vendas DESC
]

IF NOT HExecuteSQLQuery(dsRegioes, MyConnection, hQueryWithoutCorrection, sSQL_Regioes) THEN
Error(“Erro ao carregar dados por região: “ + HErrorInfo(hErrMessage))
RETURN False
END

// ===== CONSULTA 4: Resumo Geral =====
sSQL_Resumo is string = [
SELECT
‘VENDAS_HOJE’ as indicador,
COALESCE(SUM(valor_total), 0) as valor,
COUNT(*) as quantidade
FROM vendas
WHERE DATE(data_venda) = CURRENT_DATE
AND status_venda = ‘FINALIZADA’
UNION ALL
SELECT
‘VENDAS_MES’ as indicador,
COALESCE(SUM(valor_total), 0) as valor,
COUNT(*) as quantidade
FROM vendas
WHERE EXTRACT(MONTH FROM data_venda) = EXTRACT(MONTH FROM CURRENT_DATE)
AND EXTRACT(YEAR FROM data_venda) = EXTRACT(YEAR FROM CURRENT_DATE)
AND status_venda = ‘FINALIZADA’
UNION ALL
SELECT
‘CLIENTES_NOVOS’ as indicador,
COUNT(*) as valor,
0 as quantidade
FROM clientes
WHERE DATE(data_cadastro) >= CURRENT_DATE - INTERVAL ‘30 days’
UNION ALL
SELECT
‘PRODUTOS_ESTOQUE_BAIXO’ as indicador,
COUNT(*) as valor,
0 as quantidade
FROM produtos
WHERE estoque_atual <= estoque_minimo
]

IF NOT HExecuteSQLQuery(dsResumoGeral, MyConnection, hQueryWithoutCorrection, sSQL_Resumo) THEN
Error(“Erro ao carregar resumo geral: “ + HErrorInfo(hErrMessage))
RETURN False
END

// ==============================================
// BEFORE PRINTING HEADER
// ==============================================

// Configurar título dinâmico com período
STC_TITULO = “Dashboard Executivo - “ + DateToString(DateSys(), “MMMM YYYY”)
STC_PERIODO = “Período de Análise: “ + DateToString(DateSys() - 90, “DD/MM/YYYY”) + “ a “ + DateToString(DateSys(), “DD/MM/YYYY”)
STC_DATA_GERACAO = “Gerado em: “ + DateTimeToString(Now(), “DD/MM/YYYY às HH:MM”)

// ==============================================
// BEFORE PRINTING BODY
// ==============================================

// ===== CONFIGURAR GRÁFICO DE VENDAS POR MÊS =====
// Limpar gráfico
grDeleteAll(CHART_VENDAS_MES)

// Configurar propriedades do gráfico
grType(CHART_VENDAS_MES, grColumn3D)
grTitle(CHART_VENDAS_MES, “Evolução de Vendas - Últimos 12 Meses”)
grAxisTitle(CHART_VENDAS_MES, grXCoordinate, “Meses”)
grAxisTitle(CHART_VENDAS_MES, grYCoordinate, “Valor em R$”)
grColor(CHART_VENDAS_MES, 1, RGB(70, 130, 180))
grGradient(CHART_VENDAS_MES, 1, RGB(135, 206, 250), RGB(70, 130, 180), 45)

// Adicionar dados do gráfico
HReadFirst(dsVendasMes)
nContadorMes is int = 1
WHILE NOT HOut(dsVendasMes)
sMesAno is string = Right(“0” + dsVendasMes.mes, 2) + “/” + Right(dsVendasMes.ano, 2)
grAddData(CHART_VENDAS_MES, 1, dsVendasMes.total_vendas)
grCategoryLabel(CHART_VENDAS_MES, nContadorMes, sMesAno)

```
// Adicionar meta como linha
grAddData(CHART_VENDAS_MES, 2, nMetaMensal)

nTotalVendas += dsVendasMes.total_vendas
nContadorMes++
HReadNext(dsVendasMes)
```

END

// Configurar segunda série (meta)
grSeriesLabel(CHART_VENDAS_MES, 1, “Vendas Realizadas”)
grSeriesLabel(CHART_VENDAS_MES, 2, “Meta Mensal”)
grSeriesType(CHART_VENDAS_MES, 2, grLine)
grColor(CHART_VENDAS_MES, 2, RGB(255, 99, 71))

// ===== CONFIGURAR GRÁFICO DE TOP PRODUTOS =====
grDeleteAll(CHART_PRODUTOS_TOP)
grType(CHART_PRODUTOS_TOP, grPie)
grTitle(CHART_PRODUTOS_TOP, “Top 10 Produtos por Faturamento”)
grLegend(CHART_PRODUTOS_TOP, grAtBottom)

HReadFirst(dsProdutosTop)
nContadorProd is int = 1
WHILE NOT HOut(dsProdutosTop) AND nContadorProd <= 10
sNomeProduto is string = Left(dsProdutosTop.nome_produto, 20)
nValorProduto is numeric = dsProdutosTop.valor_total

```
grAddData(CHART_PRODUTOS_TOP, 1, nValorProduto)
grCategoryLabel(CHART_PRODUTOS_TOP, nContadorProd, sNomeProduto)

// Usar cores do array
IF nContadorProd <= Dimension(arrCoresGrafico) THEN
grColor(CHART_PRODUTOS_TOP, nContadorProd, HTML(arrCoresGrafico[nContadorProd]))
END

nContadorProd++
HReadNext(dsProdutosTop)
```

END

// ===== CONFIGURAR GRÁFICO DE REGIÕES =====
grDeleteAll(CHART_REGIOES)
grType(CHART_REGIOES, grBarHorizontal)
grTitle(CHART_REGIOES, “Vendas por Região - Último Mês”)
grAxisTitle(CHART_REGIOES, grXCoordinate, “Valor em R$”)
grAxisTitle(CHART_REGIOES, grYCoordinate, “Regiões”)

HReadFirst(dsRegioes)
nContadorReg is int = 1
WHILE NOT HOut(dsRegioes)
grAddData(CHART_REGIOES, 1, dsRegioes.total_vendas)
grCategoryLabel(CHART_REGIOES, nContadorReg, dsRegioes.nome_regiao)

```
// Gradiente baseado no valor
IF dsRegioes.total_vendas > 100000 THEN
grColor(CHART_REGIOES, nContadorReg, RGB(34, 139, 34)) // Verde forte
ELSE IF dsRegioes.total_vendas > 50000 THEN
grColor(CHART_REGIOES, nContadorReg, RGB(255, 165, 0)) // Laranja
ELSE
grColor(CHART_REGIOES, nContadorReg, RGB(220, 20, 60)) // Vermelho
END

nContadorReg++
HReadNext(dsRegioes)
```

END

// ===== PREENCHER TABELA DE RESUMO =====
TableDeleteAll(TABLE_RESUMO)

HReadFirst(dsResumoGeral)
WHILE NOT HOut(dsResumoGeral)
nLinha is int = TableAddLine(TABLE_RESUMO)

```
SWITCH dsResumoGeral.indicador
CASE "VENDAS_HOJE"
TABLE_RESUMO[nLinha][1] = "Vendas Hoje"
TABLE_RESUMO[nLinha][2] = NumToString(dsResumoGeral.valor, "R$ 999,999.99")
TABLE_RESUMO[nLinha][3] = dsResumoGeral.quantidade + " vendas"

// Colorir baseado na performance
IF dsResumoGeral.valor > 10000 THEN
TABLE_RESUMO[nLinha].BackgroundColor = LightGreen
ELSE IF dsResumoGeral.valor > 5000 THEN
TABLE_RESUMO[nLinha].BackgroundColor = LightYellow
ELSE
TABLE_RESUMO[nLinha].BackgroundColor = LightRed
END

CASE "VENDAS_MES"
TABLE_RESUMO[nLinha][1] = "Vendas do Mês"
TABLE_RESUMO[nLinha][2] = NumToString(dsResumoGeral.valor, "R$ 999,999.99")
TABLE_RESUMO[nLinha][3] = dsResumoGeral.quantidade + " vendas"

// Comparar com meta
nPercentualMeta is numeric = (dsResumoGeral.valor / nMetaMensal) * 100
TABLE_RESUMO[nLinha][4] = NumToString(nPercentualMeta, "999.9") + "% da meta"

CASE "CLIENTES_NOVOS"
TABLE_RESUMO[nLinha][1] = "Novos Clientes (30d)"
TABLE_RESUMO[nLinha][2] = dsResumoGeral.valor + " clientes"
TABLE_RESUMO[nLinha][3] = "Crescimento da base"

CASE "PRODUTOS_ESTOQUE_BAIXO"
TABLE_RESUMO[nLinha][1] = "Estoque Baixo"
TABLE_RESUMO[nLinha][2] = dsResumoGeral.valor + " produtos"
TABLE_RESUMO[nLinha][3] = "Requer atenção"

IF dsResumoGeral.valor > 50 THEN
TABLE_RESUMO[nLinha].Color = Red
TABLE_RESUMO[nLinha].Font.Bold = True
END
END

HReadNext(dsResumoGeral)
```

END

// ===== INDICADORES VISUAIS (CARDS) =====
// Card de Performance Geral
nPerformanceGeral is numeric = (nTotalVendas / (nMetaMensal * 12)) * 100

IF nPerformanceGeral >= 100 THEN
STC_PERFORMANCE.BackgroundColor = RGB(76, 175, 80) // Verde
STC_PERFORMANCE.Color = White
STC_PERFORMANCE_ICON = “✓”
ELSE IF nPerformanceGeral >= 80 THEN
STC_PERFORMANCE.BackgroundColor = RGB(255, 193, 7) // Amarelo
STC_PERFORMANCE.Color = Black
STC_PERFORMANCE_ICON = “⚠”
ELSE
STC_PERFORMANCE.BackgroundColor = RGB(244, 67, 54) // Vermelho
STC_PERFORMANCE.Color = White
STC_PERFORMANCE_ICON = “✗”
END

STC_PERFORMANCE = NumToString(nPerformanceGeral, “999.9”) + “%”

// Redimensionar gráficos dinamicamente
grSaveBMP(CHART_VENDAS_MES, CompleteDir(fDataDir()) + “temp_vendas_mes.bmp”)
grSaveBMP(CHART_PRODUTOS_TOP, CompleteDir(fDataDir()) + “temp_produtos_top.bmp”)
grSaveBMP(CHART_REGIOES, CompleteDir(fDataDir()) + “temp_regioes.bmp”)

// ==============================================
// AFTER PRINTING BODY
// ==============================================
// Não há loop de registros neste relatório, é um relatório único

// ==============================================
// BEFORE PRINTING FOOTER
// ==============================================

// Configurar informações de rodapé
STC_TOTAL_VENDAS_FOOTER = “Total Geral: “ + NumToString(nTotalVendas, “R$ 999,999,999.99”)
STC_META_FOOTER = “Meta Anual: “ + NumToString(nMetaMensal * 12, “R$ 999,999,999.99”)
STC_USUARIO_FOOTER = “Gerado por: “ + gsUsuarioLogado
STC_SISTEMA_FOOTER = “Sistema: “ + gsNomeSistema + “ v” + gsVersaoSistema

// ==============================================
// AFTER PRINTING REPORT
// ==============================================

// Liberar todas as consultas
HFreeQuery(dsVendasMes)
HFreeQuery(dsProdutosTop)
HFreeQuery(dsRegioes)
HFreeQuery(dsResumoGeral)

// Limpar arquivos temporários
fDelete(CompleteDir(fDataDir()) + “temp_vendas_mes.bmp”)
fDelete(CompleteDir(fDataDir()) + “temp_produtos_top.bmp”)
fDelete(CompleteDir(fDataDir()) + “temp_regioes.bmp”)

// Resetar variáveis
nTotalVendas = 0

Trace(“Dashboard executivo gerado com sucesso!”)

// ==============================================
// PROCEDIMENTOS AUXILIARES
// ==============================================

// Procedimento para configurar cores dinâmicas do gráfico
PROCEDURE ConfigurarCoresGrafico(LOCAL nomeGrafico is string, LOCAL nNumSeries is int)

arrCoresPadrao is array of strings = [
“#FF6B6B”, “#4ECDC4”, “#45B7D1”, “#96CEB4”, “#FFEAA7”,
“#DDA0DD”, “#98D8C8”, “#F06292”, “#AED581”, “#FFB74D”
]

FOR i = 1 TO nNumSeries
IF i <= Dimension(arrCoresPadrao) THEN
grColor(nomeGrafico, i, HTML(arrCoresPadrao[i]))
END
END

// Procedimento para adicionar efeitos 3D nos gráficos
PROCEDURE ConfigurarEfeitos3D(LOCAL nomeGrafico is string)

grParameter(nomeGrafico, grElevation, 30)
grParameter(nomeGrafico, grRotation, 45)
grParameter(nomeGrafico, grPerspective, 50)
grSmoothing(nomeGrafico, True)

// ==============================================
// EXEMPLO DE CHAMADA
// ==============================================
/*
// Configurar parâmetros globais
gsUsuarioLogado = “João Silva”
gsNomeSistema = “ERP Corporativo”
gsVersaoSistema = “2.1.5”

// Configurar destino
iDestination(iViewer)

// Gerar o relatório
iPrintReport(RPT_DASHBOARD_EXECUTIVO)
*/

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Miembro registrado
4.520 mensajes
Publicado el 02,julio 2025 - 15:30
2. Sistema de Etiquetas Inteligentes
2.1 Gerador de Etiquetas de Produtos com Códigos de Barras​​​​​​​​​​​​​​​​

// ==============================================
// RELATÓRIO: RPT_ETIQUETAS_PRODUTOS
// TIPO: Etiquetas com código de barras e QR Code
// FORMATO: 4 etiquetas por linha, papel A4
// CONTROLES: IMG_BARCODE, IMG_QRCODE, STC_PRODUTO, STC_PRECO, etc.
// ==============================================

// ==============================================
// DECLARAÇÕES GLOBAIS
// ==============================================
dsEtiquetas is Data Source
nEtiquetaAtual is int = 0
nEtiquetasPorLinha is int = 4
nLinhasProcessadas is int = 0
arrProdutosSelecionados is array of strings
nQuantidadePorProduto is associative array of int // ID_PRODUTO -> QUANTIDADE

// Configurações de formatação
REAL_WIDTH is real = 50 // mm
REAL_HEIGHT is real = 25 // mm

// ==============================================
// BEFORE PRINTING REPORT
// ==============================================

// Construir lista de produtos baseada na seleção
sListaProdutos is string = “”
FOR EACH sProdutoID OF arrProdutosSelecionados
IF sListaProdutos <> “” THEN sListaProdutos += “,”
sListaProdutos += “’” + sProdutoID + “’”
END

IF sListaProdutos = “” THEN
Error(“Nenhum produto selecionado para gerar etiquetas!”)
RETURN False
END

// ===== CONSULTA PRINCIPAL =====
sSQL is string = [
SELECT
p.id_produto,
p.codigo_barras,
p.codigo_interno,
p.nome_produto,
p.nome_resumido,
p.marca,
p.categoria,
p.unidade_medida,
p.peso_liquido,
p.peso_bruto,
p.data_validade,
p.lote_fabricacao,
COALESCE(pv.preco_venda, 0) as preco_venda,
COALESCE(pv.preco_promocional, 0) as preco_promocional,
CASE
WHEN pv.preco_promocional > 0 AND pv.preco_promocional < pv.preco_venda
THEN pv.preco_promocional
ELSE pv.preco_venda
END as preco_final,
CASE
WHEN pv.preco_promocional > 0 AND pv.preco_promocional < pv.preco_venda
THEN ‘S’
ELSE ‘N’
END as em_promocao,
f.nome_fornecedor,
f.cnpj_fornecedor,
p.origem_produto,
p.ncm_produto,
p.observacoes
FROM produtos p
LEFT JOIN precos_venda pv ON p.id_produto = pv.id_produto
AND pv.data_inicio <= CURRENT_DATE
AND (pv.data_fim IS NULL OR pv.data_fim >= CURRENT_DATE)
LEFT JOIN fornecedores f ON p.id_fornecedor_principal = f.id_fornecedor
WHERE p.id_produto IN (%1)
AND p.ativo = ‘S’
AND p.gerar_etiqueta = ‘S’
ORDER BY p.nome_produto
]

sSQL = StringBuild(sSQL, sListaProdutos)

IF NOT HExecuteSQLQuery(dsEtiquetas, MyConnection, hQueryWithoutCorrection, sSQL) THEN
Error(“Erro ao carregar dados dos produtos: “ + HErrorInfo(hErrMessage))
gsErroDetalhado is string = HErrorInfo(hErrFullDetails)
Trace(“Erro SQL detalhado: “ + gsErroDetalhado)
RETURN False
END

// Verificar se encontrou produtos
HReadFirst(dsEtiquetas)
IF HOut(dsEtiquetas) THEN
Info(“Nenhum produto válido encontrado para gerar etiquetas!”)
RETURN False
END

// Expandir dataset baseado na quantidade de cada produto
dsEtiquetasExpandido is Data Source
CriarDataSourceExpandido()

// ==============================================
// BEFORE PRINTING BODY
// ==============================================

// Verificar se há mais etiquetas para processar
IF HOut(dsEtiquetasExpandido) THEN
RESULT NOT_HOut(dsEtiquetasExpandido)
END

// Determinar posição da etiqueta na página (1-4)
nPosicaoEtiqueta is int = (nEtiquetaAtual MOD nEtiquetasPorLinha) + 1

// ===== CONFIGURAR CONTROLES DA ETIQUETA =====

// Determinar qual set de controles usar baseado na posição
sPrefixoControle is string = “ETQ” + nPosicaoEtiqueta + “_”

// Nome do produto (truncado se necessário)
sNomeProduto is string = dsEtiquetasExpandido.nome_produto
IF Length(sNomeProduto) > 35 THEN
sNomeProduto = Left(dsEtiquetasExpandido.nome_resumido, 32) + “…”
END

{sPrefixoControle + “STC_NOME”} = Upper(sNomeProduto)

// Código do produto
{sPrefixoControle + “STC_CODIGO”} = dsEtiquetasExpandido.codigo_interno

// Marca e categoria
{sPrefixoControle + “STC_MARCA”} = dsEtiquetasExpandido.marca
{sPrefixoControle + “STC_CATEGORIA”} = dsEtiquetasExpandido.categoria

// ===== FORMATAÇÃO DE PREÇOS =====
nPrecoFinal is numeric = dsEtiquetasExpandido.preco_final
nPrecoVenda is numeric = dsEtiquetasExpandido.preco_venda

IF dsEtiquetasExpandido.em_promocao = “S” THEN
// Produto em promoção
{sPrefixoControle + “STC_PRECO_NORMAL”} = “De: R$ “ + NumToString(nPrecoVenda, “999.99”)
{sPrefixoControle + “STC_PRECO_NORMAL”}.Font.Strikethrough = True
{sPrefixoControle + “STC_PRECO_NORMAL”}.Color = RGB(128, 128, 128)

```
{sPrefixoControle + "STC_PRECO_PROMOCIONAL"} = "R$ " + NumToString(nPrecoFinal, "999.99")
{sPrefixoControle + "STC_PRECO_PROMOCIONAL"}.Color = RGB(255, 0, 0)
{sPrefixoControle + "STC_PRECO_PROMOCIONAL"}.Font.Bold = True
{sPrefixoControle + "STC_PRECO_PROMOCIONAL"}.Font.Size = 14

// Badge de promoção
{sPrefixoControle + "STC_PROMOCAO"} = "OFERTA"
{sPrefixoControle + "STC_PROMOCAO"}.BackgroundColor = RGB(255, 0, 0)
{sPrefixoControle + "STC_PROMOCAO"}.Color = White
{sPrefixoControle + "STC_PROMOCAO"}.Visible = True

// Calcular desconto
nDesconto is numeric = ((nPrecoVenda - nPrecoFinal) / nPrecoVenda) * 100
{sPrefixoControle + "STC_DESCONTO"} = NumToString(nDesconto, "99") + "% OFF"
```

ELSE
// Produto com preço normal
{sPrefixoControle + “STC_PRECO_NORMAL”} = “R$ “ + NumToString(nPrecoFinal, “999.99”)
{sPrefixoControle + “STC_PRECO_NORMAL”}.Font.Strikethrough = False
{sPrefixoControle + “STC_PRECO_NORMAL”}.Color = Black
{sPrefixoControle + “STC_PRECO_NORMAL”}.Font.Bold = True
{sPrefixoControle + “STC_PRECO_NORMAL”}.Font.Size = 12

```
{sPrefixoControle + "STC_PRECO_PROMOCIONAL"}.Visible = False
{sPrefixoControle + "STC_PROMOCAO"}.Visible = False
{sPrefixoControle + "STC_DESCONTO"}.Visible = False
```

END

// ===== CÓDIGO DE BARRAS =====
IF dsEtiquetasExpandido.codigo_barras <> “” THEN
// Gerar código de barras usando a função nativa do WinDev
sCodigoBarras is string = dsEtiquetasExpandido.codigo_barras

```
// Configurar imagem do código de barras
{sPrefixoControle + "IMG_BARCODE"} = iBarCode(sCodigoBarras, BC_EAN13, bcCheckDigit + bcText)
{sPrefixoControle + "IMG_BARCODE"}.Width = 120
{sPrefixoControle + "IMG_BARCODE"}.Height = 40

// Texto do código de barras
{sPrefixoControle + "STC_CODIGO_BARRAS"} = sCodigoBarras
{sPrefixoControle + "STC_CODIGO_BARRAS"}.Font.Size = 8
```

ELSE
{sPrefixoControle + “IMG_BARCODE”}.Visible = False
{sPrefixoControle + “STC_CODIGO_BARRAS”}.Visible = False
END

// ===== QR CODE =====
// Criar dados para QR Code (JSON com informações do produto)
sQRData is string = [
{
“id”: “%1”,
“codigo”: “%2”,
“nome”: “%3”,
“preco”: %4,
“promocao”: “%5”,
“validade”: “%6”,
“lote”: “%7”,
“origem”: “%8”
}
]

sQRData = StringBuild(sQRData,
dsEtiquetasExpandido.id_produto,
dsEtiquetasExpandido.codigo_interno,
dsEtiquetasExpandido.nome_produto,
nPrecoFinal,
dsEtiquetasExpandido.em_promocao,
DateToString(dsEtiquetasExpandido.data_validade, “DD/MM/YYYY”),
dsEtiquetasExpandido.lote_fabricacao,
dsEtiquetasExpandido.origem_produto
)

// Gerar QR Code
{sPrefixoControle + “IMG_QRCODE”} = iQRCode(sQRData, qrLow)
{sPrefixoControle + “IMG_QRCODE”}.Width = 30
{sPrefixoControle + “IMG_QRCODE”}.Height = 30

// ===== INFORMAÇÕES ADICIONAIS =====

// Unidade de medida e peso
IF dsEtiquetasExpandido.peso_liquido > 0 THEN
{sPrefixoControle + “STC_PESO”} = NumToString(dsEtiquetasExpandido.peso_liquido, “999.999”) + “ “ + dsEtiquetasExpandido.unidade_medida
ELSE
{sPrefixoControle + “STC_PESO”} = dsEtiquetasExpandido.unidade_medida
END

// Data de validade (se aplicável)
IF dsEtiquetasExpandido.data_validade <> InvalidDate THEN
{sPrefixoControle + “STC_VALIDADE”} = “Val: “ + DateToString(dsEtiquetasExpandido.data_validade, “DD/MM/YY”)

```
// Alertar se próximo do vencimento
nDiasVencimento is int = dsEtiquetasExpandido.data_validade - DateSys()
IF nDiasVencimento <= 30 THEN
{sPrefixoControle + "STC_VALIDADE"}.Color = RGB(255, 0, 0)
{sPrefixoControle + "STC_VALIDADE"}.Font.Bold = True
END
```

ELSE
{sPrefixoControle + “STC_VALIDADE”}.Visible = False
END

// Lote de fabricação
IF dsEtiquetasExpandido.lote_fabricacao <> “” THEN
{sPrefixoControle + “STC_LOTE”} = “Lote: “ + dsEtiquetasExpandido.lote_fabricacao
ELSE
{sPrefixoControle + “STC_LOTE”}.Visible = False
END

// Origem do produto
IF dsEtiquetasExpandido.origem_produto <> “” THEN
{sPrefixoControle + “STC_ORIGEM”} = “Origem: “ + dsEtiquetasExpandido.origem_produto
{sPrefixoControle + “STC_ORIGEM”}.Font.Size = 7
END

// Data de impressão
{sPrefixoControle + “STC_DATA_IMPRESSAO”} = DateToString(DateSys(), “DD/MM/YY”)
{sPrefixoControle + “STC_DATA_IMPRESSAO”}.Font.Size = 6

// ===== CONTROLE DE VISIBILIDADE =====
// Mostrar apenas os controles da posição atual
FOR nPos = 1 TO nEtiquetasPorLinha
bVisible is boolean = (nPos = nPosicaoEtiqueta)

```
ConfigurarVisibilidadeEtiqueta(nPos, bVisible)
```

END

// Incrementar contador
nEtiquetaAtual++

// ===== QUEBRA DE PÁGINA =====
// Se completou uma linha de 4 etiquetas, preparar para próxima linha
IF nPosicaoEtiqueta = nEtiquetasPorLinha THEN
nLinhasProcessadas++

```
// Quebrar página a cada 10 linhas (40 etiquetas por página)
IF nLinhasProcessadas MOD 10 = 0 THEN
iPageEnd()
END
```

END

// ==============================================
// AFTER PRINTING BODY
// ==============================================

// Avançar para próxima etiqueta
HReadNext(dsEtiquetasExpandido)

// ==============================================
// AFTER PRINTING REPORT
// ==============================================

// Liberar recursos
HFreeQuery(dsEtiquetas)
HFreeQuery(dsEtiquetasExpandido)

// Limpar arrays
ArrayDeleteAll(arrProdutosSelecionados)
AssociativeArrayDeleteAll(nQuantidadePorProduto)

// Reset contadores
nEtiquetaAtual = 0
nLinhasProcessadas = 0

Info(“Etiquetas geradas com sucesso! Total: “ + nEtiquetaAtual)

// ==============================================
// PROCEDIMENTOS AUXILIARES
// ==============================================

// Criar dataset expandido baseado na quantidade
PROCEDURE CriarDataSourceExpandido()

// Primeiro, criar um array temporário com todos os registros expandidos
arrRegistrosExpandidos is array of structures
id_produto is string
quantidade_etiqueta is int
registro_original is buffer
END

HReadFirst(dsEtiquetas)
WHILE NOT HOut(dsEtiquetas)
nQtdEtiquetas is int = nQuantidadePorProduto[dsEtiquetas.id_produto]
IF nQtdEtiquetas <= 0 THEN nQtdEtiquetas = 1 // Mínimo 1 etiqueta

```
FOR i = 1 TO nQtdEtiquetas
stRegistro is ST_RegistroExpandido
stRegistro.id_produto = dsEtiquetas.id_produto
stRegistro.quantidade_etiqueta = i
stRegistro.registro_original = HRecordToString(dsEtiquetas)

Add(arrRegistrosExpandidos, stRegistro)
END

HReadNext(dsEtiquetas)
```

END

// Criar o datasource expandido
sCreateQuery is string = “CREATE TABLE temp_etiquetas_expandidas AS “ + HRecordToString(dsEtiquetas, “”, hRecordHeader)

// Inserir registros expandidos
FOR EACH stReg OF arrRegistrosExpandidos
HStringToRecord(dsEtiquetasExpandido, stReg.registro_original)
// Adicionar campo de sequência
dsEtiquetasExpandido.sequencia_etiqueta = stReg.quantidade_etiqueta
HAdd(dsEtiquetasExpandido)
END

HReadFirst(dsEtiquetasExpandido)

// Configurar visibilidade dos controles de uma etiqueta
PROCEDURE ConfigurarVisibilidadeEtiqueta(LOCAL nPosicao is int, LOCAL bVisivel is boolean)

sPrefixo is string = “ETQ” + nPosicao + “_”

// Lista de controles de cada etiqueta
arrControles is array of strings = [
“STC_NOME”, “STC_CODIGO”, “STC_MARCA”, “STC_CATEGORIA”,
“STC_PRECO_NORMAL”, “STC_PRECO_PROMOCIONAL”, “STC_PROMOCAO”, “STC_DESCONTO”,
“IMG_BARCODE”, “STC_CODIGO_BARRAS”, “IMG_QRCODE”,
“STC_PESO”, “STC_VALIDADE”, “STC_LOTE”, “STC_ORIGEM”, “STC_DATA_IMPRESSAO”
]

FOR EACH sControle OF arrControles
{sPrefixo + sControle}.Visible = bVisivel
END

// Configurar dados para teste sem impressora
PROCEDURE ConfigurarDadosTeste()

// Adicionar produtos para teste
Add(arrProdutosSelecionados, “1”)
Add(arrProdutosSelecionados, “2”)
Add(arrProdutosSelecionados, “3”)

nQuantidadePorProduto[“1”] = 5
nQuantidadePorProduto[“2”] = 3
nQuantidadePorProduto[“3”] = 2

// ==============================================
// EXEMPLO DE CHAMADA
// ==============================================
/*
// Configurar produtos para impressão
ArrayDeleteAll(arrProdutosSelecionados)
AssociativeArrayDeleteAll(nQuantidadePorProduto)

// Adicionar produtos selecionados
Add(arrProdutosSelecionados, “123”) // ID do produto
Add(arrProdutosSelecionados, “456”)
Add(arrProdutosSelecionados, “789”)

// Definir quantidade de etiquetas por produto
nQuantidadePorProduto[“123”] = 10
nQuantidadePorProduto[“456”] = 5
nQuantidadePorProduto[“789”] = 3

// Configurar impressora para etiquetas
iParameter(iDestination, “ETIQUETAS_ZEBRA”)
iParameter(iPaperSize, iPaper_A4)
iParameter(iMargin, 5, 5, 5, 5)

// Imprimir
iPrintReport(RPT_ETIQUETAS_PRODUTOS)
*/

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Miembro registrado
4.520 mensajes
Publicado el 02,julio 2025 - 15:38
Vamos criar um exemplo totalmente diferente e completo de impressão ZPL para uma etiqueta de rastreamento logístico, com código 100% pronto para rodar no WinDev/WebDev/Windev Mobile (WX), seguindo os padrões de geração de relatório programado para impressoras Zebra.



📦 Cenário do Exemplo:

Rastreamento logístico de encomendas com etiqueta contendo:
• Nome do destinatário
• Endereço completo
• Código de rastreamento
• Peso e dimensões
• Código de barras (rastreamento)
• QR Code com JSON do pacote



✅ Estrutura Geral do Projeto

Relatório: RPT_ETIQUETA_LOGISTICA
Impressora: Zebra_GK420t
Modo: ZPL via iPrint()
Fonte de dados: Preenchimento por programação




🔧 Código Completo

// ==============================================
// RELATÓRIO PROGRAMADO - ETIQUETA LOGÍSTICA
// IMPRESSÃO ZPL - ZEBRA
// ==============================================

dsPacoteLogistico is Data Source
sImpressoraZebra is string = "Zebra_GK420t"
nLarguraZPL is int = 406
nAlturaZPL is int = 203

// ==============================================
// BEFORE PRINTING REPORT
// ==============================================

sSQL is string = [
SELECT
id_pacote,
nome_destinatario,
endereco,
cidade,
cep,
peso_kg,
dimensao_cm,
codigo_rastreamento
FROM pacote_logistico
WHERE id_pacote = %1
]

sSQL = StringBuild(sSQL, gnPacoteSelecionado)

IF NOT HExecuteSQLQuery(dsPacoteLogistico, MyConnection, hQueryWithoutCorrection, sSQL) THEN
Error("Erro ao carregar pacote: " + HErrorInfo(hErrMessage))
RETURN False
END

HReadFirst(dsPacoteLogistico)

IF HOut(dsPacoteLogistico) THEN
RETURN False
END

// Configurar impressora
IF NOT PrinterExist(sImpressoraZebra) THEN
Error("Impressora Zebra não encontrada.")
RETURN False
END

iParameter(iDestination, sImpressoraZebra)
iParameter(iMode, iModeText)

RETURN True




🧩 BEFORE PRINTING BODY

// Geração do ZPL logístico
sZPL is string = GerarZPLLogistico()
iPrint(sZPL)




🧯 AFTER PRINTING BODY

HReadNext(dsPacoteLogistico)




🧹 AFTER PRINTING REPORT

HFreeQuery(dsPacoteLogistico)
iParameter(iDestination, iViewer)




🧠 Função: GerarZPLLogistico

FUNCTION GerarZPLLogistico() : string

sNome is string = dsPacoteLogistico.nome_destinatario
sEndereco is string = dsPacoteLogistico.endereco + ", " + dsPacoteLogistico.cidade + " - CEP: " + dsPacoteLogistico.cep
sPeso is string = NumToString(dsPacoteLogistico.peso_kg, "###0.0") + " kg"
sDimensao is string = dsPacoteLogistico.dimensao_cm + " cm"
sCodigo is string = dsPacoteLogistico.codigo_rastreamento
sData is string = DateToString(DateSys(), "DD/MM/YYYY")

// JSON resumido para QR Code
sQRData is string = [
{"destinatario":"%1","codigo":"%2","peso":"%3"}
]
sQRData = StringBuild(sQRData, sNome, sCodigo, sPeso)

sZPL is string = [
^XA
^PW%1
^LL%2
^CF0,30
^FO20,20^FDDESTINATARIO:^FS
^CF0,25
^FO20,55^FD%3^FS
^CF0,20
^FO20,90^FD%4^FS
^FO20,115^FDPeso: %5 - Dim: %6^FS
^FO20,140^FDData: %7^FS

^FO20,170^BCN,80,Y,N,N
^FD%8^FS

^CF0,15
^FO20,260^FDRastreamento: %8^FS

^FO300,30^BQ,2,5
^FDQA,%9^FS
^XZ
]

sZPL = StringBuild(sZPL,
nLarguraZPL, // %1
nAlturaZPL, // %2
sNome, // %3
sEndereco, // %4
sPeso, // %5
sDimensao, // %6
sData, // %7
sCodigo, // %8
sQRData // %9
)

RETURN sZPL




🧪 Teste Simulado

// Definir o pacote que será impresso
gnPacoteSelecionado = 15

// Chamar o relatório
iPrintReport(RPT_ETIQUETA_LOGISTICA)




✅ Vantagens do Exemplo
• Suporta QRCode nativo ZPL (^BQ)
• Imprime código de barras rastreável (^BC)
• Mostra dados úteis ao operador logístico (peso, dimensões, CEP)
• Baseado em SQL dinâmico
• Adaptável para WebDev, WinDev Mobile e WinDev

Miembro registrado
4.520 mensajes
Publicado el 02,julio 2025 - 18:42
Complemento

https://forum.pcsoft.fr/fr-FR/pcsoft.br.windev/4714-exemplo-relatorio-impressora-zebra-4721/read.awp

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/