br_danfe 0.11.2 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/Gemfile.lock +31 -31
  4. data/br_danfe.gemspec +6 -5
  5. data/config/locales/pt-BR.yml +8 -0
  6. data/lib/br_danfe/danfe.rb +2 -2
  7. data/lib/br_danfe/danfe_lib/det_body.rb +1 -1
  8. data/lib/br_danfe/danfe_lib/document.rb +1 -1
  9. data/lib/br_danfe/danfe_lib/emit_header.rb +2 -2
  10. data/lib/br_danfe/danfe_lib/helper.rb +1 -27
  11. data/lib/br_danfe/danfe_lib/infadic.rb +1 -1
  12. data/lib/br_danfe/danfe_lib/xprod.rb +15 -15
  13. data/lib/br_danfe/danfe_nfce.rb +43 -0
  14. data/lib/br_danfe/danfe_nfce_lib/document.rb +35 -0
  15. data/lib/br_danfe/danfe_nfce_lib/footer.rb +15 -0
  16. data/lib/br_danfe/danfe_nfce_lib/header.rb +67 -0
  17. data/lib/br_danfe/danfe_nfce_lib/helper.rb +9 -0
  18. data/lib/br_danfe/danfe_nfce_lib/key.rb +18 -0
  19. data/lib/br_danfe/danfe_nfce_lib/nfce_identification.rb +20 -0
  20. data/lib/br_danfe/danfe_nfce_lib/product_list.rb +100 -0
  21. data/lib/br_danfe/danfe_nfce_lib/qr_code.rb +23 -0
  22. data/lib/br_danfe/danfe_nfce_lib/recipient.rb +72 -0
  23. data/lib/br_danfe/danfe_nfce_lib/total_list.rb +78 -0
  24. data/lib/br_danfe/helper.rb +29 -0
  25. data/lib/br_danfe/{danfe_lib/options.rb → logo_config.rb} +2 -2
  26. data/lib/br_danfe/{danfe_lib/logo_options.rb → logo_options.rb} +2 -2
  27. data/lib/br_danfe/version.rb +1 -1
  28. data/lib/br_danfe/xml.rb +36 -0
  29. data/spec/features/danfe_nfce_spec.rb +31 -0
  30. data/spec/fixtures/nfce/lib/document#render.pdf +99 -0
  31. data/spec/fixtures/nfce/lib/footer#render.pdf +74 -0
  32. data/spec/fixtures/nfce/lib/header#render-homologation.pdf +0 -0
  33. data/spec/fixtures/nfce/lib/header#render-long_name_with_logo.pdf +0 -0
  34. data/spec/fixtures/nfce/lib/header#render-long_name_without_logo.pdf +118 -0
  35. data/spec/fixtures/nfce/lib/header#render-short_name_with_logo.pdf +0 -0
  36. data/spec/fixtures/nfce/lib/header#render-short_name_without_logo.pdf +118 -0
  37. data/spec/fixtures/nfce/lib/key#render.pdf +104 -0
  38. data/spec/fixtures/nfce/lib/nfce_identification#render.pdf +118 -0
  39. data/spec/fixtures/nfce/lib/product_list#render-with_many_products.pdf +307 -0
  40. data/spec/fixtures/nfce/lib/product_list#render-with_many_products_and_long_names.pdf +335 -0
  41. data/spec/fixtures/nfce/lib/product_list#render-with_single_product.pdf +181 -0
  42. data/spec/fixtures/nfce/lib/product_list#render-with_single_product_and_long_name.pdf +195 -0
  43. data/spec/fixtures/nfce/lib/product_list_with_many_products.xml +367 -0
  44. data/spec/fixtures/nfce/lib/product_list_with_many_products_and_long_names.xml +367 -0
  45. data/spec/fixtures/nfce/lib/product_list_with_single_product.xml +238 -0
  46. data/spec/fixtures/nfce/lib/product_list_with_single_product_and_long_name.xml +238 -0
  47. data/spec/fixtures/nfce/lib/qr_code#render.pdf +0 -0
  48. data/spec/fixtures/nfce/lib/recipient#render-company.pdf +95 -0
  49. data/spec/fixtures/nfce/lib/recipient#render-consumer_without_document.pdf +88 -0
  50. data/spec/fixtures/nfce/lib/recipient#render-foreign.pdf +95 -0
  51. data/spec/fixtures/nfce/lib/recipient#render-individual.pdf +95 -0
  52. data/spec/fixtures/nfce/lib/recipient#render-unidentified_consumer.pdf +81 -0
  53. data/spec/fixtures/nfce/lib/total_list#does_not_render-payment_methods.pdf +139 -0
  54. data/spec/fixtures/nfce/lib/total_list#render-grouped_payment_methods.pdf +188 -0
  55. data/spec/fixtures/nfce/lib/total_list#render-totals.pdf +139 -0
  56. data/spec/fixtures/nfce/lib/total_list#render-without_payment.pdf +202 -0
  57. data/spec/fixtures/nfce/lib/total_list#render.pdf +202 -0
  58. data/spec/fixtures/nfce/v4.00/nfce.xml +367 -0
  59. data/spec/fixtures/nfce/v4.00/rendered_nfce.fixture.pdf +0 -0
  60. data/spec/fixtures/nfce/v4.00/saved_nfce.fixture.pdf +0 -0
  61. data/spec/lib/danfe_lib/dest_spec.rb +1 -1
  62. data/spec/lib/danfe_lib/det_body_spec.rb +1 -1
  63. data/spec/lib/danfe_lib/document_spec.rb +1 -1
  64. data/spec/lib/danfe_lib/dup_spec.rb +1 -1
  65. data/spec/lib/danfe_lib/emit_header_spec.rb +1 -1
  66. data/spec/lib/danfe_lib/helper_spec.rb +0 -54
  67. data/spec/lib/danfe_lib/icmstot_spec.rb +1 -1
  68. data/spec/lib/danfe_lib/infadic_spec.rb +1 -1
  69. data/spec/lib/danfe_lib/infadic_vol_spec.rb +1 -1
  70. data/spec/lib/danfe_lib/issqn_spec.rb +1 -1
  71. data/spec/lib/danfe_lib/ticket_spec.rb +1 -1
  72. data/spec/lib/danfe_lib/transp_spec.rb +1 -1
  73. data/spec/lib/danfe_lib/vol_spec.rb +1 -1
  74. data/spec/lib/danfe_nfce_lib/document_spec.rb +19 -0
  75. data/spec/lib/danfe_nfce_lib/footer_spec.rb +42 -0
  76. data/spec/lib/danfe_nfce_lib/header_spec.rb +117 -0
  77. data/spec/lib/danfe_nfce_lib/helper_spec.rb +28 -0
  78. data/spec/lib/danfe_nfce_lib/key_spec.rb +45 -0
  79. data/spec/lib/danfe_nfce_lib/nfce_identification_spec.rb +48 -0
  80. data/spec/lib/danfe_nfce_lib/product_list_spec.rb +66 -0
  81. data/spec/lib/danfe_nfce_lib/qr_code_spec.rb +35 -0
  82. data/spec/lib/danfe_nfce_lib/recipient_spec.rb +163 -0
  83. data/spec/lib/danfe_nfce_lib/total_list_spec.rb +188 -0
  84. data/spec/lib/helper_spec.rb +102 -0
  85. data/spec/lib/logo_config_spec.rb +21 -0
  86. data/spec/lib/{danfe_lib/logo_options_spec.rb → logo_options_spec.rb} +8 -22
  87. data/spec/lib/{danfe_lib/xml_spec.rb → xml_spec.rb} +2 -2
  88. metadata +87 -18
  89. data/lib/br_danfe/danfe_lib/xml.rb +0 -38
  90. data/spec/lib/danfe_lib/options_spec.rb +0 -17
