Pular para conteúdo

Comparativo de impostos da reforma tributária (escriturado x XML do CT-e/NF-e)

Avaliada

Alguém testou este código. O registro de como e do resultado está no fim da página.

  • Tipo: exemplo
  • Estado: avaliada
  • Autor: Ponto Ini Sistemas
  • Criado: 2026-10-22T18:30:00Z
  • Tabelas: SF1, SD1, SF2, SD2, SB1, SA1, SA2, CD2, F2D, F2B, CJ2, SFT, CV3, CT2, CKO

Relatório de conferência fiscal da reforma tributária: para cada item de nota, mostra o tributo como foi escriturado e, ao lado, os mesmos números como vieram no XML transmitido. É a diferença entre as duas metades da linha que o relatório existe para expor.

O lado escriturado vem do CD2 (IBS/CBS do item), da regra do tributo em F2D/F2B (com vigência) e da incidência de escrituração em CJ2; o lado transmitido vem do XML do CT-e ou da NF-e, lido da CKO. Entrada (SF1/SD1) e saída (SF2/SD2) são dois caminhos separados, escolhidos pelo filtro, e saem juntos por UNION ALL quando o filtro é Ambos.

Fonte canônico: examples/fontes/PIFINR01.tlpp, CP-1252 sem BOM, exatamente como recebido — o código abaixo é o mesmo arquivo.

Escopo do teste — leia antes de confiar

  • CT-e: validado, entrada e saída, no ambiente de origem.
  • NF-e e NFS-e: não testados — não havia nota desses tipos no ambiente quando o relatório foi avaliado. O caminho TIPOXML = 'NFE' está no fonte e foi lido, mas não foi exercitado.
  • Volume: com retorno grande, a gravação do arquivo demora. A saída é planilha (SetDevice(4)) e o resultado é materializado inteiro de uma vez, com o XML lido uma única vez por documento.

Como está montado

Peça Papel
PIFINR01() User Function de entrada: perguntas do ParamBox (Data De/Até, Tipo Movimento, Nota De/Até), Try/Catch com FWAlertError e abertura do relatório.
ReportDef() Monta o TReport (paisagem, saída em planilha) e as 32 TRCell da seção; 11 delas não têm alias e recebem bloco em ReportPrint.
ReportPrint() Monta os WHERE, carrega os mapas auxiliares, executa a consulta principal, itera com a régua (SetMeter/IncMeter/SetMsgPrint) e imprime linha a linha.
QryEntrada() / QrySaida() As duas consultas principais; colunas com os mesmos nomes e a mesma ordem, o que é o que permite o UNION ALL.
UneQuery() Escolhe entrada, saída ou o UNION ALL conforme nTipoMov.
LoadF2D() / LoadSFT() / LoadCT2() Cargas complementares, cada uma montando um JsonObject indexado por chave.
LoadMap() Executa uma consulta e monta o mapa; em chave repetida, mantém o primeiro registro.
MontaChave() Concatena as partes da chave com \|, sem espaços.
LoadXml() / GetImpXml() Localizam o XML na CKO (índice 5) e extraem base, alíquota e valor conforme o tributo.
DescMov() / DescIncide() / DescRedBs() Decodificam códigos em texto (E/S, incidência no livro, parcela reduzida).

Chaves de junção

  • Regra do tributo — D1_IDTRIB (entrada) ou D2_IDTRIB (saída) casa com F2D.F2D_IDREL; o mapa é indexado por F2D_IDREL|F2B_TRIB e consultado por IDTRIB|CD2_IMP. Os dois lados restringem aos mesmos três tributos (CBSFED, IBSEST, IBSMUN), e é isso que faz as chaves casarem.
  • Classificação fiscal — SFT por TpMov|Doc|Série|Cli-Fornec|Loja|Item|Produto, com fallback para o CLASFIS do próprio item da nota quando o livro não traz nada.
  • Conta contábil — CV3 liga o item ao lançamento (CV3_TABORI SE2 na entrada, SD2 na saída) e o CT2 destino fornece a conta: débito, senão crédito. Chave TpMov|R_E_C_N_O_ do item; fallback para a conta do item da nota.
  • O tipo de movimento entra nas chaves de SFT e CT2 porque documento e recno se repetem entre entrada e saída.
  • XML — um DbSeek na CKO por documento (chave + .xml); o objeto é reaproveitado enquanto o documento não muda e liberado com FreeObj() na troca e no fim.

Tabelas tocadas

Tabela Uso Estrutura conferida no wiki?
SF1 / SD1 Cabeçalho e itens da NF de entrada sim — campos presentes em docs/dados/tabelas/
SF2 / SD2 Cabeçalho e itens da NF de saída sim
SB1 Descrição do produto sim (B1_DESC)
SA1 Cliente, na saída (razão social e CNPJ) sim
SA2 Fornecedor (entrada; e saída em devolução/beneficiamento) sim
CD2 Tributo escriturado do item (IBS/CBS) não
F2D / F2B Regra e vigência do tributo não
CJ2 Incidência na escrituração não
SFT Classificação fiscal do livro não
CV3 / CT2 Vínculo item → lançamento contábil não
CKO XML transmitido (índice 5) não

As tabelas marcadas como não estão fora do catálogo de dicionário publicado neste wiki. A estrutura delas não foi conferida aqui — para conferir, olhe o SX2/SX3 do seu ambiente. R_E_C_N_O_ é o recno interno das tabelas, não um campo de SX3, e por isso não aparece no dicionário.

Pontos de atenção

Leitura do código, não testada — o que vale saber ao usar e ao manter:

  • LoadMap() não confere se MPSysOpenQuery() devolveu alias vazio (o ReportPrint confere, nas linhas do alias principal). Se uma consulta auxiliar falhar, o While cai em erro em vez de degradar. Não foi observado no ambiente de origem.
  • LoadMap() guarda o primeiro registro de cada chave; havendo mais de uma linha para a mesma chave, as seguintes são descartadas em silêncio.
  • Na NF-e, o item do XML é localizado por _cProd comparado a D1_COD/D2_COD. Produto que não case devolve {0, 0, 0} de GetImpXml() — indistinguível de "declarado zero" na hora de ler o relatório.
  • Na saída, DTDIGIT recebe F2_EMISSAO: a coluna rotulada Dt. Digitação é a emissão, porque a SF2 não tem data de digitação.
  • Razão social e CNPJ da saída vêm do cliente (SA1) — ou do fornecedor (SA2) quando F2_TIPO é D (devolução) ou B (beneficiamento); o ISNULL aninhado faz a escolha.
  • GetImpXml() navega o XML com AttIsMemberOf() a cada nível antes de tocar o atributo, em vez de acessar direto: um XML sem o grupo _IBSCBS (ou com a tag ausente) devolve zeros em vez de estourar. Vale o mesmo cuidado ao estender a rotina para novos grupos.

Código

#include "tlpp-core.th"
#include "totvs.ch"

