Como montar a lógica para fazer um relatório analítico e sintético em AdvPL

Hoje irei mostrar como montar a lógica de um relatório sintético e analítico em AdvPL.

Muitos alunos me perguntam como montar em um mesmo relatório a lógica de ser sintético e analítico. Pois bem jovens, no artigo de hoje vou mostrar uma forma simples de se fazer.

Antes de começarmos, esse exemplo é feito usando TReport, mas a lógica você pode adaptar para HTML, FWMSPrinter, etc.

A lógica seria mais ou menos assim:

  • Primeiro você tem uma tela de parâmetros, como por exemplo, data de/até
  • Nessa tela de parâmetros, você tem que ter um parâmetro para definir se é Sintético ou Analítico
  • Ao confirmar o relatório, você faz a primeira query, utilizando group by e sum que será a query do sintético
  • No laço de repetição dos dados sintéticos, você vai testar o parâmetro, se for analítico, você vai fazer uma query buscando todos os itens do registro atual sintético, e irá imprimir eles

Com a lógica acima, teríamos um cenário onde por exemplo, na Nota 1 de valor R$ 100, se for analítico irá imprimir todos os itens, senão, já vai para a próxima nota.

Abaixo o print do resultado sintético:

Exemplo do relatório sintético

E do resultado analítico:

Exemplo do relatório analítico

Abaixo o código fonte do exemplo citado:

//Bibliotecas
#Include "Protheus.ch"
#Include "TopConn.ch"
     
/*/{Protheus.doc} ACOMR01
Relatório - Listagem de Documentos de Entrada                     
@author Atilio
@since 05/07/2021
@version 1.0
    @example
    u_ACOMR01()
/*/
     
User Function ACOMR01()
    Local aArea      := GetArea()
    Local oReport
    Local aPergs   := {}
    Local dDataDe  := FirstDate(Date())
    Local dDataAt  := LastDate(Date())
    Local nTipo    := 1
    Local cNotaDe  := Space(TamSX3('D1_DOC')[1])
    Local cNotaAt  := StrTran(cNotaDe, ' ', 'Z')
    Local cProdDe  := Space(TamSX3('B1_COD')[1])
    Local cProdAt  := StrTran(cProdDe, ' ', 'Z')
    Local cFornDe  := Space(TamSX3('A2_COD')[1])
    Local cFornAt  := StrTran(cFornDe, ' ', 'Z')
    Private aTamD1 := TamSX3('D1_TOTAL')
    Private cMasD1 := PesqPict('SD1', 'D1_TOTAL')
     
    //Adiciona os parâmetros
    aAdd(aPergs, {1, "Data De",        dDataDe,  "", ".T.", "",    ".T.", 80,  .F.})
    aAdd(aPergs, {1, "Data Até",       dDataAt,  "", ".T.", "",    ".T.", 80,  .T.})
    aAdd(aPergs, {2, "Tipo",           nTipo, {"1=Sintético", "2=Analítico"}, 090, ".T.", .F.})
    aAdd(aPergs, {1, "Documento De",   cNotaDe,  "", ".T.", "",    ".T.", 100, .F.})
    aAdd(aPergs, {1, "Documento Até",  cNotaAt,  "", ".T.", "",    ".T.", 100, .T.})
    aAdd(aPergs, {1, "Produto De",     cProdDe,  "", ".T.", "SB1", ".T.", 80,  .F.})
    aAdd(aPergs, {1, "Produto Até",    cProdAt,  "", ".T.", "SB1", ".T.", 80,  .T.})
    aAdd(aPergs, {1, "Fornecedor De",  cFornDe,  "", ".T.", "SA2", ".T.", 60,  .F.})
    aAdd(aPergs, {1, "Fornecedor Até", cFornAt,  "", ".T.", "SA2", ".T.", 60,  .T.})
 
    //Se a pergunta for confirmada
    If ParamBox(aPergs, "Informe os parâmetros", , , , , , , , , .F., .F.)
        MV_PAR03 := Val(cValToChar(MV_PAR03))
 
        oReport := fReportDef()
        oReport:PrintDialog()
    EndIf
     
    RestArea(aArea)
