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.