@@ -0,0 +1,9 @@
1
+ module BrDanfe
2
+ module DanfeNfceLib
3
+ class Helper
4
+ def self.address(ender_tag)
5
+ "#{ender_tag.css('xLgr').text}, #{ender_tag.css('nro').text}, #{ender_tag.css('xBairro').text}, #{ender_tag.css('xMun').text} - #{ender_tag.css('UF').text}"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module BrDanfe
2
+ module DanfeNfceLib
3
+ class Key
4
+ def initialize(pdf, xml)
5
+ @pdf = pdf
6
+ @xml = xml
7
+ end
8
+
9
+ def render
10
+ @pdf.render_blank_line
11
+
12
+ @pdf.text 'Consulte pela Chave de Acesso em', size: 7, align: :center, style: :bold
13
+ @pdf.text @xml['urlChave'], size: 7, align: :center
14
+ @pdf.text @xml['chNFe'].gsub(/(\d)(?=(\d\d\d\d)+(?!\d))/, '\\1 '), size: 6, align: :center
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ module BrDanfe
2
+ module DanfeNfceLib
3
+ class NfceIdentification
4
+ def initialize(pdf, xml)
5
+ @pdf = pdf
6
+ @xml = xml
7
+ end
8
+
9
+ def render
10
+ emitted_at = BrDanfe::Helper.format_datetime(@xml['ide/dhEmi'])
11
+ identification = "NFC-e nº #{@xml['ide/nNF']} Série #{@xml['ide/serie']} #{emitted_at}"
12
+
13
+ @pdf.render_blank_line
14
+ @pdf.text identification, size: 7, align: :center, style: :bold
15
+ @pdf.text "<b>Protocolo de autorização:</b> #{@xml['infProt/nProt']}", size: 7, align: :center, inline_format: true
16
+ @pdf.text "<b>Data de autorização: </b> #{BrDanfe::Helper.format_datetime(@xml['infProt/dhRecbto'])}", size: 7, align: :center, inline_format: true
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,100 @@
1
+ module BrDanfe
2
+ module DanfeNfceLib
3
+ class ProductList
4
+ def initialize(pdf, xml)
5
+ @pdf = pdf
6
+ @xml = xml
7
+ end
8
+
9
+ def render
10
+ render_headers(headers)
11
+ render_products(products)
12
+ end
13
+
14
+ private
15
+
16
+ def headers
17
+ [
18
+ header_column('Código', :left),
19
+ header_column('Descrição', :left),
20
+ header_column('Qtde', :right),
21
+ header_column('UN', :right),
22
+ header_column('Vl Unit', :right),
23
+ header_column('Vl Total', :right)
24
+ ]
25
+ end
26
+
27
+ def header_column(title, align)
28
+ { content: title, options: { align: align, size: 6, style: :bold } }
29
+ end
30
+
31
+ def products
32
+ @xml.collect('xmlns', 'det') { |det| product(det) }
33
+ end
34
+
35
+ def product(det)
36
+ [
37
+ cell_text(det.css('prod/cProd').text),
38
+ cell_text(det.css('prod/xProd').text),
39
+ cell_number(BrDanfe::Helper.numerify(det.css('prod/qCom').text)),
40
+ cell_text(det.css('prod/uCom').text, { align: :right }),
41
+ cell_number(BrDanfe::Helper.numerify(det.css('prod/vUnCom').text)),
42
+ cell_number(BrDanfe::Helper.numerify(det.css('prod/vProd').text))
43
+ ]
44
+ end
45
+
46
+ def cell_text(text, options = {})
47
+ cell = { content: text, options: { border_width: 0, size: 6 } }
48
+ cell[:options].merge!(options)
49
+ cell
50
+ end
51
+
52
+ def cell_number(text)
53
+ cell_text(text, { align: :right })
54
+ end
55
+
56
+ def columns
57
+ [
58
+ { width: 0.9.cm, position: 0 },
59
+ { width: 2.6.cm, position: 0.9.cm },
60
+ { width: 1.1.cm, position: 3.5.cm },
61
+ { width: 0.4.cm, position: 4.6.cm },
62
+ { width: 1.2.cm, position: 5.cm },
63
+ { width: 1.2.cm, position: 6.2.cm }
64
+ ]
65
+ end
66
+
67
+ def render_headers(headers)
68
+ 2.times { @pdf.render_blank_line }
69
+ cursor = @pdf.cursor
70
+ headers.each_with_index do |header, index|
71
+ @pdf.bounding_box [columns[index][:position], cursor], width: columns[index][:width], height: 0.2.cm do
72
+ @pdf.text header[:content], header[:options]
73
+ end
74
+ end
75
+ end
76
+
77
+ def render_products(products)
78
+ @pdf.render_blank_line
79
+ index_of_product_name = 1
80
+ products.each do |product|
81
+ box_height = box_height(product[index_of_product_name][:content])
82
+ cursor = @pdf.cursor
83
+
84
+ product.each_with_index do |product, index|
85
+ @pdf.bounding_box [columns[index][:position], cursor], width: columns[index][:width], height: box_height do
86
+ @pdf.text product[:content], product[:options]
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ def box_height(content)
93
+ line_height_base = 0.23.cm
94
+
95
+ lines = content.scan(/[\s\S]{1,20}( |$)/).length
96
+ line_height_base * lines
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,23 @@
1
+ module BrDanfe
2
+ module DanfeNfceLib
3
+ class QrCode
4
+ require 'rqrcode'
5
+ require 'chunky_png'
6
+ require 'tempfile'
7
+
8
+ def initialize(pdf, xml)
9
+ @pdf = pdf
10
+ @xml = xml
11
+ end
12
+
13
+ def render
14
+ qrcode = RQRCode::QRCode.new(@xml['qrCode'])
15
+ image = Tempfile.create('qrcode.png')
16
+ image.write(qrcode.as_png(module_px_size: 12).to_s)
17
+
18
+ box_size = 3.cm
19
+ @pdf.image image, { width: box_size, height: box_size, position: :center }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,72 @@
1
+ module BrDanfe
2
+ module DanfeNfceLib
3
+ class Recipient
4
+ def initialize(pdf, xml)
5
+ @pdf = pdf
6
+ @xml = xml
7
+ end
8
+
9
+ def render
10
+ @pdf.render_blank_line
11
+
12
+ if identified_recipient?
13
+ render_document
14
+
15
+ @pdf.text @xml['dest/xNome'], options
16
+ @pdf.text BrDanfe::DanfeNfceLib::Helper.address(@xml.css('enderDest')), options
17
+ else
18
+ @pdf.text 'CONSUMIDOR NÃO IDENTIFICADO', options
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def options
25
+ { size: 7, align: :center }
26
+ end
27
+
28
+ def identified_recipient?
29
+ @xml['dest/xNome'].present?
30
+ end
31
+
32
+ def render_document
33
+ document_text = document
34
+
35
+ @pdf.text document_text, options if document_text.present?
36
+ end
37
+
38
+ def document
39
+ return company if company?
40
+ return individual if individual?
41
+ return foreign if foreign?
42
+ return ''
43
+ end
44
+
45
+ def company?
46
+ @xml['dest/CNPJ'].present?
47
+ end
48
+
49
+ def company
50
+ cnpj = BrDocuments::CnpjCpf::Cnpj.new @xml['dest/CNPJ']
51
+ "CONSUMIDOR CNPJ: #{cnpj.formatted}"
52
+ end
53
+
54
+ def individual?
55
+ @xml['dest/CPF'].present?
56
+ end
57
+
58
+ def individual
59
+ cpf = BrDocuments::CnpjCpf::Cpf.new(@xml['dest/CPF'])
60
+ "CONSUMIDOR CPF: #{cpf.formatted}"
61
+ end
62
+
63
+ def foreign?
64
+ @xml['dest/idEstrangeiro'].present?
65
+ end
66
+
67
+ def foreign
68
+ "CONSUMIDOR Id. Estrangeiro: #{@xml['dest/idEstrangeiro']}"
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,78 @@
1
+ module BrDanfe
2
+ module DanfeNfceLib
3
+ class TotalList
4
+ require 'bigdecimal'
5
+
6
+ def initialize(pdf, xml)
7
+ @pdf = pdf
8
+ @xml = xml
9
+ end
10
+
11
+ def render
12
+ subtotal
13
+ totals
14
+ payment_methods
15
+ end
16
+
17
+ private
18
+
19
+ def subtotal
20
+ cursor = @pdf.cursor
21
+ @pdf.bounding_box [4.8.cm, cursor], width: 1.4.cm, height: 0.2.cm do
22
+ @pdf.text 'Subtotal R$', { size: 6, align: :left }
23
+ end
24
+ @pdf.bounding_box [6.2.cm, cursor], width: 1.2.cm, height: 0.2.cm do
25
+ @pdf.text BrDanfe::Helper.numerify(@xml['ICMSTot > vProd'].to_f), { size: 6, align: :right }
26
+ end
27
+ end
28
+
29
+ def totals
30
+ @pdf.render_blank_line
31
+
32
+ cursor = @pdf.cursor
33
+ print_text('Qtde. total de itens', cursor, { size: 7, align: :left })
34
+ print_text(@xml.css('det').count.to_s, cursor, { size: 7, align: :right })
35
+
36
+ cursor = @pdf.cursor
37
+ print_text('Desconto R$', cursor, { size: 7, align: :left})
38
+ print_text(BrDanfe::Helper.numerify(@xml['ICMSTot > vDesc'].to_f), cursor, { size: 7, align: :right })
39
+
40
+ cursor = @pdf.cursor
41
+ print_text('Valor Total R$', cursor, { size: 7, align: :left, style: :bold })
42
+ print_text(BrDanfe::Helper.numerify(@xml['ICMSTot > vNF'].to_f), cursor, { size: 7, align: :right, style: :bold })
43
+ end
44
+
45
+ def payment_methods
46
+ payments = {}
47
+ without_payment = '90'
48
+
49
+ @xml.css('detPag').each do |detPag|
50
+ next if detPag.css('tPag').text == without_payment
51
+ payments[detPag.css('tPag').text] ||= BigDecimal('0')
52
+ actual_payment_value = BigDecimal(detPag.css('vPag').text)
53
+ payments[detPag.css('tPag').text] += actual_payment_value
54
+ end
55
+
56
+ if payments.present?
57
+ @pdf.render_blank_line
58
+
59
+ cursor = @pdf.cursor
60
+ print_text('Forma de pagamento', cursor, { size: 7, align: :left, style: :bold })
61
+ print_text('Valor pago R$', cursor, { size: 7, align: :right, style: :bold })
62
+
63
+ payments.each do |key, value|
64
+ cursor = @pdf.cursor
65
+ print_text(I18n.t("nfce.payment_methods.#{key}"), cursor, { size: 7, align: :left })
66
+ print_text(BrDanfe::Helper.numerify(value.to_f), cursor, { size: 7, align: :right })
67
+ end
68
+ end
69
+ end
70
+
71
+ def print_text(text, cursor, options)
72
+ @pdf.bounding_box [0, cursor], width: 7.4.cm, height: 0.25.cm do
73
+ @pdf.text text, options
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,29 @@
1
+ module BrDanfe
2
+ class Helper
3
+ def self.homologation?(xml)
4
+ xml.css('nfeProc/NFe/infNFe/ide/tpAmb').text == '2'
5
+ end
6
+
7
+ def self.numerify(number)
8
+ return '' if !number || number == ''
9
+
10
+ separated_number = number.to_s.split('.')
11
+ integer_part = separated_number[0].reverse.gsub(/\d{3}(?=\d)/, '\&.').reverse
12
+ decimal_part = separated_number[1] || '00'
13
+ decimal_part += '0' if decimal_part.size < 2
14
+
15
+ integer_part + ',' + decimal_part
16
+ end
17
+
18
+ def self.format_datetime(xml_datetime)
19
+ formated = ''
20
+
21
+ unless xml_datetime.empty?
22
+ date = DateTime.strptime(xml_datetime, '%Y-%m-%dT%H:%M:%S')
23
+ formated = date.strftime('%d/%m/%Y %H:%M:%S')
24
+ end
25
+
26
+ formated
27
+ end
28
+ end
29
+ end
@@ -1,6 +1,6 @@
1
1
  module BrDanfe
