PEX / JEX / Expressões BPM

Documentação e tutoriais relacionado à PEX / JEX / Expressões BPM

Rotinas / Funções Delphi

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;
Rotinas / Funções Delphi

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;
Rotinas / Funções Delphi

Copy

Copia parte do texto de uma variável para outra. Recebe 3 parâmetros, sendo eles, em ordem:

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;
Rotinas / Funções Delphi

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

Rotinas / Funções Delphi

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;
Rotinas / Funções Delphi

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');
Rotinas / Funções Delphi

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;
Rotinas / Funções Delphi

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;
Rotinas / Funções Delphi

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;
Rotinas / Funções Delphi

Max / Min

Retorna o maior/menor valor entre dois números.

Exemplo:

begin
  Label1.Caption := Max(1,2);
  Label2.Caption := Min(1,2);
end;
Rotinas / Funções Delphi

Now

Retorna a data e hora correntes.

Exemplo:

begin
 ShowMessage('Data e hora atuais: ',DateTimeToStr(Now));
end;
Rotinas / Funções Delphi

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

Rotinas / Funções Delphi

Time

Retorna a hora corrente.

Exemplo:

begin
 ShowMessage('Hora atual: ',TimeToStr(Time));
end;
Rotinas / Funções Delphi

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:

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;
Rotinas / Funções Delphi

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: 

image-1640278005002.png

No canto esquerdo o sistema apresenta: 

image-1640278322705.png

image-1640278512669.png

image-1640278659452.png

Informações adicionais: 

Abaixo está disponibilizado um vídeo para explicar sobre o funcionamento dos novos recursos.

 

JEX

Para mais informações sobre JS JEX, clique aqui.

image-1640283424113.png

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: 

image-1640285422528.png

 As novas funções abaixo são referentes ao tipos de opções que o JEX pode utilizar no código. 

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:

Esse procedimento foi feito tanto comparando pelo ID, quanto pela descrição.

image-1640611068965.gif

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.

image-1640611972058.png

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.

image-1640612647602.png

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.

image-1640614713088.png

[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.

image-1640614954477.png

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.

image-1640614998811.png

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.

image-1640615352358.png

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: 

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:image-1640292248032.png

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:

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:

Exemplos nesse formato:

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:

image-1640292444178.png

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:

image-1640292546549.png

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:

image-1640292641471.png

Agora:

image-1640292664543.png

As seguintes alterações foram feitas: 

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:

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:

image-1640292781397.png

image-1640292799128.png

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:

image-1640292989027.png

Esse formulário possui as seguintes regras:

De que forma podemos expressar matematicamente essas regras?
O campo “Em aberto”:

O campo “Troco”:

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”:

image-1640293022346.png

Já na imagem abaixo, as expressões do campo “Troco”:

image-1640293053079.png

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;

Não funciona

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.

Na maioria dos navegadores as ferramentas do desenvolvedor podem ser acessadas através do botão F12.
Os erros das expressões são logados apenas no console do navegador, ou seja: no log do processo, que é acessível pelo Estúdio, esses erros não vão estar disponíveis para visualização.

Os exemplos de  mensagem de erro abaixo:

image-1640293252439.png

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:

image-1640293290790.png

image-1640293312263.png

image-1640293337488.png

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:

image-1640293524913.png

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:

image-1640293551328.png

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:

Má prática:

 

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: 

image-1640263139450.png

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:

  1.  Grade de Dados
  2.  Botão (Adicionar)
  3.  Dissertativa (N°/Índice)
  4.  Botão (Remover) 

image-1640616915882.png

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: 

image-1640617020645.gif

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. 

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. 

image-1640633546996.png

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.

image-1640636061218.png

  1. Ctrl + G → Vai focar no campo 'Ir para a linha' setando a linha focada. 
  2. Ctrl + ';' → Comentar ou descomentar. 
  3. Tab + texto sendo selecionado em múltiplas linhas → Indentação 2 caracteres). 
  4. F5 → Validar código fonte. 
  5. Ctrl + S → Salvar alterações. 
  6. 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)'

image-1640636594671.png

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.

Possibilita o acesso a todas as variáveis de processo e de sistema. Exemplo: 

image-1640638250923.png

Observação: Não é possível alterar o valor de variáveis de sistema. 

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.

Exemplo: 

image-1640638503845.png

Possibilita o acesso direto aos valores de cada coluna do registro atual da grade, no padrão → valoresRegistroAtual.NOMECAMPO. 
Exemplo:

image-1640638578688.png

Observação: Disponível apenas para o evento ao sair de um campo da grade. 

