Criar colunas dinamicamente conforme intervalo de meses | Ti Responde 0219

No vídeo de hoje, vamos demonstrar em como criar uma tela usando FWBrowse criando colunas dinamicamente com os meses de um período.

A dúvida de hoje, nos perguntaram, como poderia criar dinamicamente colunas em um FWBrowse, por exemplo, se o usuário colocar 3 meses no parâmetro, criar 3 colunas, se for 5 meses, 5 colunas e assim por diante.

 

Pensando nisso, montamos um exemplo, onde vamos mostrar em como fazer essa lógica criando na tabela temporária, nas colunas da tela e como popular essa informação entre duas tabelas, uma analítica e uma sintética.

 

Segue abaixo o vídeo exemplificando:

 

E abaixo o código fonte desenvolvido:

//Bibliotecas
#Include "tlpp-core.th"
#Include "TOTVS.ch"

//Declaração da namespace
Namespace custom.terminal.youtube

#Define CRLF Chr(13) + Chr(10) //Carriage Return Line Feed

/*/{Protheus.doc} User Function video0219
Teste de colunas dinâmicas conforme filtro de data
@author Atilio
@since 21/02/2025
@version 1.0
@type function
@example custom.terminal.youtube.u_video0219()
@obs Codigo gerado automaticamente pelo Autumn Code Maker
@see http://autumncodemaker.com
/*/

User Function video0219()
	Local aArea         := FWGetArea()         As Array
	Local aParameters   := {}                  As Array
    Private dCurrentDate                       As Date
	Private dInitDate   := FirstDate(Date())   As Date
	Private dLastDate   := LastDate(dInitDate) As Date
	
	//Adicionando os parametros do ParamBox
	aAdd(aParameters, {1, "Data De",  dInitDate,  "", ".T.", "", ".T.", 80,  .T.})
	aAdd(aParameters, {1, "Data Até", dLastDate,  "", ".T.", "", ".T.", 80,  .T.})
	
	//Se a pergunta for confirma, chama a tela
	If ParamBox(aParameters, 'Informe os parâmetros', /*aRet*/, /*bOk*/, /*aButtons*/, /*lCentered*/, /*nPosx*/, /*nPosy*/, /*oDlgWizard*/, /*cLoad*/, .F., .F.)
        dInitDate := MV_PAR01
        dLastDate := MV_PAR02
		createSyntheticDialog()
	EndIf
	
	FWRestArea(aArea)
Return

/*/{Protheus.doc} createSyntheticDialog
Monta a tela com a marcação de dados
@author Atilio
@since 21/02/2025
@version 1.0
@type function
@obs Codigo gerado automaticamente pelo Autumn Code Maker
@see http://autumncodemaker.com
/*/

