sefaz 0.2.0 → 1.4.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/CHANGELOG.md +121 -0
  4. data/Gemfile.lock +77 -20
  5. data/README.md +449 -17
  6. data/Rakefile +1 -0
  7. data/docs/CFe-SAT/Especificacao_SAT_v_ER_2_30_03.pdf +0 -0
  8. data/docs/CFe-SAT/Manual_Orientacao_SAT_v_MO_2_19_04.pdf +0 -0
  9. data/docs/CFe-SAT/Manual_do_Emulador_SAT-CF-e_Offline_2015_09_10.pdf +0 -0
  10. data/docs/CFe-SAT/_CANCEL_dataset.md +14 -0
  11. data/docs/CFe-SAT/_SALE_dataset.md +171 -0
  12. data/docs/CFe-SAT/emulador_off_line_v2_9_4.zip +0 -0
  13. data/docs/NFe-NFCe/ANEXO I - Leiaute e Regra de Valida/303/247/303/243o - NF-e e NFC-e.pdf +0 -0
  14. data/docs/NFe-NFCe/ANEXO II -Manual Especifica/303/247/303/265esT/303/251cnicas - Danfe-C/303/263digo-Barras.pdf +0 -0
  15. data/docs/NFe-NFCe/Anexo III - Manual de Conting/303/252ncia - NF-e.pdf +0 -0
  16. data/docs/NFe-NFCe/Anexo IV - Manual de Conting/303/252ncia - NFC-e.pdf +0 -0
  17. 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
  18. data/docs/NFe-NFCe/_dataset.md +819 -0
  19. data/lib/sefaz/assets/NFe_logo.png +0 -0
  20. data/lib/sefaz/base.rb +11 -0
  21. data/lib/sefaz/configuration.rb +66 -0
  22. data/lib/sefaz/exception.rb +14 -0
  23. data/lib/sefaz/refinement.rb +91 -0
  24. data/lib/sefaz/utils/connection.rb +35 -0
  25. data/lib/sefaz/utils/prawn_helper.rb +49 -0
  26. data/lib/sefaz/utils/signer.rb +122 -0
  27. data/lib/sefaz/version.rb +1 -1
  28. data/lib/sefaz/webservice/base.rb +13 -0
  29. data/lib/sefaz/webservice/nfe/auditor.rb +35 -0
  30. data/lib/sefaz/webservice/nfe/client.rb +471 -0
  31. data/lib/sefaz/webservice/nfe/connection.rb +17 -0
  32. data/lib/sefaz/webservice/nfe/dataset.rb +451 -0
  33. data/lib/sefaz/webservice/nfe/templates/base.rb +32 -0
  34. data/lib/sefaz/webservice/nfe/templates/evento_cancelamento.rb +48 -0
  35. data/lib/sefaz/webservice/nfe/templates/evento_carta_correcao.rb +54 -0
  36. data/lib/sefaz/webservice/nfe/templates/evento_inutilizacao.rb +22 -0
  37. data/lib/sefaz/webservice/nfe/validator.rb +63 -0
  38. data/lib/sefaz/webservice/nfe/wsdl.rb +117 -0
  39. data/lib/sefaz/webservice/sat/client.rb +44 -0
  40. data/lib/sefaz/webservice/sat/dataset/cancel.rb +34 -0
  41. data/lib/sefaz/webservice/sat/dataset/sale.rb +176 -0
  42. data/lib/sefaz/webservice/sat/templates/base.rb +54 -0
  43. data/lib/sefaz/webservice/sat/templates/cupom_fiscal_55mm.rb +307 -0
  44. data/lib/sefaz/webservice/sat/templates/cupom_fiscal_80mm.rb +307 -0
  45. data/lib/sefaz.rb +51 -3
  46. data/sefaz.gemspec +6 -3
  47. metadata +103 -9
