01. INTRODUÇÃO/OBJETIVO

Temos como objetivo, implementar técnicas para facilitar a personalização de telas TOTVS - Linha Datasul de forma lowcode, apenas com cadastro de campos por parte do cliente.

02. VISÃO GERAL

A partir da release 12.1.31, são disponibilizados as técnicas e cadastros para implementar a personalização em telas HTML da linha Datasul.

Nesta técnica de personalização, o desenvolvedor deverá cadastrar os campos a serem personalizados, criar um componente em PO-UI que utilize o PO-DYNAMIC-VIEW e criar um endpoint progress que servirá como fonte de dados para os campos personalizados.

03. PRÉ-REQUISITOS

Para utilização desta técnica será necessário ter um conhecimento de desenvolvimento com: Progress, Angular, TypeScript e PO-UI.

04. TÉCNICAS

A Técnica de personalização de telas HTML com PO-UI contempla os seguintes objetos:

1) Endpoint progress do Framework - Serve para obter a lista de campos personalizados, que devem ser cadastrados na tela de Personalização em HTML;

Neste item, deverá ser utilizado o endpoint progress /api/btb/v1/personalizationView/metadata/ + código_do_programa ,onde deve ser passado o Código do Programa Datasul que conterá a lista de campos personalizados.

Exemplo: Se formos criar uma personalização para o programa pedido-execucao-monitor, chamaremos o endpoint "/api/btb/v1/personalizationView/metadata/pedido-execucao-monitor"

2) Endpoint progress da área de negócio - Serve para obter os dados que serão apresentados nos campos personalizados;

3) Criação de um componente com PO-UI, pela área de negócio, que utilize o componente PO-DYNAMIC-VIEW. Nele será utilizado a lista de campos personalizados e os dados a serem apresentados..

Explicando o funcionamento da técnica:

O componente criado pela área de negócio com o PO-DYNAMIC-VIEW deverá fazer duas requisições ao servidor REST, que são:

1) Para buscar os valores dos dados a serem apresentados, onde poderá ser passados o código do programa personalizado e também um id do "registro corrente" para obtenção dos valores.

2) Para buscar a lista de campos personalizados utilizando o endpoint progress fornecido pelo framework, que é o /api/btb/v1/personalizationView/metadata/ + codigo_do_programa.


05. EXEMPLO DE UTILIZAÇÃO

Cadastro de campos personalizados

A seguir são apresentados as telas necessárias para a realização do cadastro dos campos personalizados.

Ao localizar no menu o programa "Campos personalizados (html.personalization-metadata)", é apresentada uma tela em formato de 'lista' que conterá todos os campos (metadados) cadastrados no produto Datasul. Para cadastrar um campo que será utilizado na personalização, basta clicar no botão +Adicionar.


A tela a seguir apresenta o cadastro do 'metadado' relacionado a um campo que pode ser apresentado no programa como personalizado.

CampoDescriçãoObrigatório
Código Programa Datasul

Código do programa "base" que podem ser aplicadas as técnicas de personalização

É possível cadastrar os campos somente em programas que permitem a personalização

Sim

Identificador Campo

Identificador único do campo (por programa), necessário para a geração da tela personalizada (código do campo)Sim

Nome Campo

Nome do campo que será apresentado na tela (label do campo)

Caso o campo não seja informado, o nome do campo apresentado será o informado no identificador.

Não
Tipo Campo

Tipo do campo cadastrado

Caso o campo não seja informado, será considerado que o campo é do tipo string

Tipos de campos permitidos:

Tipo JavaScriptTipo ProgressDescrição
booleanlogicalValores lógicos
currencydecimalValores monetários
datedate

Valores de datas. 

Aceita os tipos string e Date padrão do Javascript, por exemplo: '2017-11-28' ou new Date(2017, 10, 28).

datetimedatetime

Valor de data com horário.

Aceita o tipo string no formato ISO-8601 estendido 'yyyy-mm-ddThh:mm:ss+|-hh:mm' e o tipo Date padrão

numberintegerValores numéricos
stringcharacterTextos

Sugerimos a utilização do facilitador progress "com.totvs.framework.api.JsonAPIUtils", que faz a conversão dos tipos da dados do progress

para tipo de dados JavaScript e contêm o método abaixo :

Assinatura:

convertAblTypeToHtmlType (INPUT cType AS CHARACTER)

Exemplo:

ASSIGN cType = JsonAPIUtils:convertAblTypeToHtmlType ("integer").

O retorno no cType será "number", que é um formato reconhecido pelo PO-UI.