Static Function createSyntheticDialog()
    Local aArea                  := FWGetArea()                                                            As Array
    Private cFontName            := 'Tahoma'                                                               As Character
    Private oFontDefault         := TFont():New(cFontName, /*uPar2*/, -14)                                 As Object
    //Janela e componentes
    Private oDialogSynthetic                                                                               As Object
    //Tabela sintética
    Private oTempTableSynthetic  := Nil                                                                    As Object
    Private oPanelSynthetic                                                                                As Object
    Private oFWBrowseSynthetic                                                                             As Object
    Private cAliasSynthetic      := GetNextAlias()                                                         As Character
    Private aSyntheticFields     := {}                                                                     As Array
    Private aSyntheticColumns    := {}                                                                     As Array
    //Tabela analítica
    Private oTempTableAnalytical := Nil                                                                    As Object
    Private cAliasAnalytical     := GetNextAlias()                                                         As Character
    Private aAnalyticalFields    := {}                                                                     As Array
    //Tamanho da janela
    Private aScreenSize          := MsAdvSize()                                                            As Array
    Private nDialogWidth         := aScreenSize[5]                                                         As Numeric
    Private nDialogHeight        := aScreenSize[6]                                                         As Numeric
    
    //Adiciona as colunas da tabela analítica
    aAdd(aAnalyticalFields, { 'PROD_COD',   'C', TamSX3('B1_COD')[1],      0})
    aAdd(aAnalyticalFields, { 'PROD_DESC',  'C', TamSX3('B1_DESC')[1],     0})
    aAdd(aAnalyticalFields, { 'ANOMES',     'C', 6,                        0})
    aAdd(aAnalyticalFields, { 'C5_NUM',     'C', TamSX3('C5_NUM')[1],      0})
    aAdd(aAnalyticalFields, { 'C5_NOTA',    'C', TamSX3('C5_NOTA')[1],     0})
    aAdd(aAnalyticalFields, { 'C5_EMISSAO', 'D', TamSX3('C5_EMISSAO')[1],  0})
    aAdd(aAnalyticalFields, { 'C5_CLIENTE', 'C', TamSX3('C5_CLIENTE')[1],  0})
    aAdd(aAnalyticalFields, { 'A1_NOME',    'C', TamSX3('A1_NOME')[1],     0})
    aAdd(aAnalyticalFields, { 'C6_QTDVEN',  'N', TamSX3('C6_QTDVEN')[1],   TamSX3('C6_QTDVEN')[2]})

    //Adiciona as colunas chave da tabela sintética (código e nome)
    aAdd(aSyntheticFields, { 'PROD_COD',  'C', TamSX3('B1_COD')[1],  0})
    aAdd(aSyntheticFields, { 'PROD_DESC', 'C', TamSX3('B1_DESC')[1], 0})

    //Adiciona as colunas que serão criadas na temporária (dinamicamente conforme a data)
    dCurrentDate := dInitDate
    While dCurrentDate <= dLastDate
        aAdd(aSyntheticFields, { 'QTD_' + AnoMes(dCurrentDate), 'N', 12, 2}) //QTD_YYYYMM
        
        dCurrentDate := MonthSum(dCurrentDate, 1)
    EndDo

    //Cria as tabela temporárias
    oTempTableSynthetic:= FWTemporaryTable():New(cAliasSynthetic)
    oTempTableSynthetic:SetFields( aSyntheticFields )
    oTempTableSynthetic:AddIndex("1", {"PROD_COD"})
    oTempTableSynthetic:Create()  
    oTempTableAnalytical:= FWTemporaryTable():New(cAliasAnalytical)
    oTempTableAnalytical:SetFields( aAnalyticalFields )
    oTempTableAnalytical:AddIndex("1", {"PROD_COD"})
    oTempTableAnalytical:Create()  

    //Popula a tabela temporária
    Processa({|| insertInTempTables()}, 'Processando...')

    //Adiciona as colunas que serão exibidas no FWBrowse
    aSyntheticColumns := createSyntheticColumns()
     
    //Criando a janela
    oDialogSynthetic := TDialog():New(0, 0, nDialogHeight, nDialogWidth, 'Tela para Consulta de Dados - Sintética', , , , , , /*nCorFundo*/, , , .T.)
        //Dados
        oPanelSynthetic := tPanel():New(015, 001, '', oDialogSynthetic, /*oFont*/, /*lCentered*/, /*uParam7*/, RGB(000,000,000), RGB(254,254,254), (nDialogWidth/2) - 1, (nDialogHeight/2) - 30)
        oFWBrowseSynthetic := FWBrowse():New()
        oFWBrowseSynthetic:DisableFilter()
        oFWBrowseSynthetic:DisableConfig()
        oFWBrowseSynthetic:DisableReport()
        oFWBrowseSynthetic:DisableSeek()
        oFWBrowseSynthetic:DisableSaveConfig()
        oFWBrowseSynthetic:SetFontBrowse(oFontDefault)
        oFWBrowseSynthetic:SetAlias(cAliasSynthetic)
        oFWBrowseSynthetic:SetDataTable()
        oFWBrowseSynthetic:SetDoubleClick( {|| syntheticDoubleClick() } )         
        oFWBrowseSynthetic:SetEditCell(.F., {|| .F.}) 
        oFWBrowseSynthetic:lHeaderClick := .F.
        
        //Define as colunas, vincula ao painel e exibe
        oFWBrowseSynthetic:SetColumns(aSyntheticColumns)
        oFWBrowseSynthetic:SetOwner(oPanelSynthetic)
        oFWBrowseSynthetic:Activate()
        
    oDialogSynthetic:Activate(, , , .T., , , /*bInit*/)
    
    //Deleta a temporária e desativa o browse
    oTempTableSynthetic:Delete()
    oFWBrowseSynthetic:DeActivate()
    oTempTableAnalytical:Delete()
    
    FWRestArea(aArea)