/*/{Protheus.doc} PIFINR01
Relatório de Comparativo de Impostos (Reforma Tributária).
Apresenta tela de filtros via ParamBox (Data De/Até, Tipo Movimento e Nota De/Até)
e executa o relatório exibindo o progresso dos registros na régua de processamento.

@type function
@version 1.0
@author Ponto Ini Sistemas
@since 07/10/2026
@return logical, .T.
/*/
User Function PIFINR01()

    Local aPergs   := {}
    Local aRet     := {}
    Local dDataDe  := dDatabase
    Local dDataAte := dDatabase
    Local nTipoMov := 1
    Local cNotaDe  := Space(TamSX3("F1_DOC")[1])
    Local cNotaAte := Replicate("Z", TamSX3("F1_DOC")[1])
    Local oReport  := Nil
    Local oError   := ErrorClass():New()

    aAdd(aPergs, {1, "Data De"        , dDataDe , "", ".T.", ""   , ".T.", 60, .F.})
    aAdd(aPergs, {1, "Data Até"       , dDataAte, "", ".T.", ""   , ".T.", 60, .T.})
    aAdd(aPergs, {2, "Tipo Movimento" , nTipoMov, {"1=Entrada", "2=Saída", "3=Ambos"}, 60, ".T.", .F.})
    aAdd(aPergs, {1, "Nota Fiscal De" , cNotaDe , "@!", ".T.", "SF1", ".T.", 70, .F.})
    aAdd(aPergs, {1, "Nota Fiscal Até", cNotaAte, "@!", ".T.", "SF1", ".T.", 70, .T.})

    Try

        If ParamBox(aPergs, "Filtro - Comparativo de Impostos", @aRet, /*bOk*/, /*aButtons*/, /*lCentered*/, /*nPosx*/, /*nPosy*/, /*oDlgWizard*/, /*cLoad*/, .F., .F.)
            dDataDe  := aRet[1]
            dDataAte := aRet[2]
            nTipoMov := Val(cValToChar(aRet[3]))
            If nTipoMov == 0
                nTipoMov := 1
            EndIf

            cNotaDe  := aRet[4]
            cNotaAte := aRet[5]

            If Empty(cNotaDe)
                cNotaDe := Space(TamSX3("F1_DOC")[1])
            EndIf
            If Empty(cNotaAte)
                cNotaAte := Replicate("Z", TamSX3("F1_DOC")[1])
            EndIf

            oReport := ReportDef(dDataDe, dDataAte, nTipoMov, cNotaDe, cNotaAte)
            oReport:PrintDialog()
        EndIf

    Catch oError
        FWAlertError(oError:DESCRIPTION + ": " +  oError:ERRORSTACK, "Atenção")
    EndTry

Return .T.

/*/{Protheus.doc} ReportDef
Definição da estrutura e células do relatório TReport.
Os nomes das colunas da consulta têm no máximo 10 caracteres (limite do alias).
As células sem alias são alimentadas em ReportPrint a partir das cargas auxiliares.

@type function
@version 1.0
@param dDataDe, date, Data inicial
@param dDataAte, date, Data final
@param nTipoMov, numeric, 1=Entrada, 2=Saída, 3=Ambos
@param cNotaDe, character, Nota fiscal inicial
@param cNotaAte, character, Nota fiscal final
@return object, Objeto TReport configurado
/*/
Static Function ReportDef(dDataDe, dDataAte, nTipoMov, cNotaDe, cNotaAte)

    Local oReport
    Local oSection1
    Local cTitulo   := "Comparativo de Impostos"
    Local cDesc     := "Relatório de Comparativo de Impostos - Reforma Tributária"
    Local cNome     := "PIFINR01"
    Local cAliasRep := "QRY_IMP"

    oReport := TReport():New(cNome, cTitulo, /*uParam*/, {|oReport| ReportPrint(oReport, dDataDe, dDataAte, nTipoMov, cNotaDe, cNotaAte, cAliasRep)}, cDesc)
    oReport:SetTotalInLine(.F.)
    oReport:SetLandscape()
    oReport:SetDevice(4)
    oReport:SetTpPlanilha({.F., .F., .F., .T.})

    oReport:lParamReadOnly := .T.
    oReport:lPreview := .F.
    oReport:lBold := .T.

    oSection1 := TRSection():New(oReport, cTitulo, {cAliasRep})
    oSection1:SetTotalInLine(.F.)
    oSection1:SetHeaderBreak(.T.)
    oSection1:SetPageBreak(.F.)
    oSection1:lBold := .T.

    TRCell():New(oSection1, "TIPOMOV"       , /*cAlias*/, "Movimento"         , "@!"                , 10)
    TRCell():New(oSection1, "FILIAL"        , cAliasRep , "Filial"            , "@!"                , 04)
    TRCell():New(oSection1, "DOC"           , cAliasRep , "Num. Doc."         , "@!"                , 09)
    TRCell():New(oSection1, "SERIE"         , cAliasRep , "Série"             , "@!"                , 03)
    TRCell():New(oSection1, "ESPECIE"       , cAliasRep , "Espécie"           , "@!"                , 05)
    TRCell():New(oSection1, "DTDIGIT"       , cAliasRep , "Dt. Digitação"     , "@D"                , 10)
    TRCell():New(oSection1, "EMISSAO"       , cAliasRep , "Dt. Emissão"       , "@D"                , 10)
    TRCell():New(oSection1, "FORNECE"       , cAliasRep , "Cli/Fornec"        , "@!"                , 06)
    TRCell():New(oSection1, "LOJA"          , cAliasRep , "Loja"              , "@!"                , 02)
    TRCell():New(oSection1, "NOMEFOR"       , cAliasRep , "Razão Social"      , "@!"                , 30)
    TRCell():New(oSection1, "CNPJ"          , cAliasRep , "CNPJ/CPF"          , "@!"                , 18)
    TRCell():New(oSection1, "TRIBUTO"       , cAliasRep , "Tributo"           , "@!"                , 10)
    TRCell():New(oSection1, "IDTRIB"        , cAliasRep , "Id Rel. Trib."     , "@!"                , 36)
    TRCell():New(oSection1, "CODREGRA"      , /*cAlias*/, "Cód. Regra"        , "@!"                , 15)
    TRCell():New(oSection1, "DESCREGRA"     , /*cAlias*/, "Desc. Regra"       , "@!"                , 30)
    TRCell():New(oSection1, "CD2BC"         , cAliasRep , "Base Cálc."        , "@E 999,999,999.99" , 14)
    TRCell():New(oSection1, "CD2ALIQ"       , cAliasRep , "Alíq. (%)"         , "@E 999.9999"       , 10)
    TRCell():New(oSection1, "CD2VALOR"      , cAliasRep , "Vlr. Tributo"      , "@E 999,999,999.99" , 14)
    TRCell():New(oSection1, "BASETRANS"     , /*cAlias*/, "Base Cálc. Trans." , "@E 999,999,999.99" , 14)
    TRCell():New(oSection1, "ALIQTRANS"     , /*cAlias*/, "Alíq. Trans. (%)"  , "@E 999.9999"       , 10)
    TRCell():New(oSection1, "VLRTRANS"      , /*cAlias*/, "Vlr. Trib. Trans." , "@E 999,999,999.99" , 14)
    TRCell():New(oSection1, "CCLASSTRIB"    , /*cAlias*/, "Class. Trib."      , "@!"                , 10)
    TRCell():New(oSection1, "CHVNFE"        , cAliasRep , "Chave NFe"         , "@!"                , 44)
    TRCell():New(oSection1, "TIPONF"        , cAliasRep , "Tipo Nota"         , "@!"                , 02)
    TRCell():New(oSection1, "CFOP"          , cAliasRep , "CFOP"              , "@!"                , 05)
    TRCell():New(oSection1, "CODINCID"      , /*cAlias*/, "Cód. Incidência"   , "@!"                , 02)
    TRCell():New(oSection1, "INCIDLIVRO"    , /*cAlias*/, "Incidência Livro"  , "@!"                , 30)
    TRCell():New(oSection1, "INCIDREDUZ"    , /*cAlias*/, "Incid. Parc. Red." , "@!"                , 40)
    TRCell():New(oSection1, "PRODUTO"       , cAliasRep , "Produto"           , "@!"                , 15)
    TRCell():New(oSection1, "DESCPROD"      , cAliasRep , "Desc. Produto"     , "@!"                , 30)
    TRCell():New(oSection1, "ITEM"          , cAliasRep , "Item"              , "@!"                , 04)
    TRCell():New(oSection1, "CONTA"         , /*cAlias*/, "Conta Contábil"    , "@!"                , 20)

Return oReport