Não
Validação Campo

Caso o campo possuir alguma validação de máscaras, restrição de valores, é necessário informar neste campo (máscara de formatação do campo)

Exemplos:

Em um campo personalizado CNPJ, utilizamos o formato de exibição '99.999.999/9999-99'.

Não
Somente LeituraOpção para que o campo seja apresentado como 'somente leitura' (torna o campo readOnly)Sim
Habilita personalizaçãoOpção para habilitar ou desabilitar a apresentação da personalização por campo (torna o campo visível)Sim


Após cadastrar o campo, o mesmo é apresentado na tela inicial onde pode ser realizado filtros sobre seus resultados, bem como efetuar ações de edição ou exclusão.


Ao clicar na opção de editar, não será possível modificar o código do programa Datasul vinculado e também seu identificador. Os demais campos estão habilitados para edição.


Componente HTML com PO-DYNAMIC-VIEW

Abaixo temos os códigos da parte HTML, que são:

1) Componente HTML (personalization.component.html)

<po-loading-overlay
  [hidden]="!showLoading">
</po-loading-overlay>

<po-page-detail
  p-title="Personalização"
  [p-breadcrumb]="breadcrumb"
  (p-edit)="editClick()"
  (p-back)="goBackClick()">
  <po-dynamic-view
    [p-fields]="fields"
    [p-value]="record">
  </po-dynamic-view>
</po-page-detail>


2) Componente TypeScript (personalization.component.ts)

import { Component, OnInit } from '@angular/core';
import { PoBreadcrumb, PoBreadcrumbItem, PoNotificationService } from '@po-ui/ng-components';

import { PersonalizationService } from './personalization.service';

@Component({
  selector: 'app-personalization',
  templateUrl: './personalization.component.html',
  styleUrls: ['./personalization.component.css']
})

export class PersonalizationComponent implements OnInit {
  // definicao das variaveis utilizadas
  public currentId: string;
  public fields: Array<any> = [];
  public record = {};
  public showLoading = false;
  public cProg = 'pedido-execucao-monitor';

  public breadcrumb: PoBreadcrumb = { items: [] };
  public breadcrumbItem: PoBreadcrumbItem;

  // construtor com os servicos necessarios
  constructor(
    private service: PersonalizationService,
    private poNotification: PoNotificationService
  ) { }

  // load do componente
  public ngOnInit(): void {
    this.showLoading = true;
    this.record = {};
    // busca os valores dos dados a serem apresentados
    this.service.loadValues(this.cProg, 'esp').subscribe(resp => {
      console.log(resp);
      Object.keys(resp).forEach((key) => this.record[key] = resp[key]);
      // carrega a lista de campos somente apos receber o registro a ser apresentado
      if (this.fields === null || this.fields.length === 0) {
        this.service.loadMetadata(this.cProg).subscribe(metadata => {
          // tslint:disable-next-line:no-string-literal
          this.fields = metadata['fields'];
          this.showLoading = false;
        });
      }
    });

    this.setBreadcrumb();
  }

  private setBreadcrumb(): void {
    this.breadcrumbItem = { label: 'Home', link: '/' };
    this.breadcrumb.items = this.breadcrumb.items.concat(this.breadcrumbItem);
    this.breadcrumbItem = { label: 'Personalização' };
    this.breadcrumb.items = this.breadcrumb.items.concat(this.breadcrumbItem);
  }

  // Redireciona quando clicar no botao Edit
  public editClick(): void {
    this.poNotification.information('Voce clicou na edição dos dados');
  }

  // Redireciona quando clicar no botao Voltar
  public goBackClick(): void {
    this.poNotification.information('Voce clicou para retornar para a tela anterior');
  }
}


3) Componente de serviço (personalization.service.ts)

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { PoNotificationService } from '@po-ui/ng-components';

const httpOptions: object = {
  headers: new HttpHeaders({
    'Content-Type': 'application/json',
  })
};

@Injectable({
  providedIn: 'root'
})
export class PersonalizationService {
  // Endpoint progress do framework para obtencao da lista de campos personalizados
  private urlMetadata = '/api/btb/v1/personalizationView/metadata/';

  // Endpoint progress da area de negocio para obtencao dos valores dos campos personalizados
  private urlArea = '/api/trn/v1/idiomaValues/';

  private fieldList: Array<any> = [];

  constructor(
    private http: HttpClient,
    private poNotification: PoNotificationService,
  ) { }

  public loadMetadata(cProg) {
    return this.http.post<any[]>(this.urlMetadata + cProg, httpOptions).pipe();
  }

