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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +115 -0
  3. data/Gemfile.lock +31 -17
  4. data/README.md +449 -17
  5. data/Rakefile +1 -0
  6. data/docs/CFe-SAT/Especificacao_SAT_v_ER_2_30_03.pdf +0 -0
  7. data/docs/CFe-SAT/Manual_Orientacao_SAT_v_MO_2_19_04.pdf +0 -0
  8. data/docs/CFe-SAT/Manual_do_Emulador_SAT-CF-e_Offline_2015_09_10.pdf +0 -0
  9. data/docs/CFe-SAT/_CANCEL_dataset.md +14 -0
  10. data/docs/CFe-SAT/_SALE_dataset.md +171 -0
  11. data/docs/CFe-SAT/emulador_off_line_v2_9_4.zip +0 -0
  12. data/docs/NFe-NFCe/ANEXO I - Leiaute e Regra de Valida/303/247/303/243o - NF-e e NFC-e.pdf +0 -0
  13. data/docs/NFe-NFCe/ANEXO II -Manual Especifica/303/247/303/265esT/303/251cnicas - Danfe-C/303/263digo-Barras.pdf +0 -0
  14. data/docs/NFe-NFCe/Anexo III - Manual de Conting/303/252ncia - NF-e.pdf +0 -0
  15. data/docs/NFe-NFCe/Anexo IV - Manual de Conting/303/252ncia - NFC-e.pdf +0 -0
  16. 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
  17. data/docs/NFe-NFCe/_dataset.md +819 -0
  18. data/lib/sefaz/base.rb +11 -0
  19. data/lib/sefaz/configuration.rb +58 -0
  20. data/lib/sefaz/exception.rb +14 -0
  21. data/lib/sefaz/refinement.rb +91 -0
  22. data/lib/sefaz/utils/connection.rb +35 -0
  23. data/lib/sefaz/utils/prawn_helper.rb +47 -0
  24. data/lib/sefaz/utils/signer.rb +122 -0
  25. data/lib/sefaz/version.rb +1 -1
  26. data/lib/sefaz/webservice/base.rb +13 -0
  27. data/lib/sefaz/webservice/nfe/auditor.rb +35 -0
  28. data/lib/sefaz/webservice/nfe/client.rb +426 -0
  29. data/lib/sefaz/webservice/nfe/connection.rb +17 -0
  30. data/lib/sefaz/webservice/nfe/dataset.rb +451 -0
  31. data/lib/sefaz/webservice/nfe/validator.rb +63 -0
  32. data/lib/sefaz/webservice/nfe/wsdl.rb +117 -0
  33. data/lib/sefaz/webservice/sat/client.rb +44 -0
  34. data/lib/sefaz/webservice/sat/dataset/cancel.rb +34 -0
  35. data/lib/sefaz/webservice/sat/dataset/sale.rb +176 -0
  36. data/lib/sefaz/webservice/sat/templates/base.rb +54 -0
  37. data/lib/sefaz/webservice/sat/templates/cupom_fiscal_55mm.rb +307 -0
  38. data/lib/sefaz/webservice/sat/templates/cupom_fiscal_80mm.rb +307 -0
  39. data/lib/sefaz.rb +45 -3
  40. data/sefaz.gemspec +5 -3
  41. 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