/*/{Protheus.doc} ReportPrint
Executa a consulta principal (entrada: SF1/SD1/SB1/SA2/CD2; saída: SF2/SD2/SB1/SA1-SA2/CD2,
unidas quando "Ambos") e carrega em memória, por consultas separadas, os dados complementares
(F2D/F2B/CJ2, SFT, CV3/CT2). O XML (CKO) é lido uma única vez por documento. Imprime os registros
com atualização contínua da régua de processamento.

@type function
@version 1.0
@param oReport, object, Objeto TReport
@param dDataDe, date, Data inicial
@param dDataAte, date, Data final
@param nTipoMov, numeric, 1=Entrada, 2=Saída, 3=Ambos
@param cNotaDe, character, Nota inicial
@param cNotaAte, character, Nota final
@param cAliasRep, character, Nome do alias da seção
@return variant, Nil
/*/
Static Function ReportPrint(oReport, dDataDe, dDataAte, nTipoMov, cNotaDe, cNotaAte, cAliasRep)

    Local oSection1 := oReport:Section(1)
    Local cQuery    := ""
    Local cAliasQry := ""
    Local cWhereF1  := ""
    Local cWhereF2  := ""
    Local nTotal    := 0
    Local nAtual    := 0
    Local oMapF2D   := Nil
    Local oMapSFT   := Nil
    Local oMapCT2   := Nil
    Local oLinha    := JsonObject():New()
    Local oXml      := Nil
    Local cUltXml   := ""
    Local cArqXml   := ""
    Local cChvItem  := ""
    Local aF2D      := {}
    Local aTransm   := {}
    Local cAux      := ""

    // Filtros comuns de cabeçalho (entrada e saída), reutilizados em todas as consultas
    cWhereF1 := "     F1.D_E_L_E_T_ = ' ' "
    cWhereF1 += "     AND F1.F1_FILIAL = '" + xFilial("SF1") + "' "
    cWhereF1 += "     AND F1.F1_EMISSAO BETWEEN '" + dToS(dDataDe) + "' AND '" + dToS(dDataAte) + "' "
    cWhereF1 += "     AND F1.F1_DOC BETWEEN '" + cNotaDe + "' AND '" + cNotaAte + "' "

    cWhereF2 := "     F2.D_E_L_E_T_ = ' ' "
    cWhereF2 += "     AND F2.F2_FILIAL = '" + xFilial("SF2") + "' "
    cWhereF2 += "     AND F2.F2_EMISSAO BETWEEN '" + dToS(dDataDe) + "' AND '" + dToS(dDataAte) + "' "
    cWhereF2 += "     AND F2.F2_DOC BETWEEN '" + cNotaDe + "' AND '" + cNotaAte + "' "

    oReport:SetMsgPrint("Carregando dados complementares...")
    oMapF2D := LoadF2D(nTipoMov, cWhereF1, cWhereF2)
    oMapSFT := LoadSFT(nTipoMov, cWhereF1, cWhereF2)
    oMapCT2 := LoadCT2(nTipoMov, cWhereF1, cWhereF2)

    cQuery := UneQuery(nTipoMov, QryEntrada(cWhereF1), QrySaida(cWhereF2))
    cQuery += " ORDER BY "
    cQuery += "     FILIAL, "
    cQuery += "     TPMOV, "
    cQuery += "     DTDIGIT, "
    cQuery += "     DOC, "
    cQuery += "     SERIE, "
    cQuery += "     ITEM, "
    cQuery += "     TRIBUTO "

    cAliasQry := MPSysOpenQuery(cQuery, cAliasRep)
    If Empty(cAliasQry)
        cAliasQry := cAliasRep
    EndIf

    DbSelectArea(cAliasQry)
    Count To nTotal
    (cAliasQry)->(DbGoTop())

    If nTotal == 0
        FWAlertWarning("Nenhum registro encontrado para os parâmetros informados.", "Comparativo de Impostos")
        (cAliasQry)->(DbCloseArea())
        Return Nil
    EndIf

    oSection1:Cell("TIPOMOV"):SetBlock(     { || DescMov((cAliasQry)->TPMOV) })
    oSection1:Cell("DTDIGIT"):SetBlock(     { || sToD((cAliasQry)->DTDIGIT) })
    oSection1:Cell("EMISSAO"):SetBlock(     { || sToD((cAliasQry)->EMISSAO) })
    oSection1:Cell("CODREGRA"):SetBlock(    { || oLinha["CODREGRA"] })
    oSection1:Cell("DESCREGRA"):SetBlock(   { || oLinha["DESCREGRA"] })
    oSection1:Cell("BASETRANS"):SetBlock(   { || oLinha["BASETRANS"] })
    oSection1:Cell("ALIQTRANS"):SetBlock(   { || oLinha["ALIQTRANS"] })
    oSection1:Cell("VLRTRANS"):SetBlock(    { || oLinha["VLRTRANS"] })
    oSection1:Cell("CCLASSTRIB"):SetBlock(  { || oLinha["CCLASSTRIB"] })
    oSection1:Cell("CODINCID"):SetBlock(    { || oLinha["CODINCID"] })
    oSection1:Cell("INCIDLIVRO"):SetBlock(  { || oLinha["INCIDLIVRO"] })
    oSection1:Cell("INCIDREDUZ"):SetBlock(  { || oLinha["INCIDREDUZ"] })
    oSection1:Cell("CONTA"):SetBlock(       { || oLinha["CONTA"] })

    CKO->(DbSetOrder(5))

    oReport:SetMeter(nTotal)
    oSection1:Init()
    oReport:lBold   := .F.
    oSection1:lBold := .F.

    While !(cAliasQry)->(Eof())
        nAtual++
        oReport:SetMsgPrint("Processando registro " + cValToChar(nAtual) + " de " + cValToChar(nTotal) + " - Doc: " + AllTrim((cAliasQry)->DOC) + "...")
        oReport:IncMeter()

        If oReport:Cancel()
            Exit
        EndIf

        // Chaves de SFT e CT2 levam o tipo de movimento (E/S): doc/recno podem repetir entre entrada e saída
        cChvItem := MontaChave({(cAliasQry)->TPMOV, (cAliasQry)->DOC, (cAliasQry)->SERIE, (cAliasQry)->FORNECE, (cAliasQry)->LOJA, (cAliasQry)->ITEM, (cAliasQry)->PRODUTO})

        // F2D/F2B/CJ2 - Regra do tributo e incidência no livro
        aF2D := Nil
        If !Empty((cAliasQry)->IDTRIB) .And. !Empty((cAliasQry)->TRIBUTO)
            aF2D := oMapF2D[MontaChave({(cAliasQry)->IDTRIB, (cAliasQry)->TRIBUTO})]
        EndIf

        If ValType(aF2D) == "A"
            oLinha["CODREGRA"]   := aF2D[1]
            oLinha["DESCREGRA"]  := aF2D[2]
            oLinha["CODINCID"]   := aF2D[3]
            oLinha["INCIDLIVRO"] := DescIncide(aF2D[3])
            oLinha["INCIDREDUZ"] := DescRedBs(aF2D[4])
        Else
            oLinha["CODREGRA"]   := ""
            oLinha["DESCREGRA"]  := ""
            oLinha["CODINCID"]   := ""
            oLinha["INCIDLIVRO"] := DescIncide("")
            oLinha["INCIDREDUZ"] := DescRedBs("")
        EndIf

        // SFT - Classificação fiscal, com fallback para o item da nota
        cAux := oMapSFT[cChvItem]
        oLinha["CCLASSTRIB"] := IIf(Empty(cAux), (cAliasQry)->CLASFIS, cAux)

        // CV3/CT2 - Conta contábil, com fallback para o item da nota
        cAux := oMapCT2[MontaChave({(cAliasQry)->TPMOV, cValToChar((cAliasQry)->RECNOITEM)})]
        oLinha["CONTA"] := IIf(Empty(cAux), (cAliasQry)->CONTAITEM, cAux)

        // XML transmitido - parse uma única vez por documento
        cArqXml := AllTrim((cAliasQry)->CHVNFE) + ".xml"
        If cArqXml != cUltXml
            FreeObj(oXml)
            oXml    := LoadXml(cArqXml)
            cUltXml := cArqXml
        EndIf

        aTransm := GetImpXml(oXml, (cAliasQry)->TIPOXML, (cAliasQry)->TRIBUTO, (cAliasQry)->PRODUTO)
        oLinha["BASETRANS"] := aTransm[1]
        oLinha["ALIQTRANS"] := aTransm[2]
        oLinha["VLRTRANS"]  := aTransm[3]

        oSection1:PrintLine()
        (cAliasQry)->(DbSkip())
    EndDo

    oSection1:Finish()
    (cAliasQry)->(DbCloseArea())

    FreeObj(oXml)
    FreeObj(oMapF2D)
    FreeObj(oMapSFT)
    FreeObj(oMapCT2)
    FreeObj(oLinha)

