sefaz 0.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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