Return

/*/{Protheus.doc} insertInTempTables
Executa a query SQL e popula essa informação na tabela temporária usada no browse
@author Atilio
@since 21/02/2025
@version 1.0
@type function
@obs Codigo gerado automaticamente pelo Autumn Code Maker
@see http://autumncodemaker.com
/*/

Static Function insertInTempTables()
    Local cId            := ""     As Character
    Local cName          := ""     As Character
    Local cYearMonth     := ""     As Character
    Local nQuantity      := 0      As Numeric
    Local cQryAnalytical := ""     As Character

    //Insere na analítica
    cQryAnalytical := ""
    cQryAnalytical += " INSERT INTO " + oTempTableAnalytical:GetRealName() + CRLF
    cQryAnalytical += " (PROD_COD, PROD_DESC, ANOMES, C5_NUM, C5_NOTA, C5_EMISSAO, C5_CLIENTE, A1_NOME, C6_QTDVEN) " + CRLF
    cQryAnalytical += " SELECT " + CRLF
    cQryAnalytical += " 	B1_COD, " + CRLF
    cQryAnalytical += " 	B1_DESC, " + CRLF
    cQryAnalytical += " 	LEFT(C5_EMISSAO, 6), " + CRLF
    cQryAnalytical += " 	C5_NUM, " + CRLF
    cQryAnalytical += " 	C5_NOTA, " + CRLF
    cQryAnalytical += " 	C5_EMISSAO, " + CRLF
    cQryAnalytical += " 	C5_CLIENTE, " + CRLF
    cQryAnalytical += " 	A1_NOME, " + CRLF
    cQryAnalytical += " 	C6_QTDVEN " + CRLF
    cQryAnalytical += " FROM " + CRLF
    cQryAnalytical += " 	" + RetSQLName("SB1") + " SB1 " + CRLF
    cQryAnalytical += " 	INNER JOIN " + RetSQLName("SC6") + " SC6 ON ( " + CRLF
    cQryAnalytical += " 		C6_FILIAL = '" + FWxFilial("SC6") + "' " + CRLF
    cQryAnalytical += " 		AND C6_PRODUTO = B1_COD " + CRLF
    cQryAnalytical += " 		AND SC6.D_E_L_E_T_ = ' ' " + CRLF
    cQryAnalytical += " 	) " + CRLF
    cQryAnalytical += " 	INNER JOIN " + RetSQLName("SC5") + " SC5 ON ( " + CRLF
    cQryAnalytical += " 		C5_FILIAL = C6_FILIAL " + CRLF
    cQryAnalytical += " 		AND C5_NUM = C6_NUM " + CRLF
    cQryAnalytical += " 		AND C5_EMISSAO >= '" + dToS(dInitDate) + "' " + CRLF
    cQryAnalytical += " 		AND C5_EMISSAO <= '" + dToS(dLastDate) + "' " + CRLF
    cQryAnalytical += " 		AND C5_TIPO NOT IN ('B', 'D') " + CRLF
    cQryAnalytical += " 		AND SC5.D_E_L_E_T_ = ' ' " + CRLF
    cQryAnalytical += " 	) " + CRLF
    cQryAnalytical += " 	INNER JOIN " + RetSQLName("SA1") + " SA1 ON ( " + CRLF
    cQryAnalytical += " 		A1_FILIAL = '" + FWxFilial("SA1") + "' " + CRLF
    cQryAnalytical += " 		AND A1_COD = C5_CLIENTE " + CRLF
    cQryAnalytical += " 		AND A1_LOJA = C5_LOJACLI " + CRLF
    cQryAnalytical += " 		AND SA1.D_E_L_E_T_ = ' ' " + CRLF
    cQryAnalytical += " 	) " + CRLF
    cQryAnalytical += " WHERE " + CRLF
    cQryAnalytical += " 	B1_FILIAL = '" + FWxFilial("SB1") + "' " + CRLF
    cQryAnalytical += " 	AND SB1.D_E_L_E_T_ = ' ' " + CRLF
    u_zExecQry(cQryAnalytical, .T.)

    //Percorre agora os dados analíticos
    DbSelectArea(cAliasAnalytical)
    (cAliasAnalytical)->(DbGoTop())
    While ! (cAliasAnalytical)->(EoF())

        //Pega os campos
        cId            := (cAliasAnalytical)->PROD_COD
        cName          := (cAliasAnalytical)->PROD_DESC
        cYearMonth     := (cAliasAnalytical)->ANOMES
        nQuantity      := (cAliasAnalytical)->C6_QTDVEN

        //Se conseguir posicionar no produto na sintética, vai ser alteração
        If (cAliasSynthetic)->(MsSeek(cId))
            RecLock(cAliasSynthetic, .F.)

        //Senão vai ser inclusão
        Else
            RecLock(cAliasSynthetic, .T.)
            (cAliasSynthetic)->PROD_COD  := cId
            (cAliasSynthetic)->PROD_DESC := cName
        EndIf
        &(cAliasSynthetic + "->QTD_" + cYearMonth) += nQuantity
        (cAliasSynthetic)->(MsUnlock())

        (cAliasAnalytical)->(DbSkip())
    EndDo
    (cAliasAnalytical)->(DbGoTop())