@@ -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
@@ -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 80mm em PDF
8
+ class CupomFiscal80mm < SEFAZ::Webservice::SAT::Templates::Base
9
+
10
+ def initialize
11
+ super
12
+ @page_width = Configuration.default.cfe_page_width_80mm
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 7
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.move_down doc.font_size
38
+ doc.text "CNPJ #{emit.dig(:CNPJ).to_s.mask!("##.###.###/####-##")} IE #{emit.dig(:IE).to_s.mask!("###.###.###.###")}".to_s.strip.upcase, align: :center
39
+ doc.text "IM #{emit.dig(:IM).to_s.mask!("####.###.###.###")}".to_s.strip.upcase, align: :center if emit.dig(:IM)
40
+ end
41
+
42
+ #
43
+ # CORPO DO EXTRATO (Aut)
44
+ #
45
+ # Exibe os dados de forma dinâmica:
46
+ # Quando CFe Movimento: exibe lista dos itens a partir do XML AUTORIZADO (Resumido ou Não Resumido).
47
+ # Quando CFe Cancelamento: exibe os dados do XML AUTORIZADO em modo cancelado.
48
+ #
49
+ def body(doc, aut, summary, chCanc)
50
+ tpAmb = aut.dig(:CFe, :infCFe, :ide, :tpAmb)
51
+ nCFe = (tpAmb == 1) ? aut.dig(:CFe, :infCFe, :ide, :nCFe) : "000000"
52
+
53
+ # (I) - Título
54
+ doc.move_down 1.mm
55
+ doc.text "Extrato #{nCFe} do CUPOM FISCAL ELETRÔNICO - SAT", style: :bold, align: :center
56
+
57
+ # (A) - Título - Cancelamento
58
+ if chCanc
59
+ doc.move_down 1.mm
60
+ doc.text "CANCELAMENTO", style: :bold, align: :center
61
+ end
62
+
63
+ # (TESTE) Marca de Teste
64
+ if tpAmb == "2"
65
+ doc.move_down doc.font_size
66
+
67
+ doc.text "= TESTE =", style: :italic, align: :center
68
+ doc.move_down doc.font_size
69
+ 3.times do
70
+ doc.text_box (">" * 100), at: [0, doc.cursor], height: doc.font_size, width: doc.bounds.width
71
+ doc.move_down doc.font_size
72
+ doc.move_down doc.font_size
73
+ end
74
+ end
75
+
76
+ # Exibe o extrato no modo cancelado caso exista Chave de Cancelamento, caso contrário exibe no modo movimento
77
+ if chCanc
78
+ body_canceled_mode(doc, aut)
79
+ else
80
+ body_movement_mode(doc, aut, summary)
81
+ end
82
+ end
83
+
84
+ #
85
+ # CORPO DO EXTRATO > MODO MOVIMENTO (Aut)
86
+ #
87
+ # Exibe lista dos itens.
88
+ #
89
+ def body_movement_mode(doc, aut, summary)
90
+ # (II) - Legenda
91
+ doc.text "#|COD|DESC|QTD|UN|VL UN R$|(VL TR R$)*|VL ITEM R$", size: 6.7
92
+ dash(doc, gap: 2, space: 0.5, line_width: 0.1, x1: 0, x2: doc.bounds.width, y: doc.cursor)
93
+
94
+ # (III) - Registros de item
95
+ unless summary
96
+ det = aut.dig(:CFe, :infCFe, :det)
97
+ det = det.is_a?(Array) ? det : [det]
98
+ det.each do |i|
99
+ # DADOS: #|COD|DESC|QTD|UN|VL UN R$|(VL TR R$)*
100
+ doc.bounding_box([0, doc.cursor], width: (doc.bounds.width - 13.mm)) do
101
+ 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})"
102
+ end
103
+
104
+ # DADOS: VL ITEM R$
105
+ doc.move_down -doc.font_size
106
+ doc.text i.dig(:prod, :vProd).to_f.to_currency, align: :right
107
+
108
+ # DADOS: DESCONTO
109
+ if i.dig(:prod, :vDesc).to_f > 0
110
+ doc.float { doc.text "-#{i.dig(:prod, :vDesc).to_f.to_currency}", align: :right }
111
+ doc.text "desconto sobre item"
112
+ end
113
+
114
+ # DADOS: ACRÉSCIMO
115
+ if i.dig(:prod, :vOutro).to_f > 0
116
+ doc.float { doc.text "+#{i.dig(:prod, :vOutro).to_f.to_currency}", align: :right }
117
+ doc.text "acréscimo sobre item"
118
+ end
119
+
120
+ # DADOS: RATEIO DESCONTO
121
+ if i.dig(:prod, :vRatDesc).to_f > 0
122
+ doc.float { doc.text "-#{i.dig(:prod, :vRatDesc).to_f.to_currency}", align: :right }
123
+ doc.text "rateio de desconto sobre subtotal"
124
+ end
125
+
126
+ # DADOS: RATEIO ACRÉSCIMO
127
+ if i.dig(:prod, :vRatAcr).to_f > 0
128
+ doc.float { doc.text "+#{i.dig(:prod, :vRatAcr).to_f.to_currency}", align: :right }
129
+ doc.text "rateio de acréscimo sobre subtotal"
130
+ end
131
+
132
+ # DADOS: ISSQN
133
+ if i.dig(:imposto, :ISSQN, :vDeducISSQN).to_f > 0
134
+ doc.float { doc.text "-#{i.dig(:imposto, :ISSQN, :vDeducISSQN).to_f.to_currency}", align: :right }
135
+ doc.text "dedução para ISSQN"
136
+ doc.float { doc.text i.dig(:imposto, :ISSQN, :vBC).to_f.to_currency, align: :right }
137
+ doc.text "base de cálculo ISSQN"
138
+ end
139
+ end
140
+ end
141
+
142
+ # (IV) - Total do Cupom
143
+ total = aut.dig(:CFe, :infCFe, :total)
144
+
145
+ # DADOS: VALOR BRUTO
146
+ 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)
147
+ doc.float { doc.text total.dig(:ICMSTot, :vProd).to_f.to_currency, align: :right }
148
+ doc.text "Total bruto de itens"
149
+ end
150
+
151
+ # DADOS: TOTAL DESC/ACRES SOBRE ITENS
152
+ if (total.dig(:ICMSTot, :vDesc).to_f > 0) || (total.dig(:ICMSTot, :vOutro).to_f > 0)
153
+ valor11 = total.dig(:ICMSTot, :vOutro).to_f - total.dig(:ICMSTot, :vDesc).to_f
154
+ doc.text "Total de descontos/acréscimos sobre item"
155
+ doc.text "#{ valor11 > 0 ? '+' : '-' }#{valor11.abs.to_currency}", align: :right
156
+ end
157
+
158
+ # DADOS: DESCONTO SOBRE TOTAL
159
+ if total.dig(:DescAcrEntr, :vDescSubtot).to_f > 0
160
+ doc.float { doc.text "-#{total.dig(:DescAcrEntr, :vDescSubtot).to_f.to_currency}", align: :right }
161
+ doc.text "Desconto sobre subtotal"
162
+ end
163
+
164
+ # DADOS: ACRÉSCIMO SOBRE TOTAL
165
+ if total.dig(:DescAcrEntr, :vAcresSubtot).to_f > 0
166
+ doc.float { doc.text "+#{total.dig(:DescAcrEntr, :vAcresSubtot).to_f.to_currency}", align: :right }
167
+ doc.text "Acréscimo sobre subtotal"
168
+ end
169
+
170
+ # DADOS: TOTAL FINAL
171
+ doc.float { doc.text total.dig(:vCFe).to_f.to_currency, style: :bold, align: :right }
172
+ doc.text "Total R$", style: :bold
173
+
174
+ # (V) - Meio de Pagamento
175
+ pgto = aut.dig(:CFe, :infCFe, :pgto)
176
+ mp = aut.dig(:CFe, :infCFe, :pgto, :MP)
177
+ mp = mp.is_a?(Array) ? mp : [mp]
178
+ mp.each do |i|
179
+ # DADOS: DESCRIÇÃO MEIO DE PAGAMENTO
180
+ doc.bounding_box([0, doc.cursor], width: (doc.bounds.width - 13.mm)) do
181
+ doc.text Configuration.default.cfe_cMP_cod_desc[i.dig(:cMP)]
182
+ end
183
+
184
+ # DADOS: VALOR PAGAMENTO
185
+ doc.move_down -doc.font_size
186
+ doc.text i.dig(:vMP).to_f.to_currency, align: :right
187
+ end
188
+
189
+ # DADOS: TROCO
190
+ doc.float { doc.text pgto.dig(:vTroco).to_f.to_currency, align: :right }
191
+ doc.text "Troco R$"
192
+
193
+ # (VI) - Observações do Fisco
194
+ obsFisco = aut.dig(:CFe, :infCFe, :obsFisco)
195
+ if obsFisco
196
+ obsFisco = obsFisco.is_a?(Array) ? obsFisco : [obsFisco]
197
+ obsFisco.each do |i|
198
+ doc.text "#{i.dig(:xCampo)}: #{i.dig(:xTexto)}"
199
+ end
200
+ end
201
+
202
+ # (VII) - Dados para Entrega
203
+ entrega = aut.dig(:CFe, :infCFe, :entrega)
204
+ 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
205
+
206
+ # (VIII) - Observações do Contribuinte
207
+ infCFe = aut.dig(:CFe, :infCFe)
208
+ doc.text "OBSERVAÇÕES DO CONTRIBUINTE"
209
+ doc.text infCFe.dig(:infCpl)
210
+ doc.text "Valor aproximado dos tributos deste cupom R$"
211
+ doc.text total.dig(:vCFeLei12741).to_f.to_currency, align: :right
212
+ doc.text "(conforme Lei Fed. 12.741/2012)"
213
+ doc.move_down doc.font_size
214
+ end
215
+
216
+ #
217
+ # CORPO DO EXTRATO > MODO CANCELADO (Aut)
218
+ #
219
+ # Exibe os dados do XML AUTORIZADO em modo cancelado.
220
+ # É renderizado o rodapé do XML AUTORIZADO no final do corpo do extrato.
221
+ #
222
+ def body_canceled_mode(doc, aut)
223
+ total = aut.dig(:CFe, :infCFe, :total)
224
+ infCFe = aut.dig(:CFe, :infCFe)
225
+
226
+ # (B) - Dados do Cupom Fiscal Cancelado
227
+ doc.move_down 1.mm
228
+ doc.text "DADOS DO CUPOM ELETRÔNICO CANCELADO", style: :bold
229
+ doc.text "TOTAL R$ #{total.dig(:vCFe).to_f.to_currency}"
230
+
231
+ # (RODAPÉ DE MOVIMENTO) Exibe o rodapé do Extrato de Movimento no corpo do Extrato de Cancelamento
232
+ footer(doc, infCFe, vl_aprox_trib: false)
233
+ end
234
+
235
+ #
236
+ # RODAPÉ (Aut/Canc)
237
+ #
238
+ # Exibe os elementos de rodapé (QR Code, código de barras, número da chave, mensagens fixas, etc).
239
+ # O método permite exibir o rodapé no modo movimento ou cancelado.
240
+ #
241
+ def footer(doc, infCFe, infAdic = nil, vl_aprox_trib: true)
242
+ id = infCFe.dig(:@Id)
243
+ chCanc = infCFe.dig(:@chCanc)
244
+ ide = infCFe.dig(:ide)
245
+ total = infCFe.dig(:total)
246
+
247
+ # Título do cancelamento
248
+ if chCanc
249
+ doc.move_down 2
250
+ dash(doc, gap: 2, space: 0.5, line_width: 0.1, x1: 0, x2: doc.bounds.width, y: doc.cursor)
251
+ doc.text "DADOS DO CUPOM FISCAL ELETRÔNICO DE CANCELAMENTO", align: :center, style: :bold, size: 6.8
252
+ end
253
+
254
+ # Número da chave do Cupom Fiscal (dividos em 11 blocos com 4 números cada)
255
+ doc.move_down 2
256
+ doc.text id.to_s.delete("^0-9").scan(/.{4}/).join(" "), align: :center, style: :bold
257
+ doc.move_down doc.font_size
258
+
259
+ # Código de barra CODE 128-C (divido em duas partes)
260
+ barcode_128c(doc, value: id.to_s.delete("^0-9"), x: 0, y: doc.cursor, xdim: 0.72)
261
+ doc.move_down 1.mm
262
+
263
+ # Informação do consumidor, SAT e mensagens fixas
264
+ doc.float do
265
+ doc.move_down 5.mm
266
+ doc.bounding_box([80, doc.cursor], width: 115) do
267
+ # Informação do consumidor (CNPJ/CPF e NOME)
268
+ if !chCanc && (infCFe.dig(:dest, :CNPJ) || infCFe.dig(:dest, :CPF))
269
+ info = "<b>Consumidor:</b> "
270
+ info = info + infCFe.dig(:dest, :CNPJ).to_s.mask!("##.###.###/####-##") if infCFe.dig(:dest, :CNPJ)
271
+ info = info + infCFe.dig(:dest, :CPF).to_s.mask!("###.###.###-##") if infCFe.dig(:dest, :CPF)
272
+ doc.text info, inline_format: true, size: 6
273
+ doc.text infCFe.dig(:dest, :xNome), size: 6 if infCFe.dig(:dest, :xNome)
274
+ end
275
+
276
+ # Informação do SAT (Nº Série SAT)
277
+ doc.text "<b>No. Serie do SAT:</b> #{ide.dig(:nserieSAT)}", inline_format: true, size: 6
278
+ doc.text DateTime.parse("#{ide.dig(:dEmi)}T#{ide.dig(:hEmi)}").strftime("%d/%m/%Y - %H:%M:%S"), size: 6
279
+ doc.move_down doc.font_size
280
+
281
+ # Mensagens fixas do Cupom Fiscal
282
+ if !chCanc
283
+ doc.text "Consulte o QR Code pelo aplicativo \"#{Configuration.default.cfe_qr_code_aplicativo}\", disponível na AppStore (Apple) e PlayStore (Android)", size: 6
284
+ doc.move_down doc.font_size
285
+ doc.text "*valor aproximado dos tributos do item", size: 5 if vl_aprox_trib
286
+ end
287
+ end
288
+ end
289
+
290
+ # QR Code do Cupom Fiscal = Id|(dEmi)(hEmi)|vCFe|(CNPJ/CPF)|assinaturaQRCODE
291
+ doc.move_down 2.mm
292
+ codigo_qr_code = "#{id}|#{ide.dig(:dEmi)}#{ide.dig(:hEmi)}|#{total.dig(:vCFe)}|#{(infCFe.dig(:dest, :CNPJ) || infCFe.dig(:dest, :CPF))}|#{ide.dig(:assinaturaQRCODE)}"
293
+ qrcode(doc, value: codigo_qr_code, x: 0, y: doc.cursor)
294
+ doc.move_down 3.mm
295
+
296
+ # Informações adicionais do Cupom Fiscal (customizado pelo contribuinte)
297
+ if infAdic
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, size: 6
300
+ end
301
+ end
302
+
303
+ end
304
+ end
305
+ end
306
+ end
307
+ end
data/lib/sefaz.rb CHANGED
@@ -1,10 +1,58 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
4
+ require 'net/http'
3
5
  require 'savon'