Return Nil

/*/{Protheus.doc} QryEntrada
Consulta principal das notas de entrada (SF1/SD1/SB1/SA2/CD2).
As colunas têm os mesmos nomes e a mesma ordem de QrySaida, para permitir o UNION ALL.

@type function
@version 1.0
@param cWhereF1, character, Filtro do cabeçalho SF1 (alias F1)
@return character, Consulta SQL (sem ORDER BY)
/*/
Static Function QryEntrada(cWhereF1)

    Local cQuery := ""

    cQuery := " SELECT "
    cQuery += "     'E' AS TPMOV, "
    cQuery += "     F1.F1_FILIAL AS FILIAL, "
    cQuery += "     F1.F1_DOC AS DOC, "
    cQuery += "     F1.F1_SERIE AS SERIE, "
    cQuery += "     F1.F1_ESPECIE AS ESPECIE, "
    cQuery += "     F1.F1_DTDIGIT AS DTDIGIT, "
    cQuery += "     F1.F1_EMISSAO AS EMISSAO, "
    cQuery += "     F1.F1_FORNECE AS FORNECE, "
    cQuery += "     F1.F1_LOJA AS LOJA, "
    cQuery += "     ISNULL(A2.A2_NOME, ' ') AS NOMEFOR, "
    cQuery += "     ISNULL(A2.A2_CGC, ' ') AS CNPJ, "
    cQuery += "     ISNULL(CD2.CD2_IMP, ' ') AS TRIBUTO, "
    cQuery += "     D1.D1_IDTRIB AS IDTRIB, "
    cQuery += "     ISNULL(CD2.CD2_BC, 0) AS CD2BC, "
    cQuery += "     ISNULL(CD2.CD2_ALIQ, 0) AS CD2ALIQ, "
    cQuery += "     ISNULL(CD2.CD2_VLTRIB, 0) AS CD2VALOR, "
    cQuery += "     F1.F1_CHVNFE AS CHVNFE, "
    cQuery += "     F1.F1_TIPO AS TIPONF, "
    cQuery += "     D1.D1_CF AS CFOP, "
    cQuery += "     D1.D1_CLASFIS AS CLASFIS, "
    cQuery += "     D1.D1_CONTA AS CONTAITEM, "
    cQuery += "     D1.D1_COD AS PRODUTO, "
    cQuery += "     B1.B1_DESC AS DESCPROD, "
    cQuery += "     D1.D1_ITEM AS ITEM, "
    cQuery += "     D1.R_E_C_N_O_ AS RECNOITEM, "
    cQuery += "     'CTE' AS TIPOXML "
    cQuery += " FROM "
    cQuery += "     " + RetSqlName("SF1") + " F1 "
    cQuery += "     INNER JOIN " + RetSqlName("SD1") + " D1 ON D1.D1_FILIAL = F1.F1_FILIAL "
    cQuery += "     AND D1.D1_DOC = F1.F1_DOC "
    cQuery += "     AND D1.D1_SERIE = F1.F1_SERIE "
    cQuery += "     AND D1.D1_FORNECE = F1.F1_FORNECE "
    cQuery += "     AND D1.D1_LOJA = F1.F1_LOJA "
    cQuery += "     AND D1.D_E_L_E_T_ = ' ' "
    cQuery += "     INNER JOIN " + RetSqlName("SB1") + " B1 ON B1.B1_FILIAL = '" + xFilial("SB1") + "' "
    cQuery += "     AND B1.B1_COD = D1.D1_COD "
    cQuery += "     AND B1.D_E_L_E_T_ = ' ' "
    cQuery += "     LEFT JOIN " + RetSqlName("SA2") + " A2 ON A2.A2_FILIAL = '" + xFilial("SA2") + "' "
    cQuery += "     AND A2.A2_COD = F1.F1_FORNECE "
    cQuery += "     AND A2.A2_LOJA = F1.F1_LOJA "
    cQuery += "     AND A2.D_E_L_E_T_ = ' ' "
    cQuery += "     LEFT JOIN " + RetSqlName("CD2") + " CD2 ON CD2.CD2_FILIAL = D1.D1_FILIAL "
    cQuery += "     AND CD2.CD2_TPMOV = 'E' "
    cQuery += "     AND CD2.CD2_DOC = D1.D1_DOC "
    cQuery += "     AND CD2.CD2_SERIE = D1.D1_SERIE "
    cQuery += "     AND CD2.CD2_CODFOR = D1.D1_FORNECE "
    cQuery += "     AND CD2.CD2_LOJFOR = D1.D1_LOJA "
    cQuery += "     AND CD2.CD2_ITEM = D1.D1_ITEM "
    cQuery += "     AND CD2.CD2_CODPRO = D1.D1_COD "
    cQuery += "     AND CD2.CD2_IMP IN ('CBSFED', 'IBSEST', 'IBSMUN') "
    cQuery += "     AND CD2.D_E_L_E_T_ = ' ' "
    cQuery += " WHERE "
    cQuery += cWhereF1

Return cQuery

/*/{Protheus.doc} QrySaida
Consulta principal das notas de saída (SF2/SD2/SB1/SA1-SA2/CD2).
Razão social e CNPJ vêm do cliente (SA1) ou, em devolução/beneficiamento (F2_TIPO D/B), do fornecedor (SA2).
Sem data de digitação na saída: DTDIGIT recebe a emissão.

@type function
@version 1.0
@param cWhereF2, character, Filtro do cabeçalho SF2 (alias F2)
@return character, Consulta SQL (sem ORDER BY)
/*/
Static Function QrySaida(cWhereF2)

    Local cQuery := ""

    cQuery := " SELECT "
    cQuery += "     'S' AS TPMOV, "
    cQuery += "     F2.F2_FILIAL AS FILIAL, "
    cQuery += "     F2.F2_DOC AS DOC, "
    cQuery += "     F2.F2_SERIE AS SERIE, "
    cQuery += "     F2.F2_ESPECIE AS ESPECIE, "
    cQuery += "     F2.F2_EMISSAO AS DTDIGIT, "
    cQuery += "     F2.F2_EMISSAO AS EMISSAO, "
    cQuery += "     F2.F2_CLIENTE AS FORNECE, "
    cQuery += "     F2.F2_LOJA AS LOJA, "
    cQuery += "     ISNULL(A1.A1_NOME, ISNULL(A2.A2_NOME, ' ')) AS NOMEFOR, "
    cQuery += "     ISNULL(A1.A1_CGC, ISNULL(A2.A2_CGC, ' ')) AS CNPJ, "
    cQuery += "     ISNULL(CD2.CD2_IMP, ' ') AS TRIBUTO, "
    cQuery += "     D2.D2_IDTRIB AS IDTRIB, "
    cQuery += "     ISNULL(CD2.CD2_BC, 0) AS CD2BC, "
    cQuery += "     ISNULL(CD2.CD2_ALIQ, 0) AS CD2ALIQ, "
    cQuery += "     ISNULL(CD2.CD2_VLTRIB, 0) AS CD2VALOR, "
    cQuery += "     F2.F2_CHVNFE AS CHVNFE, "
    cQuery += "     F2.F2_TIPO AS TIPONF, "
    cQuery += "     D2.D2_CF AS CFOP, "
    cQuery += "     D2.D2_CLASFIS AS CLASFIS, "
    cQuery += "     D2.D2_CONTA AS CONTAITEM, "
    cQuery += "     D2.D2_COD AS PRODUTO, "
    cQuery += "     B1.B1_DESC AS DESCPROD, "
    cQuery += "     D2.D2_ITEM AS ITEM, "
    cQuery += "     D2.R_E_C_N_O_ AS RECNOITEM, "
    cQuery += "     'NFE' AS TIPOXML "
    cQuery += " FROM "
    cQuery += "     " + RetSqlName("SF2") + " F2 "
    cQuery += "     INNER JOIN " + RetSqlName("SD2") + " D2 ON D2.D2_FILIAL = F2.F2_FILIAL "
    cQuery += "     AND D2.D2_DOC = F2.F2_DOC "
    cQuery += "     AND D2.D2_SERIE = F2.F2_SERIE "
    cQuery += "     AND D2.D2_CLIENTE = F2.F2_CLIENTE "
    cQuery += "     AND D2.D2_LOJA = F2.F2_LOJA "
    cQuery += "     AND D2.D_E_L_E_T_ = ' ' "
    cQuery += "     INNER JOIN " + RetSqlName("SB1") + " B1 ON B1.B1_FILIAL = '" + xFilial("SB1") + "' "
    cQuery += "     AND B1.B1_COD = D2.D2_COD "
    cQuery += "     AND B1.D_E_L_E_T_ = ' ' "
    cQuery += "     LEFT JOIN " + RetSqlName("SA1") + " A1 ON A1.A1_FILIAL = '" + xFilial("SA1") + "' "
    cQuery += "     AND A1.A1_COD = F2.F2_CLIENTE "
    cQuery += "     AND A1.A1_LOJA = F2.F2_LOJA "
    cQuery += "     AND F2.F2_TIPO NOT IN ('D', 'B') "
    cQuery += "     AND A1.D_E_L_E_T_ = ' ' "
    cQuery += "     LEFT JOIN " + RetSqlName("SA2") + " A2 ON A2.A2_FILIAL = '" + xFilial("SA2") + "' "
    cQuery += "     AND A2.A2_COD = F2.F2_CLIENTE "
    cQuery += "     AND A2.A2_LOJA = F2.F2_LOJA "
    cQuery += "     AND F2.F2_TIPO IN ('D', 'B') "
    cQuery += "     AND A2.D_E_L_E_T_ = ' ' "
    cQuery += "     LEFT JOIN " + RetSqlName("CD2") + " CD2 ON CD2.CD2_FILIAL = D2.D2_FILIAL "
    cQuery += "     AND CD2.CD2_TPMOV = 'S' "
    cQuery += "     AND CD2.CD2_DOC = D2.D2_DOC "
    cQuery += "     AND CD2.CD2_SERIE = D2.D2_SERIE "
    cQuery += "     AND CD2.CD2_CODCLI = D2.D2_CLIENTE "
    cQuery += "     AND CD2.CD2_LOJCLI = D2.D2_LOJA "
    cQuery += "     AND CD2.CD2_ITEM = D2.D2_ITEM "
    cQuery += "     AND CD2.CD2_CODPRO = D2.D2_COD "
    cQuery += "     AND CD2.CD2_IMP IN ('CBSFED', 'IBSEST', 'IBSMUN') "
    cQuery += "     AND CD2.D_E_L_E_T_ = ' ' "
    cQuery += " WHERE "
    cQuery += cWhereF2

