CONTEÚDO
- Cadastrando um novo Monitor exclusivo
- Criando uma nova Api de negócio
- Implementando procedure de modo Gráfico
- Implementando procedure de modo Detalhe
01. CADASTRANDO UM NOVO MONITOR EXCLUSIVO
O primeiro passo para criar o seu Monitor Exclusivo é acessar no menu do ERP Datasul o programa Gestão à Vista - Monitores Exclusivos (ou pelo código html.supply.Monitor.Custom).
Nesta rotina é possível visualizar os monitores Exclusivos já cadastrados e realizar o cadastro de um novo monitor:
Ao clicar em Adicionar, será apresentado um formulário contendo os campos abaixo:
Para o exemplo deste guia, iremos criar um monitor exclusivo para mostrar a quantidade de Ordens de Produção pela sua situação em um determinado período.
Seguindo o cadastro do Monitor, ao informar os campos conforme acima e clicar em Salvar, será habilitado a possibilidade de cadastrar Filtros para o Monitor. Os filtros são importantes pois irão garantir a possibilidade da parametrização individual deste monitor quando ele for ser adicionado em uma Visão por um usuário.
No nosso exemplo, iremos cadastrar apenas dois filtros, Estabelecimento e Desde quando (que irá representar o número de dias no passado que iremos buscar as ordens de produção de acordo com a sua data de criação).
02. CRIANDO UMA NOVA API DE NEGÓCIO
Para criar a Api de negócio o primeiro passo é realizar a importação de classes do Progress que permitem a utilização de objetos do tipo JSON, além disso também é importante definir as includes disponibilizadas pelo Gestão à Vista.
Além da importação dos objetos Progress e definição de includes, também sugerimos criar uma função para verificar se existem RowErrors, essa função será útil para reaproveitamento de código posteriormente.
BLOCK-LEVEL ON ERROR UNDO, THROW.
USING PROGRESS.json.*.
USING PROGRESS.json.ObjectModel.*.
USING cdp.services.gestaoavista.*. //A classe ChartBuilder está definida aqui.
{method/dbotterr.i}
{cdp/services/gestaoavista/builder-utils.i}
{cdp/services/gestaoavista/monitor-utils.i}
FUNCTION fn-has-row-errors RETURNS LOGICAL ():
FOR EACH RowErrors
WHERE UPPER(RowErrors.ErrorType) = 'INTERNAL':U:
DELETE RowErrors.
END.
RETURN CAN-FIND(FIRST RowErrors
WHERE UPPER(RowErrors.ErrorSubType) = 'ERROR':U).
END FUNCTION.
Após essa etapa, será necessário definir as procedures de acordo com a forma que o seu monitor exclusivo foi cadastrado, por exemplo:
Monitores do tipo gráfico, devem implementar a procedure pi-get-monitor-data-chart.
Monitores do tipo texto, devem implementar a procedure pi-get-monitor-data-info.
Ambos os tipos de monitores suportam o modo de detalhe das informações através da procedure pi-get-monitor-detail.
02.a. IMPLEMENTANDO A PROCEDURE DE MODO GRÁFICO
No bloco de código abaixo, iremos criar a procedure pi-get-monitor-data-chart (maiores detalhes sobre ela podem ser consultados aqui), para o nosso exemplo neste guia, iremos realizar uma query dinâmica na tabela ord-prod para realizar a contagem de Ordens x Situação aplicando os filtros de estabelecimento e desde quando que é calculado em número de dias retroativos a hoje.
PROCEDURE pi-get-monitor-data-chart:
DEFINE INPUT PARAMETER TABLE FOR ttVisaoMonitor.
DEFINE OUTPUT PARAMETER monitorJsonOutput AS JsonObject.
DEFINE OUTPUT PARAMETER TABLE FOR RowErrors.
DEFINE VARIABLE cFiltroEstab AS CHARACTER NO-UNDO.
DEFINE VARIABLE dFiltroData AS DATE NO-UNDO.
DEFINE VARIABLE iStatusAberto AS INTEGER NO-UNDO.
DEFINE VARIABLE iStatusAndamento AS INTEGER NO-UNDO.
DEFINE VARIABLE iStatusEncerrado AS INTEGER NO-UNDO.
DEFINE VARIABLE ChartBuilder AS ChartBuilder NO-UNDO. // Classe utilitária que ajudará na montagem do gráfico
FIND FIRST ttVisaoMonitor.
EMPTY TEMP-TABLE RowErrors.
fn-validate-properties(). // Método interno que valida automaticamente se todos os filtros marcados como obrigatórios foram preenchidos.
IF fn-has-row-errors() THEN RETURN "NOK":U.
/* Instanciar a classe passando como parâmetro a ttVisaoMonitor, que contém as informações do monitor e visão
que estão sendo processados nesse instante */
ChartBuilder = NEW ChartBuilder(INPUT TABLE ttVisaoMonitor).
/**
Pega os valores que o usuário digitou nos filtros do monitor. O valor é sempre gravado como STRING,
portanto deve-se fazer a conversão dos dados caso exista necessidade.
Importante: Os valores passados como parâmetro devem ser os mesmos informados no campo "Propriedade" no cadastro de cada filtro
**/
ASSIGN cFiltroEstab = fn-get-valor-propriedade(INPUT "cod-estabel")
dFiltroData = TODAY - INTEGER(fn-get-valor-propriedade(INPUT "qtd-dias-atras")).
FOR EACH ord-prod NO-LOCK
WHERE ord-prod.cod-estabel >= cFiltroEstab
AND ord-prod.dt-emissao >= dFiltroData:
IF ord-prod.estado < 3 THEN
ASSIGN iStatusAberto = iStatusAberto + 1.
ELSE IF ord-prod.estado <= 6 THEN
ASSIGN iStatusAndamento = iStatusAndamento + 1.
ELSE
ASSIGN iStatusEncerrado = iStatusEncerrado + 1.
END.
CREATE ttSeries.
ASSIGN ttSeries.titulo = "Pendentes"
ttSeries.valor = STRING(iStatusAberto)
ttSeries.cor = "#A0B9BF".
CREATE ttSeries.
ASSIGN ttSeries.titulo = "Em Andamento"
ttSeries.valor = STRING(iStatusAndamento)
ttSeries.cor = "#007acc".
CREATE ttSeries.
ASSIGN ttSeries.titulo = "Concluídas"
ttSeries.valor = STRING(iStatusEncerrado)
ttSeries.cor = "#26BA41".
CREATE ttTags.
ASSIGN ttTags.valor = "Tag de Exemplo"
ttTags.icone = "po-icon-calendar"
ttTags.cor-texto = "#f5f5f5"
ttTags.cor-tag = "#080707".
CREATE ttTags.
ASSIGN ttTags.valor = "Tag de Exemplo 2"
ttTags.icone = "po-icon-manufacture"
ttTags.cor-texto = "#f5f5f5"
ttTags.cor-tag = "#080707".
/* Depois que todas as entidades estão criadas, basta setá-las no objeto ChartBuilder */
ChartBuilder:setTags(INPUT TABLE ttTags).
ChartBuilder:setSeries(INPUT TABLE ttSeries).
/* Chama o método para criar e devolver o gráfico completo e guarda o resultado na variável monitorJsonOutput */
monitorJsonOutput = ChartBuilder:createChart().
DELETE OBJECT ChartBuilder.
/* Exibe o resultado no Log do AppServer, se estiver ativo */
RUN displayJsonObject(monitorJsonOutput).
CATCH eSysError AS Progress.Lang.Error:
CREATE RowErrors.
ASSIGN RowErrors.ErrorNumber = 17006
RowErrors.ErrorDescription = eSysError:getMessage(1)
RowErrors.ErrorSubType = "ERROR".
END.
FINALLY:
IF fn-has-row-errors() THEN DO:
UNDO, RETURN 'NOK':U.
END.
END FINALLY.
END.
Após a inclusão dessa procedure na api, já é possível adicionar esse monitor exclusivo em uma Visão e observar o resultado espero conforme abaixo:
Exemplos de outros tipos de gráficos podem ser consultados em: Exemplos adicionais de monitores.
02.b. IMPLEMENTANDO A PROCEDURE DE MODO DETALHE
No bloco de código abaixo, iremos criar a procedure pi-get-monitor-data-detail (maiores detalhes sobre ela podem ser consultados aqui), através da definição dessa procedure é possível fazer o retorno do schema e dados da modal de detalhe para detalhar as informações que estão sendo mostradas no monitor (por exemplo, lista de registros que foram considerados para montar o gráfico do monitor).
No exemplo de código abaixo, ao clicar em uma parte do gráfico, será aberto uma modal com a listagem de Ordens de Produção com a situação escolhida, ou ao acionar a opção Detalhar nas configurações do monitor, será listada todas as ordens de produção que compõe o gráfico.
PROCEDURE pi-get-monitor-data-detail: /* O nome da procedure de detalhe sempre precisará ser esse. */
DEFINE INPUT PARAM TABLE FOR ttVisaoMonitor.
DEFINE INPUT PARAM iPage AS INTEGER NO-UNDO.
DEFINE INPUT PARAM cSerie AS CHARACTER NO-UNDO.
DEFINE INPUT PARAM cCategory AS CHARACTER NO-UNDO.
DEFINE OUTPUT PARAM detailJsonOutput AS JsonObject NO-UNDO.
DEFINE OUTPUT PARAM TABLE FOR RowErrors.
DEFINE VARIABLE DetailBuilder AS DetailBuilder NO-UNDO.
DEFINE VARIABLE lHasNext AS LOGICAL INITIAL FALSE NO-UNDO.
DetailBuilder = NEW DetailBuilder(). // Classe utilitária que ajudará na montagem do objeto de detalhe
FIND FIRST ttVisaoMonitor.
/* Procedure local que cria a temp-table das colunas que serão apresentadas na janela de detalhe */
RUN pi-get-colunas-detalhe(OUTPUT TABLE ttColunaDetalhe).
/* Opcionalmente, também podemos montar cabeçalhos (headers) para a janela de detalhe */
RUN pi-get-headers-detalhe(INPUT cSerie,
OUTPUT TABLE ttHeadersDetalhe).
/**
Executar o método que irá retornar a temp-table contendo os dados a serem apresentados no detalhe. Dessa vez não serão os totais,
e sim os registros individuais. Pode ser aproveitado o mesmo método de query que gera o gráfico do monitor, porém ele precisará
ser ajustado para levar em consideração qual fatia/coluna do gráfico o usuário clicou (variáveis cSerie e cCategory) e também precisará gerar
a tabela com os registros individuais, e não somente calcular os totais
**/
RUN pi-get-itens-detalhe(INPUT iPage,
INPUT cSerie,
OUTPUT lHasNext,
OUTPUT TABLE ttOrdemProducao).
CREATE ttTags.
ASSIGN ttTags.valor = "Tag de Exemplo"
ttTags.icone = "po-icon-calendar"
ttTags.cor-texto = "#f5f5f5"
ttTags.cor-tag = "#080707".
CREATE ttTags.
ASSIGN ttTags.valor = "Tag de Exemplo 2"
ttTags.icone = "po-icon-manufacture"
ttTags.cor-texto = "#f5f5f5"
ttTags.cor-tag = "#080707".
// Setamos as colunas, tags, headers e itens que serão exibidos
DetailBuilder:setTags(INPUT TABLE ttTags).
DetailBuilder:setColumns(INPUT TABLE ttColunaDetalhe).
DetailBuilder:setHeaders(INPUT TABLE ttHeadersDetalhe).
DetailBuilder:setItems(JsonAPIUtils:convertTempTableToJsonArray(TEMP-TABLE ttOrdemProducao:HANDLE, FALSE)). //O método convertTempTableToJsonArray transforma nossa temp-table em um Array, contendo os campos conforme os SERIALIZE-NAME definidos
/**
Se a consulta for paginada e tiver mais resultados além dos que estão sendo retornados, podemos setar a variável 'hasNext' como TRUE. Desse modo o botão
'Carregar mais resultados' ficará habilitado na janela de detalhe para que o usuário possa consultar os registros da próxima página. Recomendamos utilizar paginação
caso exista a possibilidade da consulta geral demorar mais que um minuto
**/
DetailBuilder:setHasNext(lHasNext).
DetailBuilder:setCanExportXLS(TRUE). // Determina se o botão de exportação para planilha ficará habilitado (TRUE) ou não (FALSE)
DetailBuilder:setModalMaxWidth("1440px"). // Tamanho máximo que a janela terá em tela
ASSIGN detailJsonOutput = DetailBuilder:createDetail(). //Gera o objeto de detalhe
/**
A temp-table RowErrors pode ser utilizada para retornar mensagens de erro, caso necessário:
CREATE RowErrors.
ASSIGN RowErrors.ErrorNumber = 17006
RowErrors.ErrorDescription = "ERRO DE EXEMPLO"
RowErrors.ErrorSubType = "ERROR".
**/
END PROCEDURE.
PROCEDURE pi-get-headers-detalhe:
DEFINE INPUT PARAM cSerie AS CHARACTER NO-UNDO.
DEFINE OUTPUT PARAMETER TABLE FOR ttHeadersDetalhe.
CREATE ttHeadersDetalhe.
ASSIGN ttHeadersDetalhe.texto-header = "Listando Ordens com Situação:"
ttHeadersDetalhe.classe-header = "po-sm-12 po-font-subtitle blue-text" //Classes de tipografia do PO-UI estão disponíveis em: https://po-ui.io/guides/typography
ttHeadersDetalhe.estilo-header = "".
CREATE ttHeadersDetalhe.
ASSIGN ttHeadersDetalhe.texto-header = IF cSerie <> "" THEN cSerie ELSE "Todos"
ttHeadersDetalhe.classe-header = "po-sm-12 po-font-text-large-bold" //Classes de tipografia do PO-UI estão disponíveis em: https://po-ui.io/guides/typography
ttHeadersDetalhe.estilo-header = "".
END PROCEDURE.
PROCEDURE pi-get-colunas-detalhe:
DEFINE OUTPUT PARAMETER TABLE FOR ttColunaDetalhe.
/* Atenção! O campo 'propriedade' da ttColunaDetalhe deve conter o mesmo nome que consta no SERIALIZE-NAME do campo que será apresentado,
conforme definição da temp-table. Estamos utilizando a temp-table ttOrdemProducao neste exemplo, veja que estamos usando os nomes em inglês que
foram definidos para cada propriedade: */
CREATE ttColunaDetalhe.
ASSIGN ttColunaDetalhe.cod-label = "Ordem"
ttColunaDetalhe.propriedade = "productionOrderNumber" //productionOrderNumber equivale ao campo nr-ord-produ na temp-table ttOrdemProducao
ttColunaDetalhe.formato = "1.0-0"
ttColunaDetalhe.tipo = "number"
ttColunaDetalhe.largura = "180px".
CREATE ttColunaDetalhe.
ASSIGN ttColunaDetalhe.cod-label = "Item"
ttColunaDetalhe.propriedade = "itemCode". //itemCode equivale ao campo it-codigo na temp-table ttOrdemProducao
CREATE ttColunaDetalhe.
ASSIGN ttColunaDetalhe.cod-label = "Data de Emissão"
ttColunaDetalhe.propriedade = "creationDate" //creationDate equivale ao campo dt-emissao na temp-table ttOrdemProducao
ttColunaDetalhe.tipo = "date".
CREATE ttColunaDetalhe.
ASSIGN ttColunaDetalhe.cod-label = "Quantidade"
ttColunaDetalhe.propriedade = "quantity" //quantity equivale ao campo qt-ordem na temp-table ttOrdemProducao
ttColunaDetalhe.tipo = "number"
ttColunaDetalhe.formato = "1.4-4".
END PROCEDURE.
PROCEDURE pi-get-itens-detalhe:
DEFINE INPUT PARAM iPage AS INTEGER NO-UNDO.
DEFINE INPUT PARAM cSerie AS CHARACTER NO-UNDO.
DEFINE OUTPUT PARAM lHasNext AS LOGICAL NO-UNDO.
DEFINE OUTPUT PARAMETER TABLE FOR ttOrdemProducao.
DEFINE VARIABLE cQuery AS CHARACTER NO-UNDO.
DEFINE VARIABLE iCount AS INTEGER NO-UNDO.
DEFINE VARIABLE cFiltroEstab AS CHARACTER NO-UNDO.
DEFINE VARIABLE dFiltroData AS DATE NO-UNDO.
ASSIGN cFiltroEstab = fn-get-valor-propriedade(INPUT "cod-estabel")
dFiltroData = TODAY - INTEGER(fn-get-valor-propriedade(INPUT "qtd-dias-atras")).
ASSIGN cQuery = 'FOR EACH ord-prod NO-LOCK':U.
ASSIGN cQuery = cQuery + ' WHERE ord-prod.cod-estabel = "' + cFiltroEstab + '"'.
ASSIGN cQuery = cQuery + ' AND ord-prod.dt-emissao >= ' + STRING(dFiltroData) + ''.
/*
Caso a cSerie esteja preenchido, irá significar que a tela de detalhes foi acionada através de uma interação
com o gráfico e deverá ser filtrado os dados de acordo com a série clicada
*/
IF cSerie <> "" THEN DO:
IF cSerie = "Em Aberto" THEN
ASSIGN cQuery = cQuery + ' AND ord-prod.estado < 3'.
ELSE IF cSerie = "Em Andamento" THEN
ASSIGN cQuery = cQuery + ' AND ord-prod.estado >= 3 AND ord-prod.estado <= 6'.
ELSE IF cSerie = "Concluídas" THEN
ASSIGN cQuery = cQuery + ' AND ord-prod.estado >= 7'.
END.
DEFINE QUERY findQuery FOR ord-prod SCROLLING.
QUERY findQuery:QUERY-PREPARE(cQuery).
QUERY findQuery:QUERY-OPEN().
QUERY findQuery:REPOSITION-TO-ROW(iPage).
REPEAT:
GET NEXT findQuery.
IF QUERY findQuery:QUERY-OFF-END THEN LEAVE.
IF iCount >= 50 THEN DO:
ASSIGN lHasNext = TRUE.
LEAVE.
END.
CREATE ttOrdemProducao.
TEMP-TABLE ttOrdemProducao:HANDLE:DEFAULT-BUFFER-HANDLE:BUFFER-COPY(
BUFFER ord-prod:HANDLE
).
FOR FIRST item FIELDS(desc-item)
WHERE item.it-codigo = ttOrdemProducao.it-codigo NO-LOCK:
END.
ASSIGN ttOrdemProducao.desc-item = IF AVAIL item THEN item.desc-item ELSE ''.
ASSIGN iCount = iCount + 1.
END.
END PROCEDURE.
Com o código exemplificado acima teremos o resultado abaixo ao clicar no gráfico:
Em anexo também estamos disponibilizando o código completo utilizado neste passo a passo:
Exemplo Monitor Exclusivo.zip
Para mais exemplos, consulte a página Exemplos adicionais de monitores.