PEX / JEX / Expressões BPM
Documentação e tutoriais relacionado à PEX / JEX / Expressões BPM
- Rotinas / Funções Delphi
- Ceil
- Concat
- Copy
- CurrToStrF
- Date
- DayOfWeek
- EncodeDate
- FormatDateTime
- Lenght
- Max / Min
- Now
- SQRT
- Time
- Trim
- Trunc
- Eventos ao sair
- Expressão - Função "Include" em FK
- Expressões BPM
- Formatação no PEX (Telefone Fixo / Celular)
- Grade - Incluir e remover linhas via PEX
- JEX / JS Sandbox
- JEX - Percorrer e alterar resultado de uma grade
- PEX - Acessando outras bases via PEX
- PEX - Comunicação com WebService - SOAP - XML
- PEX - Criar arquivo (Excel, TXT)
- PEX - Filtrando dados por data
- PEX - Função AbreSite
- PEX - Função LerBase64 e transforma no arquivo fisico
- PEX - Grade com campo de auto incremento
- PEX - Mensagem e Abortar
- PEX - Preencher grade com informações de formulários
- PEX - Preencher grade com informações de um SELECT (varias linhas)
- PEX - Remove caracteres e mantem somente números
- PEX - SELECT
- PEX - Setando variável de sistema em formulário
- PEX - Usando coluna (Verdadeiro / Falso) da grade
- PEX - Utilizando campo adicional da FK
- PEX - Validação de campo somente leitura (obrigatório)
- PEX - Validar campo de data ao sair do formulário
- Validando campos ou dados via PEX
- Webservice - Consulta de CEP via JEX
- Webservice - Consulta de CEP via PEX
- Webservice - Consulta de CNPJ via PEX
- JEX - Função gerar planilha
Rotinas / Funções Delphi
Ceil
Arrendonda um determinado número para o inteiro que seja maior ou igual a ele. No exemplo ao lado, os valores 12.0 e 12.34 serão respectivamente arredondados para 12 e 13.
Exemplo:
var
numero: Double;
begin
numero := Ceil(12.0);
ShowMessage('Ceil(12.0) = {0}', numero.ToString);
numero := Ceil(12.34);
ShowMessage('Ceil(12.34)= {0}', numero.ToString);
end;
Concat
Essa função é utilizada para fazer a concatenação(junção) de várias Strings em uma só. No exemplo, o texto exibido será "Olá, mundo!". Pois o mesmo está sendo concatenado pela função Concat.
Exemplo:
var
result : string;
begin
result := Concat('Olá',' mundo','!');
ShowMessage(result);
end;
Copy
Copia parte do texto de uma variável para outra. Recebe 3 parâmetros, sendo eles, em ordem:
- Fonte de onde os dados serão copiados;
- Posição do Caracter de onde iniciará a cópia;
- Contador de quantos caracteres serão copiados;
No exemplo, é copiado o conteúdo da variável Source, iniciando pelo terceiro carácter (3).
Após contar 4 carácteres, irá terminar a cópia do conteúdo, no número 6.
Mostrará o texto: 3456
var
Source, Target : string;
begin
Source := '12345678';
Target := Copy(Source, 3, 4);
ShowMessage('Target : '+Target);
end;
CurrToStrF
Converte um valor Currency para uma String seguindo determinada formatação.
No exemplo, indicamos que a variável valor receberá o número 1234.567 e nas mensagens, mostraremos esse número com 4 dígitos após a vírgula, e logo em seguida com 2 dígitos após a vírgula.
Exemplo:
var
valor: Currency;
begin
valor:= 1234.567;
ShowMessage('Usando 4 dígitos = '+CurrToStrF(valor, ffCurrency, 4));
ShowMessage('Usando 2 dígitos = '+CurrToStrF(valor, ffCurrency, 2));
end;
O resultado será, respectivamente: 1234.5670; 1234.56
Date
Retorna a data atual do sistema. No exemplo, a descrição do componente Label1 assumirá a data atual do sistema assim que o Button1 receber um clique.
Exemplo:
procedure TForm1.Button1Click(Sender: TObject);
begin
Label1.Caption := 'Hoje é: '+ DateToStr(Date);
end;
DayOfWeek
Retorna o dia da semana correspondente a uma data.
No exemplo, se o dia da semana extraído da informação no Edit1 corresponder a 1, então mostrará a mensagem ‘Dia da semana: Domingo’, pois Domingo é o primeiro dia da semana.
Exemplo:
if (DayOfWeek(StrToDate(edit1.text))) = 1 then
Showmessage('Dia da semana: Domingo');
EncodeDate
Junta valores de ano, mês e dia formando uma data. No exemplo, estamos informando um valor às variáveis dia, mês e ano, e, a partir da função EncodeDate() iremos concatenar esses valores no formato de data, que será armazenada na variável data.
Exemplo:
procedure TForm2.Button1Click(Sender: TObject);
var
dia, mes, ano: Word; data: TDateTime;
begin
ano := 2015;
mes := 2;
dia := 15;
data := EncodeDate(ano, mes, dia);
lblData.Caption := 'Data: ' + DateToStr(data);
end;
FormatDateTime
Formata data e hora usando um formato especificado. Ao lado, estamos definindo que o formato de data que queremos que seja exibido seja em Data (dois dígitos) / Mês (dois dígitos) / Ano (dois dígitos).
Nesse exemplo, a mensagem que seria exibida ficaria parecida com a seguinte:
'dd/mm/yy = 06/11/17'.
Exemplo:
procedure TForm2.Button1Click(Sender: TObject);
var
dataAtual: TDateTime;
begin
ShowMessage('dd/mm/yy = '+ FormatDateTime('dd/mm/yy', dataAtual));
end;
Lenght
Retorna tanto a quantidade de caracteres de uma String, quanto o número de elementos em um array. É muito utilizado para estruturas de loop, percorrendo um conjunto enquanto houver dados no mesmo.
Desse modo, o primeiro texto a ser mostrado será: "Tamanho da shortStr = 8", pois essa é a quantidade de caracteres encontradas na variável.
Agora, o segundo texto será exibido divido em linhas, da seguinte maneira:
"Letra 1 = A
Letra 2 = B
Letra 3 = C (...)" até chegar no último registro encontrado, no caso, H.
Exemplo:
var
shortStr : String;
i : Integer;
begin
shortStr := 'ABCDEFGH';
ShowMessage('Tamanho da shortStr = '+IntToStr(Length(shortStr)));
//Utilização em loops
for i := 1 to Length(shortStr) do
ShowMessage('Letra: '+IntToStr(i)+' = '+shortStr[i]);
end;
Max / Min
Retorna o maior/menor valor entre dois números.
Exemplo:
begin
Label1.Caption := Max(1,2);
Label2.Caption := Min(1,2);
end;
Now
Retorna a data e hora correntes.
Exemplo:
begin
ShowMessage('Data e hora atuais: ',DateTimeToStr(Now));
end;
SQRT
Encontra a raiz quadrada de determinado número. No exemplo utilizado, estamos falando que a variável raizQuadrada recebe a raíz quadrada de 225 (numero), e, após isso, mostramos o seu valor em uma mensagem.
Exemplo:
var
numero, raizQuadrada: Extended;
begin
numero := 225;
raizQuadrada := Sqrt(numero);
ShowMessage('Raíz quadrada de ',number,' é: ', raizQuadrada);
end;
O resultado será: 15
Time
Retorna a hora corrente.
Exemplo:
begin
ShowMessage('Hora atual: ',TimeToStr(Time));
end;
Trim
A função Trim é utilizada para que sejam removidos espaços em branco de determinados textos. Existem duas variações da função Trim, sendo elas:
- TrimLeft, que remove os espaços à esquerda; e
- TrimRight, que remove os espaços à direita.
No exemplo, os espaços removidos seriam, consecutivamente:
[Teste ]
[ Teste]
[Teste]
Exemplo:
const
StringTeste = ' Teste ';
begin
ShowMessage('[' + TrimLeft(StringTeste) + ']');
ShowMessage('[' + TrimRight(StringTeste) + ']');
ShowMessage('[' + Trim(StringTeste) + ']');
end;
Trunc
Trunca um valor do tipo Real para um do tipo Inteiro, ignorando completamente as casas decimais.
Exemplo:
begin
ShowMessage('Trunc(12.75) = '+IntToStr(trunc(12.75)));
end;
O resultado será: 12.
Eventos ao sair
Com as melhorias implementadas nas versões mais recentes do sistema foi disponibilizado para os desenvolvedores DOX um assistente para estruturar os códigos e agilizar o trabalho.
Ordem da execução de eventos:
1 - Evento do sistema ao entrar na atividade
2 - Evento PEX/JEX ao entrar na atividade
3 - Evento PEX/JEX ao sair na atividade
4 - Evento do sistema ao sair na atividade
PEX
Acesse um evento ao Sair de um formulário ou atividade e escolha a opção PEX:
No canto esquerdo o sistema apresenta:
- Formulário: Aqui será listado todos os formulários criados na atividade atual. Além de trazer o código do formulário e seu tipo (FK, Texto longo, etc), o usuário tem a opção de ler ou alterar suas propriedades, tais como:
- Valor
- Descrição
- Observação
- Obrigatório
- Somente leitura
- Visível
- Agrupamento
- Variáveis do processo: Variáveis criadas no processo BPM de forma manual. O sistema mostra a descrição da variável, assim como seu tipo, com a mesma opção de ler ou alterar seu valor.
- Variáveis do sistema: Todas as variáveis do sistema são apresentadas para o usuário utilizar no código. Nesta opção só é permitido ler seu valor, não alterar. Clique aqui.
- Retorno: Aqui estão presente as opções de mensagem e de abortar operação. Para saber mais sobre este recurso, clique aqui.
- Operações matemáticas entre campos, bastando informar os campos que serão calculados e qual o tipo de operação matemática será realizada;
- Alterar foco, que mostrará uma mensagem na tela ao usuário, e logo em seguida enviará o foco da digitação para um campo definido de acordo com a necessidade.
Informações adicionais:
- Formulário" não fica visível nos eventos "Ao sair" de campos de grades;
- O agrupamento "Grade" só fica visível nos eventos "Ao sair" de campos de grades;
- Dados da FK" só fica visível nos eventos "Ao sair" de campos que estejam vinculados a uma consulta "FK"; Nos elementos do formulário do tipo "Grade" serão exibidos, além das colunas disponíveis, os totalizadores que tenham sido configurados em cada coluna, com o objetivo de ler seus valores atuais;
- Nos campos de resposta objetiva será exibida a propriedade "Resposta selecionada", que dá acesso às operações disponíveis para obter o valor das propriedades dessa resposta (código, descrição e valor de retorno).
Abaixo está disponibilizado um vídeo para explicar sobre o funcionamento dos novos recursos.
JEX
Para mais informações sobre JS JEX, clique aqui.
Basicamente, formulários, variáveis do processo, variáveis do sistema e mensagem tem as mesmas funções do que em programação PEX. A diferença está na sintaxe, na linha de código que o sistema insere na tela. PEX é linguagem pascal, o JEX é linguagem JavaScript:
As novas funções abaixo são referentes ao tipos de opções que o JEX pode utilizar no código.
- Acesso a banco de dados: Aqui é possível criar a estrutura para comandos ou consultas SQL, inclusive utilizando um conector.
- Acesso a WebServices (Axios): Funções prontas para se utilizar o WebService. Clique para saber mais.
- Acesso a arquivos e pastas: Essa opção permite criar arquivos, copiar, deletar, ler , criar diretório, mover arquivo para sua máquina local.
- Inserir log no processo: Essa opção permite ao JEX inserir alguma informação da programação no log da instância.
- Documentações: Nós disponibilizamos algumas documentações para ajudá-lo com a sua experiência com o JEX da Ema. Pode-se consultar as informações dos sites que nos ajudaram a deixar a plataforma mais completa.
Expressão - Função "Include" em FK
Olá,
Esse tópico serve para ensinar como realizar o uso da nova função (Expressão) em campos de FK, usando especialmente a função Includes. Como exemplo, foi feito um processo simples que contém as seguintes configurações:
- Um campo de FK normal.
- Um campo que deve se preencher automaticamente conforme seja selecionada uma opção da FK.
- Um botão que fica visível caso a FK esteja preenchida com determinada informação.
Esse procedimento foi feito tanto comparando pelo ID, quanto pela descrição.
Configuração dos Campos por ID
Campo de FK por ID.
A configuração do campo é normal (marque utilizar FK na aba Propriedades), e se atente ao campo destacado em vermelho, pois você precisará fazer uso dele para configurar as expressões.
Campo de preenchimento automático.
Para o preenchimento automático, você deve marcar a expressão no campo valor, e especificar sua condição nele. Nesse caso, o processo irá preencher com a descrição caso o valor selecionado esteja preenchido, independente do preenchimento escolhido.
Código:
CAMPODEFKID.valor ? CAMPODEFKID.valor.descricao : ''
Botão que deve ficar visível caso determinada opção seja selecionada.
Esse botão está configurado para caso o ID selecionado seja igual a três, o botão ficará visível. O símbolo "?" é como se fosse uma estrutura de IF, uma questão. Nesse caso, faz a seguinte análise: Caso o campo esteja preenchido, ele ficará visível.
[3].includes(CAMPODEFKID.valor ? CAMPODEFKID.valor.id : 0)
Configuração dos Campos por Descrição
Campo de FK por Descrição.
A configuração do campo é normal (marque utilizar FK na aba Propriedades), e se atente ao campo destacado em vermelho, pois você precisará fazer uso dele para configurar as expressões. A única diferença de fazer com ID, é que o identificador está diferente por ser outro campo. O identificador nunca pode se repetir em uma atividade.
Campo de preenchimento automático.
Para o preenchimento automático, você deve marcar a expressão no campo valor, e especificar sua condição nele. Nesse caso, o processo irá preencher com a descrição caso o valor selecionado esteja preenchido, independente do preenchimento escolhido. No print, o código ultrapassou um pouco o campo, mas estará escrito abaixo caso queira utilizar.
Código:
CAMPODEFKDESCRICAO.valor ? CAMPODEFKDESCRICAO.valor.descricao : ''
Botão que deve ficar visível caso determinada opção seja selecionada.
Esse botão está configurado para caso a descrição selecionada seja igual a "Consumidor final", o botão ficará visível. O símbolo "?" é como se fosse uma estrutura de IF, uma questão. Nesse caso, faz a seguinte análise: Caso o campo esteja preenchido, ele ficará visível. Nesse caso, o print também cortou o código, e ele estará escrito abaixo caso queira utilizar.
Código:
['Consumidor final'].includes(CAMPODEFKDESCRICAO.valor ? CAMPODEFKDESCRICAO.valor.descricao : '')
Campos de Modelos diferentes
Caso queira fazer uma situação, por exemplo, com um campo FK e um campo dissertativo, você pode fazer uso da seguinte sintaxe:
Código:
[3].includes(CAMPODEFKID.valor ? CAMPODEFKID.valor.id : 0) && ['Ola'].includes(CAMPODEDESCRICAO.valor ? CAMPODEDESCRICAO.valor : '')
Dessa forma vai estar comparando um campo de FK, e um outro campo de texto incluído em uma Dissertativa.
Expressões BPM
A partir da versão 12.24, os processos de negócio (BPM) contam com um novo conceito: Propriedades definidas por expressão.
Isso traz dinamismo para a colaboração de processos sem depender do PEX.
1 - O que é uma expressão?
Expressão pode ser entendida como uma fórmula matemática que vai retornar um valor, para representar o estado de uma propriedade. A expressão pode ser uma média de valores ou uma verificação se algum campo foi informado, por exemplo.
2 - Motivações
Os principais objetivos de implementar as expressões foram:
- Dar uma alternativa ao atual evento ao sair de campos (PEX), que fosse mais performática e mais simples.
- Avançar no conceito de Low-Code dentro do DOX.
3 - Identificador
Para possibilitar as expressões, foi criado o campo “Identificador”, cujo propósito é auto-explicativo: ser um identificador único para um campo de uma atividade.
Atualmente, nos eventos ao sair, é comum encontrar linhas de código parecidas com essa:
LValorAtual := aoFormularios.GetJSON('1').GetStr('TEXTO');
Nessa abordagem fica difícil entender o que está acontecendo analisando apenas essa linha de código: Afinal, ao que corresponde o aoFormulario “1” e ao que corresponde o “TEXTO”?
Partindo para uma abordagem em que o campo pode ser identificado por um nome, ao invés de um número, o código fica muito mais compreensível.
3.1 - Gerando o identificador
Ao atualizar o sistema para a versão 12.24 e abrir uma atividade de um processo já cadastrado, uma nova coluna vai aparecer na listagem de campos: a coluna “identificador”, já preenchida, de acordo com o “Código” de cada campo, da seguinte forma: _1, _2, _3, etc. Isso porque esse campo não pode ficar vazio, nem pode se repetir na atividade e para usuários que não vão utilizar expressões, esse campo vai ser indiferente.
Na imagem abaixo é possível visualizar essa nova coluna:
Ao criar um novo processo e adicionar campos manualmente, o identificador vai ser preenchido automaticamente de acordo com o “Título” informado, sendo possível alterá-lo manualmente também.
4 - Propriedades com expressão
Atualmente, as seguintes propriedades de um campo podem ter expressão:
- Visível
- Obrigatório
- Somente leitura
- Título
- Observação
- Valor
Vale destacar que essas propriedades vão estar disponíveis de acordo com o tipo do campo (texto, inteiro, grade, etc.) no cadastro do mesmo.
5 - Acessando propriedades
Dentro de uma expressão, para acessar uma propriedade de um campo do formulário, precisamos digitar o seguinte padrão:
IDENTIFICADOR.propriedadeSelecionada
Perceba o formato desse padrão:
- Identificador do campo (que é maiúsculo)
- Ponto para separar campo e propriedade
- Propriedade do campo em camel case
Exemplos nesse formato:
- ENDERECO.valor
- CEP.visivel
- NOME.somenteLeitura
- NUMERO.observacao
- CPF.titulo
- DATANASCIMENTO.obrigatorio
OBS: Muito cuidado ao digitar letras maiúsculas e minúsculas, pois a expressão é CASE SENSITIVEL!
5.1 - Exemplo acessando propriedades
imagine um processo com 3 campos: "Número 1", "Número 2" e "Total", conforme imagem abaixo:
Imagine que queremos exibir a soma dos dois número no total. Podemos fazer isso via evento ao sair ou através de uma expressão. Através do PEX, precisaríamos adicionar o código nos eventos ao sair dos campos "Número 1" e "Número 2":
aoFormularios.GetJSON('3') .SetInt('TEXTO',
aoFormularios.GetJSON('1').GetInt('TEXTO') +
aoFormularios.GetJSON('2').GetInt('TEXTO') );
Para fazer o mesmo através de expressões, o código é similar. Precisamos somar o valor do "Número 1" e do "Número 2". O identificador desses campos é respectivamente "NUMERO1" e "NUMERO2". Seguindo o formato para acessar campos e propriedades, temos a seguinte expressão para somar o valor dos dois campos:
NUMERO1.valor + NUMERO2.valor
Agora basta selecionar que o valor do campo "Total" é definido por expressão e colocar a expressão criada, conforme imagem abaixo:
6 - Linguagem de programação
As expressões são executadas em JavaScript. Caso você entenda sobre programação, você pode imaginar a expressão como o retorno de uma função da seguinte forma:
function executaExpressao() {
return expressao;
}
**** Isso significa que você pode retornar qualquer valor (Number, String, Boolean), mas caso você tente fazer um if, um switch ou tentar definir uma var no inicio da sua expressão, ela não vai funcionar.
7 - Mudanças no cadastro
Ao criar ou editar um campo em um processo é possível ver que a tela está bem diferente.
Na imagem abaixo é possível comparar o antes e depois do cadastro de um mesmo campo:
Antes:
Agora:
As seguintes alterações foram feitas:
- Propriedades booleanas (visível, obrigatório e somente leitura) passaram de caixas de seleção para um menu de seleção com as opções: sim, não e expressão.
- Propriedades textuais (título e observação) ganharam um menu de seleção com as opções: fixo e expressão.
- A propriedade valor ganhou um menu de seleção com as opções: informado e expressão.
Dessa forma é possível definir as propriedades como já era possível além de adicionar uma nova possibilidade, através de expressões. Caso o usuário selecione a opção “expressão” no menu de seleção, um campo para a entrada da expressão vai aparecer ao lado.
7.1 - Assistente
Para ajudar o usuário a montar suas expressões, foi criado um assistente que vai trazer grande parte do que é necessário:
- Os campos do processo e as propriedades disponíveis de acordo com o tipo de campo
- Operadores matemáticos
- Operadores lógicos
- Operadores relacionais
- Conversões para tipos de dados
Para acessar o assistente, é necessário focar no campo da expressão e pressionar Ctrl + Espaço. O assistente deve aparecer na tela dessa forma:
Ao selecionar alguma opção do assistente, o modelo de código já vai ser inserido na entrada da expressão. Ao selecionar um modelo de operação pronto, como por exemplo o modelo de “Adição”, a expressão montada vai ser:
/*campo1*/ + /*campo2*/
Nesse modelo, o “campo1” e “campo2” são apenas indicações de que você deve selecionar algum valor, como o valor de algum campo do formulário.
ATENÇÃO: Não se engane pelos caracteres /* */, as expressões NÃO interpretam variáveis. Nesse modelo, ao deixar o cursor de digitação em cima do “campo1” ou do “campo2” e abrir o assistente novamente, o texto “/*campo*/” do modelo vai sumir, para que algum valor possa ser selecionado a partir do assistente.
Utilizando expressão:
8 - Exemplo de uso das expressões
Imagine o seguinte formulário:
Esse formulário possui as seguintes regras:
- Caso o valor pago seja menor que a dívida, o campo “Em aberto” deve ficar visível e exibir o valor em aberto da dívida;
- Caso o valor pago seja maior que a dívida, o campo “Troco” deve ficar visível e exibir o valor que foi pago a mais.
De que forma podemos expressar matematicamente essas regras?
O campo “Em aberto”:
- Vai ficar visível SE: a dívida menos o pagamento for maior que 0
- Vai exibir como valor: a dívida menos o pagamento
O campo “Troco”:
- Vai ficar visível SE: o pagamento menos a dívida for maior que 0
- Vai exibir como valor: o pagamento menos a dívida
A partir do momento que sabemos como definir essas expressões, basta passar para o cadastro do campo.
Na imagem abaixo, as expressões do campo “Em aberto”:
Já na imagem abaixo, as expressões do campo “Troco”:
9 - O que é possível fazer com expressões?
As tabelas abaixo foram montadas para exibir o que funciona ou não nas expressões, como um guia para quem já está familiarizado com o PEX e quer começar a montar expressões nos seus processos:
Funciona;
- Acessar campos do formulário
- Definir regras para a própria propriedade que possui a expressão
Não funciona
- Acessar variáveis
- Alterar outras propriedades deste e de outros campos do formulário
- Alterar propriedades do formulário / atividade
- Fazer requisições HTTP para retornar definir uma propriedade
- Interagir com o banco de dados
- Interagir com o sistema de arquivos
- Adicionar uma mensagem para o usuário
Para os programadores: Pense no PEX como programação imperativa e nas expressões como programação declarativa:
No PEX, ao sair um campo qualquer, determinadas ações são realizadas, por exemplo: alterar o valor de um campo, deixar outro campo invisível, deixar aquele campo obrigatório, etc.
Já as expressões, podem ser pensadas como regras que definem uma propriedade, por exemplo: este campo vai ser visível quando o valor do outro campo for menor que 100; este campo vai ser obrigatório caso outro campo tenha algum valor informado.
São duas formas de pensar diferentes. Por exemplo, na expressão da propriedade visível de um campo, não é possível definir a propriedade somente leitura desse mesmo campo.
10 - Executando as expressões
Após configurar um processo com as expressões, é possível criar uma nova instância do processo normalmente.
Ao entrar na atividade para colaborar, as expressões já vão ser executadas, e a cada vez que um campo tiver seu valor alterado, as expressões serão executadas novamente. Com isso, para o usuário, as expressões sempre vão estar sempre refletindo o estado do formulário.
É nesse momento que uma outra diferença do PEX para as expressões fica clara: as expressões vão ser executadas quase que instantaneamente, sem mostrar uma mensagem de “Aguarde...” para o usuário. Isso é possível porque as expressões são executadas diretamente no navegador do usuário do Portal. Isso permite uma performance muito maior e uma execução praticamente instantânea, além de aumentar a confiabilidade da execução, sem riscos de instabilidades no sistema.
10.1 - Erro!
Erros acontecem. Seja porque o identificador de um campo mudou e as expressões não foram atualizadas, seja por conta de um erro de sintaxe, seja porque as expressões são circulares. Há muitos jeitos de errar!
Quando um erro acontece na execução de uma expressão, a execução das demais expressões é totalmente interrompida e uma mensagem de erro é exibida para o usuário.
Para os modeladores de processo: quando uma mensagem de erro aparecer pro usuário no Portal, a mensagem de erro original vai ser logada no console das ferramentas do desenvolvedor.
Os exemplos de mensagem de erro abaixo:
10.2 - Expressões circulares
A maioria dos erros pode ser entendida facilmente, mas as expressões circulares exigem uma atenção especial. Uma expressão circular é uma expressão que depende dela mesma para ser executada.
Imagine um formulário que possua dois campos: CPF e CNPJ. Suponha que o campo “visível” de ambos esteja definido por expressão, conforme as imagens a seguir:
Esse caso vai resultar em um erro circular. Isso porque para definir a visibilidade do campo CPF, que é definida por expressão, o Portal vai buscar a visibilidade do campo CNPJ. A visibilidade do campo CNPJ é definida por expressão, por isso o Portal vai tentar buscar a visibilidade do campo CPF. É um loop infinito, porque a expressão depende dela mesma.
Esse exemplo é mais simples, mas pode acontecer com 1, 2 ou n campos definidos por expressão.
10.2 - Mas e o PEX?
O objetivo das expressões não é substituir o PEX, mas oferecer uma alternativa para os casos mais simples.
Os eventos ao sair vão continuar sendo necessários para situações mais complexas e para ações assíncronas, como requisições HTTP, interações com o banco de dados ou com o sistema de arquivos.
Eventos ao sair e expressões foram pensados para serem usados em conjunto. Nos eventos ao sair de campos é possível ler e escrever as propriedades definidas por expressão, porém ao sobrescrever o valor de uma propriedade definida por expressão, ela deixa de ser definida por expressão e assume o valor definido no PEX.
Utilizando PEX e expressões
11 - Exemplo integrando PEX e Expressões
Imagine um processo que possui campos para informar um endereço, conforme a imagem a seguir:
Este processo possui um campo chamado “Endereço completo” que concatena as informações do endereço. Utilizando expressões é possível definir o que o valor do “Endereço completo” recebe, conforme mostrado abaixo:
Se essa regra fosse implementada via evento ao sair, seria necessário copiar o código que define o valor do “Endereço completo” ao sair dos campos “Endereço” e “Número”, duplicando o código e aumentando a chance de bugs, caso a regra que define o formato de exibição mude.
Imagine agora que foram adicionados os campos “Estado” e “Município”. Para alterar a informação exibida no “Endereço completo”, basta alterar a expressão para incluir esses campos. Se fossemos fazer isso via PEX, seria necessário alterar o código já existente do “Endereço” e “Número”, além de incluir no “Estado” e no “Município”.
Perceba que o processo possui um campo CEP. Podemos utilizar um evento ao sair desse campo para buscar via requisição HTTP as informações do CEP informado e sobrescrever as informações dos campos “Endereço”, “Número”, “Estado” e “Município”, deixando esses campos somente leitura.
Veja como nesse exemplo conseguimos extrair o melhor das expressões, através de uma regra simples que define o valor de um campo, e do PEX, para buscar informações através de uma requisição HTTP.
12 - Aplicando boas práticas
As boas práticas das expressões são as mesmas que se aplicam para o JavaScript. As boas práticas em geral reduzem a complexidade ou redundância do código. Alguns exemplos são:
Boas práticas:
- CPF.visivel
- !CPF.visivel
- IDADE.valor === 20
- NUM1.valor || NUM2.valor
- NUM.valor || 10
- true
- CNPJ.somenteLeitura
- NUM1.valor > 10 && NUM2.valor > 10
- [‘joão’, ‘pedro’].includes(NOME.valor)
Má prática:
- CPF.visivel === true
- CPF.visivel === false
- IDADE.valor == 20
- NUM1.valor ? NUM1.valor : NUM2.valor
- NUM.valor !== null ? NUM.valor : 10
- 1 === 1
- CNPJ.somenteLeitura ? true : false
- (NUM1.valor > 10) && (NUM2.valor > 10)
- NOME.valor === ‘joão’ || NOME.valor === ‘pedro’
Formatação no PEX (Telefone Fixo / Celular)
Neste tópico será abordado o tema referente ao evento PEX, com o intuito de formatar o campo TELEFONE, verificando se é um TELEFONE FIXO ou CELULAR, dependendo da quantidade de caracteres digitado.
Para acessar o PEX de um formulário basta marcar a opção Ao Sair e clicar no botão com três pontos.
Segue a imagem abaixo:
Segue o código que será aplicado Ao sair - PEX:
//PEX PARA TRATATIVA DE CAMPO FORMATADO COMO TELEFONE FIXO OU CELULAR
CONST
CS_FONE = '4';
VAR
liTamanho : Integer;
lsMascara : String;
begin
liTamanho := length(aoFormularios.GetJSON(CS_FONE).GetStr('TEXTO')); //Conta a quantidade de caracteres digitada.
lsMascara := '('+ copy(aoFormularios.GetJSON(CS_FONE).GetStr('TEXTO'),0,2)+') '+copy(aoFormularios.GetJSON(CS_FONE).GetStr('TEXTO'),3,liTamanho-6)+'-'+ copy(aoFormularios.GetJSON(CS_FONE).GetStr('TEXTO'),liTamanho-3,liTamanho); //Insere a máscara (parênteses, espaçamento e traço) conforme a quantidade de caracteres digitados.
aoFormularios.GetJSON(CS_FONE).SetStr('TEXTO', lsMascara); //Preenche o campo digitado ao sair dele, já com a máscara atribuída.
// Esta parte abaixo, formata a mensagem de erro caso a quantidade de caracteres digitados for maior que o limite máximo.
if length(aoFormularios.GetJSON(CS_FONE).GetStr('TEXTO')) < 14 then
begin
aoFormularios.GetJSON(CS_FONE).SetStr('TEXTO', '');
aoMensagem.SetStr('MENSAGEM', 'Você precisa digitar a quantidade mínima de caracteres');
aoMensagem.SetStr('TIPO', 'ALERTA');
aoMensagem.SetInt('TIMEOUT', 8000{Milissegundos});
end;
// Esta parte abaixo, formata a mensagem de erro caso a quantidade de caracteres digitados for menor que o limite mínimo.
if length(aoFormularios.GetJSON(CS_FONE).GetStr('TEXTO')) > 15 then
begin
aoFormularios.GetJSON(CS_FONE).SetStr('TEXTO', '');
aoMensagem.SetStr('MENSAGEM', 'Você ultrapassou a quantidade máxima de caracteres');
aoMensagem.SetStr('TIPO', 'ALERTA');
aoMensagem.SetInt('TIMEOUT', 8000{Milissegundos});
end;
end;
Grade - Incluir e remover linhas via PEX
Olá, neste tópico iremos apresentar como gerar e modificar uma grade dinamicamente utilizando PEX.
Serão abordados a inserção de novas linhas na grade, remoção de um índice e reorganização dos índices.
Vamos criar um processo de exemplo, contendo uma atividade onde iremos adicionar uma grade de dados, dois botões (Adicionar e Remover) e um campo para informarmos a quantidade (este campo será usado para inserir "x" linhas na grade ou remover a linha de índice "x").
Com o processo e a atividade devidamente criados, vamos criar os formulários, para este exemplo serão necessários 4 formulários:
- Grade de Dados
- Botão (Adicionar)
- Dissertativa (N°/Índice)
- Botão (Remover)
Com os formulários devidamente criados, iremos configurar seus eventos.
Botão Adicionar, este botão será o responsável por inserir novas linhas na nossa grade de dados, para isso é validados a quantidade de linhas atuais da grade e os índices que serão atribuídos a ela.
const
cs_grade = '1';
cs_Qtd = '3' ;
var
liIdRegistro: Integer;
loRegistro: TJSONObject;
loNovoRegistro : TJSONObject;
liSequencia : Integer;
liQtd : Integer;
liQtdAtual : Integer;
liQtdFinal : Integer;
begin
//Define a quantidade informado no campo "N°"
liQtd := StrToInt(aoFormularios.GetJSON(cs_Qtd).GetStr('TEXTO'));
//Define a quantidade de linhas presentes atualmente na grade
liQtdAtual := aoFormularios.GetJSON(cs_grade).GetArrayJSON('DADOS').Count;
//Verifica se a grade já possui linhas adicionados
if (liQtdAtual > 0) then
begin
//Define qual será o último indices (considerando o indice atual + o número de linhas informados para adicionar)
liQtdFinal := liQtdAtual + liQtd;
//Percorremos a lista até o indice final
for liSequencia := liQtdAtual+1 to liQtdFinal do
begin
//Criamos e adicionamos uma nova linha na grade de dados
loNovoRegistro := TJSONObject.Create;
loNovoRegistro.AddPair('REGISTROS', liSequencia);
loNovoRegistro.AddPair('NOME', '');
loNovoRegistro.AddPair('DATA', Date);
loNovoRegistro.AddPair('CIDADE', '');
aoFormularios.GetJSON(cs_grade).GetArrayJSON('DADOS').Add(loNovoRegistro);
end;
end
else
begin
//Percorremos a lista até o indice final
for liSequencia:= 1 to liQtd do
begin
//Criamos e adicionamos uma nova linha na grade de dados
loNovoRegistro := TJSONObject.Create;
loNovoRegistro.AddPair('REGISTROS', liSequencia);
loNovoRegistro.AddPair('NOME', '');
loNovoRegistro.AddPair('DATA', Date);
loNovoRegistro.AddPair('CIDADE', '');
aoFormularios.GetJSON(cs_grade).GetArrayJSON('DADOS').Add(loNovoRegistro);
end
end;
end;
Botão Remover, este botão será o responsável por remover o índice informado pelo usuário e reorganizar os índices da grade, garantindo que não ficarão "furos" na indexação da grade.
const
cs_grade = '1';
cs_n = '3';
var
liIdRegistro: Integer;
loRegistro: TJSONObject;
loNovoRegistro : TJSONObject;
liRemover : Integer;
liQtd: Integer;
liI : Integer;
begin
//Define a quantidade informado no campo "N°"
liQtd := aoFormularios.GetJSON(cs_grade).GetArrayJSON('DADOS').Count ;
//Define o indice que será remover
liRemover := StrToInt(aoFormularios.GetJSON(cs_n).GetStr('TEXTO'));
//Percorre toda a grade
for liI := 0 to liQtd do
begin
//Verifica se o indice atual da repetição é igual ao indice informado para remoção
if liI = liRemover then
begin
//Remove a linha presente no indice informado
aoFormularios.GetJSON(cs_grade).GetArrayJSON('DADOS').Remove(liRemover-1);
end
end;
//Percorre a grade partindo do indice 0 até o maior indice da grade
for liIdRegistro := 0 to Pred(aoFormularios.GetJSON(cs_grade).GetArrayJSON('DADOS').Count) do
begin
//Atribui a linha presente no indice atual para a variavel loRegistro
loRegistro := aoFormularios.GetJSON(cs_grade).GetArrayJSON('DADOS').GetItemAsJson(liIdRegistro);
//Define o novo indice da linha
loRegistro.SetInt('REGISTROS', liIdRegistro+1);
end;
end;
Demonstração:
Versão Homologada: 12.15+
JEX / JS Sandbox
A partir da versão 12.25, foi disponibilizado no DOX uma alternativa ao PEX, possibilitando a codificação e a execução dos eventos de formulário (ao sair de um campo de formulário, ao sair da atividade, ao entrar na atividade, ao sair de um campo da grade) existentes em processos de negócio (BPM) com a linguagem JavaScript.
OBS: Essa nova funcionalidade só está disponível para ambientes PostgreSQL e Oracle.
E para isso, foi criado um novo servidor desenvolvido na tecnologia Node.js (JavaScript a nível de servidor), com o nome JS Sandbox, que possui diversos recursos disponíveis.
O nome JS Sandbox foi escolhido porque 'JS' é a sigla para JavaScript, e o termo 'Sandbox' é geralmente utilizado na programação para se referir a um ambiente de execução de código fonte. Os desenvolvedores DOX utilizam o termo "JEX" para se referenciar ao novo recurso também, mantendo o padrão de três letras (PEX, EMA).
Alterações no Ema DOX Estúdio
Segue as alterações feitas no Ema DOX Estúdio para possibilitar a codificação em JavaScript nos eventos de formulário.
Tela de edição de código fonte dos eventos de formulário.
Nessa tela, foi incluído um novo combo na parte superior com o nome 'Linguagem' e as opções 'Object Pascal' e 'JavaScript Node.js)'. A opção 'Object Pascal', vai manter as funcionalidades que existiam anteriormente na tela, e continuará executando o evento no Servidor PEX. Mas com a opção 'JavaScript', a tela irá ser reajustada, modificando o cabeçalho, a aba lateral 'Assistente', o design do editor, e os atalhos do teclado.
- Editor
Foi aplicado o mesmo padrão de cores utilizado nas expressões, o Solarized. A codificação será feita dentro da assinatura da função, que foi incluída apenas por questões de indentação, e para deixar claro que se trata de uma função assíncrona. E o nome da função é fixo, variando de acordo com o evento, podendo ser 'aoSairCampoFormulario', 'aoSairCampoGrade', 'aoEntrarAtividade' e 'aoSairAtividade'.
A imagem a seguir mostra como ficará o editor ao entrar em um evento ao sair de um campo do formulário.
- Aba 'Assistente'
Esse assistente possui o acesso facilitado a muitos dos recursos disponibilizados, no mesmo padrão do 'Assistente do processo' na linguagem 'Object Pascal'.
Ao clicar em algum dos assistentes, irá vincular no código fonte uma base inicial da funcionalidade em questão. O item 'Documentações' do assistente possui links para o acesso a documentação das tecnologias utilizadas, e a esse post do fórum.
- Atalhos do teclado
- Ctrl + G → Vai focar no campo 'Ir para a linha' setando a linha focada.
- Ctrl + ';' → Comentar ou descomentar.
- Tab + texto sendo selecionado em múltiplas linhas → Indentação 2 caracteres).
- F5 → Validar código fonte.
- Ctrl + S → Salvar alterações.
- Ctrl + Barra de espaço → Atalho do editor para completação de código e menu com acesso aos recursos disponibilizados no JS Sandbox.
Segue uma imagem que mostra como ficou a tela de edição de código fonte de eventos com a linguagem 'JavaScript Node.js)'
Parâmetro geral para definir linguagem padrão dos eventos.
Principais Recursos
Para os eventos de formulário, o JS Sandbox oferece acesso a diversos recursos, incluindo recursos nativos da linguagem JavaScript, acesso a várias informações da atividade, acesso a arquivos e pastas, acesso a WebServices, acesso a banco de dados, inserção de log no processo, e assim por diante.
Acesso a informações da atividade.
- Variáveis (variáveis)
Possibilita o acesso a todas as variáveis de processo e de sistema. Exemplo:
Observação: Não é possível alterar o valor de variáveis de sistema.
- Campos do formulário (formulário)
Possibilita o acesso a todos os campos cadastrados no formulário da atividade.
Observação: Está disponível em todos os eventos de formulário, com exceção do evento ao sair de um campo da grade.
Acessando propriedades de um formulário
Acessadas no padrão → formulario.IDENTIFICADOR.propriedade.
- Valores do registro atual da grade (valoresRegistroAtual)
Observação: Disponível apenas para o evento ao sair de um campo da grade.
- Objeto de mensagem (mensagem)
Possibilita definir uma mensagem para aparecer na tela de colaboração da atividade.
Propriedades:
- texto: Define o texto da mensagem.
- tipo: Define o tipo da mensagem, que pode ser:
- Informação TipoMensagem.INFO;
- Erro TipoMensagem.ERRO;
- Alerta (TipoMensagem.ALERTA);
- timeout: Define o timeout da mensagem em milissegundos.
Exemplo:
Observação: As propriedades dos objetos da atividade (no padrão → recurso.IDENTIFICADOR.propriedade) foram padronizadas para serem acessadas em CamelCase.
- Outras informações disponíveis: idProcesso, idAtividade, idElementoFoco, dadosFK, etc.
- Acesso a arquivos e pastas (fsPromises): Possibilita o acesso aos métodos para manipulação de arquivos e pastas do módulo fs/promises do Node.js.
- Acesso a web services (axios): Possibilita o acesso aos métodos disponíveis para requisições HTTP da biblioteca Axios.
- Acesso a banco de dados (conexaoBD: Possibilita a execução de um SQL no banco de dados local, ou em bancos de terceiros via conector.
-
- Banco local utilizando os métodos conexaoBD.executarComandoSQL e conexaoBD.executarConsultaSQL.
- Bancos: Postgres e Oracle.
- Bancos de terceiros via conector utilizando os métodos conexaoBD.executarComandoSQLPorConector e conexaoBD.executarConsultaSQLPorConector.
- Bancos: Postgres, Oracle, MySQL e Firebird.
- Banco local utilizando os métodos conexaoBD.executarComandoSQL e conexaoBD.executarConsultaSQL.
-
- Observações: Para o acesso a bancos Firebird via conector é necessário ter o ODBC instalado na máquina. Disponível em ODBC Driver. Em consultas SQL em qualquer banco local ou de terceiros via conector: Se a consulta resultar em apenas um registro, a função vai retornar um objeto, se resultar em mais do que um registro, o retorno será um array de objetos. E as propriedades dos objetos retornados precisam ser acessadas em minúsculo.
- Inserir log no processo (logProcesso): Possibilita a inserção de um log na instância do processo, podendo ser de informação (logProcesso.info) ou de erro (logProcesso.erro), ambos métodos possuem apenas o parâmetro 'mensagem'.
Outros detalhes importantes
- Necessário habilitar o serviço 'Ema JS Sandbox' no Ema Configurador.
- O código JavaScript vai ser executado a nível de servidor (Node.js).
- A versão do Node.js mínima exigida é a 14.
- JavaScript é Case-sensitive.
- O servidor JS Sandbox funciona de forma assíncrona, então os métodos de acesso a vários recursos são funções assíncronas, que quando executadas retornam Promises, necessitando então que seja colocado 'await' antes da chamada, ou tratar com '.then' depois da chamada. Recursos onde seus métodos retornam Promises:
- fsPromises
- conexaoBD
- axios
- logProcesso
- Para qualquer erro gerado dentro do JS Sandbox, será inserido um log de erro no processo.
- O serviço tem um log interno, encontrado na pasta de instalação → 'js sandbox' → 'nest.log'.
Links para mais informações:
Usando Promises - JavaScript
Promise - JavaScript
Funções assíncronas - JavaScript
JavaScript Assíncrono
JEX - Percorrer e alterar resultado de uma grade
Neste fórum iremos mostrar como percorrer e alterar os registros mostrados pela grade de dados utilizando JEX (JS Sandbox). Abaixo estão alguns materiais que podem ajudar no entendimento do processo:
Exporte o processo em anexo ou siga as instruções abaixo:
Criamos uma atividade manual com um formulário de FK nos trará os processos e suas informações. Lembre-se, o JavaScript é case sensitive então preste atenção no IDENTIFICADOR do formulário, é importante fazer exatamente como instruído nesse tópico:
Crie uma Fk trazendo o IDPROCESSO e DESCRIÇÃO da CRM_PROCESSO.
Código JEX ao sair do formulário:
let SQL;
let arrayIdprocessoPesquisado = formulario.SELECIONAOIDPROCESSO.valor.split(',');//arrayProcessosPesquisadasCliente
let stringIdprocessoPesquisado = formulario.SELECIONAOIDPROCESSO.valor; //stringProcessosPesquisadasCliente
if(arrayIdprocessoPesquisado[0] === ""){
arrayIdprocessoPesquisado.pop(); //se o campo estiver vazio
// Remove todos registros da grade
formulario.INFORMACOESDOIDPROCESSO.registros.splice(0 ,formulario.INFORMACOESDOIDPROCESSO.registros.length);
SQL =`SELECT IDPROCESSO,
DESCRICAO,
OBJETIVO,
STATUS,
DATAHORA
FROM CRM_PROCESSO
WHERE ROWNUM < 31`;
const DadosIdprocesso = await conexaoBD.executarConsultaSQL(SQL);
if(DadosIdprocesso){ //adiciona os dados na grade
DadosIdprocesso.forEach( dados => {
const novoRegistro = new Object();
novoRegistro.IDPROCESSO = dados.idprocesso;
novoRegistro.DESCRICAO = dados.descricao;
novoRegistro.OBJETIVO = dados.objetivo;
novoRegistro.DATAHORA = dados.datahora;
novoRegistro.STATUS= dados.status;
formulario.INFORMACOESDOIDPROCESSO.registros.push(novoRegistro);
});
}
}
if(arrayIdprocessoPesquisado.length){ //se possui valor
SQL = `SELECT IDPROCESSO,
DESCRICAO,
OBJETIVO,
STATUS,
DATAHORA
FROM CRM_PROCESSO
WHERE IDPROCESSO = ${stringIdprocessoPesquisado}`//concatena com a variável;
const resultadoPesquisadasIdprocesso = await conexaoBD.executarConsultaSQL(SQL);
if(resultadoPesquisadasIdprocesso){
// Remove todos registros da grade
formulario.INFORMACOESDOIDPROCESSO.registros.splice(0, formulario.INFORMACOESDOIDPROCESSO.registros.length);
//adiciona registros
resultadoPesquisadasIdprocesso.forEach( dados => {
const novoRegistro = new Object();
novoRegistro.IDPROCESSO = dados.idprocesso;
novoRegistro.DESCRICAO = dados.descricao;
novoRegistro.OBJETIVO = dados.objetivo;
novoRegistro.DATAHORA = dados.datahora;
novoRegistro.STATUS= dados.status;
formulario.INFORMACOESDOIDPROCESSO.registros.push(novoRegistro);
});
}
}
Criamos mais um formulário do tipo grade de dados e suas respectivas colunas. Segue imagem abaixo:
Certo, criamos a grade e suas colunas. Agora vamos criar um evento para carregar a grade com alguns processos ao ENTRAR na atividade. Dessa forma, o campo de ID seria usado mais como um campo de pesquisa para ver um processo em especifico.
Evento Estrutura de repetição - carregar:
Lembre-se, o ROWNUM é utilizado em bancos ORACLE, para PostgreSQL utilize o LIMIT = 31 depois do FROM por exemplo:
- Não foi possível adicionar o gif neste tópico, clique aqui se quiser ver a demonstração no portal.
- Lembre-se, se quiser que caso o usuário não selecione nada na FK o JEX traga a mesma consulta ao entrar na atividade, deve ser modificado o JEX da forma como desejar. No exemplo deste tópico abordamos uma parte da consulta apenas.
- Em anexo está o exemplo do BPM, baixe-o e modifique caso necessário.
- Caso queira que seja salvo em variáveis o que está na grade. ao sair crie uma estrutura de percorrer a grade e retornar o valor com os eventos do sistema.
PEX - Acessando outras bases via PEX
Neste tópico veremos como conectar em uma base diferente a que o sistema usa via PEX. Usaremos apenas um botão neste exemplo para fazer a conexão.
Código PEX aplicado :
var
liCodigoConector : integer; // <- Conector de conexão de banco de dados criado no Modulo de Processos -> Conectores,Conector da base que irá se conectar
locds : TLibCDS;
lbErpOnline : boolean; // < -- Valida se estiver contactado ou não
begin
liCodigoConector := 16; // <-- Codigo do conectar da base que irá se conectar
try
try
loCDS := of_CriaCDSporSQL('select * from versaodb where 1=2', liCodigoConector ); // select para validar se a conexão foi feita.
lbErpOnline := true;
aoMensagem.SetStr('MENSAGEM', 'Conectado');
except
aoMensagem.SetStr('MENSAGEM', 'Não conectado');
lbErpOnline := false;
end;
finally
loCDS.Free;
end;
end;
"loCDS := of_CriaCDSporSQL('select * from versaodb where 1=2', liCodigoConector );"
A tabela a qual informa deve existir no banco a qual está se conectando, caso não exista, pode se aplicar uma validação por exemplo no Else aplicar outra mensagem de Erro e mencionando que não foi possível se conectar ou inserir/alterar os dados pois o retorno da variável lsRetorno não será OK...
Versão Homologada: 12.7
PEX - Comunicação com WebService - SOAP - XML
Olá,
Se você possui necessidade de comunicar-se com outros softwares e/ou ferramentas, há uma grande chance de que em determinado momento a única possibilidade viável seja a comunicação via WebService.
Conceito básico:
Segundo (W3C, 2004), "Um serviço web é um sistema de software desenvolvido para suportar iterações máquina-máquina interoperáveis sobre uma rede. O serviço web implementa uma interface descrita em um formato que a máquina pode processar, especificamente o WSDL (Web Service Description Language), possibilitando a iteração de outros sistemas utilizando o contrato prescrito no documento WSDL utilizando mensagens SOAP (Simple Object Access Protocol), frequentemente transportadas usando o HTTP (HiperText Transfer Protocol) com serialização XML (Extensible Markup Language). "
Ou seja, WebService permite a comunicação de diferentes softwares por simples transferência de objetos/textos e/ou XML, ou seja, sem contato com a base de dados, somente respondendo a requisições em layout especifico, no nosso caso a comunicação do DOX a arquiteturas que não temos acesso ao banco de dados.
Lembre-se sempre de consultar a documentação do webservice a ser utilizado ou consultar o desenvolvedor do webservice para entender os principais aspectos, exemplos:
- Endereço de comunicação com o WS;
- Endereço de descrição do WS para testes via ferramenta; (Exemplo WSDL)
- Layout padrão de requisição;
- Layout padrão de retorno;
- Possíveis tag e/ou códigos de retorno;
- Possíveis mensagens de retorno; (Neste caso e no anterior, verificar todas as possibilidades para tratamento, exemplo: Erros de comunicação, de layout, registro duplicados, etc).
- Tipo de Webservice; (SOAP / SOA / Outros)
- Tipo de Requisição/Retorno; ( JSON / XML / Outros)
- Tipo de Comunicação; ( REST / POST / Outros)
- Necessidade de cabeçalho e/ou token;
Abaixo estou disponibilizando código comentado para comunicação via SOAP - XML com software de terceiro, esta comunicação visa de forma simbólica fazer o cadastro de um novo fornecedor e obter como retorno mensagem ou código de erro, ou obter o código do novo fornecedor cadastro no caso de obter sucesso da requisição..
#procedure EXEMPLO(const aiAtividadeAtual : Integer; aoFormularios : TJSONObject; const aoVariaveis : TVariaveisEventoFormulario; const aoMensagem : TJSONObject);
const
lscaminhoURL := 'http://enderecows.com.br/ema003/consulta/'; //Endereço do Webservice
lscabrequisicao := 'ema0003'; //Cabeçalho utilizado pelo WS, pode não ser necessário.
var
cs_retorno,
lsRequisicao,
Result,
lsCPF,
lsCNPJ,
lsInsEstado,
lsEmail,
SQL: String;
loRESTClient : TRESTClient;
loRESTRequest : TRESTRequest;
loRESTResponse : TRESTResponse;
loAuthBasica : THTTPBasicAuthenticator;
loLeitorXML : TLibLeitorXML;
begin
lsCPF := aoVariaveis.GetStr('/*CPF*/');
lsCPF := StringReplace(lsCPF, '.', '',[rfReplaceAll, rfIgnoreCase]); //Removido pontos do CPF já que WS de destino não utiliza.
lsCPF := StringReplace(lsCPF, '-', '',[rfReplaceAll, rfIgnoreCase]); //Removido traço do CPF já que WS de destino não utiliza.
lsCNPJ := aoVariaveis.GetStr('/*CNPJ*/');
lsCNPJ := StringReplace(lsCNPJ, '.', '',[rfReplaceAll, rfIgnoreCase]); //Removido pontos do CNPJ já que WS de destino não utiliza.
lsCNPJ := StringReplace(lsCNPJ, '/', '',[rfReplaceAll, rfIgnoreCase]); //Removido barras do CNPJ já que WS de destino não utiliza.
lsCNPJ := StringReplace(lsCNPJ, '-', '',[rfReplaceAll, rfIgnoreCase]); //Removido traço do CNPJ já que WS de destino não utiliza.
lsInsEstado := aoVariaveis.GetStr('/*INSCRICAOESTADUAL*/');
lsInsEstado := StringReplace(lsInsEstado, '.', '',[rfReplaceAll, rfIgnoreCase]); //Removido pontos da IE já que WS de destino não utiliza.
lsEmail := aoVariaveis.GetStr('/*EMAIL_CONTATO*/');
if lsEmail = '.' Then
begin
lsEmail := StringReplace(lsEmail, '.', '',[rfReplaceAll, rfIgnoreCase]); // Se e-mail igual a vazio ou . por padrão, o ponto é removido.
end;
if ((lsCNPJ <> '') and (lsCNPJ <>'.')) then
lsNome := copy(aoVariaveis.GetStr('/*RAZAOSOCIAL*/'), 0,30); // LIMITANDO O CAMPO RAZAO SOCIAL A 30 CARACTERES (LIMITACAO DO WS)
if ((lsCPF <> '') and (lsCPF <>'.')) then
lsNome := copy(aoVariaveis.GetStr('/*NOME*/'), 0,30);
Em sequencia ao código acima, esta é uma das partes mais importantes do nosso código, montar o corpo do XML para efetuar a requisição:
lsRequisicao:= ' <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:sap-com:document:sap:soap:functions:mc-style"> '+ chr(13) +
' <soapenv:Header/> '+ chr(13) +
' <soapenv:Body> '+ chr(13) +
' <urn:'+lscabrequisicao+'> ' + chr(13) +
' <IDadosGerais> ' +chr(13) +
' <CodPortal>'+InttoStr(aoVariaveis.GetInt('/*IDPROCESSO*/'))+'</CodPortal> '+ chr(13) +
' <Cpf>'+lsCPF+'</Cpf> '+ chr(13) +
' <Cnpj>'+lsCNPJ+ '</Cnpj> '+ chr(13) +
' <InscricaoEstadual>'+lsInsEstado+ '</InscricaoEstadual> '+ chr(13) +
' <Email>'+lsEmail+ '</Email> '+ chr(13) +
' </IDadosGerais> ' +chr(13) +
' </urn:'+lscabrequisicao+'> ' + chr(13) +
' </soapenv:Body> '+ chr(13) +
' </soapenv:Envelope>';
Explicação do código acima:
Dentro da variável lsRequisição estou adicionando uma string com toda a estrutura do XML utilizando os dados obtidos do processo e armazenados variáveis também do tipo string, esses dados podem ter origem em formulários do processo, consultas no banco de dados ou até mesmo serem números e/ou textos fixos.
Perceba que foi criada toda indentação e adicionado ao final de cada linha um ENTER (chr(13)) tornando assim os testes mais fáceis para nós e/ou para terceiros na hora de fazer um "debug" e entender a requisição.
Abaixo sequencia ao código:
ExecuteSQL('insert into WS_XML (sequencial,retorno,idprocesso) values ((select coalesce((select max(sequencial) from teste),0)+1 from versaodb),' + TSTR.Aspa(lsrequisicao) + ', ' + aoVariaveis.GetStr('/*IDPROCESSO*/')+')');
// Utilizado tabela personalizada no banco para guardar as requisições ao WS, utilizado para debug, após pleno funcionamento deve ser desabilitado.
Result := '';
loRESTClient := nil;
loRESTRequest := nil;
loRESTResponse := nil;
try
loRESTClient := TRESTClient .Create(nil); //Cria Objeto cliet para comunicação
loRESTRequest := TRESTRequest .Create(nil); //Cria Objeto para envio de Requisição
loRESTResponse := TRESTResponse.Create(nil); //Cria Objeto para Obter o Retorno
loAuthBasica := THTTPBasicAuthenticator.Create('portal_ema','passEma'); //Autenticação - THTTPBasicAuthenticator.Create('USUÁRIO','SENHA');
loRESTClient.RaiseExceptionOn500 := False; //Torna o status das exceções falso para verificar se teremos exceções
loRESTClient.BaseURL := lscaminhoURL; //Define caminho(URL) para o Client enviar a requisição
loRESTClient.ContentType := 'text/xml'; //Define o formato do conteudo (ou seja XML neste caso)
loRESTClient.Authenticator := loAuthBasica; //Define o tipo de autenticação da nossa consulta;
loRESTRequest.Method := rmPost; //Metodo utilizado na consulta (Ex: rmPost / rmREST )
loRestRequest.Resource := ''; //Resourse ficará vazio, normalmente não será necessário;
loRestRequest.AddBody(lsrequisicao,ctText_xml); //Definição da estrutura da requisição (Variavel com conteudo XML que alimentamos anteriomente.)
loRESTRequest.Client := loRESTClient; //Define o cliente da requisição (selecionamos o client que criamos incialmente)
loRESTRequest.Response := loRESTResponse; //Define metodo de retorno
try
loRESTRequest.Execute; // Executa a requisição
except
on e : exception do // Trata possiveis exeções
ShowMessage( e.message ); // Exibe msg em caso de exeção
end;
if TSTR.of_IsNotNullEma(loRESTResponse.JSONText) then // Verifica se retorno é nulo
Result := loRESTResponse.JSONText // Se possuir conteudo, atribui a uma váriavel
else
Result := loRESTResponse.Content;
ExecuteSQL( 'UPDATE CRM_PROCESSO_VARIAVEL SET VALORATUAL = ' + TSTR.ASPA(Result) + ' WHERE IDVARIAVEL = 97 AND IDPROCESSO = ' + aoVariaveis.GetStr('/*IDPROCESSO*/') );
// Utilizado uma variavel do processo para guardar todo o resultado da consulta ao WS, para analises, poderá ser desativado quando estiver operando normalmente)
loLeitorXML := TLibLeitorXML.create; // Cria o objeto leitor de XML
try
loLeitorXML.of_CarregarTextoArquivo(loRESTResponse.Content); //Carrega o arquivo
lsCodFornecedorSAP := loLeitorXML.of_GetCampoRepeticao('ECodFornecedor', tcrTexto, '', i); //Extrai a tag ECodFornecedor do XML
aoVariaveis.SetStr('/*NOVO_COD_SAP_FORN*/', lsCodFornecedorSAP); //Adicionar o conteudo a uma variavel do processo
aoVariaveis.SetStr('/*ERRO_WS*/',loLeitorXML.of_GetCampoRepeticao('ECodRetorno', tcrTexto, '', i)); //Atribui código de erro a uma variavel;
aoVariaveis.SetStr('/*MSG_WS*/',loLeitorXML.of_GetCampoRepeticao('EMsgRetorno', tcrTexto, '', i)); //Atribui possivel mensagem de erro a uma variavel;
finally
TSO.Fan(loLeitorXML); // Finaliza o leitor XML;
end;
finally
//### ATENCAO, PASSO IMPORTANTE PARA GARANTIR DESENPENHO, CONFIABILIDADE E BOM FUNCIONAMENTO DAS CONSULTAS AO WS, SEMPRE LIMPAR/LIBERAR MEMORIA;
loAuthBasica .Free; //Limpar componente de autenticação;
loRESTClient .Free; //Limpar client
loRESTRequest .Free; //Limpar requisição
loRESTResponse.Free; //Limpar retorno
end;
end;
**** Lembre-se, o exemplo serve para aprendizado, não deve ser copiado na íntegra para dentro do seu código PEX. Copiar o nome da procedure/PEX por exemplo pode causar problemas difíceis de diagnosticar.
Como citado no tópico é muito importante consultar a documentação do WebService ou o seu desenvolvedor, e somente após entender o cenário, limitações e recursos disponíveis neste WS iniciar o desenvolvimento, haja vista que cada um deles possui características específicas de acordo com suas tecnologias e recursos;
PEX - Criar arquivo (Excel, TXT)
Versão homologada: 12.10.5
Criando arquivo Excel:
Iremos focar mais no PEX neste exemplo. O processo contém apenas dois formulários
- Grade de dados:
- Coluna 1 "Cliente";
- Coluna 2 "Valor"
- Botão: Evento PEX que irá gerar o Excel
A grade será carregada com um SELECT utilizando o evento de Estrutura de repetição - Carregar ao entrar, com o seguintes SQL:
SELECT CLIFOREMP.RAZAO
CLIENTE,
SUM(RECEBER.VALORSALDO) VALOR
FROM RECEBER
JOIN CLIFOREMP ON (CLIFOREMP.IDCLIFOREMP = RECEBER.IDCLIFOREMP)
WHERE ROWNUM <= 15
AND RECEBER.VALORSALDO > 0
GROUP BY CLIFOREMP.RAZAO
HAVING SUM(RECEBER.VALORSALDO) > 0
ORDER BY 2 DESC
Será necessário o formulário do tipo botão pois é necessário acessar algumas funcionalidades da grade, e na própria grade ao sair não é possível.
Além do formulário do tipo botão é necessário criar um arquivo modelo que o sistema irá se basear para criar o novo arquivo com as informações que a grade estará preenchida, crie uma pasta Temp no C: com esse arquivo modelo.
PEX ao sair do botão:
Const
// Abaixo está os formatos da celulas que você poderá utilizar.
csMascaraFormatoInteiro : String = '#0;[RED]-#0';
csMascaraFormatoFloat : String = '#,##0.00';
csMascaraSemFormato : String = #0;
csMascaraData : String = 'DDD DD/MM/YY';
csMascaraDataSimples : String = 'DD/MM/YYYY';
// Neste código é definido qual caminho do Arquivo modelo que o sistema irá se basear para a criação do novo arquivo
csCaminhoArquivoModelo : String = 'C:\TEMP\MODELO.XLS';
var
// Criando as variaveis necessárias para o funcionamento
liIdRegistro: Integer;
loRegistro: TJSONObject;
loCDS : TlibCDS;
loPlanilha : TEmaPlanilha;
lsNomeArquivo,
lsColuna : String;
liLinha : Integer;
loStringList : TStringList;
begin
// liLinha é a linha que o sistema irá começar a preencher as informações, como na primeira definimos o cabeçalho iniciaremos os dados na segunda.
liLinha := 2;
csCaminhoArquivoModelo:= 'C:\TEMP\MODELO.XLS';
//lsNomeArquivo é aonde o arquivo será salvo e com nome definido.
lsNomeArquivo := 'c:\temp\processo.xls';
loPlanilha := nil;
// Neste comando abaixo ele irá extrair o nome do arquivo para logo abaixo fazer uma validação se o arquivo existe ou não.
lsNomeArquivo := ExtractFilePath(lsNomeArquivo) +
StringReplace(ExtractFileName(lsNomeArquivo), '/', ' ', [rfReplaceAll]);
// Valida se o arquivo existe, caso exista ele deleta o arquivo para criar com as novas informações.
if FileExists(lsNomeArquivo) then
DeleteFile(lsNomeArquivo);
// Se o arquivo não existe ele cria no caminho indicado.
if not FileExists(ExtractFilePath(lsNomeArquivo)) then
ForceDirectories(ExtractFilePath(lsNomeArquivo));
TARQ.of_CopyArquivoXXX(csCaminhoArquivoModelo, lsNomeArquivo, ExtractFileExt(lsNomeArquivo));
//Começa a criar o arquivo.
loPlanilha := TEmaPlanilha.Create(nil);
loPlanilha.of_ArquivoAbrir(lsNomeArquivo);
//Local aonde é definido o nome da aba da planilha.
loPlanilha.of_PastaSelecionar('PROCESSO'); //Nome da ABA no Excel
// Começa a estrutura de repetição para cada registro da grade ser inserida.
for liIdRegistro := 0 to Pred(aoFormularios.GetJSON('1').GetArrayJSON('DADOS').Count) do
begin
//Pega o id do registro que ele está selecionando
loRegistro := aoFormularios.GetJSON('1').GetArrayJSON('DADOS').GetItemAsJson(liIdRegistro);
// Cliente -> É informado qual celula é iniciado, neste caso está sendo concatenado A2, logo após é informado o registro da grade que será preenchido
// após é definido o nil da celula e o formato da celula
loPlanilha.op_Cell('A' + IntToStr(liLinha),
loRegistro.GetStr('CLIENTE'),
nil,
csMascaraFormatoInteiro);
// Valor
loPlanilha.op_Cell('B' + IntToStr(liLinha),
loRegistro.GetStr('VALOR'),
nil,
csMascaraSemFormato);
// Incrementa na linha.
Inc(liLinha);
end;
// Salva a planilha.
loPlanilha.of_ArquivoSalvar;
end;
Resultado final:
Criando arquivo TXT:
Um dos casos de uso dessa função seria guardar informações tais como observações, texto que venham junto ao um anexo além de salvar arquivos e imagens via evento também criando um arquivo .TXT
Segue o código abaixo, colocando no botão ou ao sair de algum outro formulário.
var
loArquivo : TStringList;
lsArquivo,
lsSQL : String;
loCDS : TlibCDS;
begin
loArquivo := nil;
lsSQL := 'SELECT * FROM CRM_PROCESSO WHERE IDPROCESSO = ' + TSTR.Aspa(aoFormularios.GetJSON('1').GetStr('TEXTO'));
loCDS := of_CriaCDSporSQL(lsSQL);
if aoFormularios.GetJSON('1').GetInt('TEXTO') > 0 then
begin
try
loArquivo := TStringList.Create;
lsArquivo := 'C:\Teste ema\IDPROCESSO_'+ aoFormularios.GetJSON('1').GetStr('TEXTO') + '.txt'; // <--- Pasta com o nome do Anexo que será criado
loCDS.of_IniciaWhile; // Cada loArquivo.add é uma linha adicionada no txt
loArquivo.Add('Dados Exportados');
loArquivo.Add('IDPROCESSO: ' + loCDS.GetStr(['IDPROCESSO']));
loArquivo.Add('DESCRIÇÃO: ' + loCDS.GetStr(['DESCRICAO']));
loArquivo.Add('ASSUNTO: ' + loCDS.GetStr(['ASSUNTO']));
loArquivo.Add('USUARIO: ' + loCDS.GetStr(['USUARIO']));
loCDS.of_Primeiro;
while not loCDS.of_FimDS do
begin
loCDS.of_ProxReg; // <--- Obrigatorio para percorrer os proximos registros caso tenha para não entrar em looping
end;
aoMensagem.SetStr('MENSAGEM', 'Anexo Exportado');
aoMensagem.SetStr('TIPO', 'INFO');
aoMensagem.SetInt('TIMEOUT', 5000{Milissegundos});
finally
loCDS.Free;
loArquivo.SaveToFile(lsArquivo); // Salva o arquivo.
loCDS.of_FinalizaWhile;
end;
end;
end;
PEX - Filtrando dados por data
Neste tópico iremos mostrar como criar uma grade de dados filtrando os dados pela data inserida em dois formulários.
Iremos retorna os dados via SQL no PEX filtrando a data utilizando dois campos com variáveis do tipo data, e ao clicar o botão a grade será carregada com os dados já filtrados.
Resultado final:
Criação dos formulários de data e grade de dados:
Inserindo o PEX ao sair do botão:
Codígo PEX:
const
cs_Dtinicio = '1';
cs_Dtfim = '2';
cs_grade = '3';
var
locds : TLibCDS;
lsSQL : string;
liIdRegistro: Integer;
loNovoRegistro: TJSONObject;
begin
lsSQL := 'select usuario,observacao,data,valortotalitens from pedido where data between '+TSTR.Aspa(FormatDateTime('dd.mm.yyyy',aoFormularios.GetJSON('1').GetDt('TEXTO')))+' and '+TSTR.Aspa(FormatDateTime('dd.mm.yyyy',aoFormularios.GetJSON('2').GetDt('TEXTO')))+' and observacao <> ''.''';
loCDS := of_CriaCDSporSQL(lsSQL);
try
if locds.of_TemDados() then
begin
loCDS.of_IniciaWhile;
try
loCDS.of_Primeiro;
while not loCDS.of_FimDS do
begin
loNovoRegistro := TJSONObject.Create;
loNovoRegistro.AddPair('USUARIO', locds.GetStr(['USUARIO']));
loNovoRegistro.AddPair('OBSERVACAO', locds.GetStr(['OBSERVACAO']));
loNovoRegistro.AddPair('DATA', locds.GetDt(['DATA']));
loNovoRegistro.AddPair('VALORTOTALITENS', Currtostr(locds.GetCurr(['VALORTOTALITENS'])));
aoFormularios.GetJSON(cs_grade).GetArrayJSON('DADOS').Add(loNovoRegistro);
loCDS.of_ProxReg;
end;
finally
loCDS.of_FinalizaWhile;
end;
end;
finally
loCDS.Free;
end;
end;
PEX - Função AbreSite
Neste tópico iremos mostrar como utilizar a função do PEX de abrir site. Com ela em um botão, o usuário seria redirecionado para o site que foi configurado no PEX.
Esta função está homologada na versão 12.11
Criei uma atividade com dois botões, onde, nos eventos ao sair um irá ser direcionado para o nosso antigo fórum, e o outro para o nosso portal.
Com o botão direito do Mouse, procurei em "Sistema operacional" e em seguida em "AbreSite".
Clicando na função, o código virá desta forma: TSO.AbreSite(asURL); Nestes botões coloquei duas formas onde pode ser utilizado esta função:
- Com uma constante com o nome URL e o valor do site que deseja abrir no botão:
const
URL='https://portal.ema.net.br/';
begin
TSO.AbreSite(URL);
end;
- Ou substituindo direto dentro do parênteses:
begin
TSO.AbreSite('https://ema.net.br/forum/');
end;
O processo no portal:
PEX - Função LerBase64 e transforma no arquivo fisico
Está função é utilizada para gerar arquivos físicos de conteúdos base64.
Ela recebe dois parâmetros, o conteúdo base64 e o caminho a qual irá ser criado. o caminho deve conter o nome do arquivo e extensão do mesmo como por exemplo : C:\Temp\ArquivoAssinado_/*IDPROCESSO*/.pdf, neste caso ele irá criar o arquivo na pasta Temp com o nome ArquivoAssinado_1.pdf
procedure pex_LerBase64(const Conteudo, Caminho : String);
var
loStream: TBytesStream;
begin
loStream := TSO.Decode64(Conteudo);
try
loStream.SaveToFile(Caminho);
finally
loStream.Free;
end;
end;
Ela pode ser criada pelo desenvolvimento, caso necessário, e utilizar em eventos como PEX - Executar função para criar dinamicamente um ou mais arquivos.
PEX - Grade com campo de auto incremento
Hoje nativamente a ferramenta DOX ainda não tem o recurso de contador de linhas de uma grade, para isso foi realizado uma forma de se resolver temporariamente esta questão, pois para alguns essa informação visualmente pode fazer diferença.
Para isso utilizaremos apenas 4 formulários:
- 1 Grade de dados
- 1 Botão "Adicionar": com evento PEX ao sair para adicionar linhas na grade acima
- 1 Formulário dissertativo: para o usuário informar o sequencial que será incrementado
- 1 Botão "Remover": com evento PEX ao sair para remover as linhas adicionadas na grade.
Conforme imagem do portal:
No formulário de grade de dados desmarque a opção "Permite incluir registros" e "Permite excluir registros". Estas opções é o que permite ao usuário fazer as ações no portal, porém como os botões nativos da grade não executam PEX ao sair, iremos retirar e criarmos os nossos de forma manual.
Na aba "Campos":
Agora, depois de criado o formulário de grade, crie os outros formulários de botão e dissertativa.
Botão "Adicionar". PEX ao sair:
const
cs_grade = '1';
var
liIdRegistro: Integer;
loRegistro: TJSONObject;
loNovoRegistro : TJSONObject;
liSequencia : Integer;
begin
//incrementa sequencial
liSequencia := 1;
for liIdRegistro := 0 to Pred(aoFormularios.GetJSON('1').GetArrayJSON('DADOS').Count) do
begin
loRegistro := aoFormularios.GetJSON('1').GetArrayJSON('DADOS').GetItemAsJson(liIdRegistro);
if loRegistro.GetInt('REGISTROS') >= liSequencia then
begin
liSequencia := loRegistro.GetInt('REGISTROS')+1;
end;
end;
//insere informações na grade
loNovoRegistro := TJSONObject.Create;
loNovoRegistro.AddPair('REGISTROS', IntToStr(liSequencia));
aoFormularios.GetJSON(cs_grade).GetArrayJSON('DADOS').Add(loNovoRegistro);
end;
Botão "Remover". PEX ao sair:
const
cs_grade = '1';
var
liIdRegistro: Integer;
loRegistro: TJSONObject;
loNovoRegistro : TJSONObject;
begin
//Percorre a grade
for liIdRegistro := 0 to Pred(aoFormularios.GetJSON('1').GetArrayJSON('DADOS').Count) do
begin
loRegistro := aoFormularios.GetJSON('1').GetArrayJSON('DADOS').GetItemAsJson(liIdRegistro);
//Verifica o valor digita se for correspondente ao Registro que contém na grade e remove.
if loRegistro.GetInt('REGISTROS') = aoFormularios.GetJSON('3').GetInt('TEXTO') then
begin
aoFormularios.GetJSON('1').GetArrayJSON('DADOS').Remove(liIdRegistro);
end;
end;
end;
No portal:
Lembrando que neste exemplo, fizemos apenas uma demonstração da utilização de PEX em grade, pode-se ser utilizado de várias validações e personalizações. Faça à vontade.
Versão homologada: 12.3.5
PEX - Mensagem e Abortar
Neste tópico veremos como criar as mensagem ou abortar uma atividade de um processo em execução. As mensagens são muito importante para informar operações que estão sendo feitas ou erros que podem ter acontecido.
Ao selecionar o campo Retorno no lado esquerdo do PEX, abrira as Opções de Mensagem e Abortar operação:
Assim que escolher a opção que atenda sua necessidade, se for mensagem irá mostrar os seguintes itens que podem ser escolhidos.
- Adicionar mensagem de informação : Pode ser usado quando quer mostrar alguma informação para o usuário:
- Adicionar mensagem de alerta: Usando quando é necessário informar alguma mensagem de alerta para o usuário. Geralmente sua cor muda para dar ênfase em sua mensagem.
- Adicionar mensagem de erro: Usado quando o usuário deve ser alertado de algum erro na execução do processo. Geralmente é acompanhado da função "Abordar operação"
- Definir timeout da mensagem: Com esta opção sendo utilizada nas mensagens, elas irão aparecer e sumir automaticamente de acordo com o tempo definido na configuração.
E a Abortar operação : Geralmente essa opção é utilizada juntamente com a mensagem de erro. Quando existe um update no código por exemplo. Caso chegue nessa opção a operação vai ser cancelada.
Escolhendo elas dando um duplo clique será inserido na linha selecionada no PEX os códigos padrões das funções.
Você pode alterar a mensagem padrão apenas mudando a informação inserida padrão 'Texto da mensagem'. Além do aborta também é possível interromper a execução do código PEX utilizando o comando: " Exit; "
PEX - Preencher grade com informações de formulários
No DOX existe a possibilidade de inserir registros em uma grade de dados por meio de outros campos da mesma atividade. Para fazer isso, utilizamos o recurso PEX ao sair de um botão, que insere os dados digitados em um campo texto na grade de dados.
Clique aqui para baixar o processo deste exemplo
Neste exemplo, há um campo para digitar a chave de acesso de uma nota fiscal eletrônica, um botão para executar a inserção e a grade, onde será gravado as informações de cada nota informada.
Crie os seguintes formulários:
- Dissertativa "Chave de acesso" - /*CHAVEACESSO*/ (Texto)
- Botão "inserir"
- Grade de dados "Notas informadas" - /*GRADENOTAS*/ (Grade de dados)
No formulário de grade crie as colunas de acordo com a imagem abaixo:
A grade foi configurada com os campos de informações que contém na chave de acesso, incluindo um campo invisível para a chave.
O código PEX foi criado no formulário do tipo Botão. Foi utilizado recurso para percorrer o registro de grade para evitar duplicidade de registros, algumas validações das informações da chave de acesso e o recurso de inserir novo registro em grade.
const
csChave = '1'; //código do formulário da chave de acesso
csGrade = '3'; //código do formulário da grade de notas
var
lsChave : string;
lsCnpj : string;
lsSerie : string;
lsNumeroNf : string;
lsEmissao : string;
liIdRegistro: Integer;
loRegistro: TJSONObject;
loNovoRegistro : TJSONObject;
liSequencia : Integer;
// declara as variáveis e seus respectivos tipos
begin
//verifica se chave está completa
if Length(aoFormularios.GetJSON(csChave).GetStr('TEXTO')) <> 44 then
begin
aoMensagem.SetStr('MENSAGEM', 'Erro! Chave inválida.');
aoMensagem.SetStr('TIPO', 'ERRO');
aoMensagem.SetInt('TIMEOUT', 6000);
exit;
end;
lsChave := aoFormularios.GetJSON(csChave).GetStr('TEXTO');
//verifica se a nota já foi inserida
for liIdRegistro := 0 to Pred(aoFormularios.GetJSON('3').GetArrayJSON('DADOS').Count) do
begin
loRegistro := aoFormularios.GetJSON('3').GetArrayJSON('DADOS').GetItemAsJson(liIdRegistro);
if loRegistro.GetStr('CHAVE') = lsChave then
begin
aoMensagem.SetStr('MENSAGEM', 'Erro! Nota já inserida.');
aoMensagem.SetStr('TIPO', 'ERRO');
aoMensagem.SetInt('TIMEOUT', 6000);
exit;
end;
end;
//verifica se CNPJ é válido
if not TSTR.Testa_CNPJ(copy(lsChave, 7, 14)) then
begin
aoMensagem.SetStr('MENSAGEM', 'Erro! CNPJ inválido.');
aoMensagem.SetStr('TIPO', 'ERRO');
aoMensagem.SetInt('TIMEOUT', 6000);
exit;
end;
lsEmissao := copy(lsChave, 5, 2) + '/20' +
copy(lsChave, 3, 2);
//verifica se a data é válida
if (StrToDate('01/' + lsEmissao) > date) or (StrToInt(copy(lsEmissao, 1, 2)) > 12) then
begin
aoMensagem.SetStr('MENSAGEM', 'Erro! Data de emissão inválida.');
aoMensagem.SetStr('TIPO', 'ERRO');
aoMensagem.SetInt('TIMEOUT', 6000);
exit;
end;
//formata CNPJ
lsCnpj := copy(lsChave, 7, 2) + '.' +
copy(lsChave, 9, 3) + '.' +
copy(lsChave, 12, 3) + '/' +
copy(lsChave, 15, 4) + '-' +
copy(lsChave, 19, 2);
//retira zero da frente do número da nota e da série
lsSerie := IntToStr(StrToInt(copy(lsChave, 23, 3)));
lsNumeroNf := IntToStr(StrToInt(copy(lsChave, 26, 9)));
//incrementa sequencial
liSequencia := 1;
for liIdRegistro := 0 to Pred(aoFormularios.GetJSON('3').GetArrayJSON('DADOS').Count) do
begin
loRegistro := aoFormularios.GetJSON('3').GetArrayJSON('DADOS').GetItemAsJson(liIdRegistro);
if loRegistro.GetInt('SEQUENCIA') >= liSequencia then
begin
liSequencia := loRegistro.GetInt('SEQUENCIA')+1;
end;
end;
//insere informações na grade
loNovoRegistro := TJSONObject.Create;
loNovoRegistro.AddPair('NUMERONF', lsNumeroNf);
loNovoRegistro.AddPair('SERIE', lsSerie);
loNovoRegistro.AddPair('EMISSAO', lsEmissao);
loNovoRegistro.AddPair('CNPJEMITENTE', lsCnpj);
loNovoRegistro.AddPair('CHAVE', lsChave);
loNovoRegistro.AddPair('SEQUENCIA', IntToStr(liSequencia));
aoFormularios.GetJSON(csGrade).GetArrayJSON('DADOS').Add(loNovoRegistro);
//limpa campos de chave de acesso
aoFormularios.GetJSON(csChave).SetStr('TEXTO', '');
end;
PEX - Preencher grade com informações de um SELECT (varias linhas)
Em alguns casos, é necessário retornar dados de uma consulta SQL em uma grade de dados na mesma tela que o filtro é acionado. Neste caso, podemos fazer um evento PEX ao sair de um campo (ex: botão) para fazer um SELECT e jogar o resultado em uma grade de dados, linha por linha.
Clique aqui para baixar o processo deste exemplo
Neste exemplo, temos a opção de consultar pedidos de um vendedor selecionado. Ao selecionar o vendedor em um campo vinculado à um conector FK, e clicar em Buscar, estes pedidos aparecerão na grade de dados.
Antes de tudo, vamos criar a FK que irá trazer os vendedores para o usuário selecionar. Para isso, utilize o seguinte comando SQL para criar uma VIEW:
CREATE OR REPLACE VIEW REPRE_PEDIDO AS
SELECT UPPER(CLI.RAZAO) AS RAZAO,
PEDIDO.IDVENDREPRE
FROM PEDIDO
JOIN CLIFOREMP CLI ON CLI.IDCLIFOREMP = PEDIDO.IDCLIFOREMP
** Lembrando que, neste exemplo, foi utilizado uma base que continham muitos registros de ERP, dados nas tabelas de pedidos e cliforemp. Caso você não tenha uma base assim, mude o SQL para trazer ouros dados existentes.
Em seguida acesse o módulo de Processos > Conectores > Novo [F2]
Ao criar um novo conector, escolha o tipo "Foreing Key" e faça conforme na imagem abaixo:
Depois de criado, basta dar um OK para salvar as informações e vamos para a criação do procedimento. Acesse o módulo de processos novamente > Processos de negócio (BPM) > Novo[F2]
Coloque as informações do novo procedimento e vamos para a criação dos formulários.
- Dissertativa - /*IDVENDEDOR*/ (inteiro)
Neste campo iremos adicionar a FK criada para selecionar os vendedores. Na aba "propriedades" desse campo, procure pelo título que colocamos VENDEDOR. Lembrando que, o sistema automaticamente coloca o prefixo "FK" antes da descrição:
- Botão com descrição "Buscar".
- Grade de dados com descrição "Pedidos". - /*GRADEPEDIDOS*/
Faça como na imagem abaixo, criando as colunas de acordo:
Depois de criado, salve as alterações e o processo.
Agora, vamos adicionar o evento PEX ao sair do botão "Buscar"
const
cs_vendedor = '1'; // código do formulário fk
cs_grade = '3'; // código do formulário de grade
var
locds : TLibCDS;
lsSQL : string;
liVendedor, liIdRegistro: Integer;
loNovoRegistro: TJSONObject;
begin
for liIdRegistro:= Pred(aoFormularios.GetJSON(cs_grade).GetArrayJSON('DADOS').Count) downto 0 do
begin
aoFormularios.GetJSON(cs_grade).GetArrayJSON('DADOS').Remove(liIdRegistro);
end;
liVendedor := aoFormularios.GetJSON(cs_vendedor).GetInt('TEXTO');
if liVendedor > 0 then
begin
lsSQL := 'SELECT ''N'' AS SELECAO, PEDIDO.IDPEDIDO, UPPER(CLI.RAZAO) AS RAZAO, PEDIDO.VALORTOTALCALCULADO TOTAL '+
'FROM PEDIDO JOIN CLIFOREMP CLI ON (CLI.IDCLIFOREMP = PEDIDO.IDCLIFOREMP) '+
'WHERE PEDIDO.DATA >= ''2019-05-01'' AND PEDIDO.DATA <= ''2022-04-04'' '+
'AND PEDIDO.STATUS IN (1) AND PEDIDO.SITUACAO IN (2) AND PEDIDO.IDVENDREPRE = '+ IntToStr(liVendedor);
loCDS := of_CriaCDSporSQL(lsSQL);
try
if locds.of_TemDados() then
begin
loCDS.of_IniciaWhile;
try
loCDS.of_Primeiro;
while not loCDS.of_FimDS do
begin
loNovoRegistro := TJSONObject.Create;
loNovoRegistro.AddPair('SELECAO', locds.GetBol(['SELECAO']));
loNovoRegistro.AddPair('IDPEDIDO', locds.GetInt(['IDPEDIDO']));
loNovoRegistro.AddPair('RAZAO', locds.GetStr(['RAZAO']));
loNovoRegistro.AddPair('TOTAL', Currtostr(locds.GetCurr(['TOTAL'])));
aoFormularios.GetJSON(cs_grade).GetArrayJSON('DADOS').Add(loNovoRegistro);
loCDS.of_ProxReg;
end;
finally
loCDS.of_FinalizaWhile;
end;
end;
finally
loCDS.Free;
end;
end
else
begin
aoMensagem.SetStr('MENSAGEM', 'Favor informar um vendedor.');
aoMensagem.SetStr('TIPO', 'ERRO');
aoMensagem.SetInt('TIMEOUT', 5000);
aoMensagem.SetInt('FOCO', StrToInt(cs_vendedor));
end;
end;
** No SQL colocamos uma condicional de data, pois em nossa base existiam muitos registros, então foi adicionado no WHERE para filtrar a consulta e trazer as informações mais rápidas.
Caso queria se conectar em outras bases o comando loCDS := of_CriaCDSporSQL(lsSQL); basta informar colocando uma virgula ao lado do lsSQL o código do conector que faz a conexão com a outra base.
- Exemplo : loCDS := of_CriaCDSporSQL(lsSQL,32);
PEX - Remove caracteres e mantem somente números
Abaixo veremos comandos de evento PEX para extrair de qualquer string somente os números, ou seja, poderá atender a diversos casos como exemplo:
- Remoção de caracteres especiais de telefones, mantendo somente os números.
- Remoção de caracteres especiais de CPF/CPNJ, mantendo somente os números.
- Seleção do numero do endereço, em caso de campos onde o número é digitado junto ao nome da rua.
- Tratamento para CEP, Placas e outros mantendo somente os números.
Procedure EXEMPLO(const aiAtividadeAtual : Integer; aoFormularios : TJSONObject; const aoVariaveis : TVariaveisEventoFormulario; const aoMensagem : TJSONObject);
var
Ind : Integer;
Result : String;
begin
Result := '';
FOR Ind := 1 to Length('TESTE123teste - evento 1 - PeX') do
begin
if TSTR.of_TemNumero(Copy('TESTE123teste - evento 1 - PeX',Ind,1)) then
begin
Result := Result + Copy('TESTE123teste - evento 1 - PeX', Ind, 1);
end;
end;
end;
- String = 'TESTE123teste - evento 1 - PeX'
- Result = '1231'
Explicação do código:
- Vamos utilizar apenas 2 variáveis, uma para índice do tipo inteiro e outra para o nosso resultado to tipo String.
- No primeiro momento, limpamos por garantia a variável Result para começar a receber os dados, após iniciamos nosso FOR estabelecendo que o índice(Ind) começará de 1 ou seja, do primeiro caractere, e percorrerá até o Length da nossa string ou seja, até seu comprimento total ( no nosso exemplo = 13).
- A cada "ciclo" do FOR ele irá verificar utilizando o comando COPY se aquela posição do índice(Ind) especifica é um numeral ou não:
- COPY('String', Posição, Numero Caracteres) - Ou seja, o COPY analisará o corpo da nossa String a partir da posição especificada, e percorrerá o numero de caracteres informado.
- Nos casos que essa condição for verdadeira, ou seja, o caracter for numérico, o mesmo será adicionado ao Result, caso contrario não, e o índice(ind) passará para a próxima posição.
Lembre-se de buscar sempre entender o código, assim podendo trata-los em diversas situações e variações, por exemplo, sua string pode vir de variáveis do processo, variáveis PEX, variáveis do sistema, consultas SQL, formulários e outros, assim como a utilização da resposta obtida no Result poderá ser aplicada em qualquer tipo de destino.
PEX - SELECT
Em certas regras, é necessário fazer uma consulta no banco de dados ao sair de um campo. Por exemplo, ao informar um CEP, o sistema verifica se o CEP está cadastrado nas tabelas locais e retorna uma mensagem para o usuário caso já esteja.
Uma opção para facilitar um pouco a digitação do código é utilizar o assistente como na imagem abaixo. Clicando com o botão direito do mouse acima da tela do PEX, em Modelo de código, clicar em Criar DataSet por SQL.
Isto é criado automático:
A variável loCDS terá que ser declarada acima do begin. (loCDS : TlibCDS;) Abaixo, o exemplo de verificação de CEP descrito neste tópico.
Use a estrutura abaixo e modifique o SQL como desejar, só não esqueça de NÃO tirar o loCDS.free, ele é responsável por "destruir" o objeto criado, uma boa prática.
const
csCEP = '2';
csEndereco = '3';
var
loCDS : TlibCDS;
lsSQL : String;
begin
lsSQL := 'SELECT ENDERECO FROM CLIFOREMP WHERE CEP = ' + TSTR.Aspa(aoFormularios.GetJSON(csCEP).GetStr('TEXTO'));
loCDS := of_CriaCDSporSQL(lsSQL);
try
if loCDS.of_TemDados() then
begin
aoFormularios.GetJSON(csEndereco).SetStr('TEXTO', loCDS.GetStr('ENDERECO'));
end
else
begin
aoMensagem.SetStr('MENSAGEM', 'Endereço não encontrado em nossa base!');
aoMensagem.SetStr('TIPO', 'ERRO');
aoMensagem.SetInt('TIMEOUT', 5000);
aoFormularios.GetJSON(csEndereco).SetStr('TEXTO', '');
end;
finally
loCDS.Free;
end;
end;
PEX - Setando variável de sistema em formulário
Neste conteúdo vamos mostrar como, em um evento ao clicar de um botão, setar o valor de uma variável de sistema em formulários do processo.
Neste exemplo de uso, foi criado um processo com dois botões, um para setar os valores, e outro para limpar os campos.
Formulários criados:
- Setando valores (Botão - Texto)
- Limpar (Botão - Texto)
- Username (Dissertativa - Texto)
- Idusuário (Dissertativa - Inteiro)
- Data (Dissertativa - Data)
- Hora (Dissertativa - Hora)
- Processo assunto (Dissertativa - Texto)
- Nome do processo (Dissertativa - Texto)
- Idprocesso (Dissertativa - Inteiro)
Configure os tamanhos dos campos como achar melhor.
Agora, no botão "Setando valores" vamos criar um evento que ao clicar, sete os valores das variáveis de sistema nos formulários que criamos. Copie o código ou utilize o assistente para configurar o PEX:
const
cs_data = '2';
cs_hora = '3'; // Aqui declaramos as constantes
cs_user = '6';
cs_iduser = '7';
cs_assunto = '4';
cs_nome = '5';
cs_idProcesso = '8';
begin
// Aqui modificamos os formulários para receberem os valores das variáveis de sistema
aoFormularios.GetJSON(cs_data).SetDt('TEXTO', aoVariaveis.GetDt('/*DATAATUAL*/'));
aoFormularios.GetJSON(cs_hora).SetStr('TEXTO', aoVariaveis.GetStr('/*HORAATUAL*/'));
aoFormularios.GetJSON(cs_user).SetStr('TEXTO', aoVariaveis.GetStr('/*USERNAME*/'));
aoFormularios.GetJSON(cs_iduser).SetInt('TEXTO', aoVariaveis.GetInt('/*IDUSUARIO*/'));
aoFormularios.GetJSON(cs_assunto).SetStr('TEXTO', aoVariaveis.GetStr('/*PROCESSOASSUNTO*/'));
aoFormularios.GetJSON(cs_nome).SetStr('TEXTO', aoVariaveis.GetStr('/*PROCESSONOME*/'));
aoFormularios.GetJSON(cs_idProcesso).SetInt('TEXTO', aoVariaveis.GetInt('/*IDPROCESSO*/'));
end;
Demonstação:
Agora, no botão "Limpar", para limpar os campos. Copie o código ou utilize o assistente para configurá-los:
const
cs_data = '2';
cs_hora = '3';
cs_user = '6'; // Aqui declaramos as constantes
cs_iduser = '7';
cs_assunto = '4';
cs_nome = '5';
cs_idProcesso = '8';
begin
// Aqui limpamos os campos
aoFormularios.GetJSON(cs_data).SetStr('TEXTO', '');
aoFormularios.GetJSON(cs_hora).SetStr('TEXTO', '00:00');
aoFormularios.GetJSON(cs_user).SetStr('TEXTO', '');
aoFormularios.GetJSON(cs_iduser).SetInt('TEXTO', 0);
aoFormularios.GetJSON(cs_assunto).SetStr('TEXTO', '');
aoFormularios.GetJSON(cs_nome).SetStr('TEXTO', '');
aoFormularios.GetJSON(cs_idProcesso).SetInt('TEXTO', 0);
end;
Demonstração:
Por fim, o processo no portal:
PEX - Usando coluna (Verdadeiro / Falso) da grade
Neste fórum faremos um exemplo de selecionar um registro da grade de dados usando uma coluna de verdadeiro/falso, e na próxima atividade mostrar em tela as informações da linha selecionada.
Verifique o passo a passo abaixo:
- Criamos a atividade 1 com um formulário do tipo grade de dados; (Variável /*G_REGISTROS*/)
- As colunas criadas foram:
- CHECK - Tipo "Verdadeiro/Falso" (Visível e obrigatório)
- FANTASIA - Tipo "Texto" (Visível e somente leitura)
- EMAIL - Tipo "Texto" (Visível e somente leitura)
- Criamos um evento de "Estrutura de repetição - carregar" na primeira atividade para trazer as informações na grade de dados. (Colunas "Fantasia" e "email" da tabela CLIFOREMP). Dessa forma, sempre que for iniciado o processo irá aparecer na grade os registros para selecionar.
- Criamos um evento PEX ao sair da primeira atividade para validar a quantidade de registros selecionados e atribuir o valor do email para uma variável do processo.
Código:
const
cs_grade = '1';
var
liIdRegistro, Contador: Integer;
loRegistro: TJSONObject;
begin
Contador := 0;
for liIdRegistro := 0 to Pred(aoFormularios.GetJSON(cs_grade).GetArrayJSON('DADOS').Count) do
begin
loRegistro := aoFormularios.GetJSON(cs_grade).GetArrayJSON('DADOS').GetItemAsJson(liIdRegistro); // Aqui percorremos a grade
if loRegistro.GetBol('CHECK') then // Se o campo "Check" estiver marcado
begin
Contador:= Contador + 1; // Passa pelo contador
end;
if Contador > 1 then // Se houver mais de um registro marcado
begin
aoMensagem.SetStr('MENSAGEM', 'Selecione apenas um registro!'); // Mensagem de informação e aborte caso a condição for verdadeira
aoMensagem.SetStr('TIPO', 'INFO');
aoMensagem.SetBol('ABORTA', True);
end;
if ((Contador = 1) and (loRegistro.GetBol('CHECK'))) then // Se for apenas um registro marcado e a coluna "Check" estiver selecionada
begin
aoVariaveis.SetStr('/*EMAIL*/', loRegistro.GetStr('EMAIL')); // Atribui o email selecionado da grade para a variável EMAIL
end;
end;
end;
- Por fim, na atividade 2 apenas criamos um formulário dissertativo contendo a variável com o valor do email.
PEX - Utilizando campo adicional da FK
Neste tópico iremos adicionar, por meio de uma evento ao sair do campo (PEX), uma informação do campo adicional de uma FK em um formulário dissertativo.
** Campo adicional são os demais campos da tabela (ou view) fora o código ou descrição que o modelador deseja utilizar no procedimento. Atualmente o sistema mostrar apenas o ID + DESCRIÇÃO para o usuário na FK, com os campos adicionais as demais informações que a tabela puxa podem ser utilizados no procedimento.
Pode-se utilizar uma tabela sem problemas, mas neste exemplo vamos criar a view colaboradores para a FK.
CREATE OR REPLACE VIEW PUBLIC.VW_COLABORADORES AS
SELECT C.IDCLIFOREMP AS ID,
C.RAZAO AS NOME,
U.IDUSUARIO AS USERID,
U.USERNAME,
U.IDPAPELFUNCAOPRINCIPAL AS IDPAPELPRINCIPAL,
F.DESCRICAO AS BPM_DESCRICAO
FROM USUARIO U
JOIN CLIFOREMP C ON C.IDCLIFOREMP = U.IDCLIFOREMP
JOIN BPM_FUNCAO F ON F.IDBPMFUNCAO = U.IDPAPELFUNCAOPRINCIPAL
WHERE U.INATIVO = 'N'::BPCHAR;
Este exemplo foi feito em um banco de dados PostgreSQL. Se necessário, personalize o SQL para o seu respectivo banco.
Crie um conector do tipo FK onde puxa as informações dessa view, de acordo com a imagem abaixo:
Agora no processo, crie quatro formulários para serem preenchidos de acordo com o campo adicional.
- Colaborador com a variável /*COLABORADOR*/
- Username com a variável /*USERNAME*/
- Bpm descrição com a variável /*BPM_DESCRICAO*/
- Userid com variável /*USERID*/
**Configure o tamanho dos campos como achar melhor
No primeiro formulário (colaborador) vincule a Fk que criamos (Fk_vw_colaboradores) e salve o processo. Volte para o formulário e vamos criar nosso evento PEX ao sair.
const
cs_username = '2'; // Aqui nós declaramos as constantes
cs_userid = '3';
cs_bpm = '4';
begin
// Aqui nós modificamos os formulários do processo
aoFormularios.GetJSON(cs_username).SetStr('TEXTO', aoValoresFK.GetStr('USERNAME'));
aoFormularios.GetJSON(cs_userid).SetInt('TEXTO', aoValoresFK.GetInt('USERID'));
aoFormularios.GetJSON(cs_bpm).SetStr('TEXTO', aoValoresFK.GetStr('BPM_DESCRICAO'));
end;
Copie esse código ou use o assistente:
Com este PEX, os valores que estão no campo adicional da FK, serão preenchidos nos demais formulários.
Demonstração no portal:
PEX - Validação de campo somente leitura (obrigatório)
Neste exemplo temos um formulário somente leitura, porém ao sair vamos verificar se o mesmo está vazio. Se sim, o sistema não deixará o usuário prosseguir. É um recurso utilizado quando é um campo somente leitura, porém preenchido por uma FK por exemplo. E seu valor muda de acordo com o que foi escolhido.
Caso ele esteja vazio, ao sair a operação de prosseguir será abortada,
Código PEX:
const
cs_formulario2 = '2';
begin
if aoFormularios.GetJSON(cs_formulario2).GetStr('TEXTO') = '' then
begin
aoMensagem.SetStr('MENSAGEM', 'O campo ' + aoFormularios.GetJSON(cs_formulario2).GetStr('DESCRICAO') +' não foi preenchido, verifique o mesmo!' );
aoMensagem.SetStr('TIPO', 'ERRO');
aoMensagem.SetBol('ABORTA', True);
end;
end;
Assim que clicar em "próximo" será mostrado a mensagem avisando que o campo não foi preenchido.
Versão Homologada: 12.5.1
PEX - Validar campo de data ao sair do formulário
Em algumas situações, é necessário que um campo de data seja validado ao sair de uma atividade. Uma data de nascimento, por exemplo, precisaríamos fazer um tratamento para não aceitar datas posteriores a data atual.
Exemplo
Neste exemplo, o usuário informa o nome e a data de nascimento de uma pessoa e ao sair da atividade, o processo verifica se a data é maior que a data atual. Se essa condição for verdadeira, o usuário não conseguirá confirmar o processo.
Fluxo:
Pra começar é necessário criar em seu processo, uma atividade com os campos dissertativos "Nome" e "Data".
Ainda na aba Eventos, criar um evento PEX ao sair que será o responsável por definir que a data de nascimento não seja maior que a data atual, e se for, mostrar uma mensagem de erro, abortando assim, o processo iniciado. Confira na imagem abaixo
begin
if aoFormularios.GetJSON('2').GetDt('TEXTO') > Date then // Condição se a data for maior que a data atual
begin
aoMensagem.SetStr('MENSAGEM', 'Erro! Data não pode ser maior que data atual'); // mostra erro na tela
aoMensagem.SetStr('TIPO', 'ERRO');
aoMensagem.SetBol('ABORTA', True); // aborta ação do botão de "confirmar" a atividade
end;
end;
Validando campos ou dados via PEX
Este tópico tem a finalidade de abordar os principais validadores presentes no PEX, como bem sabemos, o PEX nas versões maiores que 12 possuem um assistente PEX , no PEX existem também muitas rotinas e/ou funções publicadas que podem ser acessadas via menu de contexto, conforme imagem abaixo:
As funções abordadas serão:
TSTR.Testa_CNPJ(Codigo);
TSTR.Testa_CPF(Codigo);
TSTR.of_IsNotNullEMA(asText);
TSTR.of_IsNullEMA(asText);
TSTR.of_VerificaEmail(asTexto, abVerificaNull);
TSTR.of_TemNumero(s);
TSTR.of_TemTexto(s, abValida);
TSTR.of_UFValido(asUF);
TSTR.of_IsCodigoBarra(Codigo);
Valida CNPJ
Esta função deve ser utilizada com uma variável do PEX, variável do processo e/ou formulário do processo afim de validar um CPNJ, retornando False (Booleana) no caso de digitado um CPNJ inválido por engano ou algo do gênero.
var
CNPJ : string;
Resposta : Boolean;
begin
CPNJ := '07297774000175'; //CNPOJ Informado em um formulario e/ou variavel do tipo texto
Resposta := TSTR.Testa_CNPJ(CNPJ); -- Retorna True ou False para varaivel boleana
end;
Valida CPF
Esta função deve ser utilizada com uma variável do PEX, variável do processo e/ou formulário do processo afim de validar um CPF, retornando False (Booleana) no caso de digitado um CPF inválido por engano ou algo do gênero.
var
CPF : string;
Resposta : Boolean;
begin
CPF:= '06972928840'; //CPF Informado em um formulario e/ou variavel do tipo texto
Resposta := TSTR.Testa_CPF(CPF); -- Retorna True ou False para varaivel boleana
end;
Ou, dessa forma:
const
cs_cpf = '7'; //Aqui é informada a ID referente ao campo.
var
lsCPF : string;
begin
lsCPF := aoFormularios.GetJSON(cs_cpf).GetStr('TEXTO');
lsCPF := TSTR.StrTran(lsCPF, '-', '');
lsCPF := TSTR.StrTran(lsCPF, '.', '');
// Esta parte abaixo, formata a mensagem de erro caso aconteça
if (not TSTR.Testa_CPF(lsCPF)) and (aoFormularios.GetJSON(cs_cpf).GetBol('OBRIGATORIO') = true) then
begin
aoMensagem.SetStr('MENSAGEM' , 'CPF INVÁLIDO!');
aoMensagem.SetBol('ABORTA' , True);
aoMensagem.SetInt('FOCO',9);
aoMensagem.SetInt('TIMEOUT', 6000{Milissegundos});
aoMensagem.SetStr('TIPO', 'ERRO');
end;
end;
Verifica se Campo ou Váriavel é null / não preenchido:
IF TSTR.of_IsNotNullEMA(variavelpex) THEN // Como retorna uma boleada, podemos utializar diretamente no IF, assim se a variavel nao for vazia, a condicional será verdadeira.
IF TSTR.of_IsNullEMA(variavelpex) THEN // Como no exemplo acima, neste caso a condicional será verdadeira se a variavel estiver em branco/nula.
Valida E-mail :
Esta função verifica se o e-mail preenchido na variável ou campo é válido, ou seja, possui pelo menos um @ e ponto, e tem a opção de validar ou não se o campo estiver em branco nos casos de e-mail obrigatório:
//Padrão
TSTR.of_VerificaEmail(asTexto, abVerificaNull);
//Exemplo
TSTR.of_VerificaEmail(Variaveldopex, True);
Ou seja, estamos verificando o conteúdo da variável e no segundo paramento estou verificando se não foi deixado em branco.
Como podemos perceber os nomes são bem intuitivos e podem ser facilmente interpretados, em todos os casos citados o retorno da função será uma booleana, ou seja, True/False, abaixo demais exemplos ainda não citados:
TSTR.of_TemNumero(variavel); // Verifica se possuem numeros na variavel.
TSTR.of_TemTexto(variavel); // Verifica se possuem caracteres texto na variavel.
TSTR.of_UFValido(variavel); // Verifica se a UF preenchida é uma UF válida dentro do Brasil.
TSTR.of_IsCodigoBarra(variavel); // Verifica se código de barras é válido.
Webservice - Consulta de CEP via JEX
Neste tópicos veremos como fazer uma requisição do webservice de CEP via JEX. O Webservice que será usado é o da ViaCep, um webservice gratuito que busca todos os CEP's do Brasil.
Conceito:
CEP (Código postal) ou (Código de Endereçamento Postal) é um código desenvolvido pelas administrações postais e criado com o intuito de facilitar a organização logística e localização espacial de um endereço postal.
Acessando o site da ViaCep, você encontra todo a documentação necessária para realizar as requisições JSON, XML, entre outras, nos formatos de retorno.
No print, você pode ver quais os campos criados para puxar as informações conforme o CEP for preenchido. A principal configuração nesse processo, irá partir do primeiro formulário, o campo CEP.
O CEP possui um evento logo na saída do formulário. Ou seja, uma vez que sair desse campo, clicando fora dele ou pressionando enter/tab, uma série de eventos irá acontecer.
O código inserido no PEX basicamente executa: Caso o ViaCep retorne as informações do endereço daquele CEP, então, o valor deles no campo deve ser preenchido, fazendo com que as informações sejam gravadas de forma automática. Claro, apenas caso o CEP exista.
Código JEX:
async function aoSairCampoFormulario() {
if (!formulario.CEP.valor) return;
const cep = formulario.CEP.valor;
const retBuscaCEP = await axios.get('https://viacep.com.br/ws/' + cep + '/json', {} );
formulario.LOGRADOURO.valor = retBuscaCep.data['logradouro']
formulario.COMPLEMENTO.valor = retBuscaCep.data['complemento']
formulario.BAIRRO.valor = retBuscaCep.data['bairro']
formulario.LOCALIDADE.valor = retBuscaCep.data['localidade']
formulario.UF.valor = retBuscaCep.data['uf']
formulario.IBGE.valor = retBuscaCep.data['ibge']
formulario.GIA.valor = retBuscaCep.data['gia']
formulario.DDD.valor = retBuscaCep.data['ddd']
formulario.SIAFI.valor = retBuscaCep.data['siafi']
}
Webservice - Consulta de CEP via PEX
Neste tópicos veremos como fazer uma requisição do webservice de CEP via PEX.
O Webservice que será usado é o da ViaCep, webservice gratuito que busca todos os CEP's do Brasil.
Conceito:
CEP - Código postal ou Código de Endereçamento Postal é um código desenvolvido pelas administrações postais e criado com o intuito de facilitar a organização logística e localização espacial de um endereço postal.
Acessando o site da ViaCep você encontra todo a documentação necessária para realizar as requisições JSON, XML e etc.
Retorno JSON:
https://viacep.com.br/ws/88811578/json <-- Exemplo do retorno
{
"cep": "88811-578",
"logradouro": "Rua Leandro Martignago",
"complemento": "", "bairro": "Pio Corrêa",
"localidade": "Criciúma",
"uf": "SC",
"unidade": "",
"ibge": "4204608",
"gia": ""
}
No exemplo a seguir será feito um processo com os campos que o Webservice retorna por exemplo, logradouro, complemento, localidade, uf, unidade, ibge e gia.
Criando o Processo.
1° - Crie um formulário do tipo Dissertativa e vincule uma variável por exemplo /*CEP*/ do tipo texto para aplicar a mascará de CEP.
Neste formulário marque a opção Ao sair e logo após nos três ponto na direta:
Logo após insira o código conforme a imagem a seguir:
const
cs_logradouro = '2' ;
cs_complemento = '3' ;
cs_bairro = '4' ;
cs_localidade = '5' ; // <--- ID dos formularios
cs_uf = '6' ;
cs_ibge = '7' ;
cs_gia = '8' ;
cs_unidade = '9' ;
var
lsCorpoRequisicao,
lscaminhoURL,
Result,
lsCEP,
lsSQL : String;
loRESTClient : TRESTClient;
loRESTRequest : TRESTRequest; // <-- Variaveis essenciais para o funcionamento.
loRESTResponse : TRESTResponse;
loAuthBasica : THTTPBasicAuthenticator;
lojson : tjsonobject;
loCDS : Tlibcds;
begin
lsCEP := aoFormularios.GetJSON('1').GetStr('TEXTO'); // <-- Jogando o valor do campo para a variavel.
//inicia integração com web service
Result := '';
loRESTClient := nil;
loRESTRequest := nil;
loRESTResponse := nil;
lscaminhoURL := 'http://www.viacep.com.br/ws/'+lsCEP+'/json/'; //<-- Inserindo o valor do campo no link para buscar o JSON
if length(lsCEP) > 1 then
begin
//executa o requisição
try
loRESTClient := TRESTClient .Create(nil);
loRESTRequest := TRESTRequest .Create(nil);
loRESTResponse := TRESTResponse.Create(nil);
loRESTClient.RaiseExceptionOn500 := False;
loRESTClient.BaseURL := lscaminhoURL; //<-- Cria os objetos e conexões necessarias.
loRESTClient.ContentType := '';
loRESTRequest.Method := rmGet;
loRestRequest.Resource := '';
loRESTRequest.Client := loRESTClient;
loRESTRequest.Response := loRESTResponse;
try
loRESTRequest.Execute;
except
end;
if TSTR.of_IsNotNullEma(loRESTResponse.JSONText) then
begin
Result := loRESTResponse.JSONText;
lojson := tjson.op_StringToJSon(loRESTResponse.JSONText);
aoFormularios.GetJSON(cs_logradouro).SetStr('TEXTO', lojson.GetStr('logradouro'));
aoFormularios.GetJSON(cs_complemento).SetStr('TEXTO', lojson.GetStr('complemento'));
aoFormularios.GetJSON(cs_bairro).SetStr('TEXTO', lojson.GetStr('bairro'));
aoFormularios.GetJSON(cs_localidade).SetStr('TEXTO', lojson.GetStr('localidade'));
aoFormularios.GetJSON(cs_uf).SetStr('TEXTO', lojson.GetStr('uf')); //<-- Caso tenha algo no JSON retorna preenche nos campos.
aoFormularios.GetJSON(cs_unidade).SetStr('TEXTO', lojson.GetStr('unidade'));
aoFormularios.GetJSON(cs_ibge).SetStr('TEXTO', lojson.GetStr('ibge'));
aoFormularios.GetJSON(cs_gia).SetStr('TEXTO', lojson.GetStr('gia'));
end
else
begin
Result := loRESTResponse.Content;
end;
finally
loAuthBasica .Free;
loRESTClient .Free; //<-- Obrigatorio para destruir as variaveis e não ficar com lixo de memoria.
loRESTRequest .Free;
loRESTResponse.Free;
end;
end
else
begin
aoMensagem.SetStr('MENSAGEM','Para poder efetuar a busca, você precisa informar um CEP válido');
aoMensagem.SetInt('FOCO',43);
aoMensagem.SetStr('TIPO','INFO'); // <-- Caso o valor seja invalido.
aoMensagem.SetInt('TIMEOUT',6000);
end;
// Abaixo faz a validação dos campos, caso tenha algum valor preenchido pelo fica somente leitura se não fica editavel para ser preenchido
aoFormularios.GetJSON(cs_logradouro).SetBol('SOMENTELEITURA', aoFormularios.GetJSON(cs_logradouro).GetStr('TEXTO') <> '' );
aoFormularios.GetJSON(cs_complemento).SetBol('SOMENTELEITURA', aoFormularios.GetJSON(cs_complemento).GetStr('TEXTO') <> '');
aoFormularios.GetJSON(cs_bairro).SetBol('SOMENTELEITURA', aoFormularios.GetJSON(cs_bairro).GetStr('TEXTO') <> '');
aoFormularios.GetJSON(cs_localidade).SetBol('SOMENTELEITURA', aoFormularios.GetJSON(cs_localidade).GetStr('TEXTO') <> '');
aoFormularios.GetJSON(cs_uf).SetBol('SOMENTELEITURA', aoFormularios.GetJSON(cs_uf).GetStr('TEXTO') <> '');
aoFormularios.GetJSON(cs_unidade).SetBol('SOMENTELEITURA', aoFormularios.GetJSON(cs_unidade).GetStr('TEXTO') <> '');
aoFormularios.GetJSON(cs_ibge).SetBol('SOMENTELEITURA', aoFormularios.GetJSON(cs_ibge).GetStr('TEXTO') <> '');
aoFormularios.GetJSON(cs_gia).SetBol('SOMENTELEITURA', aoFormularios.GetJSON(cs_gia).GetStr('TEXTO') <> '');
end;
Não é necessário ter todos os campos, você pode adapta-lo a sua necessidade. Os campos quando não tem nenhum valor retornado pelo JSON ficam editáveis para ser preenchidos manualmente.
Versão Homologada : 12.6
Webservice - Consulta de CNPJ via PEX
Bastante semelhante ao conteúdo Webservice - Consulta CEP via PEX, neste exemplo ao digitar o CNPJ e clicar no botão "Consulta", o processo trará todas as informações referentes à aquele CNPJ.
Formulário com evento ao sair no campo do tipo Botão.
PEX com webservice no botão de consulta.
const
cs_cnpj = '1' ;
cs_nome = '2' ;
cs_uf = '4' ;
cs_bairro = '5' ;
cs_logradouro = '6' ;
cs_numero = '7' ;
cs_cep = '8' ;
cs_cidade = '9' ;
cs_complemento = '10';
cs_telefone = '11';
var
lsCorpoRequisicao,
lscaminhoURL,
Result,
lsCNPJ,
lsSQL : String;
loRESTClient : TRESTClient;
loRESTRequest : TRESTRequest;
loRESTResponse : TRESTResponse;
loAuthBasica : THTTPBasicAuthenticator;
lojson : tjsonobject;
loCDS : Tlibcds;
begin
//limpa campos
aoFormularios.GetJSON(cs_nome).SetStr('TEXTO', '');
aoFormularios.GetJSON(cs_uf).SetStr('TEXTO', '');
aoFormularios.GetJSON(cs_bairro).SetStr('TEXTO', '');
aoFormularios.GetJSON(cs_logradouro).SetStr('TEXTO', '');
aoFormularios.GetJSON(cs_numero).SetStr('TEXTO', '');
aoFormularios.GetJSON(cs_cep).SetStr('TEXTO', '');
aoFormularios.GetJSON(cs_cidade).SetStr('TEXTO', '');
aoFormularios.GetJSON(cs_complemento).SetStr('TEXTO', '');
aoFormularios.GetJSON(cs_telefone).SetStr('TEXTO', '');
//verifica se campo CNPJ está vazio
if TSTR.EliminaAlfha(aoFormularios.GetJSON(CS_CNPJ).GetStr('TEXTO')) <> '' then
begin
//retira caracteres especiais do CNPJ
lsCNPJ := aoFormularios.GetJSON(CS_CNPJ).GetStr('TEXTO');
lsCNPJ := TSTR.StrTran(lsCNPJ, '-', '');
lsCNPJ := TSTR.StrTran(lsCNPJ, '.', '');
lsCNPJ := TSTR.StrTran(lsCNPJ, '/', '');
//verifica se é um CNPJ válido
if not TSTR.Testa_CNPJ(lsCNPJ) then
begin
aoMensagem.SetStr('MENSAGEM' , 'Erro! CNPJ: ' + aoFormularios.GetJSON(CS_CNPJ).GetStr('TEXTO') + ' inválido.');
aoMensagem.SetInt('FOCO',43);
aoMensagem.SetStr('TIPO','ERRO');
aoMensagem.SetInt('TIMEOUT',6000);
aoFormularios.GetJSON(cs_cnpj).SetStr('TEXTO', '');
Abort;
end;
end;
//verifica se CNPJ está cadastrado no banco de dados
loCDS := of_CriaCDSporSQL(Format('SELECT C.FANTASIA, F.NOME UNIDADE FROM CLIFOREMP C JOIN CLIENTE CLI ON CLI.IDCLIFOREMP = C.IDCLIFOREMP JOIN FILIAL F ON F.IDFILIAL = CLI.IDFILIALRELACAO '+
'WHERE C.TIPOCLIFOREMP = 0 AND C.CNPJ = %0:s',[TSTR.Aspa(lsCNPJ)]));
try
if loCDS.of_TemDados then
begin
aoMensagem.SetStr('MENSAGEM','CNPJ '+ aoFormularios.GetJSON(CS_CNPJ).GetStr('TEXTO') +' já cadastrado para o cliente: ' + loCDS.GetStr(['FANTASIA']) + ' na Unidade: ' + loCDS.GetStr(['UNIDADE']));
aoMensagem.SetInt('FOCO',43);
aoMensagem.SetStr('TIPO','ERRO');
aoMensagem.SetInt('TIMEOUT',6000);
aoFormularios.GetJSON(CS_CNPJ).SetStr('TEXTO', '');
Abort;
end;
finally
loCDS.FREE;
end;
//inicia integração com web service
Result := '';
loRESTClient := nil;
loRESTRequest := nil;
loRESTResponse := nil;
lscaminhoURL := 'http://www.receitaws.com.br/v1/cnpj/'+lsCNPJ;
if length(lsCNPJ) > 1 then
begin
//executa o requisição
try
loRESTClient := TRESTClient .Create(nil);
loRESTRequest := TRESTRequest .Create(nil);
loRESTResponse := TRESTResponse.Create(nil);
loRESTClient.RaiseExceptionOn500 := False;
loRESTClient.BaseURL := lscaminhoURL;
loRESTClient.ContentType := '';
loRESTRequest.Method := rmGet;
loRestRequest.Resource := '';
loRESTRequest.Client := loRESTClient;
loRESTRequest.Response := loRESTResponse;
try
loRESTRequest.Execute;
except
end;
if TSTR.of_IsNotNullEma(loRESTResponse.JSONText) then
Result := loRESTResponse.JSONText
else
Result := loRESTResponse.Content;
//aoMensagem.SetStr('MENSAGEM',lscaminhoURL);
lojson := tjson.op_StringToJSon(loRESTResponse.JSONText);
//preenche campos com as informações retornadas do web service
aoFormularios.GetJSON( cs_nome ).SetStr('TEXTO',lojson.GetStr('nome'));
aoFormularios.GetJSON( cs_uf ).SetStr('TEXTO',lojson.GetStr('uf'));
aoFormularios.GetJSON( cs_bairro ).SetStr('TEXTO',lojson.GetStr('bairro'));
aoFormularios.GetJSON( cs_logradouro ).SetStr('TEXTO',lojson.GetStr('logradouro'));
aoFormularios.GetJSON( cs_numero ).SetStr('TEXTO',lojson.GetStr('numero'));
aoFormularios.GetJSON( cs_cep ).SetStr('TEXTO',lojson.GetStr('cep'));
aoFormularios.GetJSON( cs_cidade ).SetStr('TEXTO',lojson.GetStr('municipio'));
aoFormularios.GetJSON( cs_complemento ).SetStr('TEXTO',lojson.GetStr('complemento'));
aoFormularios.GetJSON( cs_telefone ).SetStr('TEXTO',lojson.GetStr('telefone'));
{lsSQL := 'select idcidade from cidade where upper(REMOVE_CHAR_ESP(cidade.descricao)) = ' + TSTR.Aspa(lojson.GetStr('municipio')) + ' and cidade.uf = ' + TSTR.Aspa(lojson.GetStr('uf'));
//aoMensagem.SetStr('MENSAGEM',lsSQL);
loCDS := of_CriaCDSporSQL(lsSQL);
try
if loCDS.of_TemDados then
begin
aoFormularios.GetJSON( cs_uf ).SetStr('TEXTO',lojson.GetStr('uf'));
aoFormularios.GetJSON(CS_CIDADE ).SetStr('TEXTO',loCDS.GetStr(['IDCIDADE']));
end;
finally
loCDS.FREE;
end;}
finally
loAuthBasica .Free;
loRESTClient .Free;
loRESTRequest .Free;
loRESTResponse.Free;
end;
end
else
begin
aoMensagem.SetStr('MENSAGEM','Para poder efetuar a busca, você precisa informar um CNPJ válido');
aoMensagem.SetInt('FOCO',43);
aoMensagem.SetStr('TIPO','INFO');
aoMensagem.SetInt('TIMEOUT',6000);
aoFormularios.GetJSON(cs_cnpj).SetStr('TEXTO', '');
end;
end;
JEX - Função gerar planilha
Essa melhoria foi implementada na versão 17.8.3.613 do sistema DOX, importante verificar se a versão está devidamente instalada antes de seguir instruções ou testar o código abaixo.
** Além da adição interna da lib SheetJS no serviço JS Sandbox, foi adicionado na aba 'Assistente' de telas de evento em JavaScript, o novo item 'Planilhas (SheetJS)', com as opções:
- Gerar planilha OpenDocument: Vai inserir código base para geração de uma planilha ODS em um arquivo.
- Gerar planilha Microsoft: Vai inserir código base para geração de uma planilha XLS em um arquivo.
Também foi adicionado em 'Documentações' o link para mais detalhes sobre a lib SheetJS (XLSX), assim como acesso direto no menu de complementação do código (CTRL + Barra de espaço).
Em anexo, está o processo utilizado de teste para validar a funcionalidade. Abaixo, está o código JEX:
const clientes = await conexaoBD.executarConsultaSQL("select idcliforemp, razao from cliforemp where tipocliforemp = 0 and inativo = 'N'");
const fornecedores = await conexaoBD.executarConsultaSQL("select idcliforemp, razao from cliforemp where tipocliforemp = 1 and inativo = 'N'");
const empregados = await conexaoBD.executarConsultaSQL("select idcliforemp, razao from cliforemp where tipocliforemp = 2 and inativo = 'N'");
const executivesResponse = await axios.get('https://sheetjs.com/data/executive.json');
const executives = executivesResponse.data.map(row => ({
name: row.name.first + " " + row.name.last,
birthday: row.bio.birthday
}));
const abaClientes = XLSX.utils.json_to_sheet(clientes, { skipHeader:true });
const abaFornecedores = XLSX.utils.json_to_sheet(fornecedores);
XLSX.utils.sheet_add_aoa(abaFornecedores, [["Código", "Razão"]], { origin: "A1" });
const abaEmpregados = XLSX.utils.json_to_sheet(empregados);
XLSX.utils.sheet_add_aoa(abaEmpregados, [["Código", "Razão"]], { origin: "A1" });
const abaExecutives = XLSX.utils.json_to_sheet(executives);
const planilha = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(planilha, abaClientes, "Clientes");
XLSX.utils.book_append_sheet(planilha, abaFornecedores, "Fornecedores");
XLSX.utils.book_append_sheet(planilha, abaEmpregados, "Empregados");
XLSX.utils.book_append_sheet(planilha, abaExecutives, "Executives");
const arquivoXLSX = 'C:/Planilha/Pessoas.xlsx';
await fsPromises.rm(arquivoXLSX, { force: true });
XLSX.writeFile(planilha, arquivoXLSX, { compression: true });
const arquivoODS = 'C:/Planilha/Pessoas.ods';
await fsPromises.rm(arquivoODS, { force: true });
XLSX.writeFile(planilha, arquivoODS, { compression: true, bookType: 'ods' });
mensagem.texto = 'Todas as planilhas exportadas com sucesso!';