Possibilita definir uma mensagem para aparecer na tela de colaboração da atividade. 
Propriedades: 

Exemplo: 

image-1640895135348.png

Observação: As propriedades dos objetos da atividade (no padrão → recurso.IDENTIFICADOR.propriedade) foram padronizadas para serem acessadas em CamelCase.

Outros detalhes importantes

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:

image-1640896503293.png

Crie uma Fk trazendo o IDPROCESSO e DESCRIÇÃO da CRM_PROCESSO. 

image-1640896520994.png

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: 

image-1640896636782.png

image-1640896659283.png

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: 

image-1641213310282.png

Lembre-se, o ROWNUM é utilizado em bancos ORACLE, para PostgreSQL utilize o LIMIT = 31 depois do FROM por exemplo:

image-1641213371172.png

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.

image-1640262238192.gif

Código PEX aplicado : 

image-1640262271193.png

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:

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

image-1640624301764.png

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

image-1640625041311.png

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.

image-1640625316311.png

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:

image-1640625520013.png

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:

image-1640627453700.png

Criação dos formulários de data e grade de dados:

image-1640627497607.png

Inserindo o PEX ao sair do botão: 

image-1640627605949.png

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.

image-1640207308682.png

Com o botão direito do Mouse, procurei em "Sistema operacional" e em seguida em "AbreSite".

image-1640207363279.png

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: 

const
   URL='https://portal.ema.net.br/'; 
begin
     TSO.AbreSite(URL);   
end;
begin
   TSO.AbreSite('https://ema.net.br/forum/');
end;

O processo no portal:

image-1640207470220.gif

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:

Conforme imagem do portal: 

image-1648835988844.png

 

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.

image-1648836436618.png

Na aba "Campos": 

image-1648836469659.png

Agora, depois de criado o formulário de grade, crie os outros formulários de botão e dissertativa.

image-1648836266579.png

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:

image-1649076492249.gif

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.

image-1640263505495.png

Ao selecionar o campo Retorno no lado esquerdo do PEX, abrira as Opções de Mensagem Abortar operação: 

image-1640263554715.png

Assim que escolher a opção que atenda sua necessidade, se for mensagem irá mostrar os seguintes itens que podem ser escolhidos.

image-1640263825356.png

image-1640263976190.png

image-1640264162297.png

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.

image-1640263693358.png

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.

image-1649170072682.jpg

Crie os seguintes formulários:

image-1649245013531.png

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.

image-1649245074939.png

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.

image-1649106853361.gif

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 outros dados existentes. 

Em seguida acesse o módulo de Processos > Conectores > Novo [F2] 

image-1649160686693.png

Ao criar um novo conector, escolha o tipo "Foreing Key" e faça conforme na imagem abaixo:

image-1649160959719.png

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.

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: 

image-1649164236454.png

Faça como na imagem abaixo, criando as colunas de acordo: 

image-1649165007525.png

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.

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:

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;
Explicação do código:

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.

image-1640264955340.png

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.

image-1640265028220.png

Isto é criado automático: 

image-1640265087232.png

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:

Configure os tamanhos dos campos como achar melhor.

image-1640261206872.png

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: 

image-1640261286571.gif

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: 

image-1640261376171.gif

Por fim, o processo no portal: 

image-1640261423821.gif

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:

image-1640627901228.png

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;

image-1640628986818.png

image-1640629010965.gif

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. 

image-1640623402610.png

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:

image-1640622910132.png

Agora no processo, crie quatro formulários para serem preenchidos de acordo com o campo adicional.

**Configure o tamanho dos campos como achar melhor 

image-1640622966981.png

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:

image-1640623041912.gif

Com este PEX, os valores que estão no campo adicional da FK, serão preenchidos nos demais formulários.
Demonstração no portal:

image-1640623463476.gif

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.

image-1648495979847.png

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.

image-1648465958543.png

Fluxo: 

image-1647367366213.jpg

Pra começar é necessário criar em seu processo, uma atividade com os campos dissertativos "Nome" e "Data".

image-1647367410677.jpg

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:

image-1640261677944.png

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.

image-1640259727719.png

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.

image-1640259764635.png

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.

image-1640259794721.png

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.

image-1640260099791.png

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.

image-1640260129434.png

Neste formulário marque a opção Ao sair  e logo após nos três ponto na direta:

image-1640260153130.png

Logo após insira o código conforme a imagem a seguir:

image-1640260195858.png

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;

image-1640260301004.gif

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.

image-1640260776228.png

Formulário com evento ao sair no campo do tipo Botão.

image-1640260809718.png

PEX com webservice no botão de consulta.

image-1640260830927.png

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: 

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!';