Return cQuery

/*/{Protheus.doc} UneQuery
Seleciona a consulta conforme o tipo de movimento: 1=Entrada, 2=Saída, 3=Ambos (UNION ALL).

@type function
@version 1.0
@param nTipoMov, numeric, 1=Entrada, 2=Saída, 3=Ambos
@param cQryEnt, character, Consulta de entrada
@param cQrySai, character, Consulta de saída
@return character, Consulta resultante
/*/
Static Function UneQuery(nTipoMov, cQryEnt, cQrySai)

    Local cQuery := ""

    Do Case
    Case nTipoMov == 2
        cQuery := cQrySai
    Case nTipoMov == 3
        cQuery := cQryEnt + " UNION ALL " + cQrySai
    Otherwise
        cQuery := cQryEnt
    EndCase

Return cQuery

/*/{Protheus.doc} LoadF2D
Carrega a regra do tributo (F2D/F2B, respeitando a vigência) e a incidência da escrituração (CJ2)
dos itens das notas do filtro (D1_IDTRIB na entrada, D2_IDTRIB na saída).
Chave: IDTRIB|F2B_TRIB

@type function
@version 1.0
@param nTipoMov, numeric, 1=Entrada, 2=Saída, 3=Ambos
@param cWhereF1, character, Filtro do cabeçalho SF1 (alias F1)
@param cWhereF2, character, Filtro do cabeçalho SF2 (alias F2)
@return object, JsonObject com {Código Regra, Descrição Regra, CJ2_INCIDE, CJ2_IREDBS}
/*/
Static Function LoadF2D(nTipoMov, cWhereF1, cWhereF2)

    Local cQryEnt := ""
    Local cQrySai := ""

    cQryEnt := " SELECT "
    cQryEnt += "     F2D.F2D_IDREL, F2B.F2B_TRIB, F2D.F2D_TRIB, F2B.F2B_DESC, "
    cQryEnt += "     ISNULL(CJ2.CJ2_INCIDE, ' ') AS CJ2_INCIDE, "
    cQryEnt += "     ISNULL(CJ2.CJ2_IREDBS, ' ') AS CJ2_IREDBS "
    cQryEnt += " FROM "
    cQryEnt += "     " + RetSqlName("SF1") + " F1 "
    cQryEnt += "     INNER JOIN " + RetSqlName("SD1") + " D1 ON D1.D1_FILIAL = F1.F1_FILIAL "
    cQryEnt += "     AND D1.D1_DOC = F1.F1_DOC "
    cQryEnt += "     AND D1.D1_SERIE = F1.F1_SERIE "
    cQryEnt += "     AND D1.D1_FORNECE = F1.F1_FORNECE "
    cQryEnt += "     AND D1.D1_LOJA = F1.F1_LOJA "
    cQryEnt += "     AND D1.D1_IDTRIB <> ' ' "
    cQryEnt += "     AND D1.D_E_L_E_T_ = ' ' "
    cQryEnt += "     INNER JOIN " + RetSqlName("F2D") + " F2D ON F2D.F2D_FILIAL = D1.D1_FILIAL "
    cQryEnt += "     AND F2D.F2D_IDREL = D1.D1_IDTRIB "
    cQryEnt += "     AND F2D.D_E_L_E_T_ = ' ' "
    cQryEnt += "     INNER JOIN " + RetSqlName("F2B") + " F2B ON F2B.F2B_FILIAL = '" + xFilial("F2B") + "' "
    cQryEnt += "     AND F2B.F2B_REGRA = F2D.F2D_TRIB "
    cQryEnt += "     AND F2B.F2B_TRIB IN ('CBSFED', 'IBSEST', 'IBSMUN') "
    cQryEnt += "     AND (F2B.F2B_VIGINI = ' ' OR F2B.F2B_VIGINI <= D1.D1_EMISSAO) "
    cQryEnt += "     AND (F2B.F2B_VIGFIM = ' ' OR F2B.F2B_VIGFIM >= D1.D1_EMISSAO) "
    cQryEnt += "     AND F2B.D_E_L_E_T_ = ' ' "
    cQryEnt += "     AND F2B.F2B_ALTERA = '2' "
    cQryEnt += "     LEFT JOIN " + RetSqlName("CJ2") + " CJ2 ON CJ2.CJ2_CODIGO = F2B.F2B_CODESC "
    cQryEnt += "     AND CJ2.CJ2_FILIAL = F1.F1_FILIAL "
    cQryEnt += "     AND CJ2.D_E_L_E_T_ = ' ' "
    cQryEnt += " WHERE "
    cQryEnt += cWhereF1

    cQrySai := " SELECT "
    cQrySai += "     F2D.F2D_IDREL, F2B.F2B_TRIB, F2D.F2D_TRIB, F2B.F2B_DESC, "
    cQrySai += "     ISNULL(CJ2.CJ2_INCIDE, ' ') AS CJ2_INCIDE, "
    cQrySai += "     ISNULL(CJ2.CJ2_IREDBS, ' ') AS CJ2_IREDBS "
    cQrySai += " FROM "
    cQrySai += "     " + RetSqlName("SF2") + " F2 "
    cQrySai += "     INNER JOIN " + RetSqlName("SD2") + " D2 ON D2.D2_FILIAL = F2.F2_FILIAL "
    cQrySai += "     AND D2.D2_DOC = F2.F2_DOC "
    cQrySai += "     AND D2.D2_SERIE = F2.F2_SERIE "
    cQrySai += "     AND D2.D2_CLIENTE = F2.F2_CLIENTE "
    cQrySai += "     AND D2.D2_LOJA = F2.F2_LOJA "
    cQrySai += "     AND D2.D2_IDTRIB <> ' ' "
    cQrySai += "     AND D2.D_E_L_E_T_ = ' ' "
    cQrySai += "     INNER JOIN " + RetSqlName("F2D") + " F2D ON F2D.F2D_FILIAL = D2.D2_FILIAL "
    cQrySai += "     AND F2D.F2D_IDREL = D2.D2_IDTRIB "
    cQrySai += "     AND F2D.D_E_L_E_T_ = ' ' "
    cQrySai += "     INNER JOIN " + RetSqlName("F2B") + " F2B ON F2B.F2B_FILIAL = '" + xFilial("F2B") + "' "
    cQrySai += "     AND F2B.F2B_REGRA = F2D.F2D_TRIB "
    cQrySai += "     AND F2B.F2B_TRIB IN ('CBSFED', 'IBSEST', 'IBSMUN') "
    cQrySai += "     AND (F2B.F2B_VIGINI = ' ' OR F2B.F2B_VIGINI <= D2.D2_EMISSAO) "
    cQrySai += "     AND (F2B.F2B_VIGFIM = ' ' OR F2B.F2B_VIGFIM >= D2.D2_EMISSAO) "
    cQrySai += "     AND F2B.D_E_L_E_T_ = ' ' "
    cQrySai += "     AND F2B.F2B_ALTERA = '2' "
    cQrySai += "     LEFT JOIN " + RetSqlName("CJ2") + " CJ2 ON CJ2.CJ2_CODIGO = F2B.F2B_CODESC "
    cQrySai += "     AND CJ2.CJ2_FILIAL = F2.F2_FILIAL "
    cQrySai += "     AND CJ2.D_E_L_E_T_ = ' ' "
    cQrySai += " WHERE "
    cQrySai += cWhereF2

