sefaz 0.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +115 -0
- data/Gemfile.lock +31 -17
- data/README.md +449 -17
- data/Rakefile +1 -0
- data/docs/CFe-SAT/Especificacao_SAT_v_ER_2_30_03.pdf +0 -0
- data/docs/CFe-SAT/Manual_Orientacao_SAT_v_MO_2_19_04.pdf +0 -0
- data/docs/CFe-SAT/Manual_do_Emulador_SAT-CF-e_Offline_2015_09_10.pdf +0 -0
- data/docs/CFe-SAT/_CANCEL_dataset.md +14 -0
- data/docs/CFe-SAT/_SALE_dataset.md +171 -0
- data/docs/CFe-SAT/emulador_off_line_v2_9_4.zip +0 -0
- data/docs/NFe-NFCe/ANEXO I - Leiaute e Regra de Valida/303/247/303/243o - NF-e e NFC-e.pdf +0 -0
- data/docs/NFe-NFCe/ANEXO II -Manual Especifica/303/247/303/265esT/303/251cnicas - Danfe-C/303/263digo-Barras.pdf +0 -0
- data/docs/NFe-NFCe/Anexo III - Manual de Conting/303/252ncia - NF-e.pdf +0 -0
- data/docs/NFe-NFCe/Anexo IV - Manual de Conting/303/252ncia - NFC-e.pdf +0 -0
- data/docs/NFe-NFCe/Manual de Orienta/303/247/303/243o ao Contribuinte - MOC - vers/303/243o 7.0 - NF-e e NFC-e.pdf +0 -0
- data/docs/NFe-NFCe/_dataset.md +819 -0
- data/lib/sefaz/base.rb +11 -0
- data/lib/sefaz/configuration.rb +58 -0
- data/lib/sefaz/exception.rb +14 -0
- data/lib/sefaz/refinement.rb +91 -0
- data/lib/sefaz/utils/connection.rb +35 -0
- data/lib/sefaz/utils/prawn_helper.rb +47 -0
- data/lib/sefaz/utils/signer.rb +122 -0
- data/lib/sefaz/version.rb +1 -1
- data/lib/sefaz/webservice/base.rb +13 -0
- data/lib/sefaz/webservice/nfe/auditor.rb +35 -0
- data/lib/sefaz/webservice/nfe/client.rb +426 -0
- data/lib/sefaz/webservice/nfe/connection.rb +17 -0
- data/lib/sefaz/webservice/nfe/dataset.rb +451 -0
- data/lib/sefaz/webservice/nfe/validator.rb +63 -0
- data/lib/sefaz/webservice/nfe/wsdl.rb +117 -0
- data/lib/sefaz/webservice/sat/client.rb +44 -0
- data/lib/sefaz/webservice/sat/dataset/cancel.rb +34 -0
- data/lib/sefaz/webservice/sat/dataset/sale.rb +176 -0
- data/lib/sefaz/webservice/sat/templates/base.rb +54 -0
- data/lib/sefaz/webservice/sat/templates/cupom_fiscal_55mm.rb +307 -0
- data/lib/sefaz/webservice/sat/templates/cupom_fiscal_80mm.rb +307 -0
- data/lib/sefaz.rb +45 -3
- data/sefaz.gemspec +5 -3
- metadata +80 -6
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SEFAZ
|
4
|
+
module Webservice
|
5
|
+
module SAT
|
6
|
+
# Principal classe de integração com o módulo CFe-SAT
|
7
|
+
class Client < SEFAZ::Webservice::Base
|
8
|
+
|
9
|
+
SERVICES = %i[ exportarCF ]
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
end
|
13
|
+
|
14
|
+
# Exportar CFe (Cupom Fiscal Eletrônico) no formato PDF
|
15
|
+
# Para gerar o CFe de Cancelamento, é obrigatório informar o @canc (CFe de Cancelmaneto) junto com o @aut (CFe de Movimento)
|
16
|
+
#
|
17
|
+
# Exemplo de CFe de Movimento:
|
18
|
+
# @pdf = @client.exportarCF(80, aut: @aut, infAdic: "Participe da Promoção de Natal.")
|
19
|
+
#
|
20
|
+
# Exemplo de CFe de Cancelmaneto:
|
21
|
+
# @pdf = @client.exportarCF(80, aut: @aut, canc: @canc, infAdic: "Participe da Promoção de Natal.")
|
22
|
+
#
|
23
|
+
# @mm(Integer) = Milímetro da bobina termica para gerar o documento PDF (opções=55,80)
|
24
|
+
# @aut(Hash ou String) = XML ou Hash - Autorizado
|
25
|
+
# @canc(Hash ou String) = XML ou Hash - Cancelamento (OPCIONAL)
|
26
|
+
# @summary(Boolean) = Exibir Extrato de Movimento no formato resumido (OPCIONAL)
|
27
|
+
# @infAdic(String) = Informações adicionais de rodapé do CFe (OPCIONAL)
|
28
|
+
def exportarCF(mm, aut:, canc: nil, summary: false, infAdic: nil)
|
29
|
+
aut = (aut.is_a?(Hash) ? aut : aut.to_hash!)
|
30
|
+
canc = (canc.is_a?(Hash) ? canc : canc.to_hash!) if canc
|
31
|
+
case mm
|
32
|
+
when 55
|
33
|
+
SEFAZ::Webservice::SAT::Templates::CupomFiscal55mm.new.render(aut, canc, summary, infAdic)
|
34
|
+
when 80
|
35
|
+
SEFAZ::Webservice::SAT::Templates::CupomFiscal80mm.new.render(aut, canc, summary, infAdic)
|
36
|
+
else
|
37
|
+
SEFAZ::Webservice::SAT::Templates::Base.new.render(aut, canc, summary, infAdic)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SEFAZ
|
4
|
+
module Webservice
|
5
|
+
module SAT
|
6
|
+
module Dataset
|
7
|
+
# Principal classe de elaboração do XML de Cancelamento para o módulo CFe-SAT
|
8
|
+
class Cancel
|
9
|
+
|
10
|
+
attr_accessor :ide
|
11
|
+
|
12
|
+
IDE = Struct.new("IDE", :CNPJ, :signAC, :numeroCaixa)
|
13
|
+
|
14
|
+
def initialize(chCanc)
|
15
|
+
@chCanc = chCanc
|
16
|
+
@ide = IDE.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def gerarCF
|
20
|
+
hash = { CFeCanc: { infCFe: { :@chCanc => ("CFe" + @chCanc.to_s) } } }
|
21
|
+
hash[:CFeCanc][:infCFe][:ide] = @ide.to_h
|
22
|
+
hash[:CFeCanc][:infCFe][:emit] = {}
|
23
|
+
hash[:CFeCanc][:infCFe][:dest] = {}
|
24
|
+
hash[:CFeCanc][:infCFe][:total] = {}
|
25
|
+
hash[:CFeCanc][:infCFe][:infAdic] = {}
|
26
|
+
|
27
|
+
return [hash.to_xml!, hash]
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SEFAZ
|
4
|
+
module Webservice
|
5
|
+
module SAT
|
6
|
+
module Dataset
|
7
|
+
# Principal classe de elaboração do XML de Venda para o módulo CFe-SAT
|
8
|
+
class Sale
|
9
|
+
|
10
|
+
attr_accessor :ide, :emit, :dest, :entrega, :det, :obsfiscodet, :total, :pgto, :mp, :infAdic
|
11
|
+
|
12
|
+
IDE = Struct.new("IDE", :CNPJ, :signAC, :numeroCaixa)
|
13
|
+
EMIT = Struct.new("EMIT", :CNPJ, :IE, :IM, :cRegTribISSQN, :indRatISSQN)
|
14
|
+
DEST = Struct.new("DEST", :CNPJ, :CPF, :xNome)
|
15
|
+
ENTREGA = Struct.new("ENTREGA", :xLgr, :nro, :xCpl, :xBairro, :xMun, :UF)
|
16
|
+
DET = Struct.new("DET", :@nItem, :prod, :imposto, :infAdProd)
|
17
|
+
PROD = Struct.new("PROD", :cProd, :cEAN, :xProd, :NCM, :CEST, :CFOP, :uCom, :qCom, :vUnCom, :indRegra, :vDesc, :vOutro, :obsFiscoDet, :cANP)
|
18
|
+
OBSFISCODET = Struct.new("OBSFISCODET", :xCampoDet, :xTextoDet)
|
19
|
+
IMPOSTO = Struct.new("IMPOSTO", :vItem12741, :ICMS, :PIS, :PISST, :COFINS, :COFINSST, :ISSQN)
|
20
|
+
ICMS = Struct.new("ICMS", :ICMS00, :ICMS40, :ICMSSN102, :ICMSSN900)
|
21
|
+
ICMS00 = Struct.new("ICMS00", :Orig, :CST, :pICMS)
|
22
|
+
ICMS40 = Struct.new("ICMS40", :Orig, :CST)
|
23
|
+
ICMSSN102 = Struct.new("ICMSSN102", :Orig, :CSOSN)
|
24
|
+
ICMSSN900 = Struct.new("ICMSSN900", :Orig, :CSOSN, :pICMS)
|
25
|
+
PIS = Struct.new("PIS", :PISAliq, :PISQtde, :PISNT, :PISSN, :PISOutr)
|
26
|
+
PISAliq = Struct.new("PISAliq", :CST, :vBC, :pPIS)
|
27
|
+
PISQtde = Struct.new("PISQtde", :CST, :qBCProd, :vAliqProd)
|
28
|
+
PISNT = Struct.new("PISNT", :CST)
|
29
|
+
PISSN = Struct.new("PISSN", :CST)
|
30
|
+
PISOutr = Struct.new("PISOutr", :CST, :vBC, :pPIS, :qBCProd, :vAliqProd)
|
31
|
+
PISST = Struct.new("PISST", :vBC, :pPIS, :qBCProd, :vAliqProd)
|
32
|
+
COFINS = Struct.new("COFINS", :COFINSAliq, :COFINSQtde, :COFINSNT, :COFINSSN, :COFINSOutr)
|
33
|
+
COFINSAliq = Struct.new("COFINSAliq", :CST, :vBC, :pCOFINS)
|
34
|
+
COFINSQtde = Struct.new("COFINSQtde", :CST, :qBCProd, :vAliqProd)
|
35
|
+
COFINSNT = Struct.new("COFINSNT", :CST)
|
36
|
+
COFINSSN = Struct.new("COFINSSN", :CST)
|
37
|
+
COFINSOutr = Struct.new("COFINSOutr", :CST, :vBC, :pCOFINS, :qBCProd, :vAliqProd)
|
38
|
+
COFINSST = Struct.new("COFINSST", :vBC, :pCOFINS, :qBCProd, :vAliqProd)
|
39
|
+
ISSQN = Struct.new("ISSQN", :vDeducISSQN, :vAliq, :cMunFG, :cListServ, :cServTribMun, :cNatOp, :indIncFisc)
|
40
|
+
TOTAL = Struct.new("TOTAL", :DescAcrEntr, :vCFeLei12741)
|
41
|
+
DescAcrEntr = Struct.new("DescAcrEntr", :vDescSubtot, :vAcresSubtot)
|
42
|
+
PGTO = Struct.new("PGTO", :MP)
|
43
|
+
MP = Struct.new("MP", :cMP, :vMP, :cAdmC, :cAut)
|
44
|
+
INFADIC = Struct.new("INFADIC", :infCpl)
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
@versaoDadosEnt = "0.08"
|
48
|
+
|
49
|
+
@ide = IDE.new
|
50
|
+
@emit = EMIT.new
|
51
|
+
@dest = DEST.new
|
52
|
+
@entrega = ENTREGA.new
|
53
|
+
@total = TOTAL.new
|
54
|
+
@total.DescAcrEntr = DescAcrEntr.new
|
55
|
+
@pgto = PGTO.new
|
56
|
+
@pgto.MP = []
|
57
|
+
@infAdic = INFADIC.new
|
58
|
+
|
59
|
+
@listas = {}
|
60
|
+
@listas[:det] = []
|
61
|
+
end
|
62
|
+
|
63
|
+
def gerarCF
|
64
|
+
# Raiz = CFe
|
65
|
+
# A01 = infCFe
|
66
|
+
# H01 = det
|
67
|
+
# I01 = prod
|
68
|
+
# M01 = imposto
|
69
|
+
# N01 = ICMS
|
70
|
+
hash = { CFe: { infCFe: { :@versaoDadosEnt => @versaoDadosEnt } } }
|
71
|
+
hash[:CFe][:infCFe][:ide] = @ide.to_h
|
72
|
+
hash[:CFe][:infCFe][:emit] = @emit.to_h
|
73
|
+
hash[:CFe][:infCFe][:dest] = @dest.to_h
|
74
|
+
hash[:CFe][:infCFe][:entrega] = @entrega.to_h
|
75
|
+
hash[:CFe][:infCFe][:det] = @listas[:det]
|
76
|
+
hash[:CFe][:infCFe][:total] = @total.to_h
|
77
|
+
hash[:CFe][:infCFe][:total][:DescAcrEntr] = @total.DescAcrEntr.to_h
|
78
|
+
hash[:CFe][:infCFe][:pgto] = @pgto.to_h
|
79
|
+
hash[:CFe][:infCFe][:infAdic] = @infAdic.to_h
|
80
|
+
compressed = hash.compress!
|
81
|
+
|
82
|
+
return [compressed.to_xml!, compressed]
|
83
|
+
end
|
84
|
+
|
85
|
+
def add(part_name)
|
86
|
+
part_object = case part_name
|
87
|
+
when "DET"
|
88
|
+
@det = DET.new
|
89
|
+
@det.prod = PROD.new
|
90
|
+
@det.prod.obsFiscoDet = []
|
91
|
+
@det.imposto = IMPOSTO.new
|
92
|
+
@det.imposto.ICMS = ICMS.new
|
93
|
+
@det.imposto.ICMS.ICMS00 = ICMS00.new
|
94
|
+
@det.imposto.ICMS.ICMS40 = ICMS40.new
|
95
|
+
@det.imposto.ICMS.ICMSSN102 = ICMSSN102.new
|
96
|
+
@det.imposto.ICMS.ICMSSN900 = ICMSSN900.new
|
97
|
+
@det.imposto.PIS = PIS.new
|
98
|
+
@det.imposto.PIS.PISAliq = PISAliq.new
|
99
|
+
@det.imposto.PIS.PISQtde = PISQtde.new
|
100
|
+
@det.imposto.PIS.PISNT = PISNT.new
|
101
|
+
@det.imposto.PIS.PISSN = PISSN.new
|
102
|
+
@det.imposto.PIS.PISOutr = PISOutr.new
|
103
|
+
@det.imposto.PISST = PISST.new
|
104
|
+
@det.imposto.COFINS = COFINS.new
|
105
|
+
@det.imposto.COFINS.COFINSAliq = COFINSAliq.new
|
106
|
+
@det.imposto.COFINS.COFINSQtde = COFINSQtde.new
|
107
|
+
@det.imposto.COFINS.COFINSNT = COFINSNT.new
|
108
|
+
@det.imposto.COFINS.COFINSSN = COFINSSN.new
|
109
|
+
@det.imposto.COFINS.COFINSOutr = COFINSOutr.new
|
110
|
+
@det.imposto.COFINSST = COFINSST.new
|
111
|
+
@det.imposto.ISSQN = ISSQN.new
|
112
|
+
@det
|
113
|
+
when "OBSFISCODET"
|
114
|
+
@obsfiscodet = OBSFISCODET.new
|
115
|
+
@obsfiscodet
|
116
|
+
when "MP"
|
117
|
+
@mp = MP.new
|
118
|
+
@mp
|
119
|
+
end
|
120
|
+
|
121
|
+
if block_given?
|
122
|
+
yield(part_object)
|
123
|
+
save(part_name)
|
124
|
+
end
|
125
|
+
|
126
|
+
nil
|
127
|
+
end
|
128
|
+
|
129
|
+
def save(part_name)
|
130
|
+
case part_name
|
131
|
+
when "DET"
|
132
|
+
if @det.is_a?(Struct::DET)
|
133
|
+
item = @det
|
134
|
+
item.prod = item.prod.to_h
|
135
|
+
item.imposto.ICMS.ICMS00 = item.imposto.ICMS.ICMS00.to_h
|
136
|
+
item.imposto.ICMS.ICMS40 = item.imposto.ICMS.ICMS40.to_h
|
137
|
+
item.imposto.ICMS.ICMSSN102 = item.imposto.ICMS.ICMSSN102.to_h
|
138
|
+
item.imposto.ICMS.ICMSSN900 = item.imposto.ICMS.ICMSSN900.to_h
|
139
|
+
item.imposto.ICMS = item.imposto.ICMS.to_h
|
140
|
+
item.imposto.PIS.PISAliq = item.imposto.PIS.PISAliq.to_h
|
141
|
+
item.imposto.PIS.PISQtde = item.imposto.PIS.PISQtde.to_h
|
142
|
+
item.imposto.PIS.PISNT = item.imposto.PIS.PISNT.to_h
|
143
|
+
item.imposto.PIS.PISSN = item.imposto.PIS.PISSN.to_h
|
144
|
+
item.imposto.PIS.PISOutr = item.imposto.PIS.PISOutr.to_h
|
145
|
+
item.imposto.PIS = item.imposto.PIS.to_h
|
146
|
+
item.imposto.PISST = item.imposto.PISST.to_h
|
147
|
+
item.imposto.COFINS.COFINSAliq = item.imposto.COFINS.COFINSAliq.to_h
|
148
|
+
item.imposto.COFINS.COFINSQtde = item.imposto.COFINS.COFINSQtde.to_h
|
149
|
+
item.imposto.COFINS.COFINSNT = item.imposto.COFINS.COFINSNT.to_h
|
150
|
+
item.imposto.COFINS.COFINSSN = item.imposto.COFINS.COFINSSN.to_h
|
151
|
+
item.imposto.COFINS.COFINSOutr = item.imposto.COFINS.COFINSOutr.to_h
|
152
|
+
item.imposto.COFINS = item.imposto.COFINS.to_h
|
153
|
+
item.imposto.COFINSST = item.imposto.COFINSST.to_h
|
154
|
+
item.imposto.ISSQN = item.imposto.ISSQN.to_h
|
155
|
+
item.imposto = item.imposto.to_h
|
156
|
+
item[:@nItem] = (@listas[:det].length + 1)
|
157
|
+
@listas[:det].push(item.to_h)
|
158
|
+
end
|
159
|
+
when "OBSFISCODET"
|
160
|
+
if @det.is_a?(Struct::DET) && @obsfiscodet.is_a?(Struct::OBSFISCODET)
|
161
|
+
@det.prod.obsFiscoDet.push(@obsfiscodet.to_h)
|
162
|
+
end
|
163
|
+
when "MP"
|
164
|
+
if @pgto.is_a?(Struct::PGTO) && @mp.is_a?(Struct::MP)
|
165
|
+
@pgto.MP.push(@mp.to_h)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
nil
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SEFAZ
|
4
|
+
module Webservice
|
5
|
+
module SAT
|
6
|
+
module Templates
|
7
|
+
# Classe base dos templates para CFe-SAT
|
8
|
+
class Base
|
9
|
+
|
10
|
+
include SEFAZ::Utils::PrawnHelper
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@page_width = 0
|
14
|
+
@margin = 0
|
15
|
+
@page_break = 80.m
|
16
|
+
@page_layout = :portrait
|
17
|
+
end
|
18
|
+
|
19
|
+
def build(doc, aut, canc, summary, infAdic)
|
20
|
+
raise SEFAZ::NotImplemented, "Você deve implementar o método 'build' em uma subclasse."
|
21
|
+
end
|
22
|
+
|
23
|
+
def render(aut, canc, summary, infAdic)
|
24
|
+
validation(aut, canc)
|
25
|
+
|
26
|
+
Prawn::Document.new(page_size: [@page_width, @page_break], margin: @margin, page_layout: @page_layout) do |doc|
|
27
|
+
build(doc, aut, canc, summary, infAdic)
|
28
|
+
doc.page.dictionary.data[:MediaBox] = [0, (doc.y - @margin), @page_width, @page_break]
|
29
|
+
end.render
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def validation(aut, canc)
|
35
|
+
unless aut.is_a?(Hash) && aut.dig(:CFe, :Signature) && aut.dig(:CFe, :infCFe, :@Id) &&
|
36
|
+
aut.dig(:CFe, :infCFe, :ide) && aut.dig(:CFe, :infCFe, :emit) && aut.dig(:CFe, :infCFe, :emit, :enderEmit) &&
|
37
|
+
aut.dig(:CFe, :infCFe, :det) && aut.dig(:CFe, :infCFe, :total) && aut.dig(:CFe, :infCFe, :pgto)
|
38
|
+
raise SEFAZ::ValidationError, "Layout do arquivo CFe-SAT inválido (Tipo de Arquivo: AUTORIZADO)."
|
39
|
+
end
|
40
|
+
|
41
|
+
if canc
|
42
|
+
unless canc.is_a?(Hash) && canc.dig(:CFeCanc, :Signature) && canc.dig(:CFeCanc, :infCFe, :@Id) && canc.dig(:CFeCanc, :infCFe, :@chCanc) &&
|
43
|
+
canc.dig(:CFeCanc, :infCFe, :ide) && canc.dig(:CFeCanc, :infCFe, :emit) && canc.dig(:CFeCanc, :infCFe, :emit, :enderEmit) &&
|
44
|
+
canc.dig(:CFeCanc, :infCFe, :total) && (aut.dig(:CFe, :infCFe, :@Id) == canc.dig(:CFeCanc, :infCFe, :@chCanc))
|
45
|
+
raise SEFAZ::ValidationError, "Layout do arquivo CFe-SAT inválido (Tipo de Arquivo: CANCELAMENTO)."
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,307 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SEFAZ
|
4
|
+
module Webservice
|
5
|
+
module SAT
|
6
|
+
module Templates
|
7
|
+
# Template para CFe-SAT 55mm em PDF
|
8
|
+
class CupomFiscal55mm < SEFAZ::Webservice::SAT::Templates::Base
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
super
|
12
|
+
@page_width = Configuration.default.cfe_page_width_55mm
|
13
|
+
@margin = Configuration.default.cfe_margin_55mm
|
14
|
+
end
|
15
|
+
|
16
|
+
def build(doc, aut, canc, summary, infAdic)
|
17
|
+
doc.font "Courier", style: :normal
|
18
|
+
doc.font_size 5
|
19
|
+
|
20
|
+
infCFe = (canc ? canc.dig(:CFeCanc, :infCFe) : aut.dig(:CFe, :infCFe))
|
21
|
+
header(doc, aut)
|
22
|
+
body(doc, aut, summary, infCFe.dig(:@chCanc))
|
23
|
+
footer(doc, infCFe, infAdic)
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# CABEÇALHO (Aut)
|
28
|
+
#
|
29
|
+
def header(doc, aut)
|
30
|
+
emit = aut.dig(:CFe, :infCFe, :emit)
|
31
|
+
enderEmit = aut.dig(:CFe, :infCFe, :emit, :enderEmit)
|
32
|
+
|
33
|
+
# Dados do emitente
|
34
|
+
doc.text emit.dig(:xFant).to_s.strip.upcase, align: :center
|
35
|
+
doc.text emit.dig(:xNome).to_s.strip.upcase, align: :center
|
36
|
+
doc.text "#{enderEmit.dig(:xLgr)} #{enderEmit.dig(:nro)} #{enderEmit.dig(:xCpl)} #{enderEmit.dig(:xBairro)} #{enderEmit.dig(:xMun)} #{enderEmit.dig(:CEP)}".to_s.strip.upcase, align: :center
|
37
|
+
doc.text "CNPJ #{emit.dig(:CNPJ).to_s.mask!("##.###.###/####-##")} IE #{emit.dig(:IE).to_s.mask!("###.###.###.###")}".to_s.strip.upcase, align: :center
|
38
|
+
doc.text "IM #{emit.dig(:IM).to_s.mask!("####.###.###.###")}".to_s.strip.upcase, align: :center if emit.dig(:IM)
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# CORPO DO EXTRATO (Aut)
|
43
|
+
#
|
44
|
+
# Exibe os dados de forma dinâmica:
|
45
|
+
# Quando CFe Movimento: exibe lista dos itens a partir do XML AUTORIZADO (Resumido ou Não Resumido).
|
46
|
+
# Quando CFe Cancelamento: exibe os dados do XML AUTORIZADO em modo cancelado.
|
47
|
+
#
|
48
|
+
def body(doc, aut, summary, chCanc)
|
49
|
+
tpAmb = aut.dig(:CFe, :infCFe, :ide, :tpAmb)
|
50
|
+
nCFe = (tpAmb == 1) ? aut.dig(:CFe, :infCFe, :ide, :nCFe) : "000000"
|
51
|
+
|
52
|
+
# (I) - Título
|
53
|
+
doc.text "Extrato #{nCFe} do", style: :bold, align: :center
|
54
|
+
doc.text "CUPOM FISCAL ELETRÔNICO - SAT", style: :bold, align: :center
|
55
|
+
|
56
|
+
# (A) - Título - Cancelamento
|
57
|
+
if chCanc
|
58
|
+
doc.move_down 1.mm
|
59
|
+
doc.text "CANCELAMENTO", style: :bold, align: :center
|
60
|
+
end
|
61
|
+
|
62
|
+
# (TESTE) Marca de Teste
|
63
|
+
if tpAmb == "2"
|
64
|
+
doc.move_down doc.font_size
|
65
|
+
|
66
|
+
doc.text "= TESTE =", style: :italic, align: :center
|
67
|
+
doc.move_down doc.font_size
|
68
|
+
3.times do
|
69
|
+
doc.text_box (">" * 100), at: [0, doc.cursor], height: doc.font_size, width: doc.bounds.width
|
70
|
+
doc.move_down doc.font_size
|
71
|
+
doc.move_down doc.font_size
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Exibe o extrato no modo cancelado caso exista Chave de Cancelamento, caso contrário exibe no modo movimento
|
76
|
+
if chCanc
|
77
|
+
body_canceled_mode(doc, aut)
|
78
|
+
else
|
79
|
+
body_movement_mode(doc, aut, summary)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# CORPO DO EXTRATO > MODO MOVIMENTO (Aut)
|
85
|
+
#
|
86
|
+
# Exibe lista dos itens.
|
87
|
+
#
|
88
|
+
def body_movement_mode(doc, aut, summary)
|
89
|
+
# (II) - Legenda
|
90
|
+
doc.text "#|COD|DESC|QTD|UN|VL UN R$|(VL TR R$)*|VL ITEM R$"
|
91
|
+
dash(doc, gap: 2, space: 0.5, line_width: 0.1, x1: 0, x2: doc.bounds.width, y: doc.cursor)
|
92
|
+
|
93
|
+
# (III) - Registros de item
|
94
|
+
unless summary
|
95
|
+
det = aut.dig(:CFe, :infCFe, :det)
|
96
|
+
det = det.is_a?(Array) ? det : [det]
|
97
|
+
det.each do |i|
|
98
|
+
# DADOS: #|COD|DESC|QTD|UN|VL UN R$|(VL TR R$)*
|
99
|
+
doc.bounding_box([0, doc.cursor], width: (doc.bounds.width - 10.mm)) do
|
100
|
+
doc.text "#{i.dig(:@nItem)} #{i.dig(:prod, :cProd)} #{i.dig(:prod, :xProd)} #{i.dig(:prod, :qCom)} #{i.dig(:prod, :uCom)} X #{i.dig(:prod, :vUnCom).to_f.to_currency} (#{i.dig(:imposto, :vItem12741).to_f.to_currency})"
|
101
|
+
end
|
102
|
+
|
103
|
+
# DADOS: VL ITEM R$
|
104
|
+
doc.move_down -doc.font_size
|
105
|
+
doc.text i.dig(:prod, :vProd).to_f.to_currency, align: :right
|
106
|
+
|
107
|
+
# DADOS: DESCONTO
|
108
|
+
if i.dig(:prod, :vDesc).to_f > 0
|
109
|
+
doc.float { doc.text "-#{i.dig(:prod, :vDesc).to_f.to_currency}", align: :right }
|
110
|
+
doc.text "desconto sobre item"
|
111
|
+
end
|
112
|
+
|
113
|
+
# DADOS: ACRÉSCIMO
|
114
|
+
if i.dig(:prod, :vOutro).to_f > 0
|
115
|
+
doc.float { doc.text "+#{i.dig(:prod, :vOutro).to_f.to_currency}", align: :right }
|
116
|
+
doc.text "acréscimo sobre item"
|
117
|
+
end
|
118
|
+
|
119
|
+
# DADOS: RATEIO DESCONTO
|
120
|
+
if i.dig(:prod, :vRatDesc).to_f > 0
|
121
|
+
doc.float { doc.text "-#{i.dig(:prod, :vRatDesc).to_f.to_currency}", align: :right }
|
122
|
+
doc.text "rateio de desconto sobre subtotal"
|
123
|
+
end
|
124
|
+
|
125
|
+
# DADOS: RATEIO ACRÉSCIMO
|
126
|
+
if i.dig(:prod, :vRatAcr).to_f > 0
|
127
|
+
doc.float { doc.text "+#{i.dig(:prod, :vRatAcr).to_f.to_currency}", align: :right }
|
128
|
+
doc.text "rateio de acréscimo sobre subtotal"
|
129
|
+
end
|
130
|
+
|
131
|
+
# DADOS: ISSQN
|
132
|
+
if i.dig(:imposto, :ISSQN, :vDeducISSQN).to_f > 0
|
133
|
+
doc.float { doc.text "-#{i.dig(:imposto, :ISSQN, :vDeducISSQN).to_f.to_currency}", align: :right }
|
134
|
+
doc.text "dedução para ISSQN"
|
135
|
+
doc.float { doc.text i.dig(:imposto, :ISSQN, :vBC).to_f.to_currency, align: :right }
|
136
|
+
doc.text "base de cálculo ISSQN"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# (IV) - Total do Cupom
|
142
|
+
total = aut.dig(:CFe, :infCFe, :total)
|
143
|
+
|
144
|
+
# DADOS: VALOR BRUTO
|
145
|
+
if (total.dig(:DescAcrEntr, :vDescSubtot).to_f > 0) || (total.dig(:DescAcrEntr, :vAcresSubtot).to_f > 0) || (total.dig(:ICMSTot, :vDesc).to_f > 0) || (total.dig(:ICMSTot, :vOutro).to_f > 0)
|
146
|
+
doc.float { doc.text total.dig(:ICMSTot, :vProd).to_f.to_currency, align: :right }
|
147
|
+
doc.text "Total bruto de itens"
|
148
|
+
end
|
149
|
+
|
150
|
+
# DADOS: TOTAL DESC/ACRES SOBRE ITENS
|
151
|
+
if (total.dig(:ICMSTot, :vDesc).to_f > 0) || (total.dig(:ICMSTot, :vOutro).to_f > 0)
|
152
|
+
valor11 = total.dig(:ICMSTot, :vOutro).to_f - total.dig(:ICMSTot, :vDesc).to_f
|
153
|
+
doc.text "Total de descontos/acréscimos sobre item"
|
154
|
+
doc.text "#{ valor11 > 0 ? '+' : '-' }#{valor11.abs.to_currency}", align: :right
|
155
|
+
end
|
156
|
+
|
157
|
+
# DADOS: DESCONTO SOBRE TOTAL
|
158
|
+
if total.dig(:DescAcrEntr, :vDescSubtot).to_f > 0
|
159
|
+
doc.float { doc.text "-#{total.dig(:DescAcrEntr, :vDescSubtot).to_f.to_currency}", align: :right }
|
160
|
+
doc.text "Desconto sobre subtotal"
|
161
|
+
end
|
162
|
+
|
163
|
+
# DADOS: ACRÉSCIMO SOBRE TOTAL
|
164
|
+
if total.dig(:DescAcrEntr, :vAcresSubtot).to_f > 0
|
165
|
+
doc.float { doc.text "+#{total.dig(:DescAcrEntr, :vAcresSubtot).to_f.to_currency}", align: :right }
|
166
|
+
doc.text "Acréscimo sobre subtotal"
|
167
|
+
end
|
168
|
+
|
169
|
+
# DADOS: TOTAL FINAL
|
170
|
+
doc.float { doc.text total.dig(:vCFe).to_f.to_currency, style: :bold, align: :right }
|
171
|
+
doc.text "Total R$", style: :bold
|
172
|
+
|
173
|
+
# (V) - Meio de Pagamento
|
174
|
+
pgto = aut.dig(:CFe, :infCFe, :pgto)
|
175
|
+
mp = aut.dig(:CFe, :infCFe, :pgto, :MP)
|
176
|
+
mp = mp.is_a?(Array) ? mp : [mp]
|
177
|
+
mp.each do |i|
|
178
|
+
# DADOS: DESCRIÇÃO MEIO DE PAGAMENTO
|
179
|
+
doc.bounding_box([0, doc.cursor], width: (doc.bounds.width - 10.mm)) do
|
180
|
+
doc.text Configuration.default.cfe_cMP_cod_desc[i.dig(:cMP)]
|
181
|
+
end
|
182
|
+
|
183
|
+
# DADOS: VALOR PAGAMENTO
|
184
|
+
doc.move_down -doc.font_size
|
185
|
+
doc.text i.dig(:vMP).to_f.to_currency, align: :right
|
186
|
+
end
|
187
|
+
|
188
|
+
# DADOS: TROCO
|
189
|
+
doc.float { doc.text pgto.dig(:vTroco).to_f.to_currency, align: :right }
|
190
|
+
doc.text "Troco R$"
|
191
|
+
|
192
|
+
# (VI) - Observações do Fisco
|
193
|
+
obsFisco = aut.dig(:CFe, :infCFe, :obsFisco)
|
194
|
+
if obsFisco
|
195
|
+
obsFisco = obsFisco.is_a?(Array) ? obsFisco : [obsFisco]
|
196
|
+
obsFisco.each do |i|
|
197
|
+
doc.text "#{i.dig(:xCampo)}: #{i.dig(:xTexto)}"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# (VII) - Dados para Entrega
|
202
|
+
entrega = aut.dig(:CFe, :infCFe, :entrega)
|
203
|
+
doc.text "ENDEREÇO DE ENTREGA: #{entrega.dig(:xLgr)} #{entrega.dig(:nro)} #{entrega.dig(:xCpl)} #{entrega.dig(:xBairro)} #{entrega.dig(:xMun)} #{entrega.dig(:UF)}".to_s.strip.upcase if entrega
|
204
|
+
|
205
|
+
# (VIII) - Observações do Contribuinte
|
206
|
+
infCFe = aut.dig(:CFe, :infCFe)
|
207
|
+
doc.text "OBSERVAÇÕES DO CONTRIBUINTE"
|
208
|
+
doc.text infCFe.dig(:infCpl)
|
209
|
+
doc.text "Valor aproximado dos tributos deste cupom R$ #{total.dig(:vCFeLei12741).to_f.to_currency}"
|
210
|
+
doc.text "(conforme Lei Fed. 12.741/2012)"
|
211
|
+
doc.move_down doc.font_size
|
212
|
+
end
|
213
|
+
|
214
|
+
#
|
215
|
+
# CORPO DO EXTRATO > MODO CANCELADO (Aut)
|
216
|
+
#
|
217
|
+
# Exibe os dados do XML AUTORIZADO em modo cancelado.
|
218
|
+
# É renderizado o rodapé do XML AUTORIZADO no final do corpo do extrato.
|
219
|
+
#
|
220
|
+
def body_canceled_mode(doc, aut)
|
221
|
+
total = aut.dig(:CFe, :infCFe, :total)
|
222
|
+
infCFe = aut.dig(:CFe, :infCFe)
|
223
|
+
|
224
|
+
# (B) - Dados do Cupom Fiscal Cancelado
|
225
|
+
doc.move_down 1.mm
|
226
|
+
doc.text "DADOS DO CUPOM ELETRÔNICO CANCELADO", style: :bold
|
227
|
+
doc.text "TOTAL R$ #{total.dig(:vCFe).to_f.to_currency}"
|
228
|
+
|
229
|
+
# (RODAPÉ DE MOVIMENTO) Exibe o rodapé do Extrato de Movimento no corpo do Extrato de Cancelamento
|
230
|
+
footer(doc, infCFe, vl_aprox_trib: false)
|
231
|
+
end
|
232
|
+
|
233
|
+
#
|
234
|
+
# RODAPÉ (Aut/Canc)
|
235
|
+
#
|
236
|
+
# Exibe os elementos de rodapé (QR Code, código de barras, número da chave, mensagens fixas, etc).
|
237
|
+
# O método permite exibir o rodapé no modo movimento ou cancelado.
|
238
|
+
#
|
239
|
+
def footer(doc, infCFe, infAdic = nil, vl_aprox_trib: true)
|
240
|
+
id = infCFe.dig(:@Id)
|
241
|
+
chCanc = infCFe.dig(:@chCanc)
|
242
|
+
ide = infCFe.dig(:ide)
|
243
|
+
total = infCFe.dig(:total)
|
244
|
+
|
245
|
+
# Título do cancelamento
|
246
|
+
if chCanc
|
247
|
+
doc.move_down 2
|
248
|
+
dash(doc, gap: 2, space: 0.5, line_width: 0.1, x1: 0, x2: doc.bounds.width, y: doc.cursor)
|
249
|
+
doc.text "DADOS DO CUPOM FISCAL \nELETRÔNICO DE CANCELAMENTO", align: :center, style: :bold
|
250
|
+
end
|
251
|
+
|
252
|
+
# Número da chave do Cupom Fiscal (dividos em 11 blocos com 4 números cada)
|
253
|
+
doc.move_down 2
|
254
|
+
doc.text id.to_s.delete("^0-9").scan(/.{4}/).join(" "), align: :center, style: :bold
|
255
|
+
doc.move_down doc.font_size
|
256
|
+
|
257
|
+
# Código de barra CODE 128-C (divido em duas partes)
|
258
|
+
id.to_s.delete("^0-9").scan(/.{22}/).each do |id_22|
|
259
|
+
barcode_128c(doc, value: id_22, x: 0.5.mm, y: doc.cursor, xdim: 0.8)
|
260
|
+
doc.move_down 3.mm
|
261
|
+
end
|
262
|
+
|
263
|
+
# Informação do consumidor e SAT
|
264
|
+
doc.float do
|
265
|
+
doc.move_down 5.mm
|
266
|
+
doc.bounding_box([75, doc.cursor], width: 55) do
|
267
|
+
# Informação do consumidor (CNPJ/CPF e NOME)
|
268
|
+
if !chCanc && (infCFe.dig(:dest, :CNPJ) || infCFe.dig(:dest, :CPF))
|
269
|
+
doc.text "Consumidor:", align: :right, style: :bold
|
270
|
+
doc.text infCFe.dig(:dest, :CNPJ).to_s.mask!("##.###.###/####-##"), align: :right if infCFe.dig(:dest, :CNPJ)
|
271
|
+
doc.text infCFe.dig(:dest, :CPF).to_s.mask!("###.###.###-##"), align: :right if infCFe.dig(:dest, :CPF)
|
272
|
+
doc.text infCFe.dig(:dest, :xNome), align: :right if infCFe.dig(:dest, :xNome)
|
273
|
+
end
|
274
|
+
|
275
|
+
# Informação do SAT (Nº Série SAT)
|
276
|
+
doc.text "No. Serie do SAT:", align: :right, style: :bold
|
277
|
+
doc.text ide.dig(:nserieSAT), align: :right
|
278
|
+
doc.text DateTime.parse("#{ide.dig(:dEmi)}T#{ide.dig(:hEmi)}").strftime("%d/%m/%Y\n%H:%M:%S"), align: :right
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# QR Code do Cupom Fiscal = Id|(dEmi)(hEmi)|vCFe|(CNPJ/CPF)|assinaturaQRCODE
|
283
|
+
doc.move_down 2.mm
|
284
|
+
codigo_qr_code = "#{id}|#{ide.dig(:dEmi)}#{ide.dig(:hEmi)}|#{total.dig(:vCFe)}|#{(infCFe.dig(:dest, :CNPJ) || infCFe.dig(:dest, :CPF))}|#{ide.dig(:assinaturaQRCODE)}"
|
285
|
+
qrcode(doc, value: codigo_qr_code, x: 0, y: doc.cursor)
|
286
|
+
doc.move_down 5.mm
|
287
|
+
|
288
|
+
# Mensagens fixas do Cupom Fiscal
|
289
|
+
if !chCanc
|
290
|
+
doc.text "Consulte o QR Code pelo aplicativo \"#{Configuration.default.cfe_qr_code_aplicativo}\",\ndisponível na AppStore (Apple) e PlayStore (Android)", align: :center, size: 4
|
291
|
+
doc.move_down doc.font_size
|
292
|
+
doc.text "*valor aproximado dos tributos do item", align: :center, size: 4 if vl_aprox_trib
|
293
|
+
end
|
294
|
+
|
295
|
+
# Informações adicionais do Cupom Fiscal (customizado pelo contribuinte)
|
296
|
+
if infAdic
|
297
|
+
doc.move_down 2
|
298
|
+
dash(doc, gap: 2, space: 0.5, line_width: 0.1, x1: 0, x2: doc.bounds.width, y: doc.cursor)
|
299
|
+
doc.text infAdic, align: :justify
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|