Return

/*/{Protheus.doc} createSyntheticColumns
Função que gera as colunas usadas no browse (similar ao antigo aHeader)
@author Atilio
@since 21/02/2025
@version 1.0
@type function
@obs Codigo gerado automaticamente pelo Autumn Code Maker
@see http://autumncodemaker.com
/*/

Static Function createSyntheticColumns()
    Local nCurrent          := 0  As Numeric
    Local aSyntheticColumns := {} As Array
    Local aStructColumns    := {} As Array
    Local oColumn                 As Object
    
    //Adicionando campos que serão mostrados na tela
    //[1] - Campo da Temporaria
    //[2] - Titulo
    //[3] - Tipo
    //[4] - Tamanho
    //[5] - Decimais
    //[6] - Máscara
    //[7] - Editável? .T. = sim, .F. = não
    //[8] - Código da Consulta Padrão
    //[9] - Bloco de Código usado na Validação (ex.: {|| fSuaValid()} )
    aAdd(aStructColumns, { 'PROD_COD',  'Produto',   'C', TamSX3('B1_COD')[1],  0, '', .F., Nil, Nil})
    aAdd(aStructColumns, { 'PROD_DESC', 'Descrição', 'C', TamSX3('B1_DESC')[1], 0, '', .F., Nil, Nil})

    //Adiciona as colunas que serão criadas na temporária dinamicamente
    dCurrentDate := dInitDate
    While dCurrentDate <= dLastDate
        cNomeMes := Capital(MesExtenso(dCurrentDate)) + " / " + cValToChar(Year(dCurrentDate))

        aAdd(aStructColumns, { 'QTD_' + AnoMes(dCurrentDate), 'Qtd Pedidos - ' + cNomeMes, 'N', 12, 2, '@E 999,999,999.99', .F., Nil, Nil})
        
        dCurrentDate := MonthSum(dCurrentDate, 1)
    EndDo

    //Percorrendo todos os campos da estrutura
    For nCurrent := 1 To Len(aStructColumns)
        //Cria a coluna
        oColumn := FWBrwColumn():New()
        oColumn:SetData(&('{|| ' + cAliasSynthetic + '->' + aStructColumns[nCurrent][1] +'}'))
        oColumn:SetTitle(aStructColumns[nCurrent][2])
        oColumn:SetType(aStructColumns[nCurrent][3])
        oColumn:SetSize(aStructColumns[nCurrent][4])
        oColumn:SetDecimal(aStructColumns[nCurrent][5])
        oColumn:SetPicture(aStructColumns[nCurrent][6])

        //Muda o alinhamento conforme o tipo, Data será Centralizado
        If aStructColumns[nCurrent][3] == 'D'
        	oColumn:nAlign := 0
        
        //Numérico, direita
        ElseIf aStructColumns[nCurrent][3] == 'N'
        	oColumn:nAlign := 2
        
        //Senão, esquerda (caractere)
        Else
        	oColumn:nAlign := 1
        EndIf

        //Se for ser possível ter o duplo clique para editar
        If aStructColumns[nCurrent][7]
        	oColumn:SetEdit(.T.)
        	oColumn:SetReadVar(cAliasSynthetic + "->" + aStructColumns[nCurrent][1])
        	
        	//Se tiver Consulta Padrão
        	If ! Empty(aStructColumns[nCurrent][8])
        		oColumn:xF3 := aStructColumns[nCurrent][8]
        	EndIf
        	
        	//Se tiver Validação
        	If ! Empty(aStructColumns[nCurrent][9])
        		oColumn:SetValid(aStructColumns[nCurrent][9])
        	EndIf
        EndIf
        
        //Adiciona a coluna
        aAdd(aSyntheticColumns, oColumn)
    Next