Return
     
/*-------------------------------------------------------------------------------*
 | Func:  fReportDef                                                             |
 | Desc:  Função que monta a definição do relatório                              |
 *-------------------------------------------------------------------------------*/
     
Static Function fReportDef()
    Local oReport
    Local oSectSin := Nil
    Local oSectAna := Nil
     
    //Criação do componente de impressão
    oReport := TReport():New(    "ACOMR01",;                                    //Nome do Relatório
                                "Relatório de Documentos de Entrada",;        //Título
                                ,;                                            //Pergunte ... Se eu defino a pergunta aqui, será impresso uma página com os parâmetros, conforme privilégio 101
                                {|oReport| fRepPrint(oReport)},;            //Bloco de código que será executado na confirmação da impressão
                                )                                            //Descrição
    oReport:SetTotalInLine(.F.)
    oReport:lParamPage := .F.
    oReport:oPage:SetPaperSize(9)
    oReport:SetPortrait()
    oReport:SetLineHeight(50)
    oReport:nFontBody := 10
     
    //Criando a seção
    oSectSin := TRSection():New(    oReport,;
                                    "Dados",;
                                    {"QRY_SIN"})
    oSectSin:SetTotalInLine(.F.)
     
    //Colunas do relatório
    TRCell():New(oSectSin, "D1_DOC",     "QRY_SIN", "Documento",  /*Picture*/, 12,        /*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
    TRCell():New(oSectSin, "D1_SERIE",   "QRY_SIN", "Série",      /*Picture*/, 03,        /*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
    TRCell():New(oSectSin, "D1_DTDIGIT", "QRY_SIN", "Data",       /*Picture*/, 12,        /*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
    TRCell():New(oSectSin, "D1_FORNECE", "QRY_SIN", "Forn.",      /*Picture*/, 08,        /*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
    TRCell():New(oSectSin, "A2_NOME",    "QRY_SIN", "Nome",       /*Picture*/, 15,        /*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
    TRCell():New(oSectSin, "TOTAL",      "QRY_SIN", "Vlr. Total", cMasD1,      aTamD1[1], /*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
    TRCell():New(oSectSin, "VLR_IPI",    "QRY_SIN", "Vlr. IPI",   cMasD1,      aTamD1[1], /*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
    TRCell():New(oSectSin, "VLR_PROP",   "QRY_SIN", "Vlr. Prop.", cMasD1,      aTamD1[1], /*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
    TRCell():New(oSectSin, "VLR_ST",     "QRY_SIN", "Vlr. ST",    cMasD1,      aTamD1[1], /*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
     
    //Criando a seção
    oSectAna := TRSection():New(    oSectSin,;
                                    "Analítico",;
                                    {"QRY_ANA"})
    oSectAna:SetTotalInLine(.F.)
     
    //Colunas do relatório
    TRCell():New(oSectAna, "D1_ITEM",    "QRY_ANA", "Item",       /*Picture*/, 06,        /*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
    TRCell():New(oSectAna, "D1_COD",     "QRY_ANA", "Produto",    /*Picture*/, 10,        /*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
    TRCell():New(oSectAna, "B1_DESC",    "QRY_ANA", "Descrição",  /*Picture*/, 30,        /*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
    TRCell():New(oSectAna, "D1_TOTAL",   "QRY_ANA", "Vlr. Total", cMasD1,      aTamD1[1], /*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
    TRCell():New(oSectAna, "D1_VALIPI",  "QRY_ANA", "Vlr. IPI",   cMasD1,      aTamD1[1], /*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
    TRCell():New(oSectAna, "D1_VALICM",  "QRY_ANA", "Vlr. Prop.", cMasD1,      aTamD1[1], /*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
    TRCell():New(oSectAna, "D1_ICMSRET", "QRY_ANA", "Vlr. ST",    cMasD1,      aTamD1[1], /*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
     
Return oReport
     
/*-------------------------------------------------------------------------------*
 | Func:  fRepPrint                                                              |
 | Desc:  Função que imprime o relatório                                         |
 *-------------------------------------------------------------------------------*/
     
Static Function fRepPrint(oReport)
    Local aArea     := GetArea()
    Local cQrySin   := ""
    Local cQryAna   := ""
    Local oSectSin  := Nil
    Local oSectAna  := Nil
    Local nAtual    := 0
    Local nTotal    := 0
     
    //Pegando as seções
    oSectSin := oReport:Section(1)
    oSectAna := oReport:Section(1):Section(1)
     
    //Faz uma consulta buscando os dados sintéticos
    cQrySin := " SELECT "                                                + CRLF
    cQrySin += "     D1_DOC, "                                            + CRLF
    cQrySin += "    D1_SERIE, "                                          + CRLF
    cQrySin += "    D1_DTDIGIT, "                                        + CRLF
    cQrySin += "    D1_FORNECE, "                                        + CRLF
    cQrySin += "    A2_NOME, "                                           + CRLF
    cQrySin += "     SUM(D1_TOTAL) AS TOTAL, "                            + CRLF
    cQrySin += "     SUM(D1_VALIPI) AS VLR_IPI, "                         + CRLF
    cQrySin += "    SUM(D1_VALICM) AS VLR_PROP, "                        + CRLF
    cQrySin += "     SUM(D1_ICMSRET) AS VLR_ST "                          + CRLF
    cQrySin += " FROM "                                                  + CRLF
    cQrySin += "     " + RetSQLName('SD1') + " SD1 "                      + CRLF
    cQrySin += "     INNER JOIN " + RetSQLName('SA2') + " SA2 ON ( "     + CRLF
    cQrySin += "         A2_FILIAL = '" + FWxFilial('SA2') + "' "        + CRLF
    cQrySin += "         AND A2_COD = D1_FORNECE "                       + CRLF
    cQrySin += "         AND A2_LOJA = D1_LOJA "                         + CRLF
    cQrySin += "         AND SA2.D_E_L_E_T_ = ' ' "                      + CRLF
    cQrySin += "     ) "                                                 + CRLF
    cQrySin += " WHERE "                                                 + CRLF
    cQrySin += "     D1_FILIAL = '" + FWxFilial('SD1') + "' "             + CRLF
    cQrySin += "     AND D1_DTDIGIT >= '" + dToS(MV_PAR01) + "' "         + CRLF
    cQrySin += "     AND D1_DTDIGIT <= '" + dToS(MV_PAR02) + "' "         + CRLF
    cQrySin += "     AND D1_DOC >= '" + MV_PAR04 + "' "                   + CRLF
    cQrySin += "     AND D1_DOC <= '" + MV_PAR05 + "' "                   + CRLF
    cQrySin += "     AND D1_COD >= '" + MV_PAR06 + "' "                   + CRLF
    cQrySin += "     AND D1_COD <= '" + MV_PAR07 + "' "                   + CRLF
    cQrySin += "     AND D1_FORNECE >= '" + MV_PAR08 + "' "               + CRLF
    cQrySin += "     AND D1_FORNECE <= '" + MV_PAR09 + "' "               + CRLF
    cQrySin += "    AND D1_TIPO NOT IN ('B', 'D') "                      + CRLF
    cQrySin += "     AND SD1.D_E_L_E_T_ = ' ' "                           + CRLF
    cQrySin += " GROUP BY "                                              + CRLF
    cQrySin += "     D1_DOC, "                                           + CRLF
    cQrySin += "     D1_SERIE, "                                         + CRLF
    cQrySin += "     D1_DTDIGIT, "                                       + CRLF
    cQrySin += "     D1_FORNECE, "                                       + CRLF
    cQrySin += "     A2_NOME "                                           + CRLF
    cQrySin += " ORDER BY "                                              + CRLF
    cQrySin += "     D1_DTDIGIT, "                                       + CRLF
    cQrySin += "     D1_DOC "                                            + CRLF
    TCQuery cQrySin New Alias "QRY_SIN"
    TCSetField("QRY_SIN", "D1_DTDIGIT", "D")
     
    //Contando o total de registros
    Count To nTotal
     
    //Se houver dados
    If nTotal != 0
         
        //Setando a régua e fazendo o laço para imprimir os dados
        oReport:SetMeter(nTotal)
        QRY_SIN->(DbGoTop())
        nAtual := 0
        oSectSin:Init()
        While ! QRY_SIN->(EoF())
            nAtual++
            oReport:SetMsgPrint("Imprimindo (nota " + cValToChar(nAtual) + " de " + cValToChar(nTotal) + ")...")
            oReport:IncMeter()
             
            //Imprimindo a linha atual
            oSectSin:PrintLine()
             
            //Se for do tipo analítico
            If MV_PAR03 == 2
             
                //Criando a consulta analítica
                cQryAna := " SELECT "                                                     + CRLF
                cQryAna += "    D1_ITEM, "                                                + CRLF
                cQryAna += "    D1_COD, "                                                 + CRLF
                cQryAna += "    B1_DESC, "                                                + CRLF
                cQryAna += "    D1_TOTAL, "                                               + CRLF
                cQryAna += "     D1_VALIPI, "                                              + CRLF
                cQryAna += "    D1_VALICM, "                                              + CRLF
                cQryAna += "     D1_ICMSRET "                                              + CRLF
                cQryAna += " FROM "                                                       + CRLF
                cQryAna += "     " + RetSQLName('SD1') + " SD1 "                          + CRLF
                cQryAna += "     INNER JOIN " + RetSQLName('SB1') + " SB1 ON ( "          + CRLF
                cQryAna += "         B1_FILIAL = '" + FWxFilial('SB1') + "' "             + CRLF
                cQryAna += "         AND B1_COD = D1_COD "                                + CRLF
                cQryAna += "         AND SB1.D_E_L_E_T_ = ' ' "                           + CRLF
                cQryAna += "     ) "                                                      + CRLF
                cQryAna += " WHERE "                                                      + CRLF
                cQryAna += "     D1_FILIAL = '" + FWxFilial('SD1') + "' "                 + CRLF
                cQryAna += "     AND D1_DOC = '" + QRY_SIN->D1_DOC + "' "                 + CRLF
                cQryAna += "     AND D1_SERIE = '" + QRY_SIN->D1_SERIE + "' "             + CRLF
                cQryAna += "     AND D1_FORNECE = '" + QRY_SIN->D1_FORNECE + "' "         + CRLF
                cQryAna += "     AND D1_TIPO NOT IN ('B', 'D') "                          + CRLF
                cQryAna += "     AND SD1.D_E_L_E_T_ = ' ' "                               + CRLF
                cQryAna += " ORDER BY "                                                   + CRLF
                cQryAna += "     D1_ITEM "                                                + CRLF
                TCQuery cQryAna New Alias "QRY_ANA"
                 
                //Enquanto houver dados analíticos
                oSectAna:Init()
                While ! QRY_ANA->(EoF())
                    oSectAna:PrintLine()
                     
                    QRY_ANA->(DbSkip())
                EndDo
                QRY_ANA->(DbCloseArea())
                oSectAna:Finish()
                 
                //Encerra o Sintético, e inicializa novamente
                oSectSin:Finish()
                oSectSin:Init()
            EndIf
             
            QRY_SIN->(DbSkip())
        EndDo
        oSectSin:Finish()
         
    EndIf
    QRY_SIN->(DbCloseArea())
     
    RestArea(aArea)
Return

Bom pessoal, por hoje é só.

Abraços e até a próxima.

Dan Atilio (Daniel Atilio)
Especialista em Engenharia de Software pela FIB. Entusiasta de soluções Open Source. E blogueiro nas horas vagas.

2 Responses

  1. Caio Mário disse:

    Bom dia.

    Conteúdo excepcional. Parabéns.

Deixe uma resposta