Return LoadMap(UneQuery(nTipoMov, cQryEnt, cQrySai), ;
        {|cAli| MontaChave({(cAli)->F2D_IDREL, (cAli)->F2B_TRIB}) }, ;
        {|cAli| {AllTrim((cAli)->F2D_TRIB), AllTrim((cAli)->F2B_DESC), AllTrim((cAli)->CJ2_INCIDE), AllTrim((cAli)->CJ2_IREDBS)} })

/*/{Protheus.doc} LoadSFT
Carrega a classificação fiscal (FT_CLASFIS) dos itens do livro (SFT) das notas do filtro.
Chave: TpMov(E/S)|Doc|Série|Cli/Fornec|Loja|Item|Produto

@type function
@version 1.0
@param nTipoMov, numeric, 1=Entrada, 2=Saída, 3=Ambos
@param cWhereF1, character, Filtro do cabeçalho SF1 (alias F1)
@param cWhereF2, character, Filtro do cabeçalho SF2 (alias F2)
@return object, JsonObject com a classificação fiscal
/*/
Static Function LoadSFT(nTipoMov, cWhereF1, cWhereF2)

    Local cQryEnt := ""
    Local cQrySai := ""

    cQryEnt := " SELECT "
    cQryEnt += "     SFT.FT_TIPOMOV, SFT.FT_NFISCAL, SFT.FT_SERIE, SFT.FT_CLIEFOR, SFT.FT_LOJA, "
    cQryEnt += "     SFT.FT_ITEM, SFT.FT_PRODUTO, SFT.FT_CLASFIS "
    cQryEnt += " FROM "
    cQryEnt += "     " + RetSqlName("SF1") + " F1 "
    cQryEnt += "     INNER JOIN " + RetSqlName("SFT") + " SFT ON SFT.FT_FILIAL = F1.F1_FILIAL "
    cQryEnt += "     AND SFT.FT_TIPOMOV = 'E' "
    cQryEnt += "     AND SFT.FT_SERIE = F1.F1_SERIE "
    cQryEnt += "     AND SFT.FT_NFISCAL = F1.F1_DOC "
    cQryEnt += "     AND SFT.FT_CLIEFOR = F1.F1_FORNECE "
    cQryEnt += "     AND SFT.FT_LOJA = F1.F1_LOJA "
    cQryEnt += "     AND SFT.FT_CLASFIS <> ' ' "
    cQryEnt += "     AND SFT.D_E_L_E_T_ = ' ' "
    cQryEnt += " WHERE "
    cQryEnt += cWhereF1

    cQrySai := " SELECT "
    cQrySai += "     SFT.FT_TIPOMOV, SFT.FT_NFISCAL, SFT.FT_SERIE, SFT.FT_CLIEFOR, SFT.FT_LOJA, "
    cQrySai += "     SFT.FT_ITEM, SFT.FT_PRODUTO, SFT.FT_CLASFIS "
    cQrySai += " FROM "
    cQrySai += "     " + RetSqlName("SF2") + " F2 "
    cQrySai += "     INNER JOIN " + RetSqlName("SFT") + " SFT ON SFT.FT_FILIAL = F2.F2_FILIAL "
    cQrySai += "     AND SFT.FT_TIPOMOV = 'S' "
    cQrySai += "     AND SFT.FT_SERIE = F2.F2_SERIE "
    cQrySai += "     AND SFT.FT_NFISCAL = F2.F2_DOC "
    cQrySai += "     AND SFT.FT_CLIEFOR = F2.F2_CLIENTE "
    cQrySai += "     AND SFT.FT_LOJA = F2.F2_LOJA "
    cQrySai += "     AND SFT.FT_CLASFIS <> ' ' "
    cQrySai += "     AND SFT.D_E_L_E_T_ = ' ' "
    cQrySai += " WHERE "
    cQrySai += cWhereF2

Return LoadMap(UneQuery(nTipoMov, cQryEnt, cQrySai), ;
        {|cAli| MontaChave({(cAli)->FT_TIPOMOV, (cAli)->FT_NFISCAL, (cAli)->FT_SERIE, (cAli)->FT_CLIEFOR, (cAli)->FT_LOJA, (cAli)->FT_ITEM, (cAli)->FT_PRODUTO}) }, ;
        {|cAli| AllTrim((cAli)->FT_CLASFIS) })