6
+ require 'prawn'
7
+ require 'prawn/measurement_extensions'
8
+ require 'barby'
9
+ require 'barby/barcode/code_128'
10
+ require 'barby/barcode/qr_code'
11
+ require 'barby/outputter/prawn_outputter'
12
+ require 'barby/outputter/png_outputter'
13
+ require 'br_danfe'
4
14
 
5
- require_relative "sefaz/version"
15
+ require 'sefaz/base'
16
+ require 'sefaz/configuration'
17
+ require 'sefaz/exception'
18
+ require 'sefaz/refinement'
19
+ require 'sefaz/version'
20
+
21
+ require 'sefaz/utils/connection'
22
+ require 'sefaz/utils/prawn_helper'
23
+ require 'sefaz/utils/signer'
24
+ require 'sefaz/webservice/base'
25
+
26
+ require 'sefaz/webservice/nfe/client'
27
+ require 'sefaz/webservice/nfe/templates/base'
28
+ require 'sefaz/webservice/nfe/templates/evento_cancelamento'
29
+ require 'sefaz/webservice/nfe/templates/evento_carta_correcao'
30
+ require 'sefaz/webservice/nfe/templates/evento_inutilizacao'
31
+ require 'sefaz/webservice/nfe/auditor'
32
+ require 'sefaz/webservice/nfe/connection'
33
+ require 'sefaz/webservice/nfe/dataset'
34
+ require 'sefaz/webservice/nfe/validator'
35
+ require 'sefaz/webservice/nfe/wsdl'
36
+
37
+ require 'sefaz/webservice/sat/client'
38
+ require 'sefaz/webservice/sat/templates/base'
39
+ require 'sefaz/webservice/sat/templates/cupom_fiscal_55mm'
40
+ require 'sefaz/webservice/sat/templates/cupom_fiscal_80mm'
41
+ require 'sefaz/webservice/sat/dataset/cancel'
42
+ require 'sefaz/webservice/sat/dataset/sale'
6
43
 
7
44
  module SEFAZ
8
- class Error < StandardError; end
9
- # Your code goes here...
45
+ # Personalize as configurações padrão da biblioteca usando bloco
46
+ # config/initializers/sefaz_config.rb
47
+ # SEFAZ.configure do |config|
48
+ # config.xxxxxxx = "xxxxxxx"
49
+ # end
50
+ # Se nenhum bloco for fornecido, retorna o objeto de configuração padrão
51
+ def self.configure
52
+ if block_given?
53
+ yield(Configuration.default)
54
+ else
55
+ Configuration.default
56
+ end
57
+ end
10
58
  end