Return aSyntheticColumns

Static Function syntheticDoubleClick()
    Local aArea := FWGetArea() As Array

    //Se a pergunta for confirmada, abre a visualização do grupo
    If FWAlertYesNo("Deseja visualizar as informações do produto '" + Alltrim((cAliasSynthetic)->PROD_DESC) + "'?")
        createAnalyticalDialog()
    EndIf

    FWRestArea(aArea)
Return

Static Function createAnalyticalDialog()
    Local cAliasFilter            := ""                     As Character
    Local nAnalyticalDialogHeight := nDialogHeight - 200    As Numeric
    Local nAnalyticalDialogWidth  := nDialogWidth  - 200    As Numeric
    Private oDialogAnalytical                               As Object
    Private oPanelAnalytical                                As Object
    Private oFWBrowseAnalytical                             As Object

    //Aplica o filtro conforme o registro posicionado da sintética
    cAliasFilter := "PROD_COD == '" + (cAliasSynthetic)->PROD_COD + "'"
    (cAliasAnalytical)->(DbClearFilter())
    (cAliasAnalytical)->(DbSetFilter({|| &(cAliasFilter)}, cAliasFilter))
    (cAliasAnalytical)->(DbGoTop())

    //Adiciona as colunas que serão exibidas no FWBrowse
    aAnalyticalColumns := createAnalyticalColumns()
     
    //Criando a janela
    oDialogAnalytical := TDialog():New(0, 0, nAnalyticalDialogHeight, nAnalyticalDialogWidth, 'Tela para Consulta de Dados - Analítica', , , , , , /*nCorFundo*/, , , .T.)
        //Dados
        oPanelAnalytical := tPanel():New(015, 001, '', oDialogAnalytical, /*oFont*/, /*lCentered*/, /*uParam7*/, RGB(000,000,000), RGB(254,254,254), (nAnalyticalDialogWidth/2) - 1, (nAnalyticalDialogHeight/2) - 30)
        oFWBrowseAnalytical := FWBrowse():New()
        oFWBrowseAnalytical:DisableFilter()
        oFWBrowseAnalytical:DisableConfig()
        oFWBrowseAnalytical:DisableReport()
        oFWBrowseAnalytical:DisableSeek()
        oFWBrowseAnalytical:DisableSaveConfig()
        oFWBrowseAnalytical:SetFontBrowse(oFontDefault)
        oFWBrowseAnalytical:SetAlias(cAliasAnalytical)
        oFWBrowseAnalytical:SetDataTable()
        oFWBrowseAnalytical:SetEditCell(.F., {|| .F.}) 
        oFWBrowseAnalytical:lHeaderClick := .F.
        
        //Define as colunas, vincula ao painel e exibe
        oFWBrowseAnalytical:SetColumns(aAnalyticalColumns)
        oFWBrowseAnalytical:SetOwner(oPanelAnalytical)
        oFWBrowseAnalytical:Activate()
        
    oDialogAnalytical:Activate(, , , .T., , , /*bInit*/)
    
    //desativa o browse
    oFWBrowseAnalytical:DeActivate()    
Return