/*/{Protheus.doc} LoadCT2
Carrega a conta contábil (débito, senão crédito) do lançamento vinculado ao item via CV3/CT2.
Entrada: CV3_TABORI 'SE2' com o recno da SD1. Saída: CV3_TABORI 'SD2' com o recno da SD2.
Chave: TpMov(E/S)|R_E_C_N_O_ do item

@type function
@version 1.0
@param nTipoMov, numeric, 1=Entrada, 2=Saída, 3=Ambos
@param cWhereF1, character, Filtro do cabeçalho SF1 (alias F1)
@param cWhereF2, character, Filtro do cabeçalho SF2 (alias F2)
@return object, JsonObject com a conta contábil
/*/
Static Function LoadCT2(nTipoMov, cWhereF1, cWhereF2)

    Local cQryEnt := ""
    Local cQrySai := ""

    cQryEnt := " SELECT "
    cQryEnt += "     'E' AS TPMOV, D1.R_E_C_N_O_ AS RECNOITEM, CT2.CT2_DEBITO, CT2.CT2_CREDIT "
    cQryEnt += " FROM "
    cQryEnt += "     " + RetSqlName("SF1") + " F1 "
    cQryEnt += "     INNER JOIN " + RetSqlName("SD1") + " D1 ON D1.D1_FILIAL = F1.F1_FILIAL "
    cQryEnt += "     AND D1.D1_DOC = F1.F1_DOC "
    cQryEnt += "     AND D1.D1_SERIE = F1.F1_SERIE "
    cQryEnt += "     AND D1.D1_FORNECE = F1.F1_FORNECE "
    cQryEnt += "     AND D1.D1_LOJA = F1.F1_LOJA "
    cQryEnt += "     AND D1.D_E_L_E_T_ = ' ' "
    cQryEnt += "     INNER JOIN " + RetSqlName("CV3") + " CV3 ON CV3.CV3_RECORI = D1.R_E_C_N_O_ "
    cQryEnt += "     AND CV3.CV3_TABORI = 'SE2' "
    cQryEnt += "     AND CV3.D_E_L_E_T_ = ' ' "
    cQryEnt += "     INNER JOIN " + RetSqlName("CT2") + " CT2 ON CT2.R_E_C_N_O_ = CV3.CV3_RECDES "
    cQryEnt += "     AND CT2.D_E_L_E_T_ = ' ' "
    cQryEnt += " WHERE "
    cQryEnt += cWhereF1

    cQrySai := " SELECT "
    cQrySai += "     'S' AS TPMOV, D2.R_E_C_N_O_ AS RECNOITEM, CT2.CT2_DEBITO, CT2.CT2_CREDIT "
    cQrySai += " FROM "
    cQrySai += "     " + RetSqlName("SF2") + " F2 "
    cQrySai += "     INNER JOIN " + RetSqlName("SD2") + " D2 ON D2.D2_FILIAL = F2.F2_FILIAL "
    cQrySai += "     AND D2.D2_DOC = F2.F2_DOC "
    cQrySai += "     AND D2.D2_SERIE = F2.F2_SERIE "
    cQrySai += "     AND D2.D2_CLIENTE = F2.F2_CLIENTE "
    cQrySai += "     AND D2.D2_LOJA = F2.F2_LOJA "
    cQrySai += "     AND D2.D_E_L_E_T_ = ' ' "
    cQrySai += "     INNER JOIN " + RetSqlName("CV3") + " CV3 ON CV3.CV3_RECORI = D2.R_E_C_N_O_ "
    cQrySai += "     AND CV3.CV3_TABORI = 'SD2' "
    cQrySai += "     AND CV3.D_E_L_E_T_ = ' ' "
    cQrySai += "     INNER JOIN " + RetSqlName("CT2") + " CT2 ON CT2.R_E_C_N_O_ = CV3.CV3_RECDES "
    cQrySai += "     AND CT2.D_E_L_E_T_ = ' ' "
    cQrySai += " WHERE "
    cQrySai += cWhereF2

Return LoadMap(UneQuery(nTipoMov, cQryEnt, cQrySai), ;
        {|cAli| MontaChave({(cAli)->TPMOV, cValToChar((cAli)->RECNOITEM)}) }, ;
        {|cAli| IIf(Empty((cAli)->CT2_DEBITO), AllTrim((cAli)->CT2_CREDIT), AllTrim((cAli)->CT2_DEBITO)) })

/*/{Protheus.doc} LoadMap
Executa a consulta e carrega o resultado em um JsonObject indexado pela chave informada.
Em caso de chave repetida, mantém o primeiro registro encontrado.

@type function
@version 1.0
@param cQuery, character, Consulta SQL
@param bChave, codeblock, Bloco que recebe o alias e retorna a chave
@param bValor, codeblock, Bloco que recebe o alias e retorna o valor
@return object, JsonObject com os registros
/*/
Static Function LoadMap(cQuery, bChave, bValor)

    Local oMap  := JsonObject():New()
    Local cAli  := MPSysOpenQuery(cQuery)
    Local cKey  := ""
    Local xVal  := Nil

    While !(cAli)->(Eof())
        cKey := Eval(bChave, cAli)
        If oMap[cKey] == Nil
            xVal := Eval(bValor, cAli)
            If !Empty(xVal)
                oMap[cKey] := xVal
            EndIf
        EndIf
        (cAli)->(DbSkip())
    EndDo

    (cAli)->(DbCloseArea())

Return oMap

/*/{Protheus.doc} MontaChave
Monta chave de busca concatenando as partes sem espaços.

@type function
@version 1.0
@param aPartes, array, Partes da chave
@return character, Chave montada
/*/
Static Function MontaChave(aPartes)

    Local cChave := ""
    Local nI     := 0

    For nI := 1 To Len(aPartes)
        cChave += IIf(nI > 1, "|", "") + AllTrim(aPartes[nI])
    Next nI

Return cChave

/*/{Protheus.doc} DescMov
Descreve o tipo de movimento retornado pela consulta (E/S).

@type function
@version 1.0
@param cTpMov, character, 'E' (entrada) ou 'S' (saída)
@return character, Descrição do movimento
/*/
Static Function DescMov(cTpMov)

    Local cRet := "Entrada"

    If cTpMov == "S"
        cRet := "Saída"
    EndIf

Return cRet

/*/{Protheus.doc} DescIncide
Descreve a incidência no livro fiscal a partir do CJ2_INCIDE.

@type function
@version 1.0
@param cCod, character, Código da incidência (CJ2_INCIDE)
@return character, Descrição da incidência
/*/
Static Function DescIncide(cCod)

    Local cRet := ""

    Do Case
    Case cCod == "1"
        cRet := "Tributado"
    Case cCod == "2"
        cRet := "Isento"
    Case cCod == "3"
        cRet := "Outros"
    Case cCod == "4"
        cRet := "Tributado e Isento"
    Case cCod == "5"
        cRet := "Tributado e Outros"
    Case cCod == "6"
        cRet := "Isento e Outros"
    Case cCod == "7"
        cRet := "Tributado, Isento e Outros"
    Otherwise
        cRet := "Não informado"
    EndCase

Return cRet

/*/{Protheus.doc} DescRedBs
Descreve a incidência da parcela reduzida a partir do CJ2_IREDBS.

@type function
@version 1.0
@param cCod, character, Código da incidência da parcela reduzida (CJ2_IREDBS)
@return character, Descrição da incidência
/*/
Static Function DescRedBs(cCod)

    Local cRet := ""

    Do Case
    Case cCod == "1"
        cRet := "Isento"
    Case cCod == "2"
        cRet := "Outros"
    Otherwise
        cRet := "Outros (padrão em operação com redução)"
    EndCase

Return cRet

/*/{Protheus.doc} LoadXml
Localiza o XML na CKO (índice 5) e retorna o objeto já interpretado.

@type function
@version 1.0
@param cArqXml, character, Nome do arquivo XML (chave + ".xml")
@return object, Objeto XML ou Nil quando não localizado
/*/
Static Function LoadXml(cArqXml)

    Local oXml   := Nil
    Local cAviso := ""
    Local cErro  := ""

    If !Empty(cArqXml) .And. CKO->(DbSeek(cArqXml))
        oXml := XmlParser(CKO->CKO_XMLRET, "", @cAviso, @cErro)
    EndIf

Return oXml

