Hoje será demonstrado um relatório para impressão de notas promissórias no sistema.
Para quem precisa imprimir notas promissórias no sistema, o Gabriel Dahm criou uma customização onde é aberto uma tela de parâmetros para informar o tipo (pagar ou receber), cliente / fornecedor e período.
Após confirmar é aberto um MarkBrowse para selecionar os títulos a serem impressos.
Marcando os registros e confirmando a operação, é processado o relatório e gerado. Abaixo um print de exemplo:

E abaixo o código fonte desenvolvido pelo Gabriel Dahm (LinkedIn):
// Bibliotecas
#include "TOTVS.ch"
#include "FWMVCDef.ch"
#include "TopConn.ch"
#Include "RPTDef.ch"
// Alinhamentos
#Define PAD_LEFT 0
#Define PAD_RIGHT 1
#Define PAD_CENTER 2
// Cores
#Define COR_PRETO RGB(0,0,0)
#Define COR_CINZA RGB(180,180,180)
/*/{Protheus.doc} User Function zPromiEmit
Função que realiza tela de parambox para receber parametro digitado pelo usuario e exibir um browse com os dados filtrados
@type Function
@author Gabriel Dahm
@since 25/08/2025
@version 1.0
/*/
User Function zPromiEmit()
Local aArea := FWGetArea()
Local aPergs := {}
Private nTipo := 3
Private cForCliDe := Space(TamSX3("A1_COD")[1])
Private cForCliAte := StrTran(cForCliDe, " ", "Z")
Private dDataDe := FirstDate(Date())
Private dDataAt := LastDate(dDataDe)
aAdd(aPergs, {2, "Tipo do Filtro", nTipo, {"1=A Pagar", "2=A Receber", "3=Ambos"}, 090, ".T.", .F.}) // MV_PAR01
aAdd(aPergs, {1, "Cliente / Fornecedor De", cForCliDe, "", ".T.", "", ".T.", 80, .F.}) // MV_PAR02
aAdd(aPergs, {1, "Cliente / Fornecedor Até", cForCliAte, "", ".T.", "", ".T.", 80, .T.}) // MV_PAR03
aAdd(aPergs, {1, "Data De", dDataDe, "", ".T.", "", ".T.", 80, .F.}) // MV_PAR04
aAdd(aPergs, {1, "Data Até", dDataAt, "", ".T.", "", ".T.", 80, .T.}) // MV_PAR05
If ParamBox(aPergs, "Informe o tipo de filtro")
nTipo := Val(cValToChar(MV_PAR01))
cForCliDe := MV_PAR02
cForCliAte := MV_PAR03
dDataDe := MV_PAR04
dDataAt := MV_PAR05
fMontaTela()
EndIf
FWRestArea(aArea)
Return
/*/{Protheus.doc} User Function fMontaTela
Função que realiza a montagem da tela com browse para marcação dos dados
@type Function
@author Gabriel Dahm
@since 25/08/2025
@version 1.0
/*/
Static Function fMontaTela()
Local aCampos := {}
Local aColunas := {}
Local cFontPad := "Tahoma"
Local oFontGrid := TFont():New(cFontPad,,-14)
Local oTempTable
// Janela e componentes
Private oDlgMark
Private oPanGrid
Private oMarkBrowse
Private cAliasTmp := GetNextAlias()
Private aRotina := MenuDef()
// Tamanho da janela
Private aTamanho := MsAdvSize()
Private nJanLarg := aTamanho[5]
Private nJanAltu := aTamanho[6]
// Define campos da temporária
aAdd(aCampos, { "OK", "C", 1, 0 })
aAdd(aCampos, { "Tipo", "C", 17, 0 })
aAdd(aCampos, { "Numero", "C", 9, 0 })
aAdd(aCampos, { "Prefixo", "C", 3, 0 })
aAdd(aCampos, { "Codigo", "C", 6, 0 })
aAdd(aCampos, { "Nome", "C", 50, 0 })
aAdd(aCampos, { "Valor", "N", 16, 2 })
aAdd(aCampos, { "Historico", "C", 50, 0 })
aAdd(aCampos, { "Razao", "C", 50, 0 })
aAdd(aCampos, { "Endereco", "C", 60, 0 })
aAdd(aCampos, { "Vencimento", "D", 8, 0 })
aAdd(aCampos, { "Complemen", "C", 60, 0 })
aAdd(aCampos, { "Cnpj", "C", 18, 0 })
aAdd(aCampos, { "Filial", "C", 4, 0 }) // Adicione na definição da temporária
// Cria e popula a tabela temporária
oTempTable := FWTemporaryTable():New(cAliasTmp)
oTempTable:SetFields(aCampos)
oTempTable:Create()
Processa({|| fPopula()}, "Processando...")
// Define colunas do browse
aColunas := fCriaCols()
// Cria a janela
DEFINE MSDIALOG oDlgMark TITLE "Tela para Marcação de Títulos" FROM 000, 000 TO nJanAltu, nJanLarg COLORS 0, 16777215 PIXEL
oPanGrid := tPanel():New(001, 001, "", oDlgMark, , , , RGB(000,000,000), RGB(254,254,254), nJanLarg - 2, nJanAltu - 60)
//Dados
oPanGrid := tPanel():New(001, 001, '', oDlgMark, /*oFont*/, /*lCentered*/, /*uParam7*/, RGB(000,000,000), RGB(254,254,254), (nJanLarg/2) - 1, (nJanAltu/2) - 1)
oMarkBrowse := FWMarkBrowse():New()
oMarkBrowse:SetAlias(cAliasTmp)
oMarkBrowse:SetDescription('Títulos Financeiros')
oMarkBrowse:DisableFilter()
oMarkBrowse:DisableConfig()
oMarkBrowse:DisableSeek()
oMarkBrowse:DisableSaveConfig()
oMarkBrowse:SetFontBrowse(oFontGrid)
oMarkBrowse:SetFieldMark('OK')
oMarkBrowse:SetTemporary(.T.)
oMarkBrowse:SetColumns(aColunas)
oMarkBrowse:SetOwner(oPanGrid)
// Legendas
oMarkBrowse:AddLegend({|| (cAliasTmp)->Tipo == "Contas a Receber"}, "BLUE", "Receber")
oMarkBrowse:AddLegend({|| (cAliasTmp)->Tipo == "Contas a Pagar" }, "GREEN", "Pagar")
oMarkBrowse:Activate()
ACTIVATE MSDIALOG oDlgMark CENTERED
// Limpeza
oTempTable:Delete()
oMarkBrowse:DeActivate()
If Type("oDlgMark") == "O" .And. oDlgMark != NIL
// Adicione aqui qualquer limpeza adicional se necessário
EndIf
Return
/*/{Protheus.doc} User Function fPopula
Função que popula a tabela temporária com os dados filtrados
@type Function
@author Gabriel Dahm
@since 25/08/2025
@version 1.0
/*/
Static Function fPopula()
Local cQryDados := ""
Local nTotal := 0
Local nAtual := 0
// Monta a query
If nTipo == 2 .Or. nTipo == 3
cQryDados += "SELECT" + CRLF
cQryDados += " 'Contas a Receber' AS Tipo, " + CRLF
cQryDados += " E1_NUM AS Numero, " + CRLF
cQryDados += " E1_PREFIXO AS Prefixo, " + CRLF
cQryDados += " E1_CLIENTE AS Codigo, " + CRLF
cQryDados += " A1_NOME AS Nome, " + CRLF
cQryDados += " CAST(E1_VALOR AS NUMERIC(16,2)) AS Valor, " + CRLF
cQryDados += " E1_HIST AS Historico, " + CRLF
cQryDados += " A1_NOME AS Razao, " + CRLF
cQryDados += " A1_END AS Endereco, " + CRLF
cQryDados += " E1_VENCREA AS Vencimento, " + CRLF
cQryDados += " CONCAT(RTRIM(A1_END), ', ', RTRIM(A1_MUN), '/', RTRIM(A1_EST)) AS Complemen, " + CRLF
cQryDados += " CASE WHEN RTRIM(A1_CGC) = '' THEN '' " + CRLF
cQryDados += " WHEN LEN(A1_CGC) = 14 THEN " + CRLF
cQryDados += " CONCAT(SUBSTRING(A1_CGC,1,2),'.',SUBSTRING(A1_CGC,3,3),'.',SUBSTRING(A1_CGC,6,3),'/',SUBSTRING(A1_CGC,9,4),'-',SUBSTRING(A1_CGC,13,2)) " + CRLF
cQryDados += " WHEN LEN(A1_CGC) = 11 THEN " + CRLF
cQryDados += " CONCAT(SUBSTRING(A1_CGC,1,3),'.',SUBSTRING(A1_CGC,4,3),'.',SUBSTRING(A1_CGC,7,3),'-',SUBSTRING(A1_CGC,10,2)) " + CRLF
cQryDados += " ELSE A1_CGC END AS Cnpj, " + CRLF
cQryDados += " E1_FILIAL AS Filial " + CRLF // Adicione na query de contas a receber
cQryDados += " FROM " + RetSQLName("SE1") + " SE1 " + CRLF
cQryDados += " INNER JOIN " + RetSQLName("SA1") + " SA1 ON (" + CRLF
cQryDados += " A1_FILIAL = '" + FWxFilial("SA1") + "' " + CRLF
cQryDados += " AND A1_COD = E1_CLIENTE " + CRLF
cQryDados += " AND A1_LOJA = E1_LOJA " + CRLF
cQryDados += " AND SA1.D_E_L_E_T_ = ' ' ) " + CRLF
cQryDados += "WHERE E1_TIPO = 'NP' " + CRLF
cQryDados += " AND E1_FILIAL = '" + FWxFilial("SE1") + "' " + CRLF
cQryDados += " AND E1_EMISSAO BETWEEN '" + dToS(dDataDe) + "' AND '" + dToS(dDataAt) + "' " + CRLF
cQryDados += " AND E1_CLIENTE BETWEEN '" + cForCliDe + "' AND '" + cForCliAte + "' " + CRLF
cQryDados += " AND E1_BAIXA = '' " + CRLF
cQryDados += " AND SE1.D_E_L_E_T_ = ' ' " + CRLF
EndIf
If nTipo == 3
cQryDados += " UNION ALL " + CRLF
EndIf
If nTipo == 1 .Or. nTipo == 3
cQryDados += "SELECT" + CRLF
cQryDados += " 'Contas a Pagar' AS Tipo, " + CRLF
cQryDados += " E2_NUM AS Numero, " + CRLF
cQryDados += " E2_PREFIXO AS Prefixo, " + CRLF
cQryDados += " E2_FORNECE AS Codigo, " + CRLF
cQryDados += " A2_NOME AS Nome, " + CRLF
cQryDados += " CAST(E2_VALOR AS NUMERIC(16,2)) AS Valor, " + CRLF
cQryDados += " E2_HIST AS Historico, " + CRLF
cQryDados += " A2_NOME AS Razao, " + CRLF
cQryDados += " A2_END AS Endereco, " + CRLF
cQryDados += " E2_VENCREA AS Vencimento, " + CRLF
cQryDados += " CONCAT(RTRIM(A2_END), ', ', RTRIM(A2_MUN), '/', RTRIM(A2_EST)) AS Complemen, " + CRLF
cQryDados += " CASE WHEN RTRIM(A2_CGC) = '' THEN '' " + CRLF
cQryDados += " WHEN LEN(A2_CGC) = 14 THEN " + CRLF
cQryDados += " CONCAT(SUBSTRING(A2_CGC,1,2),'.',SUBSTRING(A2_CGC,3,3),'.',SUBSTRING(A2_CGC,6,3),'/',SUBSTRING(A2_CGC,9,4),'-',SUBSTRING(A2_CGC,13,2)) " + CRLF
cQryDados += " WHEN LEN(A2_CGC) = 11 THEN " + CRLF
cQryDados += " CONCAT(SUBSTRING(A2_CGC,1,3),'.',SUBSTRING(A2_CGC,4,3),'.',SUBSTRING(A2_CGC,7,3),'-',SUBSTRING(A2_CGC,10,2)) " + CRLF
cQryDados += " ELSE A2_CGC END AS Cnpj, " + CRLF
cQryDados += " E2_FILIAL AS Filial " + CRLF // Adicione na query de contas a pagar
cQryDados += " FROM " + RetSQLName("SE2") + " SE2 " + CRLF
cQryDados += " INNER JOIN " + RetSQLName("SA2") + " SA2 ON (" + CRLF
cQryDados += " A2_FILIAL = '" + FWxFilial("SA2") + "' " + CRLF
cQryDados += " AND A2_COD = E2_FORNECE " + CRLF
cQryDados += " AND A2_LOJA = E2_LOJA " + CRLF
cQryDados += " AND SA2.D_E_L_E_T_ = ' ' ) " + CRLF
cQryDados += "WHERE E2_TIPO = 'NP' " + CRLF
cQryDados += " AND E2_FILIAL = '" + FWxFilial("SE2") + "' " + CRLF
cQryDados += " AND E2_EMISSAO BETWEEN '" + dToS(dDataDe) + "' AND '" + dToS(dDataAt) + "' " + CRLF
cQryDados += " AND E2_FORNECE BETWEEN '" + cForCliDe + "' AND '" + cForCliAte + "' " + CRLF
cQryDados += " AND E2_BAIXA = '' " + CRLF
cQryDados += " AND SE2.D_E_L_E_T_ = ' ' " + CRLF
EndIf
// Executa e popula
PLSQuery(cQryDados, "QRYDADTMP")
DbSelectArea("QRYDADTMP")
Count To nTotal
ProcRegua(nTotal)
QRYDADTMP->(DbGoTop())
While ! QRYDADTMP->(EoF())
nAtual++
IncProc("Analisando registro " + cValToChar(nAtual) + " de " + cValToChar(nTotal))
RecLock(cAliasTmp, .T.)
(cAliasTmp)->OK := " "
(cAliasTmp)->Tipo := QRYDADTMP->Tipo
(cAliasTmp)->Numero := QRYDADTMP->Numero
(cAliasTmp)->Prefixo := QRYDADTMP->Prefixo
(cAliasTmp)->Codigo := QRYDADTMP->Codigo
(cAliasTmp)->Nome := QRYDADTMP->Nome
(cAliasTmp)->Valor := QRYDADTMP->Valor
(cAliasTmp)->Historico := QRYDADTMP->Historico
(cAliasTmp)->Razao := QRYDADTMP->Razao
(cAliasTmp)->Endereco := QRYDADTMP->Endereco
(cAliasTmp)->Vencimento := STOD(QRYDADTMP->Vencimento) // Converta string para data
(cAliasTmp)->Complemen := QRYDADTMP->Complemen
(cAliasTmp)->Cnpj := QRYDADTMP->Cnpj
(cAliasTmp)->Filial := QRYDADTMP->Filial // Popule o campo filial
(cAliasTmp)->(MsUnlock())
QRYDADTMP->(DbSkip())
EndDo
QRYDADTMP->(DbCloseArea())
(cAliasTmp)->(DbGoTop())
Return
/*/{Protheus.doc} User Function fCriaCols
Função fCriaCols que cria as colunas do browse
@type Function
@author Gabriel Dahm
@since 25/08/2025
@version 1.0
/*/
Static Function fCriaCols()
Local aColunas := {}
Local aEstrut := {}
Local oColumn
Local nI
aAdd(aEstrut, { "Tipo", "Tipo", "C", 20, 0, "" })
aAdd(aEstrut, { "Numero", "Número", "C", 10, 0, "" })
aAdd(aEstrut, { "Prefixo", "Prefixo", "C", 5, 0, "" })
aAdd(aEstrut, { "Codigo", "Código", "C", 10, 0, "" })
aAdd(aEstrut, { "Nome", "Nome", "C", 50, 0, "" })
aAdd(aEstrut, { "Valor", "Valor", "N", 16, 2, "@E 999,999,999.99" })
aAdd(aEstrut, { "Historico", "Histórico", "C", 50, 0, "" })
For nI := 1 To Len(aEstrut)
oColumn := FWBrwColumn():New()
oColumn:SetData( &( "{|| " + cAliasTmp + "->" + aEstrut[nI][1] + " }" ) )
oColumn:SetTitle(aEstrut[nI][2])
oColumn:SetType(aEstrut[nI][3])
oColumn:SetSize(aEstrut[nI][4])
oColumn:SetDecimal(aEstrut[nI][5])
oColumn:SetPicture(aEstrut[nI][6])
aAdd(aColunas, oColumn)
Next
Return aColunas
/*/{Protheus.doc} User Function MenuDef
Função Cria botão continuar no menu do browse
@type Function
@author Gabriel Dahm
@since 25/08/2025
@version 1.0
/*/
Static Function MenuDef()
Local aRotina := {}
ADD OPTION aRotina TITLE "Continuar" ACTION "u_zImprimeProm" OPERATION 2 ACCESS 0
Return aRotina
// Função para quebra automática por palavra
Static Function MyWordWrap(cTexto, nTam)
Local aRet := {}
Local cLinha := ""
Local aPalavras := StrTokArr(cTexto, " ")
Local nI
For nI := 1 To Len(aPalavras)
If Len(cLinha) + Len(aPalavras[nI]) + 1 <= nTam
If Empty(cLinha)
cLinha := aPalavras[nI]
Else
cLinha += " " + aPalavras[nI]
EndIf
Else
aAdd(aRet, cLinha)
cLinha := aPalavras[nI]
EndIf
Next
If !Empty(cLinha)
aAdd(aRet, cLinha)
EndIf
Return aRet
/*/{Protheus.doc} User Function zImprimeProm
Função feita para realizar a impressão da promissória conforme layout solicitado
@type user function
@author Gabriel Dahm
@version 1.0
/*/
User Function zImprimeProm()
Local oPrint
Local nPageWidth := 600
Local nMargin := 60
Local nBoxWidth := nPageWidth - (2 * nMargin)
Local nLogoWidth := 110
Local nLogoHeight := 80
Local nLinTop := 50
Local nColLeft := nMargin
Local nColTexto := nColLeft + nLogoWidth + 20
Local nTextoWidth := nBoxWidth - nLogoWidth - 30
Local dVenc, cNome, cEnd, cCnpj, nValor, cValorExt, cData, cDia, cMes, cAno, cComplemen
Local nLinhaAtual, nBoxHeight
Local cTexto, aLinhas, nI
Local cFilAnt := FWFilial() // ou ajuste conforme sua filial
Local cAssinatura
Local cLogoEmp := fLogoEmp()
Local aSM0Data := FWSM0Util():GetSM0Data(, cFilAnt, {'M0_NOME', 'M0_CGC', 'M0_ENDENT', 'M0_ESTENT', 'M0_CIDENT'})
Local cEmpNome := aSM0Data[1][2] // Nome da empresa
Local cEmpCNPJ := aSM0Data[2][2] // CNPJ
Local cEmpEnd := aSM0Data[3][2] // Endereço
Local cEmpEstado := aSM0Data[4][2] // Estado
Local cEmpCidade := aSM0Data[5][2] // Cidade
oPrint := FWMSPrinter():New("Promissoria.pdf", IMP_PDF, .F., "", .T.)
oPrint:SetPortrait()
oPrint:SetPaperSize(DMPAPER_A4)
oPrint:SetMargin(10, 10, 10, 10)
DbSelectArea(cAliasTmp)
(cAliasTmp)->(DbGoTop())
While !(cAliasTmp)->(EoF())
If oMarkBrowse:IsMark()
oPrint:StartPage()
nBoxHeight := 120
oPrint:Box(nLinTop, nColLeft, nLinTop + nBoxHeight, nColLeft + nBoxWidth)
oPrint:Line(nLinTop, nColLeft + nLogoWidth, nLinTop + nBoxHeight, nColLeft + nLogoWidth)
// Imprimindo logo Milênio
If !Empty(cLogoEmp) .And. File(cLogoEmp)
oPrint:SayBitmap(nLinTop + 10, nColLeft + 10, cLogoEmp, nLogoWidth - 20, nLogoHeight - 20)
Else
oPrint:Say(nLinTop + 40, nColLeft + 20, "Logo da Empresa", TFont():New("Arial",,10), nLogoWidth - 20, 20, COR_PRETO, PAD_CENTER, 0)
EndIf
dVenc := (cAliasTmp)->Vencimento
cNome := (cAliasTmp)->Nome
cEnd := (cAliasTmp)->Endereco
cCnpj := (cAliasTmp)->Cnpj
nValor := (cAliasTmp)->Valor
cValorExt := Extenso(nValor)
cData := DtoC(dVenc)
cDia := SubStr(cData,1,2)
cMes := SubStr(cData,4,2)
cAno := SubStr(cData,7,4)
cComplemen := (cAliasTmp)->Complemen
cValorFormat := Transform(nValor, "999,999,999.99")
If AllTrim(Upper((cAliasTmp)->Tipo)) == "CONTAS A PAGAR"
// Modelo para contas a pagar: nome e cnpj da empresa, assinatura da empresa
cTexto := "No dia " + cDia + " do mês de " + cMes + " do ano " + cAno + ", " + ;
cEmpNome + ", com endereço a " + cEmpEnd + ", compromete-se pagar por esta via de nota promissória na praça " + cEmpEnd + ", " + cEmpCidade + "/" + cEmpEstado +" a " + ;
cEmpNome + ", CPF/CNPJ de número " + cEmpCNPJ + ", a quantia de valor " + cValorFormat + " (" + cValorExt + ")."
cAssinatura := "Assinatura da empresa"
Else // Contas a Receber
// Modelo padrão: nome e cnpj do cliente, assinatura do cliente
cTexto := "No dia " + cDia + " do mês de " + cMes + " do ano " + cAno + ", " + ;
cNome + ", com endereço a " + cEnd + ", compromete-se pagar por esta via de nota promissória na praça " + cComplemen + " a " + ;
cNome + ", CPF/CNPJ de número " + cCnpj + ", a quantia de valor " + cValorFormat + " (" + cValorExt + ")."
cAssinatura := "Assinatura do cliente"
EndIf
aLinhas := MyWordWrap(cTexto, 70)
nLinhaAtual := nLinTop + 10
For nI := 1 To Len(aLinhas)
oPrint:Say(nLinhaAtual, nColTexto, aLinhas[nI], TFont():New("Arial",,12), nTextoWidth, 20, COR_PRETO, PAD_LEFT, 0)
nLinhaAtual += 15
Next
nLinhaAtual += 15
oPrint:Say(nLinhaAtual, nColTexto, "____________________")
nLinhaAtual += 15
oPrint:Say(nLinhaAtual, nColTexto, cAssinatura)
oPrint:EndPage()
EndIf
(cAliasTmp)->(DbSkip())
EndDo
oPrint:Preview()
If Type("oDlgMark") == "O" .And. oDlgMark != NIL
oDlgMark:End() // Fecha o browser/tela após a impressão
EndIf
Return
/*/{Protheus.doc} Static Function fLogoEmp
Função que retorna o logo da empresa (igual a DANFE)
@author Daniel Atilio
@since 19/06/2016
@version 1.0
@type function
/*/
Static Function fLogoEmp()
Local cLogo := ""
Local cStart := GetSrvProfString("Startpath", "")
Local cFilAnt := FWFilial() // ou ajuste conforme sua filial
// Pega a imagem
cLogo := cStart + "LGMID01" + cFilAnt + ".png"
Return cLogo
Bom pessoal, por hoje é só.
Abraços e até a próxima.