Static Function createAnalyticalColumns()
    Local nCurrent           := 0  As Numeric
    Local aAnalyticalColumns := {} As Array
    Local aStructColumns     := {} As Array
    Local oColumn                  As Object
    
    //Adicionando campos que serão mostrados na tela
    //[1] - Campo da Temporaria
    //[2] - Titulo
    //[3] - Tipo
    //[4] - Tamanho
    //[5] - Decimais
    //[6] - Máscara
    //[7] - Editável? .T. = sim, .F. = não
    //[8] - Código da Consulta Padrão
    //[9] - Bloco de Código usado na Validação (ex.: {|| fSuaValid()} )
    aAdd(aStructColumns, { 'PROD_COD',   'Produto',     'C', TamSX3('B1_COD')[1],     0,                      '',                           .F., Nil, Nil})
    aAdd(aStructColumns, { 'PROD_DESC',  'Descrição',   'C', TamSX3('B1_DESC')[1],    0,                      '',                           .F., Nil, Nil})
    aAdd(aStructColumns, { 'ANOMES',     'Ano Mês',     'C', 6,                       0,                      '',                           .F., Nil, Nil})
    aAdd(aStructColumns, { 'C5_NUM',     'Pedido',      'C', TamSX3('C5_NUM')[1],     0,                      '',                           .F., Nil, Nil})
    aAdd(aStructColumns, { 'C5_NOTA',    'NF',          'C', TamSX3('C5_NOTA')[1],    0,                      '',                           .F., Nil, Nil})
    aAdd(aStructColumns, { 'C5_EMISSAO', 'Data Pedido', 'D', TamSX3('C5_EMISSAO')[1], 0,                      '',                           .F., Nil, Nil})
    aAdd(aStructColumns, { 'C5_CLIENTE', 'Cliente',     'C', TamSX3('C5_CLIENTE')[1], 0,                      '',                           .F., Nil, Nil})
    aAdd(aStructColumns, { 'A1_NOME',    'Nome',        'C', TamSX3('A1_NOME')[1],    0,                      '',                           .F., Nil, Nil})
    aAdd(aStructColumns, { 'C6_QTDVEN',  'Qtde Venda',  'N', TamSX3('C6_QTDVEN')[1],  TamSX3('C6_QTDVEN')[2], PesqPict('SC6', 'C6_QTDVEN'), .F., Nil, Nil})

    //Percorrendo todos os campos da estrutura
    For nCurrent := 1 To Len(aStructColumns)
        //Cria a coluna
        oColumn := FWBrwColumn():New()
        oColumn:SetData(&('{|| ' + cAliasAnalytical + '->' + aStructColumns[nCurrent][1] +'}'))
        oColumn:SetTitle(aStructColumns[nCurrent][2])
        oColumn:SetType(aStructColumns[nCurrent][3])
        oColumn:SetSize(aStructColumns[nCurrent][4])
        oColumn:SetDecimal(aStructColumns[nCurrent][5])
        oColumn:SetPicture(aStructColumns[nCurrent][6])

        //Muda o alinhamento conforme o tipo, Data será Centralizado
        If aStructColumns[nCurrent][3] == 'D'
        	oColumn:nAlign := 0
        
        //Numérico, direita
        ElseIf aStructColumns[nCurrent][3] == 'N'
        	oColumn:nAlign := 2
        
        //Senão, esquerda (caractere)
        Else
        	oColumn:nAlign := 1
        EndIf

        //Se for ser possível ter o duplo clique para editar
        If aStructColumns[nCurrent][7]
        	oColumn:SetEdit(.T.)
        	oColumn:SetReadVar(cAliasAnalytical + "->" + aStructColumns[nCurrent][1])
        	
        	//Se tiver Consulta Padrão
        	If ! Empty(aStructColumns[nCurrent][8])
        		oColumn:xF3 := aStructColumns[nCurrent][8]
        	EndIf
        	
        	//Se tiver Validação
        	If ! Empty(aStructColumns[nCurrent][9])
        		oColumn:SetValid(aStructColumns[nCurrent][9])
        	EndIf
        EndIf
        
        //Adiciona a coluna
        aAdd(aAnalyticalColumns, oColumn)
    Next
Return aAnalyticalColumns

Bom pessoal, por hoje é só.

 

Abraços e até a próxima.

Dan (Daniel Atilio)
Cristão de ramificação protestante. Especialista em Engenharia de Software pela FIB, graduado em Banco de Dados pela FATEC Bauru e técnico em informática pelo CTI da Unesp. Entusiasta de soluções Open Source e blogueiro nas horas vagas. Autor e mantenedor do portal Terminal de Informação.

Deixe uma resposta

Terminal de Informação