  public loadValues(cProg, cId) {
    return this.http.get<any[]>(this.urlArea + cProg + '/' + cId, httpOptions).pipe();
  }

  public getUrlArea(): string {
    return this.urlArea;
  }
}


Endpoint em Progress da área de negócio

Abaixo temos o endpoint progress que deverá ser criado pela área de negócio para obtenção dos valores a serem apresentados nos campos personalizados.

4) Endpoint progress da área de negócio para obtenção dos valores dos campos personalizados (idiomaValues.p)

{utp/ut-api.i}

{utp/ut-api-action.i pGetData GET /~* }

{utp/ut-api-notfound.i}

/** Procedure que retorna os valores **/
PROCEDURE pGetData:
    DEFINE INPUT  PARAMETER oJsonInput  AS JsonObject NO-UNDO.
    DEFINE OUTPUT PARAMETER oJsonOutput AS JsonObject NO-UNDO.

    DEFINE VARIABLE oRequest   AS JsonAPIRequestParser NO-UNDO.
    DEFINE VARIABLE oResponse  AS JsonAPIResponse      NO-UNDO.
    DEFINE VARIABLE oObj       AS JsonObject           NO-UNDO.
    DEFINE VARIABLE cProg      AS CHARACTER            NO-UNDO.
    DEFINE VARIABLE cId        AS CHARACTER            NO-UNDO.

    oObj = NEW JsonObject().

    // Le os parametros enviados pela interface HTML
    oRequest = NEW JsonAPIRequestParser(oJsonInput).
    
    // Obtem o programa e o codigo do registro corrente
    cProg = oRequest:getPathParams():getCharacter(1) NO-ERROR.
    cId = oRequest:getPathParams():getCharacter(2) NO-ERROR.

    LOG-MANAGER:WRITE-MESSAGE("getData - cProg = " + cProg, ">>>>>").
    LOG-MANAGER:WRITE-MESSAGE("getData - cId = " + cId, ">>>>>").

    FIND FIRST prog_dtsul
        WHERE prog_dtsul.cod_prog_dtsul = cProg
        NO-LOCK NO-ERROR.
    LOG-MANAGER:WRITE-MESSAGE("getData - prog found = " + string(AVAILABLE prog_dtsul), ">>>>>").
    IF  AVAILABLE prog_dtsul THEN 
        LOG-MANAGER:WRITE-MESSAGE("getData - permite personalizacao = " + string(prog_dtsul.log_permite_perzdo), ">>>>>").
        
    IF  AVAILABLE prog_dtsul 
    AND prog_dtsul.log_permite_perzdo = TRUE THEN DO:
        FIND FIRST idioma 
            WHERE idioma.cod_idioma = cId
            NO-LOCK NO-ERROR.
        LOG-MANAGER:WRITE-MESSAGE("getData - idioma found = " + string(AVAILABLE idioma), ">>>>>").
        IF  AVAILABLE idioma THEN DO:
            oObj:add("codIdioma", idioma.cod_idioma).
            oObj:add("desIdioma", idioma.des_idioma).
            oObj:add("codIdiomPadr", idioma.cod_idiom_padr).
        END.
        LOG-MANAGER:WRITE-MESSAGE("getData - oObj = " + String(oObj:getJsonText()), ">>>>>").
    END.

    oResponse   = NEW JsonAPIResponse(oObj).
    oJsonOutput = oResponse:createJsonResponse().
END PROCEDURE.

/* fim */

Caso seja enviado valores da área de negócio que não estejam cadastrados como campos personalizados, o PO-UI por padrão adicionará essa informação extra na tela, onde será apresentado como String sem um label válido.

Tela do componente HTML com o resultado da personalização


06. LINKS ÚTEIS

Documentação API Datasul:

PO-UI:

GIT Projeto:

07. CONCLUSÃO

A ideia era apresentar uma técnica de construção para a personalização de um programa TOTVS da Liinha Datasul, de forma segura e simples.

Esta documentação trata-se de um MVP, que está sendo continuamente evoluída em nossas Sprints (SQUAD TOOLS).


<!-- esconder o menu --> 


<style>
div.theme-default .ia-splitter #main {
    margin-left: 0px;
}
.ia-fixed-sidebar, .ia-splitter-left {
    display: none;
}
#main {
    padding-left: 10px;
    padding-right: 10px;
    overflow-x: hidden;
}

.aui-header-primary .aui-nav,  .aui-page-panel {
    margin-left: 0px !important;
}
.aui-header-primary .aui-nav {
    margin-left: 0px !important;
}
</style>