Painel |
---|
borderColor | lightgrey |
---|
bgColor | #F0F0F0 |
---|
borderWidth | 2 |
---|
borderStyle | dashed |
---|
|
|
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 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á realizar o cadastro dos campos a serem personalizados e criar alguns componentes em PO-UI que utilizem os componentes: PO-DYNAMIC-FORM, PO-DYNAMIC-VIEW e PO-PAGE-DYNAMIC-TABLE (este último somente se for necessário). Deve-se também implementar endpoint em Progress
no , o qual será utilizado como fonte de dados para os campos personalizados.
03. PRÉ-REQUISITOS
Para utilização desta técnica será necessário ter um possuir conhecimento de desenvolvimento com: APIs REST em 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:
Endpoint Progress do Framework
Retorna a lista de campos personalizados , que devem ser previamente 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: Implementação de personalização para o programa pedido-execucao-monitor, deve-se utilizar o endpoint "/api/btb/v1/personalizationView/metadata/pedido-execucao-monitor":
Endpoint Progress que devem ser implementados na "área de negócio"
Deve retornar os dados que serão apresentados
Endpoint Progress que devem ser implementados na "área de negócio"
Deve retornar os dados que serão apresentados nos campos personalizados;
Utilitário facilitador no Progress
Foi implementado no Progress um utilitário o utilitário - btb/personalizationUtil.p , com - com seu include btb/personalizationUtil.i, que deve ser utilizado para retornar à área de negócio a lista de campos personalizáveis de um determinado programa, cujo o intuito é facilitar a implementação para que seja enviado somente os valores dos campos personalizáveis.
Devido a característica do PO-UI dinâmico, caso seja enviado os dados de campos que não estão na lista de campos, o PO-UI irá apresentar o valor do campo com uma label com o mesmo nome do campo. Com a obtenção da lista de campos , pode-se evitar o envio de informações que estão fora da lista de campos personalizados.
Todos os componentes dinâmicos do PO-UI realizam, no mínimo, duas requisições REST, uma para obter a lista de campos personalizáveis e outra para obter os dados a serem apresentados nesses campos.
Abaixo temos o papel de cada componente dinâmico que podemos utilizar:
PO-DYNAMIC-FORM
: Para a edição e criação de um novo registro personalizado;
PO-DYNAMIC-VIEW
: Para a visualização do registro personalizado.
Dica |
---|
|
Pode-se implementar também um componente PO-PAGE-DYNAMIC-TABLE , para a navegação dos registros e permitir a visualização e edição dos mesmos. |
Em nossa técnica, em todas as requisições REST, será enviado:
- Para buscar a lista de campos personalizados utilizando o endpoint Progress fornecido pelo framework, que é o /api/btb/v1/personalizationView/metadata/ + codigo_do_programa;
- O código do programa personalizado e também um id do "registro corrente" para para obtenção dos valores, necessários para buscar os valores dos dados a serem apresentados.
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.
Image Removed
A tela a seguir apresenta o cadastro do 'metadado' relacionado a um campo que pode ser apresentado no programa como personalizado.
Image Removed
Para alguns tipos de campo, é possível informar uma lista de opções apresentadas em tela, para isso basta informar os dados (chave, valor).
Image Removed
Campo | Descrição | Obrigatório |
---|
Código Programa Datasul | Código do programa "base" que podem ser aplicadas as técnicas de personalização Nota |
---|
| É 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 Character. Tipos de campos permitidos: Tipo | Descrição |
---|
Checkbox | Valores lógicos, apresentado na interface como um componente de Switch. | Números | Valores numéricos, não sendo permitido a inserção de caracteres. | Data | Valores de datas, apresentado na interface como DatePicker. | Hora | Valor do horário, apresentado máscara '99:99' por padrão. | Character | Valores alfanuméricos. | Moeda | Valores monetários. | TextArea | Valores alfanuméricos, apresentado na interface em um box com 3 linhas. | Senha | Valores alfanuméricos, apresentados na interface como Password. | Radio Group | Lista de seleção apresentadas no formato de 'radio group', permitido no máximo 3 opções. | Checkbox Group | Lista de seleção múltipla apresentadas no formato de 'checkbox group', permitido no máximo 3 opções. | Select | Lista de seleção apresentadas no formato de 'select', é obrigatório cadastrar ao menos 4 opções. | Multiselect | Lista de seleção múltipla apresentadas no formato de 'select', é obrigatório cadastrar ao menos 4 opções. | Combobox | Lista de seleção apresentadas no formato de 'combobox', é obrigatório informar o atributo optionsService para resgatar os dados da lista). |
| Não |
Lista Opções | Informa a lista de opções a serem apresentadas em conjunto com o tipo do componente especificado. Informações |
---|
| Esta opção está disponível somente para os tipos Radio Group, Checkbox Group, Select e Multiselect, |
| Somente Leitura | Opção para que o campo seja apresentado como 'somente leitura' (torna o campo readOnly) | Sim |
Habilita personalização | Opçã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 de campos, inclusão de atributos e exclusão de campos.
Image Removed
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.
Image Removed
Atributos de campos personalizados
Com a configuração de atributos dos campo personalizado, é possível adicionar outras características tais como:
Ao renderizar os campos personalizados em tela, esses atributos serão inclusos no campo personalizado e enviados para tela.
Aviso |
---|
|
Os nomes dos atributos devem ser os mesmos que estão documentados nas propriedades do componente no PO-UI. Exemplo: Para personalizar um campo no formato CPF, criamos o campo COD_CPF e adicionamos um atributo do tipo "mask" que conterá o formato "999.999.999-99". O atributo "mask" corresponde a uma propriedade do componente PO-INPUT. |
Componentes em PO-UI
A seguir, são apresentados exemplos de códigos para a implementação com HTML e TypeScript:
Componente TypeScript (personalization-detail.component.ts)
Expandir |
---|
title | Clique para ver o código... |
---|
|
Bloco de código |
---|
language | delphi |
---|
firstline | 1 |
---|
linenumbers | true |
---|
|
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { PoBreadcrumb, PoBreadcrumbItem } from '@po-ui/ng-components';
import { PersonalizationService } from '../personalization.service';
@Component({
selector: 'app-personalization-detail',
templateUrl: './personalization-detail.component.html',
styleUrls: ['./personalization-detail.component.css']
}) |
Bloco de código |
---|
language | js |
---|
firstline | 12 |
---|
title | Variáveis |
---|
linenumbers | true |
---|
|
export Atributos de campos personalizados
Com a configuração de atributos dos campos personalizados, é possível adicionar outras características tais como:
Ao renderizar os campos personalizados em tela, esses atributos serão inclusos no campo personalizado e enviados para tela.
Aviso |
---|
|
Os nomes dos atributos devem ser os mesmos que estão documentados nas propriedades do componente no PO-UI. Exemplo: Para personalizar um campo no formato CPF, criamos o campo COD_CPF e adicionamos um atributo do tipo mask que conterá o formato 999.999.999-99. O atributo mask corresponde a uma propriedade do componente PO-INPUT. |
Componentes em PO-UI
A seguir, são apresentados exemplos de códigos para a implementação com HTML e TypeScript:
Componente HTML (personalization-detail.component.html)
Expandir |
---|
title | > > > Clique para ver o código... |
---|
|
Bloco de código |
---|
language | powershell |
---|
theme | Confluence |
---|
firstline | 1 |
---|
linenumbers | true |
---|
| <po-loading-overlay
[hidden]="!showLoading">
</po-loading-overlay>
<po-page-detail
p-title="Detalhe do Idioma"
[p-breadcrumb]="breadcrumb"
(p-edit)="editClick()"
(p-back)="goBackClick()"> |
Bloco de código |
---|
language | xml |
---|
firstline | 10 |
---|
title | Trecho da personalização |
---|
linenumbers | true |
---|
| <!-- INICIO CODIGO PERSONALIZAVEL -->
<po-dynamic-view
[p-fields]="fields"
[p-value]="record">
</po-dynamic-view>
<!-- FINAL CODIGO PERSONALIZAVEL -->
|
Bloco de código |
---|
language | xml |
---|
firstline | 18 |
---|
linenumbers | true |
---|
| </po-page-detail |
|
Componente TypeScript (personalization-detail.component.ts)
Expandir |
---|
title | > > > Clique para ver o código... |
---|
|
Bloco de código |
---|
language | delphi |
---|
firstline | 1 |
---|
linenumbers | true |
---|
| import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { PoBreadcrumb, PoBreadcrumbItem } from '@po-ui/ng-components';
import { PersonalizationService } from '../personalization.service';
@Component({
selector: 'app-personalization-detail',
templateUrl: './personalization-detail.component.html',
styleUrls: ['./personalization-detail.component.css']
}) |
Bloco de código |
---|
language | js |
---|
firstline | 12 |
---|
title | Variáveis |
---|
linenumbers | true |
---|
| export class PersonalizationDetailComponent implements OnInit {
public static cProg = 'html.aplicativos-eai';
// definicao das variaveis utilizadas
public currentId: string;
public fields: Array<any> = [];
public record = {};
public showLoading = false;
public breadcrumb: PoBreadcrumb = { items: [] };
public breadcrumbItem: PoBreadcrumbItem; |
Bloco de código |
---|
language | js |
---|
firstline | 23 |
---|
title | Construtor |
---|
linenumbers | true |
---|
| // construtor com os servicos necessarios
constructor(
private service: PersonalizationService,
private activatedRoute: ActivatedRoute,
private route: Router
) { } |
Bloco de código |
---|
language | js |
---|
firstline | 29 |
---|
title | Leitura do componente |
---|
linenumbers | true |
---|
| // load do componente
public ngOnInit(): void {
this.activatedRoute.params.subscribe(pars => {
this.showLoading = true;
this.record = {};
// Carrega o registro pelo ID
// tslint:disable-next-line:no-string-literal
this.currentId = pars['id'];
//---BUSCA OS VALORES DOS CAMPOS PERSONALIZADOS DA AREA DE NEGOCIO ----
// busca os valores dos dados a serem apresentados
this.service.loadValuesById(this.currentId).subscribe(resp => {
Object.keys(resp).forEach((key) => this.record[key] = resp[key]);
// ---BUSCA OS CAMPOS PERSONALIZADOS DO PROGRAMA ----
// carrega a lista de campos personalizados somente apos receber os dados a serem apresentados
this.service.loadMetadata().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: 'Listagem de Idiomas' , link: '/personalization' };
this.breadcrumb.items = this.breadcrumb.items.concat(this.breadcrumbItem);
this.breadcrumbItem = { label: 'Detalhe do Idioma' };
this.breadcrumb.items = this.breadcrumb.items.concat(this.breadcrumbItem);
}
} |
Bloco de código |
---|
language | js |
---|
firstline | 64 |
---|
title | Ação dos botões Editar e Voltar |
---|
linenumbers | true |
---|
| // Redireciona quando clicar no botao Edit
public editClick(): void {
this.route.navigate(['/personalization', 'edit', this.currentId]);
}
// Redireciona quando clicar no botao Voltar
public goBackClick(): void {
this.route.navigate(['/personalization']);
}
} |
|
Componente HTML (personalization-edit.component.html)
Expandir |
---|
title | > > > Clique para ver o código... |
---|
|
Bloco de código |
---|
language | powershell |
---|
theme | Confluence |
---|
firstline | 1 |
---|
linenumbers | true |
---|
| <po-loading-overlay
[hidden]="!showLoading">
</po-loading-overlay>
<po-page-edit
[p-title]="cTitle"
[p-breadcrumb]="breadcrumb"
[p-disable-submit]="formEdit.form.invalid"
(p-cancel)="cancelClick()"
(p-save)="saveClick()"> |
Bloco de código |
---|
language | xml |
---|
firstline | 11 |
---|
title | Trecho da personalização |
---|
linenumbers | true |
---|
| <!-- INICIO CODIGO PERSONALIZAVEL -->
<po-dynamic-form
#formEdit
p-auto-focus="string"
[p-fields]="fields"
[p-validate]="validationUrl"
[p-value]="record">
</po-dynamic-form>
<!-- FINAL CODIGO PERSONALIZAVEL -->
|
Bloco de código |
---|
language | xml |
---|
firstline | 22 |
---|
linenumbers | true |
---|
| </po-page-edit> |
|
Componente TypeScript (personalization-edit.component.ts)
Expandir |
---|
title | > > > Clique para ver o código... |
---|
|
|
powershelljs | theme | Confluence |
---|
firstline | 1 |
---|
linenumbers | true |
---|
| import { Component, OnInit, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { PoBreadcrumb, PoBreadcrumbItem, PoDialogService, PoNotificationService } from '@po-ui/ng-components';
import { PersonalizationService } from './../personalization.service';
@Component({
selector: 'app-personalization-edit',
templateUrl: './personalization-edit.component.html',
styleUrls: ['./personalization-edit.component.css']
}) |
|
xmljs | firstline | 13 |
---|
title | Variáveis |
---|
linenumbers | true |
---|
| export class PersonalizationEditComponent implements OnInit {
// Define as variaveis a serem utilizadas
public cTitle: string;
public currentId: string;
public record = {};
public fields: Array<any> = [];
public isUpdate = false;
public showLoading = false;
public validationUrl = this.service.getUrlAreaValidation();
public breadcrumb: PoBreadcrumb = { items: [] };
public breadcrumbItem: PoBreadcrumbItem;
// Obtem a referencia do componente HTML
@ViewChild('formEdit', { static: true })
formEdit: NgForm;
|
|
xmljs | firstline | 30 |
---|
title | Construtor |
---|
linenumbers | true |
---|
| // Construtor da classe com os servicos necessarios
constructor(
private service: PersonalizationService,
private activatedRoute: ActivatedRoute,
private route: Router,
private poDialog: PoDialogService,
private poNotification: PoNotificationService
) { }
|
|
xmljs | firstline | 38 |
---|
title | Leitura do componente |
---|
linenumbers | true |
---|
| // Load do componente
public ngOnInit(): void {
this.isUpdate = false;
this.showLoading = true;
// Carrega o registro pelo ID
this.activatedRoute.params.subscribe(pars => {
// tslint:disable-next-line:no-string-literal
this.currentId = pars['id'];
// Se nao tiver o ID definido sera um CREATE
if (this.currentId === undefined) {
this.isUpdate = false;
this.cTitle = 'Novo Idioma';
} else {
this.isUpdate = true;
this.cTitle = 'Edição do Idioma';
}
// Atualiza o breadcrumb de acordo com o tipo de edicao
this.setBreadcrumb();
// Se for uma alteracao, busca o registro a ser alterado
if (this.isUpdate) {
// |
|
------- BUSCA OS VALORES DOS CAMPOS PERSONALIZADOS DA AREA DE NEGOCIO |
|
--------
this.service.loadValuesById(this.currentId).subscribe(resp => {
Object.keys(resp).forEach((key) => this.record[key] = resp[key]);
// |
|
------- BUSCA OS CAMPOS PERSONALIZADOS DO PROGRAMA |
|
--------
// Em alteracao temos que receber o registro para depois buscar a lista de campos
this.getMetadata();
});
} else {
// Se for create, pega a lista de campos
this.getMetadata();
}
});
}
private setBreadcrumb(): void {
this.breadcrumbItem = { label: 'Home', action: this.beforeRedirect.bind(this) };
this.breadcrumb.items = this.breadcrumb.items.concat(this.breadcrumbItem);
this.breadcrumbItem = { label: 'Listagem de Idiomas', action: this.beforeRedirect.bind(this) };
this.breadcrumb.items = this.breadcrumb.items.concat(this.breadcrumbItem);
this.breadcrumbItem = { label: this.cTitle };
this.breadcrumb.items = this.breadcrumb.items.concat(this.breadcrumbItem);
}
// Retorna a lista de campos
private getMetadata() {
this.service.loadMetadata().subscribe(metadata => {
// tslint:disable-next-line:no-string-literal
this.fields = metadata['fields'];
this.showLoading = false;
});
}
// Redireciona via breadcrumb
private beforeRedirect(itemBreadcrumbLabel) {
if (this.formEdit.valid) {
this.route.navigate(['/']);
} else {
this.poDialog.confirm({
title: 'Cancelamento de edição',
message: 'Os dados ainda não foram gravados, confirma redirecinamento ?',
confirm: () => this.route.navigate(['/'])
});
}
}
|
|
xmljs | firstline | 107 |
---|
title | Gravação do registro |
---|
linenumbers | true |
---|
| // Grava o registro quando clicado no botao Salvar
public saveClick(): void {
this.showLoading = true;
if (this.isUpdate) {
// Altera um registro ja existente
this.service.update(this.currentId, this.record).subscribe(resp => {
this.poNotification.success('Dados atualizados com sucesso');
this.showLoading = false;
this.route.navigate(['/personalization']);
});
} else {
// Cria um registro novo
this.service.create(this.currentId, this.record).subscribe(resp => {
this.poNotification.success('Dados criados com sucesso');
this.showLoading = false;
this.route.navigate(['/personalization']);
});
}
}
// Cancela a edicao e redireciona ao clicar no botao Cancelar
public cancelClick(): void {
this.poDialog.confirm({
title: 'Confirmar cancelamento',
message: 'Voce deseja realmente cancelar a edição?',
confirm: () => this.route.navigate(['/personalization'])
});
}
} |
|
xmljs | firstline | 136 |
---|
title | Cancelamento da edição |
---|
linenumbers | true |
---|
| // Cancela a edicao e redireciona ao clicar no botao Cancelar
public cancelClick(): void {
this.poDialog.confirm({
title: 'Confirmar cancelamento',
message: 'Voce deseja realmente cancelar a edição?',
confirm: () => this.route.navigate(['/personalization'])
});
}
} |
|
Componente HTML (personalization-list.component.html)
Expandir |
---|
title | > > > Clique para ver o código... |
---|
|
Bloco de código |
---|
language | powershell |
---|
theme | Confluence |
---|
firstline | 1 |
---|
linenumbers | true |
---|
| <po-loading-overlay
[hidden]="!showLoading">
</po-loading-overlay> |
Bloco de código |
---|
language | xml |
---|
firstline | 4 |
---|
title | Trecho da personalização |
---|
linenumbers | true |
---|
| <!-- INICIO CODIGO PERSONALIZAVEL -->
<po-page-dynamic-table
p-auto-router
p-title="Listagem de Idiomas"
[p-actions]="actions"
[p-breadcrumb]="breadcrumb"
[p-fields]="fields"
[p-service-api]="serviceApi">
</po-page-dynamic-table>
<!-- FINAL CODIGO PERSONALIZAVEL -->
|
|
Componente TypeScript (personalization-list.component.ts)
Expandir |
---|
title | > > > Clique para ver o código... |
---|
|
|
powershelljs | theme | Confluence |
---|
firstline | 1 |
---|
linenumbers | true |
---|
| import { Component, OnInit } from '@angular/core';
import { PoBreadcrumb, PoBreadcrumbItem } from '@po-ui/ng-components';
import { PoPageDynamicTableActions } from '@po-ui/ng-templates';
import { PersonalizationService } from './../personalization.service';
@Component({
selector: 'app-personalization-list',
templateUrl: './personalization-list.component.html',
styleUrls: ['./personalization-list.component.css']
}) |
|
xmljs | firstline | 12 |
---|
title | Variáveis |
---|
linenumbers | true |
---|
| export class PersonalizationListComponent implements OnInit {
// Definicao das variaveis utilizadas
public serviceApi: string;
public fields: Array<any> = [];
public showLoading = false;
public literals;
public readonly actions: PoPageDynamicTableActions = {
new: '/personalization/create',
detail: '/personalization/detail/:id',
edit: '/personalization/edit/:id',
remove: true
};
public breadcrumb: PoBreadcrumb = { items: [] };
public breadcrumbItem: PoBreadcrumbItem;
|
|
xmljs | firstline | 30 |
---|
title | Construtor |
---|
linenumbers | true |
---|
| // Construtor da classe
constructor(
private service: PersonalizationService
) { }
|
|
xmljs | firstline | 34 |
---|
title | Leitura do componente |
---|
linenumbers | true |
---|
| // Load do componente
public ngOnInit(): void {
this.fields = [];
this.serviceApi = this.service.getUrlArea();
this.showLoading = true;
// ------- BUSCA OS CAMPOS PERSONALIZADOS DO PROGRAMA --------
this.service.loadMetadata().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: 'Listagem de Idiomas' };
this.breadcrumb.items = this.breadcrumb.items.concat(this.breadcrumbItem);
}
}
|
|
powershellxmlxmltrue | xmlxmlEndpoint em Progress para a área de negócio
A seguir, são apresentados exemplos de endpoint de endpoint em Progress, que deverão ser implementados pela área de negócio para obtenção dos valores a serem apresentados nos campos personalizados.
DEFINE INPUT PARAMETER hTab AS HANDLE NO-UNDO.
DEFINE INPUT PARAMETER oBody AS JsonObject NO-UNDO.
FOR EACH ttPersonalization:
ASSIGN cFld = ttPersonalization.codField
cType = ttPersonalization.codType.
CASE cType:
WHEN"string"THENhTab:BUFFER-FIELD(cFld):buffer-value() = oBody:getCharacter(cFld) NO-ERROR.
WHEN"number"THEN
hTab:BUFFER-FIELD(cFld):buffer-value() = oBody:getInteger(cFld) NO-ERROR.currencygetDecimalbooleangetLogicaldatetimegetDatetime WHEN "date"getDateENDCASE.
END.
END PROCEDURE.
/* fim */
Tela do componente HTML com o resultado da personalização
Tela de Listagem
Image Added
Tela de Criação/Edição
Image Added
Tela de Detalhe/Visualização
Image Added
Aviso |
---|
title | Envio de valores para PO-UI |
---|
|
Caso seja enviado valores da área de negócio que não estejam cadastrados como campos personalizados, o PO-UI, o padrão adicionará essa informação extra na tela, onde será apresentado em formato String sem uma label válida. |