2
- module DanfeLib
3
- class Options < OpenStruct
2
+ module Logo
3
+ class Config < OpenStruct
4
4
  DEFAULTOPTIONS = { logo: '', logo_dimensions: {} }.freeze
5
5
 
6
6
  def initialize(new_options = {})
@@ -1,6 +1,6 @@
1
1
  module BrDanfe
2
- module DanfeLib
3
- class LogoOptions
2
+ module Logo
3
+ class Options
4
4
  def initialize(bounding_box_size, logo_dimensions)
5
5
  @bounding_box_size = bounding_box_size
6
6
  @logo_width = logo_dimensions[:width]
@@ -1,3 +1,3 @@
1
1
  module BrDanfe
2
- VERSION = '0.11.2'.freeze
2
+ VERSION = '0.12.0'.freeze
3
3
  end
@@ -0,0 +1,36 @@
1
+ module BrDanfe
2
+ class XML
3
+ def css(xpath)
4
+ @xml.css(xpath)
5
+ end
6
+
7
+ def initialize(xml)
8
+ @xml = Nokogiri::XML(xml)
9
+ end
10
+
11
+ def [](xpath)
12
+ node = @xml.css(xpath)
13
+ node ? node.text : ''
14
+ end
15
+
16
+ def collect(ns, tag)
17
+ result = []
18
+ # With namespace
19
+ begin
20
+ @xml.xpath("//#{ns}:#{tag}").each do |det|
21
+ result << yield(det)
22
+ end
23
+ rescue StandardError
24
+ # Without namespace
25
+ @xml.xpath("//#{tag}").each do |det|
26
+ result << yield(det)
27
+ end
28
+ end
29
+ result
30
+ end
31
+
32
+ def version_is_310_or_newer?
33
+ @xml.css('infNFe').attr('versao').to_s.to_f >= 3.10
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe BrDanfe::DanfeNfce do
4
+ let(:output_pdf) { "#{base_dir}output.pdf" }
5
+ let(:base_dir) { './spec/fixtures/nfce/v4.00/' }
6
+ let(:danfe) { BrDanfe::DanfeNfce.new(File.read("#{base_dir}nfce.xml")) }
7
+
8
+ before do
9
+ danfe.options.logo = 'spec/fixtures/logo.png'
10
+ danfe.options.logo_dimensions = { width: 100, height: 100 }
11
+ end
12
+
13
+ describe '#render_pdf' do
14
+ it 'renders the NFC-e pdf' do
15
+ expected = IO.binread("#{base_dir}rendered_nfce.fixture.pdf")
16
+ expect(danfe.render_pdf).to eq expected
17
+ end
18
+ end
19
+
20
+ describe '#save_pdf' do
21
+ before { File.delete(output_pdf) if File.exist?(output_pdf) }
22
+ after { File.delete(output_pdf) if File.exist?(output_pdf) }
23
+
24
+ it 'saves the NFC-e as pdf' do
25
+ expect(File.exist?(output_pdf)).to be_falsey
26
+ danfe.save_pdf output_pdf
27
+
28
+ expect("#{base_dir}saved_nfce.fixture.pdf").to have_same_content_of file: output_pdf
29
+ end
30
+ end
31
+ end