/*/{Protheus.doc} GetImpXml
Extrai dados tributarios do XML (CT-e/NF-e) conforme o tributo.

@type function
@version 1.0
@param oXml, object, Objeto XML interpretado (LoadXml)
@param cTipo, character, Tipo do documento ('CTE' ou 'NFE')
@param cTrib, character, Codigo do tributo ('CBSFED', 'IBSEST', 'IBSMUN')
@param cProduto, character, Código do produto (NF-e)
@return array, {Base de Cálculo, Alíquota, Valor}
/*/
Static Function GetImpXml(oXml, cTipo, cTrib, cProduto)

    Local aRet      := {0, 0, 0}
    Local oGIbsCbs  := Nil
    Local oInfNFe   := Nil
    Local aItens    := {}
    Local nI        := 0
    Local oItem     := Nil
    Local oImp      := Nil

    If ValType(oXml) != "O"
        Return aRet
    EndIf

    cTrib := AllTrim(cTrib)
    cTipo := AllTrim(cTipo)

    If cTipo == 'CTE'
        If AttIsMemberOf(oXml, "_cteProc")
            If AttIsMemberOf(oXml:_cteProc, "_CTe")
                If AttIsMemberOf(oXml:_cteProc:_CTe, "_infCte")
                    If AttIsMemberOf(oXml:_cteProc:_CTe:_infCte, "_imp")
                        If AttIsMemberOf(oXml:_cteProc:_CTe:_infCte:_imp, "_IBSCBS")
                            If AttIsMemberOf(oXml:_cteProc:_CTe:_infCte:_imp:_IBSCBS, "_gIBSCBS")
                                oGIbsCbs := oXml:_cteProc:_CTe:_infCte:_imp:_IBSCBS:_gIBSCBS
                            EndIf
                        EndIf
                    EndIf
                EndIf
            EndIf
        EndIf
    ElseIf cTipo == 'NFE'
        If AttIsMemberOf(oXml, "_nfeProc")
            If AttIsMemberOf(oXml:_nfeProc, "_NFe")
                If AttIsMemberOf(oXml:_nfeProc:_NFe, "_infNFe")
                    oInfNFe := oXml:_nfeProc:_NFe:_infNFe
                EndIf
            EndIf
        ElseIf AttIsMemberOf(oXml, "_NFe")
            If AttIsMemberOf(oXml:_NFe, "_infNFe")
                oInfNFe := oXml:_NFe:_infNFe
            EndIf
        EndIf

        If oInfNFe != Nil .And. AttIsMemberOf(oInfNFe, "_det")
            If ValType(oInfNFe:_det) == "A"
                aItens := oInfNFe:_det
            ElseIf ValType(oInfNFe:_det) == "O"
                aItens := { oInfNFe:_det }
            EndIf

            For nI := 1 To Len(aItens)
                oItem := aItens[nI]

                If Empty(cProduto) .Or. ;
                        (AttIsMemberOf(oItem, "_prod") .And. AttIsMemberOf(oItem:_prod, "_cProd") .And. AllTrim(oItem:_prod:_cProd:TEXT) == AllTrim(cProduto))

                    If AttIsMemberOf(oItem, "_imposto")
                        oImp := oItem:_imposto
                    ElseIf AttIsMemberOf(oItem, "_imp")
                        oImp := oItem:_imp
                    Else
                        oImp := Nil
                    EndIf

                    If oImp != Nil .And. AttIsMemberOf(oImp, "_IBSCBS")
                        If AttIsMemberOf(oImp:_IBSCBS, "_gIBSCBS")
                            oGIbsCbs := oImp:_IBSCBS:_gIBSCBS
                            Exit
                        EndIf
                    EndIf
                EndIf
            Next nI
        EndIf
    EndIf

    If oGIbsCbs != Nil

        If AttIsMemberOf(oGIbsCbs, "_vBC")
            aRet[1] := Val(oGIbsCbs:_vBC:TEXT)
        EndIf

        If Left(cTrib, 3) == 'CBS'
            If AttIsMemberOf(oGIbsCbs, "_gCBS")
                If AttIsMemberOf(oGIbsCbs:_gCBS, "_pCBS")
                    aRet[2] := Val(oGIbsCbs:_gCBS:_pCBS:TEXT)
                EndIf
                If AttIsMemberOf(oGIbsCbs:_gCBS, "_vCBS")
                    aRet[3] := Val(oGIbsCbs:_gCBS:_vCBS:TEXT)
                EndIf
            EndIf
        ElseIf cTrib == 'IBSEST'
            If AttIsMemberOf(oGIbsCbs, "_gIBSUF")
                If AttIsMemberOf(oGIbsCbs:_gIBSUF, "_pIBSUF")
                    aRet[2] := Val(oGIbsCbs:_gIBSUF:_pIBSUF:TEXT)
                EndIf
                If AttIsMemberOf(oGIbsCbs:_gIBSUF, "_vIBSUF")
                    aRet[3] := Val(oGIbsCbs:_gIBSUF:_vIBSUF:TEXT)
                EndIf
            EndIf
        ElseIf cTrib == 'IBSMUN'
            If AttIsMemberOf(oGIbsCbs, "_gIBSMun")
                If AttIsMemberOf(oGIbsCbs:_gIBSMun, "_pIBSMun")
                    aRet[2] := Val(oGIbsCbs:_gIBSMun:_pIBSMun:TEXT)
                EndIf
                If AttIsMemberOf(oGIbsCbs:_gIBSMun, "_vIBSMun")
                    aRet[3] := Val(oGIbsCbs:_gIBSMun:_vIBSMun:TEXT)
                EndIf
            EndIf
        EndIf

    EndIf

Return aRet

Como testar

  1. Compile em um ambiente Protheus com a reforma tributária habilitada e ao menos um CT-e escriturado e transmitido (a CKO precisa ter o XML — índice 5).
  2. Rode u_PIFINR01() por um menu ou pelo SmartClient.
  3. Filtre um período curto, Tipo Movimento = Entrada, e confira uma nota contra o XML original: base, alíquota e valor do IBS/CBS escriturado devem bater com o que o CT-e declara.
  4. Repita com Saída, e depois com Ambos, para exercitar o UNION ALL.
  5. Pendente: rodar com NF-e (modelo 55) e NFS-e. É o que falta para tirar a ressalva do escopo de teste acima.
  6. Retorno grande: acompanhe o tempo de gravação do arquivo antes de usar em produção num período longo.

Avaliação

  • Data: 2026-10-22
  • Por: Ponto Ini Sistemas (ambiente de origem); registrado por hermes a pedido do usuário.
  • Como foi testado: execução do relatório em ambiente Protheus real, com notas escrituradas e XML transmitido na CKO.
  • Resultado: CT-e 100% (entrada e saída). NF-e e NFS-e não testados — não havia nota desses tipos no ambiente. Volume grande atrasa a gravação do arquivo.

Símbolos de API usados

Cada função, classe e método que este fonte chama tem página própria — a explicação da API, com citação:

  • AllTrim (funcao) → página
  • AttIsMemberOf (funcao) → página
  • DbCloseArea (funcao) → página
  • DbGoTop (funcao) → página
  • DbSeek (funcao) → página
  • DbSelectArea (funcao) → página
  • DbSetOrder (funcao) → página
  • DbSkip (funcao) → página
  • Empty (funcao) → página
  • Eof (funcao) → página
  • Eval (funcao) → página
  • FWAlertError (funcao) → página
  • FWAlertWarning (funcao) → página
  • FreeObj (funcao) → página
  • IIf (funcao) → página
  • Len (funcao) → página
  • MPSysOpenQuery (funcao) → página
  • ParamBox (funcao) → página
  • Replicate (funcao) → página
  • RetSqlName (funcao) → página
  • Space (funcao) → página
  • TamSX3 (funcao) → página
  • Val (funcao) → página
  • ValType (funcao) → página
  • XmlParser (funcao) → página
  • aAdd (funcao) → página
  • cValToChar (funcao) → página
  • dToS (funcao) → página
  • sToD (funcao) → página
  • xFilial (funcao) → página
  • ErrorClass (classe) → página
  • JsonObject (classe) → página
  • TRCell (classe) → página
  • TRSection (classe) → página
  • TReport (classe) → página
  • Cancel (metodo) → página — receptor no código: oReport
  • Cell (metodo) → página — receptor no código: oSection1
  • Finish (metodo) → página — receptor no código: oSection1
  • IncMeter (metodo) → página — receptor no código: oReport
  • Init (metodo) → página — receptor no código: oSection1
  • New (metodo) → página — receptor no código: (construtor)
  • PrintDialog (metodo) → página — receptor no código: oReport
  • PrintLine (metodo) → página — receptor no código: oSection1
  • Section (metodo) → página — receptor no código: oReport
  • SetBlock (metodo) → página — receptor no código: (encadeado)
  • SetDevice (metodo) → página — receptor no código: oReport
  • SetHeaderBreak (metodo) → página — receptor no código: oSection1
  • SetLandscape (metodo) → página — receptor no código: oReport
  • SetMeter (metodo) → página — receptor no código: oReport
  • SetMsgPrint (metodo) → página — receptor no código: oReport
  • SetPageBreak (metodo) → página — receptor no código: oSection1
  • SetTotalInLine (metodo) → página — receptor no código: oReport, oSection1
  • SetTpPlanilha (metodo) → página — receptor no código: oReport

Todos os símbolos: Funções e classes da API.

Fonte

  • Arquivo canônico: examples/comparativo-impostos-reforma-tributaria.md
  • Voltar para Exemplos e templates